feat(n7web): enhance control workflows and status feedback

This commit is contained in:
Miayui
2026-09-16 12:52:07 +08:00
parent 84a3cd73bb
commit 0e4328f548
45 changed files with 7317 additions and 1001 deletions
@@ -0,0 +1,275 @@
/* N7 Web Console design-system contract.
* Loaded last by Vite so every legacy surface consumes one geometry,
* typography, state and elevation scale while the runtime is migrated to
* Vue components incrementally. */
:root {
--n7-control-height-sm: 32px;
--n7-control-height: 38px;
--n7-icon-button-size: 38px;
--n7-radius-control: 10px;
--n7-radius-card: 14px;
--n7-radius-section: 16px;
--n7-radius-panel: 18px;
--n7-radius-dialog: 22px;
--n7-font-caption: 12px;
--n7-font-body: 13px;
--n7-font-card-title: 14px;
--n7-font-panel-title: 16px;
--n7-font-dialog-title: 22px;
--n7-shadow-card: inset 0 1px 0 var(--glass-highlight), 0 7px 20px color-mix(in oklch, var(--foreground) 8%, transparent);
--n7-shadow-panel: inset 0 1px 0 var(--glass-highlight), 0 14px 34px color-mix(in oklch, var(--foreground) 13%, transparent);
--n7-shadow-dialog: inset 0 1px 0 var(--glass-highlight), 0 28px 84px color-mix(in oklch, var(--foreground) 28%, transparent);
}
#app {
min-width: inherit;
min-height: 100dvh;
display: contents;
}
/* Workspace panels: one top-level geometry, distinct from their children. */
body.console-v2.v2-passive-display.v2-interactive-sync :is(
.main-panel > .v2-mode-rail,
#assistantDrawer,
.joint-twin-shell > .twin-band,
#v2DevicePanel,
#v2ExtensionPanel .v2-extension-sync-mount
) {
border-radius: var(--n7-radius-panel) !important;
box-shadow: var(--n7-shadow-panel) !important;
}
body.console-v2 :is(
.v2-sync-transport-field,
.v2-sync-execution-field,
.v2-can-command-panel,
.v2-nfc-inline,
.v2-extension-gateway,
.v2-extension-synced-device,
.v2-extension-empty,
.v2-pet-card,
.v2-pet-presence-card,
.v2-device-card,
.v2-synced-device,
.mode-gallery-card,
.mode-editor-command-card,
.energy-kpi,
.energy-device-row,
.assistant-dispatched-controls,
.assistant-mode-draft,
.assistant-mode-created,
.assistant-control-suggestion
) {
border-radius: var(--n7-radius-card) !important;
box-shadow: var(--n7-shadow-card) !important;
}
body.console-v2 :is(
.v2-sync-log,
.v2-pet-card-region,
.mode-gallery-group,
.mode-editor-flow,
.v2-nfc-stage,
.energy-trend-card,
.energy-devices-card
) {
border-radius: var(--n7-radius-section) !important;
}
/* Only actionable cards float. Read-only panels remain spatially stable. */
body.console-v2 :is(
.v2-sync-log,
.v2-pet-card-region,
.mode-editor-flow,
.energy-trend-card,
.energy-devices-card
):is(:hover, :focus-within) {
transform: none !important;
box-shadow: var(--n7-shadow-card) !important;
}
/* The current DOM uses .twin-selector; the previous glass selector targeted
* the removed .twin-seat-switch class and produced white-on-white tabs. */
body.console-v2 .joint-twin-shell :is(.twin-selector, .twin-selector button, .twin-reset, .twin-focus-badge) {
color: var(--muted-foreground) !important;
background: var(--glass-card) !important;
border-color: var(--glass-line) !important;
box-shadow: inset 0 1px 0 var(--glass-highlight) !important;
}
body.console-v2 .joint-twin-shell .twin-selector button.active {
color: var(--primary-foreground) !important;
background: var(--primary) !important;
border-color: color-mix(in oklch, var(--primary) 72%, white 18%) !important;
}
/* Shared heading hierarchy. */
body.console-v2 :is(
#assistantDrawer h2,
.joint-twin-shell .twin-heading h3,
.v2-device-sync-board > header h3,
.v2-extension-sync-heading h3,
.v2-mode-rail .mode-rail-title
) {
font-size: var(--n7-font-panel-title) !important;
font-weight: 750 !important;
line-height: 1.25 !important;
letter-spacing: -0.02em;
}
/* The log title shares a 217 px header with its filter, so it needs the
* compact card-title scale instead of the full panel-title scale. */
body.console-v2 .v2-sync-log h3 {
min-width: 0;
margin: 0;
overflow: hidden;
font-size: var(--n7-font-card-title) !important;
font-weight: 750 !important;
line-height: 1.25 !important;
letter-spacing: -0.02em;
text-overflow: ellipsis;
white-space: nowrap;
}
body.console-v2 :is(
.mode-gallery-header h2,
.mode-editor-header h2,
.v2-extension-dialog > header h2,
.v2-extension-dialog form > header h2,
.energy-monitor-heading h2
) {
font-size: var(--n7-font-dialog-title) !important;
font-weight: 750 !important;
line-height: 1.2 !important;
}
body.console-v2 :is(
.mode-gallery-card-heading strong,
.v2-device-card-focus strong,
.v2-nfc-inline strong,
.v2-extension-gateway strong,
.v2-pet-card > header > strong,
.energy-trend-card h3,
.energy-devices-card h3
) {
font-size: var(--n7-font-card-title) !important;
line-height: 1.35 !important;
}
body.console-v2 :is(
.mode-editor-status,
.mode-gallery-footer,
.v2-pet-settings-footnote,
.v2-pet-card-fields > label > span,
.v2-pet-nfc-field output,
.energy-kpi > small,
.energy-kpi > em,
.energy-devices-card > footer
) {
font-size: var(--n7-font-caption) !important;
line-height: 1.45 !important;
}
/* One dialog radius, elevation and close-button size. */
body.console-v2 :is(
.mode-gallery,
.mode-editor,
.v2-nfc-dialog,
.v2-gscu-tcp-dialog,
.v2-pet-settings-dialog,
.energy-monitor-page
) {
border-radius: var(--n7-radius-dialog) !important;
box-shadow: var(--n7-shadow-dialog) !important;
}
body.console-v2 :is(
.mode-gallery-close,
.mode-editor-header > button,
.v2-extension-dialog [data-dialog-close],
.v2-extension-dialog [data-close-extension-dialog],
.v2-extension-dialog [data-close-nfc-dialog],
#energyMonitorClose
) {
width: var(--n7-icon-button-size) !important;
height: var(--n7-icon-button-size) !important;
min-width: var(--n7-icon-button-size);
min-height: var(--n7-icon-button-size);
border-radius: var(--n7-radius-control) !important;
}
/* Compact icon actions still meet a usable hit target without becoming a
* second visual system. */
body.console-v2 :is(
.twin-reset,
.mode-gallery-edit,
.mode-gallery-delete,
.v2-pet-card > header > button
) {
width: var(--n7-control-height-sm) !important;
height: var(--n7-control-height-sm) !important;
min-width: var(--n7-control-height-sm);
min-height: var(--n7-control-height-sm);
border-radius: var(--n7-radius-control) !important;
}
/* Offline hardware is a destructive state, not a primary/active state. */
body.console-v2 .v2-nfc-stage:has(.v2-nfc-waiting.offline) > header {
color: var(--status-error-foreground) !important;
background: color-mix(in oklch, var(--destructive) 16%, var(--card)) !important;
border-bottom: 1px solid color-mix(in oklch, var(--destructive) 32%, var(--border));
}
body.console-v2 .v2-nfc-stage:has(.v2-nfc-waiting.offline) > header :is(span, small, h3, p, b) {
color: var(--status-error-foreground) !important;
}
/* Keep the global appearance control out of content and below every modal. */
.glass-theme-toggle {
left: max(18px, env(safe-area-inset-left)) !important;
bottom: max(18px, env(safe-area-inset-bottom)) !important;
z-index: 1100 !important;
}
body.mode-gallery-open .glass-theme-toggle,
body.mode-editor-open .glass-theme-toggle,
body:has(.v2-extension-dialog[open]) .glass-theme-toggle,
body:has(.energy-monitor-layer:not([hidden])) .glass-theme-toggle {
opacity: 0;
pointer-events: none;
}
/* The sidebar reserves the floating control's footprint instead of allowing
* it to cover the log timeline. */
body.console-v2 .v2-sync-log {
margin-bottom: 58px;
}
/* Top mode rail communicates that more content is available. */
body.console-v2 .v2-mode-rail .mode-rail-content {
position: relative;
min-width: 0;
}
body.console-v2 .v2-mode-rail .mode-rail-content::after {
width: 28px;
content: "";
position: absolute;
inset: 0 0 0 auto;
pointer-events: none;
background: linear-gradient(90deg, transparent, color-mix(in oklch, var(--glass-panel) 94%, transparent));
}
@media (max-width: 1360px) {
.glass-theme-toggle {
width: 42px !important;
height: 42px !important;
}
}
@media (prefers-reduced-motion: reduce) {
body.console-v2 * {
scroll-behavior: auto !important;
}
}