/* ════════════════════════════════════════════════════════════════════════
   Tharsol Device Console — design tokens

   Two rules hold this system together:
     1. The accent (chrome) is separate from status colour. Because the accent
        is cobalt, green is free to carry exactly one meaning: healthy device.
        It never decorates a heading, a button or a chart.
     2. Every colour is a token declared on bare :root first. A colour whose
        only definition sits inside a media query renders one theme's text on
        the other theme's ground.
   ════════════════════════════════════════════════════════════════════════ */

:root {
  /* accent ramp — cobalt */
  --accent-strong: #1B3FCC;
  --accent-500: #2E5BFF;
  --accent-400: #5B82FF;
  --accent-soft: #E7EDFF;
  --accent-soft-dk: rgba(91, 130, 255, .16);

  /* the navigation rail stays dark in both themes: one bold surface, and a
     silhouette users recognise before they read anything */
  --rail-top: #0B1526;
  --rail-bot: #132A4D;
  --rail-ink: #C6D2E8;
  --rail-ink-dim: #7F8FAC;
  --rail-line: rgba(255, 255, 255, .07);
  --rail-hover: rgba(255, 255, 255, .05);

  /* LIGHT — the complete set */
  --bg: #F1F4FA;
  --surface: #FFFFFF;
  --surface-2: #F7F9FD;
  --surface-3: #E9EEF7;
  --surface-inset: #F4F7FC;
  --line: #DBE2EE;
  --line-strong: #C2CDDF;
  --ink: #0B1526;
  --ink-2: #48546C;
  --ink-3: #78849C;

  --brand: var(--accent-500);
  --brand-strong: var(--accent-strong);
  --brand-soft: var(--accent-soft);
  --brand-on: #FFFFFF;

  --good: #0F7F66;
  --good-soft: #DCF2EB;
  --good-line: #9FDCC9;
  --warn: #9A5F12;
  --warn-soft: #FAEEDA;
  --warn-line: #E8C48A;
  --crit: #B32C36;
  --crit-soft: #FBE5E7;
  --crit-line: #EFAFB5;
  --idle: #5C6880;
  --idle-soft: #E7EBF3;


  /* Ordered ramp — one hue at four values, for data that has an order: version
     spread, devices per policy, lifecycle states. Distinct from --c1..--c5,
     which are categorical, and deliberately not green: green is semantic here
     and has to keep meaning "healthy". Strongest first, so the largest bar
     leads without being the loudest thing on screen. */
  --o1: #C6D5F6;
  --o2: #97B0EC;
  --o3: #5B82FF;
  --o4: #2E5BFF;
  /* chart marks, validated against this surface for colour-vision deficiency */
  --c1: #2563D8;
  --c2: #0F9B7A;
  --c3: #8B4FD6;
  --c4: #B3701A;
  --c5: #CE2E6B;
  --grid: #E4EAF3;

  --shadow-sm: 0 1px 2px rgba(11, 21, 38, .06), 0 1px 3px rgba(11, 21, 38, .04);
  --shadow-md: 0 4px 12px rgba(11, 21, 38, .07), 0 1px 3px rgba(11, 21, 38, .05);
  --shadow-lg: 0 18px 44px rgba(11, 21, 38, .16);

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --rail-w: 248px;

  --font-display: "Archivo", "Segoe UI", system-ui, sans-serif;
  --font-ui: "IBM Plex Sans", "Segoe UI", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", "Cascadia Mono", Consolas, monospace;

  color-scheme: light;
}

/* DARK — system default (nothing stamped on :root) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #070D18;
    --surface: #0E1626;
    --surface-2: #131E33;
    --surface-3: #1A2740;
    --surface-inset: #0A1220;
    --line: #213150;
    --line-strong: #2E4166;
    --ink: #E7EDF8;
    --ink-2: #9BAAC4;
    --ink-3: #6D7D9A;

    --brand: var(--accent-400);
    --brand-strong: var(--accent-400);
    --brand-soft: var(--accent-soft-dk);
    --brand-on: #08101F;

    --good: #3ED5A8;
    --good-soft: rgba(62, 213, 168, .13);
    --good-line: rgba(62, 213, 168, .34);
    --warn: #E9A93F;
    --warn-soft: rgba(233, 169, 63, .13);
    --warn-line: rgba(233, 169, 63, .32);
    --crit: #F4707C;
    --crit-soft: rgba(244, 112, 124, .13);
    --crit-line: rgba(244, 112, 124, .32);
    --idle: #8494B0;
    --idle-soft: rgba(132, 148, 176, .14);

    /* Ordered ramp — see the light block for why this is not green. */
    --o1: #2E4477;
    --o2: #3A5CA8;
    --o3: #4A74D8;
    --o4: #5B82FF;

    --c1: #4C8DF0;
    --c2: #12AA85;
    --c3: #9F63E8;
    --c4: #C07E1E;
    --c5: #E24C7C;
    --grid: #1B2942;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow-md: 0 6px 18px rgba(0, 0, 0, .5);
    --shadow-lg: 0 22px 54px rgba(0, 0, 0, .62);

    color-scheme: dark;
  }
}

/* DARK — explicit toggle, so it also wins over a light OS */
:root[data-theme="dark"] {
  --bg: #070D18;
  --surface: #0E1626;
  --surface-2: #131E33;
  --surface-3: #1A2740;
  --surface-inset: #0A1220;
  --line: #213150;
  --line-strong: #2E4166;
  --ink: #E7EDF8;
  --ink-2: #9BAAC4;
  --ink-3: #6D7D9A;

  --brand: var(--accent-400);
  --brand-strong: var(--accent-400);
  --brand-soft: var(--accent-soft-dk);
  --brand-on: #08101F;

  --good: #3ED5A8;
  --good-soft: rgba(62, 213, 168, .13);
  --good-line: rgba(62, 213, 168, .34);
  --warn: #E9A93F;
  --warn-soft: rgba(233, 169, 63, .13);
  --warn-line: rgba(233, 169, 63, .32);
  --crit: #F4707C;
  --crit-soft: rgba(244, 112, 124, .13);
  --crit-line: rgba(244, 112, 124, .32);
  --idle: #8494B0;
  --idle-soft: rgba(132, 148, 176, .14);

  /* Ordered ramp — see the light block for why this is not green. */
  --o1: #2E4477;
  --o2: #3A5CA8;
  --o3: #4A74D8;
  --o4: #5B82FF;

  --c1: #4C8DF0;
  --c2: #12AA85;
  --c3: #9F63E8;
  --c4: #C07E1E;
  --c5: #E24C7C;
  --grid: #1B2942;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, .5);
  --shadow-lg: 0 22px 54px rgba(0, 0, 0, .62);

  color-scheme: dark;
}

/* ── base ──────────────────────────────────────────────────────────────── */
* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  text-wrap: balance;
  letter-spacing: -.015em;
}

p { margin: 0; }
a { color: var(--brand-strong); }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Something focused only by script gets no ring.
   FocusOnNavigate puts focus on each page's <h1> after navigation so a screen reader
   announces where it just arrived — a genuinely useful thing that the template does and
   this app keeps. But it also means a heading nobody can tab to sat there ringed in
   brand blue on every page load, looking selected and offering nothing.

   tabindex="-1" is exactly "reachable by script, not by keyboard", so a focus ring on one
   can only ever be noise: there is no keyboard journey it could be reporting. Anything a
   person can actually reach has tabindex 0 or none, and keeps its ring. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible {
  outline: none;
}

.mono { font-family: var(--font-mono); font-variant-ligatures: none; }
.tnum { font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-3); }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* ── shell ─────────────────────────────────────────────────────────────── */
.shell { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); min-height: 100vh; }
.shell.collapsed { --rail-w: 74px; }

.rail {
  background: linear-gradient(178deg, var(--rail-top) 0%, var(--rail-bot) 100%);
  color: var(--rail-ink);
  display: flex; flex-direction: column; gap: 4px;
  padding: 14px 12px;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
  border-right: 1px solid rgba(0, 0, 0, .35);
}

.brand { display: flex; align-items: center; gap: 11px; padding: 6px 6px 16px; min-height: 46px; }
.brand .glyph {
  width: 34px; height: 34px; flex: none; border-radius: 9px;
  display: grid; place-items: center;
  background: linear-gradient(140deg, var(--accent-400), var(--accent-strong));
  box-shadow: 0 3px 10px rgba(0, 0, 0, .4);
}
.brand .glyph svg { width: 19px; height: 19px; display: block; }
.brand .wordmark {
  font-family: var(--font-display); font-weight: 700; font-size: 15px;
  color: #fff; letter-spacing: -.02em; line-height: 1.15;
}
.brand .wordmark small {
  display: block; font-family: var(--font-ui); font-weight: 450; font-size: 10.5px;
  letter-spacing: .09em; text-transform: uppercase; color: var(--rail-ink-dim);
}

.collapsed .brand .wordmark,
.collapsed .navgroup > span,
.collapsed .navitem > span,
.collapsed .navitem .count,
.collapsed .railfoot { display: none; }
.collapsed .brand { justify-content: center; padding-left: 0; padding-right: 0; }
.collapsed .navitem { justify-content: center; padding-left: 0; padding-right: 0; }

.navgroup {
  padding: 16px 10px 6px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .11em; text-transform: uppercase; color: var(--rail-ink-dim);
}

.navitem {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 9px 10px; border-radius: 9px;
  color: var(--rail-ink); font-size: 13.5px; font-weight: 500;
  text-align: left; position: relative; text-decoration: none;
  transition: background .14s ease, color .14s ease;
}
.navitem svg { width: 17px; height: 17px; flex: none; opacity: .82; }
.navitem:hover { background: var(--rail-hover); color: #fff; }
.navitem.active { background: rgba(255, 255, 255, .1); color: #fff; }
.navitem.active::before {
  content: ""; position: absolute; left: -12px; top: 50%; transform: translateY(-50%);
  width: 3px; height: 20px; border-radius: 0 3px 3px 0; background: var(--accent-400);
}
.navitem.active svg { opacity: 1; color: var(--accent-400); }
.navitem .count { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--rail-ink-dim); }
.navitem .count.alert { color: #F4707C; }

.railfoot { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--rail-line); }
.enterprise-card {
  display: flex; gap: 10px; align-items: center; padding: 9px 10px;
  border-radius: 10px; background: rgba(255, 255, 255, .05);
}
.enterprise-card .dot {
  width: 7px; height: 7px; border-radius: 50%; background: #3ED5A8;
  box-shadow: 0 0 0 3px rgba(62, 213, 168, .18); flex: none;
}
.enterprise-card b { display: block; font-size: 12.5px; color: #fff; font-weight: 600; }
.enterprise-card span { display: block; font-family: var(--font-mono); font-size: 10.5px; color: var(--rail-ink-dim); }

/* ── topbar ────────────────────────────────────────────────────────────── */
.main { min-width: 0; display: flex; flex-direction: column; }

.topbar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 12px;
  padding: 0 22px; height: 58px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.iconbtn {
  width: 32px; height: 32px; border-radius: 8px;
  display: grid; place-items: center; color: var(--ink-2);
  border: 1px solid transparent;
  transition: background .14s, color .14s;
}
.iconbtn:hover { background: var(--surface-3); color: var(--ink); }
.iconbtn svg { width: 16px; height: 16px; }

/* Theme picker — three segments, because "match system" is a real choice and the
   only one that follows an OS that switches itself at sunset.

   The radios are the control; the labels are only its appearance. Hiding the
   inputs with .visually-hidden rather than display:none keeps them focusable and
   keeps arrow-key navigation, which is the whole reason for using radios instead
   of three buttons. The focus ring is drawn on the label, since the input it
   belongs to has no size. */
.themepick {
  display: flex; align-items: center; gap: 2px;
  height: 30px; padding: 2px;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--surface-inset);
}
.themepick input {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.themepick label {
  display: grid; place-items: center;
  width: 28px; height: 24px; border-radius: 7px;
  color: var(--ink-3); cursor: pointer;
  transition: background .14s, color .14s;
}
.themepick label:hover { color: var(--ink-2); background: var(--surface-3); }
.themepick label svg { width: 15px; height: 15px; }

.themepick input:checked + label {
  background: var(--surface); color: var(--brand);
  box-shadow: var(--shadow-sm);
}

.themepick input:focus-visible + label {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

.searchbox {
  flex: 1; max-width: 430px; display: flex; align-items: center; gap: 9px;
  height: 34px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--surface-inset); color: var(--ink-3);
}
.searchbox svg { width: 15px; height: 15px; flex: none; }
.searchbox input {
  flex: 1; border: none; background: none; outline: none;
  color: var(--ink); font: inherit; font-size: 13px; min-width: 0;
}
.searchbox input::placeholder { color: var(--ink-3); }

/* The browser's own search-clear affordance is inconsistent across engines and unstyleable,
   so it is suppressed in favour of one that matches the rest of the chrome. */
.searchbox input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

.searchclear {
  width: 20px; height: 20px; border-radius: 5px; flex: none;
  display: grid; place-items: center; color: var(--ink-3);
  transition: background .12s, color .12s;
}
.searchclear:hover { background: var(--surface-3); color: var(--ink); }
.searchclear svg { width: 12px; height: 12px; }

.topspacer { flex: 1; }

.syncpill {
  display: flex; align-items: center; gap: 7px; height: 28px; padding: 0 11px;
  border-radius: 99px; background: var(--good-soft);
  border: 1px solid var(--good-line); color: var(--good);
  font-size: 12px; font-weight: 600;
}
.syncpill .pulse {
  width: 6px; height: 6px; border-radius: 50%; background: currentColor;
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .4; transform: scale(.8); }
}

.avatar {
  width: 31px; height: 31px; border-radius: 9px;
  display: grid; place-items: center; flex: none;
  background: linear-gradient(140deg, var(--accent-400), var(--accent-strong));
  color: #fff; font-family: var(--font-display); font-weight: 700; font-size: 12px;
}

/* ── page frame ────────────────────────────────────────────────────────── */
.page { padding: 24px 22px 64px; max-width: 1560px; width: 100%; margin: 0 auto; }

.pagehead { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; margin-bottom: 20px; }
.pagehead .eyebrow {
  font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 6px; display: block;
}
.pagehead h1 { font-size: 26px; font-weight: 700; line-height: 1.15; }
.pagehead p { color: var(--ink-2); font-size: 13.5px; margin-top: 5px; max-width: 64ch; }
.headactions { margin-left: auto; display: flex; gap: 8px; align-items: center; }

/* ── buttons ───────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 14px;
  border-radius: 9px; font-size: 13px; font-weight: 600;
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink);
  transition: background .14s, border-color .14s; white-space: nowrap; text-decoration: none;
}
.btn svg { width: 15px; height: 15px; }
.btn:hover:not(:disabled) { background: var(--surface-3); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.primary { background: var(--brand); border-color: var(--brand); color: var(--brand-on); box-shadow: var(--shadow-sm); }
.btn.primary:hover:not(:disabled) { background: var(--brand-strong); border-color: var(--brand-strong); }
.btn.ghost { border-color: transparent; background: transparent; color: var(--ink-2); }
.btn.ghost:hover:not(:disabled) { background: var(--surface-3); color: var(--ink); }
.btn.danger { color: var(--crit); border-color: var(--crit-line); background: var(--crit-soft); }
.btn.danger:hover:not(:disabled) { background: var(--crit); color: #fff; border-color: var(--crit); }
.btn.sm { height: 29px; padding: 0 10px; font-size: 12.5px; border-radius: 8px; }

/* ── panels ────────────────────────────────────────────────────────────── */
.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
}
.panel-head { display: flex; align-items: center; gap: 12px; padding: 15px 18px; border-bottom: 1px solid var(--line); }
.panel-head h3 { font-size: 14.5px; font-weight: 600; letter-spacing: -.01em; }
.panel-head .sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.panel-head .right { margin-left: auto; display: flex; gap: 6px; align-items: center; }
.panel-body { padding: 18px; }

/* ── grid ──────────────────────────────────────────────────────────────── */
.grid { display: grid; gap: 16px; }
.g-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g-main { grid-template-columns: minmax(0, 1.72fr) minmax(0, 1fr); align-items: start; }
.g-thirds { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }

@media (max-width: 1180px) {
  .g-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .g-main, .g-thirds { grid-template-columns: minmax(0, 1fr); }
}

/* ── stat tiles ────────────────────────────────────────────────────────── */
.stat {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 16px 17px; display: flex; flex-direction: column; gap: 9px;
  position: relative; overflow: hidden;
}
.stat .label { font-size: 11.5px; font-weight: 600; letter-spacing: .075em; text-transform: uppercase; color: var(--ink-3); }
.stat .value {
  font-family: var(--font-display); font-size: 31px; font-weight: 700;
  line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.stat .foot { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-2); }
.stat.attn { border-color: var(--crit-line); }
.stat.attn::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--crit); }
.stat.attn .value { color: var(--crit); }

/* ── status pills — colour is never the only cue ───────────────────────── */
.pill {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px 2px 7px;
  border-radius: 99px; font-size: 11.5px; font-weight: 600;
  border: 1px solid transparent; white-space: nowrap;
}
.pill svg { width: 12px; height: 12px; flex: none; }
.pill.good { background: var(--good-soft); color: var(--good); border-color: var(--good-line); }
.pill.warn { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.pill.crit { background: var(--crit-soft); color: var(--crit); border-color: var(--crit-line); }
.pill.info { background: var(--brand-soft); color: var(--brand-strong); }
.pill.idle { background: var(--idle-soft); color: var(--idle); }

.tag {
  display: inline-flex; align-items: center; padding: 2px 7px; border-radius: 5px;
  background: var(--surface-3); color: var(--ink-2);
  font-family: var(--font-mono); font-size: 11px; font-weight: 500; white-space: nowrap;
}

/* ── table ─────────────────────────────────────────────────────────────── */
.tablewrap { overflow-x: auto; }

table.data { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 1140px; }
table.data th {
  text-align: left; font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); padding: 9px 12px; border-bottom: 1px solid var(--line);
  background: var(--surface-2); white-space: nowrap;
}
table.data td { padding: 11px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
table.data tbody tr { cursor: pointer; transition: background .12s; }
table.data tbody tr:hover { background: var(--surface-2); }
table.data tbody tr.selected { background: var(--brand-soft); }
table.data td.stripe { width: 4px; padding: 0; }
table.data td.stripe i { display: block; width: 3px; height: 34px; border-radius: 2px; background: var(--idle); }

tr[data-state="ACTIVE"] td.stripe i { background: var(--good); }
tr[data-state="PROVISIONING"] td.stripe i { background: var(--brand); }
tr[data-state="DISABLED"] td.stripe i { background: var(--warn); }

.devcell { display: flex; flex-direction: column; gap: 2px; min-width: 190px; }
.devcell b { font-weight: 600; font-size: 13px; }
.devcell span { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }

/* ── filters ───────────────────────────────────────────────────────────── */
.filterbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 13px 16px; border-bottom: 1px solid var(--line); }

.chipfilter {
  display: inline-flex; align-items: center; gap: 6px; height: 29px; padding: 0 11px;
  border-radius: 8px; border: 1px solid var(--line); background: var(--surface);
  font-size: 12.5px; font-weight: 500; color: var(--ink-2);
}
.chipfilter:hover { border-color: var(--line-strong); color: var(--ink); }
.chipfilter.active { background: var(--brand-soft); border-color: var(--brand); color: var(--brand-strong); font-weight: 600; }
.chipfilter .n { font-family: var(--font-mono); font-size: 11px; opacity: .75; }

.ctl {
  height: 31px; padding: 0 10px; border-radius: 8px;
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink);
  font: inherit; font-size: 12.5px; font-family: var(--font-mono);
}
select.ctl { padding-right: 26px; }
input.ctl { font-family: var(--font-ui); }

/* ── charts ────────────────────────────────────────────────────────────── */
.chartbox { position: relative; }
.chartbox svg { display: block; width: 100%; height: auto; overflow: visible; }

/* A rail, not a slab. At 34px a single-state fleet was 34 pixels of saturated
   ink encoding one fact the number beside it already states — it read as an
   alert because it was sized like one. The rows below carry the data. */
.distbar {
  display: flex; gap: 2px; height: 6px; border-radius: 99px;
  background: var(--surface-3); overflow: hidden;
}
.distbar i { display: block; border-radius: 99px; }

/* ── enrollment instructions ───────────────────────────────────────────── */
/* One block per route a device can take, because the routes genuinely differ and
   the page previously described only the fully managed one — which left the
   no-factory-reset path invisible even to somebody who had just chosen it. */
.enrolways { display: grid; gap: 10px; margin-top: 14px; }
.enrolway {
  padding: 13px 15px; border: 1px solid var(--line); border-radius: 11px;
  background: var(--surface-2);
}
.wayhead {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; font-weight: 600; color: var(--ink);
}
.enrolway ol { margin: 9px 0 0; padding-left: 20px; display: grid; gap: 5px; }
.enrolway li { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }
.enrolway li b { color: var(--ink); font-weight: 600; }
.wayfoot {
  margin: 9px 0 0; padding-top: 9px; border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--ink-3); line-height: 1.5;
}
.wayfoot b { color: var(--ink-2); font-weight: 600; }

.fieldnote { margin: 6px 0 0; font-size: 11.5px; line-height: 1.45; }
.fieldnote.good { color: var(--good); }
.fieldnote.warn { color: var(--warn); }

/* ── a policy's assigned apps ──────────────────────────────────────────── */
/* The header had no styles at all, so it sat flush against the panel edge while
   every row below it was inset by 18px. */
.applist-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 18px; border-bottom: 1px solid var(--line); background: var(--surface-2);
}
.applist-count {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
}

.approw {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 13px;
  align-items: center; padding: 11px 18px; border-bottom: 1px solid var(--line);
}
.approw:last-of-type { border-bottom: none; }
.approw:hover { background: var(--surface-2); }
.approw .nm { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
.approw .pkg {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.appctls { display: flex; gap: 8px; align-items: center; }
/* The selects were 240px each and dominated every row. Their content is a single
   enum value. */
.appctls .ctl { min-width: 0; width: auto; font-size: 12px; padding-right: 26px; }

/* Only rendered for the install types that change what the device owner can do, so
   it reads as a note about this row rather than boilerplate on all of them. */
.appnote {
  grid-column: 2 / -1; margin: 5px 0 0; font-size: 11.5px; color: var(--warn);
  display: flex; align-items: flex-start; gap: 6px;
}

/* ── the embedded managed Play window ──────────────────────────────────── */
/* Google's UI, in our frame. Tall on purpose: it is a store browser, and anything
   shorter turns it into a scrolling strip inside a scrolling page. */
.playframe { height: clamp(520px, calc(100vh - 300px), 900px); }
.playframe iframe {
  display: block; width: 100%; height: 100%; border: none;
  background: var(--surface);
}

/* What the iframe cannot tell you: approving an app is not installing it. */
.afterplay {
  display: grid; grid-template-columns: auto 1fr; gap: 11px; align-items: start;
  margin-top: 14px; padding: 13px 16px;
  border: 1px solid var(--line); border-radius: 11px; background: var(--surface-2);
  font-size: 12.5px; color: var(--ink-2); line-height: 1.6;
}
.afterplay svg { width: 16px; height: 16px; color: var(--brand); margin-top: 2px; }
.afterplay b { color: var(--ink); font-weight: 600; }

/* ── the app catalogue ─────────────────────────────────────────────────── */
/* Two lines per app: the name to recognise it by, the package to be sure. The
   package is the half that disambiguates, so it never truncates out of sight. */
.catalogue td { vertical-align: middle; }
.catalogue .nm { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
.catalogue .pkg {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); margin-top: 1px;
}
.catalogue tr:hover { background: var(--surface-2); }
.catalogue .btn.sm { margin-left: 4px; }
/* A disabled Delete keeps its place and its tooltip. Hiding it would leave you
   hunting for a control that was never there. */
.catalogue .btn.sm:disabled { opacity: .4; cursor: not-allowed; }

.ctl.mono { font-family: var(--font-mono); font-size: 12.5px; }

.checkrow {
  display: grid; grid-template-columns: auto 1fr; gap: 11px; align-items: start;
  padding: 12px 13px; margin-top: 4px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2);
  cursor: pointer;
}
.checkrow input { margin-top: 2px; }
.checkrow b { display: block; font-size: 13px; font-weight: 600; }
.checkrow small { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 2px; line-height: 1.5; }

/* The icon guidance, next to a live preview of whatever icon that package has —
   which is also the quickest way to see that it has none. */
.iconadvice {
  display: grid; grid-template-columns: 30px 1fr; gap: 12px; align-items: start;
  margin: 0; font-size: 12.5px; color: var(--ink-2); line-height: 1.6;
}
.iconadvice b { color: var(--ink); font-weight: 600; }
.iconadvice code {
  font-family: var(--font-mono); font-size: 11px; word-break: break-all;
  background: var(--surface-inset); border: 1px solid var(--line);
  border-radius: 4px; padding: 1px 4px;
}

/* Looking a package up on Play, from the catalogue form. */
.lookuprow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 7px; }
.lookuprow .btn.sm { display: inline-flex; align-items: center; gap: 7px; }
.lookuprow .btn.sm svg { width: 13px; height: 13px; color: var(--brand); }
.lookuprow .spinner {
  width: 12px; height: 12px;
  border: 2px solid var(--line-strong); border-top-color: var(--brand);
}

.playfound {
  display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 11px; align-items: center;
  margin-top: 9px; padding: 10px 12px;
  border: 1px solid var(--good-line); border-radius: 10px; background: var(--good-soft);
}
.playfound .playicon { width: 34px; height: 34px; border-radius: 9px; object-fit: cover; }
.playfound .nm { display: block; font-size: 13px; font-weight: 600; color: var(--ink); }
.playfound .meta { display: block; font-size: 11px; color: var(--ink-2); margin-top: 1px; }

/* ── app picker ────────────────────────────────────────────────────────── */
/* A search field over the catalogue rather than a select of 40-odd options.
   The package name is shown on every row because it is the only thing that
   tells two apps with the same display name apart. */
/* Three children — the description, the picker, the Add button — where .setrow's
   own template is "1fr auto". Without its own template the picker column gets
   crushed to whatever is left, the option rows wrap onto two lines, and the
   package name truncates to "com.google.and…", which is the one part that has to
   stay readable. */
/* The picker gets an explicit track. As an `auto` track sized by its content, the
   child's `width: min(520px, 100%)` resolved its percentage against a width that
   depended on the child — circular, so it collapsed to the text width and the
   placeholder trimmed mid-word. */
/* .setrow.addapp, not .addapp: the element carries both classes, both selectors are
   one class each, and .setrow is declared later in this file — so it won the cascade
   and kept its own "1fr auto" template. Three children in a two-column grid wrapped
   the Add button onto its own row and left the picker whatever was spare. */
.setrow.addapp { grid-template-columns: minmax(0, 1fr) 520px auto; align-items: flex-start; }
@media (max-width: 1100px) {
  .setrow.addapp { grid-template-columns: minmax(0, 1fr) 380px auto; }
}
@media (max-width: 820px) {
  .setrow.addapp { grid-template-columns: 1fr; }
}
/* Wide enough for the longest package in the catalogue —
   com.google.android.googlequicksearchbox — plus the icon and the count, because the
   package is the half that disambiguates and trimming it defeats the point. */
.pickercol { display: grid; gap: 10px; width: 100%; min-width: 0; }

.combo { position: relative; }
.combofield {
  display: flex; align-items: center; gap: 9px; height: 34px; padding: 0 10px 0 12px;
  border: 1px solid var(--line-strong); border-radius: 9px; background: var(--surface);
}
.combofield:focus-within { outline: 2px solid var(--brand); border-color: var(--brand); }
.combofield > svg { width: 15px; height: 15px; color: var(--ink-3); flex: none; }
.combofield input {
  flex: 1; min-width: 0; border: none; background: none;
  font: inherit; font-size: 13.5px; color: var(--ink); outline: none;
}
.combofield input::placeholder { color: var(--ink-3); }
.combofield input:disabled { cursor: not-allowed; }
.combocount { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); flex: none; }
.comboclear {
  flex: none; width: 22px; height: 22px; border: none; background: none; border-radius: 6px;
  display: grid; place-items: center; color: var(--ink-3); cursor: pointer;
}
.comboclear:hover { background: var(--surface-3); color: var(--ink); }
.comboclear svg { width: 12px; height: 12px; }

/* Catches a click anywhere else so the list closes. Sits under the popup and over
   everything else. */
.combobackdrop { position: fixed; inset: 0; z-index: 29; }

.popup {
  position: absolute; z-index: 30; top: calc(100% + 5px); right: 0;
  min-width: 100%; width: max-content; max-width: min(460px, 86vw);
  max-height: 320px; overflow-y: auto; overflow-x: hidden; padding: 5px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 11px; box-shadow: var(--shadow-lg);
}
.opt {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  width: 100%; padding: 7px 9px; border: none; border-radius: 8px;
  background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.opt:hover, .opt.active { background: var(--surface-3); }
/* One line each. Wrapping made a three-word app name three rows tall and pushed
   the package out of sight. */
.opt .nm {
  font-size: 13.5px; font-weight: 500; line-height: 1.35; display: block;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.opt .pkg {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); display: block;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.opt > .pill { flex: none; align-self: center; white-space: nowrap; }
.noresult { padding: 16px 12px; text-align: center; color: var(--ink-3); font-size: 12.5px; }

/* The "add it straight from Play" row. Separated from the catalogue results by a
   rule, because it does something different: it asks Google rather than filtering
   a list, and what comes back may not be in the local table at all. */
.opt.playlookup {
  border-top: 1px solid var(--line); border-radius: 0; margin-top: 4px; padding-top: 10px;
}
.opt.playlookup:first-child { border-top: none; margin-top: 0; }
.opt.playlookup .nm { color: var(--brand); font-weight: 600; }
.opt.playlookup:disabled { cursor: progress; }
.opt.playlookup .appicon { background: var(--brand-soft); border-color: var(--brand); }
/* The shared .spinner is white, which is right on the sign-in button and invisible
   here. Same geometry, ink that works on a soft background. */
.opt.playlookup .spinner {
  width: 13px; height: 13px;
  border: 2px solid var(--line-strong); border-top-color: var(--brand);
}

.playhint {
  display: flex; gap: 9px; align-items: flex-start;
  margin: 4px 0 0; padding: 10px 10px 8px;
  border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--ink-3); line-height: 1.5;
}
.playhint svg { width: 13px; height: 13px; flex: none; margin-top: 3px; color: var(--brand); }
.linkish {
  border: none; background: none; padding: 0; font: inherit;
  font-family: var(--font-mono); font-size: 11px; color: var(--brand);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}

.lookuperror {
  margin: 6px 4px 2px; padding: 8px 10px; border-radius: 8px;
  background: var(--crit-soft); border: 1px solid var(--crit-line);
  color: var(--crit); font-size: 12px; line-height: 1.4;
}

.playmeta {
  grid-column: 1 / -1; margin: 4px 0 0; display: flex; flex-wrap: wrap; gap: 6px;
  align-items: center; font-size: 11.5px; color: var(--ink-3);
}

/* The letter tile sits behind the icon. If the file was never shipped the img
   stays hidden and the tile shows through — no broken-image glyph. */
.appicon {
  position: relative; width: 30px; height: 30px; border-radius: 8px; flex: none;
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-3); border: 1px solid var(--line);
}
.appicon .letter { font-family: var(--font-display); font-weight: 700; font-size: 12px; color: var(--ink-3); }
/* Hidden until it loads, and never removed.
   This used to be onerror="this.remove()", which deletes an element Blazor is
   managing: Blazor keeps its own reference, so when the icon version changed it
   patched src on a node that was no longer in the document. The new icon
   downloaded perfectly into something nobody could see, and only a full page
   reload — which rebuilds the tree — ever showed it.

   visibility rather than display, so the image still occupies the tile and still
   loads while hidden; onload reveals it, onerror hides it again. Self-correcting
   whichever way the src changes. */
.appicon img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  visibility: hidden;
}

.pickpreview {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  padding: 11px 13px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface);
}
.pickpreview .nm { font-size: 13.5px; font-weight: 600; display: block; }
.pickpreview .pkg {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); display: block;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pickmeaning {
  grid-column: 1 / -1; margin: 2px 0 0; font-size: 12px; color: var(--ink-3);
}
.pickwarn {
  grid-column: 1 / -1; margin: 4px 0 0; display: flex; gap: 8px; align-items: flex-start;
  font-size: 12.5px; color: var(--crit); line-height: 1.45;
}
.pickwarn svg { width: 14px; height: 14px; flex: none; margin-top: 3px; }
.pickwarn b { font-family: var(--font-mono); font-size: .94em; font-weight: 600; }

/* The small-fleet listing that stands in for the distribution charts. */
.fleetrow {
  display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; gap: 13px; align-items: center;
  padding: 13px 16px; border-bottom: 1px solid var(--line); text-decoration: none; color: inherit;
}
.fleetrow:last-child { border-bottom: none; }
.fleetrow:hover { background: var(--surface-2); }
.fleetrow .ic {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  background: var(--surface-3); color: var(--ink-2);
}
.fleetrow .ic svg { width: 18px; height: 18px; }
.fleetrow .nm { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; }
.fleetrow .meta {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3);
  margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fleetstats { display: flex; gap: 20px; text-align: right; }
.fleetstats div { display: grid; gap: 1px; }
.fleetstats dt {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
}
.fleetstats dd {
  margin: 0; font-family: var(--font-mono); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 700px) { .fleetstats { display: none; } }

.distrow { display: grid; grid-template-columns: 11px 1fr auto auto; gap: 11px; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--line); }
.distrow:last-child { border-bottom: none; }
.distrow i { width: 9px; height: 9px; border-radius: 3px; }
.distrow .nm { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-2); }
.distrow .v { font-family: var(--font-mono); font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.distrow .p { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-3); width: 44px; text-align: right; }

.meter { height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; margin-top: 5px; }
.meter i { display: block; height: 100%; border-radius: 3px; background: var(--brand); }

/* ── feed ──────────────────────────────────────────────────────────────── */
.feeditem { display: grid; grid-template-columns: 26px 1fr; gap: 11px; padding: 13px 18px; border-bottom: 1px solid var(--line); }
.feeditem:last-child { border-bottom: none; }
.feeditem .ic { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; }
.feeditem .ic svg { width: 13px; height: 13px; }
.feeditem .ic.crit { background: var(--crit-soft); color: var(--crit); }
.feeditem .ic.warn { background: var(--warn-soft); color: var(--warn); }
.feeditem .ic.good { background: var(--good-soft); color: var(--good); }
.feeditem .tx { font-size: 13px; line-height: 1.45; }
.feeditem .meta { display: flex; gap: 8px; align-items: center; margin-top: 4px; font-size: 11.5px; color: var(--ink-3); flex-wrap: wrap; }

/* ── policy builder ────────────────────────────────────────────────────── */
.builder { display: grid; grid-template-columns: 236px minmax(0, 1fr); gap: 16px; align-items: start; }
@media (max-width: 900px) { .builder { grid-template-columns: minmax(0, 1fr); } }

.catrail { display: flex; flex-direction: column; padding: 8px; gap: 2px; }
.catbtn {
  display: grid; grid-template-columns: 28px 1fr auto; gap: 10px; align-items: center;
  padding: 9px 10px; border-radius: 9px; text-align: left; color: var(--ink-2);
  transition: background .13s; width: 100%;
}
.catbtn:hover { background: var(--surface-2); }
.catbtn.active { background: var(--brand-soft); color: var(--brand-strong); }
.catbtn .ci { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; background: var(--surface-3); color: var(--ink-2); }
.catbtn.active .ci { background: var(--brand); color: var(--brand-on); }
.catbtn .ci svg { width: 14px; height: 14px; }
.catbtn b { display: block; font-size: 13px; font-weight: 600; color: inherit; }
.catbtn small { display: block; font-size: 11px; color: var(--ink-3); }
.catbtn .n { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }

.setrow { display: grid; grid-template-columns: 1fr auto; gap: 20px; align-items: center; padding: 13px 18px; border-bottom: 1px solid var(--line); }
.setrow:last-child { border-bottom: none; }
.setrow .k { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.setrow .k b { font-size: 13.5px; font-weight: 600; }
.setrow .d { font-size: 12px; color: var(--ink-3); margin-top: 3px; max-width: 62ch; }

.switch {
  width: 40px; height: 23px; border-radius: 99px; background: var(--line-strong);
  position: relative; flex: none; transition: background .16s;
}
.switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 17px; height: 17px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
  transition: transform .16s;
}
.switch.on { background: var(--brand); }
.switch.on::after { transform: translateX(17px); }
.switch.lock.on { background: var(--crit); }

/* ── policy cards ──────────────────────────────────────────────────────── */
.polcard { display: flex; flex-direction: column; overflow: hidden; }
.polcard .top { padding: 16px 17px 13px; display: flex; flex-direction: column; gap: 9px; }
.polcard .nm { display: flex; align-items: center; gap: 9px; }
.polcard .nm b { font-family: var(--font-mono); font-size: 13.5px; font-weight: 600; }
.polcard .facts {
  display: flex; gap: 16px; padding: 12px 17px; background: var(--surface-2);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.polcard .fact { display: flex; flex-direction: column; gap: 1px; }
.polcard .fact span { font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; }
.polcard .fact b { font-family: var(--font-mono); font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
.polcard .acts { display: flex; gap: 7px; padding: 11px 17px; }

.lockmeter { display: flex; gap: 2px; align-items: center; }
.lockmeter i { width: 13px; height: 4px; border-radius: 2px; background: var(--surface-3); }
.lockmeter i.on { background: var(--warn); }
.lockmeter i.max { background: var(--crit); }

/* ── enrollment ────────────────────────────────────────────────────────── */
.qrpanel { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 22px; padding: 20px; }
@media (max-width: 760px) { .qrpanel { grid-template-columns: minmax(0, 1fr); } }

.qrframe {
  width: 220px; height: 220px; padding: 12px; border-radius: var(--r-md);
  background: #fff; border: 1px solid var(--line); box-shadow: var(--shadow-sm);
}
.qrframe svg { display: block; width: 100%; height: 100%; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 8px 18px; align-items: baseline; margin: 0; }
.kv dt { font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); white-space: nowrap; }
.kv dd { margin: 0; font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); word-break: break-all; }

.hint { display: flex; gap: 9px; padding: 11px 13px; border-radius: var(--r-md); background: var(--brand-soft); font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }
.hint svg { width: 15px; height: 15px; flex: none; color: var(--brand-strong); margin-top: 1px; }

/* ── slide-over ────────────────────────────────────────────────────────── */
.scrim { position: fixed; inset: 0; background: rgba(6, 12, 22, .5); z-index: 40; }

.slideover {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(452px, 94vw);
  background: var(--surface); border-left: 1px solid var(--line);
  box-shadow: var(--shadow-lg); z-index: 41;
  display: flex; flex-direction: column;
  animation: slidein .24s cubic-bezier(.4, 0, .2, 1);
}
@keyframes slidein { from { transform: translateX(100%); } to { transform: none; } }

.so-head { padding: 18px 20px; border-bottom: 1px solid var(--line); display: flex; gap: 13px; align-items: flex-start; }
.so-head .dev { width: 40px; height: 40px; border-radius: 11px; background: var(--surface-3); display: grid; place-items: center; flex: none; color: var(--ink-2); }
.so-head .dev svg { width: 20px; height: 20px; }
.so-body { overflow-y: auto; flex: 1; }
.so-sect { padding: 16px 20px; border-bottom: 1px solid var(--line); }
.so-sect h4 { font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 11px; }
.so-foot { padding: 14px 20px; border-top: 1px solid var(--line); display: flex; gap: 8px; flex-wrap: wrap; background: var(--surface-2); }

.nc-item { display: flex; gap: 9px; padding: 10px 0; border-bottom: 1px dashed var(--line); }
.nc-item:last-child { border-bottom: none; }
.nc-item .code { font-family: var(--font-mono); font-size: 11px; color: var(--crit); font-weight: 600; }

/* ── states ────────────────────────────────────────────────────────────── */
.loading, .empty, .errorbox { padding: 40px 20px; text-align: center; color: var(--ink-3); font-size: 13.5px; }
.errorbox { color: var(--crit); }

.spinner {
  width: 22px; height: 22px; margin: 0 auto 12px;
  border: 2.5px solid var(--surface-3); border-top-color: var(--brand);
  border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.banner {
  display: flex; align-items: center; gap: 9px; padding: 10px 14px;
  border-radius: var(--r-md); font-size: 13px; margin-bottom: 16px;
}
.banner svg { width: 15px; height: 15px; flex: none; }
.banner.crit { background: var(--crit-soft); color: var(--crit); border: 1px solid var(--crit-line); }
.banner.good { background: var(--good-soft); color: var(--good); border: 1px solid var(--good-line); }

/* ── login ───────────────────────────────────────────────── */
/* The ground is an enrollment matrix: a technical grid scattered with QR-like
   modules, because every device on the fleet joined by scanning exactly that.
   Drawn entirely in CSS — no image is fetched, so the screen is never half
   painted while something loads.

   It stays dark in both themes, like the nav rail. The card on top of it uses
   --surface and so follows the viewer's theme choice. */
.loginpage {
  min-height: 100vh; display: grid; place-items: center; padding: 24px;
  position: relative; overflow: hidden; isolation: isolate;
  background: #080F1C;
}

.loginbg { position: absolute; inset: 0; z-index: -2; }

.loginbg .grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(120, 150, 210, .11) 1px, transparent 1px),
    linear-gradient(90deg, rgba(120, 150, 210, .11) 1px, transparent 1px);
  background-size: 34px 34px;
}

/* Module positions are rendered server-side, so they are there before any
   script runs and identical on every load. */
.loginbg .modules { position: absolute; inset: 0; opacity: .5; }
.loginbg .modules span {
  position: absolute; border-radius: 3px;
  background: rgba(91, 130, 255, .15); border: 1px solid rgba(91, 130, 255, .24);
}

.loginbg .glowpool {
  position: absolute; inset: 0;
  background:
    radial-gradient(680px 460px at 50% 42%, rgba(46, 91, 255, .24), transparent 70%),
    radial-gradient(420px 300px at 88% 88%, rgba(27, 63, 204, .20), transparent 70%);
}

/* One moving thing on the page, so it reads as deliberate rather than busy. */
.loginbg .scan {
  position: absolute; left: 0; right: 0; height: 190px;
  background: linear-gradient(180deg, transparent, rgba(91, 130, 255, .07), transparent);
  animation: scan 9s linear infinite;
}
@keyframes scan { from { top: -190px; } to { top: 100%; } }

/* Film grain, as an inline SVG turbulence tile. Settles the grid's hard edges
   on cheap panels, where thin lines otherwise shimmer. */
.loginpage .grain {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  opacity: .30; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
}

.logincard {
  position: relative; width: min(400px, 100%); background: var(--surface);
  border-radius: 18px; box-shadow: 0 26px 60px rgba(0, 0, 0, .45); padding: 32px;
}
.logincard .brandline { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; }
.logincard h1 { font-size: 21px; font-weight: 700; margin-bottom: 6px; }
.logincard p.lede { font-size: 13px; color: var(--ink-2); margin-bottom: 22px; }

/* Indeterminate on purpose: the server cannot say how far through it is, so a
   percentage would be a lie. */
.cardbar {
  position: absolute; top: 0; left: 0; right: 0; height: 3px; overflow: hidden;
  border-radius: 18px 18px 0 0; opacity: 0; transition: opacity .18s;
}
.logincard[data-busy] .cardbar { opacity: 1; }
.cardbar i {
  position: absolute; inset: 0; display: block;
  background: linear-gradient(90deg, transparent, var(--accent-400), transparent);
  transform: translateX(-100%); animation: sweep 1.15s ease-in-out infinite;
}
@keyframes sweep { to { transform: translateX(100%); } }

.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field label {
  font-size: 11.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3);
}
.inputwrap { position: relative; display: flex; align-items: center; }
.field input {
  width: 100%; height: 40px; padding: 0 12px; border-radius: 9px;
  border: 1px solid var(--line-strong); background: var(--surface-inset);
  color: var(--ink); font: inherit; font-size: 14px;
}
.field input:focus { outline: 2px solid var(--brand); outline-offset: 0; border-color: var(--brand); }
.logincard[data-busy] .field input { opacity: .55; }

/* Both icons ship in the markup and CSS picks one, so no script builds SVG. */
.peek {
  position: absolute; right: 6px; width: 30px; height: 30px; border: none; background: none;
  border-radius: 7px; color: var(--ink-3); cursor: pointer; display: grid; place-items: center;
}
.peek:hover { color: var(--ink); }
.peek svg { width: 16px; height: 16px; }
.peek .eyeoff { display: none; }
.peek[data-showing] .eye { display: none; }
.peek[data-showing] .eyeoff { display: block; }

.capslock {
  display: flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--warn); margin-top: 2px;
}
/* Needed, and easy to miss: a class setting `display` outranks the UA stylesheet's
   [hidden] { display: none }, so without this the warning is on screen from the
   first paint, permanently claiming Caps Lock is on. */
.capslock[hidden] { display: none; }
.capslock svg { width: 13px; height: 13px; flex: none; }

.signin {
  position: relative; width: 100%; height: 42px; margin-top: 10px;
  border: none; border-radius: 10px; cursor: pointer;
  background: var(--brand); color: var(--brand-on);
  font: inherit; font-size: 14.5px; font-weight: 600;
  display: grid; place-items: center;
}
.signin:hover { background: var(--brand-strong); }
.signin:focus-visible { outline: 2px solid var(--accent-400); outline-offset: 2px; }
.signin[aria-busy="true"] { cursor: progress; background: var(--brand-strong); }
.signin .lbl { display: flex; align-items: center; gap: 9px; }
.spinner {
  width: 15px; height: 15px; border-radius: 50%; flex: none;
  border: 2px solid rgba(255, 255, 255, .32); border-top-color: #fff;
  animation: spin .62s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.statusline {
  margin-top: 12px; min-height: 16px; font-size: 12.5px; color: var(--ink-3);
  display: flex; align-items: center; gap: 7px; justify-content: center;
}
.statusline .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent-400); }

/* ── the enrolment guide ───────────────────────────────────── */

/* Four routes, one at a time. A row rather than a grid: the routes are alternatives, and reading
   them side by side is what makes somebody pick the wrong one. */
.guidetabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }

.guidetab {
  flex: 1 1 150px; min-width: 0; text-align: left; cursor: pointer;
  display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto;
  column-gap: 9px; row-gap: 1px; padding: 9px 11px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px;
  color: var(--ink-2); font: inherit;
}
.guidetab:hover { border-color: var(--line-strong); }
.guidetab.on { background: var(--brand-soft); border-color: var(--brand); color: var(--ink); }

.guidetab .n {
  grid-row: 1 / 3; align-self: center;
  width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center;
  background: var(--surface-3); color: var(--ink-3);
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
}
.guidetab.on .n { background: var(--brand); color: var(--brand-on); }
.guidetab .t { font-size: 13px; font-weight: 600; }
.guidetab .m { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }
.guidetab.on .m { color: var(--brand); }

.guidebody { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 22px; align-items: start; }
@media (max-width: 760px) { .guidebody { grid-template-columns: 1fr; } }

.guideart { display: grid; justify-items: center; gap: 8px; }
.guideart .artcap {
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); text-align: center;
  max-width: 150px; line-height: 1.5;
}

.guidetext { min-width: 0; display: grid; gap: 12px; justify-items: start; }
.guidefacts { display: flex; gap: 7px; flex-wrap: wrap; }
.guidelead { margin: 0; font-size: 13px; color: var(--ink); line-height: 1.6; max-width: 72ch; }

.guidesteps { margin: 0; padding-left: 20px; display: grid; gap: 7px; max-width: 76ch; }
.guidesteps li { font-size: 12.5px; color: var(--ink-2); line-height: 1.6; }
.guidesteps li b { font-family: var(--font-mono); font-size: .95em; font-weight: 600; color: var(--ink); }

.guidecaveat {
  margin: 0; font-size: 12px; color: var(--ink-2); line-height: 1.6; max-width: 76ch;
  padding-left: 11px; border-left: 2px solid var(--line-strong);
}

.guidefoot {
  margin: 18px 0 0; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--ink-2); line-height: 1.6; max-width: 86ch;
}
.guidefoot b { font-weight: 600; color: var(--ink); }

/* The handset schematics. Coloured here rather than by presentation attribute, so a browser that
   will not resolve var() inside SVG attributes still draws them. */
.phone { width: 150px; height: auto; display: block; }
.phone .ph-body { fill: var(--surface-2); stroke: var(--line-strong); stroke-width: 2; }
.phone .ph-screen { fill: var(--surface); stroke: var(--line); }
.phone .ph-dot { fill: var(--line-strong); }
.phone .ph-bar, .phone .ph-url { fill: var(--surface-3); }
.phone .ph-chrome { fill: var(--surface-3); }
.phone .ph-field { fill: var(--surface-inset); stroke: var(--line); }
.phone .ph-focus { fill: var(--surface-inset); stroke: var(--brand); stroke-width: 1.5; }
.phone .ph-caret, .phone .ph-accent, .phone .ph-btn, .phone .ph-mark { fill: var(--brand); }
.phone .ph-ripple { fill: none; stroke: var(--brand); stroke-width: 1.5; }
.phone .ph-ripple.far { opacity: .32; }
.phone .ph-ripple.near { opacity: .6; }
.phone .ph-tick { fill: none; stroke: var(--brand-on); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.phone .ph-rule { fill: none; stroke: var(--line); stroke-dasharray: 3 3; }
.phone .ph-arrow { fill: none; stroke: var(--brand); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

.phone text { font-family: var(--font-ui); }
.phone .ph-title { font-size: 12px; font-weight: 700; fill: var(--ink); }
.phone .ph-head { font-size: 10px; font-weight: 700; fill: var(--ink); }
.phone .ph-label { font-size: 6.5px; fill: var(--ink-3); }
.phone .ph-count { font-size: 9px; font-weight: 700; fill: var(--brand); }
.phone .ph-btntext { font-size: 7px; font-weight: 700; fill: var(--brand-on); }
.phone .ph-mono { font-family: var(--font-mono); font-size: 9px; font-weight: 600; fill: var(--ink); }
.phone .ph-mono.small { font-size: 7px; }

/* ── sign-in enrolment ─────────────────────────────────────── */

.paneltext { font-size: 12.5px; color: var(--ink-2); margin: 0 0 14px; max-width: 76ch; line-height: 1.6; }
.paneltext b { font-family: var(--font-mono); font-size: .94em; font-weight: 600; color: var(--ink); }

/* Two fields side by side. Deliberately not .setrow, which is a settings row with a rule under
   it and an auto second column — reusing it put a border through the middle of a form. */
.twofields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 720px) { .twofields { grid-template-columns: 1fr; } }

.formactions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }

.formerror { font-size: 12.5px; color: var(--crit); margin: 0 0 12px; }
.formerror.warn { color: var(--warn); }

/* The printable half: a QR beside what to do with it. */
.signinlive {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 18px; align-items: start;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
}
@media (max-width: 720px) { .signinlive { grid-template-columns: 1fr; } }

.signinqr {
  padding: 10px; border-radius: 11px; background: #fff; border: 1px solid var(--line);
  line-height: 0; flex: none;
}
.signinqr svg { display: block; width: 180px; height: 180px; }

.signinsteps { min-width: 0; display: grid; gap: 9px; justify-items: start; }
.signinsteps ol { margin: 0; padding-left: 18px; display: grid; gap: 5px; }
.signinsteps li { font-size: 12.5px; color: var(--ink-2); line-height: 1.55; }
.signinsteps li b { font-family: var(--font-mono); font-size: .94em; font-weight: 600; color: var(--ink); }

/* The page the handset lands on. Shown for the moment before the redirect to Google lands, so
   it has to read as progress rather than as a destination. */
.enrolling { min-height: 100vh; display: grid; place-items: center; padding: 24px; background: var(--bg); }
.enrolling-card {
  width: min(360px, 100%); padding: 30px 28px; text-align: center;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow-lg);
}
.enrolling-card h1 { font-family: var(--font-display); font-size: 17px; font-weight: 700; margin: 16px 0 6px; }
.enrolling-card p { font-size: 13px; color: var(--ink-2); margin: 0; line-height: 1.55; }
.enrolling-spin {
  display: inline-block; width: 28px; height: 28px; border-radius: 50%;
  border: 2.5px solid var(--surface-3); border-top-color: var(--brand);
  animation: spin .7s linear infinite;
}

/* ── reconnect dialog ──────────────────────────────────────────────────── */
/* Blazor's default one is built inside a shadow root, so app.css cannot reach it
   and it renders in the framework's colours — a white card whose text is all but
   invisible on this console. Supplying our own #components-reconnect-modal makes
   Blazor use that instead, in the light DOM, where these rules apply.

   Blazor toggles the state classes; everything below just decides what shows. */
#components-reconnect-modal {
  display: none;
  position: fixed; inset: 0; z-index: 2000;
  place-items: center; padding: 24px;
  background: rgba(4, 9, 18, .62);
  backdrop-filter: blur(2px);
}
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected { display: grid; }
#components-reconnect-modal.components-reconnect-hide { display: none; }

.reconnect-card {
  width: min(400px, 100%); padding: 26px 28px; text-align: center;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow-lg);
}
.reconnect-title { font-size: 15.5px; font-weight: 600; margin: 14px 0 6px; }
.reconnect-body { font-size: 13px; color: var(--ink-2); margin: 0; line-height: 1.55; }
.reconnect-body span { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }

.reconnect-spin {
  display: inline-block; width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--surface-3); border-top-color: var(--brand);
  animation: spin .7s linear infinite;
}

/* Which message belongs to which state. Retrying is the default, so the other two
   stay hidden until Blazor says otherwise. */
.reconnect-when-failed, .reconnect-when-rejected { display: none; }

.components-reconnect-failed .reconnect-when-retrying,
.components-reconnect-rejected .reconnect-when-retrying { display: none; }
.components-reconnect-failed .reconnect-when-failed { display: block; }
.components-reconnect-rejected .reconnect-when-rejected { display: block; }

/* Retrying is not a dead end, so nothing to press. The other two are. */
.reconnect-reload { display: none; margin-top: 18px; }
.components-reconnect-failed .reconnect-reload,
.components-reconnect-rejected .reconnect-reload { display: inline-flex; }

.components-reconnect-failed .reconnect-spin,
.components-reconnect-rejected .reconnect-spin {
  animation: none; border-top-color: var(--crit); border-color: var(--crit-soft);
}

/* Blazor's own error UI */
#blazor-error-ui {
  background: var(--warn-soft); color: var(--warn); bottom: 0; box-shadow: var(--shadow-md);
  display: none; left: 0; padding: 12px 16px 12px 20px; position: fixed; width: 100%; z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 12px; top: 8px; }
