docs(mockups): palette comparison (classic vs complementary)
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user