docs(mockups): palette comparison (classic vs complementary)

This commit is contained in:
2026-05-09 18:58:12 +00:00
parent a144490297
commit 29728fa4a9
+437
View File
@@ -0,0 +1,437 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HAI · LCARS Router Console — Palette Variants</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=Antonio:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
/* ====== CLASSIC LCARS PALETTE ====== */
:root,
[data-palette="classic"] {
--bg: #000000;
--ink: #FF9966; /* dominant: peach */
--ink-2: #FFCC99;
--wan: #CC6666; /* warning: dim red */
--mgmt: #9999CC; /* lavender */
--cam: #FFCC66; /* yellow */
--hai: #99CC99; /* green */
--iot: #CC99CC; /* purple */
--beige: #FFCC99;
--warn: #FF6666;
--bar: #FF9966;
--bar-dim: #663322;
--cell: #111111;
--cell-2: #1a1a1a;
--hai-text: #000; /* text color when hai is the bg */
}
/* ====== COMPLEMENTARY PALETTE (180° rotated) ====== */
[data-palette="complementary"] {
--bg: #000000;
--ink: #66CCFF; /* dominant: sky cyan */
--ink-2: #99CCFF;
--wan: #66CCCC; /* warning: dim turquoise */
--mgmt: #CCCC99; /* olive-cream */
--cam: #6699FF; /* royal blue */
--hai: #CC99CC; /* mauve */
--iot: #99CC99; /* green (was purple) */
--beige: #99CCFF;
--warn: #99FFFF; /* alarm: bright cyan */
--bar: #66CCFF;
--bar-dim: #224466;
--cell: #0A0E12;
--cell-2: #141a20;
--hai-text: #000;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--ink); font-family: 'Antonio', 'Arial Narrow', sans-serif; letter-spacing: 0.02em; transition: background 200ms ease, color 200ms ease; }
body { min-height: 100vh; padding: 24px; }
.mono { font-family: 'JetBrains Mono', monospace; }
button { font-family: inherit; cursor: pointer; }
/* ===== Toolbar ===== */
.toolbar {
display: flex; gap: 24px; align-items: center; margin-bottom: 24px;
flex-wrap: wrap;
}
.toolbar .group {
display: flex; gap: 8px; align-items: center;
}
.toolbar .label {
font-size: 11px; letter-spacing: 0.2em; color: var(--ink-2);
text-transform: uppercase; margin-right: 8px;
}
.toolbar button {
background: var(--bar-dim); color: var(--ink); border: none;
padding: 10px 20px; border-radius: 18px;
font-size: 13px; letter-spacing: 0.15em; text-transform: uppercase;
transition: all 120ms ease;
}
.toolbar button.active { background: var(--ink); color: #000; }
.toolbar button:hover { background: var(--bar); color: #000; }
/* Palette swatch preview */
.palette-preview {
display: flex; gap: 4px; margin-left: 8px;
}
.palette-preview .sw {
width: 18px; height: 18px; border-radius: 3px;
}
/* ===== Console chrome ===== */
.console {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
gap: 8px;
min-height: 820px;
}
.topbar {
grid-column: 1 / -1;
display: grid;
grid-template-columns: 200px 1fr auto;
gap: 8px;
height: 64px;
}
.topbar .corner {
background: var(--ink);
border-radius: 0 0 0 32px;
display: flex; align-items: flex-end; justify-content: flex-end;
padding: 10px 16px;
color: #000; font-weight: 700; font-size: 12px; letter-spacing: 0.3em;
}
.topbar .title-strip {
background: var(--bg);
border-bottom: 4px solid var(--ink);
display: flex; align-items: flex-end; padding: 0 16px 8px;
gap: 24px;
}
.topbar .sys-title {
font-size: 28px; font-weight: 700; letter-spacing: 0.1em;
color: var(--ink);
}
.topbar .sys-id {
font-size: 12px; color: var(--ink-2); letter-spacing: 0.25em;
margin-bottom: 4px;
}
.topbar .clock {
background: var(--ink); color: #000;
padding: 0 24px;
display: flex; align-items: center;
border-radius: 32px 0 0 0;
font-weight: 700; font-size: 14px; letter-spacing: 0.15em;
}
/* Left rail */
.leftrail {
background: var(--bg);
display: flex; flex-direction: column; gap: 4px;
}
.pill {
background: var(--bar-dim); color: var(--ink);
border: none; padding: 14px 16px; text-align: right;
font-size: 13px; letter-spacing: 0.15em; text-transform: uppercase;
border-radius: 0;
transition: all 100ms ease;
}
.pill.active { background: var(--ink); color: #000; }
.pill:hover { background: var(--bar); color: #000; }
.pill.wan { color: var(--wan); }
.pill.wan.active { background: var(--wan); color: #000; }
.pill.mgmt { color: var(--mgmt); }
.pill.mgmt.active { background: var(--mgmt); color: #000; }
.pill.cam { color: var(--cam); }
.pill.cam.active { background: var(--cam); color: #000; }
.pill.hai { color: var(--hai); }
.pill.hai.active { background: var(--hai); color: var(--hai-text); }
.pill .num {
font-family: 'JetBrains Mono', monospace;
font-size: 10px; opacity: 0.7; margin-right: 6px;
}
/* Bottom bar */
.bottombar {
grid-column: 1 / -1;
display: grid;
grid-template-columns: 200px 1fr;
gap: 8px; height: 56px;
}
.bottombar .corner-bl {
background: var(--ink);
border-radius: 32px 0 0 0;
display: flex; align-items: flex-start; justify-content: flex-end;
padding: 8px 16px; color: #000; font-weight: 700;
font-size: 11px; letter-spacing: 0.25em;
}
.status-strip {
border-top: 4px solid var(--ink);
display: flex; align-items: center; gap: 24px;
padding: 0 16px;
font-size: 12px;
font-family: 'JetBrains Mono', monospace;
}
.status-chip {
display: inline-flex; align-items: center; gap: 8px;
padding: 4px 12px; border-radius: 12px;
background: var(--cell-2);
letter-spacing: 0.1em; color: var(--ink-2);
}
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot.ok { background: var(--hai); box-shadow: 0 0 8px var(--hai); }
.dot.warn { background: var(--cam); box-shadow: 0 0 8px var(--cam); }
.dot.err { background: var(--warn); box-shadow: 0 0 8px var(--warn); animation: blink 1.2s infinite; }
@keyframes blink { 50% { opacity: 0.3; } }
/* Main grid */
.main {
background: var(--bg);
padding: 0 8px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto auto;
gap: 12px;
}
.panel {
background: var(--cell);
border-radius: 16px;
padding: 16px 20px;
border-left: 6px solid var(--ink);
}
.panel.wide { grid-column: 1 / -1; }
.panel.wan { border-left-color: var(--wan); }
.panel.mgmt { border-left-color: var(--mgmt); }
.panel.cam { border-left-color: var(--cam); }
.panel.hai { border-left-color: var(--hai); }
.panel.iot { border-left-color: var(--iot); }
.panel h3 {
margin: 0 0 12px 0;
font-size: 13px; letter-spacing: 0.25em;
text-transform: uppercase; color: var(--ink-2);
display: flex; justify-content: space-between; align-items: baseline;
}
.panel h3 .id {
font-family: 'JetBrains Mono', monospace;
font-size: 10px; color: var(--ink);
background: var(--bar-dim);
padding: 2px 8px; border-radius: 8px;
}
.kv { display: grid; grid-template-columns: 110px 1fr; gap: 6px 12px; font-family: 'JetBrains Mono', monospace; font-size: 12px; }
.kv .k { color: var(--ink-2); letter-spacing: 0.1em; }
.kv .v { color: var(--ink); }
.kv .v.green { color: var(--hai); }
.kv .v.red { color: var(--wan); }
.kv .v.blue { color: var(--mgmt); }
.kv .v.yellow { color: var(--cam); }
/* Detail pane */
.detail-pane {
background: var(--cell);
border-radius: 16px;
padding: 16px 20px;
border-left: 6px solid var(--mgmt);
grid-column: 2; grid-row: 1 / span 3;
overflow-y: auto;
}
.detail-pane h2 {
margin: 0 0 16px 0; font-size: 22px;
letter-spacing: 0.15em; color: var(--mgmt);
}
.svclist { display: flex; flex-direction: column; gap: 6px; }
.svc {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 12px; align-items: center;
background: var(--cell-2); padding: 8px 14px; border-radius: 8px;
font-family: 'JetBrains Mono', monospace; font-size: 12px;
}
.svc .name { color: var(--ink); letter-spacing: 0.1em; }
.svc .meta { color: var(--ink-2); font-size: 10px; }
.actionrow { display: flex; gap: 8px; margin-top: 12px; }
.actionrow button {
background: var(--ink); color: #000; border: none;
padding: 10px 20px; border-radius: 18px;
font-size: 12px; letter-spacing: 0.2em; text-transform: uppercase;
font-weight: 700;
}
.actionrow button.secondary { background: var(--bar-dim); color: var(--ink); }
.actionrow button.danger { background: var(--wan); color: #000; }
.notes {
background: var(--cell-2); border-radius: 12px;
padding: 16px 20px; margin-top: 16px;
border-left: 4px solid var(--mgmt);
font-size: 13px; line-height: 1.6; color: var(--ink-2);
}
.notes strong { color: var(--ink); }
@media (max-width: 900px) {
.console { grid-template-columns: 1fr; }
.topbar, .bottombar { grid-template-columns: 1fr; }
.main { grid-template-columns: 1fr; }
.detail-pane { grid-column: 1; grid-row: auto; }
.leftrail { flex-direction: row; flex-wrap: wrap; }
.pill { flex: 1 1 30%; text-align: center; padding: 10px; font-size: 11px; }
}
</style>
</head>
<body data-palette="classic">
<div class="toolbar">
<div class="group">
<span class="label">Palette</span>
<button class="pal-btn active" data-palette="classic">Classic</button>
<button class="pal-btn" data-palette="complementary">Complementary</button>
</div>
<div class="palette-preview" id="preview"></div>
</div>
<div class="console">
<div class="topbar">
<div class="corner">HAI · 47-A</div>
<div class="title-strip">
<div class="sys-id">ROUTER ID 8B-2F-A1 · SITE LAB · OPENWRT 23.05</div>
<div class="sys-title">BRIDGE OPS</div>
</div>
<div class="clock mono" id="clock">2026.05.01 · 19:27:25</div>
</div>
<div class="leftrail">
<button class="pill active"><span class="num">10</span>OVERVIEW</button>
<button class="pill wan"><span class="num">20</span>WAN</button>
<button class="pill"><span class="num">30</span>VLAN-FW</button>
<button class="pill cam"><span class="num">31</span>CAMS</button>
<button class="pill hai"><span class="num">32</span>HAI</button>
<button class="pill mgmt"><span class="num">33</span>MGMT</button>
<button class="pill"><span class="num">40</span>DHCP/DNS</button>
<button class="pill"><span class="num">50</span>WIFI</button>
<button class="pill mgmt"><span class="num">60</span>TAILSCALE</button>
<button class="pill"><span class="num">70</span>MQTT BR</button>
<button class="pill"><span class="num">80</span>SERVICES</button>
<button class="pill"><span class="num">90</span>GITOPS</button>
<button class="pill"><span class="num">99</span>LOGS</button>
</div>
<div class="main">
<div class="panel wan">
<h3>WAN <span class="id">IF-WAN0</span></h3>
<div class="kv">
<span class="k">STATE</span><span class="v red">UP · DHCP</span>
<span class="k">IPv4</span><span class="v">192.0.2.47/24</span>
<span class="k">GW</span><span class="v">192.0.2.1</span>
<span class="k">UPTIME</span><span class="v">14d 03h</span>
<span class="k">FAILOVER</span><span class="v">mwan3 · LTE standby</span>
</div>
</div>
<div class="panel hai">
<h3>HAI DEVICE <span class="id">VLAN-20</span></h3>
<div class="kv">
<span class="k">DEVICE</span><span class="v green">RK3588 · ONLINE</span>
<span class="k">IP</span><span class="v">192.168.20.10</span>
<span class="k">MQTT</span><span class="v green">CONNECTED</span>
<span class="k">FRIGATE</span><span class="v green">RUNNING · NPU</span>
<span class="k">LAST EVT</span><span class="v">12s ago</span>
</div>
</div>
<div class="panel cam wide">
<h3>CAMERAS <span class="id">VLAN-10 · QUARANTINED</span></h3>
<div class="kv" style="grid-template-columns: 80px 1fr 80px 1fr;">
<span class="k">CAM-01</span><span class="v yellow">192.168.10.11 · RTSP OK</span>
<span class="k">CAM-02</span><span class="v yellow">192.168.10.12 · RTSP OK</span>
<span class="k">CAM-03</span><span class="v yellow">192.168.10.13 · RTSP OK</span>
<span class="k">CAM-04</span><span class="v red">192.168.10.14 · OFFLINE</span>
<span class="k">DNS BLOCK</span><span class="v">847 calls today</span>
<span class="k">EGRESS</span><span class="v red">DENIED · ALL</span>
</div>
</div>
<div class="detail-pane">
<h2 class="mono">▸ OVERVIEW</h2>
<div class="kv" style="margin-bottom: 16px;">
<span class="k">SITE</span><span class="v">site-lab</span>
<span class="k">FLEET</span><span class="v">hai-edge / 4 cams · 1 hai</span>
<span class="k">CONFIG REV</span><span class="v">git: a3f8b21 · clean</span>
<span class="k">LAST PUSH</span><span class="v">2h 14m ago by ops</span>
<span class="k">TS NODE</span><span class="v blue">router-site-lab.tail-net.ts</span>
</div>
<h3 style="font-size: 11px; color: var(--ink-2); letter-spacing: 0.25em; margin: 16px 0 8px;">SUBSYSTEMS</h3>
<div class="svclist">
<div class="svc"><span class="name">dnsmasq</span><span class="meta">14d 03h</span><span class="dot ok"></span></div>
<div class="svc"><span class="name">mosquitto</span><span class="meta">14d 03h</span><span class="dot ok"></span></div>
<div class="svc"><span class="name">mqtt-bridge → hub</span><span class="meta">connected · q:0</span><span class="dot ok"></span></div>
<div class="svc"><span class="name">tailscaled</span><span class="meta">peer × 7</span><span class="dot ok"></span></div>
<div class="svc"><span class="name">chrony (ntp)</span><span class="meta">offset ±2ms</span><span class="dot ok"></span></div>
<div class="svc"><span class="name">node_exporter</span><span class="meta">:9100</span><span class="dot ok"></span></div>
<div class="svc"><span class="name">adblock-dns</span><span class="meta">12.4k entries</span><span class="dot warn"></span></div>
</div>
<div class="actionrow">
<button>RECONCILE</button>
<button class="secondary">PULL CONFIG</button>
<button class="danger">REBOOT</button>
</div>
<div class="notes">
<strong>Palette logic preserved:</strong> dominant warm / cool, accent for warnings, distinct hue per VLAN domain. The complementary variant rotates the wheel 180° but keeps the same semantic mappings — cyan replaces peach as the "nominal" color, mauve becomes the HAI compute domain, royal blue marks the camera quarantine. The same panel reads identically in both modes.
</div>
</div>
</div>
<div class="bottombar">
<div class="corner-bl">STATUS · NOMINAL</div>
<div class="status-strip">
<span class="status-chip"><span class="dot ok"></span>WAN UP</span>
<span class="status-chip"><span class="dot ok"></span>MQTT BR</span>
<span class="status-chip"><span class="dot ok"></span>TS PEERS 7</span>
<span class="status-chip"><span class="dot warn"></span>CAM-04 OFFLINE</span>
<span class="status-chip"><span class="dot ok"></span>GIT CLEAN</span>
<span class="status-chip">CPU 18% · MEM 42% · LOAD 0.31</span>
</div>
</div>
</div>
<script>
// Palette swap
const previewEl = document.getElementById('preview');
function renderPreview() {
const cs = getComputedStyle(document.body);
const tokens = ['--ink', '--wan', '--mgmt', '--cam', '--hai', '--iot'];
previewEl.innerHTML = tokens
.map(t => `<div class="sw" style="background:${cs.getPropertyValue(t).trim()}" title="${t}"></div>`)
.join('');
}
document.querySelectorAll('.pal-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.pal-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.body.dataset.palette = btn.dataset.palette;
renderPreview();
});
});
renderPreview();
// Live clock
function tick() {
const now = new Date();
const pad = n => String(n).padStart(2, '0');
document.getElementById('clock').textContent =
`${now.getFullYear()}.${pad(now.getMonth()+1)}.${pad(now.getDate())} · ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
}
tick(); setInterval(tick, 1000);
</script>
</body>
</html>