/*
 * WhatHasChanged — application stylesheet.
 *
 * Derived from designs/src/mockup.css, with three deliberate departures from it:
 *
 *   1. No fixed 1440px width and no fake browser chrome. The mockups are
 *      screenshot targets; this is the real thing.
 *   2. Real interaction states (:hover, :focus-visible, [disabled], [aria-current])
 *      instead of the .on / .active classes the mockups painted by hand.
 *   3. Every rule that the mockups expressed as a style="" attribute is a class
 *      here. That is not tidiness: the CSP is `style-src 'self'` with no
 *      unsafe-inline (src/index.ts), which BLOCKS inline style attributes. A
 *      style="" copied over from a mockup would silently not apply.
 *
 * Colours are the mockup palette, gathered into the tokens in `:root` below, with
 * one departure: muted text is darker than the mockups' #6b7684, which measured 4.0:1
 * on the page background and failed WCAG AA at these sizes. The type is larger than
 * the mockups for the same reason, and is one of six sizes.
 *
 * Spacing is on a 4px grid: every padding, margin and gap is a multiple of 4, with 1px
 * and 2px allowed only for hairlines and the inside of tags. A 9px or 13px is a value
 * nobody chose, and a screen of them is what reads as "slightly off".
 */

/*
 * IBM Plex Sans for text, IBM Plex Mono for IDs and receipts — SIL Open Font License,
 * fonts/OFL.txt. Served from here rather than a font CDN because the CSP is
 * `default-src 'self'`, which a Google Fonts link would silently fail against.
 *
 * Latin only, which covers Latin-1 (Müller, José, Søren). Anything outside the range
 * falls through to the system fonts named after it in the stacks below, per glyph.
 */
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-latin-400-normal.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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-latin-600-normal.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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-latin-700-normal.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: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-mono-latin-400-normal.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;
}

/*
 * Design tokens. Every colour and type size in this file is one of these; a value typed
 * directly into a rule is a bug, because it is the one that will not follow the next change.
 *
 * Colour pairs are named for their role, not their hue: `--err-bg` / `--err-border` /
 * `--err-text` are one banner's three parts and are only meant to be used together.
 * Text colours all pass WCAG AA on both `--surface` and `--page`; `--fg-disabled` is
 * exempt, as disabled controls are.
 */
:root {
  /* brand */
  --navy: #1f3a5f;
  --navy-hover: #2a4a76;
  --on-navy: #c5d3e5;
  --on-navy-border: rgba(255, 255, 255, 0.55);
  --brand: #2d5385;
  --brand-hover: #35608f;
  --brand-soft: #4a76b0;
  --on-brand: #fff;
  --admin: #7b3f9d;

  /* text, strongest to weakest */
  --fg: #1f2933;
  --fg-2: #3d4855;
  --fg-3: #525c68;
  --fg-4: #6b7684;
  --fg-disabled: #a8b0ba;

  /* surfaces and lines */
  --page: #eef0f3;
  --surface: #fff;
  --surface-2: #f7f9fb;
  --surface-3: #eef2f6;
  --line: #e6eaef;
  --border: #dbe0e6;
  --border-strong: #c8ced6;
  --border-hover: #98a2ad;

  /* status */
  --ok-bg: #eaf6ef;
  --ok-border: #b4dcc4;
  --ok-text: #14532d;
  --ok-strong: #1f7a4d;
  --warn-bg: #fff8e6;
  --warn-border: #f0d9a8;
  --warn-text: #6b4e00;
  --warn-strong: #946c00;
  --err-bg: #fdecea;
  --err-border: #f0b8b0;
  --err-text: #8a1f13;
  --err-strong: #c0392b;
  --info-bg: #eaf1f9;
  --info-border: #b8cde5;
  --info-hover: #d6e4f4;
  --info-text: #1f3a5f;

  /*
   * Type: six sizes. `--fs-base` is body text; `--fs-sm` is secondary text — hints,
   * table detail, labels; `--fs-xs` is the floor, for tags and counters only.
   */
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 14px;
  --fs-lg: 16px;
  --fs-xl: 20px;
  --fs-2xl: 24px;

  --font-sans: 'IBM Plex Sans', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
  --font-mono: 'IBM Plex Mono', Consolas, 'SFMono-Regular', monospace;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-sans);
  background: var(--page);
  color: var(--fg);
  font-size: var(--fs-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  color: var(--brand);
}

/* A visible focus ring everywhere. The register is used by keyboard for long
   stretches — filter, tab, sort, page — and an invisible focus position is the
   fastest way to make that unusable. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.hidden {
  display: none !important;
}

/* Off screen until focused, then the first thing on the page. */
.skip {
  position: absolute;
  left: 8px;
  top: -48px;
  z-index: 10;
  background: var(--surface);
  color: var(--fg);
  padding: 8px 12px;
  border-radius: 5px;
  font-weight: 600;
}

.skip:focus {
  top: 8px;
}

/* Focused by the skip link; the ring would outline the whole page. */
main:focus {
  outline: none;
}

/* ---------- app header ---------- */
header.app {
  background: var(--navy);
  color: var(--on-brand);
  padding: 0 24px;
  display: flex;
  align-items: center;
  min-height: 56px;
  gap: 24px;
  flex-wrap: wrap;
}

header.app .brand {
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: 0.2px;
}

header.app .brand span {
  opacity: 0.8;
  font-weight: 400;
  font-size: var(--fs-sm);
  margin-left: 8px;
}

header.app nav {
  display: flex;
  gap: 4px;
  flex: 1;
}

header.app nav a {
  color: var(--on-navy);
  text-decoration: none;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: var(--fs-base);
}

header.app nav a:hover {
  background: var(--navy-hover);
  color: var(--on-brand);
}

header.app nav a[aria-current='page'] {
  background: var(--brand);
  color: var(--on-brand);
  font-weight: 600;
}

.who {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: var(--fs-sm);
}

.who .avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--on-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: var(--fs-xs);
}

/*
 * Sign out, on the navy header. The generic `.linkbtn` is link-blue on white; here it
 * was link-blue on navy, which is barely there. A ghost button in the header's own
 * colours: visible, and quieter than the navigation beside it.
 */
header.app .who .linkbtn {
  color: var(--on-brand);
  font-size: var(--fs-sm);
  text-decoration: none;
  border: 1px solid var(--on-navy-border);
  border-radius: 4px;
  padding: 4px 12px;
}

header.app .who .linkbtn:hover {
  background: var(--navy-hover);
  border-color: var(--on-brand);
}

header.app .who .linkbtn:focus-visible {
  outline-color: var(--on-brand);
}

/* The role pill is decoration. Every authorization decision is made server-side
   from the token (SEC-3); this only tells the user what to expect. */
.pill-role {
  background: var(--brand);
  padding: 4px 8px;
  border-radius: 10px;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-weight: 600;
}

.pill-role.admin {
  background: var(--admin);
}

/* ---------- layout ---------- */
main {
  padding: 20px 24px 40px;
  max-width: 1600px;
  margin: 0 auto;
}

main.centred {
  max-width: 520px;
  margin: 80px auto;
  text-align: center;
}

main.centred h1 {
  font-size: var(--fs-xl);
  margin: 0 0 16px;
}

main.centred p {
  color: var(--fg-2);
  margin: 0 0 12px;
}

.crumb {
  font-size: var(--fs-xs);
  color: var(--fg-3);
  margin-bottom: 8px;
}

h1.view {
  font-size: var(--fs-2xl);
  font-weight: 600;
  margin: 0 0 4px;
}

.sub {
  color: var(--fg-3);
  font-size: var(--fs-sm);
  margin-bottom: 16px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.card + .card {
  margin-top: 16px;
}

.card > h2 {
  font-size: var(--fs-base);
  font-weight: 600;
  padding: 12px 16px;
  margin: 0;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.card .body {
  padding: 16px;
}

/* ---------- collapsible cards ---------- */
/*
 * The back-office sections are `<details class="card">` (app.js). A `<summary>` therefore
 * has to read as the same head as the `.card > h2` above — the sections are the same
 * objects as every other card, they just open and shut — so this repeats that rule rather
 * than combining the two selectors: the h2 version must not become clickable-looking.
 *
 * The browser's own disclosure triangle is removed in both idioms browsers use for it,
 * `list-style` and the WebKit pseudo-element, and replaced by `.chevron`, which can sit on
 * the right of the row next to the hint where the eye is already going.
 */
details.card > summary {
  font-size: var(--fs-base);
  font-weight: 600;
  padding: 12px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  list-style: none;
}

details.card > summary::-webkit-details-marker {
  display: none;
}

/* The rule divides a head from a body, so it is only drawn when there is a body under it.
   A closed card with a line across its bottom edge reads as a rendering fault. */
details.card[open] > summary {
  border-bottom: 1px solid var(--line);
}

details.card > summary:hover {
  background: var(--surface-2);
}

/* Inside the border rather than the global +2px: the ring belongs to the head, and offset
   outwards it is cut by the card's own rounded corner. */
details.card > summary:focus-visible {
  outline-offset: -2px;
}

/* The heading inside the summary carries no layout of its own — the summary is the flex
   row. It stays an h2 so the document outline is unchanged by collapsing. */
.sectiontitle {
  font-size: var(--fs-base);
  font-weight: 600;
  margin: 0;
}

/*
 * What a shut section still tells you: the delivery counts, how many approvers there are,
 * whether Coralogix is configured. This is what makes collapsing safe, since a collapsed
 * section cannot be found with the browser's own search.
 *
 * `margin-left: auto` rather than `space-between` on the summary, so the chevron stays
 * beside the hint instead of the three items spreading across the full width.
 */
.cardhint {
  margin-left: auto;
  color: var(--fg-3);
  font-size: var(--fs-sm);
  font-weight: 400;
}

/* inline-block because `transform` does not apply to a non-replaced inline element. */
.chevron {
  display: inline-block;
  color: var(--border-hover);
  font-size: 10px;
  transition: transform 0.12s ease;
}

.chevron::before {
  content: '\25b6';
}

details[open] > summary .chevron {
  transform: rotate(90deg);
}

/* ---------- search + filters ---------- */
.searchrow {
  display: flex;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.searchbox {
  flex: 1;
  min-width: 260px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 2px solid var(--brand);
  border-radius: 5px;
  padding: 8px 12px;
}

.searchbox .ico {
  color: var(--fg-3);
  font-size: var(--fs-base);
}

.searchbox input {
  flex: 1;
  border: 0;
  font-size: var(--fs-base);
  outline: none;
  color: var(--fg);
  font-family: inherit;
  background: transparent;
}

.btn {
  border: 1px solid var(--border-strong);
  background: var(--surface);
  border-radius: 5px;
  padding: 8px 16px;
  font-size: var(--fs-base);
  cursor: pointer;
  color: var(--fg);
  white-space: nowrap;
  font-family: inherit;
}

.btn:hover:not([disabled]) {
  background: var(--surface-2);
}

.btn[disabled] {
  color: var(--fg-disabled);
  cursor: default;
}

.btn.primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--on-brand);
  font-weight: 600;
}

.btn.primary:hover:not([disabled]) {
  background: var(--brand-hover);
}

/* After `.primary`, which would otherwise keep a disabled button looking pressable. */
.btn.primary[disabled] {
  background: var(--surface-2);
  border-color: var(--fg-disabled);
  color: var(--fg-disabled);
}

.btn.sm {
  padding: 4px 12px;
  font-size: var(--fs-sm);
}

/*
 * Outlined rather than filled red. `.primary` is filled because it is the expected
 * action on its screen; suppression is the exception on a sealed record, and a solid
 * red block reads as the thing to click. It also keeps this visually distinct from a
 * delete button, which is what it is deliberately not (FR-9, ST-10).
 */
.btn.danger {
  background: var(--surface);
  border-color: var(--err-strong);
  color: var(--err-strong);
}

.btn.danger:hover:not([disabled]) {
  background: var(--err-bg);
}

.filters {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px 16px;
  margin-bottom: 16px;
}

.filters .line {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.filters .line + .line {
  margin-top: 12px;
  border-top: 1px solid var(--line);
  padding-top: 12px;
}

.facet {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs-sm);
  color: var(--fg-3);
}

select,
input[type='date'],
input[type='text'] {
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 8px 12px;
  font-size: var(--fs-sm);
  font-family: inherit;
  color: var(--fg);
  background: var(--surface);
}

select:hover {
  border-color: var(--border-hover);
}

.chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.chipcount {
  font-size: var(--fs-xs);
  color: var(--fg-3);
}

/* Chips are how the restored state is made visible WITHOUT a banner. SR-14.7
   requires restoration to be silent; the chips and the exact result count are
   what tell the user which filters are in force. */
.chip {
  background: var(--info-bg);
  border: 1px solid var(--info-border);
  color: var(--navy);
  border-radius: 12px;
  padding: 4px 4px 4px 12px;
  font-size: var(--fs-xs);
  display: flex;
  align-items: center;
  gap: 8px;
}

.chip button {
  border: 0;
  background: transparent;
  color: var(--brand-soft);
  font-weight: 700;
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: 1;
  padding: 2px 4px;
  border-radius: 8px;
  font-family: inherit;
}

.chip button:hover {
  background: var(--info-hover);
  color: var(--navy);
}

.linkbtn {
  border: 0;
  background: transparent;
  font-size: var(--fs-xs);
  color: var(--brand);
  text-decoration: underline;
  cursor: pointer;
  font-family: inherit;
  padding: 0;
}

.spacer {
  flex: 1;
}

/* ---------- tables ---------- */
table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  /* Figures of one width, so dates and counts line up down a column. */
  font-variant-numeric: tabular-nums;
}

thead th {
  text-align: left;
  padding: 0;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--fg-2);
  font-weight: 600;
}

thead th .colhead {
  padding: 12px;
}

/* A sortable header is a button, not a div with a click handler: it has to be
   reachable by keyboard and announce itself as pressable. */
thead th button.colhead {
  border: 0;
  background: transparent;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  width: 100%;
  text-align: left;
  display: flex;
  align-items: center;
  gap: 4px;
}

thead th button.colhead:hover {
  color: var(--navy);
  background: var(--surface-3);
}

thead th[aria-sort] button.colhead {
  color: var(--navy);
}

thead th .sortmark {
  color: var(--brand);
  font-size: 10px;
}

tbody td {
  padding: 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

tbody tr.group:hover td {
  background: var(--surface-2);
}

tbody tr.child td {
  background: var(--surface-2);
  font-size: var(--fs-sm);
  color: var(--fg-2);
}

tbody tr.child td:first-child {
  padding-left: 32px;
}

/* Suppressed rows stay visible, struck through, with the reason inline —
   ST-3/ST-4. They are never filtered out: suppression is a label, not a
   soft delete. */
tbody tr.suppressed td {
  color: var(--fg-4);
}

tbody tr.suppressed .gname {
  text-decoration: line-through;
}

.gname {
  font-weight: 600;
  color: var(--navy);
}

.rowlink {
  text-decoration: none;
  border: 0;
  background: transparent;
  font: inherit;
  font-weight: 600;
  color: var(--navy);
  cursor: pointer;
  padding: 0;
  text-align: left;
}

.rowlink:hover {
  text-decoration: underline;
}

.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

.ref {
  color: var(--fg-3);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
}

.reason-supp {
  color: var(--err-strong);
}

.expander {
  border: 0;
  background: transparent;
  color: var(--fg-3);
  font-size: var(--fs-xs);
  cursor: pointer;
  padding: 2px 4px;
  border-radius: 3px;
  font-family: inherit;
}

.expander:hover {
  background: var(--line);
  color: var(--fg);
}

.tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 3px;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.2px;
}

/* Production tinting comes from the `is_production` flag carried on the facet
   payload, NOT from a hard-coded list of environment names (ENV-9) — so adding
   an environment later needs no CSS change. */
.tag.env {
  background: var(--info-bg);
  color: var(--info-text);
  border: 1px solid var(--info-border);
}

/* Production says so in a word beside the environment, not only in a tint. */
.envpair {
  display: inline-flex;
  gap: 4px;
  flex-wrap: wrap;
}

.tag.prod {
  background: var(--err-bg);
  color: var(--err-text);
  border: 1px solid var(--err-border);
}

/* A status as a dot and a word: the colour helps, the word carries the meaning. */
.status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  color: var(--fg-2);
}

.status::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--fg-4);
  flex: none;
}

.status.ok::before {
  background: var(--ok-strong);
}

.status.warn::before {
  background: var(--warn-strong);
}

/* A wide table scrolls inside its card rather than widening the page. */
.tablescroll {
  overflow-x: auto;
}

.tag.dept {
  background: var(--surface-3);
  color: var(--fg-2);
  border: 1px solid var(--border);
}

.tag.sealed {
  background: var(--ok-bg);
  color: var(--ok-text);
  border: 1px solid var(--ok-border);
}

.flagself {
  color: var(--warn-strong);
  font-size: var(--fs-xs);
}

/*
 * OWN-7 — "recorded on behalf of someone else".
 *
 * A DIFFERENT colour from .flagself, deliberately. The two marks sit next to each other
 * on a group row and mean different things with different remedies: one says a change
 * went in without a second pair of eyes, the other says the person who typed it is not
 * the person named as having done it. Rendered identically they would blur into a
 * generic "unusual", which is the state of knowing that something is wrong without
 * knowing what.
 *
 * Slate blue rather than a second warning colour, because this is NOT a warning.
 * Recording a vendor's or a colleague's work is legitimate and expected; the mark exists
 * so it is countable, and red would tell every reader that a permitted act is a fault.
 *
 * Applied to a <td> as well as to a <span>, so `color` and not `background` — a td that
 * changed colour would read as an error cell.
 */
.flagentered {
  color: var(--brand-soft);
  font-size: var(--fs-xs);
}

/*
 * OWN-6 — the marker on an attribution recovered from the audit log rather than stored.
 *
 * Muted further than .ref, which is already the quiet class. This is a footnote on a
 * value, not a value: it must be readable by anyone who wonders where the name came
 * from, and invisible to everyone who does not.
 */
.derived {
  color: var(--fg-4);
  font-style: italic;
}

/* ---------- pagination ---------- */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--fg-2);
  gap: 12px;
  flex-wrap: wrap;
}

.pager .nums {
  display: flex;
  gap: 4px;
  align-items: center;
}

.pgbtn {
  min-width: 30px;
  height: 30px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: var(--surface);
  font-size: var(--fs-sm);
  padding: 0 8px;
  cursor: pointer;
  font-family: inherit;
  color: var(--fg);
}

.pgbtn:hover:not([disabled]):not([aria-current='true']) {
  background: var(--surface-3);
}

.pgbtn[aria-current='true'] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--on-brand);
  font-weight: 600;
}

.pgbtn[disabled] {
  color: var(--fg-disabled);
  cursor: default;
}

.pgdots {
  color: var(--border-hover);
  padding: 0 4px;
}

/* ---------- detail view ---------- */
/* The heading and the Admin actions on one line, as the mockups drew them. */
.detailhead {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: 16px;
}

.detailhead .grow {
  flex: 1;
}

.actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

/*
 * The status form reuses `.facet` and `.line` from the filter panel, which are scoped
 * to `.filters` there. These two rules give them the same layout inside a card.
 */
.card .body .facet {
  margin-bottom: 12px;
}

.card .body .line {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 16px;
}

.card .body input.grow {
  flex: 1;
  min-width: 280px;
}

.kv {
  display: grid;
  grid-template-columns: 170px 1fr;
  gap: 8px 16px;
  font-size: var(--fs-sm);
  margin: 0;
}

.kv dt {
  color: var(--fg-3);
}

.kv dd {
  color: var(--fg);
  margin: 0;
}

.evidence {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  word-break: break-all;
}

/* ---------- service tokens ---------- */
/*
 * The one card in this application that shows a credential — API-2. Deliberately loud:
 * it appears once, and an operator who mistakes it for an ordinary confirmation loses
 * the only copy.
 */
.card.minted {
  border: 2px solid var(--warn-strong);
}

/*
 * The token itself. Larger than .evidence and selectable in one gesture, because the
 * fallback when the clipboard API is unavailable is the user selecting it by hand — and
 * `user-select: all` turns that into a single click instead of a drag across 51
 * characters where a missed one produces a token that fails authentication for no
 * visible reason.
 */
.tokenvalue {
  font-size: var(--fs-base);
  padding: 12px;
  margin: 0 0 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  user-select: all;
}

/* ---------- banners ---------- */
/*
 * Used for FAILURES and for the delivery outcome of a submission, never for
 * "your preferences were restored" — SR-14.7 makes restoration silent, and this
 * was decided rather than defaulted (see designs/README.md).
 */
.banner {
  border-radius: 5px;
  padding: 12px 16px;
  font-size: var(--fs-sm);
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin-bottom: 16px;
}

.banner.warn {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  color: var(--warn-text);
}

.banner.err {
  background: var(--err-bg);
  border: 1px solid var(--err-border);
  color: var(--err-text);
}

.banner.info {
  background: var(--info-bg);
  border: 1px solid var(--info-border);
  color: var(--navy);
}

.banner b {
  font-weight: 600;
}

.empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--fg-3);
  font-size: var(--fs-sm);
}

.note {
  font-size: var(--fs-xs);
  color: var(--fg-3);
  font-style: italic;
  padding: 8px 16px;
  border-top: 1px dashed var(--border);
  background: var(--surface-2);
}

.busy {
  color: var(--fg-3);
  font-size: var(--fs-sm);
  padding: 32px 0;
  text-align: center;
}

/* ---------- entry forms ---------- */
/*
 * The draft editor and the back office. Everything below exists because the register
 * screens are read-mostly and these are not: a filter row holds six short controls, a
 * change holds nine fields of which four are sentences.
 */

/*
 * A stacked label over its control, not the inline `.facet` the filters use.
 *
 * `.facet` puts a 12px grey label to the left of its control, which reads as "narrow
 * the list by this". The same layout around a 400px description field reads as a
 * filter the user is typing into, which is the opposite of what is happening.
 */
.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
}

.field .flabel {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--fg-2);
}

/*
 * Field hints, and the one-line notes beside a section head.
 *
 * Present in quantity on purpose. Almost every field on the entry form has a rule
 * behind it that the field cannot state by itself — the date cannot be in the future,
 * the approver comes from a list, the resource type is the layer changed and not the
 * layer affected. A user who learns those by having a submission rejected learns them
 * expensively.
 */
.hint {
  font-size: var(--fs-xs);
  color: var(--fg-3);
}

/* Short fields that belong together on one line, wrapping rather than shrinking. */
.fieldrow {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

.fieldrow > .field {
  flex: 1;
  min-width: 180px;
}

/*
 * Textareas share the input palette, and `resize: vertical` rather than `both`:
 * horizontal resize inside a flex row drags the neighbouring field around.
 */
textarea {
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 8px 12px;
  font-size: var(--fs-sm);
  font-family: inherit;
  color: var(--fg);
  background: var(--surface);
  resize: vertical;
  width: 100%;
}

.field input[type='text'],
.field input[type='datetime-local'],
.field select {
  width: 100%;
}

input[type='datetime-local'] {
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 8px 12px;
  font-size: var(--fs-sm);
  font-family: inherit;
  color: var(--fg);
  background: var(--surface);
}

/*
 * One change in a draft, and one setting in the back office.
 *
 * Bordered and separated rather than run together, because the repeated group is the
 * unit a user reasons about: "change 2 is missing its reason" has to correspond to
 * something with an edge on screen. A group of thirty changes in one undivided column
 * is unreadable and, worse, unreferenceable.
 */
.changeblock {
  border: 1px solid var(--line);
  border-radius: 5px;
  margin-bottom: 16px;
}

.changeblock:last-of-type {
  margin-bottom: 0;
}

.blockhead {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.blockhead h3 {
  font-size: var(--fs-sm);
  font-weight: 600;
  margin: 0;
}

.blockbody {
  padding: 12px;
}

/* A section head inside a card body, below the card's own h2. */
.subhead {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--fg-2);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin: 20px 0 12px;
}

.card .body > .subhead:first-child {
  margin-top: 0;
}

/*
 * The autosave acknowledgement.
 *
 * It occupies its space whether or not it is showing a word, so the heading it sits in
 * does not jump every time a field saves. Transitioned rather than toggled for the same
 * reason the register has no toast: it should be noticeable without demanding attention.
 */
.savedmark {
  font-size: var(--fs-xs);
  color: var(--ok-strong);
  opacity: 0;
  transition: opacity 0.25s;
  min-width: 42px;
}

.savedmark.on {
  opacity: 1;
}

/* Success, in the same family as the other three banners. */
.banner.ok {
  background: var(--ok-bg);
  border: 1px solid var(--ok-border);
  color: var(--ok-text);
}

/*
 * A deactivated reference-list row — an Approval List member who has left.
 *
 * Greyed but fully legible, and NOT struck through like a suppressed register row. A
 * former approver is not a mistake and nothing about them has been withdrawn: records they
 * approved remain valid, and their name still has to be read on those records. The row is
 * quiet because it is no longer selectable, not because it is discredited.
 */
tbody tr.inactive td {
  color: var(--fg-4);
  background: var(--surface-2);
}

/* A banner whose text should take the width left over beside its actions. */
.banner .grow {
  flex: 1;
}

.banner .actions {
  align-items: flex-start;
}

/* ---------- the Logs screen's tab strip ---------- */
/*
 * Tabs, not collapsible sections. The panels are ALTERNATIVES — an operator reads one log
 * at a time and wants the screen's full height for it — whereas the back office's sections
 * are a checklist somebody scans several of at once.
 *
 * The strip and the panel are drawn as one object: the strip has the panel's border and
 * background, the selected tab has no bottom border, and the panel's top corners are
 * square. So the selected tab looks like the front of the panel rather than a button that
 * happens to be highlighted — which matters because only one panel is visible, and the
 * strip is the only thing on screen saying which.
 */
.tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--border);
  padding: 0 4px;
  margin-bottom: -1px;
}

.tab {
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 5px 5px 0 0;
  background: none;
  font-family: inherit;
  font-size: var(--fs-sm);
  color: var(--fg-2);
  padding: 8px 16px;
  cursor: pointer;
  position: relative;
}

.tab:hover:not(.on) {
  background: var(--surface-3);
  color: var(--fg);
}

.tab.on {
  background: var(--surface);
  border-color: var(--border);
  color: var(--fg);
  font-weight: 600;
  /* Covers the strip's own bottom border, which is what joins the tab to its panel. */
  padding-bottom: 9px;
  margin-bottom: -1px;
}

.tabpanel {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.radios {
  display: inline-flex;
  gap: 20px;
}

.radios label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.radios input {
  accent-color: var(--brand);
  margin: 0;
}

/* Sibling panels are hidden, not absent, so `.card + .card` would push all but the first down. */
.card.tabpanel {
  margin-top: 0;
}

/*
 * A panel nested inside a card body, under a `.tabs.sub` strip. It has no border of its own:
 * the card is the box, and the underlined tab is enough to say which panel this is.
 */
.tabpanel.inner {
  padding: 16px 0 0;
}

/*
 * Tabs nested inside a card (External providers, Configuration): an underline rather than
 * a folder tab, so the panel needs no border of its own and there is no box inside a box.
 */
.tabs.sub {
  padding: 0;
  gap: 4px;
}

.tabs.sub .tab {
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: 8px 12px;
  margin-bottom: -1px;
}

.tabs.sub .tab:hover:not(.on) {
  background: none;
  border-bottom-color: var(--border-strong);
}

.tabs.sub .tab.on {
  background: none;
  border-bottom-color: var(--brand);
  padding-bottom: 8px;
}

/* The state of a tab in a few words, e.g. "2 not recorded". */
.tab .tabhint {
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--fg-3);
  margin-left: 6px;
}

.tab .tabhint:empty {
  display: none;
}

/*
 * The BackOffice section menu: a column on the left, the selected section beside it. Below
 * 900px the menu becomes a row above the section.
 */
.sidetabs {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.tabs.side {
  flex-direction: column;
  gap: 0;
  padding: 4px 0;
  margin: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
}

.tabs.side .tab {
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 0;
  text-align: left;
  padding: 8px 12px;
  margin: 0;
  font-size: var(--fs-base);
}

.tabs.side .tab.on {
  background: var(--surface-3);
  border-left-color: var(--brand);
  padding-bottom: 8px;
  margin: 0;
}

.tabs.side .tab .tabhint {
  display: block;
  margin: 2px 0 0;
}

.sidetabs .tabpanel {
  border-radius: 6px;
}

@media (max-width: 900px) {
  .sidetabs {
    grid-template-columns: minmax(0, 1fr);
  }

  .tabs.side {
    flex-direction: row;
    overflow-x: auto;
  }

  .tabs.side .tab {
    border-left: 0;
    border-bottom: 3px solid transparent;
  }

  .tabs.side .tab.on {
    border-bottom-color: var(--brand);
  }
}

/*
 * The panel is focusable (tabIndex 0 in app.js) so that a keyboard user leaving the strip
 * lands on the log itself rather than on its first link. It therefore needs a visible ring,
 * but only from the keyboard: a mouse click inside a table of audit rows should not outline
 * the whole panel.
 */
.tabpanel:focus-visible,
.tab:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* ---------- an editable row in a key/value list ---------- */
/*
 * The external-provider panel puts a field inside a `.kv` row (app.js, `providerField`), so
 * the input has to fit the `1fr` value column rather than its own intrinsic width.
 *
 * `min-width: 0` is the part that is not obvious: a flex item will not shrink below the
 * input element's default width without it, so on a narrow card the field would push out of
 * its grid cell and the Save button off the row.
 */
.kv dd .line input[type='text'] {
  flex: 1;
  min-width: 0;
}

/* ---------- mistake-proofing ---------- */

/*
 * Optional fields are the ones marked, not the required ones: most fields on a change are
 * required, and a label that marks the exception is quieter than one asterisk per field.
 */
.flabel .optional {
  font-weight: 400;
  color: var(--fg-3);
}

/* A field the pre-submit check found wanting. Cleared when the field next changes. */
.invalid,
.field .invalid {
  border-color: var(--err-strong);
  box-shadow: 0 0 0 1px var(--err-strong);
}

.banner ul.problems {
  margin: 8px 0 0;
  padding-left: 20px;
}

.banner ul.problems li {
  margin: 4px 0;
}

.banner ul.problems .linkbtn {
  font-size: var(--fs-sm);
  color: inherit;
  text-align: left;
}

/* Typing the production environment's name before it can be recorded. */
.typeconfirm {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  margin-top: 12px;
}

.typeconfirm input[type='text'] {
  width: 180px;
}

/* ---------- tasks ---------- */

/* The heading rows that divide the task list into kinds of task. */
tr.taskgroup th {
  padding: 8px 12px;
  background: var(--surface-3);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--fg-3);
  font-weight: 600;
  text-align: left;
}

.taskname {
  font-weight: 600;
}

/* ---------- sanity test ---------- */

/* A run's result as a pill: the colour helps, the word carries the meaning. */
.verdict {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--fg-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}

.verdict.passed {
  background: var(--ok-bg);
  border-color: var(--ok-border);
  color: var(--ok-text);
}

.verdict.warnings,
.verdict.running {
  background: var(--warn-bg);
  border-color: var(--warn-border);
  color: var(--warn-text);
}

.verdict.failed {
  background: var(--err-bg);
  border-color: var(--err-border);
  color: var(--err-text);
}

.lastrun {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}

.sanity-counts {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}

.count-chip {
  padding: 0 8px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  background: var(--surface-3);
  color: var(--fg-3);
}

.count-chip.st-pass {
  background: var(--ok-bg);
  color: var(--ok-text);
}

.count-chip.st-warn {
  background: var(--warn-bg);
  color: var(--warn-text);
}

.count-chip.st-fail {
  background: var(--err-bg);
  color: var(--err-text);
}

.sanity-progress {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0;
  font-size: var(--fs-sm);
  color: var(--fg-3);
}

.sanity-progress progress {
  flex: 1;
  height: 8px;
  appearance: none;
  border: 0;
  border-radius: 4px;
  overflow: hidden;
  background: var(--surface-3);
}

.sanity-progress progress::-webkit-progress-bar {
  background: var(--surface-3);
}

.sanity-progress progress::-webkit-progress-value {
  background: var(--brand);
  transition: width 0.3s ease;
}

.sanity-progress progress::-moz-progress-bar {
  background: var(--brand);
}

.sanity-section {
  margin-top: 20px;
}

.sanity-section h4 {
  margin: 0 0 8px;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--fg-3);
  font-weight: 600;
}

.sanity-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}

.sanity-step {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  gap: 12px;
  align-items: start;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  border-left: 4px solid transparent;
  background: var(--surface);
  transition: background 0.2s ease;
}

.sanity-step:first-child {
  border-top: 0;
}

.sanity-step.st-pass {
  border-left-color: var(--ok-strong);
}

.sanity-step.st-warn {
  border-left-color: var(--warn-strong);
  background: var(--warn-bg);
}

.sanity-step.st-fail {
  border-left-color: var(--err-strong);
  background: var(--err-bg);
}

.sanity-step.st-skip,
.sanity-step.st-notrun {
  border-left-color: var(--fg-disabled);
}

.sanity-step.st-running {
  border-left-color: var(--brand);
  background: var(--info-bg);
}

.sanity-step .title {
  font-weight: 600;
  line-height: 24px;
}

.sanity-step.st-pending .title,
.sanity-step.st-notrun .title {
  color: var(--fg-3);
}

.sanity-step .summary {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--fg-2);
}

.sanity-step .time {
  line-height: 24px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--fg-4);
  white-space: nowrap;
}

.sanity-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 2px 12px;
  margin: 8px 0 0;
  font-size: var(--fs-xs);
}

.sanity-facts dt {
  color: var(--fg-3);
}

.sanity-facts dd {
  margin: 0;
  font-family: var(--font-mono);
  color: var(--fg-2);
  overflow-wrap: anywhere;
}

.sanity-icon {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1;
  color: var(--on-brand);
  border: 2px solid var(--border-strong);
}

.sanity-icon.st-pass {
  background: var(--ok-strong);
  border-color: var(--ok-strong);
}

.sanity-icon.st-warn {
  background: var(--warn-strong);
  border-color: var(--warn-strong);
}

.sanity-icon.st-fail {
  background: var(--err-strong);
  border-color: var(--err-strong);
}

.sanity-icon.st-skip {
  background: var(--fg-disabled);
  border-color: var(--fg-disabled);
}

.sanity-icon.st-notrun {
  border-style: dashed;
}

.sanity-icon.st-running {
  border: 3px solid var(--info-border);
  border-top-color: var(--brand);
  animation: sanity-spin 0.8s linear infinite;
}

@keyframes sanity-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sanity-icon.st-running {
    animation: none;
  }
}

/* The summary at the top of a finished run. */
.sanity-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 24px;
  margin: 16px 0 4px;
  padding: 20px 24px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: linear-gradient(135deg, var(--surface-2), var(--surface));
}

.sanity-hero.passed {
  border-color: var(--ok-border);
  background: linear-gradient(135deg, var(--ok-bg), var(--surface) 70%);
}

.sanity-hero.warnings {
  border-color: var(--warn-border);
  background: linear-gradient(135deg, var(--warn-bg), var(--surface) 70%);
}

.sanity-hero.failed {
  border-color: var(--err-border);
  background: linear-gradient(135deg, var(--err-bg), var(--surface) 70%);
}

.sanity-hero .words {
  flex: 1;
  min-width: 280px;
}

.sanity-hero h3 {
  margin: 0;
  font-size: var(--fs-2xl);
  line-height: 1.2;
  color: var(--fg);
}

.sanity-hero.passed h3 {
  color: var(--ok-text);
}

.sanity-hero.warnings h3 {
  color: var(--warn-text);
}

.sanity-hero.failed h3 {
  color: var(--err-text);
}

.sanity-hero .meta {
  margin: 4px 0 0;
  font-size: var(--fs-sm);
  color: var(--fg-3);
}

.sanity-ring {
  width: 136px;
  height: 136px;
  flex: none;
}

.sanity-ring circle {
  fill: none;
  stroke-width: 14;
}

.sanity-ring .track {
  stroke: var(--surface-3);
}

.sanity-ring .seg.st-pass {
  stroke: var(--ok-strong);
}

.sanity-ring .seg.st-warn {
  stroke: var(--warn-strong);
}

.sanity-ring .seg.st-fail {
  stroke: var(--err-strong);
}

.sanity-ring .seg.st-skip {
  stroke: var(--fg-disabled);
}

.sanity-ring .big {
  font-size: var(--fs-2xl);
  font-weight: 700;
  fill: var(--fg);
}

.sanity-ring .small {
  font-size: var(--fs-xs);
  fill: var(--fg-3);
}

.sanity-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(96px, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.sanity-tile {
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--line);
  border-top: 4px solid var(--fg-disabled);
  background: var(--surface);
}

.sanity-tile .n {
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  color: var(--fg-3);
}

.sanity-tile .label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--fg-3);
}

.sanity-tile.st-pass {
  border-top-color: var(--ok-strong);
}

.sanity-tile.st-pass .n {
  color: var(--ok-strong);
}

.sanity-tile.st-warn {
  border-top-color: var(--warn-strong);
}

.sanity-tile.st-warn .n {
  color: var(--warn-strong);
}

.sanity-tile.st-fail {
  border-top-color: var(--err-strong);
}

.sanity-tile.st-fail .n {
  color: var(--err-strong);
}
