/* Outback Backup — console design system.
 *
 * Tokens first, then layout, then components. Every ink/surface pair below was
 * checked for WCAG contrast rather than eyeballed: body and secondary text clear
 * 4.5:1 on their surfaces in both modes, status text clears 4.5:1 on its own
 * tinted chip, and focus rings clear 3:1.
 *
 * Dark mode is *selected*, not an inversion — its own steps against a dark
 * surface. The OS preference is the default signal; the header toggle overrides
 * it in both directions.
 *
 * Status colour never carries meaning alone: every status chip pairs a dot with
 * a word, so it survives colour-blindness, greyscale printing, and forced-colors.
 *
 * The shapes here follow the marketing page's Utouch language — Nunito, pill
 * controls, rounded tabs, soft wide shadows, the seven-band rule across the top
 * of the header — but at the console's own *scale*, which is the part that is
 * not negotiable. Body stays 14px, numerals stay tabular, table rows keep their
 * hairlines and their height. The two surfaces are the same product; only one of
 * them has to stay readable at two hundred rows. See BRAND.md.
 */

/* The landing page's face, on the console's terms: 400 for body and 700/800/900
   where the theme sets headings and controls. Same vendored file as the landing
   page, so the second page anybody opens has it cached. */
@font-face {
  font-family: 'Nunito';
  src: url('/vendor/fonts/nunito-latin-var.woff2') format('woff2-variations'),
       url('/vendor/fonts/nunito-latin-var.woff2') format('woff2');
  font-weight: 200 1000;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light;

  /* surfaces */
  --bg:          #F5F6FA;
  --surface:     #FFFFFF;
  --surface-2:   #F2F4F9;
  --surface-3:   #E8EBF3;
  --border:      #E2E6F0;
  --border-2:    #D8DDE9;

  /* ink — 18.6 / 8.7 / 4.7 : 1 on --surface */
  --ink:         #10131C;
  --ink-2:       #444B5E;
  --muted:       #6B7387;

  /* brand */
  --accent:      #AC4318;
  --accent-hover:#8F3813;
  --accent-soft: #FAECE4;
  --accent-ink:  #FFFFFF;
  --ochre:       #C97F1E;
  --ochre-soft:  #FBF0DE;

  /* status — chip background + text, each pair ≥ 4.5:1 */
  --ok:      #0A7A3D; --ok-bg:   #E8F6EE; --ok-dot:   #0CA30C;
  --warn:    #8A5A00; --warn-bg: #FDF2DC; --warn-dot: #FAB219;
  --crit:    #B22A2A; --crit-bg: #FCEAEA; --crit-dot: #D03B3B;
  --info:    #AC4318; --info-bg: #FAECE4; --info-dot: #C15120;
  --off:     #5B6377; --off-bg:  #EEF0F5; --off-dot:  #8A93A8;

  /* Decorative only — the header's rule, and anywhere a section wants a colour
     that is not a status. The mark's rust gradient, the monolith ochre, and one
     eucalypt green. These never carry state; that is what the status tokens
     above are for. Shared with the landing page, which names them identically. */
  --u1: #7A2E0E; --u2: #C15120; --u3: #E8A24A;
  --u4: #C97F1E; --u5: #0A7A3D; --u6: #B2542E;

  /* Wider and softer than a hairline UI would ask for — the theme's cards float
     rather than sit — but a fraction of the marketing page's `20px 0 60px`,
     which at console density would smear a table of cards together. */
  --shadow:    0 1px 2px rgba(16,19,28,.04), 6px 0 20px rgba(16,19,28,.06);
  --shadow-md: 0 2px 6px rgba(16,19,28,.06), 10px 0 34px rgba(16,19,28,.10);
  --shadow-lg: 0 12px 48px rgba(16,19,28,.18);

  /* --r-md was referenced by .menupop, .guests and .vols but never defined, so
     all three had been rendering with square corners. */
  --r-sm: 9px; --r: 11px; --r-md: 12px; --r-lg: 14px; --r-xl: 18px;
  --r-pill: 50px;
  --space: 16px;
  /* Includes the 6px rule the header carries above its content row. */
  --header-h: 64px;
  --font: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* Dark steps, chosen against #141826 rather than flipped. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme=light]) {
    color-scheme: dark;
    --bg:        #0B0D14;
    --surface:   #141826;
    --surface-2: #1B2032;
    --surface-3: #232941;
    --border:    #242A3C;
    --border-2:  #2A3145;
    --ink:       #EDEFF5;
    --ink-2:     #A9B1C6;
    --muted:     #828CA3;
    --accent:      #F09878;
    --accent-hover:#F4AF95;
    --accent-soft: #2A1A12;
    --accent-ink:  #0B0D14;
    --ochre:       #E8A24A;
    --ochre-soft:  #2A2113;
    --ok:   #4ECB8B; --ok-bg:   #12251B; --ok-dot:   #3FBE8A;
    --warn: #F0B84B; --warn-bg: #2A2113; --warn-dot: #E7A63E;
    --crit: #F2807C; --crit-bg: #2A1618; --crit-dot: #EC6B64;
    --info: #F09878; --info-bg: #2A1A12; --info-dot: #E88D6C;
    --off:  #9AA3B8; --off-bg:  #1B2032; --off-dot:  #6E7689;
    --shadow:    0 1px 2px rgba(0,0,0,.3), 6px 0 20px rgba(0,0,0,.28);
    --shadow-md: 0 2px 6px rgba(0,0,0,.35), 10px 0 34px rgba(0,0,0,.34);
    --shadow-lg: 0 12px 48px rgba(0,0,0,.55);
  }
}
:root[data-theme=dark] {
  color-scheme: dark;
  --bg:        #0B0D14;
  --surface:   #141826;
  --surface-2: #1B2032;
  --surface-3: #232941;
  --border:    #242A3C;
  --border-2:  #2A3145;
  --ink:       #EDEFF5;
  --ink-2:     #A9B1C6;
  --muted:     #828CA3;
  --accent:      #F09878;
  --accent-hover:#F4AF95;
  --accent-soft: #2A1A12;
  --accent-ink:  #0B0D14;
  --ochre:       #E8A24A;
  --ochre-soft:  #2A2113;
  --ok:   #4ECB8B; --ok-bg:   #12251B; --ok-dot:   #3FBE8A;
  --warn: #F0B84B; --warn-bg: #2A2113; --warn-dot: #E7A63E;
  --crit: #F2807C; --crit-bg: #2A1618; --crit-dot: #EC6B64;
  --info: #F09878; --info-bg: #2A1A12; --info-dot: #E88D6C;
  --off:  #9AA3B8; --off-bg:  #1B2032; --off-dot:  #6E7689;
  --shadow:    0 1px 2px rgba(0,0,0,.3), 6px 0 20px rgba(0,0,0,.28);
  --shadow-md: 0 2px 6px rgba(0,0,0,.35), 10px 0 34px rgba(0,0,0,.34);
  --shadow-lg: 0 12px 48px rgba(0,0,0,.55);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--font); font-size: 14px; line-height: 1.5;
  font-variant-numeric: tabular-nums; -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* Nunito is a rounder, wider face than the system UI font this replaced, so the
   negative tracking that kept the old headings tight now closes them up. Sizes
   are unchanged — the console's scale is the thing the theme does not get to
   move — and the weight climbs to the theme's 800/900 instead. */
h1 { font-size: 22px; line-height: 1.25; margin: 0 0 2px; letter-spacing: 0; font-weight: 900; }
h2 { font-size: 17px; margin: 26px 0 10px; letter-spacing: 0; font-weight: 800; }
h3 { font-size: 14.5px; margin: 0; font-weight: 800; letter-spacing: 0; }
.sub { color: var(--muted); font-size: 13.5px; margin-bottom: 20px; max-width: 78ch; }
.small { font-size: 12.5px; }
.muted { color: var(--muted); }
.mono { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.nm { font-weight: 700; }
.sp { flex: 1; }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ---------------------------------------------------------------- header */
/* The seven-band rule is drawn as a background rather than as seven elements, so
   every page that already has a .top gets it without touching its markup — and
   it stays even at any width, which the theme's fixed-percentage floats did not.
   The 6px it occupies is inside --header-h, so .nav's sticky offset and the docs
   page's scroll-margins keep working off the one number. */
.top {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 20px 0; height: var(--header-h);
  border-bottom: 1px solid var(--border);
  background-color: var(--surface);
  background-image: linear-gradient(90deg,
    var(--u1) 0 15%, var(--u2) 15% 29%, var(--u6) 29% 43%, var(--u4) 43% 57%,
    var(--u3) 57% 71%, var(--u5) 71% 85%, var(--u1) 85% 100%);
  background-repeat: no-repeat;
  background-size: 100% 6px;
  background-position: top left;
  position: sticky; top: 0; z-index: 30;
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 900; font-size: 16px; letter-spacing: -.01em; color: var(--ink); }
.brand:hover { text-decoration: none; }
.brand b { color: var(--accent); font-weight: 900; }
.brand svg { flex: none; }
.env {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); padding: 4px 10px;
  border: 1px solid var(--border); border-radius: var(--r-pill); white-space: nowrap;
}
/* Round, like the theme's header controls. */
.iconbtn {
  width: 34px; height: 34px; display: grid; place-items: center; flex: none;
  border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface); color: var(--ink-2); cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
.iconbtn:hover { color: var(--accent); border-color: var(--accent); background: var(--surface-2); }
.tenantsel, .sel {
  font: inherit; font-size: 13px; font-weight: 600; color: var(--ink); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-pill);
  padding: 7px 13px; cursor: pointer; max-width: 100%;
}
.tenantsel { font-weight: 700; max-width: 190px; }

/* Rounded menu, after the theme: the current section is a filled pill rather
   than an underline. It states itself with surface *and* colour, which the
   2px rule did with colour alone. */
/* Every section is reachable without scrolling sideways.
 *
 * This was a single row with `overflow-x: auto` and the scrollbar hidden, which
 * is fine on a phone and quietly broken on a desktop: past about a dozen
 * sections the rest sat beyond the right edge with nothing to say so and no
 * scrollbar to drag. Shift-scroll reaches them, and nobody knows that. Adding
 * Databases made a real fleet's owner account overflow on an ordinary laptop.
 *
 * So it wraps instead. A second row is honest and costs 34px; a section nobody
 * can find costs a support call. The padding below is tightened to keep the
 * common case on one line. */
.nav {
  display: flex; flex-wrap: wrap; gap: 3px 2px; padding: 7px 20px; background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky; top: var(--header-h); z-index: 29;
}
.nav a {
  padding: 7px 11px; font-size: 13px; font-weight: 700; color: var(--muted);
  border-radius: var(--r-pill); white-space: nowrap;
  transition: background .15s, color .15s;
}
.nav a:hover { color: var(--ink); background: var(--surface-2); text-decoration: none; }
.nav a.on { color: var(--accent); background: var(--accent-soft); }
.nav .count {
  font-size: 11px; font-weight: 700; background: var(--surface-3); color: var(--ink-2);
  border-radius: 10px; padding: 1px 6px; margin-left: 6px; font-variant-numeric: tabular-nums;
}
.nav a.on .count { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }

/* The content uses the window it is given.
 *
 * A fixed 1280px column is right for prose and wrong for this: these pages are
 * dense tables, and on a wide screen the fleet was reading eleven columns
 * through a letterbox with a third of the display empty on either side. The cap
 * is now generous rather than absent — text still needs a measure, and a table
 * stretched across a 5K display is its own kind of unreadable. */
main.wrap { max-width: min(2000px, 100%); margin: 0 auto; padding: 22px 24px 56px; }

/* ---------------------------------------------------------------- stat tiles */
/* A single number is a stat tile, not a chart. The label sits above, the value
   is the largest thing in the tile, and any context is quieter than both. */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 13px; margin-bottom: 22px; }
.kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 15px 16px; box-shadow: var(--shadow);
}
/* The theme's sup-title over the theme's oversized rate. */
.kpi .k { font-size: 11px; color: var(--muted); font-weight: 900; text-transform: uppercase; letter-spacing: .1em; }
.kpi .v { font-size: 28px; font-weight: 900; letter-spacing: -.01em; margin-top: 7px; line-height: 1.1; }
.kpi .v small { font-size: 13px; color: var(--muted); font-weight: 700; letter-spacing: 0; }
.kpi .foot { font-size: 12px; color: var(--muted); margin-top: 5px; }
.kpi.alert { border-color: color-mix(in srgb, var(--crit-dot) 40%, var(--border)); }
.kpi.alert .v { color: var(--crit); }

/* ---------------------------------------------------------------- cards */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
  margin-bottom: 18px; overflow: hidden;
}
.card-h {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 13px 16px; border-bottom: 1px solid var(--border);
}
.card-b { padding: 16px; }
.scroll-x { overflow-x: auto; }

/* -------------------------------------------------------------- activity */
/* A device that is working says so, and says what it is working on. The pulse
   is the only moving thing on the page, so "something is happening" reads
   without needing to be read. */
.busy { font-variant-numeric: tabular-nums; }
.d.pulse { animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .d.pulse { animation: none; } }
.spin.sm { width: 12px; height: 12px; border-width: 2px; vertical-align: -1px; }

/* ------------------------------------------------------------ row menus */
/* A row's actions collapsed into one control. The popup is positioned fixed
   from coordinates measured when it opened: absolute positioning would be
   clipped by the table's .scroll-x, which is overflow:auto in both axes. */
.menuwrap { position: relative; display: inline-block; }
.menubtn .caret {
  display: inline-block; margin-left: 6px; border: 4px solid transparent;
  border-top-color: currentColor; transform: translateY(2px);
}
.menubtn.on { background: var(--surface-3); border-color: var(--border-2); }

.menupop {
  position: fixed; z-index: 60; min-width: 208px; padding: 5px;
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--r-md); box-shadow: var(--shadow-md);
  text-align: left;
}
.menuitem {
  display: block; width: 100%; padding: 7px 10px; border: 0; border-radius: var(--r-sm);
  background: none; color: var(--ink); font: inherit; font-size: 13px;
  text-align: left; text-decoration: none; cursor: pointer; white-space: nowrap;
}
.menuitem:hover { background: var(--surface-2); }
.menuitem:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.menuitem.danger { color: var(--crit); }
.menuitem.danger:hover { background: var(--crit-bg); }
/* Disabled items stay listed and say why. An action that simply disappears
   leaves somebody hunting for it; one greyed out with no reason is worse. */
.menuitem.off { color: var(--muted); cursor: default; }
.menuitem.off:hover { background: none; }
.menuitem .why { display: block; font-size: 11px; color: var(--muted); }
.menusep { height: 1px; margin: 5px 6px; background: var(--border); }
.menuhead {
  padding: 7px 10px 3px; font-size: 11px; font-weight: 900; letter-spacing: .1em;
  color: var(--muted); text-transform: uppercase;
}
/* No rule above the first heading: it sits at the top of the popup, where a
   line would be a border on nothing. */
.menuhead:first-child { padding-top: 3px; }

/* ------------------------------------------------------- master / detail */
/* The device list beside what it filters, on the two pages people arrive at
   asking "what do I have for THIS machine?". A dropdown hides that answer
   behind a click and forgets it again the moment it closes.

   minmax(0, 1fr) on the detail column is load-bearing: it holds a table in a
   .scroll-x, and a default `auto` column would size to the table's full width
   and push the rail off the page instead of scrolling. */
.master {
  display: grid; grid-template-columns: minmax(190px, 232px) minmax(0, 1fr);
  gap: 16px;
  /* The rail runs the full height of the table beside it. Aligned to the top it
     ended halfway down a long list, leaving the page looking like the sidebar
     had run out rather than that the list had more in it. */
  align-items: stretch;
}
.master .detail { min-width: 0; }
/* min-height:0 keeps the rail from driving the row height: without it a host
   list longer than the table would stretch the row and leave the table sitting
   in dead space. The list scrolls inside instead. */
.master .rail { display: flex; flex-direction: column; min-height: 0; margin-bottom: 0; }
.master .rail .raillist { flex: 1 1 auto; min-height: 0; max-height: none; }
@media (max-width: 860px) {
  /* Stacked, the rail becomes a header for the list rather than a sidebar. */
  .master { grid-template-columns: 1fr; }
  .master .rail .raillist { max-height: 220px; }
}

.rail { margin-bottom: 18px; }
.raillist { max-height: min(62vh, 640px); overflow-y: auto; }

.ritem {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 12px 9px 10px; border: 0; border-left: 3px solid transparent;
  background: none; text-align: left; cursor: pointer; color: var(--ink);
  font: inherit; border-bottom: 1px solid var(--border);
}
.ritem:last-child { border-bottom: 0; }
.ritem:hover { background: var(--surface-2); }
.ritem:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The selected row is stated by surface AND edge, not colour alone. */
.ritem.on { background: var(--accent-soft); border-left-color: var(--accent); }

/* Reorderable rows. The grab cursor is the only affordance that says a row can
   be moved; without it the feature is invisible and nobody finds it. */
.ritem.drag { cursor: grab; }
.ritem.drag:active { cursor: grabbing; }

/* Where the dragged row would land. A line above the target rather than a
   filled highlight, because the question is "between which two", and a
   highlighted row reads as "onto this one" — which would mean something else
   entirely on a list whose rows are also drop targets for devices. */
.ritem.dropto { box-shadow: inset 0 2px 0 0 var(--accent); }
.ritem.on .rname { font-weight: 620; }

.rico { display: flex; width: 18px; flex: none; color: var(--muted); }
.rico svg { width: 18px; height: 18px; }
.rtext { min-width: 0; flex: 1; }
.rname {
  display: block; font-size: 13px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rmeta {
  display: block; font-size: 11px; color: var(--muted); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rcount {
  flex: none; font-size: 11.5px; font-variant-numeric: tabular-nums;
  color: var(--ink-2); background: var(--surface-3);
  border-radius: 10px; padding: 1px 7px;
}
.rcount.warn { background: var(--warn-bg); color: var(--warn); }

/* ---------------------------------------------------------------- tables */
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
/* The theme's sup-title, doing a column header's job: uppercase, tracked wide,
   and heavy enough to hold the top of a long list. */
th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); font-weight: 900; padding: 10px 16px;
  border-bottom: 1px solid var(--border); white-space: nowrap;
  background: var(--surface); position: sticky; top: 0;
}
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--ink); }
/* A sorted column is stated, not hinted: which column the order comes from is
   the first thing somebody needs to know when a list surprises them. */
th.sortable.sorted { color: var(--ink); }
th .arr { opacity: .6; font-size: 10px; }
th.sortable.sorted .arr { opacity: 1; }
td { padding: 11px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tr:last-child td { border-bottom: 0; }
tbody tr.hoverable:hover { background: var(--surface-2); cursor: pointer; }
tbody tr.picked { background: var(--accent-soft); }
tbody tr.picked:hover { background: var(--accent-soft); }
.selbar .btn.link { padding: 2px 6px; }
td.right, th.right { text-align: right; }
td.nowrap, th.nowrap { white-space: nowrap; }
.cell { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cell > div { min-width: 0; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.os {
  width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--ink-2); flex: none;
}
.os svg { width: 16px; height: 16px; }

/* ---------------------------------------------------------------- status */
/* Dot + word, always. Colour is reinforcement, never the message. */
.pill {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font-size: 12px; font-weight: 700; padding: 3px 11px 3px 8px; border-radius: var(--r-pill);
}
.pill .d { width: 7px; height: 7px; border-radius: 50%; flex: none; }
/* A pill that is a link keeps the pill's colour rather than taking the page's
   link blue, which would make one row of engine names look like a different
   kind of thing from the identical row beside it. The pointer and the lift on
   hover are what say it is clickable. */
a.pill { text-decoration: none; color: inherit; cursor: pointer; }
a.pill:hover { filter: brightness(1.08); text-decoration: none; }
.ok   { background: var(--ok-bg);   color: var(--ok); }   .ok .d   { background: var(--ok-dot); }
.warn { background: var(--warn-bg); color: var(--warn); } .warn .d { background: var(--warn-dot); }
.crit { background: var(--crit-bg); color: var(--crit); } .crit .d { background: var(--crit-dot); }
.info { background: var(--info-bg); color: var(--info); } .info .d { background: var(--info-dot); }
.off  { background: var(--off-bg);  color: var(--off); }  .off .d  { background: var(--off-dot); }
.tag {
  display: inline-block; font-size: 11.5px; font-weight: 700; padding: 2px 10px;
  border-radius: var(--r-pill); background: var(--surface-3); color: var(--ink-2);
}

/* Usage meter — magnitude as length, which reads faster than a number alone. */
.meter { height: 6px; border-radius: 3px; background: var(--surface-3); overflow: hidden; min-width: 90px; }
.meter > i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.meter.warn > i { background: var(--warn-dot); }
.meter.crit > i { background: var(--crit-dot); }

/* ---------------------------------------------------------------- buttons */
/* Pills, after the theme, and the primary one throws the same coloured glow on
   hover. Not uppercased: the theme sets its buttons in 900 caps, which works for
   six of them on a marketing page and turns a table row carrying "Back up now",
   "Restore" and "Manage" into shouting. */
.btn {
  font: inherit; font-weight: 700; font-size: 12.5px; padding: 7px 15px;
  border-radius: var(--r-pill); border: 1px solid var(--border-2);
  background: var(--surface); color: var(--ink); cursor: pointer; white-space: nowrap;
  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
}
.btn:hover:not(:disabled) { border-color: var(--muted); background: var(--surface-2); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.primary:hover:not(:disabled) {
  background: var(--accent-hover); border-color: var(--accent-hover);
  box-shadow: 6px 0 18px 0 color-mix(in srgb, var(--accent) 40%, transparent);
}
.btn.danger { color: var(--crit); border-color: color-mix(in srgb, var(--crit-dot) 35%, var(--border-2)); }
.btn.danger:hover:not(:disabled) { border-color: var(--crit-dot); background: var(--crit-bg); }
.btn.sm { font-size: 12px; padding: 5px 12px; }
.btn.link { border: 0; background: none; color: var(--accent); padding: 2px 8px; }
.btn.link:hover { background: var(--accent-soft); }

/* ---------------------------------------------------------------- forms */
label.f { display: block; margin-bottom: 14px; }
label.f > span { display: block; font-size: 12px; font-weight: 650; color: var(--ink-2); margin-bottom: 5px; }
label.f .hint { font-weight: 400; color: var(--muted); }
input[type=text], input[type=number], input[type=search], input[type=password], select, textarea {
  font: inherit; font-size: 13.5px; width: 100%; padding: 9px 13px;
  border-radius: var(--r); border: 1px solid var(--border-2);
  background: var(--surface); color: var(--ink);
}
textarea { min-height: 84px; resize: vertical; font-family: var(--mono); font-size: 12.5px; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--muted); }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.grid5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0 10px; }
.search { max-width: 300px; }
.chk { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.chk input { width: auto; }
/* A checkbox inside a modal form is both .f and .chk, and label.f is the more
   specific selector — so display:block won and the box, its caption and its
   text stacked into three lines each. These have to out-specify label.f rather
   than merely follow it, which is why they name both classes. */
label.f.chk { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
label.f.chk > span { display: inline; font-size: 13px; font-weight: 500; color: var(--ink); margin: 0; }
label.f.chk input { flex: none; margin: 0; }
.fsection { margin: 18px 0 8px; font-size: 12px; font-weight: 650; color: var(--ink-2); }
.fsection .hint { display: block; font-weight: 400; color: var(--muted); margin-top: 2px; }

/* One database engine in the policy editor: a collapsed summary that still says
   what the policy does, opening onto the databases the fleet reports. Collapsed
   by default when nothing is selected, so an engine nobody uses costs one line
   rather than a screen of names. */
.dbengine {
  border: 1px solid var(--border); border-radius: var(--r-md, 8px);
  padding: 10px 12px; margin-bottom: 8px; background: var(--surface-2);
}
.dbengine > summary { cursor: pointer; font-size: 13px; color: var(--ink); }
.dbengine > summary .hint { color: var(--muted); font-weight: 400; }
/* The engine's own controls are indented under the summary so the "every
   database" switch reads as being about this engine and not the next one. */
.dbengine > *:not(summary) { margin-left: 14px; }
.dbengine > label.f.chk { margin-top: 10px; }
.dbengine textarea { min-height: 52px; }

/* ---------------------------------------------------------------- pagination */
.pager {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 11px 16px; border-top: 1px solid var(--border); background: var(--surface-2);
  font-size: 12.5px; color: var(--muted);
}
.pager .pages { display: flex; gap: 4px; align-items: center; }
.pager button {
  font: inherit; font-size: 12.5px; font-weight: 700; min-width: 32px; padding: 5px 10px;
  border-radius: var(--r-pill); border: 1px solid var(--border-2);
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.pager button:hover:not(:disabled):not(.on) { background: var(--surface-3); }
.pager button.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); cursor: default; }
.pager button:disabled { opacity: .4; cursor: not-allowed; }
.pager .gap { color: var(--muted); padding: 0 2px; }
.pager .size { margin-left: auto; display: flex; align-items: center; gap: 7px; }
.pager .size select { font-size: 12.5px; padding: 4px 7px; }

/* ---------------------------------------------------------------- states */
.empty { padding: 40px 22px; text-align: center; color: var(--muted); }
.empty strong { display: block; color: var(--ink); font-size: 15.5px; margin-bottom: 6px; font-weight: 800; }
.empty .btn { margin-top: 14px; }
.spin {
  width: 15px; height: 15px; border: 2px solid var(--border-2); border-top-color: var(--accent);
  border-radius: 50%; display: inline-block; animation: spin .7s linear infinite; vertical-align: -3px;
}
@keyframes spin { to { transform: rotate(360deg); } }

.code { position: relative; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r); padding: 13px 46px 13px 14px; margin: 9px 0; }
.code pre { margin: 0; font-family: var(--mono); font-size: 12.5px; color: var(--ink); white-space: pre-wrap; word-break: break-all; }
.code .copy { position: absolute; top: 8px; right: 8px; }


/* A secondary strip inside a card: the filter/summary bar that sits between the
   header and the table. Recessed so it reads as controls for the content below
   rather than as content itself. */
.card-b.subtle {
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  padding: 10px 16px;
}
.card-b.subtle p:last-child { margin-bottom: 0; }
.card-b.subtle .field > span { font-size: 11px; }

/* Tabs inside a note: the platform switcher on a new install command. The page
   tabs above assume a card edge; these sit inside a callout, so they carry their
   own spacing and no surface of their own. */
.note .tabs { padding: 0; margin: 12px 0 10px; background: none; border-bottom-color: var(--border-2); }
.note .tabs button { font-size: 12.5px; padding: 7px 12px; display: inline-flex; align-items: center; gap: 6px; }
/* The platform glyph on an install tab: the console's OS_ICON vocabulary, small
   and inline rather than the boxed .os chip the fleet rows use. The macOS+Linux
   tab carries two, so tico lays its children out in a row with a hair of space. */
.note .tabs .tico { display: inline-flex; align-items: center; gap: 3px; color: var(--muted); }
.note .tabs button.on .tico { color: var(--accent); }
.note .tabs .tico svg { width: 15px; height: 15px; display: block; }

/* A button that carries a platform glyph beside its label — the download
   buttons on the support page. The glyph is the same OS_ICON vocabulary the
   fleet uses, but inline and unboxed: .os is a 30px chip with its own surface
   and border, and inside a pill button that is a box within a box. */
.btn.ico { display: inline-flex; align-items: center; gap: 6px; }
.btn.ico .tico { display: inline-flex; align-items: center; color: var(--muted); }
.btn.ico .tico svg { width: 14px; height: 14px; display: block; }

/* Storage test results: one line per check, so the step that failed is obvious
   rather than buried in a paragraph. */
.checks { display: grid; gap: 6px; margin: 10px 0 4px; }
.check { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.check .pill { min-width: 104px; justify-content: center; }
tr.testrow > td { background: var(--surface-2); padding: 6px 16px 14px; }

.crit-text { color: var(--crit); }

/* The bell carries a count of what is unacknowledged. It is only ever coloured
   when something is actually wrong, so a coloured bell means something. */
#bell { position: relative; }
#bell.alerting { color: var(--crit); }
.badge-dot {
  position: absolute; top: 2px; right: 2px; min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 8px; background: var(--crit); color: #fff; font-size: 10px; font-weight: 700;
  line-height: 15px; text-align: center;
}

/* One filter row. Every list page uses this: controls sit on a single line and
   only wrap when the viewport genuinely cannot hold them, rather than stacking
   into a tall block that pushes the table off the screen. The search box takes
   whatever width is left. */
.filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* Controls default to width:100% for stacked modal forms. In a wrapping flex row
   that makes every one of them claim a full line, which is exactly the stacking
   this layout exists to avoid — so size them to their content here. */
.filters input, .filters select, .filters .sel {
  width: auto; flex: 0 0 auto; height: 32px; padding: 5px 9px; font-size: 13px;
}
.filters select { max-width: 220px; }
.filters .grow { flex: 1 1 200px; min-width: 140px; width: auto; }
.filters input[type=date] { min-width: 132px; }
.filters label.inline {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font-size: 12px; font-weight: 650; color: var(--ink-2);
}
.filters label.inline input[type=date] { font-weight: 400; }
.filters label.inline.chk { font-weight: 500; color: var(--ink-2); }
.filters label.inline.chk input { margin: 0; }
.nowrap { white-space: nowrap; }

/* An inline form row: the labelled controls that sit in a card body rather than
   in a modal. label.f above is the modal's stacked variant; this one lays out in
   a row and lets the free-text field take the slack. */
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field > span { font-size: 12px; font-weight: 650; color: var(--ink-2); }
.field input, .field select { min-width: 0; }
.field:has(input[type=text]), .field:has(input:not([type])) { flex: 1 1 220px; }
.row.wrap { flex-wrap: wrap; align-items: flex-end; }
.row.gap { gap: 12px; }

/* A callout inside a card: the result of an action, kept visually distinct from
   the form that produced it. Colour follows the same status tokens as .pill. */
.note {
  border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--r);
  background: var(--surface-2); padding: 14px 16px; margin: 14px 0 2px;
}
.note > strong { display: block; color: var(--ink); font-size: 14px; font-weight: 800; margin-bottom: 4px; }
.note h4 { margin: 16px 0 0; font-size: 12px; font-weight: 650; color: var(--ink-2); }
.note h4:first-of-type { margin-top: 12px; }
.note p:last-child { margin-bottom: 0; }
.note.ok   { border-left-color: var(--ok-dot);   background: var(--ok-bg); }
.note.warn { border-left-color: var(--warn-dot); background: var(--warn-bg); }
.note.crit { border-left-color: var(--crit-dot); background: var(--crit-bg); }
.note.info { border-left-color: var(--info-dot); background: var(--info-bg); }

/* ---------------------------------------------------------------- overlays */
.scrim { position: fixed; inset: 0; background: rgba(11,13,20,.55); display: grid; place-items: center; z-index: 60; padding: 20px; backdrop-filter: blur(2px); }
.modal {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg); width: 100%; max-width: 560px; max-height: 88vh;
  display: flex; flex-direction: column;
}
.modal.wide { max-width: 840px; }
.modal-h { display: flex; align-items: center; gap: 10px; padding: 16px 20px; border-bottom: 1px solid var(--border); }
.modal-h h3 { font-size: 16px; }
.modal-b { padding: 20px; overflow: auto; }
.modal-f { display: flex; gap: 9px; justify-content: flex-end; padding: 14px 20px; border-top: 1px solid var(--border); background: var(--surface-2); }

.drawer-scrim { position: fixed; inset: 0; background: rgba(11,13,20,.45); z-index: 50; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(720px, 100%);
  background: var(--bg); border-left: 1px solid var(--border); box-shadow: var(--shadow-lg);
  z-index: 51; display: flex; flex-direction: column;
}
/* The slide belongs to the panel arriving, not to every paint of it. The panel
   is rewritten whole for each tab, each action and each menu opened over it, and
   while the transform runs the drawer is the containing block for the fixed
   actions menu inside it — which would then be drawn 26px from where it was
   measured, and fade in at the same time. */
.drawer.in { animation: slide .18s ease-out; }
@keyframes slide { from { transform: translateX(26px); opacity: .4; } }
.drawer-h { padding: 17px 20px; border-bottom: 1px solid var(--border); background: var(--surface); }
.drawer-b { overflow: auto; padding: 18px 20px; flex: 1; }
/* Same rounded menu as .nav, one level down. */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); background: var(--surface); padding: 7px 20px; overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  font: inherit; font-size: 13px; font-weight: 700; padding: 7px 13px; border: 0;
  border-radius: var(--r-pill); background: none; color: var(--muted);
  cursor: pointer; white-space: nowrap; transition: background .15s, color .15s;
}
.tabs button:hover { color: var(--ink); background: var(--surface-2); }
.tabs button.on { color: var(--accent); background: var(--accent-soft); }
/* The device's actions share the tab strip rather than sitting on a row of
   their own above it. They act on the machine whichever tab is open, so a line
   to themselves was a line of header spent saying nothing. Kept clear of the
   tabs' bottom rule, and not scrolled away with them on a narrow drawer. */
.tabs .tabactions {
  display: flex; align-items: center; gap: 6px; padding: 5px 0 5px 12px;
  position: sticky; right: 0; background: var(--surface); flex: none;
}
.tabs .tabactions .btn { white-space: nowrap; }

.dl { display: grid; grid-template-columns: 168px 1fr; gap: 9px 16px; font-size: 13.5px; }
.dl dt { color: var(--muted); font-weight: 600; font-size: 12.5px; }
.dl dd { margin: 0; word-break: break-word; }

/* ---------------------------------------------------------------- feed */
.feed { list-style: none; margin: 0; padding: 0; }
.feed li { display: flex; gap: 12px; padding: 11px 16px; border-bottom: 1px solid var(--border); }
.feed li:last-child { border-bottom: 0; }
.feed .lvl { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; flex: none; background: var(--off-dot); }
.feed .lvl.info { background: var(--info-dot); }
.feed .lvl.warn { background: var(--warn-dot); }
.feed .lvl.error { background: var(--crit-dot); }
.feed .msg { flex: 1; min-width: 0; }
.feed .meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
.feed time { color: var(--muted); font-size: 12px; white-space: nowrap; }

/* ---------------------------------------------------------------- files */
.crumbs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; font-size: 13px; }
.crumbs button { font: inherit; font-size: 13px; border: 0; background: none; color: var(--accent); cursor: pointer; padding: 3px 5px; border-radius: 5px; }
.crumbs button:hover { background: var(--accent-soft); }
.crumbs .sep { color: var(--muted); }
.files td:first-child { width: 36px; }
.fname { display: flex; align-items: center; gap: 9px; min-width: 0; }
.fname svg { width: 16px; height: 16px; color: var(--muted); flex: none; }
.fname .dir { color: var(--ochre); }
.selbar { display: flex; align-items: center; gap: 10px; padding: 11px 16px; background: var(--accent-soft); border-bottom: 1px solid var(--border); font-size: 13px; font-weight: 700; }

/* ---------------------------------------------------------------- toasts */
.toasts { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: center; width: min(560px, 92vw); }
.toast {
  background: var(--ink); color: var(--bg); padding: 13px 18px; border-radius: var(--r-lg);
  font-weight: 700; font-size: 13px; box-shadow: var(--shadow-lg); animation: rise .2s ease-out; width: 100%;
}
.toast.bad  { background: var(--crit-dot); color: #fff; }
.toast.good { background: var(--ok-dot);   color: #fff; }
@keyframes rise { from { transform: translateY(10px); opacity: 0; } }

.dot-live { width: 8px; height: 8px; border-radius: 50%; background: var(--ok-dot); display: inline-block; margin-right: 5px; animation: pulse 2s infinite; }
.dot-live.paused { background: var(--muted); animation: none; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

kbd { font-family: inherit; font-size: 11px; font-weight: 600; background: var(--surface-3); border: 1px solid var(--border-2); border-bottom-width: 2px; border-radius: 5px; padding: 1px 5px; color: var(--ink-2); }

/* ---------------------------------------------------------------- docs page
 *
 * /docs is public and self-contained: somebody reads it before they have an
 * account, so it carries no build step, no external asset, and no script beyond
 * the theme toggle. It reuses the console's header, cards, notes, tables and
 * code blocks so the documentation and the product read as one thing — what is
 * new here is only the two-column reading layout, because nothing else in this
 * product is a long page of prose.
 */
.doc {
  display: grid; grid-template-columns: 214px minmax(0, 1fr); gap: 34px;
  max-width: 1180px; margin: 0 auto; padding: 26px 24px 72px; align-items: start;
}

/* The contents list stays put while the page scrolls. The sections are long, and
   a reader who has scrolled into "Restoring" should not have to scroll back up
   to reach "Storage". */
.doc-toc { position: sticky; top: calc(var(--header-h) + 18px); display: flex; flex-direction: column; gap: 2px; }
.doc-toc strong {
  font-size: 11px; font-weight: 900; text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); margin-bottom: 8px; padding: 0 13px;
}
.doc-toc a {
  padding: 7px 13px; border-radius: var(--r-pill); font-size: 13px; font-weight: 700; color: var(--ink-2);
}
.doc-toc a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }

.doc-main { min-width: 0; }
.doc-main h1 { font-size: clamp(24px, 3.4vw, 31px); letter-spacing: 0; margin-bottom: 12px; }
/* scroll-margin keeps a heading clear of the sticky header when the page is
   jumped to from the contents list; without it the anchor lands under the bar. */
.doc-main h2 {
  font-size: 20px; letter-spacing: 0; margin: 42px 0 8px;
  scroll-margin-top: calc(var(--header-h) + 16px);
}
.doc-main h3 { font-size: 14.5px; margin: 24px 0 7px; scroll-margin-top: calc(var(--header-h) + 16px); }
.doc-main p { color: var(--ink-2); line-height: 1.62; margin: 0 0 12px; max-width: 74ch; }
.doc-main ul, .doc-main ol { color: var(--ink-2); line-height: 1.62; margin: 0 0 14px; padding-left: 19px; max-width: 74ch; }
.doc-main li { margin-bottom: 6px; }
.doc-main li:last-child { margin-bottom: 0; }
.doc-main .card { max-width: 74ch; }
.doc-lede { font-size: 15.5px; margin-bottom: 6px; }
.doc-foot {
  border-top: 1px solid var(--border); background: var(--surface);
  padding: 24px 0; color: var(--muted); font-size: 13px;
}
.doc-foot .doc { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; padding-block: 0; }

/* Header links on the public pages: the console's nav weight without its tab
   underline, since on a static page there is nothing to be "on". */
.toplink {
  font-size: 13.5px; font-weight: 700; color: var(--ink-2);
  padding: 8px 14px; border-radius: var(--r-pill); transition: background .15s, color .15s;
}
.toplink:hover { color: var(--accent); background: var(--surface-2); text-decoration: none; }

@media (max-width: 900px) {
  /* Stacked, a sticky contents list would follow the reader down the page and
     sit on top of the prose, so it becomes an ordinary block of links instead. */
  .doc { grid-template-columns: 1fr; gap: 16px; padding: 20px 16px 56px; }
  .doc-toc { position: static; flex-direction: row; flex-wrap: wrap; gap: 6px; }
  .doc-toc strong { width: 100%; margin-bottom: 0; padding: 0; }
  .doc-toc a { background: var(--surface-2); border: 1px solid var(--border); }
  .doc-main .card { max-width: none; }
}

/* ---------------------------------------------------------------- responsive */
/* Tables are the hard case: rather than shrink text, the low-value columns drop
   out and the row keeps its identity, actions and status. */
@media (max-width: 1040px) {
  .dl { grid-template-columns: 140px 1fr; }
  .grid5 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 820px) {
  main.wrap { padding: 18px 14px 48px; }
  .top, .nav { padding-inline: 14px; }
  .grid2, .grid5 { grid-template-columns: 1fr; }
  .dl { grid-template-columns: 1fr; gap: 2px 0; }
  .dl dd { margin-bottom: 10px; }
  .dl dt { margin-top: 6px; }
  .search { max-width: none; flex: 1 1 180px; }
  .tenantsel { max-width: 128px; }
  th, td { padding-inline: 12px; }
  .hide-md { display: none !important; }
  .kpi .v { font-size: 24px; }
  .modal, .modal.wide { max-width: 100%; }
  .drawer { width: 100%; }
  .pager { justify-content: center; }
  .pager .size { margin-left: 0; width: 100%; justify-content: center; }
}
@media (max-width: 560px) {
  .hide-sm { display: none !important; }
  .kpis { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .kpi { padding: 12px 13px; }
  .kpi .v { font-size: 21px; }
  .card-h { padding: 11px 13px; }
  .brand span.word { display: none; }
  h1 { font-size: 19px; }
  .btn { padding: 7px 11px; }
  /* Action buttons stack rather than overflow the row. */
  td.actions { white-space: normal; }
  td.actions .btn { margin: 2px 0 0 2px; }
}

@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }

/* Forced-colors: keep structure, let the OS own the palette. Status still reads,
   because every chip carries a word beside its dot. */
@media (forced-colors: active) {
  .card, .kpi, .modal, .drawer { border: 1px solid CanvasText; }
  .pill { border: 1px solid CanvasText; }
  .btn.primary { border: 2px solid CanvasText; }
}

/* ------------------------------------------------------------ backup runs */
/* A policy run makes one restore point per source, so a nightly backup of a
   machine with four folders lands as four rows seconds apart. The header names
   the run; its restore points sit under it, indented, so the eye reads thirty
   backups rather than a hundred and twenty events. */
tr.runhead > td {
  background: var(--surface-2); border-top: 1px solid var(--border-2);
  padding-top: 9px; padding-bottom: 9px;
}
tr.runhead + tr > td:first-child,
tr.runhead ~ tr:not(.runhead) > td:first-child { padding-left: 26px; }

/* ---------------------------------------------------------------- guests */
/* A hypervisor's guests, in the device panel. This was one comma-separated
   line, which is technically the information and practically unreadable — and
   said nothing about whether any of it was protected, which is the question the
   panel is open to answer. */
.guests { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.guest {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 12px;
  background: var(--surface); padding: 8px 10px; align-items: baseline;
}
.gname { font-weight: 700; font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.gshape { grid-column: 1; }
.gback { grid-column: 2; grid-row: 1 / span 2; text-align: right; white-space: nowrap; align-self: center; }

/* --------------------------------------------------------------- machine */
/* What a machine says about itself, in the device panel.
   Tiles rather than more rows of the definition list: these are five short
   readings compared against each other far more often than read one at a time,
   and a column of them among policy names and enrolment dates gets scanned as
   prose. Auto-fit so the same markup is a row in the drawer and a stack on a
   phone, with no second breakpoint to keep in step. Magnitude is drawn as well
   as written, because "6.6 GB" says nothing until you know what it is 6.6 GB
   of. */
.mstats { display: grid; grid-template-columns: repeat(auto-fit, minmax(124px, 1fr)); gap: 11px 14px; }
.mstat { min-width: 0; }
.mk { color: var(--muted); font-weight: 600; font-size: 11.5px; }
.mv { font-size: 13.5px; margin-top: 1px; }

/* The volumes take the guests' shape above — same border, same one-pixel gap —
   so two lists in one panel do not read as two different kinds of thing. */
.vols { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.vol {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 12px;
  background: var(--surface); padding: 8px 10px; align-items: baseline;
}
.vname { grid-column: 1; font-weight: 700; font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.vfree { grid-column: 2; text-align: right; white-space: nowrap; }
.vroles { grid-column: 1; }
.vmeter { grid-column: 1 / -1; }

/* Every group in a grouped fleet is its own table, so each one sized its
   columns from its own rows: a section holding one short name and a section
   holding a long one disagreed about where every column began, and the page
   read as a stack of unrelated tables rather than one list. Fixed layout with
   named widths makes them agree, and the device column absorbs the slack so
   the widths are a shape rather than a total that has to add up.

   Only the fleet's tables. Everything else on the page still sizes to its
   content, which is right where there is one table and nothing to line up with. */
table.fleet { table-layout: fixed; }
/* The device column is what is left after these, so every pixel one of them
   does not need is a hostname that fits. 296px here paid for a "Back up now"
   button that has since moved into the menu, and the column went on reserving
   it — which is why names were being cut to "lapt…" beside 90px of nothing. */
table.fleet th:nth-child(1) { width: 34px; }   /* the select box */
table.fleet th:nth-child(2) { width: auto; }   /* device — takes what is left */
table.fleet th:nth-child(3) { width: 210px; }  /* status pills and group chips */
table.fleet th:nth-child(4) { width: 150px; }  /* policy */
table.fleet th:nth-child(5) { width: 120px; }  /* last seen */
table.fleet th:nth-child(6) { width: 150px; }  /* last backup */
table.fleet th:nth-child(7) { width: 90px; }   /* agent version */
table.fleet th:nth-child(8) { width: 200px; }  /* Manage, and the menu */
/* Fixed columns clip rather than widen, so anything that could be long says so
   with an ellipsis instead of being cut mid-character. The device name and the
   policy are the two that get long; the rest are short by construction. */
table.fleet td { overflow: hidden; text-overflow: ellipsis; }
table.fleet td .nm, table.fleet td .mono {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The machines in a group sit in from its heading, so the list reads as nested
   rather than as a heading that happens to have a table under it.

   The indent is on the device cell alone, not on the table. Indenting the whole
   table took the column headers with it, and a repeated "Device / Status /
   Policy" row shifted right of everything else on the page reads as a mistake
   rather than as nesting — the machines are what belongs to the group, not the
   column names. Leaving the table where it is also means the header still sits
   over the column it names. */
.groupbody tbody td:nth-child(2) { padding-left: 34px; }

/* The select box moves with the name, or the row is indented from its second
   column and the tick sits outside the block it belongs to.

   It shifts rather than being padded because its column is a fixed 34px — just
   the box — so padding has nothing to give and would clip it. The distance is
   the same less a few pixels, and it travels into the gap the name's own padding
   just opened — the shortfall is what keeps the box off the first letter of the
   name rather than up against it. */
.groupbody tbody td:nth-child(1) { overflow: visible; }
.groupbody tbody td:nth-child(1) > input { transform: translateX(26px); }

@media (max-width: 860px) {
  /* Below this the row genuinely cannot fit, so it is allowed to wrap — a
     button running off the side of a phone is worse than two short rows. */
  .pageactions { flex-wrap: wrap; }
  /* On a narrow screen the width matters more than the nesting, and the group
     heading is directly above its rows anyway. */
  .groupbody tbody td:nth-child(2) { padding-left: 16px; }
  .groupbody tbody td:nth-child(1) > input { transform: translateX(8px); }
}

/* The summary folds away. Somebody watching a fleet all day wants the machines
   at the top of the screen, and four tiles they already know the contents of
   are the first thing worth giving up. */
.kpirow { display: flex; margin-bottom: 8px; }
.kpitoggle {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 7px 3px 4px; border: 0; border-radius: var(--r-sm);
  background: none; color: var(--muted); font: inherit; font-size: 12px;
  font-weight: 650; letter-spacing: .01em; cursor: pointer;
}
.kpitoggle:hover { background: var(--surface-2); color: var(--ink-2); }
/* The caret points down when the tiles are showing and right when they are
   folded, which is the one part of this control that says which way it goes.

   It has to be drawn here as well. The triangle was only ever defined under
   .menubtn, so inside this button the span rendered as nothing at all — the
   control was the word "Summary" in small grey type, with no arrow and nothing
   saying it could be clicked, which is exactly how somebody ends up asking
   where the collapse control is. */
.kpitoggle .caret {
  display: inline-block; border: 4px solid transparent;
  border-top-color: currentColor; transform: translateY(2px);
  transition: transform .12s ease;
}
.kpitoggle[aria-expanded="false"] .caret { transform: translateY(-1px) rotate(-90deg); }
.kpis.hidden { display: none; }

/* The fleet's controls sit on the title line rather than in the table's own
   header: they act on the whole page — the rail, the grouping and the table at
   once — and a control that changes what the sidebar shows is misfiled inside
   the thing beside it.

   Two blocks, not one row of eight things. The first attempt was the flat row,
   and on a real fleet it wrapped into three ragged lines with the subtitle
   stranded underneath the buttons: flex-wrap breaks between whatever happens to
   be next, so the title, the filters and the actions all came apart from each
   other. Grouping them means a narrow window drops the actions below the title
   as a unit, which is the only break worth having. */
.pagehead {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px 16px; flex-wrap: wrap; margin-bottom: 20px;
}
.pagehead h1 { margin: 0; }
/* The title gives way, the controls do not, and neither one grows.
   The subtitle otherwise claimed its full 78ch and squeezed the controls until
   "Add device" wrapped to a line of its own — a button on its own row,
   right-aligned under nothing, which reads as a mistake. Letting the title
   *shrink* fixes that; letting it *grow* re-creates it, because the free space
   the controls need would be handed to the subtitle instead. */
.pagetitle { flex: 0 1 auto; min-width: 0; }
.pagetitle .sub { margin-bottom: 0; }
/* One row, and it moves as one.
   Left free to wrap internally, it wrapped "Add device" onto a second line
   while 500px of the header sat empty beside it — a flex container that wraps
   is then sized to its wrapped width, so it never reclaims the space it just
   gave up. Kept on one line it asks for its true width, and when the window
   really is too narrow the outer .pagehead drops the whole block below the
   title, which is the only break worth having. */
.pageactions {
  display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
  justify-content: flex-end; margin-left: auto; flex: 0 0 auto;
}
/* Inputs in this app are width:100% by default, which inside a flex row means
   "take everything" — that is what stretched the group select across the page.
   Both are given their own size and told not to grow. */
.pageactions .search { width: 230px; flex: none; }
/* Its caption is two words and must stay two words on one line: squeezed, the
   label broke after "Group" and left "by" sitting under it. */
.pageactions .chk { flex: none; white-space: nowrap; }
.pageactions .sel { flex: none; max-width: 200px; }

/* An agent's own readings, as a two-column list rather than "key — value" runs.
   The dash worked while every value was a word; it stopped working the moment a
   machine reported eleven guest names, because there was nothing to line the
   values up against and no way to scan down them. */
.healthlist { display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr); gap: 2px 14px; }
.healthlist > div { display: contents; }
.healthlist > div > span:last-child { overflow-wrap: anywhere; }

/* A button reduced to a single character.
   It has to be square, or a "+" in a button shaped for two words reads as a
   label that failed to load. The name lives in aria-label and title, because a
   glyph on its own tells a screen reader nothing and a pointer nothing until it
   is hovered. */
.btn.glyph {
  padding: 0; width: 30px; height: 30px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 500;
}

/* The same menu button, reduced to its glyph where the word will not fit.
   Squarer than a text button so it reads as an affordance rather than as a
   truncated label, and the dots are nudged up because ⋯ sits on the baseline
   while the heading beside it is centred on its own. */
.btn.sm.menubtn.glyph {
  padding: 4px 8px; line-height: 1; font-size: 15px; letter-spacing: 1px;
}

/* One mark per machine, before its name: is anything wrong here.
   Deliberately small and deliberately first — it is meant to be read by
   scanning a column of them, not by looking at any one. The ring keeps it
   visible on the row's hover tint, where a flat dot of the same colour as the
   pills beside it started to disappear. */
.statedot {
  width: 9px; height: 9px; border-radius: 50%; flex: none; display: inline-block;
  box-shadow: 0 0 0 2px var(--surface);
}
/* The -dot variants, not the text colours: those are darkened for contrast
   against a background and read as brown and olive at 9px. */
.statedot.ok { background: var(--ok-dot); }
.statedot.warn { background: var(--warn-dot); }
.statedot.bad { background: var(--crit-dot); }

/* The enrolment symbol. Centred, on its own white card whatever the page theme
   is doing — an inverted QR is one most scanners refuse, and the SVG carries
   its own white ground for the same reason. */
.qrwrap { display: flex; justify-content: center; margin: 12px 0; }
.qrwrap svg { border-radius: var(--r-sm); box-shadow: 0 0 0 1px var(--border); }

/* The welcome steps. A number that becomes a tick, so somebody coming back can
   see what is already done rather than reading the whole list again. */
.wstep { display: flex; gap: 14px; align-items: flex-start; padding: 14px 0; border-bottom: 1px solid var(--border); }
.wstep:last-child { border-bottom: 0; }
.wnum {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--border);
}
.wstep.done .wnum { background: var(--ok-bg); color: var(--ok); border-color: var(--ok); }
.wtitle { font-weight: 700; margin-bottom: 3px; }
.wstep.done .wtitle { color: var(--muted); }

/* A list of things an account cannot do, on the platform page.
 *
 * Struck through and quiet rather than red: these are not failures or warnings,
 * they are the shape of a boundary somebody chose. A customer asking "can your
 * staff read our backups?" should be able to be shown this list, so it reads as
 * a statement rather than as an error. */
.cannot { list-style: none; margin: 14px 0; padding: 0; display: grid; gap: 8px; }
.cannot li {
  position: relative; padding: 9px 13px 9px 34px; font-size: 13.5px;
  background: var(--surface-2); border-radius: var(--r); color: var(--ink-2);
}
.cannot li b { color: var(--ink); font-family: var(--mono); font-size: 12.5px; }
/* A crossed circle, drawn rather than an icon font: it has to read at a glance
   and it must not depend on anything being fetched. */
.cannot li::before {
  content: ''; position: absolute; left: 11px; top: 50%; margin-top: -7px;
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--crit-dot);
  background: linear-gradient(45deg, transparent 44%, var(--crit-dot) 44%, var(--crit-dot) 56%, transparent 56%);
}

/* ---- the protection report ------------------------------------------------
   A document rather than a page: it is meant to be printed or saved as a PDF
   and read by somebody who was not here when it was generated. Everything that
   is console furniture — the nav, the filters, the buttons — is dropped at
   print, because a report with a "Print" button on it looks like a screenshot
   rather than a statement. */
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0 14px; }
.report { padding: 22px 24px; }
.rep-title h3 { margin: 0 0 4px; font-size: 20px; }
.rep-title { border-bottom: 1px solid var(--border); padding-bottom: 14px; margin-bottom: 16px; }
.rep-findings { display: grid; gap: 8px; margin-bottom: 18px; }
.rep-finding { border-left: 3px solid var(--border); padding: 8px 0 8px 12px; }
.rep-finding.bad { border-left-color: var(--crit-dot); }
.rep-finding.warn { border-left-color: var(--warn-dot); }
.rep-finding.ok { border-left-color: var(--ok-dot); }
.rep-figures { margin: 0 0 20px; }
.rep-figures .fig .v { font-size: 20px; font-weight: 600; }
.rep-figures .fig .l { font-size: 12px; color: var(--muted); }
.rep-table { width: 100%; }
.rep-table tr.rep-bad td { background: color-mix(in srgb, var(--crit-dot) 6%, transparent); }
.bad-text { color: var(--crit-dot); }
.rep-note { margin-top: 16px; max-width: 62ch; }

@media print {
  /* Only the report survives. Anything else on the page is the application,
     not the document. */
  /* The chrome is .top and .nav — there is no <header> or <aside> in this page,
     and naming them was a rule that hid nothing while looking correct. */
  .no-print, .top, .nav, .toasts, .scrim, #overlay { display: none !important; }
  .wrap { padding: 0 !important; max-width: none !important; }
  body, .report { background: #fff !important; color: #000 !important; }
  .report { border: 0; padding: 0; }
  .rep-table tr.rep-bad td { background: #f4f4f4 !important; }
  .muted, .rep-figures .fig .l { color: #444 !important; }
  a { color: #000 !important; text-decoration: none; }
  .rep-table { page-break-inside: auto; }
  .rep-table tr { page-break-inside: avoid; }
}

@media (max-width: 900px) { .grid4 { grid-template-columns: repeat(2, 1fr); } }

/* The desktop-notification toggle.
 *
 * Fixed to the corner and appended to <body> rather than placed in the header,
 * because render() rewrites #nav and #view every five seconds and a control
 * inside either would be rebuilt out from under a click. It is owned entirely
 * by notify.js, which is also why its styling lives at the end of this file
 * rather than among the header rules. */
.notifytoggle {
  position: fixed; right: 16px; bottom: 16px; z-index: 28;
  font: inherit; font-size: 12.5px; font-weight: 700;
  padding: 8px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--border-2); background: var(--surface); color: var(--muted);
  box-shadow: 0 2px 10px rgb(0 0 0 / 12%); cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.notifytoggle:hover { color: var(--ink); background: var(--surface-2); }
.notifytoggle.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
@media print { .notifytoggle { display: none !important; } }
