Files
ledgrab/server/docs/ui-redesign-mockup.html
T
alexei.dolgolyov 539e43195f feat(ui): Lumenworks studio-console WebUI redesign
Full-app UI/UX refresh committing to a tech-instrument / studio-console
aesthetic inspired by hardware synths, Eurorack panels, and DAW layouts.

Design tokens and fonts:
- Embed Manrope (body), JetBrains Mono (labels/metrics), Big Shoulders
  Display (numeric readouts) as local .woff2 variable fonts with
  latin + latin-ext + cyrillic + cyrillic-ext subsets via unicode-range.
- New Lumenworks token layer in base.css: --lux-bg-0..3, --lux-line(-bold),
  --lux-ink(-dim/-mute/-faint), --ch-signal/-cyan/-magenta/-amber/-coral/
  -violet channel palette, --lux-signal-glow, --lux-shadow-rack, all
  theme-aware for dark + light. Existing tokens untouched for compat.

Shell (header + sidebar):
- Header rebuilt as a 3-column CSS-grid transport bar (brand | center |
  toolbar) with a glowing LED brand mark rendered via pseudo-elements on
  .header-title. Gradient channel-color rule under the bottom border.
- New sidebar.css introduces a vertical channel-strip nav. Active tab
  gets a glowing left stripe + radial tint + LED pip. .sidebar-foot
  contains a live CPU/FPS meter plate.
- Sidebar collapses to a 56 px icon rail at <=1100 px and hides via
  display:contents at <=600 px so mobile.css's fixed bottom tab-bar
  flows through unchanged.

Cards and dashboard:
- .card gets channel stripe (data-card-type + .ch-* utilities auto-map
  from data-target-id / data-stream-id / data-automation-id etc.), corner
  bracket, gradient background, subtle rack shadow.
- .card-running replaces the old @property --border-angle conic-gradient
  rotating border with a lightweight signalFlow linear-gradient strip on
  the bottom edge (cheaper paint, no GPU layer compositing per card).
- Skeleton loaders rewritten: left hairline + corner bracket + gradient
  shimmer instead of the old text-color opacity pulse.
- .dashboard-target rows pick up the same channel-stripe + signalFlow
  treatment. Section headers use mono micro-caps with a channel-green
  underline accent consistent across the app.
- .perf-chart-card: channel stripe replaces old border-top; per-metric
  accents moved to the channel palette (CPU=coral, RAM=violet, GPU=green,
  temp=amber). Metric values use tabular-nums + a soft glow.

Live bindings (no new endpoints):
- _updateSidebarMeter: binds the sidebar Load + FPS bars to the existing
  /system/performance poll.
- _updateTransportStatus: toggles the transport chip between "Ready" and
  "Armed - N live" whenever the dashboard's running-target set is
  recomputed.

Tree-nav + sub-tabs:
- tree-nav.css trigger pill gets a channel-stripe left edge that glows
  when open; panel has a gradient channel-accent rule across the top;
  group headers use silkscreened micro-caps; active leaf has a pulsing
  LED pip + channel tint.
- .stream-tab-btn / .subtab-section-header adopt the same mono-caps +
  channel-underline language for consistency.
- Graph editor toolbar gets gradient + hairline + rack shadow + backdrop
  blur. Canvas and nodes untouched.

Modals (40+ modals share modal.css):
- Radial-dim + 6 px blur backdrop. Content gets a gradient background,
  hairline border, deep rack shadow, top channel-accent rule driven by
  --modal-ch, bottom-right corner bracket (hidden on mobile fullscreen).
- Per-modal-ID channel lanes: target editors = green, source/input
  editors = cyan, audio = magenta, automation/scene/game = violet,
  settings/auth = amber, confirm = coral.
- Modal headers: vertical channel stripe left of the title + hairline
  divider. Modal footers: hairline top border + subtle gradient wash.

Forms:
- Inputs use hairline borders; number inputs switch to mono + tabular-nums
  for column alignment. Focus state: channel-green ring + soft glow.
- Buttons use mono-uppercase type with signal-glow on primary and coral-
  glow on danger.

Mobile (<=600 px):
- Fixed bottom .tab-bar gets the full Lumenworks treatment: gradient fill,
  top channel-accent rule matching the transport bar, backdrop blur.
  Active tab has an LED pip above the icon + channel tint + icon recolor.
- Fullscreen modals: corner bracket hidden, header stripe slimmed.

Microcopy (en / ru / zh):
- "Targets" -> "Channels" / "Каналы" / "通道"
- "Sources" -> "Inputs"    / "Входы"   / "输入"
- Internal tab keys (dashboard/automations/targets/streams/integrations/
  graph) kept stable so no JS or localStorage migration is needed.
- Added: sidebar.workspaces, sidebar.load, sidebar.fps,
  transport.status.ready, transport.status.armed.

Compatibility:
- All existing class hooks preserved (.tab-bar, .tab-btn, .card,
  .card-running, .tree-dd-*, .cs-*, .perf-chart-card, .modal-content,
  .dashboard-target, etc.). No JS or API changes required for the new
  look to take effect.
- Tour selectors survive (header .header-title, #tab-btn-*, onclick
  markers on theme/settings/search, #cp-wrap-accent, etc.).
- Mobile <=600 px bottom tab-bar keeps working via display:contents
  fall-through in the new sidebar.

Build: tsc --noEmit clean; npm run build clean. CSS bundle grew from
~177 KB to ~201 KB for the full new visual system. Fonts loaded lazily
per unicode-range subset (~98 KB critical path for English).

Phased plan + deferred follow-ups (dashboard hero strip, legacy-token
cleanup) recorded at the top of TODO.md.

Reference mockup: server/docs/ui-redesign-mockup.html.
2026-04-24 15:46:47 +03:00

1379 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LUMENWORKS — LedGrab Redesign Mockup</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@500;700;900&family=JetBrains+Mono:wght@300;400;500;600;700&family=Manrope:wght@300;400;500;600;700;800&family=Orbitron:wght@600;800;900&display=swap" rel="stylesheet">
<style>
/* ============================================================
LUMENWORKS — proposed studio-instrument aesthetic for LedGrab
============================================================ */
:root {
--bg-0: #0a0b0d;
--bg-1: #101216;
--bg-2: #15181d;
--bg-3: #1c2027;
--line: #232831;
--line-bold: #2e3440;
--ink: #e6ebf2;
--ink-dim: #8b95a5;
--ink-mute: #5b6473;
--ink-faint: #3a414c;
/* Channel/lane palette — assigned to entity types like patch cables */
--signal: #00ff7a; /* primary — capture/playback */
--signal-dim: #00b85a;
--ch-cyan: #00d8ff; /* data / network */
--ch-magenta: #ff4ade; /* audio */
--ch-amber: #ffb800; /* warning / autostart */
--ch-coral: #ff5e5e; /* alarm / stop */
--ch-violet: #8b7eff; /* graph / nodes */
--signal-glow: 0 0 14px color-mix(in srgb, var(--signal) 40%, transparent);
--shadow-rack: 0 1px 0 rgba(255,255,255,0.03), 0 8px 24px rgba(0,0,0,0.5);
--font-display: 'Big Shoulders Display', 'Orbitron', sans-serif;
--font-brand: 'Orbitron', sans-serif;
--font-mono: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, monospace;
--font-body: 'Manrope', system-ui, sans-serif;
--r-sm: 3px;
--r-md: 6px;
--r-lg: 10px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg-0);
color: var(--ink);
font-family: var(--font-body);
font-feature-settings: "ss01", "cv11";
-webkit-font-smoothing: antialiased;
min-height: 100%;
}
body {
background:
radial-gradient(900px 600px at 12% -5%, color-mix(in srgb, var(--signal) 7%, transparent), transparent 60%),
radial-gradient(700px 500px at 95% 8%, color-mix(in srgb, var(--ch-cyan) 5%, transparent), transparent 60%),
radial-gradient(800px 600px at 50% 110%, color-mix(in srgb, var(--ch-magenta) 4%, transparent), transparent 70%),
var(--bg-0);
position: relative;
overflow-x: hidden;
}
/* Dotted grid texture across the entire app */
body::before {
content: '';
position: fixed;
inset: 0;
background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
background-size: 16px 16px;
background-position: 0 0;
z-index: 0;
pointer-events: none;
}
/* Subtle scanlines overlay */
body::after {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent 0,
transparent 2px,
rgba(255,255,255,0.012) 2px,
rgba(255,255,255,0.012) 3px
);
z-index: 1;
pointer-events: none;
mix-blend-mode: overlay;
}
/* ─── Layout shell ─────────────────────────────────────────── */
.shell {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: 248px 1fr;
grid-template-rows: 64px 1fr;
min-height: 100vh;
}
/* ─── Top transport bar ────────────────────────────────────── */
.transport {
grid-column: 1 / -1;
display: grid;
grid-template-columns: 248px 1fr auto;
align-items: center;
border-bottom: 1px solid var(--line-bold);
background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 100%);
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: blur(8px);
}
.brand {
display: flex;
align-items: center;
gap: 12px;
padding: 0 20px;
height: 100%;
border-right: 1px solid var(--line-bold);
}
.brand-mark {
width: 28px;
height: 28px;
background: var(--signal);
border-radius: 4px;
position: relative;
box-shadow: var(--signal-glow), inset 0 0 0 1px rgba(0,0,0,0.2);
display: grid;
place-items: center;
}
.brand-mark::after {
content: '';
width: 12px;
height: 12px;
background: var(--bg-0);
border-radius: 1px;
box-shadow: 0 0 0 2px var(--signal);
display: block;
}
.brand-text {
display: flex;
flex-direction: column;
line-height: 1;
gap: 2px;
}
.brand-name {
font-family: var(--font-brand);
font-weight: 900;
font-size: 1rem;
letter-spacing: 0.18em;
background: linear-gradient(90deg, var(--ink), var(--signal) 50%, var(--ink));
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: shimmer 8s linear infinite;
}
.brand-tag {
font-family: var(--font-mono);
font-size: 0.55rem;
letter-spacing: 0.25em;
color: var(--ink-mute);
text-transform: uppercase;
}
@keyframes shimmer {
to { background-position: -200% 0; }
}
.transport-center {
display: flex;
align-items: center;
gap: 6px;
padding: 0 24px;
}
.transport-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
color: var(--ink-dim);
font-family: var(--font-mono);
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
}
.transport-btn:hover {
color: var(--ink);
border-color: var(--line-bold);
background: var(--bg-3);
}
.transport-btn.is-armed {
color: var(--signal);
border-color: color-mix(in srgb, var(--signal) 40%, transparent);
background: color-mix(in srgb, var(--signal) 8%, transparent);
box-shadow: inset 0 0 12px color-mix(in srgb, var(--signal) 12%, transparent);
}
.transport-btn .dot {
width: 6px; height: 6px; border-radius: 50%; background: var(--signal);
box-shadow: var(--signal-glow);
animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.45; transform: scale(0.85); }
}
.transport-meta {
display: flex;
align-items: center;
gap: 18px;
padding: 0 20px;
font-family: var(--font-mono);
font-size: 0.7rem;
color: var(--ink-mute);
}
.meta-cell { display: flex; flex-direction: column; gap: 2px; line-height: 1; align-items: flex-end; }
.meta-cell .k { font-size: 0.55rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-faint); }
.meta-cell .v { color: var(--ink); font-weight: 500; font-variant-numeric: tabular-nums; }
.meta-sep { width: 1px; height: 22px; background: var(--line); }
.icon-btn {
width: 28px; height: 28px;
display: grid; place-items: center;
background: transparent;
border: 1px solid var(--line);
border-radius: var(--r-sm);
color: var(--ink-dim);
cursor: pointer;
transition: all 0.15s ease;
}
.icon-btn:hover { color: var(--ink); border-color: var(--line-bold); background: var(--bg-2); }
.icon-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* ─── Sidebar (channel strip nav) ──────────────────────────── */
.sidebar {
border-right: 1px solid var(--line-bold);
background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 100%);
padding: 20px 0;
position: sticky;
top: 64px;
height: calc(100vh - 64px);
overflow-y: auto;
display: flex;
flex-direction: column;
}
.sidebar-section { padding: 0 14px 18px; }
.sidebar-label {
display: flex;
align-items: center;
gap: 8px;
font-family: var(--font-mono);
font-size: 0.55rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--ink-mute);
padding: 0 6px 8px;
border-bottom: 1px dashed var(--line);
margin-bottom: 8px;
}
.sidebar-label::before { content: '['; color: var(--ink-faint); }
.sidebar-label::after { content: ']'; color: var(--ink-faint); margin-left: auto; }
.nav-item {
display: grid;
grid-template-columns: 18px 1fr auto;
gap: 12px;
align-items: center;
padding: 8px 8px;
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--ink-dim);
cursor: pointer;
border-radius: var(--r-sm);
margin-bottom: 1px;
position: relative;
transition: all 0.15s ease;
letter-spacing: 0.04em;
}
.nav-item:hover { color: var(--ink); background: var(--bg-2); }
.nav-item.active {
color: var(--ink);
background: linear-gradient(90deg, color-mix(in srgb, var(--signal) 12%, transparent), transparent);
box-shadow: inset 2px 0 0 var(--signal);
}
.nav-item svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.nav-item.active svg { color: var(--signal); }
.nav-item .nav-count {
font-size: 0.6rem;
background: var(--bg-3);
color: var(--ink-mute);
padding: 1px 5px;
border-radius: 8px;
font-variant-numeric: tabular-nums;
}
.nav-item.active .nav-count { background: var(--signal); color: var(--bg-0); }
.sidebar-bottom {
margin-top: auto;
padding: 16px 14px;
border-top: 1px dashed var(--line);
}
.cpu-meter {
font-family: var(--font-mono);
display: flex;
flex-direction: column;
gap: 8px;
}
.cpu-meter-row { display: flex; justify-content: space-between; font-size: 0.6rem; color: var(--ink-mute); letter-spacing: 0.1em; text-transform: uppercase; }
.cpu-meter-row b { color: var(--ink); font-weight: 500; font-variant-numeric: tabular-nums; }
.cpu-bar { height: 4px; background: var(--bg-3); border-radius: 2px; overflow: hidden; position: relative; }
.cpu-bar > i {
display: block;
height: 100%;
background: linear-gradient(90deg, var(--signal), var(--ch-cyan));
box-shadow: 0 0 8px color-mix(in srgb, var(--signal) 50%, transparent);
}
/* ─── Main rack area ───────────────────────────────────────── */
.rack {
padding: 28px 36px 60px;
position: relative;
min-width: 0;
}
.rack-header {
display: flex;
align-items: flex-end;
justify-content: space-between;
padding-bottom: 22px;
margin-bottom: 28px;
border-bottom: 1px solid var(--line);
position: relative;
}
.rack-header::after {
content: '';
position: absolute;
left: 0; bottom: -1px;
width: 80px;
height: 1px;
background: var(--signal);
box-shadow: var(--signal-glow);
}
.rack-title {
display: flex;
flex-direction: column;
gap: 6px;
}
.rack-title .crumb {
font-family: var(--font-mono);
font-size: 0.6rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--ink-mute);
}
.rack-title .crumb b { color: var(--signal); font-weight: 500; }
.rack-title h1 {
font-family: var(--font-display);
font-weight: 900;
font-size: 2.4rem;
line-height: 1;
letter-spacing: -0.01em;
color: var(--ink);
}
.rack-title h1 em {
font-style: normal;
color: var(--ink-mute);
font-weight: 500;
font-size: 1.6rem;
margin-left: 8px;
}
.rack-actions { display: flex; align-items: center; gap: 8px; }
/* ─── Section banner ───────────────────────────────────────── */
.section {
margin-bottom: 36px;
}
.section-head {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 14px;
position: relative;
}
.section-head h2 {
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.3em;
text-transform: uppercase;
color: var(--ink-dim);
display: flex;
align-items: baseline;
gap: 10px;
}
.section-head h2::before {
content: '◆';
color: var(--signal);
font-size: 0.6rem;
}
.section-head .count {
font-family: var(--font-mono);
font-size: 0.6rem;
color: var(--ink-mute);
letter-spacing: 0.15em;
}
.section-head .rule {
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--line) 0%, transparent 100%);
}
.section-head .actions { display: flex; gap: 6px; }
/* ─── Hero strip — system overview ─────────────────────────── */
.hero {
display: grid;
grid-template-columns: 1.2fr 1fr 1fr 1fr;
gap: 1px;
margin-bottom: 36px;
background: var(--line);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
}
.hero-cell {
background: var(--bg-1);
padding: 22px 22px 18px;
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
overflow: hidden;
}
.hero-cell::before {
content: '';
position: absolute;
top: 12px; right: 12px;
width: 28px; height: 14px;
border: 1px solid var(--line-bold);
border-bottom-color: transparent;
border-left-color: transparent;
}
.hero-cell .label {
font-family: var(--font-mono);
font-size: 0.58rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--ink-mute);
}
.hero-cell .big {
font-family: var(--font-display);
font-size: 3.4rem;
font-weight: 900;
line-height: 0.95;
color: var(--ink);
font-variant-numeric: tabular-nums;
}
.hero-cell .big .unit { color: var(--ink-mute); font-size: 1.1rem; margin-left: 6px; font-weight: 500; }
.hero-cell .sub { font-family: var(--font-mono); font-size: 0.7rem; color: var(--ink-dim); display: flex; gap: 10px; }
.hero-cell .sub .delta { color: var(--signal); }
.hero-cell .sub .delta.bad { color: var(--ch-coral); }
/* Hero — primary cell with running channels */
.hero-cell.primary {
background:
radial-gradient(420px 280px at 110% 110%, color-mix(in srgb, var(--signal) 10%, transparent), transparent 60%),
var(--bg-1);
}
.channels-running {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 6px;
}
.ch-row {
display: grid;
grid-template-columns: 6px 1fr auto;
align-items: center;
gap: 10px;
font-family: var(--font-mono);
font-size: 0.72rem;
}
.ch-row .stripe { width: 4px; height: 20px; background: var(--signal); border-radius: 2px; box-shadow: 0 0 6px currentColor; }
.ch-row .stripe.cyan { background: var(--ch-cyan); }
.ch-row .stripe.magenta { background: var(--ch-magenta); }
.ch-row .name { color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ch-row .fps { color: var(--signal); font-variant-numeric: tabular-nums; }
/* Mini sparkline for hero numerics */
.spark { height: 28px; width: 100%; margin-top: 8px; }
.spark path.line { fill: none; stroke: var(--signal); stroke-width: 1.5; }
.spark path.fill { fill: url(#spark-fade); opacity: 0.4; }
/* ─── Module grid (the cards) ──────────────────────────────── */
.modules {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 14px;
}
.module {
--ch: var(--signal);
position: relative;
background:
linear-gradient(180deg, var(--bg-1) 0%, var(--bg-2) 100%);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 20px 16px;
display: flex;
flex-direction: column;
gap: 14px;
overflow: hidden;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.module:hover {
transform: translateY(-2px);
border-color: var(--line-bold);
box-shadow: var(--shadow-rack);
}
/* Channel stripe — left edge color tag */
.module::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: var(--ch);
box-shadow: 0 0 12px color-mix(in srgb, var(--ch) 50%, transparent);
}
/* Corner brackets — silkscreened panel feel */
.module::after {
content: '';
position: absolute;
top: 8px; right: 8px;
width: 14px; height: 14px;
border-top: 1px solid var(--line-bold);
border-right: 1px solid var(--line-bold);
pointer-events: none;
}
.module.is-running {
border-color: color-mix(in srgb, var(--ch) 35%, var(--line));
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ch) 25%, transparent), 0 6px 24px rgba(0,0,0,0.35);
}
.mod-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.mod-id {
display: flex;
flex-direction: column;
gap: 4px;
}
.mod-id .badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: var(--font-mono);
font-size: 0.55rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--ch);
padding: 2px 6px;
border: 1px solid color-mix(in srgb, var(--ch) 35%, var(--line));
border-radius: 3px;
background: color-mix(in srgb, var(--ch) 8%, transparent);
align-self: flex-start;
}
.mod-id h3 {
font-family: var(--font-body);
font-weight: 700;
font-size: 1.05rem;
color: var(--ink);
letter-spacing: -0.01em;
}
.mod-id .meta {
font-family: var(--font-mono);
font-size: 0.66rem;
color: var(--ink-mute);
letter-spacing: 0.06em;
}
/* LED status cluster (top-right) */
.leds {
display: flex;
align-items: center;
gap: 4px;
padding: 5px 7px;
background: var(--bg-0);
border: 1px solid var(--line);
border-radius: var(--r-sm);
}
.led {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--ink-faint);
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5);
}
.led.on { background: var(--ch); box-shadow: 0 0 6px color-mix(in srgb, var(--ch) 80%, transparent); }
.led.amber { background: var(--ch-amber); box-shadow: 0 0 6px color-mix(in srgb, var(--ch-amber) 80%, transparent); }
.led.blink {
animation: blink 1.2s ease-in-out infinite;
}
.led.blink:nth-child(2) { animation-delay: 0.2s; }
.led.blink:nth-child(3) { animation-delay: 0.4s; }
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.25; }
}
/* Metric grid inside a module */
.mod-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0;
background: var(--bg-0);
border: 1px solid var(--line);
border-radius: var(--r-sm);
overflow: hidden;
}
.metric {
padding: 10px 12px;
border-right: 1px solid var(--line);
display: flex;
flex-direction: column;
gap: 2px;
position: relative;
min-width: 0;
}
.metric:last-child { border-right: none; }
.metric .k {
font-family: var(--font-mono);
font-size: 0.55rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-mute);
}
.metric .v {
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 700;
line-height: 1;
color: var(--ink);
font-variant-numeric: tabular-nums;
}
.metric .v.signal { color: var(--ch); text-shadow: 0 0 14px color-mix(in srgb, var(--ch) 60%, transparent); }
.metric .v small { font-size: 0.65rem; color: var(--ink-mute); margin-left: 2px; font-weight: 500; }
/* Sparkline inside a metric cell */
.metric svg.spark-sm { width: 100%; height: 22px; margin-top: 2px; }
.metric svg.spark-sm path { fill: none; stroke: var(--ch); stroke-width: 1.4; }
.metric svg.spark-sm .area { fill: var(--ch); opacity: 0.12; stroke: none; }
/* Module footer — playback row + patch points */
.mod-foot {
display: flex;
align-items: center;
gap: 10px;
}
.mod-foot .patch {
display: flex;
align-items: center;
gap: 6px;
font-family: var(--font-mono);
font-size: 0.6rem;
color: var(--ink-mute);
letter-spacing: 0.1em;
margin-right: auto;
}
.patch-dot {
width: 8px; height: 8px;
border-radius: 50%;
background: var(--bg-0);
border: 1px solid var(--ch);
position: relative;
}
.patch-dot.is-live::after {
content: '';
position: absolute;
inset: 1px;
border-radius: 50%;
background: var(--ch);
box-shadow: 0 0 6px var(--ch);
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 7px 14px;
border: 1px solid var(--line-bold);
border-radius: var(--r-sm);
background: var(--bg-2);
color: var(--ink-dim);
cursor: pointer;
transition: all 0.15s ease;
}
.btn:hover { color: var(--ink); border-color: color-mix(in srgb, var(--signal) 40%, var(--line-bold)); }
.btn-go {
background: var(--ch);
color: var(--bg-0);
border-color: var(--ch);
box-shadow: 0 0 16px color-mix(in srgb, var(--ch) 35%, transparent);
}
.btn-go:hover { color: var(--bg-0); filter: brightness(1.08); }
.btn-stop {
background: transparent;
color: var(--ch-coral);
border-color: color-mix(in srgb, var(--ch-coral) 40%, transparent);
}
.btn-stop:hover { background: color-mix(in srgb, var(--ch-coral) 12%, transparent); }
.btn .glyph { width: 10px; height: 10px; display: inline-grid; place-items: center; }
/* Channel color variants */
.module.ch-cyan { --ch: var(--ch-cyan); }
.module.ch-magenta { --ch: var(--ch-magenta); }
.module.ch-amber { --ch: var(--ch-amber); }
.module.ch-violet { --ch: var(--ch-violet); }
/* ─── "PATCHED" — animated signal flow indicator on running modules ─── */
.module.is-running .signal-flow {
position: absolute;
bottom: 0;
left: 3px;
right: 0;
height: 2px;
background: linear-gradient(
90deg,
transparent 0%,
var(--ch) 50%,
transparent 100%
);
background-size: 30% 100%;
background-repeat: no-repeat;
animation: patchflow 2.4s linear infinite;
opacity: 0.55;
pointer-events: none;
}
@keyframes patchflow {
0% { background-position: -30% 0; }
100% { background-position: 130% 0; }
}
/* ─── Footer rack info plate ──────────────────────────────── */
.plate {
margin-top: 48px;
padding: 14px 20px;
border: 1px dashed var(--line);
border-radius: var(--r-md);
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 24px;
font-family: var(--font-mono);
font-size: 0.65rem;
color: var(--ink-mute);
letter-spacing: 0.12em;
text-transform: uppercase;
}
.plate .stamp {
font-family: var(--font-brand);
font-weight: 800;
font-size: 0.7rem;
letter-spacing: 0.3em;
color: var(--signal);
padding: 4px 8px;
border: 1px solid color-mix(in srgb, var(--signal) 40%, transparent);
border-radius: 3px;
}
.plate .dots { display: flex; gap: 5px; }
.plate .dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-faint); display: block; }
.plate .dots i:nth-child(1), .plate .dots i:nth-child(3), .plate .dots i:nth-child(5) { background: var(--signal); box-shadow: 0 0 4px var(--signal); }
/* ─── Color-picker / settings cluster (top right of transport) ─── */
.swatches { display: flex; gap: 6px; }
.swatch { width: 18px; height: 18px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.1); cursor: pointer; transition: transform 0.15s; }
.swatch:hover { transform: scale(1.18); }
.swatch.s1 { background: var(--signal); box-shadow: 0 0 8px var(--signal); }
.swatch.s2 { background: var(--ch-cyan); }
.swatch.s3 { background: var(--ch-magenta); }
.swatch.s4 { background: var(--ch-amber); }
.swatch.s5 { background: var(--ch-violet); }
/* ─── Detail / diagonal callout for the mockup file ─── */
.tag-mock {
position: fixed;
top: 14px; right: 14px;
z-index: 999;
font-family: var(--font-mono);
font-size: 0.55rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--bg-0);
background: var(--ch-amber);
padding: 4px 8px;
border-radius: 2px;
box-shadow: 0 0 12px color-mix(in srgb, var(--ch-amber) 50%, transparent);
}
@media (max-width: 1100px) {
.shell { grid-template-columns: 1fr; }
.sidebar { display: none; }
.transport { grid-template-columns: 1fr auto auto; }
.brand { border-right: none; padding: 0 14px; }
.hero { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
.hero { grid-template-columns: 1fr; }
.modules { grid-template-columns: 1fr; }
.rack { padding: 18px; }
}
</style>
</head>
<body>
<div class="tag-mock">PROTOTYPE · LUMENWORKS v0.1</div>
<svg width="0" height="0" style="position:absolute">
<defs>
<linearGradient id="spark-fade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#00ff7a" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#00ff7a" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>
<div class="shell">
<!-- ─── TRANSPORT BAR ───────────────────────────────── -->
<header class="transport">
<div class="brand">
<div class="brand-mark"></div>
<div class="brand-text">
<div class="brand-name">LUMENWORKS</div>
<div class="brand-tag">LED · GRAB · v0.4.2</div>
</div>
</div>
<div class="transport-center">
<button class="transport-btn is-armed"><span class="dot"></span> Armed · 3 channels live</button>
<button class="transport-btn">⏯ Master Stop</button>
<button class="transport-btn">⌘ K — Search</button>
</div>
<div class="transport-meta">
<div class="meta-cell"><span class="k">Uptime</span><span class="v">04:21:08</span></div>
<span class="meta-sep"></span>
<div class="meta-cell"><span class="k">CPU</span><span class="v">14%</span></div>
<div class="meta-cell"><span class="k">Mem</span><span class="v">182MB</span></div>
<span class="meta-sep"></span>
<div class="swatches">
<span class="swatch s1" title="Signal Green"></span>
<span class="swatch s2"></span>
<span class="swatch s3"></span>
<span class="swatch s4"></span>
<span class="swatch s5"></span>
</div>
<span class="meta-sep"></span>
<button class="icon-btn" title="Theme"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg></button>
<button class="icon-btn" title="Settings"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
</div>
</header>
<!-- ─── SIDEBAR (channel-strip nav) ─────────────────── -->
<aside class="sidebar">
<div class="sidebar-section">
<div class="sidebar-label">Workspaces</div>
<div class="nav-item active">
<svg viewBox="0 0 24 24"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>
<span>Dashboard</span>
<span class="nav-count">3</span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><rect width="8" height="4" x="8" y="2" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4M12 16h4M8 11h.01M8 16h.01"/></svg>
<span>Automations</span>
<span class="nav-count">7</span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg>
<span>Targets</span>
<span class="nav-count">12</span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><path d="m17 2-5 5-5-5"/><rect width="20" height="15" x="2" y="7" rx="2"/></svg>
<span>Sources</span>
<span class="nav-count">8</span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><path d="M12 22v-5M15 8V2"/><path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"/><path d="M9 8V2"/></svg>
<span>Integrations</span>
<span class="nav-count">4</span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><circle cx="5" cy="6" r="3"/><circle cx="19" cy="6" r="3"/><circle cx="12" cy="18" r="3"/><path d="M7.5 7.5L10.5 16M16.5 7.5L13.5 16"/></svg>
<span>Graph</span>
<span class="nav-count">2</span>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Live Targets</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 1v6M12 17v6"/></svg>
<span>Living Room</span>
<span class="nav-count" style="background:var(--signal);color:var(--bg-0)"></span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/></svg>
<span>Desk Strip</span>
<span class="nav-count" style="background:var(--ch-cyan);color:var(--bg-0)"></span>
</div>
<div class="nav-item">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/></svg>
<span>Bedroom Halo</span>
<span class="nav-count" style="background:var(--ch-magenta);color:var(--bg-0)"></span>
</div>
</div>
<div class="sidebar-bottom">
<div class="cpu-meter">
<div class="cpu-meter-row"><span>System Load</span><b>14.2%</b></div>
<div class="cpu-bar"><i style="width:14.2%"></i></div>
<div class="cpu-meter-row"><span>Capture FPS</span><b>59.7</b></div>
<div class="cpu-bar"><i style="width:88%;background:linear-gradient(90deg,var(--ch-cyan),var(--ch-magenta))"></i></div>
</div>
</div>
</aside>
<!-- ─── RACK (main content) ─────────────────────────── -->
<main class="rack">
<div class="rack-header">
<div class="rack-title">
<div class="crumb">Workspace · <b>Dashboard</b> · Studio A</div>
<h1>Live Channels<em>3 of 12 patched</em></h1>
</div>
<div class="rack-actions">
<button class="btn"> New Target</button>
<button class="btn">▤ Layout</button>
<button class="btn-go btn">◉ Capture All</button>
</div>
</div>
<!-- ─── HERO STRIP — system overview ─── -->
<div class="hero">
<div class="hero-cell primary">
<span class="label">Active Patches</span>
<div class="big">03<span class="unit">/ 12</span></div>
<div class="channels-running">
<div class="ch-row"><span class="stripe"></span><span class="name">Living Room WLED</span><span class="fps">59.4 FPS</span></div>
<div class="ch-row"><span class="stripe cyan"></span><span class="name">Desk Strip</span><span class="fps">29.8 FPS</span></div>
<div class="ch-row"><span class="stripe magenta"></span><span class="name">Bedroom Halo</span><span class="fps">44.2 FPS</span></div>
</div>
</div>
<div class="hero-cell">
<span class="label">Throughput</span>
<div class="big">133<span class="unit">FPS</span></div>
<div class="sub"><span class="delta">▲ 4.2</span><span>vs last min</span></div>
<svg class="spark" viewBox="0 0 200 28" preserveAspectRatio="none">
<path class="line" d="M0,18 L20,14 L40,16 L60,9 L80,12 L100,7 L120,11 L140,5 L160,8 L180,4 L200,6"/>
<path class="fill" d="M0,18 L20,14 L40,16 L60,9 L80,12 L100,7 L120,11 L140,5 L160,8 L180,4 L200,6 L200,28 L0,28 Z"/>
</svg>
</div>
<div class="hero-cell">
<span class="label">CPU · Capture Pipe</span>
<div class="big">14<span class="unit">%</span></div>
<div class="sub"><span class="delta">▼ 1.1</span><span>nominal</span></div>
<svg class="spark" viewBox="0 0 200 28" preserveAspectRatio="none">
<path class="line" style="stroke:var(--ch-cyan)" d="M0,16 L20,18 L40,15 L60,17 L80,13 L100,16 L120,11 L140,14 L160,12 L180,15 L200,13"/>
</svg>
</div>
<div class="hero-cell">
<span class="label">Latency · WLED Net</span>
<div class="big">12<span class="unit">ms</span></div>
<div class="sub"><span class="delta bad">▲ 0.4</span><span>p95 stable</span></div>
<svg class="spark" viewBox="0 0 200 28" preserveAspectRatio="none">
<path class="line" style="stroke:var(--ch-amber)" d="M0,18 L20,16 L40,18 L60,14 L80,16 L100,12 L120,15 L140,11 L160,13 L180,9 L200,12"/>
</svg>
</div>
</div>
<!-- ─── SECTION: Capture targets ─── -->
<section class="section">
<div class="section-head">
<h2>Capture Targets <span class="count">03 LIVE / 12 TOTAL</span></h2>
<div class="rule"></div>
<div class="actions">
<button class="icon-btn"><svg viewBox="0 0 24 24"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>
<button class="icon-btn"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg></button>
</div>
</div>
<div class="modules">
<!-- MODULE 1 — running (signal green) -->
<div class="module is-running">
<div class="signal-flow"></div>
<div class="mod-head">
<div class="mod-id">
<span class="badge">CH·01 · WLED</span>
<h3>Living Room Strip</h3>
<div class="meta">192.168.1.42 · 144 LEDs · DDP</div>
</div>
<div class="leds">
<span class="led on blink"></span>
<span class="led on blink"></span>
<span class="led on blink"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">FPS</span>
<span class="v signal">59<small>.4</small></span>
<svg class="spark-sm" viewBox="0 0 100 22" preserveAspectRatio="none">
<path class="area" d="M0,18 L10,12 L20,14 L30,8 L40,11 L50,6 L60,10 L70,4 L80,9 L90,5 L100,7 L100,22 L0,22 Z"/>
<path d="M0,18 L10,12 L20,14 L30,8 L40,11 L50,6 L60,10 L70,4 L80,9 L90,5 L100,7"/>
</svg>
</div>
<div class="metric">
<span class="k">Latency</span>
<span class="v">8<small>ms</small></span>
<svg class="spark-sm" viewBox="0 0 100 22" preserveAspectRatio="none">
<path d="M0,16 L10,14 L20,15 L30,12 L40,14 L50,11 L60,13 L70,10 L80,12 L90,9 L100,11" style="stroke:var(--signal-dim)"/>
</svg>
</div>
<div class="metric">
<span class="k">Source</span>
<span class="v" style="font-size:0.85rem;font-family:var(--font-mono)">SCREEN<br><small>2560×1440</small></span>
</div>
</div>
<div class="mod-foot">
<div class="patch">
<span class="patch-dot is-live"></span>
<span>PATCHED · IN→OUT</span>
</div>
<button class="btn btn-stop">■ Stop</button>
<button class="btn"></button>
</div>
</div>
<!-- MODULE 2 — running (cyan / data) -->
<div class="module ch-cyan is-running">
<div class="signal-flow"></div>
<div class="mod-head">
<div class="mod-id">
<span class="badge">CH·02 · ARTNET</span>
<h3>Desk Underglow</h3>
<div class="meta">10.0.0.88 · 60 LEDs · ArtNet U1</div>
</div>
<div class="leds">
<span class="led on blink"></span>
<span class="led on"></span>
<span class="led"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">FPS</span>
<span class="v signal">29<small>.8</small></span>
<svg class="spark-sm" viewBox="0 0 100 22" preserveAspectRatio="none">
<path class="area" d="M0,12 L10,14 L20,11 L30,13 L40,10 L50,15 L60,9 L70,12 L80,8 L90,11 L100,10 L100,22 L0,22 Z"/>
<path d="M0,12 L10,14 L20,11 L30,13 L40,10 L50,15 L60,9 L70,12 L80,8 L90,11 L100,10"/>
</svg>
</div>
<div class="metric">
<span class="k">Latency</span>
<span class="v">14<small>ms</small></span>
</div>
<div class="metric">
<span class="k">Effect</span>
<span class="v" style="font-size:0.85rem;font-family:var(--font-mono)">GRAD<br><small>fire+sparkle</small></span>
</div>
</div>
<div class="mod-foot">
<div class="patch">
<span class="patch-dot is-live"></span>
<span>PATCHED · CSS→RGB</span>
</div>
<button class="btn btn-stop">■ Stop</button>
<button class="btn"></button>
</div>
</div>
<!-- MODULE 3 — running (magenta / audio) -->
<div class="module ch-magenta is-running">
<div class="signal-flow"></div>
<div class="mod-head">
<div class="mod-id">
<span class="badge">CH·03 · AUDIO</span>
<h3>Bedroom Halo (FFT)</h3>
<div class="meta">192.168.1.55 · 90 LEDs · E1.31</div>
</div>
<div class="leds">
<span class="led on blink"></span>
<span class="led on blink"></span>
<span class="led on blink"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">FPS</span>
<span class="v signal">44<small>.2</small></span>
<svg class="spark-sm" viewBox="0 0 100 22" preserveAspectRatio="none">
<path class="area" d="M0,16 L8,4 L16,18 L24,6 L32,15 L40,3 L48,16 L56,8 L64,14 L72,5 L80,17 L88,9 L96,12 L100,7 L100,22 L0,22 Z"/>
<path d="M0,16 L8,4 L16,18 L24,6 L32,15 L40,3 L48,16 L56,8 L64,14 L72,5 L80,17 L88,9 L96,12 L100,7"/>
</svg>
</div>
<div class="metric">
<span class="k">Peak</span>
<span class="v">6<small>dB</small></span>
</div>
<div class="metric">
<span class="k">Source</span>
<span class="v" style="font-size:0.85rem;font-family:var(--font-mono)">MIC<br><small>loopback</small></span>
</div>
</div>
<div class="mod-foot">
<div class="patch">
<span class="patch-dot is-live"></span>
<span>PATCHED · FFT→COLOR</span>
</div>
<button class="btn btn-stop">■ Stop</button>
<button class="btn"></button>
</div>
</div>
<!-- MODULE 4 — idle (amber / autostart) -->
<div class="module ch-amber">
<div class="mod-head">
<div class="mod-id">
<span class="badge">CH·04 · WLED</span>
<h3>Kitchen Counter</h3>
<div class="meta">192.168.1.61 · 30 LEDs · DDP · idle</div>
</div>
<div class="leds">
<span class="led amber"></span>
<span class="led"></span>
<span class="led"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">FPS</span>
<span class="v" style="color:var(--ink-mute)">--</span>
</div>
<div class="metric">
<span class="k">Last Run</span>
<span class="v" style="font-size:0.85rem;font-family:var(--font-mono);color:var(--ink-dim)">2h<br><small>ago</small></span>
</div>
<div class="metric">
<span class="k">Autostart</span>
<span class="v" style="font-size:0.85rem;font-family:var(--font-mono);color:var(--ch-amber)">ARMED</span>
</div>
</div>
<div class="mod-foot">
<div class="patch">
<span class="patch-dot"></span>
<span>STANDBY</span>
</div>
<button class="btn btn-go" style="--ch:var(--ch-amber)">▶ Start</button>
<button class="btn"></button>
</div>
</div>
<!-- MODULE 5 — offline (coral / alarm) -->
<div class="module" style="--ch:var(--ch-coral)">
<div class="mod-head">
<div class="mod-id">
<span class="badge">CH·05 · WLED</span>
<h3>Hallway Run</h3>
<div class="meta">192.168.1.77 · OFFLINE · last seen 3d</div>
</div>
<div class="leds">
<span class="led" style="background:var(--ch-coral);box-shadow:0 0 6px var(--ch-coral)"></span>
<span class="led"></span>
<span class="led"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">FPS</span>
<span class="v" style="color:var(--ink-mute)">--</span>
</div>
<div class="metric">
<span class="k">Health</span>
<span class="v" style="color:var(--ch-coral);font-size:0.95rem">OFFLINE</span>
</div>
<div class="metric">
<span class="k">Action</span>
<span class="v" style="font-size:0.75rem;font-family:var(--font-mono);color:var(--ink-dim)">PING<br><small>required</small></span>
</div>
</div>
<div class="mod-foot">
<div class="patch">
<span class="patch-dot" style="border-color:var(--ch-coral)"></span>
<span>UNREACHABLE</span>
</div>
<button class="btn">↻ Ping</button>
<button class="btn"></button>
</div>
</div>
<!-- MODULE 6 — graph node (violet) -->
<div class="module ch-violet">
<div class="mod-head">
<div class="mod-id">
<span class="badge">CH·06 · GRAPH</span>
<h3>Sunset Scene Composer</h3>
<div class="meta">3 inputs · 2 outputs · scheduled</div>
</div>
<div class="leds">
<span class="led on" style="background:var(--ch-violet);box-shadow:0 0 6px var(--ch-violet)"></span>
<span class="led on" style="background:var(--ch-violet);box-shadow:0 0 6px var(--ch-violet)"></span>
<span class="led"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">Nodes</span>
<span class="v signal">14</span>
</div>
<div class="metric">
<span class="k">Schedule</span>
<span class="v" style="font-size:0.85rem;font-family:var(--font-mono);color:var(--ink-dim)">19:42<br><small>12m</small></span>
</div>
<div class="metric">
<span class="k">Targets</span>
<span class="v" style="font-size:1.1rem">Living + Bedroom</span>
</div>
</div>
<div class="mod-foot">
<div class="patch">
<span class="patch-dot"></span>
<span>SCHEDULED</span>
</div>
<button class="btn">▶ Run Now</button>
<button class="btn"></button>
</div>
</div>
</div>
</section>
<!-- ─── SECTION: Sources panel preview ─── -->
<section class="section">
<div class="section-head">
<h2>Active Sources <span class="count">04 STREAMING</span></h2>
<div class="rule"></div>
<div class="actions">
<button class="btn"> Add Source</button>
</div>
</div>
<div class="modules">
<div class="module ch-cyan is-running">
<div class="signal-flow"></div>
<div class="mod-head">
<div class="mod-id">
<span class="badge">SRC·01 · SCREEN</span>
<h3>Display 2 — 4K HDR</h3>
<div class="meta">3840×2160 · 60 Hz · BGRA · DXGI</div>
</div>
<div class="leds">
<span class="led on blink" style="background:var(--ch-cyan);box-shadow:0 0 6px var(--ch-cyan)"></span>
<span class="led on" style="background:var(--ch-cyan);box-shadow:0 0 6px var(--ch-cyan)"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">Capture</span>
<span class="v signal">60.0<small>fps</small></span>
</div>
<div class="metric">
<span class="k">CPU</span>
<span class="v">3.2<small>%</small></span>
</div>
<div class="metric">
<span class="k">Patches</span>
<span class="v">2</span>
</div>
</div>
</div>
<div class="module ch-magenta is-running">
<div class="signal-flow"></div>
<div class="mod-head">
<div class="mod-id">
<span class="badge">SRC·02 · AUDIO</span>
<h3>Stereo Mix · 48kHz</h3>
<div class="meta">WASAPI loopback · 1024 FFT · log scale</div>
</div>
<div class="leds">
<span class="led on blink" style="background:var(--ch-magenta);box-shadow:0 0 6px var(--ch-magenta)"></span>
<span class="led on blink" style="background:var(--ch-magenta);box-shadow:0 0 6px var(--ch-magenta)"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">Peak L</span>
<span class="v signal">4<small>dB</small></span>
</div>
<div class="metric">
<span class="k">Peak R</span>
<span class="v signal">6<small>dB</small></span>
</div>
<div class="metric">
<span class="k">Patches</span>
<span class="v">1</span>
</div>
</div>
</div>
<div class="module ch-amber">
<div class="mod-head">
<div class="mod-id">
<span class="badge">SRC·03 · CSS</span>
<h3>Gradient Fire</h3>
<div class="meta">linear-gradient · 12 stops · animated 4s</div>
</div>
<div class="leds">
<span class="led on" style="background:var(--ch-amber);box-shadow:0 0 6px var(--ch-amber)"></span>
</div>
</div>
<div class="mod-metrics">
<div class="metric">
<span class="k">Refresh</span>
<span class="v">30<small>Hz</small></span>
</div>
<div class="metric">
<span class="k">CPU</span>
<span class="v">0.4<small>%</small></span>
</div>
<div class="metric">
<span class="k">Patches</span>
<span class="v">0</span>
</div>
</div>
</div>
</div>
</section>
<div class="plate">
<span class="stamp">SYSTEM NOMINAL</span>
<div>BUILD 0.4.2 · KERNEL READY · 3 CHANNELS PATCHED · 04:21:08 UPTIME · NO ERRORS</div>
<div class="dots"><i></i><i></i><i></i><i></i><i></i></div>
</div>
</main>
</div>
</body>
</html>