/* Tokens, names and values are shared with the desktop app and the Flutter app.
   docs/DESIGN-TOKENS.md is the arbiter; change a value there and in all three. */
:root {
  --bg: #f4f5f8; --bg-2: #eceef2; --panel: #ffffff; --panel-2: #f6f7f9;
  --border: #e4e7ec; --border-soft: #edeff2;
  --text: #1f2430; --text-dim: #434b5b; --text-faint: #606878;
  --accent: #2f6bf0; --accent-2: #6366f1; --accent-soft: #e9eefe;
  --up: #16a34a; --danger: #e5484d; --gw: #d97706; --cyan: #0d6d78;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
  --r: 10px;
  --zebra: rgba(0,0,0,.022);
  --card-shadow: 0 1px 2px rgba(16,24,40,.06);
  /* Native controls — checkbox, select, scrollbar — read this, not our tokens. */
  color-scheme: light;
}
/* This is a change of default, stated plainly: the dashboard was dark-only, and
   now it follows the operator's OS. Anyone on a light desktop gets a light
   dashboard on the next load, which is the point of adding the theme — but it
   will surprise someone. .light/.dark on <html> pins it either way. */
@media (prefers-color-scheme: dark) {
  :root:not(.light) {
    --bg: #0f1420; --bg-2: #151b29; --panel: #161c2b; --panel-2: #1c2434;
    --border: #283143; --border-soft: #212a3b;
    --text: #e6e9ef; --text-dim: #abb4d0; --text-faint: #858eaa;
    --accent: #4f8cff; --accent-2: #8b5cf6; --accent-soft: rgba(79,140,255,.14);
    --up: #34d399; --danger: #f87171; --gw: #fbbf24; --cyan: #2dd4bf;
    --zebra: rgba(255,255,255,.012);
    --card-shadow: inset 0 1px 0 rgba(255,255,255,.03), 0 1px 2px rgba(0,0,0,.3);
    color-scheme: dark;
  }
}
:root.dark {
  --bg: #0f1420; --bg-2: #151b29; --panel: #161c2b; --panel-2: #1c2434;
  --border: #283143; --border-soft: #212a3b;
  --text: #e6e9ef; --text-dim: #abb4d0; --text-faint: #858eaa;
  --accent: #4f8cff; --accent-2: #8b5cf6; --accent-soft: rgba(79,140,255,.14);
  --up: #34d399; --danger: #f87171; --gw: #fbbf24; --cyan: #2dd4bf;
  --zebra: rgba(255,255,255,.012);
  --card-shadow: inset 0 1px 0 rgba(255,255,255,.03), 0 1px 2px rgba(0,0,0,.3);
  color-scheme: dark;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text);
  font-family: var(--sans); font-size: 14px;
  overflow-x: hidden; }
.hidden { display: none !important; }
#view-settings.settings-loading { visibility: hidden; }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.muted { color: var(--text-dim); } .mono { font-family: var(--mono); }
.ok { color: var(--up); } .bad { color: var(--danger); }
.err { color: var(--danger); min-height: 1.2em; margin-top: 8px; font-size: 13px; }

button { font: inherit; cursor: pointer; border-radius: var(--r); border: 1px solid var(--border);
  background: var(--panel-2); color: var(--text); padding: 8px 14px; transition: border-color .12s; }
button:hover { border-color: var(--accent); }
button.primary { background: var(--accent); border-color: var(--accent); color: #ffffff; font-weight: 600; }
button.primary:hover { filter: brightness(1.08); }
button.link { background: none; border: none; color: var(--accent); padding: 0; }
button:disabled { opacity: .55; cursor: default; }
/* Checkboxes and radios are excluded: this rule is written for text fields, and
   giving a native checkbox a border/padding makes Chrome drop its built-in
   rendering the moment :focus recolours the border — the box then jumps from
   13px to the padded size mid-click. */
input:not([type=checkbox]):not([type=radio]), select, textarea { font: inherit; background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r); padding: 8px 10px; width: 100%; transition: border-color .12s, box-shadow .12s; }
input:not([type=checkbox]):not([type=radio]):focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); outline: none; }
input[type=checkbox], input[type=radio] { width: auto; accent-color: var(--accent); }
input[type=checkbox]:focus-visible, input[type=radio]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
textarea { font-family: var(--mono); font-size: 13px; resize: vertical; }
label { display: block; color: var(--text-dim); font-size: 12.5px; margin: 14px 0 6px; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r); }
a { color: var(--accent); }

/* login */
.login { position: fixed; inset: 0; display: grid; place-items: center;
  background: radial-gradient(680px 460px at 50% 32%, var(--bg-2), var(--bg)); }
.login-card { width: min(400px, 92vw); background: var(--panel); border: 1px solid var(--border);
  border-radius: 16px; padding: 36px 32px;
  box-shadow: 0 24px 70px rgba(0,0,0,.55); display: flex; flex-direction: column; gap: 4px; }
.login-card input, .login-card button { margin-top: 14px; }
.brand { font-weight: 700; letter-spacing: -.01em; display: inline-flex; align-items: center; gap: 9px; }
.brand::before { content: ""; width: 20px; height: 20px; border-radius: 5px; flex: none;
  background: center/contain no-repeat url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%3E%3Crect%20width='100'%20height='100'%20rx='24'%20fill='%2317191e'/%3E%3Crect%20x='17'%20y='64'%20width='66'%20height='4.6'%20rx='1.3'%20fill='%23fafafa'/%3E%3Crect%20x='25'%20y='45'%20width='15'%20height='19'%20rx='2'%20fill='%23fafafa'/%3E%3Crect%20x='42.5'%20y='39'%20width='15'%20height='25'%20rx='2'%20fill='%2333cf90'/%3E%3Crect%20x='60'%20y='48'%20width='15'%20height='16'%20rx='2'%20fill='%23fafafa'/%3E%3C/svg%3E"); }
.brand span { color: var(--accent); }
.brand.big { font-size: 27px; }
.brand.big::before { width: 28px; height: 28px; border-radius: 7px; }
.ver-badge { font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--text-faint);
  margin-left: 6px; line-height: 1.4; font-variant-numeric: tabular-nums; }

/* layout */
.topbar { display: flex; align-items: center; gap: 18px; padding: 12px 20px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border); background: var(--bg-2); position: sticky; top: 0; z-index: 5; }
.topbar nav { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: thin; min-width: 0; flex: 0 1 auto; }
.topbar .status { flex-wrap: wrap; min-width: 0; }
@media (max-width: 880px) { .grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 600px) {
  .topbar { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .topbar .brand { grid-column: 1; grid-row: 1; }
  .topbar .status {
    grid-column: 1; grid-row: 2; width: 100%;
    justify-self: start; justify-content: flex-start;
  }
  .topbar nav {
    grid-column: 1; grid-row: 3; width: 100%;
    flex-wrap: wrap; overflow: visible;
  }
  .topbar .spacer { display: none; }
  .topbar .nav { padding: 5px 8px; }
}
.nav { background: none; border: none; color: var(--text-dim); padding: 6px 12px; border-radius: var(--r); }
.nav.active { background: var(--panel-2); color: var(--text); }
.spacer { margin-left: auto; }
.status { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 13px; }
.live-pill { display: inline-flex; align-items: center; gap: 7px; background: var(--panel-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 4px 11px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); display: inline-block; }
.dot.live { background: var(--up); box-shadow: 0 0 8px var(--up); }
.dot.dead { background: var(--danger); }
.langsel { width: auto; padding: 5px 8px; font-size: 12.5px; }
main { padding: 20px; max-width: 1300px; margin: 0 auto; }

/* alert banner */
.banner { display: flex; align-items: center; gap: 12px; background: rgba(248,113,113,.1);
  border: 1px solid rgba(248,113,113,.4); color: var(--danger); border-radius: var(--r); padding: 10px 14px; margin-bottom: 16px; }
.banner.stale { background: rgba(251,191,36,.1); border-color: rgba(251,191,36,.4); color: var(--gw); }
.banner button { margin-left: auto; }

/* stats */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 18px; }
.stat { position: relative; background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; overflow: hidden;
  box-shadow: var(--card-shadow); }
/* No Material left-accent bar on cards — status reads from text/number colour. */
.stat-n { font-size: 27px; font-weight: 700; line-height: 1.1; }
.stat.s-online .stat-n { color: var(--up); font-size: 40px; text-shadow: 0 0 22px rgba(52,211,153,.25); }
.stat-n.mono { font-size: 15px; font-family: var(--mono); }
.stat-l { color: var(--text-faint); font-size: 11px; text-transform: uppercase; letter-spacing: .6px; margin-top: 3px; }

/* grid + panels */
.grid { display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 16px; }
/* Grid/flex items default to min-width:auto, which lets a wide table blow the
   column past the viewport instead of scrolling inside .tablewrap. */
.grid > * { min-width: 0; }
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
.panel h3 { margin: 0 0 12px; font-size: 13px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .6px; font-weight: 700; }
.toolbar { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.toolbar .searchwrap { position: relative; flex: 1; min-width: 180px; }
.toolbar .clearx { position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: none; color: var(--text-faint); padding: 2px 6px; }
.toolbar select { width: auto; }
.chk { display: flex; align-items: flex-start; gap: 6px; margin: 0; color: var(--text-dim); }
.chk input { margin-top: 2px; }
.chk input { width: auto; }
.count { color: var(--text-faint); font-size: 12px; margin-bottom: 8px; }

/* table */
.tablewrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
caption { text-align: left; }
th { text-align: left; color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .4px;
  padding: 7px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: default; user-select: none; }
th[data-sort] { cursor: pointer; }
th[data-sort]:hover .arrow { color: var(--text); }
th[aria-sort] .arrow::after { content: " ↕"; color: var(--text-faint); }
th[aria-sort="ascending"] .arrow::after { content: " ↑"; color: var(--accent); }
th[aria-sort="descending"] .arrow::after { content: " ↓"; color: var(--accent); }
td { padding: 10px 12px; border-bottom: 1px solid var(--bg-2); vertical-align: middle; }
/* Zebra needs a token, not a fixed white wash: on the light panel the old
   rgba(255,255,255,.012) was white on white and the striping disappeared. */
tbody tr:nth-child(even) td { background: var(--zebra); }
tbody tr.clickable { cursor: pointer; }
tbody tr:hover td { background: var(--panel-2); }
/* A focused row must be as obvious as a hovered one. outline on <tr> is not
   painted reliably across engines, so the cells carry the indicator. */
tbody tr.clickable:focus-visible { outline: none; }
tbody tr.clickable:focus-visible td { background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--accent); }
tbody tr.clickable:focus-visible td:first-child { box-shadow: inset 2px -2px 0 var(--accent); }
tbody tr.clickable:focus-visible td:last-child { box-shadow: inset -2px -2px 0 var(--accent); }
.dname { font-weight: 600; }
.dtype { color: var(--text-dim); font-size: 11.5px; }
.cell-mono { font-family: var(--mono); font-size: 12.5px; color: var(--text-dim); }
.col-ip { width: 120px; } .col-mac { width: 150px; } .col-rtt { width: 80px; } .col-state { width: 28px; }
/* Ports need room to sit side by side. Without a floor the browser hands this
   column the width of a single chip and stacks four ports down four lines,
   which triples the row height on any device with a few services open. */
.col-ports { min-width: 150px; }
.cell-ports { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
/* A device name is one line with an ellipsis, like the vendor beside it —
   'USW-Pro-24' broken across two lines reads like two devices. */
.dname { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vendor { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pill { display: inline-block; font-size: 11px; font-family: var(--mono); background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 6px; padding: 1px 5px; color: var(--text-dim); }
.statedot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.skel { display: inline-block; height: 12px; width: 70px; border-radius: 4px;
  background: linear-gradient(90deg, var(--panel-2), var(--border), var(--panel-2)); background-size: 200% 100%; animation: shimmer 1.2s infinite; }
@keyframes shimmer { to { background-position: -200% 0; } }
.state-msg { color: var(--text-faint); padding: 28px; text-align: center; font-size: 14px; }
.state-msg .big { font-size: 22px; display: block; margin-bottom: 6px; }

/* timeline */
.timeline ul { list-style: none; margin: 0; padding: 0; max-height: 72vh; overflow-y: auto; }
/* Event severity is carried by a dot glyph + tint (never a colored left
   accent border — house design rule). */
.timeline li { padding: 8px 0 8px 12px; border-bottom: 1px solid var(--bg-2); font-size: 13px; }
.timeline li::before { content: "●"; font-size: 9px; margin-right: 8px; color: var(--border); vertical-align: 1px; }
.timeline li.ev-new::before { color: var(--accent); }
.timeline li.ev-online::before { color: var(--up); }
.timeline li.ev-offline::before { color: var(--danger); }
.timeline li.ev-new { background: rgba(108,140,255,.05); }
.timeline li.ev-offline { background: rgba(248,113,113,.05); }
.ev-when { color: var(--text-faint); font-size: 11px; margin-top: 2px; }

/* settings */
.form { max-width: 740px; margin-bottom: 16px; }
.help { font-size: 12px; color: var(--text-faint); }
/* Same look as .help, separate name: inside the drawer's .rename block, .help
   is reserved for "this device has no MAC and cannot be edited". */
.asset-note { font-size: 12px; color: var(--text-faint); }
.settings-search { position: relative; max-width: 740px; margin-bottom: 8px; }
.settings-search input { width: 100%; padding-left: 38px; }
.settings-search::before { content: "⌕"; position: absolute; left: 14px; top: 9px; z-index: 1; color: var(--text-faint); font-size: 20px; pointer-events: none; }
.settings-search-results { position: absolute; z-index: 30; top: calc(100% + 5px); left: 0; right: 0;
  max-height: min(440px, 65vh); overflow-y: auto; padding: 5px; background: var(--panel);
  border: 1px solid var(--border); box-shadow: 0 16px 36px rgba(0,0,0,.38); }
.settings-search-result { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  width: 100%; gap: 16px; padding: 10px 11px; border: 0; border-bottom: 1px solid var(--border);
  background: transparent; color: var(--text); text-align: left; border-radius: 0; }
.settings-search-result:last-child { border-bottom: 0; }
.settings-search-result:hover, .settings-search-result.active { background: var(--bg-2); }
.settings-search-result small { color: var(--text-faint); white-space: nowrap; }
.settings-search-empty { padding: 14px 11px; color: var(--text-faint); }
.settings-search-hit { animation: settings-hit 1.5s ease-out; }
@keyframes settings-hit { 0%, 35% { outline: 2px solid var(--accent); outline-offset: 2px; } 100% { outline-color: transparent; } }
.row { display: flex; align-items: center; gap: 12px; margin-top: 16px; max-width: 740px; flex-wrap: wrap; }
/* Autosave note: a quiet static line — never a fixed overlay that covers
   controls. The per-save feedback appears inline in #saveResult. */
.savebar { padding: 10px 2px; font-size: 12.5px; }
.server-info-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 18px; }
.server-info-grid > div { display: grid; gap: 5px; min-width: 0; }
.server-info-grid a { color: var(--accent); overflow-wrap: anywhere; }
@media (max-width: 620px) { .server-info-grid { grid-template-columns: 1fr; } }

/* In-page pills (network sub-views). */
.subnav { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
.pill { background: var(--panel); border: 1px solid var(--border); color: var(--text-dim);
  padding: 6px 14px; border-radius: 999px; cursor: pointer; font-size: 13px; text-decoration: none; }
.pill.active, .pill:hover { color: var(--text); border-color: var(--accent); }

/* Settings: a side navigation of sections, one section on screen, its cards
   in two columns. Everything below is scoped to .settings-layout so the
   740 px card cap that suits a single-column form does not apply here. */
.settings-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 24px; align-items: start; }
.settings-search { max-width: none; }
.setnav { display: flex; flex-direction: column; gap: 2px; position: sticky; top: 76px; }
.setnav a { display: block; padding: 8px 12px; border-radius: 8px; border: 1px solid transparent;
  color: var(--text-dim); text-decoration: none; font-size: 13.5px; }
.setnav a:hover { background: var(--bg-2); color: var(--text); }
.setnav a.active { background: var(--panel); border-color: var(--border); color: var(--text); font-weight: 600; }
.setnav a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.settings-content { min-width: 0; }
.setgroup { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
.setgroup[hidden] { display: none; }
.setgroup-title { grid-column: 1 / -1; font-size: 13px; text-transform: uppercase; letter-spacing: .8px;
  color: var(--text-faint); margin: 6px 0 0; }
.setgroup .panel.form { max-width: none; margin-bottom: 0; }
.setgroup .panel.wide { grid-column: 1 / -1; }
@media (max-width: 1100px) { .setgroup { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 800px) {
  .settings-layout { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .setnav { position: static; flex-direction: row; flex-wrap: wrap; gap: 6px; }
  .setnav a { border-color: var(--border); border-radius: 999px; padding: 6px 14px; }
}
.license { font-size: 14px; } .license b { color: var(--text); }
.ai-settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 14px; margin: 10px 0 14px; }
.ai-settings-grid > div { min-width: 0; }
.ai-model-control { display: grid; grid-template-columns: minmax(0, 1fr) 42px; gap: 8px; align-items: center; }
.ai-model-control .icon-btn { width: 42px; height: 42px; padding: 0; font-size: 20px; line-height: 1; }
.secret-field-control { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; }
.compact-actions { align-items: center; }
@media (max-width: 560px) { .ai-settings-grid { grid-template-columns: 1fr; } }
.pve-settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 14px; margin: 10px 0; }
.pve-settings-grid > div { min-width: 0; }
.pve-insecure { margin-top: 32px; }
.pve-module-picker { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0; margin-top: 14px; border-top: 1px solid var(--border); }
.pve-module-option { display: grid; grid-template-columns: 18px minmax(0, 1fr); align-items: start; gap: 9px; min-width: 0; padding: 11px 8px; margin: 0; border-bottom: 1px solid var(--border); cursor: pointer; }
.pve-module-option:nth-child(odd) { border-right: 1px solid var(--border); }
.pve-module-option input { width: auto; margin: 2px 0 0; }
.pve-module-option span { display: grid; gap: 3px; min-width: 0; }
.pve-module-option strong { font-size: 12.5px; }
.pve-module-option small { color: var(--text-faint); font-size: 11px; line-height: 1.35; }
.pve-module-option.fixed { cursor: default; }
.pve-module-option.fixed strong { color: var(--text-dim); }
@media (max-width: 700px) { .pve-settings-grid, .pve-module-picker { grid-template-columns: 1fr; } .pve-insecure { margin-top: 12px; } .pve-module-option:nth-child(odd) { border-right: 0; } }

/* notification builder */
.targets { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.target-item { display: flex; align-items: center; gap: 10px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: var(--r); padding: 9px 12px; }
.target-item .ti-icon { font-size: 16px; }
.target-item .ti-name { font-weight: 600; white-space: nowrap; }
.target-item .ti-url { color: var(--text-faint); font-family: var(--mono); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.target-item .ti-actions { display: flex; gap: 6px; margin-left: auto; }
.target-item button { padding: 4px 9px; font-size: 12.5px; }
.targets-empty { color: var(--text-faint); font-size: 13px; padding: 6px 0; }
.addbox { background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--r); padding: 14px; margin-top: 6px;
  scroll-margin-top: 160px; }
.addbox > select { max-width: 280px; }
@media (max-width: 600px) { .addbox { scroll-margin-top: 220px; } }
.provfields { display: grid; gap: 8px; margin: 12px 0 4px; }
.provfields .pf-label { color: var(--text-dim); font-size: 12px; margin-bottom: 4px; display: block; }
.provfields .pf-hint { color: var(--text-faint); font-size: 11.5px; margin-top: 4px; line-height: 1.4; }
.provfields select { width: 100%; max-width: 280px; }
.advanced { margin-top: 16px; border-top: 1px solid var(--bg-2); padding-top: 10px; }
.advanced summary { cursor: pointer; color: var(--text-dim); font-size: 13px; }
.advanced[open] summary { margin-bottom: 8px; }

/* drawer */
.drawer-bg { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 10; }
.drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(440px, 94vw); background: var(--panel);
  border-left: 1px solid var(--border); z-index: 11; padding: 22px; overflow-y: auto; box-shadow: -20px 0 60px rgba(0,0,0,.5); }
.drawer h2 { margin: 0 0 4px; font-size: 18px; }
.drawer .kv { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--bg-2); }
.drawer .kv span { color: var(--text-dim); } .drawer .kv b { font-family: var(--mono); font-weight: 600; font-size: 13px; }
.drawer .closex { float: right; }
.bigchart { width: 100%; height: 70px; margin: 12px 0; }

/* login helper + token disclosure */
.login-hint { color: var(--text-faint); font-size: 12px; margin: 8px 0 0; line-height: 1.45; }
.login-where { margin-top: 12px; }
.login-where summary { cursor: pointer; color: var(--accent); font-size: 12.5px; }
.login-where p { color: var(--text-dim); font-size: 12px; line-height: 1.5; margin: 8px 0 0; }

/* topbar scan-status + license chip */
.scan-every { color: var(--text-faint); font-size: 12px; white-space: nowrap; }
.chip { border-radius: 999px; padding: 4px 11px; font-size: 12px; font-weight: 600; line-height: 1;
  background: var(--panel-2); border: 1px solid var(--border); color: var(--text-dim); }
.chip.ok { color: var(--up); border-color: rgba(52,211,153,.4); }
.chip.warn { color: var(--gw); border-color: rgba(251,191,36,.45); background: rgba(251,191,36,.1); }
.chip.bad { color: var(--danger); border-color: rgba(248,113,113,.45); background: rgba(248,113,113,.1); }

/* live pill: make the not-live states visible, not just an 8px dot */
.live-pill.dead { background: rgba(248,113,113,.12); border-color: rgba(248,113,113,.45); color: var(--danger); }
.live-pill.stale { background: rgba(251,191,36,.12); border-color: rgba(251,191,36,.45); color: var(--gw); }
.live-pill.stale .dot { background: var(--gw); }

/* onboarding card */
.onboard { position: relative; background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--border); border-radius: 12px;
  padding: 18px 20px; margin-bottom: 18px; }
.onboard h2 { margin: 0 0 6px; font-size: 18px; }
.onboard p { margin: 0 0 10px; color: var(--text-dim); }
.onboard ol { margin: 0 0 14px; padding-left: 20px; color: var(--text-dim); line-height: 1.6; }
.onboard-x { position: absolute; top: 10px; right: 10px; background: none; border: none; color: var(--text-faint); padding: 4px 8px; }

/* diagnostic empty state */
.empty-card { max-width: 520px; margin: 0 auto; }
.empty-title { font-size: 16px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
.empty-hint { margin-top: 12px; font-size: 12.5px; color: var(--text-faint); line-height: 1.5;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--r); padding: 10px 12px; text-align: left; }

/* section subheading + empty timeline */
.subhead { margin: 4px 0 0; font-size: 12px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .5px; font-weight: 700; }
.badge-snmp { display: inline-block; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .4px;
  color: var(--accent-2); background: rgba(56,189,248,.12); border: 1px solid rgba(56,189,248,.35);
  border-radius: 5px; padding: 0 5px; margin-left: 6px; vertical-align: middle; }

/* multi-NIC interface picker */
.iface-list { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 4px; }
.iface-item { display: flex; align-items: center; gap: 10px; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r); padding: 8px 11px; margin: 0; cursor: pointer; }
.iface-item input { width: auto; }
.iface-item .iface-name { font-weight: 600; }
.iface-item .iface-meta { color: var(--text-faint); font-size: 11.5px; margin-left: auto; font-family: var(--mono); }
.iface-item.missing { opacity: .7; }

/* device metadata editor (drawer) */
.rename { margin: 10px 0 14px; }
.rename .rename-row { display: flex; gap: 8px; }
.rename .rename-row input { flex: 1; }
.rename .rename-row button { flex: 1; }
.rename input, .rename select { width: 100%; }
.rename .pf-label { display: block; margin: 10px 0 4px; }
.rename .pf-label:first-child { margin-top: 0; }
.meta-flags { display: flex; flex-wrap: wrap; gap: 14px; margin: 12px 0 10px; }
.meta-flags label { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.meta-flags input { width: auto; }
.rename #drNameMsg { display: inline-block; margin-top: 6px; font-size: 12px; }
.tl-empty { color: var(--text-faint); font-size: 13px; border-left: 2px solid transparent !important; }

/* provider fields: intro line, 2-column grid, optional "own server" disclosure */
.provfields { display: block; }
.provgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; margin: 10px 0 4px; }
.prov-intro { color: var(--text-dim); font-size: 12.5px; line-height: 1.5; margin: 10px 0 2px; }
.prov-adv { margin-top: 8px; }
.prov-adv summary { cursor: pointer; color: var(--accent); font-size: 12.5px; }
.prov-adv[open] summary { margin-bottom: 6px; }

@media (max-width: 880px) { .grid { grid-template-columns: 1fr; } .stats { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .toolbar { flex-direction: column; } .toolbar .searchwrap, .toolbar select { width: 100%; } }
/* The sort select duplicates the clickable column headers — keep it only on
   narrow screens where the headers are cramped. */
@media (min-width: 561px) { #sort, label[for="sort"] { display: none; } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  .dot.live { box-shadow: none; }
}

/* WAN-down explainer (collapsible help under the WAN check) */
.wan-help { margin: 4px 0 10px; font-size: 13px; color: var(--text-dim); }
.wan-help summary { cursor: pointer; color: var(--accent); }
.wan-help p { margin: 8px 0; }
.wan-help ul { margin: 6px 0 2px; padding-left: 18px; }
.wan-help li { margin: 4px 0; line-height: 1.5; }

/* Update panel */
.upd-box { margin-top: 12px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); }
.upd-box.hidden { display: none; }
.upd-new { font-weight: 600; color: var(--accent); margin-bottom: 6px; }
/* A routine update and one that fixes a defect you are running should not look
   identical. Colour carries it, but the urgency line states it in words too —
   colour alone would say nothing to a colour-blind reader or in a screenshot. */
.upd-new.upd-important, .upd-urgency.upd-important { color: var(--gw); }
.upd-new.upd-security, .upd-urgency.upd-security { color: var(--danger); }
.upd-urgency { font-weight: 600; font-size: 13px; margin-bottom: 6px; }
.upd-notes { font-size: 13px; color: var(--text-dim); margin-bottom: 8px; white-space: pre-wrap; }
.upd-cmd { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.upd-cmd code { background: var(--bg-2); border: 1px solid var(--border); border-radius: 7px; padding: 6px 9px; font-size: 12.5px; }
.upd-hint { font-size: 12px; color: var(--text-dim); margin-top: 8px; }
.upd-link { font-size: 13px; margin-top: 8px; }

/* Wave 1 monitors list (reuses .target-item rows) */
.checks { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 12px; }
.checks .statedot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; margin-right: 8px; flex: none; }
.checks .target-item .ti-name { font-weight: 600; }

.sensor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px; }
.sensor-item { min-width: 0; border: 1px solid var(--border); border-radius: 8px; padding: 12px; background: var(--bg-2); }
.sensor-head { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
.sensor-head > div { min-width: 0; display: flex; flex-direction: column; }
.sensor-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sensor-head small, .sensor-item time { color: var(--text-faint); font-family: var(--mono); font-size: 10.5px; }
.sensor-state { width: 9px; height: 9px; flex: none; border-radius: 50%; background: var(--up); }
.sensor-state.warn { background: var(--gw); }
.sensor-state.error { background: var(--danger); }
.sensor-channels { display: grid; gap: 5px; margin-bottom: 9px; }
.sensor-channel { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; color: var(--text-dim); font-size: 12px; }
.sensor-channel strong { color: var(--text); font-family: var(--mono); font-size: 12px; text-align: right; }
.sensor-error { min-height: 34px; font-size: 12px; overflow-wrap: anywhere; }
.bw-analysis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-top: 16px; }
.bw-analysis > div { min-width: 0; }
.bw-analysis h4 { margin: 0 0 7px; font-size: 12px; color: var(--text-dim); }
.bw-analysis td { padding: 6px 7px; font-size: 11px; overflow-wrap: anywhere; }
@media (max-width: 800px) { .bw-analysis { grid-template-columns: minmax(0, 1fr); } }

/* Wave 2: reports / SLA tab */
.reports-toolbar{display:flex;align-items:center;gap:10px;margin-left:auto}
.reports-toolbar select{padding:4px 8px}
.reports-overall{font-size:.95rem}
.reports-note{margin:4px 0 14px;font-size:.8rem}
#view-reports h4{margin:18px 0 6px;font-size:.85rem;text-transform:uppercase;letter-spacing:.04em;color:var(--text-faint)}
.rtable{width:100%;border-collapse:collapse;font-size:.9rem}
.rtable th[data-report-sort]{padding:0}
.report-sort{display:flex;width:100%;align-items:center;gap:5px;padding:7px 10px;border:0;border-radius:0;background:transparent;color:inherit;font:inherit;text-transform:inherit;letter-spacing:inherit;text-align:left;white-space:nowrap}
.rsla .report-sort,.rsamp .report-sort{justify-content:flex-end;text-align:right}
.report-sort:hover{color:var(--text)}
.report-sort:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.report-sort-arrow::after{content:'↕';color:var(--text-faint)}
.rtable th[aria-sort="ascending"] .report-sort-arrow::after{content:'↑';color:var(--accent)}
.rtable th[aria-sort="descending"] .report-sort-arrow::after{content:'↓';color:var(--accent)}
.spark{display:block;color:var(--accent);margin-top:2px}
#reportWindow{width:auto;min-width:80px}
.reports-toolbar{display:flex;gap:10px;align-items:center}
.thr-hint{display:block;margin:-4px 0 8px;font-size:12px}
/* rtable outputs are injected without a wrapper — contain them. */
#infraHealth,#infraDocker,#infraPve,#topoOut,#syslogOut,#bwOut{overflow-x:auto}
.card-head span{display:flex;gap:8px;align-items:center}
#infraRange{width:auto;padding:4px 8px}
.rtable td{padding:7px 10px;border-top:1px solid var(--border);vertical-align:middle}
.rtable td:first-child{font-weight:500}
.report-identity{display:flex;min-width:0;flex-direction:column;gap:2px}
.report-identity strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
.report-identity small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-faint);font:10.5px var(--mono)}
.rsla{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}
.rsamp{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.rsla.ok{color:var(--up)}.rsla.warn{color:var(--gw)}.rsla.bad{color:var(--danger)}
.rsla-dt{font-size:11px;font-weight:400;color:var(--text-faint);margin-left:6px}
.reports-export{display:flex;flex-wrap:wrap;gap:8px}
.reports-export .btn{text-decoration:none}

/* ---- Quick-wins: uptime heatmap, read-only mode, push URL ---- */
/* Kuma-style uptime bars */
.heatmap-wrap { margin: 8px 0 4px; }
.ub-legend { display: flex; gap: 16px; align-items: center; font-size: .75rem; color: var(--text-dim); margin: 2px 0 10px; }
.ub-dot { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 5px; vertical-align: -1px; }
.ub-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .6px; color: var(--text-faint); margin: 14px 0 6px; }
.ub-win { text-transform: none; letter-spacing: 0; opacity: .8; }
.ub-row { display: flex; align-items: center; gap: 12px; padding: 3px 0; }
.ub-name { flex: 0 0 190px; max-width: 190px; min-width: 0; font-size: .85rem; }
.ub-bar { flex: 1 1 auto; display: flex; gap: 1px; height: 24px; min-width: 0; }
.ub-seg { flex: 1 1 0; min-width: 2px; border-radius: 1px; background: var(--border); }
.ub-pct { flex: 0 0 56px; text-align: right; font-variant-numeric: tabular-nums; font-size: .85rem; }
.ub-ok { background: var(--up); }
.ub-warn { background: var(--gw); }
.ub-bad { background: var(--danger); }
.ub-empty { background: var(--border); opacity: .5; }
.ub-pct.ok { color: var(--up); }
.ub-pct.warn { color: var(--gw); }
.ub-pct.bad { color: var(--danger); }
.ub-pct.muted { color: var(--text-faint); }
/* Fresh-server empty state: a "collecting data" note over shimmering ghost bars
   that preview the layout, so the section never reads as broken/blank. */
.ub-collect .state-msg { padding: 6px 8px 16px; }
.ub-collect .state-msg .big { font-size: 16px; color: var(--text-dim); }
.ub-skel-row { opacity: .8; }
.ub-seg.ub-skel { background: linear-gradient(90deg, var(--panel-2), var(--border), var(--panel-2));
  background-size: 200% 100%; animation: shimmer 1.6s infinite; opacity: .5; }
.skel.skel-name { width: 110px; height: 11px; }
.skel.skel-pct { width: 38px; height: 11px; }
.banner.ro { background: color-mix(in srgb, var(--accent-2) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-2) 40%, transparent); color: var(--accent-2); }
body.readonly .admin-only { display: none !important; }
.row .grow, input.grow { flex: 1 1 auto; min-width: 0; }
.pair-box { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.pair-box img { width: 200px; height: 200px; border-radius: 10px; background: #fff; padding: 8px; }
.server-url-block { display: grid; gap: 6px; margin: 12px 0; }
.server-url { min-width: 0; overflow-wrap: anywhere; font-weight: 650; }

/* Device-drawer on-demand probes */
.drProbes { margin-top: 14px; }
.probe-btns { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
/* One button vocabulary: .btn (regular) and .btn-sm (compact). The legacy
   markup variants `class="btn"` and `class="btn sm"` map onto these. */
.btn { font-size: 13px; padding: 7px 14px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--panel-2); color: var(--text); cursor: pointer; text-decoration: none; display: inline-block; }
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn.sm { font-size: 12px; padding: 5px 10px; border-radius: 7px; }
.btn-sm { font-size: 12px; padding: 5px 10px; border: 1px solid var(--border); border-radius: 7px;
  background: transparent; color: var(--text); cursor: pointer; }
.btn-sm:hover { border-color: var(--accent); color: var(--accent); }
/* Secondary weight for an undo-style action that must stay reachable without
   competing with the primary one next to it. Contrast stays at text-dim, not
   fainter, so it remains readable in both themes. */
/* Dimmer than the primary action, but it keeps its border: a borderless label
   is exactly what made the action column read as "this row has no button". */
.btn-sm.quiet { color: var(--text-dim); border-color: var(--border); background: var(--panel); }
.btn-sm.quiet:hover { color: var(--accent); border-color: var(--accent); }
#clearList.armed, #clearList.armed:hover { color: #ef4444; border-color: #ef4444; background: rgba(220,38,38,.12); }
.probe-out { white-space: pre-wrap; word-break: break-word; font-size: 11.5px; line-height: 1.45;
  margin-top: 6px; max-height: 220px; overflow: auto; color: var(--text-dim); font-family: ui-monospace, monospace; }
.probe-out:empty { display: none; }

/* Security / Topology / Syslog / AI views */
.sec-score { display: flex; align-items: baseline; gap: 6px; margin: 8px 0 14px; }
.sec-score .score-num { font-size: 34px; font-weight: 800; }
.score-num.ok { color: var(--up); } .score-num.warn { color: var(--gw); } .score-num.bad { color: var(--danger); }
.finding { border: 1px solid var(--border); border-radius: 9px; padding: 10px 12px; margin-bottom: 8px; }
.finding .f-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.finding .f-detail { color: var(--text-dim); font-size: 12.5px; margin-top: 4px; line-height: 1.45; }
.sev { font-size: 10px; font-weight: 700; text-transform: uppercase; padding: 2px 7px; border-radius: 99px; }
.sev-high { background: rgba(255,93,93,.15); color: var(--danger); } .sev-medium { background: rgba(245,166,35,.15); color: var(--gw); }
.sev-low { background: rgba(79,140,255,.15); color: var(--accent); } .sev-info { background: rgba(154,163,178,.15); color: var(--text-faint); }
.ai-log { min-height: 120px; max-height: 46vh; overflow: auto; display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.ai-msg { padding: 8px 11px; border-radius: 10px; font-size: 13px; line-height: 1.5; white-space: pre-wrap; max-width: 90%; }
.ai-user { align-self: flex-end; background: rgba(79,140,255,.16); }
.ai-assistant { align-self: flex-start; background: var(--panel-2); border: 1px solid var(--border); }
.ai-input { display: flex; gap: 8px; }
.ai-input input { flex: 1 1 auto; }
.ai-actions { display: flex; gap: 6px; margin-bottom: 6px; }

/* Monitoring workspace: rankings, alarms, groups, time series and topology. */
.workspace-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.workspace-head h2 { margin: 0 0 4px; font-size: 20px; }
.workspace-head p { margin: 0; }
.rankings { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border: 1px solid var(--border);
  border-radius: 8px; margin-bottom: 14px; background: var(--panel); overflow: hidden; }
.ranking { min-width: 0; padding: 13px 15px; border-right: 1px solid var(--border); }
.ranking:last-child { border-right: 0; }
.ranking h3 { margin: 0 0 9px; color: var(--text-faint); font-size: 11px; text-transform: uppercase; letter-spacing: .4px; }
.ranking ol { margin: 0; padding: 0; list-style: none; }
.ranking li { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; align-items: center; gap: 7px; min-height: 25px; font-size: 12px; }
.rank-pos { color: var(--text-faint); font-family: var(--mono); }
.rank-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ranking b { font-size: 11px; font-weight: 600; white-space: nowrap; }
.view-mode, .segmented { display: inline-flex; border: 1px solid var(--border); border-radius: 7px; overflow: hidden; margin-bottom: 10px; }
.mode-btn { border: 0; border-right: 1px solid var(--border); border-radius: 0; padding: 6px 12px; color: var(--text-dim); background: var(--panel); }
.mode-btn:last-child { border-right: 0; }
.mode-btn.active { color: var(--text); background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--accent); }
.device-groups { display: grid; gap: 10px; }
.device-group { border: 1px solid var(--border); border-radius: 8px; background: var(--panel); overflow: hidden; }
.device-group summary { display: flex; justify-content: space-between; align-items: center; padding: 11px 14px; cursor: pointer;
  color: var(--text-dim); font-size: 12px; text-transform: uppercase; letter-spacing: .4px; }
.device-group summary b { color: var(--text); }
.device-group-row { width: 100%; display: grid; grid-template-columns: 12px minmax(170px, 1fr) minmax(360px, 2fr); align-items: center;
  gap: 12px; border: 0; border-top: 1px solid var(--bg-2); border-radius: 0; padding: 10px 14px; text-align: left; background: transparent; }
.device-group-row:hover { background: var(--panel-2); }
.group-device { min-width: 0; display: flex; flex-direction: column; }
.group-device b, .group-device small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-device small { color: var(--text-faint); margin-top: 2px; }
.group-sensors { display: grid; grid-template-columns: repeat(4, minmax(70px, 1fr)); gap: 5px; }
.group-sensors span { min-width: 0; padding: 5px 7px; border: 1px solid var(--border); border-radius: 5px; color: var(--text-dim);
  font: 11px var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.alarm-summary { display: flex; gap: 1px; margin-bottom: 12px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; width: fit-content; }
.alarm-count { display: flex; align-items: baseline; gap: 8px; min-width: 130px; padding: 9px 13px; background: var(--panel); color: var(--text-dim); font-size: 12px; }
.alarm-count + .alarm-count { border-left: 1px solid var(--border); }
.alarm-count b { font-size: 21px; color: var(--text); }
.alarm-count.error b { color: var(--danger); } .alarm-count.warn b { color: var(--gw); }
.alarm-panel { padding: 0; overflow: hidden; }
.alarm-table th, .alarm-table td { padding: 10px 12px; }
.alarm-row.error td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
.alarm-row.warn td:first-child { box-shadow: inset 3px 0 0 var(--gw); }
.alarm-state, .alarm-muted { display: block; width: fit-content; font-size: 10px; text-transform: uppercase; font-weight: 700; letter-spacing: .4px; }
.alarm-state.error { color: var(--danger); } .alarm-state.warn { color: var(--gw); }
.alarm-muted { color: var(--text-dim); margin-top: 4px; }
/* A muted alarm is out of the working list by default. The rows stay in the
   DOM so revealing them costs no request and moves nothing else. */
.alarm-panel:not(.show-muted) tr.alarm-row.muted { display: none; }
/* Revealed muted rows read as set aside — dimmed surface and text, never colour
   alone: the STUMM badge says it too. */
.alarm-panel.show-muted tr.alarm-row.muted td { background: var(--bg-2); color: var(--text-dim); }
.alarm-panel.show-muted tr.alarm-row.muted .alarm-monitor { font-weight: 600; color: var(--text-dim); }
/* The muted counter is the control that reveals them — a number you cannot act
   on only raises the question of where the rows went. */
button.alarm-count { border: 0; font: inherit; text-align: left; cursor: pointer; gap: 8px; }
button.alarm-count:hover { color: var(--accent); }
button.alarm-count:hover b { color: var(--accent); }
.alarm-count-hint { margin-left: 6px; text-decoration: underline; }
.alarm-action { text-align: right; }
/* The row takes focus after an acknowledgement repaints the table, so it needs
   the same visible ring as a clickable row — the level stripe stays intact. */
.alarm-row:focus-visible { outline: none; }
.alarm-row:focus-visible td { background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--accent); }
.alarm-row.error:focus-visible td:first-child { box-shadow: inset 3px 0 0 var(--danger), inset 0 -2px 0 var(--accent); }
.alarm-row.warn:focus-visible td:first-child { box-shadow: inset 3px 0 0 var(--gw), inset 0 -2px 0 var(--accent); }
.alarm-row:focus-visible td:last-child { box-shadow: inset -2px -2px 0 var(--accent); }
.alarm-head-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }
.alarm-head-actions .muted { font-size: 12px; }
.alarm-trend { display: flex; width: 150px; height: 24px; gap: 1px; align-items: stretch; }
.alarm-trend-seg { flex: 1 1 0; min-width: 2px; border-radius: 1px; background: var(--border); opacity: .55; }
.alarm-trend-seg.ok { background: var(--up); opacity: .85; } .alarm-trend-seg.warn { background: var(--gw); opacity: .9; }
.alarm-trend-seg.bad { background: var(--danger); opacity: .9; }

.bw-detail { margin: 14px 0 18px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); padding: 12px 0; }
.chart-toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.chart-toolbar select { width: min(340px, 100%); }
.series-toggle { display: flex; align-items: center; gap: 5px; margin: 0; font-size: 12px; }
.series-toggle input { width: auto; }
.series-toggle.in { color: var(--accent-2); } .series-toggle.out { color: var(--gw); }
#bwTotals { margin-left: auto; font-size: 11px; }
.timeseries-chart { width: 100%; height: 260px; min-height: 260px; }
.timeseries-chart svg { display: block; width: 100%; height: 100%; overflow: visible; }
.chart-grid line { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.chart-grid text, .chart-unit { fill: var(--text-faint); font: 10px var(--mono); text-anchor: end; }
.chart-unit { text-anchor: start; }
.bw-line { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
.bw-line.in { stroke: var(--accent-2); } .bw-line.out { stroke: var(--gw); }
.bw-row { cursor: pointer; }

.monitor-chart-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 14px; margin-top: 18px; }
.monitor-chart-head h4 { margin-bottom: 4px !important; }
.monitor-chart-head .segmented { margin-bottom: 4px; flex: none; }
.monitor-chart-controls { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.monitor-chart-controls select { width: auto; max-width: 220px; margin-bottom: 4px; }
.monitor-charts { border-top: 1px solid var(--border); }
.monitor-history-row { min-width: 0; padding: 12px 0; border-bottom: 1px solid var(--border); }
.monitor-history-summary { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 16px; margin-bottom: 8px; }
.monitor-history-pct { font-size: 14px; font-variant-numeric: tabular-nums; }
.monitor-history-track { display: flex; gap: 2px; width: 100%; height: 22px; }
.monitor-history-track.long { gap: 0; }
.monitor-history-seg { min-width: 0; flex: 1 1 0; border-radius: 1px; background: var(--border); }
.monitor-history-seg:is(button) { height: 100%; padding: 0; border: 0; cursor: pointer; }
.monitor-history-seg:is(button):focus-visible { outline: 2px solid var(--text); outline-offset: 2px; z-index: 1; }
.monitor-history-seg.ub-ok { background: var(--up); }
.monitor-history-seg.ub-warn { background: var(--gw); }
.monitor-history-seg.ub-bad { background: var(--danger); }
.monitor-history-seg.ub-empty { background: var(--border); opacity: .5; }
.monitor-history-axis, .monitor-history-stats { display: flex; justify-content: space-between; gap: 16px; color: var(--text-faint); font-size: 10.5px; }
.monitor-history-axis { margin-top: 4px; }
.monitor-history-detail { min-height: 16px; margin-top: 5px; color: var(--text-dim); font: 10.5px var(--mono); }
.monitor-history-stats { margin-top: 7px; font-variant-numeric: tabular-nums; }
.monitor-history-stats span:last-child { text-align: right; }
@media (max-width: 700px) {
  .monitor-chart-head { align-items: stretch; flex-direction: column; }
  .monitor-chart-controls { justify-content: space-between; }
  .monitor-chart-controls select { min-width: 0; max-width: calc(100% - 112px); }
}

.topology-canvas { position: relative; width: 100%; height: clamp(430px, 62vh, 720px); margin-top: 14px; overflow: hidden;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-2); touch-action: none; }
.topology-canvas svg { width: 100%; height: 100%; cursor: grab; }
.topology-canvas svg:active { cursor: grabbing; }
.topology-controls { position: absolute; z-index: 2; top: 10px; right: 10px; display: flex; border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }
.topology-controls button { width: 34px; height: 32px; border: 0; border-right: 1px solid var(--border); border-radius: 0; padding: 0; background: var(--panel); font-size: 17px; }
.topology-controls button:last-child { border-right: 0; }
.topo-edge { fill: none; stroke: var(--text-faint); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.topo-edge-label { fill: var(--text-faint); font: 10px var(--mono); text-anchor: middle; paint-order: stroke; stroke: var(--bg-2); stroke-width: 4px; }
.topo-node { cursor: pointer; }
.topo-node rect { fill: var(--panel); stroke: var(--border); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.topo-node:hover rect { stroke: var(--accent); }
.topo-node.infra rect { fill: var(--panel-2); }
.topo-node circle { fill: var(--up); } .topo-node.offline circle { fill: var(--danger); }
.topo-label { fill: var(--text); font: 600 12px system-ui, sans-serif; }
.topo-meta { fill: var(--text-faint); font: 10px var(--mono); }
.topology-details { margin-top: 10px; border-top: 1px solid var(--border); padding-top: 8px; }
.topology-details summary { cursor: pointer; color: var(--text-dim); font-size: 12px; }

/* Proxmox operational workspace */
.pve-workspace { border: 1px solid var(--border); border-radius: 8px; background: var(--panel); overflow: hidden; }
.pve-titlebar { padding: 14px 16px 10px; align-items: flex-start; }
.pve-titlebar h3 { margin: 0 0 3px; }
.pve-titlebar p { margin: 0; }
.pve-titlebar > div { min-width: 0; }
.pve-tabs { display: flex; min-height: 42px; overflow-x: auto; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); scrollbar-width: thin; }
.pve-tab { flex: 0 0 auto; min-height: 41px; padding: 9px 14px; border: 0; border-right: 1px solid var(--border); border-radius: 0; background: transparent; color: var(--text-dim); white-space: nowrap; }
.pve-tab.active { color: var(--text); background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--accent); }
.pve-content { min-height: 150px; overflow: hidden; }
.pve-loading, .pve-state { min-height: 150px; display: flex; align-items: center; justify-content: center; gap: 10px; padding: 28px; color: var(--text-dim); text-align: center; }
.pve-state { flex-direction: column; }
.pve-notice { margin: 12px 16px 0; padding: 9px 11px; border: 1px solid currentColor; border-radius: 6px; font-size: 12px; }
.pve-notice summary { cursor: pointer; }
.pve-notice div { margin-top: 5px; overflow-wrap: anywhere; }
.pve-overview-core { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border-bottom: 1px solid var(--border); }
.pve-overview-block { min-width: 0; padding: 16px; }
.pve-overview-block + .pve-overview-block { border-left: 1px solid var(--border); }
.pve-overview-block h4, .pve-module h4, .pve-section-title { margin: 0 0 11px; color: var(--text-faint); font-size: 11px; text-transform: uppercase; letter-spacing: .4px; }
.pve-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.pve-metric { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pve-metric > span, .pve-metric small, .pve-module small, .pve-table td small, .pve-inline-status small { color: var(--text-faint); font-size: 11px; }
.pve-metric strong { font-size: 20px; overflow-wrap: anywhere; }
.pve-problem-list { margin: 12px 0 0; padding: 9px 0 0 18px; border-top: 1px solid var(--border); color: var(--danger); font-size: 12px; }
.pve-problem-list li + li { margin-top: 4px; }
.pve-modules { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.pve-module { min-width: 0; min-height: 112px; padding: 15px 16px; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); display: flex; flex-direction: column; align-items: flex-start; }
.pve-module-number { font-size: 24px; line-height: 1.1; }
.pve-module small { margin-top: 4px; overflow-wrap: anywhere; }
.pve-table-wrap { overflow-x: auto; }
.pve-table { font-size: 12.5px; }
.pve-table td small { display: block; margin-top: 2px; white-space: nowrap; }
.pve-empty { padding: 28px 16px; color: var(--text-faint); text-align: center; }
.pve-inline-status { display: grid; grid-template-columns: auto auto minmax(0, 1fr); align-items: baseline; gap: 10px; padding: 13px 16px; border-bottom: 1px solid var(--border); }
.pve-inline-status span { color: var(--text-faint); font-size: 11px; text-transform: uppercase; }
.pve-section-title { padding: 15px 16px 0; }
.warn { color: var(--gw); }

@media (max-width: 1050px) {
  .rankings { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ranking:nth-child(2) { border-right: 0; }
  .ranking:nth-child(-n+2) { border-bottom: 1px solid var(--border); }
}
@media (max-width: 700px) {
  .rankings { grid-template-columns: 1fr; }
  .ranking { border-right: 0; border-bottom: 1px solid var(--border); }
  .ranking:last-child { border-bottom: 0; }
  .device-group-row { grid-template-columns: 12px minmax(0, 1fr); }
  .group-sensors { grid-column: 2; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .monitor-chart-head { align-items: flex-start; flex-direction: column; }
  .ub-row { align-items: flex-start; flex-wrap: wrap; gap: 6px 10px; }
  .ub-name { flex: 1 1 calc(100% - 66px); max-width: none; }
  .ub-bar { flex-basis: calc(100% - 66px); order: 3; }
  .ub-pct { margin-left: auto; }
  .alarm-summary { width: 100%; }
  .alarm-count { min-width: 0; flex: 1; flex-direction: column; gap: 2px; }
  #bwTotals { width: 100%; margin-left: 0; }
  .timeseries-chart { height: 210px; min-height: 210px; }
  .pve-overview-core { grid-template-columns: 1fr; }
  .pve-overview-block + .pve-overview-block { border-left: 0; border-top: 1px solid var(--border); }
  .pve-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pve-table, .pve-table tbody, .pve-table tr, .pve-table td { display: block; width: 100%; }
  .pve-table thead { display: none; }
  .pve-table tr { padding: 8px 14px; border-bottom: 1px solid var(--border); }
  .pve-table td { display: grid; grid-template-columns: minmax(92px, 34%) minmax(0, 1fr); gap: 10px; padding: 5px 0; border: 0; background: transparent !important; overflow-wrap: anywhere; }
  .pve-table td::before { content: attr(data-label); color: var(--text-faint); font-size: 10px; text-transform: uppercase; }
}

/* An icon the user uploaded on the desktop. Fetched from /api/usericons/<id>
   rather than drawn as a character, because it is a picture, not a glyph.
   object-fit keeps a non-square upload from being stretched. */
.usericon { width: 18px; height: 18px; object-fit: contain; vertical-align: -3px; }
h2 .usericon { width: 22px; height: 22px; vertical-align: -4px; }
/* An icon from the shared set, fetched from /api/icons/<name>.svg. The
   monochrome ones are drawn in currentColor, so they follow the text colour in
   both themes — which is exactly why they are served as SVG rather than PNG. */
.typeicon { width: 18px; height: 18px; object-fit: contain; vertical-align: -3px; }
h2 .typeicon { width: 22px; height: 22px; vertical-align: -4px; }
/* The monochrome half: the SVG is a stencil, the colour comes from here, so the
   icon reads in both themes instead of being black on black. */
.typeicon.mono { display: inline-block; background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain; }

/* ---- Custom device types: the manager dialog and its icon picker ----------
   The dashboard had no modal of its own, so this brings the chrome with it.
   Deliberately the same shapes and names as the desktop's, so the two do not
   drift apart in look or in the CSS that has to be maintained. */
.cdlg-back { position: fixed; inset: 0; background: rgba(0,0,0,.45); display: grid;
  place-items: center; z-index: 60; padding: 16px; }
.cdlg { background: var(--panel, #fff); color: var(--text, #111); border: 1px solid var(--border, #ddd);
  border-radius: 14px; box-shadow: 0 20px 60px rgba(0,0,0,.25); padding: 20px; width: 460px; max-width: 100%; }
/* Bounded in BOTH directions: an unbounded dialog is cut off at top and bottom
   as soon as a few types exist, taking the Add button with it. */
.cdlg-types { width: 760px; max-height: 92vh; display: flex; flex-direction: column; min-height: 0; }
.cdlg-title { font-size: 16px; font-weight: 700; margin-bottom: 10px; }
.cdlg-row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; flex: 0 0 auto; }

.type-list { display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow-y: auto;
  margin-bottom: 12px; flex: 0 1 auto; min-height: 48px; }
.type-row { display: flex; align-items: center; gap: 10px; padding: 6px 8px;
  border: 1px solid var(--border, #e3e3e3); border-radius: 8px; background: var(--panel-2, #fafafa); }
.type-row-ic { width: 24px; display: grid; place-items: center; flex-shrink: 0; }
.type-row-nm { flex: 1; min-width: 0; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.type-sub { font-size: 11px; opacity: .7; margin: 10px 0 4px; text-transform: uppercase; letter-spacing: .04em; }
.type-add { display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; min-height: 0; }
.type-add-label { font-size: 11.5px; opacity: .7; }
.type-add-row { display: flex; gap: 8px; flex: 0 0 auto; }
.type-add-nm { flex: 1; min-width: 0; padding: 8px 10px; font: inherit; font-size: 14px;
  background: var(--panel-2, #fafafa); border: 1px solid var(--border, #ddd); border-radius: 8px; color: inherit; }
/* Sits beside the type dropdown in the drawer. */
.type-field { display: flex; gap: 8px; align-items: center; }
.type-field select { flex: 1; min-width: 0; }
.btn-mini { padding: 6px 10px; font-size: 12.5px; border-radius: 7px; cursor: pointer;
  border: 1px solid var(--border, #ddd); background: var(--panel-2, #fafafa); color: inherit; }
.btn-mini:hover { background: var(--accent-soft, #eef2ff); }
.btn-mini.danger:hover { border-color: #d33; color: #d33; }

.icon-search-row { display: flex; gap: 8px; align-items: stretch; }
.icon-search { flex: 1 1 auto; min-width: 0; box-sizing: border-box; padding: 8px 10px; font: inherit;
  font-size: 13.5px; background: var(--panel-2, #fafafa); border: 1px solid var(--border, #ddd);
  border-radius: 8px; color: inherit; }
.icon-search:focus { outline: 2px solid var(--accent, #2563eb); outline-offset: 1px; }
.icon-upload-btn { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  cursor: pointer; font-size: 13px; padding: 8px 12px; border: 1px solid var(--border, #ddd);
  border-radius: 8px; background: var(--panel-2, #fafafa); }
/* The file input is hidden but stays in the tab order, so the label has to show
   ITS focus — otherwise the ring disappears into something invisible. */
.icon-upload-in:focus-visible + .icon-upload-btn { outline: 2px solid var(--accent, #2563eb); outline-offset: 2px; }
.type-upload-hint { font-size: 11px; opacity: .7; }
.icon-picker-count { font-size: 11.5px; opacity: .75; min-height: 1em; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* The height lives on the FRAME, not on the grid: with it on the grid, the grid
   grows out of its frame in a short window and draws over the buttons below. */
.icon-pick { display: flex; gap: 10px; align-items: stretch; overflow: hidden;
  flex: 1 1 auto; min-height: 150px; height: min(46vh, 380px); }
/* Compact on purpose: with wrapping labels the column outgrew its frame and the
   last group sat below the fold of a sidebar nobody expects to scroll on its
   own. Eleven single-line entries fit. */
.icon-nav { display: flex; flex-direction: column; gap: 1px; width: 150px; flex: 0 0 150px;
  height: 100%; overflow-y: auto; }
.icon-nav-b { display: flex; align-items: center; justify-content: space-between; gap: 6px; text-align: start;
  padding: 5px 8px; font: inherit; font-size: 12px; line-height: 1.2; white-space: nowrap;
  flex: 0 0 auto; border: 1px solid transparent;
  border-radius: 7px; background: none; color: inherit; opacity: .8; cursor: pointer; }
.icon-nav-b:hover { background: var(--panel-2, #f2f2f2); opacity: 1; }
.icon-nav-b:focus-visible { outline: 2px solid var(--accent, #2563eb); outline-offset: -1px; }
.icon-nav-b.on { background: var(--accent-soft, #eef2ff); color: var(--accent, #2563eb);
  border-color: var(--accent, #2563eb); font-weight: 600; opacity: 1; }
.icon-nav-n { font-size: 10.5px; opacity: .7; font-variant-numeric: tabular-nums; flex-shrink: 0; }
.icon-nav-more { margin-top: 6px; border-top: 1px solid var(--border, #e3e3e3); border-radius: 0;
  padding-top: 8px; font-size: 11.5px; }

.icon-picker { flex: 1 1 auto; min-width: 0; height: 100%; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-wrap: wrap; gap: 6px; align-content: flex-start; padding: 2px; }
.icon-opt { width: 40px; height: 40px; flex: 0 0 auto; display: grid; place-items: center; padding: 0;
  border: 1px solid var(--border, #ddd); border-radius: 8px; background: var(--panel-2, #fafafa);
  cursor: pointer; color: inherit; }
.icon-opt:hover { background: var(--accent-soft, #eef2ff); }
.icon-opt:focus-visible { outline: 2px solid var(--accent, #2563eb); outline-offset: 2px; }
.icon-opt.sel { border-color: var(--accent, #2563eb); background: var(--accent-soft, #eef2ff);
  color: var(--accent, #2563eb); box-shadow: inset 0 0 0 1px var(--accent, #2563eb); position: relative; }
/* A second, non-colour signal for the chosen tile. */
.icon-opt.sel::after { content: "✓"; position: absolute; right: -3px; bottom: -4px; font-size: 10px;
  line-height: 1; padding: 1px 2px; border-radius: 4px; background: var(--accent, #2563eb); color: #fff; }
.icon-opt .typeicon, .icon-opt .usericon { width: 22px; height: 22px; }

/* The DHCP pool as a bar. The mark is the level the alert fires at, so the
   picture and the notification cannot disagree about where "full" is. */
.ipam-bar { position: relative; height: 14px; border-radius: 7px; background: var(--panel-2); border: 1px solid var(--border); overflow: hidden; margin: 6px 0; }
.ipam-fill { display: block; height: 100%; background: var(--up); }
.ipam-mark { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--gw); }
.ipam-conflicts > li { margin-bottom: 8px; }
.ipam-conflicts ul, .ipam-statics { margin: 4px 0 0; padding-left: 18px; }

/* The triage dry run. Room for a table, but no fixed height: on a fresh network
   this shows two rows, after a first scan it can show forty. */
.triage-preview { margin-top: 10px; }
.triage-preview table { width: 100%; }

/* The year report as text: monospaced so the columns the server aligned stay
   aligned, wrapping rather than scrolling sideways, and selectable — the copy
   button is a convenience, not the only way to get the text out. */
.year-text-head { display: flex; align-items: center; gap: 10px; margin: 14px 0 6px; }
.year-text { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
  line-height: 1.5; white-space: pre-wrap; word-break: break-word; margin: 0;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--panel-2); color: var(--text); user-select: text; }

/* Label sheet controls. The number field is small because the value is one or
   two digits (positions to leave free on a part-used sheet), and a full-width
   input would suggest a longer answer than the field takes. */
.reports-toolbar .num-sm { width: 72px; padding: 4px 8px; }
/* A print link with nothing to print stays visible and says why (the summary
   line above it), rather than vanishing and leaving an unexplained gap. */
.btn.disabled { opacity: .5; pointer-events: none; }

/* The favourite star in the device row. It is the visible half of the drawer's
   Favorit checkbox, which for a long time saved a flag that nothing displayed —
   so the control looked broken while working perfectly. Colour is not the only
   signal: the shape is a star, and the screen-reader text says "Favourite". */
.fav-star, .row-flag { font-size: .95em; line-height: 1; }
.fav-star, .row-flag:first-child { color: var(--warn, #f59e0b); }
/* Tags and the note, as the desktop list shows them: the drawer used to be the
   only place either existed, so a device with three tags looked identical to
   one with none. */
.tag-chip { display: inline-block; font-size: .72em; padding: .05em .4em;
  border-radius: 999px; border: 1px solid var(--edge, #475569);
  color: var(--muted, #94a3b8); vertical-align: middle; }
.cmt-note { font-size: .78em; color: var(--muted, #94a3b8); margin-top: .15em;
  max-width: 40ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
