Files
silo-server/docs/design/devices-redesign.html
T

1266 lines
51 KiB
HTML
Raw Normal View History

2026-05-22 20:26:11 -04:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Devices — Silo admin (redesign)</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=Instrument+Sans:ital,wght@0,400..700;1,400..700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<style>
/* -------- design tokens -------- */
:root {
/* surfaces — warm near-black with quiet elevation */
--bg: #0a0a0b;
--bg-elev-1: #111113;
--bg-elev-2: #17171a;
--bg-row-hover: #1c1c20;
--border: #232327;
--border-strong: #2e2e34;
/* type */
--text: #f3f1ec;
--text-2: #a6a39e;
--text-3: #65615d;
--text-4: #3a3835;
/* one strong accent: sodium-vapor amber. Reads as instrument panel,
not "AI gradient". Used sparingly. */
--accent: #f3c969;
--accent-soft: rgba(243, 201, 105, 0.12);
--accent-line: rgba(243, 201, 105, 0.45);
/* status — desaturated to feel like instrumentation */
--good: #84d59a;
--warn: #f3c969;
--bad: #f0846a;
/* fonts */
--serif: "Instrument Serif", "Times New Roman", serif;
--sans: "Instrument Sans", system-ui, -apple-system, sans-serif;
--mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;
--rad: 6px;
--rad-lg: 10px;
}
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
height: 100%; overflow: hidden;
background: var(--bg); color: var(--text);
font-family: var(--sans);
font-size: 13px; line-height: 1.45;
font-feature-settings: "ss01", "cv11";
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
button, input, select { font-family: inherit; color: inherit; }
/* faint film-grain so the dark surfaces have atmosphere instead of looking flat */
.grain {
pointer-events: none;
position: fixed; inset: 0; z-index: 999;
opacity: 0.035; mix-blend-mode: overlay;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
/* -------- layout -------- */
.app {
display: grid;
grid-template-rows: auto auto 1fr;
height: 100vh;
}
/* page header */
.page-h {
padding: 28px 36px 18px;
border-bottom: 1px solid var(--border);
display: grid;
grid-template-columns: 1fr auto;
gap: 32px;
align-items: end;
}
.page-h h1 {
margin: 0;
font-family: var(--serif);
font-weight: 400;
font-size: 64px;
line-height: 0.92;
letter-spacing: -0.025em;
}
.page-h h1 em { font-style: italic; color: var(--accent); }
.page-h p {
margin: 10px 0 0;
max-width: 560px;
color: var(--text-2);
font-size: 13px;
}
.page-h p code { font-family: var(--mono); font-size: 11.5px; color: var(--text); background: var(--bg-elev-1); padding: 1px 5px; border-radius: 3px; border: 1px solid var(--border); }
/* command bar (search + global actions) */
.cmdbar { display: flex; align-items: center; gap: 8px; }
.search {
display: flex; align-items: center; gap: 8px;
width: 340px;
padding: 8px 10px 8px 12px;
background: var(--bg-elev-1);
border: 1px solid var(--border-strong);
border-radius: var(--rad);
color: var(--text-2);
transition: border-color 140ms ease, background 140ms ease;
}
.search:focus-within {
border-color: var(--accent-line);
background: var(--bg-elev-2);
}
.search input {
flex: 1; background: transparent; border: 0; outline: 0;
color: var(--text); font-size: 13px;
}
.search input::placeholder { color: var(--text-3); }
kbd {
font-family: var(--mono);
font-size: 10px;
color: var(--text-3);
border: 1px solid var(--border-strong);
border-bottom-width: 2px;
border-radius: 3px;
padding: 1px 5px;
background: var(--bg);
}
/* -------- fleet pulse strip -------- */
.pulse {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 32px;
align-items: center;
padding: 14px 36px;
border-bottom: 1px solid var(--border);
background: linear-gradient(to bottom, var(--bg-elev-1), var(--bg));
}
.stats { display: flex; gap: 22px; flex-wrap: wrap; align-items: baseline; }
.stat { display: inline-flex; gap: 6px; align-items: baseline; }
.stat-n {
font-family: var(--mono); font-weight: 500;
font-size: 13px; color: var(--text);
}
.stat-n.warn { color: var(--bad); }
.stat-l {
font-size: 10px; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.1em;
}
.pulse-mid { display: flex; align-items: center; gap: 14px; justify-content: center; }
.pulse-mid .label {
font-size: 10px; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.1em;
}
.histogram { display: flex; gap: 2px; align-items: end; height: 28px; }
.histogram .bar {
width: 5px;
background: var(--border-strong);
border-radius: 1px;
transition: background 160ms;
}
.histogram .bar.hi { background: var(--accent); }
.histogram .bar.med { background: var(--text-3); }
.pivots { display: flex; justify-content: end; gap: 8px; flex-wrap: wrap; }
.seg {
display: inline-flex;
border: 1px solid var(--border-strong);
border-radius: var(--rad);
overflow: hidden;
background: var(--bg-elev-1);
}
.seg button {
padding: 5px 10px;
background: transparent; color: var(--text-2);
border: 0;
border-right: 1px solid var(--border);
font-size: 11.5px;
cursor: pointer;
transition: background 140ms, color 140ms;
}
.seg button:last-child { border-right: 0; }
.seg button:hover:not(.active) { background: var(--bg-row-hover); color: var(--text); }
.seg button.active {
background: var(--accent-soft);
color: var(--accent);
}
.seg .seg-l {
padding: 5px 8px 5px 10px;
color: var(--text-3);
font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
border-right: 1px solid var(--border);
align-self: center;
}
/* -------- main 3-pane -------- */
.main {
display: grid;
grid-template-columns: 240px 380px 1fr;
overflow: hidden;
min-height: 0;
}
.pane { overflow-y: auto; min-height: 0; }
.pane + .pane { border-left: 1px solid var(--border); }
/* scrollbar */
.pane::-webkit-scrollbar { width: 8px; height: 8px; }
.pane::-webkit-scrollbar-track { background: transparent; }
.pane::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
.pane::-webkit-scrollbar-thumb:hover { background: var(--text-3); }
/* -------- filters (left) -------- */
.filters { padding: 22px 18px; }
.filter-grp { margin-bottom: 26px; }
.filter-h {
display: flex; justify-content: space-between; align-items: center;
font-size: 10px; text-transform: uppercase; letter-spacing: 0.12em;
color: var(--text-3); font-weight: 600;
margin-bottom: 10px;
}
.filter-h .clear {
font-family: var(--serif); font-style: italic;
text-transform: none; letter-spacing: 0;
font-size: 12px; color: var(--text-3);
cursor: pointer;
}
.filter-h .clear:hover { color: var(--accent); }
.facet {
display: flex; align-items: center; justify-content: space-between;
padding: 5px 2px;
cursor: pointer;
color: var(--text-2);
transition: color 140ms;
}
.facet:hover { color: var(--text); }
.facet-l { display: flex; align-items: center; gap: 9px; font-size: 12.5px; }
.facet-l input[type=checkbox] {
appearance: none;
width: 13px; height: 13px;
border: 1px solid var(--border-strong);
border-radius: 3px;
background: var(--bg-elev-1);
position: relative; cursor: pointer;
transition: all 140ms;
}
.facet-l input:checked {
background: var(--accent);
border-color: var(--accent);
}
.facet-l input:checked::after {
content: "";
position: absolute;
left: 3.5px; top: 0.5px;
width: 4px; height: 8px;
border: solid var(--bg);
border-width: 0 1.6px 1.6px 0;
transform: rotate(45deg);
}
.facet-c {
font-family: var(--mono); font-size: 10.5px;
color: var(--text-3);
}
.saved-view {
display: flex; align-items: center; gap: 8px;
padding: 6px 8px;
margin: 0 -8px;
border-radius: 4px;
cursor: pointer;
color: var(--text-2);
transition: background 140ms, color 140ms;
}
.saved-view:hover { background: var(--bg-elev-1); color: var(--text); }
.saved-view svg { color: var(--text-3); }
.saved-view .count {
margin-left: auto;
font-family: var(--mono); font-size: 10.5px;
color: var(--text-3);
}
.saved-view.flag svg { color: var(--bad); }
/* -------- device list (middle) -------- */
.list-toolbar {
position: sticky; top: 0; z-index: 2;
display: flex; justify-content: space-between; align-items: center;
padding: 12px 18px;
background: var(--bg);
border-bottom: 1px solid var(--border);
font-size: 11px; color: var(--text-3);
text-transform: uppercase; letter-spacing: 0.08em;
}
.list-toolbar .lt-r { display: flex; gap: 12px; align-items: center; }
.list-toolbar a { color: var(--text-2); cursor: pointer; }
.list-toolbar a:hover { color: var(--text); }
.group {
border-bottom: 1px solid var(--border);
}
.group-h {
position: sticky; top: 41px; z-index: 1;
display: grid;
grid-template-columns: 16px 1fr auto;
gap: 10px;
align-items: center;
padding: 11px 18px 10px;
background: var(--bg-elev-1);
cursor: pointer;
transition: background 140ms;
}
.group-h:hover { background: var(--bg-elev-2); }
.group-h .chev {
transition: transform 160ms;
color: var(--text-3);
}
.group.open .group-h .chev { transform: rotate(90deg); }
.group-name {
font-weight: 500; font-size: 13px; color: var(--text);
display: flex; align-items: center; gap: 8px;
}
.group-name .anom-tag {
display: inline-flex; align-items: center; gap: 4px;
padding: 1px 6px;
border-radius: 3px;
background: rgba(240, 132, 106, 0.1);
color: var(--bad);
font-size: 10px;
letter-spacing: 0.04em;
}
.group-meta {
grid-column: 2 / 4;
display: flex; gap: 12px;
font-family: var(--mono); font-size: 10.5px; color: var(--text-3);
margin-top: 1px;
}
.group-meta span + span { padding-left: 12px; border-left: 1px solid var(--border-strong); }
.group .devices { display: none; }
.group.open .devices { display: block; }
.device {
display: grid;
grid-template-columns: 28px 1fr auto;
gap: 12px; align-items: center;
padding: 11px 18px 11px 36px;
cursor: pointer;
border-left: 2px solid transparent;
transition: background 120ms, border-color 120ms;
position: relative;
}
.device:hover { background: var(--bg-elev-1); }
.device.active {
background: var(--bg-elev-1);
border-left-color: var(--accent);
}
.device-icon {
width: 28px; height: 28px;
border-radius: 6px;
background: var(--bg-elev-2);
border: 1px solid var(--border);
display: grid; place-items: center;
color: var(--text-2);
transition: all 140ms;
}
.device.active .device-icon {
background: var(--accent-soft);
color: var(--accent);
border-color: var(--accent-line);
}
.device-info .name {
font-size: 13px; color: var(--text); font-weight: 500;
display: flex; align-items: center; gap: 6px;
}
.device-info .name .anom {
width: 5px; height: 5px; border-radius: 50%;
background: var(--bad);
box-shadow: 0 0 0 3px rgba(240, 132, 106, 0.15);
}
.device-info .meta {
font-family: var(--mono); font-size: 10.5px;
color: var(--text-3); margin-top: 2px;
display: flex; gap: 8px; align-items: center;
}
.device-info .meta .sep { color: var(--border-strong); }
.device-info .meta .last { color: var(--text-3); }
.device-info .meta .last.fresh { color: var(--good); }
.device-r { display: flex; flex-direction: column; align-items: end; gap: 4px; }
.ovr-gauge { display: inline-flex; align-items: center; gap: 4px; }
.ovr-bars { display: inline-flex; gap: 2px; }
.ovr-bars span {
width: 3px; height: 12px;
background: var(--border-strong);
border-radius: 1px;
transition: background 160ms;
}
.ovr-bars span.on { background: var(--accent); }
.ovr-bars span.dim { background: var(--text-4); }
.ovr-num {
font-family: var(--mono);
font-size: 11px;
color: var(--text-2);
min-width: 14px; text-align: right;
}
.device.active .ovr-num { color: var(--accent); }
/* -------- detail (right) -------- */
.detail { padding: 28px 36px 60px; }
.detail-h {
display: flex; align-items: start; justify-content: space-between;
gap: 24px;
padding-bottom: 22px;
border-bottom: 1px solid var(--border);
}
.detail-title { display: flex; gap: 16px; align-items: start; }
.detail-icon {
width: 52px; height: 52px;
border-radius: 12px;
background: linear-gradient(135deg, var(--bg-elev-2), var(--bg-elev-1));
border: 1px solid var(--border-strong);
display: grid; place-items: center;
color: var(--accent);
}
.detail-name {
font-family: var(--serif);
font-weight: 400;
font-size: 36px;
line-height: 1;
letter-spacing: -0.015em;
}
.detail-meta {
display: flex; flex-wrap: wrap; gap: 10px;
margin-top: 10px; align-items: center;
color: var(--text-3); font-size: 12px;
}
.detail-meta .id {
font-family: var(--mono); font-size: 11px; color: var(--text-2);
padding: 2px 8px;
border: 1px solid var(--border);
border-radius: 99px;
background: var(--bg-elev-1);
}
.detail-meta .pill {
padding: 2px 9px;
border: 1px solid var(--border);
border-radius: 99px;
color: var(--text-2);
font-size: 11px;
}
.detail-meta .pill svg { display: inline-block; margin-right: 4px; vertical-align: -1px; }
.detail-meta .live { color: var(--good); }
.detail-meta .live::before {
content: ""; display: inline-block;
width: 6px; height: 6px; border-radius: 50%;
background: var(--good);
box-shadow: 0 0 0 3px rgba(132, 213, 154, 0.18);
margin-right: 6px; vertical-align: 1px;
}
.detail-actions { display: flex; gap: 8px; }
.btn {
padding: 7px 12px;
background: var(--bg-elev-1);
color: var(--text-2);
border: 1px solid var(--border-strong);
border-radius: var(--rad);
font-size: 12px;
cursor: pointer;
transition: all 140ms;
display: inline-flex; gap: 6px; align-items: center;
}
.btn:hover { color: var(--text); border-color: var(--text-3); background: var(--bg-elev-2); }
.btn-warn { color: var(--bad); border-color: rgba(240, 132, 106, 0.3); }
.btn-warn:hover { color: var(--bad); background: rgba(240, 132, 106, 0.08); border-color: var(--bad); }
/* summary strip — instrument readout */
.readout {
margin-top: 22px;
padding: 16px 20px;
background: var(--bg-elev-1);
border: 1px solid var(--border);
border-left: 2px solid var(--accent);
border-radius: var(--rad-lg);
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}
.readout-cell .l {
font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.12em;
color: var(--text-3);
}
.readout-cell .v {
font-family: var(--mono); font-size: 15px;
color: var(--text); margin-top: 4px;
}
.readout-cell .v.accent { color: var(--accent); }
.readout-cell .sub {
font-size: 11px; color: var(--text-3); margin-top: 2px;
}
/* profile tabs */
.tabs {
display: flex; gap: 0;
margin-top: 28px;
border-bottom: 1px solid var(--border);
}
.tab {
padding: 11px 16px;
background: transparent; border: 0;
color: var(--text-2);
font-size: 13px;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
display: inline-flex; align-items: center; gap: 8px;
transition: color 140ms;
}
.tab .badge {
font-family: var(--mono); font-size: 10px;
color: var(--text-3);
background: var(--bg-elev-1);
padding: 1px 6px; border-radius: 3px;
border: 1px solid var(--border);
}
.tab .avatar {
width: 16px; height: 16px;
border-radius: 50%;
background: var(--bg-elev-2);
border: 1px solid var(--border-strong);
display: grid; place-items: center;
font-size: 9px; color: var(--text-2);
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--text); border-bottom-color: var(--accent); }
.tab.active .badge {
color: var(--accent);
background: var(--accent-soft);
border-color: var(--accent-line);
}
.profile-meta {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 0 10px;
color: var(--text-3); font-size: 11.5px;
}
.profile-meta .id {
font-family: var(--mono);
display: inline-flex; gap: 6px; align-items: center;
}
.profile-meta .id::before {
content: ""; width: 6px; height: 6px; border-radius: 50%;
background: var(--accent);
}
.reset-all {
display: inline-flex; gap: 5px; align-items: center;
color: var(--text-3); cursor: pointer;
font-size: 11.5px;
transition: color 140ms;
}
.reset-all:hover { color: var(--text); }
/* override rows */
.ovr {
display: grid;
grid-template-columns: 1fr 240px;
gap: 32px;
padding: 18px 0;
border-bottom: 1px solid var(--border);
align-items: start;
}
.ovr:last-child { border-bottom: 0; }
.ovr-name {
font-size: 14px;
font-weight: 500;
color: var(--text);
display: flex; align-items: center; gap: 8px;
}
.ovr-name .flag {
font-size: 9.5px;
text-transform: uppercase; letter-spacing: 0.08em;
padding: 1px 6px;
border-radius: 3px;
background: rgba(240, 132, 106, 0.1);
color: var(--bad);
}
.ovr-key {
font-family: var(--mono); font-size: 11px;
color: var(--text-3); margin-top: 3px;
}
.ovr-desc {
color: var(--text-2); font-size: 12.5px;
margin-top: 8px; max-width: 60ch;
}
.ovr-state {
margin-top: 10px;
font-family: var(--mono);
font-size: 11px; color: var(--text-3);
display: flex; gap: 8px; align-items: center;
}
.ovr-state em { font-style: normal; color: var(--text); }
.ovr-state .dot { width: 4px; height: 4px; border-radius: 50%; background: var(--text-4); }
/* control side */
.ovr-c { display: flex; flex-direction: column; align-items: end; gap: 8px; }
.toggle {
width: 34px; height: 20px;
border-radius: 99px;
background: var(--border-strong);
border: 0; padding: 2px;
cursor: pointer; position: relative;
transition: background 200ms;
}
.toggle::after {
content: "";
display: block;
width: 16px; height: 16px;
border-radius: 50%;
background: var(--text-2);
transition: transform 200ms cubic-bezier(.5,1.4,.5,1), background 200ms;
}
.toggle.on { background: var(--accent); }
.toggle.on::after { transform: translateX(14px); background: var(--bg); }
.select {
padding: 7px 30px 7px 12px;
background: var(--bg-elev-1);
color: var(--text);
border: 1px solid var(--border-strong);
border-radius: var(--rad);
font-size: 12px;
cursor: pointer;
appearance: none;
background-image:
linear-gradient(45deg, transparent 50%, var(--text-2) 50%),
linear-gradient(135deg, var(--text-2) 50%, transparent 50%);
background-position:
calc(100% - 14px) 50%,
calc(100% - 9px) 50%;
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
min-width: 160px;
transition: border-color 140ms;
}
.select:hover { border-color: var(--text-3); }
.select:focus { outline: 0; border-color: var(--accent-line); }
.reset {
display: inline-flex; gap: 4px; align-items: center;
color: var(--text-3); cursor: pointer;
font-size: 11px;
transition: color 140ms;
}
.reset:hover { color: var(--text); }
.reset.disabled { opacity: 0.4; cursor: default; }
.json-btn {
padding: 6px 11px;
background: var(--bg);
color: var(--text-2);
border: 1px solid var(--border-strong);
border-radius: var(--rad);
font-family: var(--mono); font-size: 11px;
cursor: pointer;
transition: all 140ms;
}
.json-btn:hover { color: var(--accent); border-color: var(--accent-line); }
/* mini-graph: small bar charts inline */
.inline-bars {
display: inline-flex; gap: 2px; vertical-align: -2px;
margin-left: 4px;
}
.inline-bars span {
width: 3px;
background: var(--text-3);
border-radius: 1px;
}
/* page-load reveal */
@keyframes rise {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.page-h, .pulse, .filters, .list-toolbar, .group, .detail {
animation: rise 520ms cubic-bezier(.2,.7,.2,1) both;
}
.pulse { animation-delay: 60ms; }
.filters { animation-delay: 120ms; }
.list-toolbar { animation-delay: 160ms; }
.group { animation-delay: 200ms; }
.detail { animation-delay: 240ms; }
</style>
</head>
<body>
<div class="grain"></div>
<div class="app">
<!-- ============ HEADER ============ -->
<header class="page-h">
<div>
<h1>Devices<em>.</em></h1>
<p>
Inspect, tune, and reset per-profile playback overrides across the fleet.
Filter by user, platform, or override pattern. Press <code>⌘K</code> to jump anywhere.
</p>
</div>
<div class="cmdbar">
<label class="search">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>
<input placeholder="Search devices, users, IDs, override keys…" />
<kbd>⌘K</kbd>
</label>
</div>
</header>
<!-- ============ FLEET PULSE ============ -->
<section class="pulse">
<div class="stats">
<div class="stat"><span class="stat-n">9</span><span class="stat-l">users</span></div>
<div class="stat"><span class="stat-n">28</span><span class="stat-l">devices</span></div>
<div class="stat"><span class="stat-n">33</span><span class="stat-l">profiles</span></div>
<div class="stat"><span class="stat-n">85</span><span class="stat-l">overrides</span></div>
<div class="stat"><span class="stat-n warn">3</span><span class="stat-l">anomalies</span></div>
</div>
<div class="pulse-mid">
<span class="label">override density · 28 devices</span>
<div class="histogram" id="histo"></div>
</div>
<div class="pivots">
<div class="seg">
<span class="seg-l">view</span>
<button class="active">Devices</button>
<button>Overrides</button>
<button>Platforms</button>
</div>
<div class="seg">
<span class="seg-l">group by</span>
<button class="active">User</button>
<button>Platform</button>
<button>Activity</button>
</div>
</div>
</section>
<!-- ============ THREE-PANE MAIN ============ -->
<main class="main">
<!-- ---- Filters ---- -->
<aside class="pane filters">
<div class="filter-grp">
<div class="filter-h">
Saved views
<span class="clear">manage</span>
</div>
<div class="saved-view flag">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
Anomalies <span class="count">3</span>
</div>
<div class="saved-view">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
Updated 7d <span class="count">11</span>
</div>
<div class="saved-view">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
HDR-related <span class="count">6</span>
</div>
<div class="saved-view">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
Subtitle custom <span class="count">4</span>
</div>
<div class="saved-view">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v8M8 12h8"/></svg>
Dormant 30d <span class="count">5</span>
</div>
</div>
<div class="filter-grp">
<div class="filter-h">Platform <span class="clear">clear</span></div>
<label class="facet"><span class="facet-l"><input type="checkbox" checked /> tvOS</span><span class="facet-c">12</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> iOS</span><span class="facet-c">8</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> macOS Web</span><span class="facet-c">5</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> android-tv</span><span class="facet-c">3</span></label>
</div>
<div class="filter-grp">
<div class="filter-h">Override count</div>
<label class="facet"><span class="facet-l"><input type="checkbox" /> none</span><span class="facet-c">4</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> 12</span><span class="facet-c">11</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" checked /> 35</span><span class="facet-c">9</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> 6+</span><span class="facet-c">4</span></label>
</div>
<div class="filter-grp">
<div class="filter-h">Last seen</div>
<label class="facet"><span class="facet-l"><input type="checkbox" /> &lt; 24h</span><span class="facet-c">7</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> &lt; 7d</span><span class="facet-c">14</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> &lt; 30d</span><span class="facet-c">22</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> &gt; 30d</span><span class="facet-c">5</span></label>
</div>
<div class="filter-grp">
<div class="filter-h">Override key</div>
<label class="facet"><span class="facet-l"><input type="checkbox" /> playback.*</span><span class="facet-c">38</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> player.*</span><span class="facet-c">27</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> subtitle_*</span><span class="facet-c">12</span></label>
<label class="facet"><span class="facet-l"><input type="checkbox" /> hdr_*</span><span class="facet-c">8</span></label>
</div>
</aside>
<!-- ---- Device list ---- -->
<aside class="pane">
<div class="list-toolbar">
<span>28 devices · 11 shown</span>
<span class="lt-r">
<a>Select</a><span style="color:var(--border-strong)">·</span><a>Sort: activity</a>
</span>
</div>
<!-- admin -->
<div class="group open">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">
admin
<span class="anom-tag">2 anomalies</span>
</div>
<div class="ovr-num">5</div>
<div class="group-meta">
<span>5 devices</span><span>17 overrides</span><span>updated 18h</span>
</div>
</div>
<div class="devices">
<div class="device active" data-d="a1">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
<div class="device-info">
<div class="name">Apple TV 4K (3rd gen) <span class="anom" title="anomaly: Profile 7 fallback diverges from fleet"></span></div>
<div class="meta">tvOS 17.4<span class="sep">·</span>973df8c5<span class="sep">·</span><span class="last fresh">2d</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span></span>
<span class="ovr-num">7</span>
</div>
</div>
</div>
<div class="device" data-d="a2">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
<div class="device-info">
<div class="name">Apple TV 4K (3rd gen)</div>
<div class="meta">tvOS 17.4<span class="sep">·</span>4a08e2c9<span class="sep">·</span><span class="last">11d</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span class="on"></span><span></span><span></span></span>
<span class="ovr-num">3</span>
</div>
</div>
</div>
<div class="device" data-d="a3">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
<div class="device-info">
<div class="name">NVIDIA SHIELD Android TV <span class="anom"></span></div>
<div class="meta">android-tv<span class="sep">·</span>c7f1d0b4<span class="sep">·</span><span class="last">3d</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span></span>
<span class="ovr-num">5</span>
</div>
</div>
</div>
<div class="device" data-d="a4">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="4" width="20" height="13" rx="2"/><path d="M2 8h20M6 13h8"/></svg></div>
<div class="device-info">
<div class="name">Chrome on macOS</div>
<div class="meta">macOS Web<span class="sep">·</span>a09b3e21<span class="sep">·</span><span class="last fresh">4h</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span></span><span></span><span></span></span>
<span class="ovr-num">2</span>
</div>
</div>
</div>
<div class="device" data-d="a5">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="6" y="2" width="12" height="20" rx="2.5"/><path d="M11 19h2"/></svg></div>
<div class="device-info">
<div class="name">iPhone 17 Pro</div>
<div class="meta">iOS 18.4<span class="sep">·</span>5d2afc18<span class="sep">·</span><span class="last fresh">2h</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span><span></span></span>
<span class="ovr-num">4</span>
</div>
</div>
</div>
</div>
</div>
<!-- MeM -->
<div class="group open">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">MeM</div>
<div class="ovr-num">2</div>
<div class="group-meta"><span>2 devices</span><span>9 overrides</span><span>updated 2d</span></div>
</div>
<div class="devices">
<div class="device" data-d="m1">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
<div class="device-info">
<div class="name">Apple TV</div>
<div class="meta">tvOS 17.2<span class="sep">·</span>2b9efa01<span class="sep">·</span><span class="last">2d</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span class="on"></span><span></span><span></span></span>
<span class="ovr-num">3</span>
</div>
</div>
</div>
<div class="device" data-d="m2">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="6" y="2" width="12" height="20" rx="2.5"/><path d="M11 19h2"/></svg></div>
<div class="device-info">
<div class="name">iPhone <span class="anom"></span></div>
<div class="meta">iOS 17.5<span class="sep">·</span>e1c8db70<span class="sep">·</span><span class="last">9d</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span><span class="on"></span></span>
<span class="ovr-num">6</span>
</div>
</div>
</div>
</div>
</div>
<!-- user@example.com -->
<div class="group open">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">user@example.com</div>
<div class="ovr-num">1</div>
<div class="group-meta"><span>1 device</span><span>1 override</span><span>updated 1d</span></div>
</div>
<div class="devices">
<div class="device" data-d="n1">
<div class="device-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
<div class="device-info">
<div class="name">Apple TV</div>
<div class="meta">tvOS 17.4<span class="sep">·</span>fa3c1e88<span class="sep">·</span><span class="last fresh">1d</span></div>
</div>
<div class="device-r">
<div class="ovr-gauge">
<span class="ovr-bars"><span class="on"></span><span></span><span></span><span></span><span></span></span>
<span class="ovr-num">1</span>
</div>
</div>
</div>
</div>
</div>
<!-- Olek -->
<div class="group">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">Olek</div>
<div class="ovr-num">2</div>
<div class="group-meta"><span>2 devices</span><span>4 overrides</span><span>updated 2d</span></div>
</div>
</div>
<!-- Plexshare973 -->
<div class="group">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">Plexshare973
<span class="anom-tag">1 anomaly</span>
</div>
<div class="ovr-num">4</div>
<div class="group-meta"><span>4 devices</span><span>13 overrides</span><span>updated 1d</span></div>
</div>
</div>
<!-- plexy -->
<div class="group">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">plexy</div>
<div class="ovr-num">1</div>
<div class="group-meta"><span>1 device</span><span>2 overrides</span><span>updated 1d</span></div>
</div>
</div>
<!-- roblenz -->
<div class="group">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">roblenz</div>
<div class="ovr-num">3</div>
<div class="group-meta"><span>3 devices</span><span>11 overrides</span><span>updated 4h</span></div>
</div>
</div>
<!-- spawnster -->
<div class="group">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">spawnster</div>
<div class="ovr-num">4</div>
<div class="group-meta"><span>4 devices</span><span>14 overrides</span><span>updated 8h</span></div>
</div>
</div>
<!-- guest -->
<div class="group">
<div class="group-h">
<svg class="chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m9 18 6-6-6-6"/></svg>
<div class="group-name">guest</div>
<div class="ovr-num">6</div>
<div class="group-meta"><span>6 devices</span><span>14 overrides</span><span>updated 3d</span></div>
</div>
</div>
</aside>
<!-- ---- Detail ---- -->
<section class="pane detail">
<div class="detail-h">
<div class="detail-title">
<div class="detail-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
</div>
<div>
<div class="detail-name">Apple TV 4K</div>
<div class="detail-meta">
<span class="id">973df8c5-7ada-4896-8d7b-5120b1fa04e8</span>
<span class="pill">tvOS 17.4</span>
<span class="pill">admin <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 17 17 7M7 7h10v10"/></svg></span>
<span class="pill live">live · 2h</span>
</div>
</div>
</div>
<div class="detail-actions">
<button class="btn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3.5-7.1"/><path d="M21 4v5h-5"/></svg>
Compare to fleet
</button>
<button class="btn btn-warn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
Reset all 11
</button>
</div>
</div>
<!-- instrument readout -->
<div class="readout">
<div class="readout-cell">
<div class="l">Overrides</div>
<div class="v accent">11 <span style="color:var(--text-3); font-size: 11px">/ 24 fields</span></div>
<div class="sub">across 3 profiles</div>
</div>
<div class="readout-cell">
<div class="l">Profiles touched</div>
<div class="v">admin · Test · aaa</div>
<div class="sub">7 / 2 / 2 overrides</div>
</div>
<div class="readout-cell">
<div class="l">Anomaly</div>
<div class="v" style="color: var(--bad)">Profile 7 fallback</div>
<div class="sub">no other tvOS device disables it</div>
</div>
<div class="readout-cell">
<div class="l">Last update</div>
<div class="v">2d 04h</div>
<div class="sub">subtitle_appearance · admin</div>
</div>
</div>
<!-- profile tabs -->
<div class="tabs">
<button class="tab active"><span class="avatar">A</span>admin <span class="badge">7</span></button>
<button class="tab"><span class="avatar">T</span>Test <span class="badge">2</span></button>
<button class="tab"><span class="avatar">a</span>aaaaaaaa <span class="badge">2</span></button>
</div>
<div class="profile-meta">
<span class="id">8bf465bc — 3a0b — 4cca — 87b9 — 4a1473890be6 · 7 overrides</span>
<span class="reset-all">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
Reset profile overrides
</span>
</div>
<div class="ovrs">
<!-- 1 — anomaly -->
<div class="ovr">
<div>
<div class="ovr-name">Profile 7 HDR10 fallback <span class="flag">anomaly</span></div>
<div class="ovr-key">player.dv_profile7_hdr10_fallback</div>
<div class="ovr-desc">Play Dolby Vision Profile 7 as the HDR10 base layer on this device.</div>
<div class="ovr-state"><em>Disabled</em><span class="dot"></span>fleet default <em>Enabled</em><span class="dot"></span>last 2d ago</div>
</div>
<div class="ovr-c">
<button class="toggle" aria-label="toggle"></button>
<span class="reset"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg> reset</span>
</div>
</div>
<!-- 2 -->
<div class="ovr">
<div>
<div class="ovr-name">Auto-play next episode</div>
<div class="ovr-key">playback.auto_play_next</div>
<div class="ovr-desc">Start the next episode automatically on this device after the current one ends.</div>
<div class="ovr-state"><em>Enabled</em><span class="dot"></span>matches default<span class="dot"></span>last 11d ago</div>
</div>
<div class="ovr-c">
<button class="toggle on" aria-label="toggle"></button>
<span class="reset"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg> reset</span>
</div>
</div>
<!-- 3 -->
<div class="ovr">
<div>
<div class="ovr-name">Subtitle appearance</div>
<div class="ovr-key">subtitle_appearance</div>
<div class="ovr-desc">Customize subtitles on this device — font, size, edge style, and background. Stored as a JSON document; edit inline for fine-grained control.</div>
<div class="ovr-state"><em>Custom</em><span class="dot"></span>5 fields differ from default<span class="dot"></span>last 2d ago</div>
</div>
<div class="ovr-c">
<button class="json-btn">{ } Edit JSON</button>
<span class="reset"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg> reset</span>
</div>
</div>
<!-- 4 -->
<div class="ovr">
<div>
<div class="ovr-name">Playback speed</div>
<div class="ovr-key">player.playback_speed</div>
<div class="ovr-desc">Choose the default playback speed for this device.</div>
<div class="ovr-state"><em>1×</em><span class="dot"></span>matches default<span class="dot"></span>last 18d ago</div>
</div>
<div class="ovr-c">
<select class="select"><option>1× (Normal)</option><option>1.25×</option><option>1.5×</option><option>2×</option></select>
<span class="reset disabled">reset</span>
</div>
</div>
<!-- 5 -->
<div class="ovr">
<div>
<div class="ovr-name">Video fit</div>
<div class="ovr-key">player.video_gravity</div>
<div class="ovr-desc">Control how video should fill the screen on this device.</div>
<div class="ovr-state"><em>Fit</em><span class="dot"></span>matches default<span class="dot"></span>last 14d ago</div>
</div>
<div class="ovr-c">
<select class="select"><option>Fit</option><option>Fill</option><option>Stretch</option></select>
<span class="reset disabled">reset</span>
</div>
</div>
<!-- 6 -->
<div class="ovr">
<div>
<div class="ovr-name">Preferred audio language</div>
<div class="ovr-key">playback.audio_language</div>
<div class="ovr-desc">Choose which spoken language Silo should prefer first on this device.</div>
<div class="ovr-state"><em>English</em><span class="dot"></span>fleet default <em>Auto</em><span class="dot"></span>last 7d ago</div>
</div>
<div class="ovr-c">
<select class="select"><option>English</option><option>Auto</option><option>Original</option><option>Japanese</option></select>
<span class="reset"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg> reset</span>
</div>
</div>
<!-- 7 -->
<div class="ovr">
<div>
<div class="ovr-name">Preferred quality</div>
<div class="ovr-key">playback.preferred_quality</div>
<div class="ovr-desc">Pick the quality Silo should prefer on this device for the current profile.</div>
<div class="ovr-state"><em>2160p / 4K</em><span class="dot"></span>fleet default <em>Auto</em><span class="dot"></span>last 7d ago</div>
</div>
<div class="ovr-c">
<select class="select"><option>2160p / 4K</option><option>1080p</option><option>720p</option><option>Auto</option></select>
<span class="reset"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg> reset</span>
</div>
</div>
</div>
</section>
</main>
</div>
<script>
/* generate the override-density histogram (28 devices) */
const counts = [7,3,5,2,4,3,6,1,2,1,4,3,2,5,2,1,0,3,4,2,1,3,5,2,1,2,3,4];
const max = Math.max(...counts);
const histo = document.getElementById("histo");
counts.forEach(n => {
const b = document.createElement("div");
b.className = "bar" + (n >= 5 ? " hi" : n >= 2 ? " med" : "");
b.style.height = (Math.max(n / max, 0.06) * 100) + "%";
histo.appendChild(b);
});
/* simple selection */
document.querySelectorAll(".device").forEach(d => {
d.addEventListener("click", () => {
document.querySelectorAll(".device.active").forEach(x => x.classList.remove("active"));
d.classList.add("active");
});
});
/* group expand/collapse */
document.querySelectorAll(".group-h").forEach(h => {
h.addEventListener("click", () => h.parentElement.classList.toggle("open"));
});
/* toggle interactivity */
document.querySelectorAll(".toggle").forEach(t => {
t.addEventListener("click", () => t.classList.toggle("on"));
});
/* tab swap */
document.querySelectorAll(".tab").forEach(t => {
t.addEventListener("click", () => {
document.querySelectorAll(".tab.active").forEach(x => x.classList.remove("active"));
t.classList.add("active");
});
});
/* segment toggles (visual only) */
document.querySelectorAll(".seg").forEach(seg => {
seg.querySelectorAll("button").forEach(b => {
b.addEventListener("click", () => {
seg.querySelectorAll("button.active").forEach(x => x.classList.remove("active"));
b.classList.add("active");
});
});
});
/* ⌘K focus */
document.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
document.querySelector(".search input").focus();
}
});
</script>
</body>
</html>