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

1266 lines
51 KiB
HTML
Raw Permalink 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">
<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>