1013 lines
26 KiB
Svelte
1013 lines
26 KiB
Svelte
<script lang="ts">
|
||
import { createEventDispatcher } from "svelte";
|
||
import type {
|
||
ConnectionState,
|
||
HudConfigLink,
|
||
HudNoticeTone,
|
||
LocaleCode,
|
||
WindowControlAction
|
||
} from "$lib/types/hud";
|
||
|
||
export let appName = "";
|
||
export let suiteName = "";
|
||
export let controlAreaLabel = "";
|
||
export let locale: LocaleCode = "zh-CN";
|
||
export let connectionState: ConnectionState = "offline";
|
||
export let connectionLabel = "";
|
||
export let connectedLabel = "";
|
||
export let connectingLabel = "";
|
||
export let disconnectedLabel = "";
|
||
export let deviceLabel = "";
|
||
export let deviceValue = "";
|
||
export let sampleRateLabel = "";
|
||
export let sampleRateValue = "";
|
||
export let channelsLabel = "";
|
||
export let channelsValue = "";
|
||
export let serialPortLabel = "";
|
||
export let serialPortValue = "";
|
||
export let serialPortOptions: string[] = [];
|
||
export let refreshPortsLabel = "";
|
||
export let configLinksLabel = "";
|
||
export let configLinks: HudConfigLink[] = [];
|
||
export let connectActionLabel = "";
|
||
export let disconnectActionLabel = "";
|
||
export let exportActionLabel = "";
|
||
export let exportingActionLabel = "";
|
||
export let importActionLabel = "";
|
||
export let connectionNotice = "";
|
||
export let connectionNoticeTone: HudNoticeTone = "info";
|
||
export let isRefreshingPorts = false;
|
||
export let isConnectDisabled = false;
|
||
export let isExporting = false;
|
||
export let isExportDisabled = false;
|
||
export let isWindowMaximized = false;
|
||
|
||
const dispatch = createEventDispatcher<{
|
||
windowcontrol: WindowControlAction;
|
||
localechange: LocaleCode;
|
||
configlink: string;
|
||
portchange: string;
|
||
serialrefresh: void;
|
||
serialconnect: string;
|
||
serialexport: void;
|
||
csvimport: void;
|
||
noticeclear: void;
|
||
}>();
|
||
|
||
const connectionToneByState: Record<ConnectionState, "ok" | "warn" | "idle"> = {
|
||
online: "ok",
|
||
connecting: "warn",
|
||
offline: "idle"
|
||
};
|
||
|
||
$: connectionTone = connectionToneByState[connectionState];
|
||
$: connectionText =
|
||
connectionState === "online"
|
||
? connectedLabel
|
||
: connectionState === "connecting"
|
||
? connectingLabel
|
||
: disconnectedLabel;
|
||
$: connectButtonText =
|
||
connectionState === "online"
|
||
? disconnectActionLabel
|
||
: connectionState === "connecting"
|
||
? connectingLabel
|
||
: connectActionLabel;
|
||
$: exportButtonText = isExporting ? exportingActionLabel || exportActionLabel : exportActionLabel;
|
||
$: resolvedSerialPortOptions =
|
||
serialPortOptions.length > 0 ? serialPortOptions : serialPortValue ? [serialPortValue] : [];
|
||
|
||
function emitWindowControl(action: WindowControlAction): void {
|
||
dispatch("windowcontrol", action);
|
||
}
|
||
|
||
function switchLocale(nextLocale: LocaleCode): void {
|
||
dispatch("localechange", nextLocale);
|
||
}
|
||
|
||
function emitConfigLink(linkId: string): void {
|
||
dispatch("configlink", linkId);
|
||
}
|
||
|
||
function emitPortChange(event: Event): void {
|
||
const target = event.currentTarget as HTMLSelectElement;
|
||
dispatch("portchange", target.value);
|
||
}
|
||
|
||
function emitSerialConnect(): void {
|
||
dispatch("serialconnect", serialPortValue);
|
||
}
|
||
|
||
function emitSerialRefresh(): void {
|
||
dispatch("serialrefresh");
|
||
}
|
||
|
||
function emitSerialExport(): void {
|
||
dispatch("serialexport");
|
||
}
|
||
|
||
function emitCsvImport(): void {
|
||
dispatch("csvimport");
|
||
}
|
||
|
||
function emitNoticeClear(): void {
|
||
dispatch("noticeclear");
|
||
}
|
||
</script>
|
||
|
||
<section class="hud-panel" aria-label={controlAreaLabel}>
|
||
<div class="title-bar" role="group" aria-label="Title Bar" data-tauri-drag-region>
|
||
<div class="title-cluster" data-tauri-drag-region>
|
||
<span class="title-pulse" aria-hidden="true"></span>
|
||
<strong class="app-name">{appName}</strong>
|
||
<span class="suite-tag">{suiteName}</span>
|
||
</div>
|
||
|
||
<div class="window-controls" aria-label="Window Controls">
|
||
<button
|
||
type="button"
|
||
class="window-btn"
|
||
on:click={() => emitWindowControl("minimize")}
|
||
aria-label="Minimize window"
|
||
>
|
||
<svg viewBox="0 0 12 12" aria-hidden="true">
|
||
<path d="M2 6h8"></path>
|
||
</svg>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="window-btn"
|
||
class:is-maximized={isWindowMaximized}
|
||
on:click={() => emitWindowControl("toggle-maximize")}
|
||
aria-label="Toggle maximize window"
|
||
>
|
||
<svg viewBox="0 0 12 12" aria-hidden="true">
|
||
<path d="M2.6 2.6h6.8v6.8h-6.8z"></path>
|
||
</svg>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="window-btn is-close"
|
||
on:click={() => emitWindowControl("close")}
|
||
aria-label="Close window"
|
||
>
|
||
<svg viewBox="0 0 12 12" aria-hidden="true">
|
||
<path d="M2.6 2.6l6.8 6.8"></path>
|
||
<path d="M9.4 2.6l-6.8 6.8"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-bar">
|
||
<div class="control-main-row">
|
||
<section class="config-links" aria-label={configLinksLabel}>
|
||
{#each configLinks as link (link.id)}
|
||
<button
|
||
type="button"
|
||
class="config-link tone-{link.tone ?? 'neutral'}"
|
||
class:is-active={Boolean(link.active)}
|
||
on:click={() => emitConfigLink(link.id)}
|
||
>
|
||
<span class="config-indicator" aria-hidden="true"></span>
|
||
<span class="config-label">{link.label}</span>
|
||
</button>
|
||
{/each}
|
||
</section>
|
||
|
||
<section class="state-card" aria-label={connectionLabel}>
|
||
<span class="state-dot" class:ok={connectionTone === "ok"} class:warn={connectionTone === "warn"}></span>
|
||
<span class="state-label">{connectionLabel}</span>
|
||
<strong class="state-value">{connectionText}</strong>
|
||
</section>
|
||
|
||
<label class="serial-select" aria-label={serialPortLabel}>
|
||
<span class="serial-tag">{serialPortLabel}</span>
|
||
<span class="serial-select-wrap">
|
||
<select
|
||
class="serial-select-input"
|
||
value={serialPortValue}
|
||
disabled={connectionState !== "offline" || isRefreshingPorts}
|
||
on:change={emitPortChange}
|
||
>
|
||
{#each resolvedSerialPortOptions as option}
|
||
<option value={option}>{option}</option>
|
||
{/each}
|
||
</select>
|
||
<span class="serial-select-caret" aria-hidden="true"></span>
|
||
</span>
|
||
</label>
|
||
|
||
<button
|
||
type="button"
|
||
class="refresh-btn"
|
||
disabled={isRefreshingPorts || connectionState !== "offline"}
|
||
on:click={emitSerialRefresh}
|
||
>
|
||
<svg viewBox="0 0 16 16" aria-hidden="true">
|
||
<path d="M13 4.8V1.8"></path>
|
||
<path d="M13 1.8H10"></path>
|
||
<path d="M13 1.8a5.7 5.7 0 0 0-9.7 3.4"></path>
|
||
<path d="M3 11.2v3"></path>
|
||
<path d="M3 14.2h3"></path>
|
||
<path d="M3 14.2a5.7 5.7 0 0 0 9.7-3.4"></path>
|
||
</svg>
|
||
<span>{isRefreshingPorts ? `${refreshPortsLabel}...` : refreshPortsLabel}</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
class="connect-btn"
|
||
class:is-busy={connectionState === "connecting"}
|
||
class:is-connected={connectionState === "online"}
|
||
disabled={isConnectDisabled}
|
||
on:click={emitSerialConnect}
|
||
>
|
||
<span class="connect-btn-indicator" aria-hidden="true"></span>
|
||
<span>{connectButtonText}</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
class="export-btn"
|
||
disabled={isExportDisabled || isExporting}
|
||
on:click={emitSerialExport}
|
||
>
|
||
<svg viewBox="0 0 16 16" aria-hidden="true">
|
||
<path d="M8 2.2v7.2"></path>
|
||
<path d="M5.4 7.8L8 10.5l2.6-2.7"></path>
|
||
<path d="M3.1 12.3h9.8"></path>
|
||
</svg>
|
||
<span>{exportButtonText}</span>
|
||
</button>
|
||
|
||
<button type="button" class="import-btn" on:click={emitCsvImport}>
|
||
<svg viewBox="0 0 16 16" aria-hidden="true">
|
||
<path d="M8 10.8V3.6"></path>
|
||
<path d="M5.4 6.3L8 3.6l2.6 2.7"></path>
|
||
<path d="M3.1 12.6h9.8"></path>
|
||
</svg>
|
||
<span>{importActionLabel}</span>
|
||
</button>
|
||
|
||
<section class="locale-switch" aria-label="Language">
|
||
<button
|
||
type="button"
|
||
class="locale-btn"
|
||
class:is-active={locale === "zh-CN"}
|
||
on:click={() => switchLocale("zh-CN")}
|
||
>
|
||
中文
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="locale-btn"
|
||
class:is-active={locale === "en-US"}
|
||
on:click={() => switchLocale("en-US")}
|
||
>
|
||
English
|
||
</button>
|
||
</section>
|
||
</div>
|
||
|
||
{#if connectionNotice}
|
||
<div class="connection-notice tone-{connectionNoticeTone}" role={connectionNoticeTone === "warn" ? "alert" : "status"}>
|
||
<p class="connection-notice-text">{connectionNotice}</p>
|
||
<button
|
||
type="button"
|
||
class="notice-close-btn"
|
||
aria-label={locale === "zh-CN" ? "关闭提示" : "Dismiss notice"}
|
||
on:click={emitNoticeClear}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
{/if}
|
||
|
||
<section class="info-grid">
|
||
<article class="info-cell">
|
||
<p class="meta-label">{deviceLabel}</p>
|
||
<p class="meta-value">{deviceValue}</p>
|
||
</article>
|
||
<article class="info-cell">
|
||
<p class="meta-label">{sampleRateLabel}</p>
|
||
<p class="meta-value">{sampleRateValue}</p>
|
||
</article>
|
||
<article class="info-cell">
|
||
<p class="meta-label">{channelsLabel}</p>
|
||
<p class="meta-value">{channelsValue}</p>
|
||
</article>
|
||
</section>
|
||
</div>
|
||
</section>
|
||
|
||
<style>
|
||
.hud-panel {
|
||
display: grid;
|
||
grid-template-rows: auto auto;
|
||
gap: clamp(0.5rem, 1.2vw, 0.85rem);
|
||
--panel-line: rgb(var(--hud-border-rgb) / 0.34);
|
||
--panel-line-soft: rgb(var(--hud-border-rgb) / 0.22);
|
||
--panel-line-strong: rgb(var(--hud-border-strong-rgb) / 0.42);
|
||
--panel-surface: rgb(var(--hud-surface-rgb) / 0.7);
|
||
--panel-surface-strong: rgb(var(--hud-surface-alt-rgb) / 0.84);
|
||
--panel-surface-deep: rgb(var(--hud-surface-deep-rgb) / 0.9);
|
||
--panel-text: rgb(var(--hud-text-main-rgb) / 0.96);
|
||
--panel-text-dim: rgb(var(--hud-text-dim-rgb) / 0.84);
|
||
--panel-glow: rgb(var(--hud-glow-rgb) / 0.12);
|
||
--panel-glow-alt: rgb(var(--hud-glow-alt-rgb) / 0.12);
|
||
}
|
||
|
||
.title-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid var(--panel-line-soft);
|
||
padding: 0.05rem 0.1rem 0.55rem 0.1rem;
|
||
background: linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.32), transparent);
|
||
}
|
||
|
||
.title-cluster {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.55rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.title-pulse {
|
||
inline-size: 0.52rem;
|
||
block-size: 0.52rem;
|
||
border-radius: 50%;
|
||
background: rgb(var(--hud-lime-rgb) / 0.95);
|
||
box-shadow: 0 0 0 2px rgb(var(--hud-lime-rgb) / 0.14);
|
||
}
|
||
|
||
.app-name {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.07em;
|
||
color: rgb(var(--hud-text-main-rgb) / 0.98);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.suite-tag {
|
||
font-size: 0.72rem;
|
||
color: var(--hud-text-dim);
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.window-controls {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.38rem;
|
||
}
|
||
|
||
.window-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
inline-size: 1.8rem;
|
||
block-size: 1.52rem;
|
||
border: 1px solid var(--panel-line);
|
||
border-radius: 0.34rem;
|
||
color: rgb(var(--hud-cyan-rgb) / 0.92);
|
||
background: rgb(var(--hud-surface-rgb) / 0.82);
|
||
cursor: pointer;
|
||
transition:
|
||
background-color 200ms ease,
|
||
border-color 200ms ease,
|
||
color 200ms ease;
|
||
}
|
||
|
||
.window-btn svg {
|
||
inline-size: 0.8rem;
|
||
block-size: 0.8rem;
|
||
stroke: currentColor;
|
||
stroke-width: 1.4;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
|
||
.window-btn:hover {
|
||
border-color: rgb(var(--hud-cyan-rgb) / 0.42);
|
||
background: rgb(var(--hud-surface-alt-rgb) / 0.9);
|
||
color: rgb(var(--hud-text-main-rgb) / 1);
|
||
}
|
||
|
||
.window-btn.is-maximized {
|
||
border-color: rgb(var(--hud-lime-rgb) / 0.5);
|
||
color: rgb(var(--hud-lime-rgb) / 0.92);
|
||
}
|
||
|
||
.window-btn.is-close:hover {
|
||
border-color: rgb(var(--hud-orange-rgb) / 0.62);
|
||
background: rgb(var(--hud-surface-deep-rgb) / 0.92);
|
||
color: rgb(var(--hud-orange-rgb) / 0.96);
|
||
}
|
||
|
||
.control-bar {
|
||
display: grid;
|
||
gap: 0.45rem;
|
||
padding: 0 0.1rem;
|
||
background: linear-gradient(90deg, rgb(var(--hud-glow-rgb) / 0.02), transparent 45%, rgb(var(--hud-glow-alt-rgb) / 0.015));
|
||
}
|
||
|
||
.control-main-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.58rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.state-card {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.42rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid var(--panel-line);
|
||
border-radius: 999px;
|
||
padding: 0.2rem 0.62rem 0.2rem 0.36rem;
|
||
background: var(--panel-surface);
|
||
}
|
||
|
||
.state-dot {
|
||
inline-size: 0.55rem;
|
||
block-size: 0.55rem;
|
||
border-radius: 50%;
|
||
background: rgb(var(--hud-text-dim-rgb) / 0.92);
|
||
box-shadow: 0 0 0 2px rgb(var(--hud-text-dim-rgb) / 0.14);
|
||
}
|
||
|
||
.state-dot.ok {
|
||
background: var(--hud-lime);
|
||
box-shadow: 0 0 0 2px rgb(var(--hud-lime-rgb) / 0.16);
|
||
}
|
||
|
||
.state-dot.warn {
|
||
background: var(--hud-orange);
|
||
box-shadow: 0 0 0 2px rgb(var(--hud-orange-rgb) / 0.16);
|
||
}
|
||
|
||
.state-label {
|
||
color: var(--panel-text-dim);
|
||
font-size: 0.66rem;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
line-height: 1;
|
||
}
|
||
|
||
.state-value {
|
||
color: var(--panel-text);
|
||
font-size: 0.92rem;
|
||
letter-spacing: 0.02em;
|
||
font-weight: 600;
|
||
line-height: 1;
|
||
}
|
||
|
||
.serial-select {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.38rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid var(--panel-line);
|
||
border-radius: 999px;
|
||
padding: 0.18rem 0.2rem 0.18rem 0.56rem;
|
||
background: var(--panel-surface);
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.serial-select-wrap {
|
||
position: relative;
|
||
display: inline-block;
|
||
min-inline-size: 0;
|
||
}
|
||
|
||
.serial-tag {
|
||
color: var(--panel-text-dim);
|
||
font-size: 0.66rem;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.serial-select-input {
|
||
appearance: none;
|
||
inline-size: 100%;
|
||
min-inline-size: 7rem;
|
||
border: 1px solid var(--panel-line);
|
||
border-radius: 999px;
|
||
padding: 0.3rem 1.5rem 0.3rem 0.6rem;
|
||
background: rgb(var(--hud-surface-deep-rgb) / 0.84);
|
||
color: rgb(var(--hud-text-main-rgb) / 0.92);
|
||
font-size: 0.84rem;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
outline: none;
|
||
transition:
|
||
border-color 180ms ease,
|
||
box-shadow 180ms ease;
|
||
}
|
||
|
||
.serial-select-input:hover {
|
||
border-color: rgb(var(--hud-cyan-rgb) / 0.36);
|
||
}
|
||
|
||
.serial-select-input:focus-visible {
|
||
border-color: rgb(var(--hud-cyan-rgb) / 0.5);
|
||
box-shadow: 0 0 0 2px rgb(var(--hud-cyan-rgb) / 0.14);
|
||
}
|
||
|
||
.serial-select-caret {
|
||
position: absolute;
|
||
inset-inline-end: 0.68rem;
|
||
inset-block-start: 50%;
|
||
inline-size: 0.42rem;
|
||
block-size: 0.42rem;
|
||
border-right: 1px solid rgb(var(--hud-text-main-rgb) / 0.82);
|
||
border-bottom: 1px solid rgb(var(--hud-text-main-rgb) / 0.82);
|
||
transform: translateY(-64%) rotate(45deg);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.refresh-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.36rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid var(--panel-line);
|
||
border-radius: 999px;
|
||
padding: 0.24rem 0.64rem;
|
||
background:
|
||
linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.92), rgb(var(--hud-surface-rgb) / 0.88)),
|
||
radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.1), transparent 58%);
|
||
color: rgb(var(--hud-text-main-rgb) / 0.96);
|
||
font-size: 0.74rem;
|
||
letter-spacing: 0.05em;
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 180ms ease,
|
||
background-color 180ms ease,
|
||
color 180ms ease,
|
||
box-shadow 200ms ease,
|
||
opacity 180ms ease;
|
||
}
|
||
|
||
.refresh-btn svg {
|
||
inline-size: 0.84rem;
|
||
block-size: 0.84rem;
|
||
stroke: currentColor;
|
||
stroke-width: 1.35;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
|
||
.refresh-btn:hover:not(:disabled) {
|
||
border-color: rgb(var(--hud-cyan-rgb) / 0.44);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgb(var(--hud-border-strong-rgb) / 0.07),
|
||
0 0 10px rgb(var(--hud-glow-rgb) / 0.1);
|
||
}
|
||
|
||
.refresh-btn:disabled {
|
||
opacity: 0.72;
|
||
cursor: default;
|
||
}
|
||
|
||
.connect-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.42rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid rgb(var(--hud-lime-rgb) / 0.4);
|
||
border-radius: 999px;
|
||
padding: 0.24rem 0.76rem;
|
||
background:
|
||
linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.96), rgb(var(--hud-surface-rgb) / 0.92)),
|
||
radial-gradient(circle at 50% 0, rgb(var(--hud-glow-alt-rgb) / 0.12), transparent 58%);
|
||
color: rgb(var(--hud-text-main-rgb) / 0.98);
|
||
font-size: 0.78rem;
|
||
letter-spacing: 0.05em;
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 180ms ease,
|
||
background-color 180ms ease,
|
||
color 180ms ease,
|
||
box-shadow 200ms ease,
|
||
opacity 180ms ease;
|
||
}
|
||
|
||
.connect-btn:hover:not(:disabled) {
|
||
border-color: rgb(var(--hud-lime-rgb) / 0.62);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgb(var(--hud-text-main-rgb) / 0.08),
|
||
0 0 12px rgb(var(--hud-glow-alt-rgb) / 0.14);
|
||
}
|
||
|
||
.connect-btn:disabled {
|
||
opacity: 0.72;
|
||
cursor: default;
|
||
}
|
||
|
||
.connect-btn.is-busy {
|
||
border-color: rgb(var(--hud-orange-rgb) / 0.48);
|
||
background:
|
||
linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.96), rgb(var(--hud-surface-deep-rgb) / 0.92)),
|
||
radial-gradient(circle at 50% 0, rgb(var(--hud-orange-rgb) / 0.12), transparent 58%);
|
||
color: rgb(var(--hud-orange-rgb) / 0.96);
|
||
}
|
||
|
||
.connect-btn.is-connected {
|
||
border-color: rgb(var(--hud-cyan-rgb) / 0.46);
|
||
background:
|
||
linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.96), rgb(var(--hud-surface-rgb) / 0.92)),
|
||
radial-gradient(circle at 50% 0, rgb(var(--hud-cyan-rgb) / 0.14), transparent 58%);
|
||
color: rgb(var(--hud-text-main-rgb) / 0.98);
|
||
}
|
||
|
||
.connect-btn-indicator {
|
||
inline-size: 0.4rem;
|
||
block-size: 0.4rem;
|
||
border-radius: 999px;
|
||
background: var(--hud-lime);
|
||
box-shadow: 0 0 0 2px rgb(133 255 68 / 0.15);
|
||
}
|
||
|
||
.connect-btn.is-busy .connect-btn-indicator {
|
||
background: var(--hud-orange);
|
||
box-shadow: 0 0 0 2px rgb(255 91 63 / 0.16);
|
||
}
|
||
|
||
.connect-btn.is-connected .connect-btn-indicator {
|
||
background: var(--hud-cyan);
|
||
box-shadow: 0 0 0 2px rgb(62 232 255 / 0.16);
|
||
}
|
||
|
||
.export-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.36rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid rgb(62 232 255 / 0.4);
|
||
border-radius: 999px;
|
||
padding: 0.24rem 0.72rem;
|
||
background:
|
||
linear-gradient(180deg, rgb(10 28 32 / 0.96), rgb(6 15 18 / 0.92)),
|
||
radial-gradient(circle at 50% 0, rgb(62 232 255 / 0.14), transparent 58%);
|
||
color: rgb(227 251 255 / 0.98);
|
||
font-size: 0.74rem;
|
||
letter-spacing: 0.05em;
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 180ms ease,
|
||
background-color 180ms ease,
|
||
color 180ms ease,
|
||
box-shadow 200ms ease,
|
||
opacity 180ms ease;
|
||
}
|
||
|
||
.export-btn svg {
|
||
inline-size: 0.84rem;
|
||
block-size: 0.84rem;
|
||
stroke: currentColor;
|
||
stroke-width: 1.35;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
|
||
.export-btn:hover:not(:disabled) {
|
||
border-color: rgb(115 245 255 / 0.62);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgb(231 255 255 / 0.08),
|
||
0 0 12px rgb(62 232 255 / 0.14);
|
||
}
|
||
|
||
.export-btn:disabled {
|
||
opacity: 0.72;
|
||
cursor: default;
|
||
}
|
||
|
||
.import-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.36rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid rgb(122 198 255 / 0.36);
|
||
border-radius: 999px;
|
||
padding: 0.24rem 0.72rem;
|
||
background:
|
||
linear-gradient(180deg, rgb(11 22 32 / 0.94), rgb(7 13 20 / 0.9)),
|
||
radial-gradient(circle at 50% 0, rgb(122 198 255 / 0.13), transparent 58%);
|
||
color: rgb(226 243 255 / 0.97);
|
||
font-size: 0.74rem;
|
||
letter-spacing: 0.05em;
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 180ms ease,
|
||
background-color 180ms ease,
|
||
color 180ms ease,
|
||
box-shadow 200ms ease;
|
||
}
|
||
|
||
.import-btn svg {
|
||
inline-size: 0.84rem;
|
||
block-size: 0.84rem;
|
||
stroke: currentColor;
|
||
stroke-width: 1.35;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
|
||
.import-btn:hover {
|
||
border-color: rgb(164 220 255 / 0.6);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgb(231 244 255 / 0.08),
|
||
0 0 12px rgb(122 198 255 / 0.14);
|
||
}
|
||
|
||
.connection-notice {
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.6rem;
|
||
border: 1px solid rgb(95 132 158 / 0.32);
|
||
border-radius: 0.5rem;
|
||
padding: 0.38rem 0.45rem 0.38rem 0.7rem;
|
||
background: rgb(8 14 19 / 0.72);
|
||
}
|
||
|
||
.connection-notice-text {
|
||
margin: 0;
|
||
flex: 1;
|
||
min-width: 0;
|
||
color: rgb(214 236 248 / 0.96);
|
||
font-size: 0.72rem;
|
||
letter-spacing: 0.03em;
|
||
}
|
||
|
||
.connection-notice.tone-warn {
|
||
border-color: rgb(255 91 63 / 0.42);
|
||
background: rgb(28 12 11 / 0.78);
|
||
color: rgb(255 218 208 / 0.96);
|
||
}
|
||
|
||
.connection-notice.tone-ok {
|
||
border-color: rgb(133 255 68 / 0.4);
|
||
background: rgb(18 26 16 / 0.76);
|
||
color: rgb(228 255 214 / 0.96);
|
||
}
|
||
|
||
.connection-notice.tone-info {
|
||
border-color: rgb(62 232 255 / 0.34);
|
||
background: rgb(8 17 22 / 0.76);
|
||
}
|
||
|
||
.notice-close-btn {
|
||
inline-size: 1.36rem;
|
||
block-size: 1.36rem;
|
||
border: 1px solid rgb(116 151 176 / 0.4);
|
||
border-radius: 0.28rem;
|
||
background: rgb(7 12 16 / 0.82);
|
||
color: rgb(194 225 245 / 0.92);
|
||
font-size: 0.92rem;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
transition:
|
||
border-color 180ms ease,
|
||
color 180ms ease,
|
||
background-color 180ms ease;
|
||
}
|
||
|
||
.notice-close-btn:hover {
|
||
border-color: rgb(62 232 255 / 0.5);
|
||
color: rgb(237 250 255 / 0.98);
|
||
background: rgb(9 16 22 / 0.92);
|
||
}
|
||
|
||
.connection-notice.tone-warn .notice-close-btn:hover {
|
||
border-color: rgb(255 91 63 / 0.6);
|
||
color: rgb(255 227 220 / 0.98);
|
||
background: rgb(34 13 12 / 0.9);
|
||
}
|
||
|
||
.connection-notice.tone-ok .notice-close-btn:hover {
|
||
border-color: rgb(133 255 68 / 0.56);
|
||
color: rgb(236 255 227 / 0.98);
|
||
background: rgb(17 28 14 / 0.9);
|
||
}
|
||
|
||
.info-grid {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1.05rem;
|
||
flex-wrap: wrap;
|
||
padding-inline: 0.15rem;
|
||
}
|
||
|
||
.info-cell {
|
||
min-width: 0;
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: 0.42rem;
|
||
}
|
||
|
||
.meta-label {
|
||
margin: 0;
|
||
font-size: 0.58rem;
|
||
letter-spacing: 0.1em;
|
||
color: rgb(140 163 181 / 0.82);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.meta-value {
|
||
margin: 0;
|
||
font-size: 0.78rem;
|
||
letter-spacing: 0.03em;
|
||
color: rgb(205 228 245 / 0.92);
|
||
}
|
||
|
||
.locale-switch {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.34rem;
|
||
border: 1px solid rgb(95 132 158 / 0.34);
|
||
border-radius: 999px;
|
||
padding: 0.18rem;
|
||
background: rgb(10 16 20 / 0.7);
|
||
}
|
||
|
||
.config-links {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.2rem;
|
||
min-block-size: 2rem;
|
||
border: 1px solid rgb(95 132 158 / 0.36);
|
||
border-radius: 999px;
|
||
padding: 0.17rem 0.2rem;
|
||
background: linear-gradient(180deg, rgb(9 15 19 / 0.9), rgb(4 8 12 / 0.86));
|
||
box-shadow: inset 0 0 0 1px rgb(140 184 210 / 0.06);
|
||
}
|
||
|
||
.config-link {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.34rem;
|
||
border: 1px solid transparent;
|
||
border-radius: 999px;
|
||
padding: 0.26rem 0.64rem;
|
||
background: transparent;
|
||
color: rgb(164 188 208 / 0.9);
|
||
font-size: 0.81rem;
|
||
letter-spacing: 0.05em;
|
||
cursor: pointer;
|
||
transition:
|
||
color 180ms ease,
|
||
border-color 180ms ease,
|
||
background-color 180ms ease,
|
||
box-shadow 220ms ease;
|
||
}
|
||
|
||
.config-indicator {
|
||
inline-size: 0.34rem;
|
||
block-size: 0.34rem;
|
||
border-radius: 999px;
|
||
background: rgb(136 157 174 / 0.88);
|
||
box-shadow: 0 0 0 2px rgb(136 157 174 / 0.16);
|
||
transition:
|
||
background-color 180ms ease,
|
||
box-shadow 200ms ease;
|
||
}
|
||
|
||
.config-label {
|
||
line-height: 1;
|
||
}
|
||
|
||
.config-link:hover {
|
||
color: #d7edfb;
|
||
border-color: rgb(62 232 255 / 0.26);
|
||
}
|
||
|
||
.config-link.is-active {
|
||
color: #f1fdff;
|
||
border-color: rgb(106 150 180 / 0.56);
|
||
background: rgb(18 27 35 / 0.9);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgb(167 218 252 / 0.08),
|
||
0 0 10px rgb(62 232 255 / 0.12);
|
||
}
|
||
|
||
.config-link.tone-cyan.is-active {
|
||
border-color: rgb(62 232 255 / 0.48);
|
||
}
|
||
|
||
.config-link.tone-lime.is-active {
|
||
border-color: rgb(133 255 68 / 0.52);
|
||
}
|
||
|
||
.config-link.tone-orange.is-active {
|
||
border-color: rgb(255 91 63 / 0.52);
|
||
}
|
||
|
||
.config-link.tone-cyan.is-active .config-indicator {
|
||
background: var(--hud-cyan);
|
||
box-shadow: 0 0 0 2px rgb(62 232 255 / 0.17);
|
||
}
|
||
|
||
.config-link.tone-lime.is-active .config-indicator {
|
||
background: var(--hud-lime);
|
||
box-shadow: 0 0 0 2px rgb(133 255 68 / 0.17);
|
||
}
|
||
|
||
.config-link.tone-orange.is-active .config-indicator {
|
||
background: var(--hud-orange);
|
||
box-shadow: 0 0 0 2px rgb(255 91 63 / 0.18);
|
||
}
|
||
|
||
.locale-btn {
|
||
border: 1px solid transparent;
|
||
border-radius: 999px;
|
||
padding: 0.26rem 0.6rem;
|
||
background: transparent;
|
||
color: rgb(150 173 189 / 0.9);
|
||
font-size: 0.7rem;
|
||
letter-spacing: 0.04em;
|
||
cursor: pointer;
|
||
transition:
|
||
color 180ms ease,
|
||
border-color 180ms ease,
|
||
background-color 180ms ease;
|
||
}
|
||
|
||
.locale-btn:hover {
|
||
color: #d7edfb;
|
||
border-color: rgb(62 232 255 / 0.3);
|
||
}
|
||
|
||
.locale-btn.is-active {
|
||
color: #f1fdff;
|
||
border-color: rgb(133 255 68 / 0.48);
|
||
background: rgb(24 31 25 / 0.9);
|
||
}
|
||
|
||
@media (max-width: 1080px) {
|
||
.config-links {
|
||
flex-wrap: wrap;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 820px) {
|
||
.control-main-row {
|
||
gap: 0.44rem;
|
||
}
|
||
|
||
.config-links {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.serial-select {
|
||
padding-inline-start: 0.45rem;
|
||
}
|
||
|
||
.info-grid {
|
||
gap: 0.84rem;
|
||
}
|
||
|
||
}
|
||
|
||
@media (max-width: 620px) {
|
||
.suite-tag {
|
||
display: none;
|
||
}
|
||
|
||
.control-main-row {
|
||
align-items: stretch;
|
||
}
|
||
|
||
.serial-select {
|
||
inline-size: 100%;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.info-grid {
|
||
gap: 0.42rem 0.8rem;
|
||
}
|
||
|
||
.info-cell {
|
||
inline-size: 100%;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.window-btn {
|
||
inline-size: 1.75rem;
|
||
}
|
||
|
||
}
|
||
</style>
|