/* Safenix, both dashboards — light, dense, and sharing the site's palette.
 *
 * WHY THE PALETTE HAS NUMBERS BESIDE IT
 *
 * This was written for the operator console and then carried, unexamined, onto
 * the pages a customer meets. It was never looked at that way until 2026-08-21,
 * and three things were wrong with it — one of them on the two links a customer
 * has to follow before agreeing to anything.
 *
 * Every colour used for text now carries its contrast ratio against the
 * background it actually appears on. WCAG 2.2 AA is 4.5:1 for body text, 3:1
 * for large text and for the boundary of a control you have to be able to find.
 * The numbers are here so that changing a colour means recomputing one rather
 * than judging it by eye on a good monitor — which is how a link ended up at
 * 2.09:1 and nobody noticed for a month.
 *
 * The European Accessibility Act reaches consumer-facing digital services, and
 * a business customer's own obligations can reach their suppliers. That is a
 * reason to write the numbers down; it is not the reason to get it right.
 *
 * WHY THE PALETTE IS THE SITE'S AND THE VALUES ARE NOT
 *
 * 2026-08-29: safenix.eu has a visual identity and these dashboards had another,
 * so a customer crossing from the site to the sign-up form crossed two products.
 * The site's tokens are adopted here; most of its values are not, and that
 * distinction is the whole of this change.
 *
 * A marketing page uses colour large — a green button, a blue heading. This one
 * uses it at 11px, in a table cell, as the word PROTECTED. Five of the site's
 * twelve colours are below the threshold as text on the site's own background:
 * --green at 2.23:1, --blue at 2.37:1, --gold at 2.14:1, --muted at 2.66:1, and
 * --border at 1.18:1 where the edge of a form field needs 3:1. So each was
 * derived rather than copied — the site's hue kept, the lightness lowered only
 * as far as the threshold demands, and the ratio that came out written down
 * beside it. Text is derived to 5.2:1 and a boundary to 3.3:1 rather than to
 * 4.5 and 3.0, because a colour that clears by a hundredth fails on the next
 * adjustment and there is nothing to be bought by sitting that close.
 *
 * The polarity changed with it: this sheet was near-black and is now the site's
 * light ground. That is why eleven colours which had been written as literals in
 * the rules below are tokens here. Every one of them was chosen for a dark page,
 * and such a literal does not announce itself when the ground turns light — it
 * stops being readable, in a role badge or the border of a warning box, which
 * are exactly the places nobody looks at until the day they need them.
 *
 * Fonts and corner radii are deliberately not part of this pass.
 */

/* ---- type ----
 *
 * Inter for text and Plus Jakarta Sans for headings and figures, both of them
 * variable, so one file carries the whole weight axis instead of one file per
 * weight. Four files rather than sixteen.
 *
 * WHY THESE ARE SERVED FROM THIS BINARY
 *
 * A @font-face pointing at fonts.gstatic.com makes the reader's browser
 * disclose its IP address to Google before the page has drawn. A German court
 * has already ruled on that, and these are authenticated pages whose readers
 * are European businesses — the same people who are here because Safenix told
 * them nothing about them leaves the EU. Loading a font from a third country to
 * render that sentence would be a poor way to keep it.
 *
 * The dashboards contact no external host at all, which is a property rather
 * than an accident: TestTheDashboardsContactNoExternalHost fails on any absolute
 * URL in either app.
 *
 * WHY EACH FAMILY IS SPLIT IN TWO
 *
 * unicode-range lets the browser fetch only the file it needs for the characters
 * on the page. An Italian reader's accents are all in Latin-1 and cost the latin
 * file alone; the Polish and Czech letters are in latin-ext and are fetched only
 * by somebody whose page contains one. That is 75 KB for most readers rather
 * than 182 KB for everybody, and it is the same split the other two Safenix
 * sites use — one convention across three sites is worth more than three.
 *
 * font-display: swap, so text is readable in the fallback while the file is in
 * flight. The alternative hides the words for up to three seconds. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ---- the three grounds ----
   *
   * Every colour used as text is measured against all three, and --bg-hover is
   * the darkest, so it is what decides every number below. It is a real
   * background: a table row under the pointer.
   *
   * The first two are the site's --bg and --bg-card, unchanged. The third has no
   * counterpart there, a marketing page having nothing that hovers, so it is
   * derived: 1.116:1 against --bg, which is the step the dark palette had at
   * 1.139:1. Lighter and a hovered row stops being visible; darker and it spends
   * the headroom every text colour below is measured with. */
  --bg: #F4F8FC;
  --bg-raised: #FFFFFF;
  --bg-hover: #E4ECF6;

  /* Structure: table rules, panel edges. Decorative, so 3:1 does not apply —
     losing one of these tells a reader nothing they cannot see another way.
     This is the one token that departs from the site's own value for a reason
     that is not contrast: #DDE6F0 is 1.18:1 here where the rule it replaces was
     1.34:1, and on a table of twenty rows that is the rows ceasing to separate.
     Darkened one step on the same hue. 1.29 / 1.38:1 on --bg and --bg-raised. */
  --border: #D0DDEC;

  /* The boundary of something you operate: an input, a select, a ghost button.
     WCAG 1.4.11 wants 3:1 for these. The site's --muted is #8A9BB0, which is
     2.66:1 here — the same defect this token was added to fix, a form field with
     no visible edge. Derived from that hue instead: 3.70 / 3.94 / 3.31:1. The
     third number is why it is not lighter: a ghost button sits in a table cell,
     and a table row changes colour under the pointer. */
  --field-border: #6D829C;

  /* ---- text ----
   *
   * Both are the site's own and unchanged: its --dark and its --mid. What does
   * not survive is the site's third grey, --muted at 2.66:1. There it is
   * tertiary text on a page whose smallest type is 16px; here --muted is table
   * headings, field labels and the sentence beside a checkbox, all at 11-12px,
   * all of which have to pass. So the site's second grey becomes this palette's
   * quiet one and its third is not carried over — one gradation fewer than the
   * site has, which is the price of having small text at all. */
  --fg: #0D1B2A;          /* 16.30 / 17.39 / 14.60:1 */
  --muted: #4A6177;       /* 6.02 / 6.43 / 5.39:1 */

  /* ---- semantics ----
   *
   * Each keeps the site's hue and is darkened only as far as it must go, so the
   * colour stays recognisable as the site's while carrying text at 11px.
   *
   * --danger is the one colour here derived from nothing, because the site's
   * palette has no red. It is the colour that says a backup is not what it
   * should be, so it cannot be left out for want of a source. Chosen slightly
   * cool, the rest of this palette leaning blue. If the site ever declares an
   * error red, derive from that instead of keeping this. */
  --accent: #476B1C;      /* from the site's green #7AB930: healthy. 5.81 / 6.20 / 5.20:1 */
  --danger: #B3312A;      /* at risk, and errors. 5.81 / 6.20 / 5.20:1 */
  --warn: #735E23;        /* from the site's gold #C9A84C: needs attention. 5.87 / 6.26 / 5.26:1 */

  /* Links, and the focus ring, share a colour on purpose: both mean "this is
     something you can act on", and neither may borrow the green that means
     healthy or the red that means broken. From the site's blue #00AEEF.
     5.83 / 6.22 / 5.22:1.
     There were no link rules at all once, so links rendered in the browser
     default #0000EE — 2.09:1 on the palette this replaces, and 1.78:1 visited. */
  --link: #00688F;
  --focus: #00688F;

  /* ---- fills, and the boundaries of coloured things ----
   *
   * These are here because the same colours used to be literals in the rules
   * below. While the palette never changed polarity that cost nothing; every one
   * of them was picked for a near-black page, so the change of ground is what
   * makes them a hazard rather than an untidiness.
   *
   * A ratio beside a fill is the contrast of what is written on it. */
  --accent-dim: #4C741E;  /* the fill of a primary button, and the edge of a healthy
                             status. Not one step lighter: at #5D8D25 a white label
                             is 3.96:1. 5.49:1 under --on-accent */
  --accent-fill-hover: #3E6118;  /* 7.17:1 under --on-accent */
  --on-accent: #FFFFFF;   /* the label on --accent-dim */

  --danger-dim: #C7362F;  /* the edge of a destructive control. 4.92 / 5.25 / 4.41:1 */
  --danger-wash: #FBEAE9; /* that control's hover ground, and so a fourth background
                             the three-way check above does not reach: --danger on it
                             is 5.32:1 and --danger-dim 4.52:1 */
  --warn-dim: #997D2E;    /* the edge of a warning box. 3.69 / 3.94 / 3.31:1 */
  --link-dim: #0089BD;    /* the edge of a link-coloured badge. 3.70 / 3.95 / 3.32:1 */

  /* A QR code needs a white quiet zone whatever the page around it is doing, so
     this stays white on a light palette exactly as it was on a dark one. It is a
     token rather than a literal #fff so that the next change of ground finds it
     and can decide, rather than passing over it. */
  --qr-ground: #FFFFFF;

  /* ---- type ----
   *
   * Every stack keeps its system fallback behind the web font, so a reader whose
   * download has not arrived, or is blocked, gets the page in something sensible
   * rather than in a serif default. --display is the site's heading face and is
   * used for headings and for the large figures on the customer's cards; it is
   * deliberately not used for table numerals, which are set in --sans with
   * tabular-nums so that columns line up. */
  --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --display: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

  /* ---- corners ----
   *
   * The site's scale is 6 / 12 / 20 / 32px and this sheet had none at all.
   * --radius-sm is the things you operate, --radius-md the surfaces they sit on,
   * --radius-lg the one standalone box on a page of its own.
   *
   * The site's fourth step, 32px, is absent rather than forgotten: it belongs to
   * a hero panel on a landing page and there is no surface in either dashboard
   * large enough to carry it without looking like a mistake. A token nothing
   * uses is a token nothing measures, so it is not declared.
   *
   * Tables keep square corners. border-radius has no effect under
   * border-collapse: collapse, and switching to separate to gain rounded corners
   * changes how every rule inside every table is drawn — which is a change to
   * the rules, and this pass is a change to the surfaces. */
  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 20px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.45;
}

.hidden { display: none !important; }
.muted { color: var(--muted); }

/* A label that is read aloud and not drawn.
 *
 * Used where a field sits in a one-line form and a visible label would push the
 * layout around — the restore password beside its button, the invite address
 * beside its role. It is a real <label for>, so the field has a name for
 * anybody using a screen reader, and the placeholder stays as the visual hint.
 *
 * Not display:none and not visibility:hidden: both of those take the text out
 * of the accessibility tree, which is the opposite of the point. This is the
 * standard clip technique — one pixel, clipped, still rendered. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Labels that are drawn. Small and quiet, above their field.
   Not scoped to `form`: a field is not always inside one — the register panel
   asks what to protect beside a plain button, and the WordPress restore's
   snapshot chooser was already in that position and already rendering as an
   unstyled inline label. Both are what this rule describes, so both get it. */
label:not(.check):not(.sr-only) {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-top: 4px;
}

fieldset { border: none; padding: 0; margin: 0; }
fieldset legend {
  font-size: 12px;
  color: var(--muted);
  padding: 0;
  margin-bottom: 4px;
}

/* ---- links and focus ----
 *
 * Underlined always, not only on hover. A link told apart from the text around
 * it by colour alone fails WCAG 1.4.1 unless the colour difference against that
 * text reaches 3:1, and relying on that leaves anybody who cannot separate the
 * two hues with nothing. An underline is the cue that does not depend on
 * seeing a colour at all.
 */
a {
  color: var(--link);
  text-decoration: underline;
  text-underline-offset: 2px;
}

a:hover { text-decoration-thickness: 2px; }

/* A visited Terms of Service is still a Terms of Service. On the near-black
   palette this replaces, leaving it alone meant the browser's #551A8B at
   1.78:1, the lowest-contrast colour on the page. On this ground that same
   purple clears AA at 10.32:1, so the reason to pin it is no longer contrast:
   it is that a followed link changing hue makes the two documents on the
   sign-up form look like two different kinds of thing. */
a:visited { color: var(--link); }

/* One focus ring for everything, and it is deliberately loud.
 *
 * :focus-visible rather than :focus, so a mouse click does not leave a ring
 * behind while keyboard use always does. Two pixels with an offset rather than
 * one, because the previous rule replaced the browser's own focus ring on
 * inputs with a 1px dim green — which is a downgrade from the default, on the
 * only control that had any rule at all. Everything else relied on whatever the
 * browser drew over a near-black background. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.accent { color: var(--accent); }
.error { color: var(--danger); margin-top: 8px; }
.warn { color: var(--warn); }

code, .hash, td.mono {
  font-family: var(--mono);
  font-size: 12px;
}

/* Headings, and the large figures on the customer's summary cards, in the
   site's display face. Nothing else takes it: a display face at 26px on a card
   is what the site does with it, and the same face at 11px in a table cell is a
   different design wearing the same name. Table numerals stay in --sans with
   tabular-nums, because what matters about a column of figures is that it lines
   up. */
h1, h2, .card-value { font-family: var(--display); }

/* ---- login ---- */

#login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

.login-box {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px 32px;
  width: 380px;
}

.login-box h1 { font-size: 18px; letter-spacing: 1px; }
.login-box p.muted { margin: 4px 0 16px; }
.login-box p.step { margin: 10px 0; }

.login-box form { display: flex; flex-direction: column; gap: 8px; }
.login-box input { width: 100%; }

#enroll-qr {
  display: block;
  margin: 12px auto;
  background: var(--qr-ground);
  border-radius: var(--radius-md);
  padding: 8px;
  image-rendering: pixelated;
}

#enroll-secret { word-break: break-all; }

#recovery-codes {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--accent);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  margin: 8px 0;
  columns: 2;
  user-select: all;
}

/* ---- chrome ---- */

header {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-raised);
}

header h1 { font-size: 15px; letter-spacing: 1px; }
header nav { display: flex; gap: 4px; flex: 1; }
main { padding: 16px; max-width: 1400px; }

.tab {
  background: none;
  border: 1px solid transparent;
  color: var(--muted);
  padding: 5px 14px;
  cursor: pointer;
  font-size: 13px;
  border-radius: var(--radius-sm);
}

.tab:hover { color: var(--fg); }

/* The tab you are on. Colour is not the only thing saying so: the border is a
   visible one rather than the near-invisible structural grey, the weight
   changes, and showView sets aria-current on it — a screen reader user was
   otherwise given no way at all to tell which view they were in. */
.tab.active {
  color: var(--accent);
  border-color: var(--field-border);
  background: var(--bg);
  font-weight: 600;
}

/* ---- controls ---- */

input, button {
  font-family: var(--sans);
  font-size: 13px;
}

input {
  background: var(--bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-sm);
  color: var(--fg);
  padding: 6px 10px;
}

/* Placeholder text is a hint and not a label — every field that has one now
   has a real label too — but it still has to be readable while it is on
   screen. The browser default is far below AA on this background. */
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }

/* Focus is handled once, above, by :focus-visible. This rule used to replace
   the browser's ring with a 1px dim green outline and is deliberately gone. */
input:focus-visible { border-color: var(--focus); }

#login-token { font-family: var(--mono); }

/* What a new server should back up. It carries six paths by default and is
   meant to be edited in place, so it gets the panel's width rather than the
   browser's twenty-odd characters, and the same face as the command it ends up
   in — a list of paths is not prose. */
#backup-paths { width: 100%; max-width: 62ch; font-family: var(--mono); }

button {
  background: var(--accent-dim);
  border: 1px solid var(--accent-dim);
  border-radius: var(--radius-sm);
  color: var(--on-accent);
  padding: 6px 14px;
  cursor: pointer;
}

button:hover { background: var(--accent-fill-hover); }

button.ghost {
  background: none;
  border-color: var(--field-border);
  color: var(--muted);
}

button.ghost:hover { color: var(--fg); border-color: var(--muted); }

button.danger {
  background: none;
  border-color: var(--danger-dim);
  color: var(--danger);
  padding: 2px 10px;
  font-size: 12px;
}

button.danger:hover { background: var(--danger-wash); border-color: var(--danger); }

button:disabled { opacity: 0.4; cursor: default; }

/* A button that reads as a link. It opens the sign-up form in place rather than
   going anywhere, so it was written as <a href="#"> with preventDefault — which
   announces itself to a screen reader as a link to nowhere. It is a button that
   looks like a link, which is the way round that matches what it does. */
button.linkish {
  background: none;
  border: none;
  padding: 0;
  color: var(--link);
  font-size: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

button.linkish:hover { text-decoration-thickness: 2px; }

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 12px 0 8px;
}

.pager { display: flex; gap: 6px; }

/* ---- tables ---- */

table {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid var(--border);
}

th, td {
  text-align: left;
  padding: 5px 10px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

th {
  background: var(--bg-raised);
  color: var(--muted);
  font-weight: 500;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

tbody tr:hover { background: var(--bg-hover); }
/* A row about something that no longer exists — a disconnected server. Dimmed
   with --muted rather than with opacity: every text colour in these dashboards
   has its contrast computed from the palette tokens (accessibility_test.go),
   and an opacity would drop text below AA with nothing able to see it. */
tbody tr.retired td { color: var(--muted); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.empty { text-align: center; color: var(--muted); padding: 18px; }

.hash { color: var(--fg); cursor: help; }

/* ---- status ---- */

.status {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  padding: 1px 7px;
  border: 1px solid;
  border-radius: var(--radius-sm);
}

.status.ok { color: var(--accent); border-color: var(--accent-dim); }
.status.bad { color: var(--danger); border-color: var(--danger-dim); }
.status.off { color: var(--muted); border-color: var(--border); }
/* The two role badges. They were #ffd23f and #59c2ff, which on the dark palette
   were already within a hair of --warn and --link; on a light ground the amber
   family compresses, and two golds both clearing 4.5:1 are two browns nobody
   tells apart. So they take those tokens rather than inventing a distinction
   the palette cannot carry. If the roles ever need to be told apart at a
   glance, the answer is a tinted fill and not a fourth and fifth hue. */
.status.god { color: var(--warn); border-color: var(--warn-dim); }
.status.demigod { color: var(--link); border-color: var(--link-dim); }

.ok-msg { color: var(--accent); margin-top: 8px; }
td.actions { display: flex; gap: 6px; }
button.small { padding: 2px 8px; font-size: 12px; }

header .whoami { display: flex; align-items: center; gap: 8px; }

select {
  background: var(--bg);
  border: 1px solid var(--field-border);
  border-radius: var(--radius-sm);
  color: var(--fg);
  padding: 6px 10px;
  font-family: var(--sans);
  font-size: 13px;
}

/* ---- panels ---- */

.panel {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
  margin-bottom: 14px;
}

/* A row inside a panel: one item in a list where each item is a heading and a
   sentence rather than a table cell. The operator console's attention list and
   the customer's plan choices are both this shape — a line somebody reads
   rather than scans. */
.panel-row {
  border-top: 1px solid var(--border);
  padding: 10px 0;
}

.panel-row:first-child {
  border-top: none;
  padding-top: 0;
}

/* A row's severity. The coloured left edge was the only thing carrying it,
   which is a distinction that disappears for anybody who cannot separate red
   from amber — so the row now also states its severity in words, styled here.
   The bar stays: for everybody else it is the faster cue. */
.panel-row.bad {
  border-left: 3px solid var(--danger);
  padding-left: 10px;
}

.panel-row.warn {
  border-left: 3px solid var(--warn);
  padding-left: 10px;
}

.severity {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-right: 6px;
}

.panel-row.bad .severity { color: var(--danger); }
.panel-row.warn .severity { color: var(--warn); }
.panel-row.muted .severity { color: var(--muted); }

.panel-row p {
  margin: 4px 0 0;
}

/* A short label with a count on it: a version, a subscription state. Used by
   the operator console in several places and previously undefined, which is
   why it rendered as running text. */
.pill {
  display: inline-block;
  border: 1px solid var(--border);
  padding: 1px 7px;
  margin: 2px 4px 2px 0;
  font-size: 11px;
  font-family: var(--mono);
  border-radius: var(--radius-sm);
}

/* A checkbox and its sentence, side by side rather than stacked, so the
   sentence reads as the thing being agreed to. */
.check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
  text-align: left;
  margin: 8px 0;
}

.check input {
  width: auto;
  margin-top: 2px;
  flex: none;
}

.panel h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--muted);
  margin-bottom: 10px;
}

.panel .bar { margin: 0; justify-content: flex-start; }
form.inline { display: flex; gap: 8px; }
form.inline input { width: 320px; }

/* ---- token reveal ---- */

.token-reveal {
  margin-top: 12px;
  border: 1px solid var(--warn);
  border-radius: var(--radius-md);
  padding: 10px 12px;
}

.token-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 8px 0;
}

.token-row code {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  color: var(--accent);
  user-select: all;
}

/* ---- deletion certificate ----
   The document as it is handed over: fixed width, wrapped where it was
   wrapped, nothing reflowed. It is evidence, so what is on screen has to be
   what a customer's auditor reads. */

pre.certificate {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  margin: 8px 0;
  max-height: 26em;
  overflow: auto;
  white-space: pre;
  font-size: 12px;
  line-height: 1.45;
}

/* ---- vault verify ---- */

#vault-verify-result {
  margin-top: 12px;
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

#vault-verify-result.intact { border-color: var(--accent-dim); color: var(--accent); }
#vault-verify-result.broken { border-color: var(--danger); color: var(--danger); }
#vault-verify-result .detail { color: var(--fg); margin-top: 4px; }

/* ---- customer app ----
   The customer app reuses everything above and only adds what it needs:
   summary cards, prose blocks, and slightly roomier tables. Density is the
   one place the two apps deliberately differ — operators want data per
   pixel, customers want to be told what is going on. */

body.customer { font-size: 14px; }
body.customer main { max-width: 1100px; }
body.customer th, body.customer td { padding: 9px 12px; }
body.customer th { font-size: 11px; letter-spacing: 0.4px; }
body.customer header h1 { letter-spacing: 3px; }

.prose {
  max-width: 62ch;
  margin-bottom: 10px;
  color: var(--fg);
  line-height: 1.6;
}

/* Explains why a form is asking for less than usual — the authenticator
   rotation form drops its code field for a recovery-code session. Marked
   rather than muted: it is the reason the form looks different, so it has
   to read as deliberate instead of as missing text. */
.recovery-note {
  border-left: 2px solid var(--accent-dim);
  padding-left: 10px;
  color: var(--fg);
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}

.card-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--muted);
}

.card-value { font-size: 26px; line-height: 1.2; }
.card-value.good { color: var(--accent); }
.card-value.bad { color: var(--danger); }
.card-note { font-size: 12px; color: var(--muted); }

#install-command, #recovery-codes {
  white-space: pre-wrap;
  word-break: break-all;
}

#install-command {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--accent);
  padding: 10px 12px;
  margin: 8px 0;
  user-select: all;
}

#verify-result {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

#verify-result.intact { border-color: var(--accent-dim); color: var(--accent); }
#verify-result.broken { border-color: var(--danger); color: var(--danger); }
#verify-result .detail { color: var(--muted); margin-top: 6px; font-size: 12px; }

/* Scheduled backup verification. The limitation is not styled as small
   print: a customer who reads only the coloured line must still not come
   away believing a restore was tested. */
.check-list { list-style: none; margin: 8px 0 0; padding: 0; }
.check-list li { padding: 4px 0; border-top: 1px solid var(--border); font-size: 12px; }
.check-list li:first-child { border-top: none; }
.check-list .check-status {
  display: inline-block;
  min-width: 122px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.check-list .pass { color: var(--accent); }
.check-list .fail { color: var(--danger); }
.check-list .skip { color: var(--muted); }
.check-list .never { color: var(--warn); }

.verify-scope {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--warn-dim);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  color: var(--fg);
  font-size: 13px;
}

body.customer .whoami { gap: 10px; }

/* ---- account settings (shared by both dashboards) ---- */

#forced-change {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

#forced-change .login-box { width: 420px; }

form.stacked {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 360px;
}

ul.rules {
  margin: 0 0 12px 18px;
  color: var(--muted);
  font-size: 12px;
}

ul.rules li { margin-bottom: 2px; }

#view-settings .panel { max-width: 720px; }
#view-settings table { margin-top: 4px; }
#view-settings img { background: var(--qr-ground); border-radius: var(--radius-md); padding: 8px; margin: 8px 0; }
