
/* ---------------------------------------------------------------- tokens ---
   Light is the base definition; dark redefines only what changes. Both the media
   query and the explicit [data-theme] selector are present, so the in-console theme
   switch wins in either direction while a viewer who has chosen nothing still
   follows their operating system. */
:root {
  /* THE GROUND IS TINTED, AND THAT IS THE WHOLE MOVE.

     The console used to sit on a near-white that was a hair cooler than its cards. It
     read as careful and it read as flat: with almost no separation between page and
     panel, nothing floated, and the eye had nowhere to rest.

     Here the page is a definite pale blue and the cards are pure white. The panels lift
     off it without needing a heavy border or a strong shadow, which is what makes a dense
     compliance screen feel airy rather than packed. Everything else below follows from
     that one decision. */
  /* PAPER, NOT A BLUE SAAS GROUND.

     The reference is monochrome and the discipline is the point: with no colour spent on
     decoration, the only coloured thing on a screen is the thing that means something.
     Light mode is white paper, black ink, and hairlines between cells.

     Cards do not float. They are cells in a grid, divided by 1px rules, square cornered.
     A drop shadow under a rounded card is the SaaS default this product was being mistaken
     for; a ruled grid is a drawing, and it holds far more information per inch. */
  /* CORE SAMPLE.

     Umber ground, bone ink, iron oxide. Mineral pigments rather than interface colours,
     and warm throughout - which is what keeps it from reading as the near-black every
     second console ships.

     Ground and cards are the same family: dark on dark here, light on light in the other
     theme. There is no mixed inversion, so --card-ink IS --ink and nothing has to redefine
     anything for its subtree. Two earlier palettes here did invert, and every
     white-on-white panel this product ever shipped came from that. */
  --bg: #071e36;
  --bg-2: #0b2a4a;
  --surface: #0b2a4a;
  --surface-2: #123a60;
  --surface-3: #123a60;

  --ink: #eaf1fb;
  --ink-2: #c8d6ee;
  --muted: #93a6c4;
  /* The mono micro-labels. #7d7169 was the proof's value and measures 3.18 on a raised
     panel, so it is lifted until it reads on the worst surface it can land on. */
  --muted-2: #8fa2bf;

  --card-ink: #eaf1fb;
  --card-ink-2: #c8d6ee;
  --card-muted: #93a6c4;

  --rule: #26406b;
  --rule-2: #33507f;
  --card-rule: #26406b;
  --field-border: #3f5d8d;

  /* Iron oxide: the colour of rust, and of the ground a cable plant stands on. It is a
     FILL - as text it measures 2.94 on a raised panel - so --accent-text carries the
     lifted version and every label uses that. */
  --accent: #0066cc;
  --accent-2: #0066cc;
  --accent-hover: #1f7fe0;
  --accent-ink: #ffffff;
  --accent-soft: rgba(0, 102, 204, .16);
  --accent-line: rgba(0, 102, 204, .42);
  --accent-text: #5fb0ff;
  --accent-text-on-light: #5fb0ff;
  --grad: linear-gradient(135deg, #0066cc 0%, #0057ad 100%);
  --grad-soft: linear-gradient(135deg, rgba(0, 102, 204, .14) 0%, rgba(0, 102, 204, .03) 100%);
  /* A gradient that is read as text, not sat behind white text. The button gradient's dark
     end (#0057ad) is 2.3:1 against this ground - fine under white lettering, below the 3:1
     a 40px numeral needs on its own. The two lighter steps of the validated rust ramp. */
  --grad-text: linear-gradient(135deg, #00c2a8 0%, #5fb0ff 100%);

  --cta: #0066cc;
  --cta-ink: #ffffff;
  --cta-hover: #1f7fe0;
  --cta-soft: rgba(0, 102, 204, .14);
  --cta-text: #5fb0ff;

  --gold: #00c2a8;
  --warn: #e0a340;
  --warn-soft: #2a2712;
  --warn-line: #4a4226;
  --danger: #f4826f;
  --danger-soft: #331b22;
  --danger-line: #4a2a33;
  --danger-ink: #1a0d10;
  --ok: #5cc561;
  --ok-soft: #10331c;
  --ok-line: #1f5a2a;
  --ok-text: var(--ok);
  --warn-text: var(--warn);
  --danger-text: var(--danger);
  --display: "InterVariable", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* The identity's face: Poppins SemiBold and Bold for the wordmark and headings. The
     light numerals stay on --display; Poppins has no 300 and a synthesised one is not a
     brand. */
  --brand: "Poppins", "InterVariable", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Shadows carry the ground, not grey. A neutral shadow under a light card on a
     near-black page reads as dirt; matching it to the ground reads as lift. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .28);
  --shadow-md: 0 2px 6px rgba(0, 0, 0, .34), 0 18px 40px -20px rgba(0, 0, 0, .6);

  /* A quiet inset panel: long prose inside a record, where a card would be one border too
     many. Held apart from --surface-2 so a panel inside a card is still visible. */
  --panel: #123a60;
  /*
   * The one dark surface, and the only one.
   *
   * Deliberately not --panel, which is light in light mode. This is dark in BOTH themes:
   * the page needs a single element that is obviously the subject, and scarcity is the
   * whole mechanism. Adding a second one costs the first one its meaning.
   */
  --focus-bg: #04142a;
  --focus-ink: #eaf1fb;
  --focus-muted: #93a6c4;
  --focus-line: rgba(0, 194, 168, .38);
  --focus-rule: rgba(255, 255, 255, .22);
  --focus-accent: #00c2a8;
  --focus-row: #2b1a2a;
  --panel-ink: #eaf1fb;
  --panel-muted: #93a6c4;
  --panel-line: #26406b;

  --row-pad-y: 11px;
  --row-pad-x: 14px;
  --card-pad: 20px;

  /* Rounder than before, because the reference is and because soft corners are most of
     why it reads as approachable rather than administrative. 12px is the point where a
     card looks deliberate; beyond it a dense table starts to look like a toy. */
  --radius: 12px;
  --radius-sm: 8px;
  --radius-pill: 999px;
  --side-w: 250px;

  /* The mono face carries every micro-label in the chrome: stage numbers, section
     headings, the confidence line. It is doing the job a small-caps sans does badly -
     staying legible at 9.5px with letter-spacing on it - and it reads as instrumentation
     rather than as decoration. System faces only; a webfont for nine-pixel labels is a
     network request to make three words look slightly different. */
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* THE DEEP GROUND, AND WHY IT DOES NOT FOLLOW THE THEME.

     Two places go darker than the page: a band set between ordinary sections, and the
     roadmap, which is one immersive screen. Both are an INVERSION in the light theme and a
     DEEPENING in the dark one, so the colour is the same in both - a band that follows the
     theme stops being a band.

     Defined here with the rest of the palette rather than beside the rules that use it.
     The previous pair lived next to their rules, were named after a palette that has since
     been replaced twice, and were dropped by the repalette that replaced this block; the
     grounds silently stopped painting and the light ink written for them stayed. */
  --deep: #061a30;
  --deep-2: #0b2a4a;

  /* THE BAR.
     Navigation moved from a dark rail down the side to a light bar across the top, so
     these are no longer a dark palette. They keep the rail name because forty files
     reference them and renaming tokens is churn with no result on screen.

     A white bar on a blue ground needs a rule beneath it rather than a shadow: a shadow
     under a full-width bar reads as a seam. */
  --rail: #071e36;
  --rail-hover: #0b2a4a;
  --rail-ink: #eaf1fb;
  --rail-muted: #93a6c4;
  --rail-line: #26406b;
  --rail-accent: #5fb0ff;

  /* THE ALARM.
     The one dark surface left, and the only place a dark ground still earns itself:
     clocks that have already run out. It was oxblood against a dark green rail; with the
     rail now white it can be the deep navy the rest of the palette is built from, which
     makes it read as part of the product rather than as a warning sticker.

     Measured on --alarm: --alarm-muted 8.1:1, --alarm-ink 16.4:1. */
  --alarm: #04142a;
  --alarm-ink: #eaf1fb;
  --alarm-muted: #93a6c4;
  --alarm-line: rgba(255, 255, 255, .16);
  --top-h: 56px;
  --side-w: 236px;

  /* Status palette for charts — fixed, never themed, never reused as a series colour.
     Warning yellow is below 3:1 on the light surface, so every chart using it ships
     visible labels AND a table view (the "relief rule"). */

  --chart-good: #4caf50;
  --chart-warning: #c98a12;
  --chart-critical: #0066cc;
  --chart-neutral: #5d6f8c;
}
/* Dark mode brings the page down to meet the rail, so the rail lifts one step to keep
   its edge. Both blocks exist because a theme the user chose has to beat the one their
   system asked for, in both directions. */
/*
 * Dark mode is the same design one step deeper, and nothing else.
 *
 * WHAT WAS HERE BEFORE
 *
 * Four near-identical blocks carrying the indigo-violet palette this design replaced: a
 * navy top bar, a purple brand gradient, blue-grey body text, and dark tints for the
 * state colours. That last one is what made status pills unreadable - the cards stay
 * LIGHT in both themes, so a dark --ok-soft put a dark green pill on a white card and
 * measured 1.93:1.
 *
 * WHY IT IS THIS SHORT NOW
 *
 * The base palette is ALREADY a dark ground with light panels on it. There is no second
 * palette to switch to. Dark mode drops the ground a step and leans on the shadows, and
 * every other token inherits - which is also why it cannot drift out of date again.
 */
/*
 * Dark mode is a TRUE inversion: dark ground and dark cards.
 *
 * The design before this one kept cards light in both themes, which is what made a status
 * pill render dark green ink on a dark green tint over a white card. If the page goes
 * dark, everything on it goes dark, and then no component has to know which theme it is
 * in - it reads --ink and gets the right answer.
 */
/*
 * THREE STATES, NOT TWO.
 *
 * The base is the dark design, so a system asking for dark gets exactly it with nothing
 * restated. But a reader whose system asks for LIGHT and who has never touched the theme
 * control would have got the dark base too - the default is not neutral once the base
 * takes a side. So light is applied by media query as well as by explicit choice, and an
 * explicit choice wins in both directions.
 */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #f5f7fa;
    --bg-2: #e9edf3;
    --surface: #ffffff;
    --surface-2: #ffffff;
    --surface-3: #e3ebf5;
    --ink: #0b2a4a;
    --ink-2: #2b4a6e;
    --muted: #526a86;
    --muted-2: #526a86;
    --card-ink: #0b2a4a;
    --card-ink-2: #2b4a6e;
    --card-muted: #526a86;
    --rule: rgba(11, 42, 74, .10);
    --rule-2: rgba(11, 42, 74, .20);
    --card-rule: rgba(11, 42, 74, .10);
    --field-border: rgba(11, 42, 74, .28);
    --accent: #0066cc;
    --accent-2: #0057ad;
    --accent-hover: #0066cc;
    --accent-ink: #ffffff;
    --accent-soft: rgba(0, 102, 204, .10);
    --accent-line: rgba(0, 102, 204, .32);
    --accent-text: #0066cc;
    --accent-text-on-light: #0066cc;
    --grad: linear-gradient(135deg, #0066cc 0%, #004a94 100%);
    --grad-soft: linear-gradient(135deg, rgba(0, 102, 204, .09) 0%, rgba(0, 102, 204, .02) 100%);
    --grad-text: var(--grad);
    --cta: #0066cc;
    --cta-ink: #ffffff;
    --cta-hover: #0066cc;
    --cta-soft: rgba(0, 102, 204, .10);
    --cta-text: #0066cc;
    --gold: #00806f;
    --warn: #855b0a;
    --warn-soft: #fbf1de;
    --warn-line: #e6d3a8;
    --danger: #b3341f;
    --danger-soft: #fbeae6;
    --danger-line: #f0c6bb;
    --danger-ink: #ffffff;
    --ok: #2b7430;
    --ok-soft: #e8f5e9;
    --ok-line: #a5d6a7;
    --ok-text: var(--ok);
    --warn-text: var(--warn);
    --danger-text: var(--danger);
    --panel: #e3ebf5;
    --panel-ink: #0b2a4a;
    --panel-muted: #526a86;
    --panel-line: rgba(11, 42, 74, .13);
    --focus-bg: #0b2a4a;
    --focus-ink: #f2f6fb;
    --focus-muted: #93a6c4;
    --focus-rule: rgba(255, 255, 255, .22);
    --focus-accent: #5fb0ff;
    --rail: #ffffff;
    --rail-hover: #e3ebf5;
    --rail-ink: #0b2a4a;
    --rail-muted: #526a86;
    --rail-line: rgba(11, 42, 74, .13);
    --rail-accent: #0066cc;
    --alarm: #0b2a4a;
    --alarm-ink: #f2f6fb;
    --alarm-muted: #93a6c4;
    --alarm-line: rgba(255, 255, 255, .16);
    --shadow-sm: 0 1px 2px rgba(11, 42, 74, .06);
    --shadow-md: 0 2px 6px rgba(11, 42, 74, .10), 0 16px 34px -20px rgba(11, 42, 74, .28);
  }
}
:root[data-theme="light"] {
  --bg: #f5f7fa;
  --bg-2: #e9edf3;
  --surface: #ffffff;
  --surface-2: #ffffff;
  --surface-3: #e3ebf5;
  --ink: #0b2a4a;
  --ink-2: #2b4a6e;
  --muted: #526a86;
  --muted-2: #526a86;
  --card-ink: #0b2a4a;
  --card-ink-2: #2b4a6e;
  --card-muted: #526a86;
  --rule: rgba(11, 42, 74, .10);
  --rule-2: rgba(11, 42, 74, .20);
  --card-rule: rgba(11, 42, 74, .10);
  --field-border: rgba(11, 42, 74, .28);
  --accent: #0066cc;
  --accent-2: #0057ad;
  --accent-hover: #0066cc;
  --accent-ink: #ffffff;
  --accent-soft: rgba(0, 102, 204, .10);
  --accent-line: rgba(0, 102, 204, .32);
  --accent-text: #0066cc;
  --accent-text-on-light: #0066cc;
  --grad: linear-gradient(135deg, #0066cc 0%, #004a94 100%);
  --grad-soft: linear-gradient(135deg, rgba(0, 102, 204, .09) 0%, rgba(0, 102, 204, .02) 100%);
  --grad-text: var(--grad);
  --cta: #0066cc;
  --cta-ink: #ffffff;
  --cta-hover: #0066cc;
  --cta-soft: rgba(0, 102, 204, .10);
  --cta-text: #0066cc;
  --gold: #00806f;
  --warn: #855b0a;
  --warn-soft: #fbf1de;
  --warn-line: #e6d3a8;
  --danger: #b3341f;
  --danger-soft: #fbeae6;
  --danger-line: #f0c6bb;
  --danger-ink: #ffffff;
  --ok: #2b7430;
  --ok-soft: #e8f5e9;
  --ok-line: #a5d6a7;
  --ok-text: var(--ok);
  --warn-text: var(--warn);
  --danger-text: var(--danger);
  --panel: #e3ebf5;
  --panel-ink: #0b2a4a;
  --panel-muted: #526a86;
  --panel-line: rgba(11, 42, 74, .13);
  --focus-bg: #0b2a4a;
  --focus-ink: #f2f6fb;
  --focus-muted: #93a6c4;
  --focus-rule: rgba(255, 255, 255, .22);
  --focus-accent: #5fb0ff;
  --rail: #ffffff;
  --rail-hover: #e3ebf5;
  --rail-ink: #0b2a4a;
  --rail-muted: #526a86;
  --rail-line: rgba(11, 42, 74, .13);
  --rail-accent: #0066cc;
  --alarm: #0b2a4a;
  --alarm-ink: #f2f6fb;
  --alarm-muted: #93a6c4;
  --alarm-line: rgba(255, 255, 255, .16);
  --shadow-sm: 0 1px 2px rgba(11, 42, 74, .06);
  --shadow-md: 0 2px 6px rgba(11, 42, 74, .10), 0 16px 34px -20px rgba(11, 42, 74, .28);
}

/* ------------------------------------------------------------------ base --- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* Served from this origin, addressed by the hash of its own contents. Displayed with
   swap, so the page is readable immediately in the fallback rather than blank while a
   third of a megabyte arrives — on a console people open forty times a day, a flash of
   fallback text beats a flash of nothing.
   (No backticks in this file: it is one long template literal, and a stray one ends it.) */
@font-face {
  font-family: "InterVariable";
  src: url("/assets/inter.693b77d4f32ee9b8.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/assets/poppins-600.f4e80d9dfd374d02.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/assets/poppins-700.9338e65fc077355c.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 15px/1.55 "InterVariable", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* cv05 gives the lowercase l a tail, which stops it being an I or a 1 at 13px in a
     table of control references. ss03 rounds the ampersand. Both are legibility, not
     decoration. */
  font-feature-settings: "cv05" 1, "ss03" 1;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Slightly negative at body size is what stops a UI face reading as loose on a dense
     page. Headings tighten further, below. */
  letter-spacing: -.006em;
}

/* Arabic is deliberately NOT Inter, which has no Arabic coverage. A Latin face stretched
   over Arabic text is worse than a system font that was actually drawn for it. */
[dir="rtl"] body {
  font-family: "Noto Sans Arabic", "Segoe UI", Tahoma, system-ui, sans-serif;
  font-feature-settings: normal;
  letter-spacing: normal;
}
a { color: var(--accent-text); text-decoration: none; text-underline-offset: 2px; }
a:hover { color: var(--accent-hover); text-decoration: underline; }
/* Two rings: the accent, and a ground-coloured halo so it stays visible against a
   button already painted in the accent. The old single ring vanished on primary
   buttons, which are exactly the controls somebody tabbing is heading for. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
  box-shadow: 0 0 0 4px var(--accent-soft);
}
main:focus { outline: none; }
.ico { flex: none; vertical-align: -.15em; }
/* Motion here is decorative; anyone who has asked for less gets none of it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
.skip {
  position: absolute; inset-inline-start: -9999px; top: 8px; z-index: 60;
  background: var(--surface); color: var(--card-ink); border: 1px solid var(--accent);
  padding: 9px 14px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 600;
}
.skip:focus { inset-inline-start: 12px; }
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- folding the sidebar away ----------------------------------------------------------- */

/* ============================================================== the shell ===

   A sidebar and a page. The sidebar holds the sections a compliance function
   recognises, each with the pages it is used for and the rest under "More"; the top
   bar holds the breadcrumb, search, the companion and the person. Nothing is reachable
   only by search. */
.shell {
  display: grid; grid-template-columns: var(--side-w) minmax(0, 1fr);
  min-height: 100vh;
}
.side {
  position: sticky; top: 0; height: 100vh; overflow-y: auto; overscroll-behavior: contain;
  background: var(--rail); border-inline-end: 1px solid var(--rail-line);
  display: flex; flex-direction: column;
}
.side > .brand { padding: 18px 20px 14px; }
.brand .mark { display: block; flex: none; }
.side .nav { flex: 1; }
.side .nav-foot { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--rail-line); position: sticky; bottom: 0; background: var(--rail); padding-bottom: 6px; }
.nav-more > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; font-size: 13px; color: var(--rail-muted); border-radius: var(--radius-sm);
}
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::before { content: "+"; width: 16px; text-align: center; font-weight: 600; opacity: .7; }
.nav-more[open] > summary::before { content: "−"; }
.nav-more > summary:hover { background: var(--rail-hover); color: var(--rail-ink); }
.nav-more a { font-size: 14px; padding-inline-start: 36px; }

/* The sidebar folds to a drawer on a narrow screen; the top bar gains a menu button. */
.side-toggle { display: none; }
@media (max-width: 1000px) {
  .shell { grid-template-columns: 1fr; }
  .side { display: none; }
  .side-toggle { display: block; }
  .side-toggle > summary { padding: 6px; }
  .side-toggle[open] + .crumbs { display: none; }
  body:has(.side-toggle[open]) .side {
    display: flex; position: fixed; inset: var(--top-h) auto 0 0; width: min(300px, 86vw); z-index: 45;
    box-shadow: var(--shadow-md);
  }
  [dir="rtl"] body:has(.side-toggle[open]) .side { inset: var(--top-h) 0 0 auto; }
}

.tbar-right { display: flex; align-items: center; gap: 6px; flex: none; }
/* Collecting right now. The dot pulses because the fact it reports is continuous; it is
   the only motion in the chrome, and it stops under reduced motion. */
.live {
  display: flex; align-items: center; gap: 7px; white-space: nowrap;
  font-size: 12.5px; color: var(--muted); padding-inline-end: 6px;
}
.live i { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); flex: none; animation: live-beat 2.6s ease-in-out infinite; }
@keyframes live-beat { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .live i { animation: none; } }
@media (max-width: 1100px) { .live { display: none; } }
@keyframes tn-drop { from { opacity: 0; transform: translateY(-4px); } }

/* ---- the Zenith foot ------------------------------------------------------------------ */

.zen-k {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--rail-muted);
}
.zen-n {
  font-size: 40px; font-weight: 300; line-height: 1; letter-spacing: -.04em;
  color: var(--rail-ink); font-variant-numeric: tabular-nums; margin-top: 3px;
}
.zen-b { font-size: 13.5px; font-weight: 600; color: var(--accent-text); margin-top: 2px; }
.zen-none { font-size: 14px; font-weight: 600; color: var(--rail-ink); margin: 5px 0 3px; }
.zen-bar { height: 3px; background: var(--rail-line); margin: 9px 0 6px; overflow: hidden; }
.zen-bar i { display: block; height: 100%; background: var(--accent); }
.zen-conf { font-family: var(--mono); font-size: 9.5px; color: var(--rail-muted); }
.zen-act {
  display: block; margin-top: 9px; font-size: 13px; color: var(--rail-muted);
  text-decoration: none; border-bottom: 1px solid var(--rail-line); padding-bottom: 2px;
}
.zen-act:hover { color: var(--rail-ink); border-bottom-color: var(--accent); }
/* The one thing that would move the figure most. A filled button because it is the only
   action in the chrome, and because the score above it is otherwise something to look at
   rather than something to do. */
.zen-do {
  display: block; margin-top: 11px; padding: 9px 12px; text-align: center;
  background: var(--accent); color: var(--accent-ink); text-decoration: none;
  border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 600; line-height: 1.3;
}
.zen-do:hover { background: var(--accent-hover); }
.zen-do + .zen-act { margin-top: 9px; }

/* ---- narrow ---------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  .zen-n { font-size: 22px; margin: 0; }
  .zen-bar, .zen-act, .zen-k { display: none; }
}
@media (max-width: 700px) {
  .top .wt { display: none; }
}

.brand {
  display: flex; align-items: center; gap: 10px;
  color: var(--rail-ink); text-decoration: none;
}
.brand .wm { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
/* The product name is the one place the display face appears outside a heading. It is a
   masthead; set in the body sans it was a label. */
.brand .wm b {
  font-family: var(--display); font-size: 17px; letter-spacing: -.022em;
  font-weight: 800; color: var(--rail-ink);
}
.brand .wm span { font-size: 11px; color: var(--rail-muted); }
.workspace {
  margin: 0 12px 12px; padding: 8px 11px; border: 1px solid var(--rail-line);
  border-radius: var(--radius-sm); background: rgba(255, 255, 255, .045);
  display: flex; align-items: center; gap: 9px; min-width: 0;
}
.av {
  width: 26px; height: 26px; border-radius: var(--radius-sm); flex: none;
  background: var(--accent); color: var(--accent-ink);
  display: grid; place-items: center; font-size: 12.5px; font-weight: 700;
}
.wt { min-width: 0; line-height: 1.25; }
.wt b { display: block; font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wt span { font-size: 12px; color: var(--muted); }
.nav { padding: 2px 10px 14px; }
.nav-group { margin-top: 18px; }
.nav-group:first-child { margin-top: 0; }
.nav-group > h2 {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--rail-muted);
  font-weight: 600; margin: 0 0 6px; padding-inline-start: 10px;
}
.nav a {
  position: relative;
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; margin-bottom: 2px;
  color: var(--rail-muted); text-decoration: none; font-size: 14.5px; border-radius: var(--radius-sm);
  transition: background .14s ease, color .14s ease;
}
/* The marker for the current page is drawn rather than filled: a stroke that grows from
   nothing. It is the only motion in the chrome, it runs for 220ms, and it marks the one
   thing a person needs to find again after looking away — which is the test for whether
   motion has earned its place. */
.nav a::before {
  content: ""; position: absolute; inset-inline-start: 0; top: 50%;
  width: 3px; height: 0; border-radius: 0 3px 3px 0; background: var(--rail-accent);
  transform: translateY(-50%); transition: height .22s cubic-bezier(.2, .8, .3, 1);
}
[dir="rtl"] .nav a::before { border-radius: 3px 0 0 3px; }
.nav a .lbl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav a:hover { background: var(--rail-hover); color: var(--rail-ink); }
.nav a:hover::before { height: 14px; }
.nav a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent-text); font-weight: 600;
}
.nav a[aria-current="page"]::before { height: 20px; }
.nav a[aria-current="page"] svg { color: var(--accent-text); }
.badge {
  font-size: 11px; font-weight: 700; padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--danger); color: var(--danger-ink); font-variant-numeric: tabular-nums;
}
/* The user menu and the mobile navigation are <details>: disclosure without script. */
details.menu { position: relative; }
details.menu > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 9px;
  padding: 7px 9px; border-radius: var(--radius-sm); color: var(--ink-2); font-size: 14px;
}
details.menu > summary::-webkit-details-marker { display: none; }
details.menu > summary:hover { background: var(--surface-2); }
details.menu > summary .chev { margin-inline-start: auto; color: var(--muted); transition: transform .15s ease; }
details.menu[open] > summary .chev { transform: rotate(180deg); }
/*
 * THE MENU OPENED UPWARDS, WHICH IS WHY IT WAS NEVER VISIBLE.
 *
 * Written when the user menu sat at the FOOT of a left rail, where the only free space
 * is above it. The navigation then moved to a bar across the top and this rule came
 * with it, so the sheet rendered at top: -378px - off the top of the window. The
 * <details> opened correctly every time and there was nothing on screen to show for it,
 * which reads exactly like a dead control.
 *
 * It opens down and end-aligned, because the bar is at the top of the window and that
 * is the only place this menu lives. The rail it was written for is gone - deleted in
 * this same change, so there is no second layout left to write a second rule for.
 */
.sheet {
  position: absolute; top: calc(100% + 7px); inset-inline-end: 0; min-width: 236px; z-index: 60;
  background: var(--surface); border: 1px solid var(--card-rule); border-radius: var(--radius);
  box-shadow: var(--shadow-md); padding: 7px;
  transform-origin: top var(--sheet-origin, right);
  animation: sheet-in .14s cubic-bezier(.2, .8, .3, 1) both;
}
@keyframes sheet-in { from { opacity: 0; transform: translateY(-5px) scale(.985); } }
.sheet a, .sheet button.item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: start;
  padding: 7px 9px; border-radius: var(--radius-sm); font-size: 14px; color: var(--ink-2);
  text-decoration: none; background: none; border: none; cursor: pointer; font-weight: 400;
}
.sheet a:hover, .sheet button.item:hover { background: var(--surface-2); color: var(--ink); }
.sheet hr { border: none; border-top: 1px solid var(--rule); margin: 6px 2px; }
.sheet .head { padding: 7px 9px 8px; }
.sheet .head b { display: block; font-size: 14px; }
.sheet .head span { font-size: 12.5px; color: var(--muted); }
.sheet .lab { padding: 2px 9px 4px; font-size: 12.5px; letter-spacing: .01em; color: var(--muted); font-weight: 620; }
.seg { display: flex; gap: 4px; padding: 2px 7px 6px; }
.seg a {
  flex: 1; justify-content: center; padding: 5px 4px; font-size: 13px;
  border: 1px solid var(--rule); border-radius: var(--radius-sm); color: var(--muted);
}
.seg a[aria-current="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); font-weight: 600; }

/* ---------------------------------------------------------------- top bar --- */
.top {
  position: sticky; top: 0; z-index: 40; min-height: var(--top-h);
  display: flex; align-items: center; gap: 14px; padding: 0 24px;
  background: var(--surface); border-bottom: 1px solid var(--rule);
}
.cmdk-hint { display: none; align-items: center; height: 32px; padding: 0 8px; border: 1px solid var(--field-border); border-radius: var(--radius-sm); background: var(--surface); color: var(--muted); cursor: pointer; font: inherit; }
.cmdk-hint.on { display: inline-flex; }
.cmdk-hint kbd { font-family: var(--mono); font-size: 11px; }
.cmp-open {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px;
  border: 1px solid var(--accent-line); border-radius: 999px; background: var(--accent-soft);
  color: var(--accent-text); font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
}
.cmp-open[aria-expanded="true"] { background: var(--accent); color: var(--accent-ink); }
.cmp-open svg { display: none; }
.crumbs { display: flex; align-items: center; gap: 7px; font-size: 14px; color: var(--muted); min-width: 0; flex: 1 1 auto; overflow: hidden; }
.crumbs a { color: var(--muted); text-decoration: none; white-space: nowrap; }
.crumbs a:hover { color: var(--ink); text-decoration: underline; }
.crumbs .sep { opacity: .45; }
.crumbs .now { color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.find { display: flex; align-items: center; }
.find input {
  width: 216px; height: 32px; padding: 0 11px; font-size: 14px;
  border: 1px solid var(--field-border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--card-ink);
}

/* ---------------------------------------------------------------- content --- */
/* Centred rather than left-aligned against a rail that is no longer there, and
   given more room to breathe: the reference is airy mostly because it is generous
   with the space around a card rather than because of anything inside one. */
main {
  /* The horizontal padding, as a token, because one thing cancels it: the roadmap breaks
     out of it to run its ground to the edges. Written as two numbers that happened to
     agree, they stopped agreeing the moment a breakpoint changed one of them - the page
     then scrolled 17px sideways between 621px and 900px, which is a scrollbar's width and
     reads as a rendering fault. */
  --main-pad: 32px;
  padding: 28px var(--main-pad) 40px; max-width: 1320px; margin: 0 auto; width: 100%;
}
@media (max-width: 620px) { main { --main-pad: 16px; padding: 20px var(--main-pad) 32px; } }
/* A rule in the accent under the page title.
   Every one of the competitors floods their brand colour across whole surfaces; this
   console used its green on a 1px border and nowhere else, which is why the colour
   registered as absent. One deliberate stroke is enough on a working screen, and it
   costs no space. */
.page-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 18px; flex-wrap: wrap; margin-bottom: 20px; position: relative; padding-bottom: 0; }
.page-head .acts { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* Tracking tightens as size grows — the same correction a metal typeface got from its
   optical sizes, and the single clearest tell between type that was set and type that
   was merely used. */
/* Four sizes inside nine points is not a scale. Widened so a page has a shape that
   reads at arm's length, and so a section heading is unmistakably a level below the
   page title rather than a slightly bolder paragraph. */
/* Set rather than merely used: a serif wants less negative tracking than a sans at the
   same size, and more line-height, because its letterforms already carry the contrast a
   sans has to get from weight. */
h1 {
  font-family: var(--brand);
  font-size: 24px; margin: 0 0 4px; letter-spacing: -.015em;
  font-weight: 700; line-height: 1.2; text-wrap: balance;
}
/*
 * Two-tone headlines, which the reference uses to carry meaning rather than decoration:
 * the part naming the subject takes the pine, the rest stays ink. One accent per heading.
 */
h1 .tone, h2 .tone { color: var(--accent); }
/* 22px display serif gave a chart group called "Coverage and activity" the same visual
   authority as the page title. A section heading is a signpost, not a masthead. */
h2 {
  font-family: var(--brand);
  font-size: 17px; margin: 28px 0 10px; letter-spacing: -.01em;
  font-weight: 600; line-height: 1.25; text-wrap: balance;
}
h3 { font-size: 15px; margin: 0 0 6px; font-weight: 700; letter-spacing: -.012em; }
.sub { color: var(--muted); font-size: 14px; margin: 0; max-width: 78ch; line-height: 1.55; }
.page-head .sub { margin-top: 4px; }
/* A section's own subtitle. The page title's stays as it was — that one orients somebody
   who has just arrived. This one annotates a heading they are already reading, and at
   full size 156 of them are why the console reads as though it is explaining itself. */
.section > header .sub, h2 + .sub {
  font-size: 13.5px; color: var(--muted); line-height: 1.45; max-width: 74ch;
}
.card {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--card-pad); margin-bottom: 14px; box-shadow: var(--shadow-sm);
}
/* A grid child will happily grow past its track unless told not to, which is how a
   wide table inside a card pushes the whole page sideways on a phone. */
.card, .chart-card { min-width: 0; }
/* A card carrying a figure sits slightly off the page; one carrying a paragraph does not. */
.chart-card, .hero-card { box-shadow: var(--shadow-md); }
/* The wrapper for anything that is legitimately wider than the phone it is read on:
   a matrix, a chart with many columns. It scrolls itself rather than the page. */
.wide-scroll { overflow-x: auto; max-width: 100%; }
/* A control that reports the click landed. 60ms, and only on the press — a button that
   animates on hover is a button that fidgets. */
.btn:active, button.btn:active { filter: brightness(.96); }
.btn { transition: background .14s ease, border-color .14s ease, color .14s ease, transform .06s ease; }

.card > :first-child { margin-top: 0; }
/* A card heading directly under the page title is the page's second LEVEL — that is
   semantics, and a screen reader navigating by heading hears a missing section if it is
   an h3. Its SIZE is a separate question, and inside a card it is a card heading. Level
   and size are different concerns and this is where they are allowed to disagree. */
.card > h2, .cards .card > h2 { font-size: 15px; letter-spacing: -.01em; margin: 0 0 6px; }
.card > :last-child { margin-bottom: 0; }
/*
 * EVERY AUTO-FIT GRID HERE CAN BE NARROWER THAN ITS OWN MINIMUM.
 *
 * minmax(210px, 1fr) sets a FLOOR on the track. Put the grid in a 190px column - a phone,
 * a folded sidebar, a narrow card - and the track stays 210px and the PAGE scrolls
 * sideways. The layout sweep caught it on the compliance page as a 20px spill; the same
 * mistake was in eighteen grids, each one a sideways scroll waiting for a container narrow
 * enough to trigger it.
 *
 * min(210px, 100%) changes nothing when there is room and yields when there is not.
 */
.grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); }
.cols-2 { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); align-items: start; }

/* ---------------------------------------------------------------- sections ---
   One rhythm for the whole console. Before this, every page invented its own gap
   between a heading and the thing under it, and the pages read as a collection of
   screens rather than as one product. */
.section { margin-top: 34px; }
.section:first-of-type { margin-top: 0; }
.section > header {
  display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
  flex-wrap: wrap; margin-bottom: 12px; padding-bottom: 9px; border-bottom: 1px solid var(--rule);
}
.section > header h2 { margin: 0; font-size: clamp(20px, 2vw, 25px); }
.section > header .sub { margin-top: 4px; }
.section > header .acts { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.section .lead { color: var(--muted); font-size: 14.5px; margin: -4px 0 14px; max-width: 80ch; }

/* The heading in the language the law was written in, under our rendering of it. The
   reader who is about to cite a provision has to be able to see which is which. */
.art-src {
  display: block; font-size: 13px; color: var(--muted); margin-top: 1px;
  font-style: italic;
}

/* The instruction, as against the context above it. "Where you are" is a sentence; "what
   to do next" is the reason the page exists, so it gets the weight and the marker. */
.jl-next {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  margin: 6px 0 14px; font-size: 14.5px; color: var(--ink-2); line-height: 1.5;
}
.jl-next-k {
  flex: none; font-size: 12.5px; font-weight: 700; color: var(--accent-text);
  background: var(--accent-soft); border-radius: var(--radius-pill); padding: 2px 9px;
}
.jl-next strong { color: var(--ink); }

/* ------------------------------------------------------------- add forms --- */
/* An occasional form, folded away. Same device as the dashboard's analysis and the pack
   card's add step: one vocabulary for "there is more here", learned once. */
.add-form { margin: 18px 0; }
.add-form > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 13px 8px 11px; border: 1px solid var(--rule-2); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink-2); font-size: 14.5px; font-weight: 620;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.add-form > summary::-webkit-details-marker { display: none; }
.add-form > summary:hover { border-color: var(--accent-line); color: var(--ink); background: var(--surface-2); }
.add-form > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.af-plus {
  font-size: 15px; line-height: 1; color: var(--accent-text); font-weight: 700;
  transition: transform .18s cubic-bezier(.2, .8, .3, 1);
}
/* The plus becomes a cross, by rotation rather than by swapping a glyph — the movement
   says the same control closes it again. */
.add-form[open] > summary .af-plus { transform: rotate(45deg); }
.add-form[open] > summary { border-color: var(--accent-line); color: var(--ink); }
.af-body {
  margin-top: 12px; padding: 16px 18px; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius);
}
.af-body > :first-child { margin-top: 0; }
.af-body > :last-child { margin-bottom: 0; }

/* ------------------------------------------- web interface guidelines --- */
/* Five rules from the Vercel Web Interface Guidelines that apply to a server-rendered
   console. The rest of that document is about a client bundle this app does not have. */

/* A heading that drops its last word onto a line of its own reads as a mistake, and
   which headings do it depends on where the viewport happens to fall — invisible in
   review, constant in use. Balance is ignored by browsers that do not support it. */
h1, h2, h3, .cmd-title, .pk-name, .ul-name, .sp-t {
  text-wrap: balance;
}
/* Body copy wants pretty rather than balance: balance is for a few lines, and applying
   it to a paragraph is expensive and does nothing useful. */
.sub, .cmd-mean, .pk-sum { text-wrap: pretty; }

/* The topbar is sticky. Without this, following a link to a heading anchor scrolls that
   heading underneath it and the reader lands on the paragraph after the one they
   clicked. var(--top-h) plus a little air, so the heading arrives clear of the bar. */
h1[id], h2[id], h3[id], [id].section, .art-chapter[id] {
  scroll-margin-top: calc(var(--top-h) + 14px);
}

/* Touch: without touch-action a browser waits ~300ms after every tap in case a second
   one is coming, which makes the console feel slow on a tablet in a way nobody can put
   their finger on. The tap highlight is set to the accent instead of the browser's
   blue-grey, so a tap looks like this product responding. */
a, button, summary, label, [role="button"] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(20, 107, 77, .14);
}

/* --------------------------------------------------------------- pack cards --- */
/* A shop for help with obligations, which has exactly one failure mode: an obligation
   presented as an upsell, or a certification presented as a requirement. The card states
   its standing at the top, before the name, because that is the fact a reader needs
   first and the one a vendor has an incentive to bury. */
.packs { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); }
.pk {
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 15px 16px 14px;
  background: var(--surface); display: flex; flex-direction: column; min-width: 0;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.pk:hover { border-color: var(--accent-line); box-shadow: var(--shadow-md); }
.pk-top { display: flex; align-items: center; gap: 8px; justify-content: space-between; margin-bottom: 9px; }
.pk-region { font-size: 12.5px; color: var(--muted); font-weight: 620; }
.pk-name { margin: 0; font-family: var(--display); font-size: 20px; font-weight: 760; letter-spacing: -.02em; line-height: 1.15; }
.pk-name a { color: inherit; text-decoration: none; }
.pk-name a:hover { color: var(--accent-text); }
.pk-auth { margin: 3px 0 0; font-size: 13px; color: var(--muted); }
.pk-sum { margin: 9px 0 0; font-size: 14px; color: var(--ink-2); line-height: 1.45; }
.pk-scale {
  margin: 10px 0 0; font-size: 13.5px; color: var(--ink-2); font-variant-numeric: tabular-nums;
  padding-top: 9px; border-top: 1px solid var(--rule);
}
/* How much of a pack the connected systems answer. Carried in the ok colour because it is
   the one line on the card that is good news about work somebody does not have to do. */
.pk-auto { margin: 5px 0 0; font-size: 13.5px; color: var(--ok); font-variant-numeric: tabular-nums; }
.pk-get { margin: 5px 0 0; font-size: 13px; color: var(--muted); line-height: 1.4; }
.pk-act { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--rule); display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* What binds you is not for sale, and the card says so with its ground rather than only
   with a word somebody may not read. */
.pk-binding { border-inline-start: 3px solid var(--danger); }
.pk-chosen { border-inline-start: 3px solid var(--accent); }

/* The reason field opens in place. Asking why somebody is taking a framework on is not
   friction for its own sake: an auditor asks it, and "somebody clicked add" is no answer. */
.pk-add > summary, .pk-drop > summary { list-style: none; cursor: pointer; display: inline-flex; }
.pk-add > summary::-webkit-details-marker,
.pk-drop > summary::-webkit-details-marker { display: none; }
.pk-add[open] > summary, .pk-drop[open] > summary { display: none; }
.pk-add .stack, .pk-drop .stack { margin-top: 4px; display: grid; gap: 9px; }
/* The confirmation step for removing a pack. With no client script a disclosure is the
   confirmation: it names the consequence before the button that does it. */
.pk-drop .note { margin: 0; max-width: 34ch; }

/* ------------------------------------------------------------- story spine --- */
/* A ledger spread, not a game board. The people who open this console are preparing for
   somebody with statutory powers to disagree with them, and badges read as unserious at
   exactly the moment the product needs to read as authoritative. */
.spine { list-style: none; margin: 0 0 26px; padding: 0; position: relative; }
/* One continuous rule behind the numerals, so the chapters read as a sequence rather
   than as a stack of unrelated rows. Inset so it starts and ends inside the numbers. */
.spine::before {
  content: ""; position: absolute; inset-inline-start: 15px; top: 14px; bottom: 14px;
  width: 2px; background: var(--rule-2); border-radius: 2px;
}
.sp { position: relative; display: grid; grid-template-columns: 32px minmax(0, 1fr); gap: 14px; }
.sp + .sp { margin-top: 2px; }
.sp-n {
  grid-column: 1; z-index: 1; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; font-size: 13.5px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--surface); border: 2px solid var(--card-rule); color: var(--card-muted);
}
.sp-body { grid-column: 2; min-width: 0; padding-bottom: 16px; }
.sp-t { margin: 0; font-size: 14.5px; font-weight: 650; letter-spacing: -.008em; }
.sp-m { margin: 3px 0 0; font-size: 14.5px; color: var(--ink-2); max-width: 62ch; line-height: 1.5; }

/* Closed: one line, a date, and out of the way. It happened; it is not the story now. */
.sp-done .sp-n { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.sp-done .sp-body { padding-bottom: 10px; }
.sp-done .sp-t { font-size: 14.5px; font-weight: 600; color: var(--ink-2); }
.sp-when { margin: 1px 0 0; font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Open: the only chapter set at full size, because a roadmap showing seven steps at
   equal weight has said where you are and not what to do. */
.sp-here .sp-n {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  box-shadow: 0 0 0 5px var(--accent-soft);
}
.sp-here .sp-body {
  background: var(--surface); border: 1px solid var(--card-rule); border-radius: var(--radius);
  box-shadow: var(--shadow-md); padding: 16px 18px 18px; margin-bottom: 18px;
  /* The open chapter is the one light panel in the spine, so it carries its own ink. */
  --ink: var(--card-ink); --ink-2: var(--card-ink-2); --muted: var(--card-muted);
  --rule: var(--card-rule); --accent-text: var(--accent-text-on-light);
  color: var(--card-ink);
}
.sp-here .sp-t { font-family: var(--display); font-size: 26px; font-weight: 780; letter-spacing: -.022em; line-height: 1.15; }
.sp-eyebrow {
  margin: 0 0 4px; font-size: 13px; font-weight: 650; color: var(--accent-text);
}
.sp-left { margin: 13px 0 0; padding: 0; list-style: none; display: grid; gap: 7px; }
.sp-left li {
  font-size: 14.5px; color: var(--ink-2); padding-inline-start: 20px; position: relative;
}
/* An open square, not a tick: these are the things NOT done, and a tick beside an
   outstanding item is the sort of detail that makes somebody distrust the whole page. */
.sp-left li::before {
  content: ""; position: absolute; inset-inline-start: 0; top: 4px;
  width: 11px; height: 11px; border: 1.5px solid var(--field-border); border-radius: 3px;
}
.sp-act { margin-top: 15px; }

/* Ahead: visible and quiet. Seeing where the story goes is the whole difference between
   a progress bar and a roadmap, so these are dimmed rather than hidden. */
.sp-ahead .sp-n { border-style: dashed; }
.sp-ahead .sp-t, .sp-ahead .sp-m { color: var(--muted); }
.sp-ahead .sp-t { font-weight: 600; }

/* ---------------------------------------------------------------- unlocks --- */
.unlocks { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(min(268px, 100%), 1fr)); }
.ul {
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 15px 16px;
  background: var(--surface); transition: border-color .14s ease, box-shadow .14s ease;
}
.ul:hover { border-color: var(--accent-line); box-shadow: var(--shadow-md); color: inherit; }
.ul-kind { display: block; font-size: 13px; font-weight: 650; color: var(--muted); }
.ul-name {
  display: block; font-family: var(--display); font-size: 19px; font-weight: 600;
  letter-spacing: -.01em; margin-top: 2px; line-height: 1.2;
}
.ul-why { display: block; font-size: 14px; color: var(--ink-2); margin-top: 6px; line-height: 1.45; }
.ul-get { display: block; font-size: 13px; color: var(--muted); margin-top: 9px; line-height: 1.4; }

/* An obligation is not an offer, and must not be able to be mistaken for one. */
.ul-obligation { border-color: var(--rule-2); background: var(--danger-soft); }
.ul-obligation .ul-kind { color: var(--danger); }
.ul-obligation:hover { border-color: var(--danger); }

.ul-meter { margin-top: 11px; }
.ul-track {
  display: block; height: 6px; border-radius: 3px; background: var(--surface-3);
  overflow: hidden;
}
.ul-fill {
  display: block; height: 100%; background: var(--accent); border-radius: 3px;
  width: var(--w, 0%); animation: ul-grow .5s cubic-bezier(.2, .8, .3, 1) both;
  animation-delay: calc(.1s + var(--i) * .06s);
}
/* No denominator, no proportion. A bar drawn to an invented width is decoration standing
   where evidence should be, which is the move that makes somebody stop believing a page. */
.ul-nodenom { width: 100%; background: var(--accent-line); animation: none; }
@keyframes ul-grow { from { width: 0; } to { width: var(--w, 0%); } }
.ul-count {
  display: block; margin-top: 5px; font-size: 13px; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ stats --- */
.stat { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: 15px 17px; box-shadow: var(--shadow-sm); }
.stat .n { font-size: 27px; font-weight: 640; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.stat .l { font-size: 13.5px; color: var(--muted); margin-top: 2px; }
.stat.warn .n { color: var(--warn); } .stat.danger .n { color: var(--danger); } .stat.ok .n { color: var(--ok); }
/* One object divided by hairlines, not six cards floating side by side. The 1px gap
   over a ruled ground draws the separators, which is the one construction that keeps
   them correct when the row wraps — borders on the children do not. */
.kpi-row {
  display: grid; gap: 1px; grid-template-columns: repeat(auto-fit, minmax(min(134px, 100%), 1fr));
  background: var(--rule); border: 1px solid var(--rule);
  border-radius: var(--radius); overflow: hidden;
  /* Anything after a KPI row needs clearance. Without this, a page whose first section
     follows the tiles gets margin-top: 0 from .section:first-of-type and its heading
     sits on top of them — which only happens on a workspace with no data yet, so it is
     the first thing a new customer sees and the last thing anybody tests. */
  margin-bottom: 22px;
}
.kpi {
  border: 0; border-radius: 0; padding: 15px 16px 14px;
  background: var(--surface); box-shadow: none; text-decoration: none; color: inherit;
  display: block; transition: background .14s ease;
}
/* A tile in a band cannot lift off the page without tearing the band, so it lights
   instead. The accent stroke says which one is under the pointer. */
a.kpi { position: relative; }
a.kpi::after {
  content: ""; position: absolute; inset-inline: 0; bottom: 0; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: left center;
  transition: transform .18s cubic-bezier(.2, .8, .3, 1);
}
[dir="rtl"] a.kpi::after { transform-origin: right center; }
a.kpi:hover { background: var(--surface-2); color: inherit; }
a.kpi:hover::after { transform: none; }
.kpi .n { font-size: 26px; font-weight: 300; letter-spacing: -.025em; font-variant-numeric: tabular-nums; }
.kpi .l { font-size: 12.5px; color: var(--muted); margin-top: 1px; line-height: 1.3; }
/* The sparkline sits under the number, never instead of it: the tile always states
   the current value in text, so the line is a second channel and not the only one. */
.kpi .spark { margin-top: 7px; height: 24px; opacity: .9; }
.kpi .top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.kpi .delta { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--muted); }
.kpi .delta.up { color: var(--ok); }
.kpi .delta.down { color: var(--danger); }
/* A border is the weakest signal a tile has. A tinted ground and a coloured numeral
   read from across a desk, which is where somebody actually notices a deadline. */
.kpi.alert { border-color: var(--rule-2); background: var(--danger-soft); box-shadow: none; }
.kpi.alert .n { color: var(--danger); }
.kpi.alert .l { color: var(--ink-2); }
.kpi.warn { border-color: var(--rule-2); background: var(--warn-soft); box-shadow: none; }
.kpi.warn .n { color: var(--warn); }
.kpi.warn .l { color: var(--ink-2); }
/* And the quiet ones get quieter, so the loud ones have something to be louder than. */
/* The display face on figures, which is what it is for: a number is the one piece of
   text on a console page that is looked at rather than read, and an old-style serif
   numeral has the width to be looked at. Tabular, so a column of them lines up. */
.kpi .n {
  /* Large and LIGHT.

     A figure set at 300 reads as a measurement being reported; the same figure at 600
     reads as a shout, and on a screen carrying twenty of them everything shouting is
     indistinguishable from nothing shouting. Tabular numerals so a column of them lines
     up on the decimal rather than dancing. */
  font-family: var(--display); font-size: 40px; font-weight: 300;
  letter-spacing: -.03em; line-height: 1;
}
.kpi .l { font-size: 13px; margin-top: 4px; }

/* ----------------------------------------------------------------- tables --- */
table { width: 100%; border-collapse: collapse; font-size: 14.5px; }
.scroll {
  overflow-x: auto; border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
th {
  text-align: start; font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; padding: calc(var(--row-pad-y) - 1px) var(--row-pad-x); background: var(--surface);
  border-bottom: 1px solid var(--rule-2); white-space: nowrap;
}
th a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
th a:hover { color: var(--ink); }
th .dir { font-size: 9px; }
td { padding: var(--row-pad-y) var(--row-pad-x); border-bottom: 1px solid var(--rule); vertical-align: top; }
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background .1s ease; }
tbody tr:hover { background: var(--surface-2); }
td.num, th.num { text-align: end; font-variant-numeric: tabular-nums; }

/* Everywhere a figure is compared down a column. Applied by element rather than by
   remembering a class at each call site, because the failure mode is silent: one
   forgotten class and that column alone jitters. */
table, code, .metric, .kpi, .stat, time, .count, .progress + *,
input[type="number"], input[type="date"] {
  font-variant-numeric: tabular-nums;
}
.ttl { font-weight: 600; }
.ttl a { text-decoration: none; }
.ttl a:hover { text-decoration: underline; }
.nowrap { white-space: nowrap; }
/* A matrix table sizes to its content and scrolls, rather than crushing the labels. */
table.matrix { table-layout: auto; }
table.matrix td:not(:first-child), table.matrix th:not(:first-child) { text-align: center; }
table.matrix td:first-child { white-space: nowrap; }

/* ------------------------------------------------------------------ pills --- */
/* A status is a small block of tinted colour with the word set in it, not an outlined
   capsule with a bullet in front. The dot and the border were each doing the job the tint
   already does, and three signals for one fact is what makes a dense table look busy.

   Squared off rather than fully round: a pill shape reads as a tag you could remove, and
   these are states rather than labels somebody attached. */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0; text-transform: none;
  padding: 3px 10px; border-radius: 999px; white-space: nowrap; border: 0;
}
/* Kept so callers passing a dot do not break, and made invisible: nothing renders it now.
   Removing the element from twelve call sites to delete one span is churn. */
.pill .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .85; }
.pill.ok { color: var(--ok); background: var(--ok-soft); }
.pill.warn { color: var(--warn); background: var(--warn-soft); }
.pill.danger { color: var(--danger); background: var(--danger-soft); }
.pill.muted { color: var(--card-muted); background: var(--surface-3); }

/* ---------------------------------------------------- a fact about a record ---

   The label/value pairs at the top of a detail view: STATUS, DUE DATE, OWNING UNIT. The
   label is small, spaced and quiet; the value carries the weight. Uppercase earns itself
   here in a way it would not on a heading — these are field names in a dense record, and
   the case difference is what stops them being read as content. */
.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: 18px 28px; }
.fact-k {
  display: block; margin: 0 0 5px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.fact-v { font-size: 14.5px; color: var(--ink); font-weight: 550; }
.fact-v.urgent { color: var(--danger); font-weight: 700; }

/* ------------------------------------------------------------ a quiet inset ---

   Long prose inside a record — a control's text, a risk statement, an instruction. On a
   white card a second white box needs a border to exist, and a border around four
   paragraphs reads as a warning. A tint reads as a quotation. */
.inset {
  background: var(--panel); border-radius: var(--radius);
  padding: 16px 18px; color: var(--panel-ink); line-height: 1.6; font-size: 14px;
}
.inset p:first-child { margin-top: 0; }
.inset p:last-child { margin-bottom: 0; }

/* ----------------------------------------------------------- people, stacked ---

   Overlapping initials with the overflow counted. Five avatars and a number is legible at
   a glance; five names on one line is not, and this is the only place in the product where
   several people attach to one row. */
.who { display: inline-flex; align-items: center; }
.who .av {
  width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; letter-spacing: -.01em;
  border: 2px solid var(--surface); margin-inline-start: -7px;
  background: var(--accent-soft); color: var(--ink);
}
.who .av:first-child { margin-inline-start: 0; }
.who .more {
  margin-inline-start: 7px; font-size: 13px; font-weight: 650; color: var(--muted);
}

/* ------------------------------------------------------ a foldable section ---

   The reference's core list idiom: a heading you can collapse, with what is inside it
   counted on the same line. Nesting one inside another is how a hundred rows stay
   navigable — a group, its members, then the rows.

   details/summary again, so it is keyboard-operable with no script, and so the open state
   survives the browser's own find-on-page. */
.fold { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface); margin-bottom: 12px; }
.fold > summary {
  display: flex; align-items: center; gap: 10px; cursor: pointer; list-style: none;
  padding: 13px 16px; border-radius: var(--radius);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:hover { background: var(--surface-2); }
.fold[open] > summary { border-bottom: 1px solid var(--rule); border-radius: var(--radius) var(--radius) 0 0; }
.fold > summary > svg:first-child { flex: none; color: var(--muted); transition: transform .16s ease; }
.fold[open] > summary > svg:first-child { transform: rotate(180deg); }
.fold-t { font-size: 15px; font-weight: 700; letter-spacing: -.012em; color: var(--ink); }
/* The count sits beside the name, not in a badge. It is context, not an alert. */
.fold-n { font-size: 13.5px; color: var(--muted); font-weight: 500; }
.fold-r { margin-inline-start: auto; display: flex; align-items: center; gap: 12px; }
.fold-body { padding: 4px 16px 14px; }
.fold-body > .fold:last-child { margin-bottom: 0; }
/* A group nested inside a group loses its own frame, or the page becomes boxes in boxes. */
.fold .fold { border: 0; border-radius: 0; margin-bottom: 0; border-top: 1px solid var(--rule); }
.fold .fold > summary { border-radius: 0; padding-inline-start: 8px; }
.fold .fold[open] > summary { border-bottom: 0; }
.fold .fold .fold-t { font-size: 14.5px; font-weight: 650; }

/* A control reference that is also the way into everything known about it. Underlined on
   hover only: in a column of a hundred references, a hundred underlines is a wall. */
.ref-link { text-decoration: none; color: inherit; border-radius: 4px; }
.ref-link:hover code { color: var(--accent-text); text-decoration: underline; }
.ref-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ------------------------------------------------------------------- asking ---

   The question box is the widest thing on the page and the only thing above the fold,
   because the page has exactly one job. Everything below it is the answer. */
.as-form { display: flex; gap: 10px; margin: 0 0 26px; max-width: 780px; }
.as-form input[type="search"] {
  flex: 1; padding: 13px 18px; font-size: 15.5px;
  border: 1px solid var(--field-border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--card-ink);
}
.as-form input[type="search"]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent);
}
.as-form .btn { border-radius: var(--radius-pill); padding-inline: 20px; }
.as-egs { display: flex; flex-wrap: wrap; gap: 8px; }

/* A destination is a card rather than a list item: there are rarely more than four, and
   the answer to "where do I go" should look like somewhere to go. */
.as-dests { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); gap: 10px; }
.as-dest {
  display: block; padding: 14px 16px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  transition: border-color .14s ease, background .14s ease;
}
.as-dest:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.as-dest-l { display: block; font-size: 15px; font-weight: 700; color: var(--ink); letter-spacing: -.012em; }
.as-dest-g { display: block; margin-top: 2px; font-size: 13px; color: var(--muted); }

/* A quotation is set as a quotation, with its source attached and linked. The product is
   citing itself, and the citation is the reason the answer can be trusted. */
.as-quote { margin: 0 0 12px; }
.as-quote blockquote {
  margin: 0; padding: 15px 18px; background: var(--panel);
  border-inline-start: 3px solid var(--accent-line); border-radius: 0 var(--radius) var(--radius) 0;
  font-size: 14.5px; line-height: 1.6; color: var(--panel-ink);
  --ink: var(--panel-ink); --muted: var(--panel-muted);
}
.as-quote figcaption { margin-top: 6px; font-size: 13.5px; color: var(--muted); }
.as-quote figcaption a { color: var(--muted); }
.as-quote figcaption a:hover { color: var(--accent-text); }

/* --------------------------------------------------------- working a sheet ---

   The toolbar over a long assessment: search on the left, the states as counted chips on
   the right. Both are links and a GET form rather than script, so a filtered view can be
   reloaded, bookmarked, and sent to the colleague who owns those rows.

   The counts sit ON the chips. Somebody wants to know how many are left to answer, and
   making them select the filter to find out is asking a question back. */
.exec-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 14px; margin-bottom: 16px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
}
.exec-bar input[type="search"] {
  flex: 1 1 260px; min-width: 180px; padding: 8px 12px;
  border: 1px solid var(--field-border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--ink); font-size: 14.5px;
}
.exec-filters { display: flex; gap: 6px; flex-wrap: wrap; margin-inline-start: auto; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: var(--radius-pill);
  border: 1px solid var(--rule); background: var(--surface);
  font-size: 13.5px; color: var(--card-ink-2); text-decoration: none; white-space: nowrap;
}
.chip:hover { background: var(--surface-2); color: var(--card-ink); }
.chip b { font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.chip.on b { color: var(--accent-ink); opacity: .85; }
.chips-row { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 10px; }

/* A section heading stays put while its rows scroll under it, so the domain you are in is
   never a question you have to scroll up to answer. */
.fold[open] > summary { position: sticky; top: var(--top-h); z-index: 5; background: var(--surface); }

/* ------------------------------------------------------------- the pipeline ---

   Where a record is in its own lifecycle, drawn as the steps it passes through.

   Distinct from the roadmap's journey line, which is a seven-level scale across a whole
   framework. This is three or four states belonging to ONE record — drafted, submitted,
   signed — and its job is to answer "what happens after I press this" before somebody
   presses it. A status word alone answers where you are and says nothing about what is
   next, which is most of why a workflow feels opaque.

   The line is drawn behind the markers and clipped by them, so it reads as one track
   rather than as segments that happen to line up. */
.pipe { display: flex; align-items: flex-start; gap: 0; margin: 0; padding: 0; list-style: none; }
.pipe li { flex: 1; position: relative; text-align: center; min-width: 0; }
.pipe li::before {
  content: ""; position: absolute; top: 13px; height: 2px; background: var(--rule-2);
  inset-inline-start: -50%; width: 100%;
}
.pipe li:first-child::before { display: none; }
/* A step that has been passed pulls the line behind it up to the accent, so the filled
   part of the track is exactly the part that happened. */
.pipe li.past::before, .pipe li.now::before { background: var(--accent); }
.pipe-dot {
  position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--card-rule); color: var(--card-muted);
  font-size: 13px; font-weight: 700;
}
.pipe li.past .pipe-dot { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.pipe li.now .pipe-dot {
  background: var(--surface); border-color: var(--accent); color: var(--accent-text);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.pipe-t { display: block; margin-top: 8px; font-size: 13.5px; font-weight: 600; color: var(--muted); }
.pipe li.now .pipe-t { color: var(--ink); font-weight: 700; }
.pipe li.past .pipe-t { color: var(--ink-2); }
.pipe-s { display: block; margin-top: 2px; font-size: 12.5px; color: var(--muted); line-height: 1.4; }
@media (max-width: 620px) {
  .pipe { flex-direction: column; gap: 14px; }
  .pipe li { text-align: start; display: flex; gap: 10px; align-items: flex-start; }
  .pipe li::before { display: none; }
  .pipe-t, .pipe-s { margin-top: 0; }
}

/* ------------------------------------------------------------- how far along ---

   A count against a total, drawn small enough to sit on a section heading. The bar is only
   ever shown with both numbers beside it — a bar with no denominator is decoration
   standing where evidence should be. */
.done { display: inline-flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--muted); }
.done-track { width: 76px; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.done-fill { display: block; height: 100%; border-radius: 3px; background: var(--accent); }
.done.complete .done-fill { background: var(--ok); }
.done.complete { color: var(--ok); font-weight: 650; }
.pill + .pill { margin-inline-start: 5px; }

/* ------------------------------------------------------------------ forms --- */
form.stack { display: flex; flex-direction: column; gap: 14px; max-width: 560px; }
form.stack.wide { max-width: 780px; }
.field-row { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); }
label { display: block; font-size: 13.5px; font-weight: 640; margin-bottom: 5px; }
label .hint { font-weight: 400; color: var(--muted); margin-left: .35em; }
.req { color: var(--danger-text); font-weight: 700; }
input, select, textarea {
  width: 100%; padding: 8px 11px; font: inherit; font-size: 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--field-border); border-radius: var(--radius-sm);
  transition: border-color .12s ease, box-shadow .12s ease;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); outline: none;
}
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft);
}
.err { color: var(--danger-text); font-size: 13px; margin-top: 5px; font-weight: 600; }
textarea { min-height: 88px; resize: vertical; }
input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--accent); }
fieldset { border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 13px 14px; margin: 0; }
legend { font-size: 13px; font-weight: 640; color: var(--ink-2); padding: 0 6px; }
.check { display: flex; align-items: flex-start; gap: 9px; margin-bottom: 8px; }
.check-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr)); gap: 2px 14px; }
.check-grid .check { margin-bottom: 4px; }
.check:last-child { margin-bottom: 0; }
.check label { margin: 0; font-weight: 400; font-size: 14.5px; line-height: 1.4; }
.check input { margin-top: 3px; }

button, .btn {
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; line-height: 20px;
  padding: 7px 14px; border-radius: var(--radius-sm); border: 1px solid var(--accent);
  background: var(--accent); color: var(--accent-ink); text-decoration: none;
  display: inline-flex; align-items: center; gap: 7px; justify-content: center;
  white-space: nowrap; transition: background .12s ease, border-color .12s ease, color .12s ease;
}
button:hover, .btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }
button.ghost, .btn.ghost { background: transparent; color: var(--accent-text); }
button.ghost:hover, .btn.ghost:hover { background: var(--accent-soft); color: var(--accent-text); border-color: var(--accent); }
button.subtle, .btn.subtle { background: var(--surface); color: var(--card-ink-2); border-color: var(--card-rule); }
button.subtle:hover, .btn.subtle:hover { background: var(--surface-2); color: var(--card-ink); border-color: var(--card-rule); }
button.danger, .btn.danger { background: var(--danger); border-color: var(--danger); color: var(--danger-ink); }
button.danger:hover, .btn.danger:hover { background: var(--danger); border-color: var(--danger); filter: brightness(1.1); color: var(--danger-ink); }
button.sm, .btn.sm { padding: 4px 10px; font-size: 13px; line-height: 20px; }
button[disabled] { opacity: .5; cursor: not-allowed; }
.btn-row { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }

/* ---------------------------------------------------- risk, added 2026-08-31 --
   The 5x5 matrix and the two-number score cell. Colour carries the band and the
   NUMBER carries the meaning: every cell prints its count and every score prints its
   value, so the matrix survives greyscale, a colour vision deficiency, and the
   printed board pack it will end up inside. A matrix that encodes only in colour is
   a picture of a risk profile rather than a statement of one. */
td.mx {
  text-align: center; font-variant-numeric: tabular-nums; font-weight: 660;
  padding: 10px 8px; border-inline-start: 1px solid var(--rule);
}
td.mx.ok { background: var(--ok-soft); color: var(--ok); }
td.mx.muted { background: var(--surface-2); color: var(--card-ink-2); }
td.mx.warn { background: var(--warn-soft); color: var(--warn); }
td.mx.danger { background: var(--danger-soft); color: var(--danger); }
/* An empty cell keeps its band colour and drops only its weight.
   It is NOT dimmed. A 50% opacity was the first attempt, and it was measured rather
   than assumed: compositing the band colour against its own soft background at half
   alpha yields 1.98:1 to 2.92:1 across the four bands in both themes, against a 4.5:1
   floor for 15px text. Every one of them failed. Weight alone is enough to say "this
   cell is empty" and costs no contrast at all.
   Recolouring an empty critical cell to green was the other rejected option: it would
   claim both that there are no critical risks and that the cell itself is safe, which
   are different statements and only one of them is true. */
td.mx.zero { font-weight: 500; }
.mx-n { font-size: 15px; }

.score-pair { display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
.score-pair .note { font-size: 12.5px; }

/* The "band scale" strip under a matrix. */
.band-scale { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; }

/* A read-only banner shown where a role can see a module but not act in it. It is a
   sentence, not a disabled button: a greyed-out control tells somebody they cannot do
   a thing and never why, which is the single most common complaint about role-based
   access in a compliance tool. */
.readonly-note {
  border: 1px dashed var(--rule-2); border-radius: var(--radius-sm);
  padding: 11px 13px; color: var(--card-ink-2); font-size: 14px; background: var(--surface-2);
  --ink: var(--card-ink); --rule-2: var(--card-rule);
}
.readonly-note strong { color: var(--card-ink); }

/* An explanatory note that is neither a warning nor an error: how something works, or
   what a setting will cost. Distinguished from a flash by having no dismissal and no
   urgency — it is part of the page rather than the result of an action. */
.callout {
  border: 1px solid var(--card-rule); border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 13px 15px; background: var(--surface-2); font-size: 14.5px;
  /* Paint and ink in one rule. A callout is a light panel wherever it lands, and setting
     its background without its ink is what made this one white-on-white. */
  --ink: var(--card-ink); --ink-2: var(--card-ink-2); --muted: var(--card-muted);
  --rule: var(--card-rule); --accent-text: var(--accent-text-on-light);
  color: var(--card-ink-2);
}
.callout strong { color: var(--ink); }

/* A hairline divider, for separating two ways of doing the same thing. */
.rule { height: 1px; background: var(--rule); border: 0; }
.callout p { margin: 0; }
.callout code { font-size: 13.5px; }

/* ---- the first-run guide -------------------------------------------------------
   A numbered checklist that says what to do next, in the order that makes the next
   step possible. The states are deliberately three, not two: done, available now, and
   waiting on something earlier — because "why is this greyed out" is the question a
   two-state checklist cannot answer. */
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.step {
  display: grid; grid-template-columns: 34px 1fr auto; gap: 14px; align-items: start;
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 14px 16px; background: var(--surface);
}
.step.is-done { background: var(--surface-2); border-color: var(--rule-2); }
.step.is-next { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent-soft); }
.step .num {
  width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  font-size: 14px; font-weight: 650; font-variant-numeric: tabular-nums;
  background: var(--surface-2); color: var(--card-muted); border: 1px solid var(--card-rule);
}
.step.is-done .num { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }
.step.is-next .num { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.step h3 { margin: 0 0 3px; font-size: 15px; }
.step .why { margin: 0; font-size: 14px; color: var(--muted); }
.step .act { align-self: center; }
/* On a narrow screen the action drops under the text rather than being crushed into a
   40px column. The number stays beside the title so the sequence is still readable. */
@media (max-width: 640px) {
  .step { grid-template-columns: 34px 1fr; }
  .step .act { grid-column: 2; align-self: start; margin-top: 10px; }
}
.step.is-done h3 { color: var(--ink-2); }

/* The progress bar on the guide. A bar rather than a percentage, because "4 of 9" is
   what somebody wants to know and a percentage makes them do arithmetic. */
.progress { height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; border: 1px solid var(--rule-2); }
.progress > i { display: block; height: 100%; background: var(--accent); }

/* The role card at the top of the guide. */
.role-card { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
.role-card .rc-main { flex: 1 1 320px; min-width: 0; }

/* A checkbox whose input sits INSIDE its label.
   Used where the same control repeats once per table row: the check() component
   derives an id from the field name, and a repeated id across thirty rows breaks the
   label-to-input association for a screen reader on every one of them. Wrapping needs
   no id at all. */
label.check-inline {
  display: flex; align-items: flex-start; gap: 7px; font-weight: 400;
  font-size: 13.5px; margin-bottom: 4px; cursor: pointer;
}
label.check-inline input { margin-top: 2px; flex: none; }

/* ------------------------------------------------ the public trust centre --
   Served without a session and without the console chrome, so it needs its own
   container. It reuses the same tokens deliberately: a trust page that looks like a
   different product than the console behind it reads as a template somebody bought. */
body.public { background: var(--bg); }
.public-page {
  max-width: 900px; margin: 0 auto; padding: 48px 24px 80px;
}
.public-page > .page-head { margin-bottom: 8px; }
.public-page footer {
  margin-top: 56px; padding-top: 18px; border-top: 1px solid var(--rule);
  font-size: 14px; color: var(--muted); line-height: 1.6;
}
@media (max-width: 640px) { .public-page { padding: 28px 16px 56px; } }

/* --------------------------------------------------------------- printing --
   A compliance console's output ends up in a board pack and an audit file, and until
   now printing one produced the navigation, the user menu and a wall of link
   underlines. This is the difference between a screen somebody photographs and a
   document somebody files.

   The rules are ordinary but the reasoning is not:
     - Chrome is removed, not hidden behind a "print view" nobody finds.
     - Backgrounds are dropped and pills keep a BORDER, because a status encoded only
       in a background colour vanishes on a monochrome printer — the same rule the
       risk matrix follows on screen.
     - A URL is printed after every external link, since a reader holding paper cannot
       click one.
     - Sections and table rows are kept off page breaks, because a finding split across
       two pages is a finding somebody misses. */
@media print {
  @page { margin: 14mm; }

  .nav, .tbar, .btn, .btn-row, form, .toolbar, .flash, .readonly-note { display: none !important; }

  body { background: #fff !important; color: #000 !important; font-size: 11pt; }
  .shell, .main, .page { display: block !important; max-width: none !important; padding: 0 !important; }

  a { color: #000 !important; text-decoration: none !important; }
  /* Only external links get their address printed. An internal one is meaningless on
     paper and would litter every table with the same host. */
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #444; }

  .card, .section, .scroll, .chart-card {
    border: 1px solid #999 !important; box-shadow: none !important;
    background: #fff !important; break-inside: avoid;
  }
  .section, h2, h3 { break-after: avoid; }
  tr, .kpi, .metric { break-inside: avoid; }
  thead { display: table-header-group; }

  .pill, .chip, .tag {
    background: #fff !important; color: #000 !important; border: 1px solid #444 !important;
  }
  .pill .dot { outline: 1px solid #000; }

  /* The matrix keeps its numbers and loses its colour, which is exactly how it was
     designed to survive greyscale in the first place. */
  td.mx { background: #fff !important; color: #000 !important; border: 1px solid #999 !important; }
  td.mx.zero { color: #666 !important; }

  .wide-scroll, .scroll { overflow: visible !important; }
  table { min-width: 0 !important; font-size: 9.5pt; }
}

/* -------------------------------------------------------------- density --
   A compliance console is a table product, and how many rows fit on a screen is a real
   preference rather than a cosmetic one: an auditor working through four hundred
   controls wants density, and somebody reading one finding wants air. Stored in a
   cookie and applied as a class on the root, so it costs no script. */
:root[data-density="compact"] {
  --row-pad-y: 6px;
  --row-pad-x: 11px;
  --card-pad: 12px 14px;
}
/* Two rules beyond the tokens, and no more. Everything else about the page stays
   identical, so a reader switching density is not re-learning the interface. */
:root[data-density="compact"] .section { margin-top: 22px; }
:root[data-density="compact"] .kpi-row { gap: 8px; }

/* --------------------------------------------------------------- toolbars --- */
.toolbar {
  display: flex; gap: 9px; align-items: center; flex-wrap: wrap;
  padding: 11px 13px; margin-bottom: 13px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.toolbar input, .toolbar select { width: auto; height: 33px; padding: 0 10px; font-size: 14px; }
.toolbar input[type="search"] { min-width: 210px; flex: 1 1 210px; }
.toolbar .spacer { flex: 1 1 auto; }
.toolbar .count { font-size: 13.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- feedback --- */
.flash {
  padding: 12px 15px; border-radius: var(--radius); margin-bottom: 18px; font-size: 14.5px;
  border: 1px solid;
}
.flash.ok { background: var(--ok-soft); border-color: var(--accent-line); color: var(--ok); }
.flash.warn { background: var(--warn-soft); border-color: var(--rule-2); color: var(--warn); }
.flash.error { background: var(--danger-soft); border-color: var(--rule-2); color: var(--danger); }
.flash strong { font-weight: 700; }
/* After an inline action the server's flash is shown as a toast and then leaves. */
.flash.toast { position: fixed; inset-inline-end: 18px; bottom: 18px; z-index: 60; max-width: 420px; box-shadow: var(--shadow-md); margin: 0; transition: opacity .4s, transform .4s; }
.flash.toast.gone { opacity: 0; transform: translateY(8px); pointer-events: none; }
form.busy button { opacity: .6; cursor: progress; }
.note { font-size: 13.5px; color: var(--muted); }

/* ---- four classes the markup wrote and the sheet never defined -------------------------- */

/*
 * WITHDRAWN, SUPERSEDED, RETIRED.
 *
 * A knowledge article that has been superseded, an audit-universe entry no longer in
 * scope, a row that has been stood down. This had NO RULE, so a superseded record
 * rendered identically to a current one - in a system whose whole job is to say what is
 * in force. That is a correctness fault wearing a styling fault's clothes.
 *
 * Muted ink rather than opacity. Opacity fades the row's links and status pills with it
 * and takes their contrast down below four and a half to one; this keeps every readable
 * thing readable and only says the row is not the current one.
 */
.dim > td, .dim > th { color: var(--muted); }
/* The links inside a dimmed row are handled at the end of the sheet: see the note there
   about the rule that governs every link on a card. */

/*
 * THE SUBJECT OF THE ROW.
 *
 * The one link in a row that says what the row IS, as against the half-dozen that say
 * where else you could go from it. It read as an ordinary link everywhere, so a table of
 * twelve rows had no entry point - the eye had nothing to run down.
 */
/* .what is handled at the end of the sheet, for the same reason as .dim. */

/* A list that carries structure rather than enumeration. The connectors catalogue was
   rendering with browser bullets and browser indentation. */
.plain { list-style: none; margin: 0; padding: 0; }

/* A filter in a toolbar: its label above its control, tight enough to sit in the bar. */
.tb-f {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 12px; color: var(--muted-2);
}
.tb-f select, .tb-f input { margin: 0; }

/* Long recorded text - a model's answer, a pasted policy - kept inside its column instead
   of forcing the page sideways. The rule lived in a style attribute on each of the four
   places that needed it. */
.wrap { white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }

.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.between { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.empty { padding: 42px 24px; text-align: center; color: var(--muted); font-size: 14.5px; }
.empty .et { display: block; color: var(--ink); font-weight: 640; font-size: 15px; margin-bottom: 5px; }
.empty .btn { margin-top: 14px; }
code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 13.5px;
  background: var(--surface-2); padding: 1.5px 5px; border-radius: 4px; color: var(--card-ink-2);
}
ul.clean { margin: 0; padding-inline-start: 18px; }
ul.clean li { margin-bottom: 5px; }
dl.kv { display: grid; grid-template-columns: max-content 1fr; gap: 8px 20px; margin: 0; font-size: 14.5px; }
dl.kv dt { color: var(--muted); font-size: 13.5px; }
dl.kv dd { margin: 0; }

/* A vertical record of what happened to one item, in order. */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li { position: relative; padding-inline-start: 22px; padding-bottom: 15px; }
.timeline li::before {
  content: ""; position: absolute; inset-inline-start: 2px; top: 5px;
  width: 9px; height: 9px; border-radius: 50%; background: var(--accent); opacity: .55;
}
.timeline li::after {
  content: ""; position: absolute; inset-inline-start: 6px; top: 16px; bottom: 0;
  width: 1px; background: var(--rule);
}
.timeline li:last-child { padding-bottom: 0; }
.timeline li:last-child::after { display: none; }
.timeline .tw { font-size: 13px; color: var(--muted); }

/* ------------------------------------------------------------------- auth --- */
.auth-wrap { display: grid; place-items: center; min-height: 100vh; padding: 24px; }
/* Edge to edge on a real screen. A 920px card centred in a 1440px window leaves the
   product looking like a form somebody embedded, which is the opposite of the first
   impression this screen exists to make. */
/* Descendant-qualified so these beat the max-width: 920px rule further down the file.
   Equal specificity loses to whichever comes last, and that one does.
   (No backticks anywhere in this file: it is one long template literal.) */
@media (min-width: 960px) {
  /* .auth-wrap IS the main element, so it inherits main's max-width: 1280px and stops
     short of the window edge on anything wider. Cleared here rather than by weakening
     the rule that every other page depends on. */
  .auth-wrap:has(.auth-card.wide) { padding: 0; place-items: stretch; max-width: none; }
  .auth-wrap:has(.auth-card.wide) .auth-card.wide {
    max-width: none; width: 100%; border: 0; border-radius: 0; box-shadow: none;
  }
  .auth-wrap:has(.auth-card.wide) .auth-grid { min-height: 100vh; grid-template-columns: minmax(400px, 38%) 1fr; }
  .auth-wrap:has(.auth-card.wide) .auth-form {
    display: flex; flex-direction: column; justify-content: center; padding: 48px 56px;
  }
  .auth-wrap:has(.auth-card.wide) .auth-pitch { padding: 56px 64px; }
  .auth-wrap:has(.auth-card.wide) .auth-pitch h2 { font-size: 36px; }
}
.auth-card {
  width: 100%; max-width: 400px; background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 30px 30px 26px; box-shadow: var(--shadow-md);
}
.auth-card .brand { padding: 0 0 20px; }
.auth-foot { margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--rule); font-size: 13px; color: var(--muted); text-align: center; }

/* The two-panel sign-in.
   Form on one side, what the product actually claims on the other. It collapses to a
   single column below 860px and the pitch panel goes last, so a phone shows the form
   first — the panel is context, and context should never be the thing somebody has to
   scroll past to sign in. */
.auth-card.wide { max-width: 920px; padding: 0; overflow: hidden; }
.auth-card { box-shadow: var(--shadow-md), 0 0 0 1px var(--rule); }
.auth-grid { display: grid; grid-template-columns: 1fr; }
@media (min-width: 860px) { .auth-grid { grid-template-columns: 1fr 1fr; } }
.auth-form { padding: 30px 30px 26px; }
/* The dark surface the console already owns, reused rather than a new colour invented
   for this screen. It is the only place besides "needs you now" that gets it, and both
   are places where there is no data for it to fight. */
/* The half of the sign-in screen that makes the argument.
   It used --panel, which is within a couple of steps of the page ground now that the
   ground is tinted — so the split between the form and the pitch stopped reading and the
   screen became one pale rectangle. A deeper tint of the same blue separates them without
   introducing a colour that appears nowhere else. */
.auth-pitch {
  padding: 38px 40px 40px; background: var(--deep); color: var(--ink);
  display: flex; flex-direction: column; justify-content: center; gap: 22px;

  /* The deep ground owns its inks, the same way the dashboard's layer does. A tint of the
     accent was doing the separating before, which made the two halves of the card two
     shades of the same thing rather than surface and object. */
  --ink: #eaf1fb;
  --ink-2: #c8d6ee;
  --muted: #93a6c4;
  --panel-ink: #eaf1fb;
  --panel-muted: #93a6c4;
  --panel-line: rgba(255, 255, 255, .14);
  --rule: rgba(255, 255, 255, .14);
  --rule-2: rgba(255, 255, 255, .26);
  --accent-text: #5fb0ff;
}
/* The figure, at emblem size: strata only, no depth ruler and no register. */
.auth-pitch .cs-rig {
  height: 272px; border: 0; background: none; margin: -8px 0 -6px;
}
.auth-pitch .cs-stack { left: 50%; width: min(230px, 78%); }
.auth-pitch .cs-key { display: none; }
@media (max-width: 860px) { .auth-pitch .cs-rig { display: none; } }
@media (prefers-reduced-motion: reduce) { .auth-pitch .cs-slab { animation: none; } }
.auth-pitch h2 { letter-spacing: -.03em; font-weight: 800; }
/* The form side stays pure white, so the two halves are surface and tint rather than two
   tints a hair apart. */
.auth-form { background: var(--surface); }
@media (min-width: 860px) { .auth-pitch { border-top: 0; } }
.auth-pitch h2 {
  margin: 0; font-family: var(--display); font-size: 30px; line-height: 1.14;
  letter-spacing: -.012em; font-weight: 600; color: var(--panel-ink); max-width: 18ch;
}
.auth-pitch ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px; }
.auth-pitch li { font-size: 14.5px; color: var(--panel-muted); line-height: 1.6; }
/* The claim is the line somebody reads; the sentence under it is the evidence. Giving
   them the same weight is why a panel like this reads as a wall of grey. */
.auth-pitch li b {
  display: block; color: var(--panel-ink); font-size: 14px; font-weight: 620;
  letter-spacing: -.012em; margin-bottom: 4px;
}
.auth-pitch .fine {
  font-size: 12.5px; color: var(--panel-muted); margin: 0;
  padding-top: 18px; border-top: 1px solid var(--panel-line);
}

/* ---------------------------------------------------------------- charts --- */
.chart-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(310px, 100%), 1fr)); margin-bottom: 14px; }

/* ---- the dashboard as two columns ------------------------------------------------ */

/* ---- the inversion: light panels carry their own ink ----------------------------- */

/* A link on a light card is the ink underlined. Lime is a fill colour; as text on white it
   is unreadable, and it is reserved for what you press. */
.card a:not(.btn):not(.pct), .chart-card a:not(.btn), table a:not(.btn) {
  color: var(--card-ink); text-decoration: none; text-underline-offset: 2px;
}
.card a:not(.btn):hover, .chart-card a:not(.btn):hover, table a:not(.btn):hover {
  text-decoration-color: currentColor;
}

/* Text sitting directly on the near-black ground stays light. */
.page-head h1 { color: var(--ink); }
.page-head .sub, .section > header .sub, .crumbs, .crumbs a { color: var(--muted); }
.section > header h2 { color: var(--ink); }

/* A figure cell is a link but the whole cell is the target, so the underline would strike
   through a display numeral. It moves to the label on hover. */

/*
 * The orb: near-black with a lime rim, as the reference draws its circular hero.
 *
 * The previous version derived its fill from --surface-3, which is now a LIGHT value, so
 * it was rendering as a pale disc on a light cell. It carries its own colours.
 */
.orb-n { color: var(--accent-text); }

/* ---- dot matrix ------------------------------------------------------------------ */

/*
 * One dot per control, grouped by domain.
 *
 * The dots are deliberately small. At 9px a hundred and eight of them fit in a block a
 * reader takes in at once, which is the entire point: the SHAPE of the programme becomes
 * visible, and a domain nobody has started is an obvious grey block rather than a row in a
 * table somebody has to scroll to.
 */
.dm { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(min(184px, 100%), 1fr)); }
.dm-h { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
.dm-l { font-size: 13px; font-weight: 650; color: var(--ink); }
.dm-dots { display: flex; flex-wrap: wrap; gap: 4px; }
.dm-d { width: 9px; height: 9px; border-radius: 2px; display: block; background: var(--rule-2); }
.dm-d.good { background: var(--accent); }
.dm-d.warning { background: var(--warn); }
.dm-d.critical { background: var(--danger); }
.dm-d.neutral { background: var(--rule-2); }
a.dm-d:hover { outline: 2px solid var(--ink); outline-offset: 1px; }

/* ---- before and after ------------------------------------------------------------ */

.ba-key { display: flex; gap: 14px; font-size: 12.5px; color: var(--muted); margin: 0 0 4px; }
.ba-key span { display: inline-flex; align-items: center; gap: 5px; }
.ba-s { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.ba-s.before { background: var(--chart-neutral); }
.ba-s.after { background: var(--accent); }
figure.ba { margin: 0; }

/* ---- scored rows ----------------------------------------------------------------- */

/*
 * Share and response in the same row.
 *
 * A bar chart answers "how much" and then needs a paragraph underneath saying what to do.
 * This carries both, which is why the reference uses it for root-cause analysis.
 */
.scored table { width: 100%; border-collapse: collapse; }
.scored th {
  text-align: left; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); font-weight: 650; padding: 0 10px 8px 0;
}
.scored td { padding: 10px 10px 10px 0; border-top: 1px solid var(--rule); vertical-align: middle; }
.sc-k {
  font-size: 12px; color: var(--card-muted); white-space: nowrap;
  background: var(--surface-3); border-radius: 999px; padding: 3px 10px !important;
}
.sc-n { font-size: 14.5px; font-weight: 600; }
.sc-p { width: 1%; }
.sc-a { font-size: 13.5px; color: var(--muted); }
@media (max-width: 620px) { .sc-a, .scored th:last-child { display: none; } }

/* ---- the deep band --------------------------------------------------------------------- */

/*
 * A section set deeper than the page around it, carrying the same motif inverted.
 *
 * Everything inside reads the tokens it always reads; the band simply answers them
 * differently. That is why the flow map, the pills and the cards need no dark variants of
 * themselves - inside the band --accent is the lit oxide and the one contrasting surface
 * flips from a dark pill to a light one.
 *
 * Every ink below is measured against --deep before it is written; see the script in the
 * scratchpad, which refuses to emit a value under 4.5:1.
 */
.section.deep {
  background: var(--deep);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 34px 34px 38px;
  margin: 40px 0;

  --ink: #eaf1fb;
  --ink-2: #c8d6ee;
  --muted: #93a6c4;
  --card-ink: #eaf1fb;
  --card-ink-2: #c8d6ee;
  --card-muted: #93a6c4;
  --rule: rgba(255, 255, 255, .14);
  --rule-2: rgba(255, 255, 255, .26);
  --card-rule: rgba(255, 255, 255, .14);
  --surface: rgba(255, 255, 255, .05);
  --surface-2: rgba(255, 255, 255, .09);
  --surface-3: rgba(255, 255, 255, .07);
  --accent: #0066cc;
  --accent-ink: #ffffff;
  --accent-text: #5fb0ff;
  --accent-line: rgba(95, 176, 255, .42);
  --accent-soft: rgba(95, 176, 255, .14);
  --ok-text: #5cc561;
  --warn-text: #e0a340;
  --danger-text: #f4826f;
  /* The contrasting surface flips: on the page it is a dark pill, in the band a light one. */
  --focus-bg: #eaf1fb;
  --focus-ink: #0b2a4a;
  --focus-muted: #3f5d8d;
  --focus-rule: rgba(11, 42, 74, .20);
  --focus-accent: #0066cc;
  color: var(--ink);
}
.section.deep > header h2 { color: var(--ink); }
.section.deep > header .sub { color: var(--muted); }
.section.deep .chart-card,
.section.deep .card { background: var(--surface); border-color: var(--rule); box-shadow: none; }

@media (max-width: 620px) { .section.deep { padding: 22px 18px 26px; border-radius: var(--radius-sm); } }

/* ---- a card riding on the route ------------------------------------------------------ */

/*
 * The reference hangs small cards off its connector lines - a heading and two or three
 * ticked lines - so the route carries what has to be DONE at a step, not only its name.
 */
.fm-card {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--rule);
  display: flex; flex-direction: column; gap: 4px; width: 100%;
}
.fm-card li {
  display: flex; align-items: flex-start; gap: 6px;
  font-size: 12.5px; line-height: 1.35; color: var(--muted); text-align: start;
}
.fm-card li::before {
  content: ""; flex: none; width: 11px; height: 11px; margin-top: 2px; border-radius: 50%;
  border: 1.5px solid var(--rule-2);
}
.fm-card li.on { color: var(--ink); }
.fm-card li.on::before { background: var(--accent); border-color: var(--accent); }
.fm-node.has-card { white-space: normal; min-width: 156px; max-width: 190px; align-items: stretch; }
/*
 * Seven nodes each 170px wide need about 1200px of route before they touch. Below that the
 * cards collide with one another, which is worse than not showing them - so the route
 * keeps the names and the states, and drops the detail.
 */
@media (max-width: 1180px) {
  .fm-card { display: none; }
  .fm-node.has-card { min-width: 0; max-width: none; white-space: nowrap; }
}
.fm-node.has-card b, .fm-node.has-card > span { text-align: center; }

/* ---- the flow map ------------------------------------------------------------------- */

/*
 * A route with the levels sitting on it, rather than seven boxes in a row.
 *
 * The nodes are HTML over an SVG line layer. Text in SVG cannot wrap, is awkward to
 * select, and takes fill rather than color - the exact omission that hid every chart
 * label in this product from the contrast sweep for three rounds.
 */
.fm { margin: 0; padding: 112px 0 76px; }
.fm-plot { position: relative; width: 100%; aspect-ratio: 1000 / 260; min-height: 190px; }
.fm-plot > svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

/* The travelled road draws itself in. --fm-rest is 100 minus the percentage reached, so
   the reveal stops exactly on the node the programme has got to. */
.fm-run {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: fm-travel 1.5s cubic-bezier(.25, .8, .3, 1) .1s forwards;
}
@keyframes fm-travel { to { stroke-dashoffset: var(--fm-rest, 100); } }

.fm-dot {
  transform-box: fill-box; transform-origin: center;
  animation: fm-land .38s cubic-bezier(.2, 1.5, .4, 1) both;
  animation-delay: calc(.3s + var(--fm-i, 0) * .16s);
}
@keyframes fm-land { from { transform: scale(0); } }
/* The node you are on keeps a soft pulse, because it is the only one asking for anything. */
.fm-dot.fm-here { animation: fm-land .38s cubic-bezier(.2, 1.5, .4, 1) both, fm-pulse 3.4s ease-in-out 1.6s infinite; }
@keyframes fm-pulse { 0%, 100% { r: 9; } 50% { r: 11; } }

.fm-node {
  position: absolute; transform: translate(-50%, 0);
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 7px 13px; border-radius: var(--radius-pill);
  background: var(--surface); border: 1px solid var(--rule);
  text-decoration: none; white-space: nowrap; text-align: center;
  box-shadow: 0 1px 2px rgba(12, 47, 39, .06);
  animation: fm-in .42s cubic-bezier(.2, .8, .3, 1) both;
  animation-delay: calc(.36s + var(--fm-i, 0) * .16s);
}
@keyframes fm-in { from { opacity: 0; transform: translate(-50%, 6px); } }
.fm-node.above { margin-top: 22px; }
.fm-node.below { transform: translate(-50%, -100%); margin-top: -22px; }
@keyframes fm-in-below { from { opacity: 0; transform: translate(-50%, calc(-100% + 6px)); } }
.fm-node.below { animation-name: fm-in-below; }

.fm-node b { font-size: 14px; font-weight: 620; letter-spacing: -.005em; color: var(--ink); }
.fm-node span { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Done is quiet and settled; here is the one dark pill on the page, which is the
   reference's own device and the reason the eye lands there first. */
.fm-node.fm-done { border-color: var(--accent-line); }
.fm-node.fm-here {
  background: var(--focus-bg); border-color: var(--focus-bg);
  box-shadow: 0 6px 18px -8px rgba(12, 47, 39, .5);
}
.fm-node.fm-here b { color: var(--focus-ink); }
.fm-node.fm-here span { color: var(--focus-muted); }
/* Everything inside the pill answers to the pill, not to the ground behind it. */
.fm-node.fm-here {
  --ink: var(--focus-ink);
  --ink-2: var(--focus-ink);
  --muted: var(--focus-muted);
  --rule: var(--focus-rule);
  --rule-2: var(--focus-rule);
  --accent: var(--focus-accent);
}
.fm-node.fm-ahead { border-style: dashed; }
.fm-node.fm-ahead b { color: var(--muted); font-weight: 560; }

.card a.fm-node, .chart-card a.fm-node { text-decoration: none; }
a.fm-node:hover { border-color: var(--accent); }
a.fm-node:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@media (max-width: 860px) {
  /* The curve stops being readable long before the labels do, so below a laptop the
     route becomes a list and keeps the states. */
  .fm { padding: 0; }
  .fm-plot { aspect-ratio: auto; min-height: 0; }
  .fm-plot > svg { display: none; }
  .fm-node {
    position: static; transform: none; margin: 0 0 6px; width: 100%;
    flex-direction: row; justify-content: space-between; gap: 10px; animation: none;
  }
  .fm-node.below { transform: none; margin: 0 0 6px; }
}
@media (prefers-reduced-motion: reduce) {
  .fm-run { animation: none; stroke-dashoffset: var(--fm-rest, 100); }
  .fm-dot, .fm-node { animation: none; }
}

/* ---- a 3D scene panel ---------------------------------------------------------------- */

/*
 * Decoration that knows it is decoration.
 *
 * The runtime and one scene are 841KB gzipped, so the canvas is fetched only when the
 * panel is near the viewport, and the section says what it is about in HTML above it. A
 * scene that never loads, a browser without WebGL and a screen reader all get the same
 * thing: the words. Until it is live the panel draws a survey grid, so it is a considered
 * surface rather than an empty box waiting for something.
 */
/*
 * The panel does not exist until something can draw in it.
 *
 * Without script it was a 420px bordered grid with a caption - a decorative hole where a
 * reader with no JavaScript, or a browser with no WebGL, gets nothing but the outline of
 * something they cannot see. The legend beside it already says everything true, so the
 * canvas is opt-in: the script arms it when it wires the mount, and nothing else ever
 * reveals it.
 */
.s3d { display: none; }
.s3d.armed {
  display: block;
  position: relative; border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden; background: var(--surface-3); min-height: 240px;
  height: var(--s3d-h, 420px);
}
.s3d-canvas { position: absolute; inset: 0; width: 100% !important; height: 100% !important; display: block; }
.s3d::before {
  content: ""; position: absolute; inset: 0; opacity: 1; transition: opacity .6s ease;
  background:
    repeating-linear-gradient(0deg, transparent 0 39px, var(--rule) 39px 40px),
    repeating-linear-gradient(90deg, transparent 0 39px, var(--rule) 39px 40px);
}
.s3d.is-live::before { opacity: 0; }
.s3d-cap {
  position: absolute; left: 16px; bottom: 13px; right: 16px; z-index: 2; margin: 0;
  font-size: 12.5px; letter-spacing: .04em; color: var(--muted);
}
/*
 * Once the scene is drawing, the caption is no longer on the page - it is on the scene's
 * own dark ground, and --muted is the page's value. It measured dark-on-dark. Light ink
 * plus a scrim, because a caption over a moving image cannot rely on what is behind it
 * staying still.
 */
.s3d.is-live .s3d-cap {
  color: #eaf1fb;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9), 0 0 14px rgba(0, 0, 0, .7);
}
/* A scene that failed says so quietly rather than leaving a considered-looking void. */
.s3d.is-failed .s3d-cap::after { content: " (scene unavailable)"; color: var(--muted); }

.s3d-wrap { display: grid; gap: 14px; }
@media (min-width: 1000px) {
  .s3d-wrap:has(.s3d.armed) { grid-template-columns: 1fr 280px; align-items: start; }
}

/*
 * The obligations are named here rather than in the mesh.
 *
 * Text in a 3D scene cannot be translated into Arabic, cannot be selected, and is silent
 * to a screen reader. This list is all three, and it is also the fallback: when the scene
 * does not load, the legend alone still says everything true.
 */
.s3d-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.s3d-legend li {
  display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: baseline;
  padding: 9px 12px; background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
}
.s3d-legend li.off { opacity: .58; }
.s3d-legend li { cursor: pointer; transition: border-color .16s ease, background .16s ease; }
.s3d-legend li:hover { border-color: var(--accent-line); }
/* Selected from the scene or from the list: the two halves of the page stay in step. */
.s3d-legend li[data-picked="1"] {
  border-color: var(--accent); box-shadow: inset 2px 0 0 var(--accent); opacity: 1;
}
.s3d-n { font-size: 11px; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
.s3d-l { font-size: 14px; font-weight: 600; }
.s3d-s { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.s3d-legend li.on .s3d-n { color: var(--accent-text); }

/* ---- the immersive roadmap ------------------------------------------------------------ */

/*
 * One page where the route runs behind everything.
 *
 * The scene is fixed and the content scrolls over it, so the journey stays present while
 * the reader works down the page rather than being a picture they scroll past once.
 */
/*
 * THE GROUND BELONGS TO THE PAGE, NOT TO THE SCENE.
 *
 * The first version let the fixed canvas supply the dark ground. With script disabled the
 * canvas is never armed, so the page kept its dark ink and lost its dark ground: the
 * heading measured 1.05 against the paper body. The scene is an ornament and an ornament
 * cannot be load-bearing for legibility.
 *
 * Breaking out of main's padding rather than sitting inside it, so the dark runs to the
 * edges the way an immersive page has to.
 */
.rm-imm {
  position: relative;
  background: var(--deep);
  /* Cancels main's padding exactly, whatever it currently is. */
  margin: -28px calc(-1 * var(--main-pad, 32px)) -40px;
  padding: 28px var(--main-pad, 32px) 40px;
}
@media (max-width: 620px) { .rm-imm { margin-block: -20px -72px; padding-block: 20px 72px; } }

.rm-imm > .s3d-wrap {
  position: fixed; z-index: 0;
  top: var(--top-h, 60px); bottom: 0;
  inset-inline-start: var(--side-w); inset-inline-end: 0;
  display: block; pointer-events: none; gap: 0;
}
@media (max-width: 1000px) {
  /* The sidebar is a strip above the work at this width, not a column beside it. */
  .rm-imm > .s3d-wrap { inset-inline-start: 0; }
}
.rm-imm > .s3d-wrap .s3d.armed {
  height: 100%; border: 0; border-radius: 0; min-height: 0; background: var(--deep);
}
/* Darkened towards the bottom, where the content is densest and needs the most support. */
.rm-imm > .s3d-wrap::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(10, 8, 7, .05) 0%, rgba(10, 8, 7, .28) 52%, rgba(10, 8, 7, .58) 100%);
}
.rm-imm-body { position: relative; z-index: 1; padding-bottom: 40px; }

/*
 * THE PAGE RESTATES THE PALETTE FOR EVERYTHING INSIDE IT.
 *
 * The console is light and this page is dark, and printing the light theme's ink over a
 * dark scene measures about 1.2:1 - the exact fault this codebase spent a week removing.
 * Restating the tokens here means every component inside inverts with the page and none
 * of them needs to know it is on the roadmap.
 */
.rm-imm {
  --ink: #f2f6fb;
  --ink-2: #c8d6ee;
  --muted: #93a6c4;
  --card-ink: #f2f6fb;
  --card-ink-2: #c8d6ee;
  --card-muted: #93a6c4;
  --rule: rgba(255, 255, 255, .14);
  --rule-2: rgba(255, 255, 255, .26);
  --card-rule: rgba(255, 255, 255, .14);
  --surface: rgba(28, 23, 20, .72);
  --surface-2: rgba(38, 31, 27, .78);
  --surface-3: rgba(255, 255, 255, .08);
  --accent: #0066cc;
  --accent-ink: #ffffff;
  --accent-text: #5fb0ff;
  --accent-line: rgba(95, 176, 255, .42);
  --accent-soft: rgba(95, 176, 255, .16);
  --ok-text: #5cc561;
  --warn-text: #e0a340;
  --danger-text: #f4826f;
  /*
   * The FILL tokens too, which this block used to leave behind.
   *
   * Several components print --ok / --warn / --danger as text - the automation strip's
   * headline, a stat's figure, a matrix cell. On the light theme those resolve to inks
   * chosen for a white ground, and this ground is near-black: the roadmap's headline
   * measured 2.9:1 against a floor of 4.5, on the one line that says what is broken.
   * These are the dark theme's values, which is what this band is.
   */
  --ok: #5cc561;
  --ok-soft: #10331c;
  --warn: #e0a340;
  --warn-soft: #2a2712;
  --danger: #f4826f;
  --danger-soft: #331b22;
  --focus-bg: #f2f6fb;
  --focus-ink: #0b2a4a;
  --focus-muted: #3c5077;
  --focus-rule: rgba(11, 42, 74, .22);
  --focus-accent: #0066cc;
  /*
   * The quiet grey, which this block was not restating at all - the depth axis took the
   * light theme's value onto a near-black ground and measured 3.1:1.
   *
   * The first correction, #7a8eab, was chosen by eye and measured 4.14 against the panel
   * ground - still under the floor, and carrying every column header, every "level 3 of 7"
   * and every obligation count on the page. This one measures 5.08. Arithmetic, not taste.
   */
  --muted-2: #8fa2bf;
  color: var(--ink);
}
/* Panels get their own ground so nothing depends on what the scene is doing behind them. */
.rm-imm .card, .rm-imm .chart-card, .rm-imm .s3d-legend li, .rm-imm .auto, .rm-imm .ul {
  background: var(--surface);
  border-color: var(--rule);
  backdrop-filter: blur(14px) saturate(1.1);
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
}
.rm-imm h1, .rm-imm h2, .rm-imm h3 { color: var(--ink); }
.rm-imm .page-head h1 { color: var(--accent-text); }
.rm-imm .sub, .rm-imm .note { color: var(--muted); }

@media (prefers-reduced-motion: reduce) { .rm-imm > .s3d-wrap { position: absolute; } }

/* ---- depth ---------------------------------------------------------------------------- */

/*
 * The console has a third dimension, and it is used for two things only.
 *
 * ONE: A CARD ANSWERS THE POINTER. It tilts a couple of degrees toward wherever the
 * pointer is. That is the whole effect - enough that the surface reads as a physical
 * object rather than a rectangle, little enough that nobody notices it happening. The
 * transform is set inline by the script and cleared on leave, so nothing is transformed at
 * rest and no entrance animation is fighting for the same property.
 *
 * TWO: HEIGHT MEANS SEVERITY. A tile in alert sits further off the page than one that is
 * fine, so the thing that needs somebody is nearer to them. This is the only place in the
 * product where depth carries data rather than decorating, and it is deliberately the only
 * one: if everything floats, nothing is raised.
 */
.deck { perspective: 1200px; perspective-origin: 50% 40%; }

.card, .chart-card, .kpi, .stat {
  transition: transform .3s cubic-bezier(.2, .8, .3, 1), box-shadow .3s ease;
  will-change: auto;
}
/* Set by the script while the pointer is over a card, removed when it leaves. */
[data-tilt] { transform-style: preserve-3d; }

/*
 * HEIGHT BY STATE, WITHOUT translateZ.
 *
 * The first version lifted an alarming tile with translateZ(14px) inside a 1200px
 * perspective. That does not raise a thing, it moves it CLOSER, so the browser scales it
 * by 1200/(1200-14) - about 1.2% - and text rasterised at 100% then drawn at 101.2% is
 * soft. Permanently, on precisely the tiles somebody most needs to read.
 *
 * Elevation is a shadow and a pixel of travel instead. Neither resamples anything, both
 * read as height, and the numbers stay sharp.
 */
.kpi.warn { transform: translateY(-2px); box-shadow: 0 6px 16px -8px rgba(0, 0, 0, .30); }
.kpi.alert { transform: translateY(-4px); box-shadow: 0 12px 26px -10px rgba(0, 0, 0, .38); }
.deck .kpi.alert { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .card, .chart-card, .kpi, .stat { transition: none; }
  .kpi.warn, .kpi.alert { transform: none; }
}

/* ---- the control set, by domain -------------------------------------------------------- */

/*
 * A register rather than a grid. See domainRows() for why: the question is which domain is
 * the problem, and a wall of blocks is the one shape that cannot answer it.
 */
.dr { margin: 0; }
.dr ol { list-style: none; margin: 0; padding: 0; }
.dr-row {
  display: grid; grid-template-columns: 26px minmax(150px, 1.1fr) minmax(0, 2fr) 74px;
  align-items: center; gap: 16px;
  padding: 13px 0; border-bottom: 1px solid var(--rule);
}
.dr-row:last-child { border-bottom: 0; }
.dr-n {
  font-family: var(--mono); font-size: 12px; color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}
.dr-t { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.dr-t b { font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
.dr-t b a { color: inherit; text-decoration: none; }
.dr-t b a:hover { text-decoration: underline; text-underline-offset: 3px; }
.dr-t > span {
  font-size: 13px; color: var(--muted); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The dots wrap rather than scroll: a domain with 40 controls is a taller row, not a row
   with 30 controls hidden off the side of it. */
.dr-dots { display: flex; flex-wrap: wrap; gap: 3px; align-content: center; }
.dr-fig { text-align: end; }
.dr-fig b {
  display: block; font-size: 15px; font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.dr-fig > span { font-family: var(--mono); font-size: 11px; color: var(--muted-2); }

@media (max-width: 760px) {
  /* The dots go under the name rather than shrinking three columns into illegibility. */
  .dr-row { grid-template-columns: 26px 1fr 74px; row-gap: 9px; }
  .dr-dots { grid-column: 2 / -1; }
}

/* ---- the AI mark ----------------------------------------------------------------------- */

/*
 * See aiMark() for why this never appears without a state.
 *
 * Deliberately not coloured like a status pill. A rating drafted by a machine is not good
 * news or bad news - it is unfinished work - and giving it the green of an approved control
 * would be the single most misleading thing on the screen.
 */
.ai-mark {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--muted);
}
.ai-glyph {
  font-family: var(--mono); font-style: normal; font-size: 9px; font-weight: 700;
  letter-spacing: .1em; padding: 2px 5px; border-radius: 3px;
  background: var(--surface-3); color: var(--ink-2); border: 1px solid var(--rule-2);
}
.ai-proposed .ai-glyph { border-color: var(--accent); color: var(--accent-text); }
.ai-accepted .ai-glyph { border-color: var(--ok-line); color: var(--ok-text); }
.ai-rejected { opacity: .75; }

/* The draft itself, sitting on the row it belongs to. */
.ai-draft {
  margin-top: 8px; padding: 11px 13px;
  border: 1px solid var(--accent-line); border-inline-start-width: 2px;
  border-radius: var(--radius-sm); background: var(--accent-soft);
}
.ai-draft-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 7px; }
.ai-draft-body {
  margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink-2);
  white-space: pre-wrap; max-height: 12em; overflow-y: auto;
}
.ai-draft-act { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 9px; }
.ai-draft-act .note { margin-inline-start: auto; }

/* ---- the signature on a control -------------------------------------------------------- */

/*
 * Who stands behind this control, on the row itself.
 *
 * Quiet when unsigned and definite when signed, because the difference between a
 * self-assessment and a position is exactly this line, and it should be visible at a glance
 * down a column of a hundred controls.
 */
.ar-sign {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--rule);
}
.ar-signed {
  font-size: 13.5px; color: var(--ok-text); font-weight: 550;
}
/* The literal character, not a CSS escape. TypeScript reads this template literal before
   the browser ever sees it, and a backslash followed by four digits is an octal escape
   there - illegal. Third time this file has been bitten by it, including once by the
   comment explaining it. */
.ar-signed::before { content: "✓ "; }
.ar-unsigned { font-size: 13.5px; color: var(--muted); }

/* ---- the stops in a stage -------------------------------------------------------------- */

/*
 * Every destination inside a stage, with how much is in each.
 *
 * Cards rather than a list because the size is the point: a customer choosing where to go
 * next is choosing between "214 pieces of evidence" and "no suppliers recorded", and those
 * are different decisions that a list of identical rows makes look the same.
 *
 * An empty register still gets a card, showing a dash. Hiding it would mean the product
 * quietly deciding a customer has no suppliers rather than that they have not recorded any.
 */
.stops {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr));
  gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden;
}
.stop {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px; background: var(--surface);
  text-decoration: none; color: var(--ink);
}
.stop:hover { background: var(--surface-2); }
.stop-i { display: flex; color: var(--accent-text); flex: none; opacity: .9; }
.stop-t { flex: 1; min-width: 0; font-size: 14px; font-weight: 550; }
.stop-n {
  display: flex; align-items: center; gap: 7px; flex: none;
  font-family: var(--mono); font-size: 14px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* The count that needs somebody, beside the count that is merely context. */
.stop-b {
  font-style: normal; font-size: 11px; font-weight: 700;
  background: var(--danger-soft); color: var(--danger-text);
  padding: 2px 6px; border-radius: 999px;
}

/* ---- the calendar, and the arc --------------------------------------------------------- */

/*
 * Both taken from the security-operations dashboards, which are the closest analogue to
 * this one: dense, dark, and read at a glance by somebody who did not choose to be looking.
 * See activityCalendar() and scoreArc() for what each answers.
 */
.cal { margin: 8px 0 0; }
.cal-grid {
  display: grid; grid-template-rows: repeat(7, 1fr); grid-auto-flow: column;
  grid-auto-columns: 1fr; gap: 2px; min-height: 74px;
}
.cal-d { display: block; border-radius: 1px; aspect-ratio: 1; min-height: 6px; }
/* Zero is its own step and is never dimmed into the lowest band: "nothing happened" and
   "a little happened" are the distinction the chart exists to draw. */
.cal-0 { background: var(--rule); }
/* Ink, not accent. Activity is not an alert - it is the quietest fact on the page, and a
   half-year of oxide squares was competing with the five things that are actually late. */
.cal-1 { background: color-mix(in srgb, var(--ink) 18%, var(--rule)); }
.cal-2 { background: color-mix(in srgb, var(--ink) 38%, var(--rule)); }
.cal-3 { background: color-mix(in srgb, var(--ink) 60%, var(--rule)); }
.cal-4 { background: var(--ink-2); }
.cal-pad { background: none; }
.cal-note { margin: 9px 0 0; font-size: 13px; color: var(--muted); line-height: 1.4; }

.arc { display: block; width: 100%; max-width: 220px; height: auto; margin: 4px 0 0; overflow: visible; }
.arc-track { fill: none; stroke: var(--rule); stroke-width: 9; stroke-linecap: round; }
.arc-on { fill: none; stroke: var(--accent); stroke-width: 9; stroke-linecap: round; }
.arc-tick { stroke: var(--rule-2); stroke-width: 1.5; }

/* ---- the dashboard, in one screen ------------------------------------------------------ */

/*
 * Nine cells on a four-column grid. See dashboardBody() for what each one is and why it
 * earned its place; the short version is that Drata document their own dashboard widget by
 * widget, two rules run through it - every cell is a filtered entry point, and a cell with
 * nothing in it does not appear - and both are followed here.
 */
.bento {
  display: grid; gap: 1px; background: var(--rule);
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden; margin-bottom: 18px;
}
.bx {
  background: var(--surface); padding: 15px 17px 17px; min-width: 0;
  display: flex; flex-direction: column; gap: 3px;
  text-decoration: none; color: inherit;
}
a.bx:hover { background: var(--surface-2); }
.bx-k {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted-2);
}
.bx-n {
  font-size: 32px; font-weight: 300; letter-spacing: -.04em; line-height: 1.05;
  color: var(--ink); font-variant-numeric: tabular-nums; margin-top: 5px;
}
.bx-s { font-size: 13px; color: var(--muted); line-height: 1.4; }
/* Evidence inside the warning window, or lapsed: the one line under the bars that asks for work. */
.bx-ev { color: var(--warn); }
/* Late is the one state that gets colour. Everything else on this page is ink on ground,
   so that when something IS coloured the eye goes to it. */
.bx.is-late .bx-n { color: var(--danger-text); }
.bx-more {
  margin-top: auto; padding-top: 8px; font-size: 13px; color: var(--accent-text);
  text-decoration: none;
}
.bx-more:hover { text-decoration: underline; text-underline-offset: 3px; }

/* The work, and the core. The two cells that hold their own content. */
.bx-work { grid-column: span 2; padding: 0; background: none; }
.bx-work .attn { border: 0; border-radius: 0; box-shadow: none; height: 100%; }
.bx-core { grid-column: span 3; }
.bx-core-in { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 18px; }
.bx-core-fig { min-width: 0; }
.bx-core-fig .ch .ch-rig {
  height: 244px; border: 0; background: none; padding: 2px 0 0;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 0 8px;
}
.bx-core-fig .ch-foot, .bx-core-fig .ch-ann { display: none; }
.bx-core-fig .ch-ruler {
  margin: 0; justify-content: space-around;
}
.bx-core-fig .ch-ruler li::before { width: 5px; }
.bx-core-fig .ch-ruler span { font-size: 9px; }
.bx-core-fig .ch-stack { width: min(340px, 100%); }
/* Centred, not at 34%. The readiness page puts the stack left of centre because the
   level register sits down the right-hand side of its rig; in this cell the register is
   the panel in the next column, so the offset just left 87px of dead ground on one side
   and pushed the stack 27px into the axis on the other. */
.bx-core-fig .cs-stack { left: 50%; }
.bx-core-fig .ch .ch-rig {
  --rule: color-mix(in srgb, var(--ink) 20%, transparent);
  --rule-2: color-mix(in srgb, var(--ink) 38%, transparent);
}
.bx-core-read { min-width: 0; display: flex; flex-direction: column; }
.bx-core-t {
  margin: 2px 0 10px; font-size: 22px; font-weight: 600; letter-spacing: -.03em; color: var(--ink);
}
.bx-core-t span {
  font-family: var(--mono); font-size: 11px; font-weight: 400; color: var(--muted);
  letter-spacing: .06em; text-transform: uppercase;
}

/* The score. The only cell with a figure this large, because it is the only one that
   speaks for the whole workspace. */
.bx-score-n {
  font-size: 46px; font-weight: 300; letter-spacing: -.045em; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums; margin-top: 6px;
}
.bx-score-b { font-size: 14px; font-weight: 600; color: var(--accent-text); }
.bx-score-none { font-size: 17px; font-weight: 600; color: var(--ink); margin-top: 6px; }

/* The figure sits inside the arc rather than beside it, so the eye reads one object. */
.bx-arc { position: relative; }
.bx-arc-n {
  position: absolute; inset-inline: 0; bottom: 2px; text-align: center;
  font-size: 38px; font-weight: 300; letter-spacing: -.045em; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums;
}

/* WHERE THE CELL GOES, SAID OUT LOUD.
   Every cell was already an anchor and none of them looked like one. Both SOC dashboards
   in the reference searches print the destination at the foot of the card with a chevron,
   and a cell you can click but cannot tell you can click is a cell nobody clicks. */
.bx-go {
  margin-top: auto; padding-top: 14px; font-size: 12.5px; color: var(--muted);
  display: flex; align-items: center; gap: 5px;
}
/* The content sits at the top of its cell and the destination at the foot, with the slack
   between them capped. Without the cap a two-line cell in a 257px row had 150px of nothing
   through the middle, which reads as a cell that failed to load rather than as space. */
.bx { justify-content: start; }
.bx > :not(.bx-go):last-of-type { margin-bottom: 0; }
.bx-go::after { content: "→"; transition: transform .16s ease; }
a.bx:hover .bx-go { color: var(--accent-text); }
a.bx:hover .bx-go::after { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { .bx-go::after { transition: none; } }

/* One column. Twenty-six weeks is twenty-six columns, which at this width is about ten
   pixels a day - enough to see a quiet fortnight, and it keeps the grid to three rows
   rather than four with a hole in the last one. */
/* Programme milestones: the cell, and the page. A reached milestone carries the good tone
   on its mark only; the lapsed state borrows the warning ink, because a lapse is a fact
   the reader has to act on rather than a decoration to admire. */
.bx-ms .bx-of { font-size: 14px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.ms-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); margin-inline-end: 6px; vertical-align: 1px; }
.ms-lapsed { color: var(--warn-text, var(--warn)); }
.ms-stage { margin-top: 22px; }
.ms-stage-h { display: flex; align-items: baseline; gap: 12px; margin: 0 0 8px; font-size: 15px; }
.ms-stage-k { font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.ms-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; }
.ms { display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 12px; padding: 14px 16px; background: var(--surface); }
.ms-mark { width: 26px; height: 26px; border-radius: 50%; border: 1.5px solid var(--rule-2, var(--rule)); display: grid; place-items: center; font-size: 14px; font-weight: 700; color: var(--muted); }
.ms.is-on .ms-mark { border-color: var(--ok); background: var(--ok); color: var(--ok-soft); }
.ms.is-lapsed .ms-mark { border-color: var(--warn); color: var(--warn); }
.ms-t { font-weight: 650; }
.ms-means { margin: 3px 0 4px; color: var(--ink-2); font-size: 14px; line-height: 1.45; }
.ms .note { margin: 0; }
.ms-body { min-width: 0; }
.bx-cal .cal-grid { gap: 1px; min-height: 62px; }
.bx-cal .cal-d { min-height: 4px; }
.bx-bar { display: block; height: 3px; background: var(--rule); margin: 9px 0 6px; overflow: hidden; }
.bx-bar i { display: block; height: 100%; background: var(--accent); }

/* Readiness per obligation. */
.bx-oblig { grid-column: span 1; }
.ob-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.ob a {
  display: grid; grid-template-columns: minmax(0, 1fr) 54px 22px; align-items: center; gap: 8px;
  text-decoration: none; color: inherit;
}
.ob a:hover .ob-n { text-decoration: underline; text-underline-offset: 3px; }
.ob-n { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ob-t { height: 3px; background: var(--rule); overflow: hidden; }
/* Muted. An obligation's progress is not an alarm; the level number beside it carries the
   meaning, and the bar is there to be compared with the bar above it. */
.ob-t i { display: block; height: 100%; background: var(--muted); }
.ob-l {
  font-family: var(--mono); font-size: 11px; color: var(--muted-2); text-align: end;
  font-variant-numeric: tabular-nums;
}
/*
 * The next step, on its own line under the bar.
 *
 * It spans the whole row rather than sharing the first column, because an obligation name
 * and a stage title are both long enough to want the width, and side by side each one
 * gets an ellipsis after four words. Two lines with a full measure beats one line with
 * neither of them legible.
 */
.ob-x {
  grid-column: 1 / -1; font-size: 12px; color: var(--muted-2); line-height: 1.35;
  margin-top: -1px;
  /* Two lines, then cut. A stage title is authored copy and bounded, but the row must not
     be able to push the cell taller than the ones beside it. */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.ob a:hover .ob-x { color: var(--ink-2); }
/* The rows breathe once each carries two lines; at the old 7px they read as one block. */
.ob-list { gap: 11px; }

/*
 * THE NEAREST STATUTORY DEADLINE.
 *
 * The count above it says how many clocks are running. This says when the first one runs
 * out, which is the only fact on this dashboard that cannot be caught up later. It is
 * pushed to the foot of the cell so it sits directly above the way in to the register.
 */
.bx-near {
  margin-top: auto; padding-top: 12px; display: flex; flex-direction: column; gap: 1px;
  min-width: 0;
}
.bx-near-t {
  font-family: var(--mono); font-size: 14px; font-variant-numeric: tabular-nums;
  letter-spacing: -.01em; color: var(--ink);
}
.bx-near-l {
  font-size: 13px; color: var(--ink-2); line-height: 1.35;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.bx-near-r { font-size: 12px; color: var(--muted-2); }
/* Colour only when it is genuinely past its window, which is the rule the whole page keeps. */
.bx.is-late .bx-near-t { color: var(--danger-text); }
/* .bx-go is margin-top:auto too, and two auto margins split the slack between them - which
   put the deadline in the middle of the cell instead of above the link. */
.bx-near ~ .bx-go { margin-top: 0; padding-top: 12px; }

/* The pages that own the analysis this dashboard used to reprint. */
.deeper {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 13px 17px; border: 1px solid var(--rule); border-radius: var(--radius);
}
.deeper-k {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted-2);
}
.deeper a { font-size: 14px; color: var(--ink-2); text-decoration: none; }
.deeper a:hover { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }

@media (max-width: 1200px) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bx-work { grid-column: span 2; grid-row: auto; }
  .bx-core { grid-column: span 2; }
  .bx-core-in { grid-template-columns: 1fr; }
  .bx-core-fig .ch .ch-rig { height: 210px; }
  /* The figure now has the whole cell rather than half of it, so the 340px cap that keeps
     the stack in proportion beside the reading panel leaves 400px of ground on either side.
     Measured at 900px: the stack held 22% of its rig, against 44% when they share a row. */
  .bx-core-fig .ch-stack { width: min(460px, 100%); }
}
@media (max-width: 700px) {
  .bento { grid-template-columns: minmax(0, 1fr); }
  .bx-work, .bx-core { grid-column: span 1; }
}

/* ---- the dial: how far each obligation has been taken ----------------------------------- */

/*
 * See levelDial() in charts.ts. A disc of seven rings, one spoke per obligation running
 * from the centre out to the ring it has reached.
 *
 * RADIAL ON PURPOSE. Every 3D object in this product had been the same idiom - a pile of
 * slabs seen at an angle - across three pages. By the third it read as a house style
 * rather than as a drawing of anything, and a house style is not a reading.
 *
 * TILTED GENTLY ON PURPOSE. The first version used 58 degrees and squashed a 400px disc
 * to 217px: every ring an ellipse, every marker 18x8, no number legible in any of them.
 * Fourteen degrees costs three per cent of the height and keeps a circle a circle.
 */
.dl { margin: 0; }
/* The markers are centred ON the outer ring, so half of each - about ten pixels - falls
   outside the disc. Without room for them the figure overflowed its own box by 8px. */
.dl-rig { position: relative; display: grid; place-items: center; padding: 14px 12px 12px; }

.dl-disc {
  position: relative;
  /* 240, not 268: a marker is centred ON the outer ring, so the painted figure is the
     disc plus one marker's width. At 268 the whole thing measured 308px in a 300px
     column and the page had an 8px overflow nobody would have seen until a phone. */
  width: min(240px, 100%); aspect-ratio: 1;
  transform-style: preserve-3d;
  transform: perspective(1000px) rotateX(14deg) rotateZ(-9deg);
}

.dl-rings, .dl-spokes { position: absolute; inset: 0; transform-style: preserve-3d; }

/* The rings. Concentric, centre outwards, one per level. */
.dl-ring {
  position: absolute; left: 50%; top: 50%;
  width: var(--s); height: var(--s); margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);
  border: 1px solid var(--rule-2); border-radius: 50%;
}
/* The rim is the far edge of the programme - level seven - and reads as the boundary of
   the whole picture rather than as one more ring. */
.dl-ring:last-child { border-color: var(--rule-2); border-style: solid; opacity: .85; }

/*
 * The floor: the level EVERY obligation has passed, which is where the workspace stands.
 *
 * Filled rather than outlined, because it is ground that has been covered. Everything
 * outside it is still to do, and the eye should land on the boundary between them.
 */
.dl-floor {
  position: absolute; left: 50%; top: 50%;
  width: var(--s); height: var(--s); margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);
  border-radius: 50%;
  background: radial-gradient(circle at 50% 42%,
    color-mix(in srgb, var(--accent) 30%, transparent) 0%,
    color-mix(in srgb, var(--accent) 10%, transparent) 78%,
    transparent 100%);
  border: 1px solid var(--accent-line);
}

/* The depth axis, up the disc's own vertical radius: centre is nothing, rim is assured. */
.dl-axis {
  position: absolute; left: 50%; top: 0; bottom: 50%; width: 0;
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; justify-content: space-between;
  align-items: center;
}
.dl-axis li {
  font-family: var(--mono); font-size: 8.5px; color: var(--muted-2);
  font-variant-numeric: tabular-nums; background: var(--deep); padding: 0 3px;
}

/*
 * One obligation. The spoke is a strip pinned to the centre and rotated, so the arm is a
 * plain percentage width and nothing here needs trigonometry.
 */
.dl-spoke {
  position: absolute; left: 50%; top: 50%; width: 50%; height: 0;
  transform-origin: 0 0; transform: rotate(var(--a));
  transform-style: preserve-3d;
}
.dl-arm {
  position: absolute; left: 0; top: -1.5px; height: 3px; width: var(--r);
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--ink) 26%, transparent),
    var(--dial-arm, var(--ink-2)));
  border-radius: 3px;
}
.dl-mark {
  position: absolute; left: var(--r); top: 0;
  width: 21px; height: 21px; margin: -10.5px 0 0 -10.5px;
  border-radius: 50%;
  background: var(--surface-2); border: 1px solid var(--rule-2);
  /* Lifted off the plane so it catches the tilt and stands ON the disc rather than
     reading as a hole cut in it. */
  transform: translateZ(16px);
  box-shadow: 0 8px 12px -7px rgba(0, 0, 0, .75);
  display: grid; place-items: center;
}
/* Counter-rotated out of the tilt AND out of the spoke's own rotation, so the number is
   upright wherever its arm points. */
.dl-num {
  font-family: var(--mono); font-size: 11px; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1;
  transform: rotate(calc(-1 * var(--a))) rotateZ(9deg) rotateX(-14deg);
}
/* An obligation sitting ON the floor is the reason the workspace is where it is, so it is
   the only thing on the dial that takes the accent. */
.dl-spoke.is-floor .dl-mark { background: var(--accent); border-color: var(--accent); }
.dl-spoke.is-floor .dl-num { color: var(--accent-ink); }
.dl-spoke.is-floor .dl-arm { --dial-arm: var(--accent); }

/* The shape and the sentence, side by side. Centred alone, the disc left about 320px of
   empty page either side of it and pushed the table below the fold. */
.rm-head {
  display: grid; gap: 8px 30px; align-items: center;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  margin: 0 0 18px;
}
.rm-said { min-width: 0; }
.rm-say { margin: 0; font-size: 15px; line-height: 1.55; color: var(--ink-2); }
.rm-say strong { color: var(--ink); }
.rm-legend { margin: 10px 0 0; font-size: 13.5px; color: var(--muted); line-height: 1.5; max-width: 56ch; }
@media (max-width: 900px) { .rm-head { grid-template-columns: minmax(0, 1fr); } }

/* ---- the plan: every obligation, its depth, and the next step --------------------------- */

/*
 * See levelGrid() in charts.ts. This is the roadmap itself, not a picture beside it: the
 * figure above summarises the shape, this names every row and carries the action.
 */
.pl { width: 100%; border-collapse: collapse; margin: 0 0 4px; font-size: 14px; }
.pl caption { text-align: start; }
.pl thead th {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted-2); font-weight: 400; text-align: start;
  padding: 0 8px 7px; border-bottom: 1px solid var(--rule);
}
.pl-h { text-align: center; width: 30px; padding-inline: 2px; }
.pl-hn { font-size: 11px; letter-spacing: 0; }
.pl-nh { padding-inline-start: 14px; }

/* The entity a run of obligations binds. A heading inside the table, not above it, so the
   grouping survives the table being sorted, copied or read aloud. */
.pl-g th {
  text-align: start; padding: 16px 8px 6px; font-size: 14.5px; font-weight: 650;
  color: var(--ink); border-bottom: 1px solid var(--rule);
}
.pl-g:first-child th { padding-top: 8px; }
.pl-gn { font-family: var(--mono); font-size: 11px; color: var(--muted-2); margin-inline-start: 10px; font-weight: 400; }

.pl tbody tr:not(.pl-g):hover { background: var(--surface-2); }
.pl-o { text-align: start; font-weight: 400; padding: 7px 8px; min-width: 150px; }
.pl-o a { color: var(--ink); text-decoration: none; font-weight: 600; }
.pl-o a:hover { text-decoration: underline; text-underline-offset: 3px; }
.pl-k { display: block; font-size: 12px; color: var(--muted-2); margin-top: 1px; }

/*
 * One level. Filled to the depth reached, and the ragged right-hand edge these make down
 * the table is the profile of the whole programme - the thing the figure above draws,
 * here for free and with every row named.
 */
.pl-c {
  padding: 0 2px; height: 30px;
}
.pl-c::before {
  content: ""; display: block; height: 15px; border-radius: 3px;
  border: 1px solid var(--rule);
}
.pl-c.is-done::before { background: var(--ink-2); border-color: var(--ink-2); }
.pl-c.is-here::before { background: var(--accent); border-color: var(--accent); }
.pl-c.is-ahead::before { background: none; border-style: dashed; }

.pl-n { padding: 7px 8px 7px 14px; min-width: 170px; }
.pl-go { font-size: 13.5px; color: var(--accent-text); text-decoration: none; }
.pl-go:hover { text-decoration: underline; text-underline-offset: 3px; }
.pl-go::after { content: " →"; }
.pl-clear { font-size: 13.5px; color: var(--muted-2); }

@media (prefers-reduced-motion: reduce) { .dl-disc { transition: none; } }
/*
 * Below a laptop the dial stops being countable and the plan stops fitting.
 *
 * The seven cells become a single depth figure - the level is already printed under every
 * obligation's name - and the dial goes, because a 300px disc holding nine spokes on a
 * phone is a smudge with numbers on it.
 */
@media (max-width: 900px) {
  .dl { display: none; }
  .pl-h, .pl-c { display: none; }
  .pl { font-size: 14.5px; }
  .pl-o, .pl-n { min-width: 0; }
}

/* ---- the estate ------------------------------------------------------------------------ */

/* The scene sits on the deep ground so it shares an edge with the layer below it rather
   than floating as a second card. Its own canvas is transparent. */
.estate {
  border: 1px solid var(--rule); border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--deep); overflow: hidden;
}
.estate .s3d-wrap { gap: 0; }
.estate .s3d { border: 0; border-radius: 0; background: none; }
/* The section beneath loses its top corners, so the two read as one object. */
.estate + .layer { border-top-left-radius: 0; border-top-right-radius: 0; }

/* ---- the layer, read ---------------------------------------------------------------- */

/*
 * The core and the panel that reads it, as one object on the deep ground.
 *
 * They share a ground and a border rather than sitting in two cards, because they are one
 * argument: the picture raises three questions and the panel answers them. Two cards with
 * a gap between them invites the reader to take the picture and leave.
 */
.layer {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 400px);
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--deep); overflow: hidden; margin-bottom: 16px;

  --ink: #eaf1fb;
  --ink-2: #c8d6ee;
  --muted: #93a6c4;
  --muted-2: #8fa2bf;
  --rule: rgba(255, 255, 255, .13);
  --rule-2: rgba(255, 255, 255, .24);
  --accent-text: #5fb0ff;
  --accent-ink: #ffffff;
  color: var(--ink);
}
.layer-fig { min-width: 0; }
.layer-fig .ch-rig { border: 0; border-radius: 0; border-inline-end: 1px solid var(--rule); }
.layer-read {
  padding: 26px 26px 24px; min-width: 0;
  display: flex; flex-direction: column;
}
.hp-k {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted-2);
}
.hp-k2 { margin-top: 18px; }
.hp-t {
  margin: 6px 0 0; font-size: 30px; font-weight: 600; letter-spacing: -.03em;
  color: var(--ink); line-height: 1.05;
}
.hp-t span { font-family: var(--mono); font-size: 12px; font-weight: 400; color: var(--muted); letter-spacing: .06em; text-transform: uppercase; }
.hp-s { margin: 9px 0 0; font-size: 14.5px; line-height: 1.5; color: var(--muted); max-width: 46ch; }

/* The three measures. A number, its denominator, and a rule under it - the denominator is
   never dropped, because a count without one is the oldest way to overstate a position. */
.hp-bars { list-style: none; margin: 18px 0 0; padding: 0; }
.hp-bar { display: grid; grid-template-columns: 1fr auto; gap: 3px 10px; margin-bottom: 11px; }
.hp-b-l { font-size: 13.5px; color: var(--ink-2); }
.hp-b-n { font-family: var(--mono); font-size: 13.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.hp-b-n i { color: var(--muted-2); font-style: normal; font-size: 12px; }
.hp-b-t { grid-column: 1 / -1; height: 2px; background: var(--rule); overflow: hidden; }
.hp-b-t i { display: block; height: 100%; background: var(--muted); }
.hp-bar.ok .hp-b-t i { background: var(--chart-good); }
.hp-bar.warn .hp-b-t i { background: var(--chart-warning); }
.hp-bar.gap .hp-b-t i { background: var(--chart-critical); }

/* What is stopping the layer finishing. Every row is a stage the journey engine says is
   unsettled, and every action goes to the screen that settles it. */
.hp-hold { list-style: none; margin: 9px 0 0; padding: 0; }
.hp-hold li {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 11px; margin-bottom: 5px;
  border-inline-start: 2px solid var(--accent); background: rgba(255, 255, 255, .04);
  font-size: 14px;
}
.hp-h-t { flex: 1; min-width: 0; color: var(--ink-2); }
.hp-h-a { font-size: 13.5px; color: var(--accent-text); text-decoration: none; white-space: nowrap; }
.hp-h-a:hover { text-decoration: underline; }

.hp-act { display: flex; gap: 8px; flex-wrap: wrap; margin-top: auto; padding-top: 20px; }

/* ---- the instrument strip -------------------------------------------------------------- */

.meter {
  display: flex; flex-direction: column; gap: 3px;
  padding: 16px 18px 18px; text-decoration: none; color: inherit;
  border-inline-end: 1px solid var(--rule);
}
.meter:last-child { border-inline-end: 0; }
.meter:hover { background: var(--surface-2); }
.meter-k {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted-2);
}
.meter-tag {
  font-style: normal; font-size: 8.5px; letter-spacing: .1em; padding: 2px 5px;
  border-radius: 3px; margin-inline-start: auto;
}
.meter-tag.ok { background: var(--ok-soft); color: var(--ok-text); }
.meter-tag.warn { background: var(--warn-soft); color: var(--warn-text); }
.meter-tag.danger { background: var(--danger-soft); color: var(--danger-text); }
.meter-n {
  font-size: 34px; font-weight: 300; letter-spacing: -.04em; line-height: 1.05;
  color: var(--ink); font-variant-numeric: tabular-nums; margin-top: 4px;
}
.meter-s { font-size: 13px; color: var(--muted); }

@media (max-width: 900px) {
  .meter:nth-child(2) { border-inline-end: 0; }
  .meter:nth-child(-n+2) { border-bottom: 1px solid var(--rule); } }
@media (max-width: 520px) {
  .meter { border-inline-end: 0; border-bottom: 1px solid var(--rule); }
  .meter:last-child { border-bottom: 0; } }

/* ---- the core, at hero size ------------------------------------------------------------ */

/*
 * See coreHero() in charts.ts. The rig is a stage: ruler on the left, core in the middle,
 * annotations on the right, all flat except the core itself.
 */
.ch { margin: 0; }
.ch-eyebrow {
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-text); margin-bottom: 8px;
}
.ch .ch-rig {
  position: relative; height: 470px; overflow: hidden;
  /* Three columns rather than three absolutes: ruler, core, annotations. Positioned, the
     core spilled across the annotation column at every width under about 1300px and the
     labels printed on top of the strata they name. */
  display: grid; grid-template-columns: 52px minmax(210px, 1fr) minmax(160px, 240px);
  gap: 0 14px; padding: 30px 18px 18px; align-items: stretch;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background:
    radial-gradient(110% 80% at 42% 4%, var(--accent-soft) 0%, rgba(0, 0, 0, 0) 58%),
    var(--deep);
  /* The survey grid the core is logged against. Two hairline gradients rather than an
     image, so it costs nothing and scales with the box. */
  background-image:
    radial-gradient(110% 80% at 42% 4%, var(--accent-soft) 0%, rgba(0, 0, 0, 0) 58%),
    repeating-linear-gradient(0deg, rgba(255, 255, 255, .028) 0 1px, transparent 1px 44px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .028) 0 1px, transparent 1px 44px);
  background-color: var(--deep);
  touch-action: none;
  /* Lighter than the section's, because the unreached strata are drawn ENTIRELY from this
     hairline as survey outline. At the section's value they measured 1.6:1 on the deep
     ground and the picture showed one slab and six blanks. */
  --rule: rgba(255, 255, 255, .17);
  --rule-2: rgba(255, 255, 255, .34);
}
.ch-core { position: relative; min-width: 0; }
/* Scoped so it outranks .cs-stack's fixed width, which sits later in the sheet; at equal
   specificity the later rule won and the stack spilled its column by 10px on the stage page. */
.ch-core .cs-stack.ch-stack { left: 50%; width: min(300px, 100%); }

/* The depth axis. Levels, because that is the only depth this product measures in - it
   was millimetres, which is the unit a real core log uses and a unit nothing here has. */
.ch-ruler {
  margin: 0 0 26px; padding: 0; list-style: none;
  display: flex; flex-direction: column; justify-content: space-between;
}
.ch-ruler li { display: flex; align-items: center; gap: 7px; }
.ch-ruler li::before { content: ""; width: 9px; height: 1px; background: var(--rule-2); }
.ch-ruler span { font-family: var(--mono); font-size: 9.5px; color: var(--muted-2); }

/* The annotations. Flat HTML, so they stay crisp and a screen reader can read them. */
.ch-ann {
  margin: 0 0 26px; padding: 0; list-style: none; z-index: 2;
  display: flex; flex-direction: column; min-width: 0;
}
.ch-a {
  display: flex; align-items: baseline; gap: 8px; flex: 1;
  font-size: 13.5px; color: var(--muted);
  border-top: 1px solid var(--rule); padding-top: 6px;
}
.ch-a b {
  font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.ch-a-n { color: var(--ink-2); }
.ch-a-s {
  margin-inline-start: auto; font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .06em; text-transform: uppercase; opacity: .85; white-space: nowrap;
}
.ch-a.is-here { color: var(--ink); border-top-color: var(--accent); }
.ch-a.is-here b, .ch-a.is-here .ch-a-n { color: var(--accent-text); font-weight: 600; }
.ch-a.is-ahead { opacity: .5; }

.ch-foot {
  position: absolute; inset-inline-start: 0; bottom: -20px; margin: 0;
  display: flex; align-items: center; gap: 9px; white-space: nowrap;
}
.ch-split {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2); background: none; border: 0; border-bottom: 1px solid var(--rule-2);
  padding: 2px 0; cursor: pointer;
}
.ch-split:hover { color: var(--ink); border-bottom-color: var(--accent); }
.ch-split[aria-pressed="true"] { color: var(--accent-text); border-bottom-color: var(--accent); }
.ch-hint {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted-2);
}

/* Split apart, so the strata can be counted rather than estimated. Driven by a class the
   script toggles; the transform below is what the slabs already animate on. */
.ch-rig.is-split .cs-slab { margin-bottom: 16px; }
.cs-slab { transition: margin-bottom .38s cubic-bezier(.2, .8, .3, 1); }

@media (max-width: 1000px) {
  /* Below a laptop the core stops being readable long before the annotations do, so the
     annotations become the whole thing rather than shrinking both into illegibility. */
  .ch .ch-rig { height: auto; background: none; border: 0; padding: 0; display: block; }
  .ch-ruler, .ch-foot, .ch-core { display: none; }
  .ch-ann { margin: 0; }
  .ch-a { flex: none; padding: 9px 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cs-slab { transition: none; }
}

/* ---- the layer, responsive ------------------------------------------------------------- */

/*
 * These live at the END of both blocks on purpose.
 *
 * They override rules in two sections above, at the same specificity, so where they sit is
 * what decides whether they win. Placed beside the component they belong to, they were
 * silently losing to the base rule declared after them - the depth ruler stayed visible at
 * every width and the core column collapsed to 52px. Twice, in one afternoon.
 */
@media (max-width: 1450px) {
  /* Below this the ruler no longer fits beside a core wide enough to read, so the core
     takes its column. Verified by measuring the columns at nine widths, not by eye: the
     first attempt at this left the figure column at exactly zero pixels. */
  .ch .ch-rig { grid-template-columns: minmax(210px, 1fr) minmax(150px, 220px); }
  .ch .ch-ruler { display: none; }
}
@media (max-width: 1250px) {
  /* And below this the core and the reading panel cannot share a row at all. */
  .layer { grid-template-columns: 1fr; }
  .layer-fig .ch-rig { border-inline-end: 0; border-bottom: 1px solid var(--rule); }
  .layer-read { border-top: 0; }
}

/*
 * THE DASHBOARD'S CORE IS NOT THE READINESS PAGE'S CORE, AND MUST STOP INHERITING ITS
 * BREAKPOINTS.
 *
 * The two blocks above are written for the readiness page, where the figure shares a row
 * with a reading panel and the page's width IS the figure's width. In the bento the figure
 * sits inside one cell, so the viewport says nothing about how much room it has - and the
 * inherited rules were wrecking it two different ways at once:
 *
 *   - The rule hiding .ch-ruler fires at 1450px. That left .ch-core as the only
 *     grid item, so it was placed in the FIRST track - the 34px one reserved for the ruler.
 *     Measured: a 30px stack in a 357px column, 4.6% of the area it was given, on every
 *     screen narrower than 1450px, which is most of them.
 *   - The rule hiding .ch-ruler, .ch-foot and .ch-core fires at 1000px, and the bento
 *     already hides .ch-ann. Between them the cell rendered EMPTY: a titled box with
 *     nothing in it, on every laptop in portrait and every tablet.
 *
 * Both are restored explicitly here rather than by raising specificity above, because these
 * blocks live at the end of the sheet on purpose and anything declared earlier loses to
 * them at equal specificity - the mistake this file has now made three times.
 */
.bx-core-fig .ch .ch-ruler { display: flex; }
.bx-core-fig .ch .ch-core { display: block; }
@media (max-width: 1000px) {
  .bx-core-fig .ch .ch-rig {
    display: grid; height: 244px; background: none; border: 0; padding: 2px 0 0;
  }
}
/* Under about 420px of cell the axis costs more than it explains, and the stack needs
   every pixel. The cell reaches that width when the bento is one column on a phone. */
@media (max-width: 700px) {
  .bx-core-fig .ch .ch-ruler { display: none; }
  .bx-core-fig .ch .ch-rig { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the Zenith page ------------------------------------------------------------------ */

/* The dial and the breakdown side by side, because the number and its parts are one
   argument. Split across two scroll positions, a reader takes the number and leaves. */
.zen-page {
  display: grid; grid-template-columns: minmax(320px, 420px) 1fr; gap: 22px;
  align-items: start; margin-bottom: 24px;
}
.zen-hero {
  padding: 18px; border: 1px solid var(--rule); border-radius: var(--radius);
  background:
    radial-gradient(120% 90% at 50% 8%, var(--accent-soft) 0%, rgba(0, 0, 0, 0) 60%),
    var(--surface);
}
.zen-side { min-width: 0; }
/* The band the workspace is standing in, marked in the table of all five. */
.zen-page + .section tr.is-here th, .section tr.is-here th { color: var(--accent-text); }
.section tr.is-here { background: var(--accent-soft); }
@media (max-width: 900px) {
  .zen-page { grid-template-columns: 1fr; }
}

/* ---- the core ------------------------------------------------------------------------ */

/*
 * Seven strata, read from the side, with the register beside them rather than on them.
 * See coreStack() in charts.ts for why the extrusion is lit rather than built and why the
 * labels are not inside the rotated subtree.
 */
.cs { margin: 0; }
.cs-rig {
  position: relative; height: 420px; overflow: hidden;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background:
    radial-gradient(120% 90% at 50% 10%, var(--accent-soft) 0%, rgba(0, 0, 0, 0) 62%),
    var(--surface-3);
}
.cs-stack {
  position: absolute; left: 34%; top: 50%;
  width: 300px;
  transform-style: preserve-3d;
  display: flex; flex-direction: column-reverse; align-items: stretch;
  transform:
    translate(-50%, -50%)
    rotateX(calc(19deg + var(--rx, 0deg)))
    rotateY(calc(-29deg + var(--ry, 0deg)));
  transition: transform .45s cubic-bezier(.2, .8, .3, 1);
}
.cs-slab {
  position: relative; height: var(--t);
  background: var(--fill); border: 1px solid var(--edge);
  box-shadow: inset 0 -7px 12px -6px rgba(0, 0, 0, .5);
  animation: cs-settle .5s cubic-bezier(.2, .9, .3, 1) both;
  animation-delay: calc(.12s + var(--i) * .1s);
}
@keyframes cs-settle { from { opacity: 0; transform: translate3d(0, 30px, -50px); } }
.cs-cap { position: absolute; left: 0; right: 0; top: 0; height: 5px; background: var(--top); }
.cs-cheek {
  position: absolute; top: 0; bottom: 0; right: 0; width: 9px;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0), var(--side));
  opacity: .85;
}
/* A level not reached is the survey outline of where the drill has not gone. */
.cs-ahead {
  background: repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--rule) 55%, transparent) 0 2px, transparent 2px 11px);
  border-style: dashed;
  border-color: color-mix(in srgb, var(--rule-2) 70%, transparent);
  box-shadow: none;
}
.cs-here { box-shadow: inset 0 -7px 12px -6px rgba(0, 0, 0, .4), 0 0 0 1px var(--accent); }

/* The register. Flat HTML, so it stays crisp and a screen reader can read it. */
.cs-key {
  position: absolute; right: 24px; top: 34px; bottom: 34px; width: 210px;
  margin: 0; padding: 0; list-style: none; z-index: 2;
  display: flex; flex-direction: column;
}
.cs-row {
  display: flex; align-items: baseline; gap: 8px; flex: 1;
  font-size: 13px; color: var(--muted);
  border-top: 1px solid var(--rule); padding-top: 5px;
}
.cs-row b { font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.cs-said { margin-inline-start: auto; font-size: 12px; opacity: .8; }
.cs-row.is-here { color: var(--ink); border-top-color: var(--accent); }
.cs-row.is-here b { color: var(--accent-text); }
.cs-row.is-ahead { opacity: .6; }

/* The same register used on its own, per entity: in normal flow rather than over a rig. */
.cs-key-flat { position: static; width: auto; margin: 10px 0 12px; }
.cs-key-flat .cs-row { flex: none; padding: 7px 0; }

/*
 * The core inside the dashboard's identity cell.
 *
 * Shorter, and the register drops out: the cell already names the framework and the level
 * beside it, so repeating all seven rows there would be the same facts twice in a space
 * that has room for neither. On the roadmap the register stays, because there the core IS
 * the reading.
 */
/* ---- the Zenith dial ---------------------------------------------------------------- */

/*
 * A climb from the horizon to the point directly overhead, which is what the name means.
 * The console already had a progress ring and a compliance gauge; a third circle would
 * have said "here is a percentage" for the third time. Altitude reads as distance still to
 * travel, which is the honest shape of a compliance position.
 */
.zd { margin: 0; display: block; }
.zd svg { display: block; overflow: visible; }
.zd-n {
  font-family: var(--display); font-size: 46px; font-weight: 300; letter-spacing: -.035em;
  fill: var(--ink); font-variant-numeric: tabular-nums;
}
.zd-b {
  font-size: 11px; font-weight: 700; letter-spacing: .16em; fill: var(--muted);
}
.zd-cap { font-size: 9px; letter-spacing: .14em; fill: var(--muted); opacity: .8; }

/* The travelled arc draws itself on load. --zd-rest is 100 minus the score, so the
   reveal stops exactly where the marker sits. */
.zd-run {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: zd-climb 1.15s cubic-bezier(.22, .85, .28, 1) .12s forwards;
}
@keyframes zd-climb { to { stroke-dashoffset: var(--zd-rest, 100); } }

.zd-mark {
  transform-box: fill-box; transform-origin: center;
  animation: zd-pop .42s cubic-bezier(.2, 1.6, .4, 1) 1.05s both;
}
@keyframes zd-pop { from { transform: scale(0); } to { transform: scale(1); } }

/* The uncertainty band breathes, because the figure inside it is not settled. */
.zd-band { animation: zd-breathe 5.5s ease-in-out 1.3s infinite; }
@keyframes zd-breathe { 0%, 100% { opacity: .17; } 50% { opacity: .29; } }

.zd-foot {
  display: flex; flex-direction: column; gap: 5px; margin-top: 4px;
  padding-top: 12px; border-top: 1px solid var(--rule);
}
.zd-conf {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13.5px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.zd-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; }
.zd-conf.thin { color: var(--warn); }
.zd-conf.thin .zd-dot { background: var(--warn); }
.zd-note { font-size: 13.5px; line-height: 1.45; color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
  .zd-run { animation: none; stroke-dashoffset: var(--zd-rest, 100); }
  .zd-mark { animation: none; }
  .zd-band { animation: none; }
}

/* ---- what the Zenith is made of ----------------------------------------------------- */

/*
 * Shown every time the dial is shown. A combined figure with its composition hidden is
 * the first thing an assessor takes apart, so the product has no view that renders one
 * without the other.
 */
.zp { display: block; }
.zp-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 15px; }
.zp-head { display: flex; align-items: baseline; gap: 9px; }
.zp-l { font-size: 14.5px; font-weight: 620; letter-spacing: -.005em; }
.zp-w {
  font-size: 11px; font-weight: 700; letter-spacing: .04em; color: var(--muted);
  border: 1px solid var(--rule); border-radius: var(--radius-pill); padding: 1px 7px;
  font-variant-numeric: tabular-nums;
}
.zp-v {
  margin-inline-start: auto; font-size: 14px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.zp-track {
  height: 6px; border-radius: 3px; background: var(--rule); overflow: hidden; margin: 7px 0 5px;
}
.zp-track > i {
  display: block; height: 100%; width: var(--zp, 0%); border-radius: 3px;
  background: var(--accent);
  animation: zp-fill .8s cubic-bezier(.22, .85, .28, 1) .25s both;
}
@keyframes zp-fill { from { width: 0; } }
.zp-basis { margin: 0; font-size: 13px; line-height: 1.45; color: var(--muted); }
.zp-row.unmeasured .zp-v { color: var(--muted); font-weight: 500; }
.zp-row.unmeasured .zp-track > i { background: var(--rule-2); }
@media (prefers-reduced-motion: reduce) { .zp-track > i { animation: none; } }

/* ---- segmented pills -------------------------------------------------------------- */

/*
 * Links, not script. A lens survives a reload and can be sent to a colleague, and it needs
 * nothing from a content policy that forbids inline script.
 */
.lens {
  display: inline-flex; gap: 6px; padding: 5px; border-radius: 999px;
  background: var(--surface-3); flex-wrap: wrap;
  --ink: var(--card-ink); --muted: var(--card-muted); color: var(--card-ink);
}
.lens-i {
  display: inline-flex; align-items: center; gap: 6px; padding: 7px 15px; border-radius: 999px;
  font-size: 14px; font-weight: 600; color: var(--card-ink); text-decoration: none;
  background: var(--surface-2); transition: background-color .14s ease, color .14s ease;
}
.lens-i:hover { background: #fff; }
.lens-i.on { background: var(--accent); color: var(--accent-ink); }
.lens-i b { font-size: 12px; font-weight: 700; opacity: .62; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .lens-i { transition: none; } }

/* ---- pills ----------------------------------------------------------------------- */

/*
 * Everything pressable is a pill.
 *
 * The reference has no square button anywhere. Primary is lime with black text - the
 * loudest thing on the screen, used once per view. Secondary is white with black. On the
 * dark ground a quiet button is a hairline outline in the light ink.
 */
.btn {
  border-radius: var(--radius-sm); font-weight: 600; letter-spacing: -.005em;
  transition: background-color .14s ease, border-color .14s ease;
}
.btn:not(.subtle):not(.ghost) {
  background: var(--accent); color: var(--accent-ink); border: 1px solid var(--accent);
}
.btn:not(.subtle):not(.ghost):hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.subtle {
  background: var(--surface); color: var(--card-ink); border: 1px solid var(--field-border);
}
.btn.subtle:hover { background: var(--surface-3); }
.card .btn.ghost, .chart-card .btn.ghost { color: var(--card-ink); border-color: var(--card-rule); }
.btn.ghost { border-radius: var(--radius-sm); border: 1px solid var(--rule-2); color: var(--ink); background: transparent; }
.btn.ghost:hover { background: var(--surface-3); }
@media (prefers-reduced-motion: reduce) { .btn { transition: none; } .btn:hover { transform: none; } }

/* A percentage badge: a small lime pill with a black figure, as the reference uses for
   every contribution and share. */
.pct {
  display: inline-flex; align-items: center; justify-content: center; min-width: 42px;
  padding: 3px 9px; border-radius: 6px; background: var(--accent); color: var(--accent-ink);
  font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums;
}

/* ---- nesting --------------------------------------------------------------------- */

/*
 * Dark page, light card, white card. Three levels, each a step lighter.
 *
 * That progression is what gives the reference its depth: it is doing with value what most
 * consoles do with shadow, and it survives being printed.
 */
.card, .chart-card { background: var(--surface); box-shadow: var(--shadow-sm); }
.card .card, .chart-card .inset, .card .inset { background: var(--surface-2); border-radius: var(--radius-sm); }

/* ---- figures --------------------------------------------------------------------- */

/*
 * Big and BOLD, the opposite of the previous pass and right for this reference: these are
 * display figures, not measurements set quietly.
 */
.kpi .n {
  font-family: var(--display); font-weight: 800; letter-spacing: -.04em;
  color: var(--card-ink); background: none; -webkit-text-fill-color: currentColor;
}
.kpi::after { display: none; }
.kpi.alert .n { color: var(--danger); }
.kpi.warn .n { color: var(--warn); }

/* The page title sits on the dark ground, so it is light rather than a gradient. */
.page-head h1 { color: var(--ink); background: none; -webkit-text-fill-color: currentColor; }

/* ---- motion ---------------------------------------------------------------------- */

/*
 * One entrance, once, on the panels that carry the page.
 *
 * Not a fade on every card: staggered entrances on ten elements is the tell of a template.
 * A single short rise on the hero composition, and nothing else moves unless a person
 * moved it.
 */
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.attn { animation: rise .34s cubic-bezier(.2,.7,.3,1) .06s both; }
@media (prefers-reduced-motion: reduce) { .attn { animation: none; } }

/* ---- the look ------------------------------------------------------------------- */

/*
 * A figure with presence.
 *
 * The tile carries a soft tint of its own state, the numeral takes the brand gradient, and
 * a hairline top edge in the state colour finishes it. On a fresh workspace this is the
 * difference between five grey boxes containing zeros and something a prospect wants to
 * fill in.
 */
.kpi {
  position: relative; background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 16px 16px 14px; overflow: hidden;
}
.kpi::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--grad);
  opacity: .9;
}
.kpi.alert::after { background: var(--danger); }
.kpi.warn::after { background: var(--warn); }
.kpi.alert { background: var(--danger-soft); border-color: var(--danger-line); }
.kpi.warn { background: var(--warn-soft); border-color: var(--warn-line); }
/*
 * The gradient numeral, with the text still there if the clip is not supported.
 *
 * A transparent colour plus a text background-clip renders nothing at all where the clip
 * fails - an invisible figure rather than an unstyled one. The colour is only dropped
 * inside an @supports that has just proved the clip works, so the fallback is a solid
 * accent numeral rather than a blank tile.
 */
.kpi .n { color: var(--accent-text); font-weight: 700; letter-spacing: -.035em; }
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .kpi .n {
    background: var(--grad-text); -webkit-background-clip: text; background-clip: text;
    color: transparent;
  }
}
/* A state tile keeps a flat colour: a gradient numeral inside a tinted tile is two
   effects fighting, and the state is the thing that has to win. */
.kpi.alert .n { background: none; -webkit-text-fill-color: var(--danger); color: var(--danger); }
.kpi.warn .n { background: none; -webkit-text-fill-color: var(--warn); color: var(--warn); }


/*
 * The page title carries the brand.
 *
 * One gradient headline per screen. The accent stroke beneath it goes: the gradient in the
 * type is doing that job now, and both together is a page shouting twice.
 */
/*
 * Flat pine rather than a clipped gradient.
 *
 * The gradient ran from the accent to a slightly darker accent, which at heading size is
 * indistinguishable from flat colour - and it cost a @supports guard plus a permanent hole
 * in the contrast sweep, because the colour under a glyph on a gradient cannot be read
 * from computed style. Removing it makes the page title measurable for the first time.
 */
/*
 * A PAGE TITLE IS NOT A HEADLINE.
 *
 * This was var(--accent) at up to 62px, which made the company's own name the loudest
 * object on every screen in the product - above the five things that are overdue. Drata
 * and Vanta both set their page titles at around 24 to 28px in ordinary ink, and they are
 * right: the title says where you are, and where you are is the least surprising thing on
 * the page.
 *
 * The 62px display size stays for the signed-out pitch, where a headline IS the content.
 */
.page-head h1 {
  color: var(--ink);
  font-size: 24px;
  letter-spacing: -.015em;
  font-weight: 700;
  margin-bottom: 4px;
}

/* The stage rings take the gradient on the completed arc, so progress reads as brand
   rather than as ink. Set on the SVG stroke via currentColor at the call site. */
/* Lime is a FILL colour. As 19px text on a light cell it measures under 2:1, so the
   reached stage is marked by ink and weight and the lime stays in the arc. */

/*
 * Cards get a very slight wash rather than flat white.
 *
 * Two percent of the brand across the top of a panel is invisible as colour and reads as
 * depth, which is what stops a page of white rectangles looking like a spreadsheet.
 */
.section > .card, .chart-card {
  background-image: linear-gradient(to bottom, var(--surface-2) 0%, var(--surface) 78px);
}

/* ---- the drawing: cells, rules, marks ------------------------------------------- */

/*
 * Panels are cells in a grid, not cards on a surface.
 *
 * The rounded card with a soft shadow is the default every B2B product arrives at, and it
 * is most of why this console was mistaken for one. A hairline rule states the edge more
 * precisely than a shadow suggests it, costs no vertical space, and lets two panels share
 * a boundary instead of each carrying its own.
 */
/*
 * Cards, lifted.
 *
 * The previous pass ruled everything into a flat grid of cells. It was disciplined and it
 * was lifeless, particularly on a new workspace where the whole screen is empty boxes.
 * Elevation costs nothing and gives the eye a hierarchy to travel.
 */
.chart-card, .card, .section > .card {
  border-radius: var(--radius); box-shadow: var(--shadow-sm); border: 1px solid var(--rule);
}
.chart-grid { gap: 14px; background: transparent; border: 0; }
.chart-grid > .chart-card { border: 1px solid var(--rule); border-radius: var(--radius); }

/*
 * Corner registration marks.
 *
 * Four L-shaped ticks inset from the corners, the way a print grid marks a live area. They
 * are drawn with gradients on two pseudo-elements rather than four spans: this is a
 * drawing convention, not content, and there is nothing here for a screen reader to meet.
 */
.attn { position: relative; }
.attn::before, .attn::after {
  content: ""; position: absolute; width: 9px; height: 9px; pointer-events: none;
  border-color: var(--focus-muted); opacity: .55;
}
.attn::before { top: 6px; left: 6px; border-top: 1px solid; border-left: 1px solid; }
.attn::after { bottom: 6px; right: 6px; border-bottom: 1px solid; border-right: 1px solid; }

/*
 * The orb: one luminous circle, used once.
 *
 * The single most recognisable element of the reference. It is a radial gradient rather
 * than an image, so it costs nothing, scales to any size and needs no CSP allowance. Used
 * for the thing the page is ABOUT and nowhere else - the moment there are two of them on
 * a screen, neither is the subject.
 */
/*
 * THE ONE DARK OBJECT, AND WHY IT IS DARK.
 *
 * It sits inside a light card on a dark page, and it is the only element on the dashboard
 * that is neither. That is the point: scarcity is what makes it read as the subject
 * rather than as another panel.
 *
 * It also has to be dark for the numeral to exist at all. The lime measures 14:1 on this
 * ground and 1.26:1 on a light one, and a second rule that put --surface-3 at the centre
 * is exactly how the level indicator came to be invisible.
 *
 * The custom properties are restated here because the card outside redefines them for a
 * light panel, and inside this disc the ground palette is the correct one again.
 */
.orb {
  position: relative; display: grid; place-items: center; aspect-ratio: 1;
  border-radius: 50%; isolation: isolate;
  background:
    radial-gradient(circle at 50% 30%, rgba(0, 194, 168, .24) 0%, rgba(0, 194, 168, .06) 42%, rgba(0, 0, 0, 0) 68%),
    radial-gradient(circle at 50% 50%, #0f3a5e 0%, #071c38 78%);
  color: var(--focus-ink);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .13), 0 14px 34px -16px rgba(7, 28, 56, .6);
  /* The single dark object on a light page, so it restates the palette for its subtree.
     The mint measures 11.0 on the disc; the pine accent would be 1.3. */
  --ink: var(--focus-ink); --ink-2: #cfeee9; --muted: var(--focus-muted);
  --accent-text: #00c2a8;
}
.orb-t {
  font-family: var(--display); font-weight: 300; letter-spacing: -.03em;
  line-height: 1.02; text-align: center; padding: 0 12%; font-size: clamp(15px, 2.2vw, 30px);
}
.orb-n { font-size: clamp(28px, 5vw, 58px); font-weight: 200; letter-spacing: -.04em; line-height: 1; }
.orb-of { font-size: .42em; opacity: .65; }
.orb-s { font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--focus-muted); margin-top: 6px; }

/*
 * The X through a framed area, as the reference draws it.
 *
 * Two hairline diagonals, decorative, on the frame around the orb. Kept very faint: at
 * full strength it competes with the thing it is framing.
 */
.orb-frame { position: relative; padding: 10px; }

/* ---- the journey as a numbered board --------------------------------------------- */

/*
 * Seven stages in a row, joined by one hairline that runs behind them.
 *
 * The rule is drawn on the container rather than between cells, so it is a single
 * continuous track: the sequence is the point, and seven separate widgets in a row do not
 * read as a sequence.
 */
.orb-frame::before {
  content: ""; position: absolute; inset: 10px; pointer-events: none; opacity: .16;
  background:
    linear-gradient(to bottom right, transparent calc(50% - .5px), var(--focus-muted) 50%, transparent calc(50% + .5px)),
    linear-gradient(to bottom left, transparent calc(50% - .5px), var(--focus-muted) 50%, transparent calc(50% + .5px));
  border: 1px solid var(--focus-line);
}

/* ---- roadmap bands --------------------------------------------------------------- */

/*
 * Three headings over the obligation list: not started, being worked, finished.
 *
 * The heading is quiet on purpose. It is a divider, not a section title, and giving it the
 * weight of one would make the page look like three pages.
 */
.lane-band + .lane-band { margin-top: 18px; }
.lane-band-h { display: flex; align-items: baseline; gap: 8px; padding: 0 2px 7px; border-bottom: 1px solid var(--rule); margin-bottom: 8px; }
.lane-band-t { font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.lane-band-n { font-size: 12px; font-weight: 700; color: var(--muted); background: var(--rule-2); border-radius: 9px; padding: 1px 7px; }
.lane-band-note { font-size: 13px; color: var(--muted); }
@media (max-width: 640px) { .lane-band-note { display: none; } }

/* ---- what needs you today -------------------------------------------------------- */

/*
 * One panel with a row per late thing, replacing five stacked full-width banners.
 *
 * Five bands of the same width and the same red is a wall rather than a list: nothing is
 * first, so a missed statutory notification reads exactly like two overdue tasks. A row
 * with a coloured edge keeps the severity and gives the eye somewhere to land.
 */
.attn { background: var(--focus-bg); color: var(--focus-ink); border: 1px solid var(--focus-line); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-md); }
.attn-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 13px 14px; border-bottom: 1px solid var(--focus-line); }
.attn-head h2 { margin: 0; font-size: 14px; letter-spacing: .01em; color: var(--focus-ink); }
.attn-n { min-width: 22px; height: 22px; padding: 0 7px; border-radius: 11px; background: var(--danger); color: var(--danger-ink); font-size: 13px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; }
.attn-list { list-style: none; margin: 0; padding: 0; }
/*
 * The severity edge carries the tone, not a tinted background.
 *
 * On a dark surface a red or amber wash goes muddy and stops being readable; a 3px edge
 * against navy stays legible and keeps the row text at full contrast.
 */
.attn-row { padding: 11px 14px 11px 15px; border-left: 3px solid var(--focus-line); border-bottom: 1px solid var(--focus-line); font-size: 14px; line-height: 1.45; color: var(--focus-ink); }
.attn-row:last-child { border-bottom: 0; }
.attn-row.error { border-left-color: var(--danger); background: var(--focus-row); }
.attn-row.warn { border-left-color: var(--warn); }
.attn-row a { color: #fff; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.attn-row a:hover { color: var(--focus-muted); }
.attn.ok { background: var(--surface); color: inherit; border-color: var(--rule); box-shadow: none; }
.attn.ok .attn-head { border-bottom: 0; }
.attn.ok .attn-head h2 { color: inherit; }
.attn-none { margin: 0; padding: 0 14px 14px; font-size: 14px; color: var(--muted); }

.chart-card {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 17px 19px 15px; box-shadow: var(--shadow-sm);
}
.chart-card h3 { font-size: 14.5px; font-weight: 650; letter-spacing: -.01em; color: var(--ink); margin: 0 0 3px; }
.chart-card .cap { font-size: 13.5px; color: var(--muted); margin: 0 0 14px; }
.chart-card.wide { grid-column: 1 / -1; }
.chart-card > header { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.chart-card .figure { display: flex; justify-content: center; padding: 4px 0 2px; }
/* Two charts of the same subject side by side, sharing one caption. */
.chart-pair { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); align-items: center; }
.hero-card { display: flex; gap: 26px; align-items: center; flex-wrap: wrap; }
.hero-card .gauge { flex: 0 0 190px; max-width: 190px; }
.hero-card .hero-side { flex: 1 1 240px; min-width: 200px; }
.legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 12px; font-size: 13.5px; color: var(--ink-2); }
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-swatch { width: 10px; height: 10px; border-radius: 2px; flex: none; }
.legend-item b { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
/* The table view that discharges the contrast relief rule — every charted number is
   also readable as text, so colour is never the only channel. */
.chart-table { width: 100%; margin-top: 12px; font-size: 13.5px; }
.chart-table td { padding: 5px 0; border: none; }
.chart-table tr:hover { background: none; }
.chart-table td:last-child { text-align: end; font-variant-numeric: tabular-nums; font-weight: 600; }
.chart-table .sw { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-inline-end: 7px; }
.chart-table th {
  position: static; background: none; border-bottom: 1px solid var(--rule);
  padding: 0 0 6px; font-size: 11px;
}
.chart-table td:first-child { width: 100%; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 13px; padding: 4px 10px;
  border: 1px solid var(--card-rule); border-radius: var(--radius-pill); background: var(--surface);
  color: var(--card-ink-2); text-decoration: none;
}
.chip:hover { border-color: var(--accent); color: var(--accent-text); }
.chip .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.chip.advisory { border-style: dashed; }
/* Compliance bar: colour encodes the score, so a weak result reads as weak at a
   glance rather than only in the number beside it. */
.bar { display: inline-block; vertical-align: middle; width: 120px; height: 7px;
  background: var(--surface-2); border-radius: 4px; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 4px; }
.bar-num { margin-inline-start: 8px; font-weight: 600; font-variant-numeric: tabular-nums; font-size: 14px; }
.inline-form { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.inline-form select { width: auto; min-width: 150px; padding: 6px 8px; font-size: 14px; }
.inline-form input { width: auto; flex: 1 1 150px; padding: 6px 8px; font-size: 14px; }

/* ------------------------------------------------------------ small screens --- */
@media (max-width: 900px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  main { --main-pad: 15px; padding: 18px var(--main-pad) 32px; }
  .top { padding: 0 14px; gap: 10px; }
  .find input { width: 132px; }
}

/* ------------------------------------------------------------------ print --- */
/* The console is printed into board packs and regulator submissions, so the printed
   page has to identify itself: which workspace, which page, generated when. */
@media print {
  .tbar, .top, .toolbar, .skip, .page-head .acts { display: none !important; }
  .shell { grid-template-columns: 1fr; }
  main { padding: 0; max-width: none; }
  body { background: #fff; }
  .card, .kpi, .chart-card, .scroll { break-inside: avoid; box-shadow: none; }
  .print-head { display: block !important; margin-bottom: 18px; padding-bottom: 10px; border-bottom: 2px solid currentColor; }
  tbody tr:hover { background: none; }
}
.print-head { display: none; }

/* ---------------------------------------------------------------- record ---
   Every register on one page. */
.rec-group { margin-top: 22px; }
.rec-group h2 { font-size: 15px; margin: 0 0 8px; display: flex; align-items: baseline; gap: 10px; }
.rec-stage { font-family: var(--display); font-size: 13px; font-weight: 500; color: var(--muted); }
.rec-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr)); gap: 8px; }
.rec {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; text-decoration: none; color: var(--ink);
  background: var(--surface); border: 1px solid var(--card-rule); border-radius: var(--radius-sm); min-width: 0;
}
.rec:hover { border-color: var(--rule-2); }
.rec-ic { color: var(--accent-text); display: flex; flex: none; }
.rec-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rec-l { font-weight: 600; font-size: 14.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rec-s { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.rec-s b { color: var(--ink-2); font-weight: 600; }
.rec-q.warn { color: var(--warn-text); }
.rec-q.danger { color: var(--danger-text); }
.rec-q.ok { color: var(--ok-text); }
.rec-q.muted { color: var(--muted); }

/* -------------------------------------------------------------- position ---
   One card per obligation: ring, stage track, judgement mix, domain tiles. */
.pos-entity { font-size: 17px; margin: 26px 0 10px; }
.pos-obl { display: grid; grid-template-columns: 88px 1fr; gap: 18px; align-items: start; padding: 18px 20px; margin-bottom: 12px; }
.pos-body { min-width: 0; }
.pos-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pos-name { font-family: var(--brand); font-weight: 600; font-size: 16px; color: var(--ink); text-decoration: none; }
.pos-name:hover { text-decoration: underline; }
.pos-fig { margin-inline-start: auto; color: var(--muted); font-size: 13.5px; }
.pos-track { display: flex; align-items: center; margin: 12px 0 4px; }
.pos-dot { display: block; width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--rule-2); background: var(--surface); flex: none; }
.pos-dot.d { background: var(--accent); border-color: var(--accent); }
.pos-dot.h { background: var(--gold); border-color: var(--gold); box-shadow: 0 0 0 4px var(--accent-soft); }
.pos-dot.s { border-style: dashed; }
.pos-dot-l { display: block; line-height: 0; }
.pos-track i { flex: 1; height: 2px; background: var(--rule-2); }
.pos-track i.d { background: var(--accent); }
.pos-track-l { color: var(--muted); font-size: 13px; }
.pos-track-l .here { color: var(--accent-text); font-weight: 600; }
.pos-stack { display: flex; height: 10px; border-radius: 5px; overflow: hidden; background: var(--surface-3); margin: 12px 0 6px; }
.pos-stack b { display: block; height: 100%; }
.pos-legend { display: flex; gap: 12px; flex-wrap: wrap; color: var(--muted); font-size: 13px; align-items: center; }
.pos-legend > span:not(.pos-basis)::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-inline-end: 6px; background: var(--surface-3); }
.pos-legend .k-ok::before { background: var(--ok); } .pos-legend .k-largely::before { background: var(--gold); }
.pos-legend .k-partial::before { background: var(--warn); } .pos-legend .k-not::before { background: var(--danger); } .pos-legend .k-none::before { background: var(--surface-3); }
.pos-basis { margin-inline-start: auto; }
.pos-heat { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 8px; margin-top: 12px; }
.pos-cell { border-radius: 8px; padding: 8px 10px; font-size: 13px; color: var(--ink-2); text-decoration: none; background: var(--surface-3); }
.pos-cell b { display: block; font-family: var(--brand); font-weight: 600; font-size: 15px; color: var(--ink); }
.pos-cell.ok { background: var(--ok-soft); } .pos-cell.accent { background: var(--accent-soft); } .pos-cell.warn { background: var(--warn-soft); }
.q-body { min-width: 0; }
@media (max-width: 640px) { .pos-obl { grid-template-columns: 1fr; } .pos-basis { margin-inline-start: 0; } }

/* ---------------------------------------------------------------- canvas ---
   One object, one layout: judgement, basis, artefacts, related, chain. */
.cv-crumb { color: var(--muted); font-size: 13.5px; margin-bottom: 4px; }
.cv-crumb a { color: var(--muted); text-decoration: none; } .cv-crumb a:hover { text-decoration: underline; }
.cv-title { margin: 0; }
.cv-text { color: var(--ink-2); max-width: 78ch; margin: 6px 0 18px; }
.cv-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-bottom: 14px; }
.cv-block { padding: 16px 18px; }
.cv-block .lbl { display: block; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
.cv-proposal { padding: 16px 18px; margin-bottom: 14px; border-color: var(--accent-line); }
.cv-jud-row { display: flex; align-items: center; gap: 14px; }
.cv-jud { font-family: var(--brand); font-weight: 600; font-size: 17px; margin-bottom: 2px; }
.cv-jud .pill { font-family: var(--display); font-size: 13px; }
.cv-reason { color: var(--ink-2); border-inline-start: 2px solid var(--rule-2); padding-inline-start: 12px; margin: 12px 0 0; max-width: 80ch; white-space: pre-line; }
.cv-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; align-items: center; }
.cv-acts form { display: contents; }
.cv-basis { display: flex; flex-direction: column; }
.cv-brow { display: grid; grid-template-columns: 22px 1fr auto; gap: 8px 10px; padding: 7px 0; border-top: 1px dashed var(--rule); align-items: baseline; }
.cv-brow:first-child { border-top: 0; }
.cv-gl { font-family: var(--mono); color: var(--accent-text); }
.cv-absent .cv-gl, .cv-absent .cv-bt { color: var(--muted); }
.cv-mapping .cv-gl { color: var(--gold); }
.cv-bt { min-width: 0; overflow-wrap: anywhere; }
.cv-says { color: var(--muted); font-size: 13.5px; margin-inline-start: 6px; }
.cv-bm { color: var(--muted); font-size: 13px; max-width: 220px; text-align: end; }
.cv-doc { white-space: pre-wrap; max-width: 80ch; line-height: 1.55; }
.cv-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); gap: 8px; }
.cv-tile { border: 1px solid var(--card-rule); border-radius: 10px; padding: 10px 12px; display: flex; gap: 10px; align-items: flex-start; }
.cv-tic { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; flex: none; font-size: 14px; font-weight: 700; }
.cv-tile.on .cv-tic { background: var(--ok-soft); color: var(--ok-text); }
.cv-tile.off .cv-tic { background: var(--surface-3); color: var(--muted); }
.cv-tile b { display: block; font-size: 13.5px; font-weight: 600; }
.cv-tile span { font-size: 13px; color: var(--muted); }
.cv-kv { display: grid; grid-template-columns: 120px 1fr; gap: 8px 14px; align-items: baseline; }
.cv-kv .k { color: var(--muted); }
.cv-rel { display: flex; gap: 6px; flex-wrap: wrap; }
.cv-rel .pill { text-decoration: none; white-space: normal; max-width: 100%; text-align: start; }
.cv-kv > span { min-width: 0; }
.cv-chain { position: relative; padding-inline-start: 18px; font-size: 13.5px; }
.cv-chain::before { content: ""; position: absolute; inset-inline-start: 5px; top: 8px; bottom: 8px; width: 2px; background: var(--rule-2); }
.cv-chain > div { position: relative; display: grid; grid-template-columns: 110px 1fr auto; gap: 10px; padding: 5px 0; color: var(--ink-2); }
.cv-chain > div::before { content: ""; position: absolute; inset-inline-start: -17px; top: 11px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.cv-chain code { font-size: 12.5px; color: var(--muted); }
.cv-when { color: var(--muted); }
@media (max-width: 1060px) { .cv-grid { grid-template-columns: 1fr; } .cv-kv { grid-template-columns: 1fr; } .cv-chain > div { grid-template-columns: 1fr; } .cv-brow { grid-template-columns: 22px 1fr; } .cv-bm { grid-column: 2; max-width: none; text-align: start; } }

/* ---------------------------------------------------------------- decide ---
   The home is a queue. One figure on the left, the decision in the middle, the actions
   on the right; groups by what outranks what. */
.q-figures { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 4px 0 18px; }
.q-fig { padding: 14px 16px; }
.q-fig .lbl { display: block; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
.q-fig-row { display: flex; align-items: center; gap: 12px; }
.q-big { font-family: var(--brand); font-weight: 700; font-size: 28px; letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.q-big small { font-family: var(--display); font-weight: 500; font-size: 13.5px; color: var(--muted); letter-spacing: 0; margin-inline-start: 5px; }
.q-fig-alert { border-color: var(--danger-line); }
.q-fig .spark { display: block; width: 100%; height: 34px; margin: 6px 0 2px; }
.q-group { margin-top: 18px; }
.q-head { font-family: var(--brand); font-weight: 600; font-size: 14.5px; color: var(--ink-2); margin: 0 0 8px; display: flex; align-items: center; gap: 8px; letter-spacing: 0; }
.q-head small { font-family: var(--display); font-weight: 500; font-size: 13px; color: var(--muted); }
.q-fileall { margin-inline-start: auto; }
.q-fw { padding: 12px 16px 8px; margin-bottom: 18px; }
.q-more { margin-inline-start: auto; font-weight: 500; font-size: 13px; color: var(--accent-text); text-decoration: none; }
.q-fw-row { display: grid; grid-template-columns: minmax(180px, 1.2fr) 3fr minmax(120px, auto); gap: 16px; align-items: center; padding: 10px 0; border-top: 1px solid var(--rule); text-decoration: none; color: inherit; }
.q-fw-row:first-of-type { border-top: 0; }
.q-fw-row:hover .q-fw-name b { color: var(--accent-text); }
.q-fw-name b { display: block; font-size: 14.5px; font-weight: 600; }
.q-fw-name span { font-size: 13px; color: var(--muted); }
.q-fw-bar { height: 8px; border-radius: 999px; background: var(--rule); overflow: hidden; }
.q-fw-bar i { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.q-fig .q-fw-bar { margin: 8px 0 6px; }
.q-fw-num { text-align: end; font-variant-numeric: tabular-nums; }
.q-fw-num b { display: block; font-family: var(--brand); font-size: 18px; font-weight: 700; }
.q-fw-num span { font-size: 12.5px; color: var(--muted); }
@media (max-width: 640px) { .q-fw-row { grid-template-columns: 1fr; gap: 6px; } .q-fw-num { text-align: start; } }
.q-sug { padding: 12px 16px 6px; margin-bottom: 18px; border-color: var(--accent-line); }
.q-sug .q-head { margin-bottom: 4px; }
.q-sug-row { display: flex; align-items: center; gap: 14px; padding: 8px 0; border-top: 1px solid var(--rule-2); }
.q-sug-row > div { flex: 1; min-width: 0; }
.q-sug-text { font-size: 14.5px; }
@media (max-width: 640px) { .q-sug-row { flex-wrap: wrap; } }
.qi { display: grid; grid-template-columns: 56px 1fr auto; gap: 14px; align-items: center; padding: 12px 16px; margin-bottom: 8px; }
.qi-urgent { border-color: var(--danger-line); box-shadow: var(--shadow-md); }
.qi .ring { display: block; }
.q-who {
  width: 44px; height: 44px; margin: auto; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-text); font-weight: 600; font-size: 14px;
}
.q-ttl { font-weight: 600; font-size: 14.5px; }
.q-ttl a { color: var(--ink); text-decoration: none; }
.q-ttl a:hover { text-decoration: underline; }
.q-ttl small { font-weight: 500; font-size: 13.5px; color: var(--muted); margin-inline-start: 8px; }
.q-ttl .pill { margin-inline-start: 8px; vertical-align: 1px; }
.q-basis { color: var(--muted); font-size: 13.5px; margin-top: 3px; }
.q-basis .sep { opacity: .6; }
.q-acts { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.q-acts form { display: contents; }
.q-empty { color: var(--muted); margin-top: 18px; }
@media (max-width: 900px) { .q-figures { grid-template-columns: 1fr; } }
/* A PHONE SHOWS DECIDE, A CANVAS AND THE COMMAND BAR.

   Approvals only: file, approve, request, record. The chrome keeps the Decide tab and the
   tab of the place you are on; Position and Record are a tap away in the bar, which is
   an icon here. Figures go — Decide's tiles, Position's rings, tracks, bars and heat —
   because a chart read on a phone is a chart misread; the sentence beside each stays. */
@media (max-width: 640px) {
  .ttabs { margin-inline-start: 0; overflow: hidden; }
  .ttab { padding: 0 12px; }
  .ttab:not(.on):not(:first-child) { display: none; }
  .tbar-right { gap: 4px; padding: 0 6px; }
  .tbar .live { display: none; }
  .tbar details.menu > summary { padding: 4px; }
  .tbar details.menu .chev { display: none; }
  .cmdk-scrim { padding: 8px; }
  .cmdk ul { max-height: 60vh; }

  .q-figures { display: none; }
  .qi { grid-template-columns: 44px 1fr; }
  .qi .ring { width: 44px; height: 44px; }
  .q-acts { grid-column: 1 / -1; justify-content: flex-start; }

  .pos-fig, .pos-track, .pos-stack, .pos-legend > span:not(.pos-basis), .pos-heat, .pos-line svg { display: none; }
  .pos-obl > .ring { display: none; }
  .pos-obl { grid-template-columns: 1fr; }
  .page-head .acts { flex-wrap: wrap; }

  .cv-jud-row { align-items: flex-start; flex-wrap: wrap; }
  .cv-jud-row > * { min-width: 0; }
  .rm-imm-body { overflow-x: hidden; }
  .cv-tiles { grid-template-columns: 1fr; }
  .cv-acts { flex-wrap: wrap; }
}

.draft-note {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border: 1px dashed var(--accent); border-radius: var(--radius-sm);
  padding: 9px 12px; margin-bottom: 8px; font-size: 14px;
  background: var(--surface-2); color: var(--card-ink-2);
}
.filter-status { font-size: 13.5px; color: var(--muted); }

/* ---- journeys ----------------------------------------------------------------
   The level, the ring and the ladder carry the state. Words are the exception here,
   not the medium: a stage list with a paragraph each is the wall of text nobody reads. */
/* The disclosure holding everything beyond the first six links. Closed on a new
   workspace, open once the programme is under way — the product grows with the
   customer instead of arriving all at once. */
.nav-more { border-top: 1px solid var(--rule); margin-top: 6px; padding-top: 4px; }
.nav-more > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 9px 12px; margin: 0 6px; cursor: pointer; list-style: none;
  font-size: 13.5px; font-weight: 640; letter-spacing: .002em;
  color: var(--muted); border-radius: var(--radius-sm);
}
.nav-more-n { margin-inline-start: auto; margin-inline-end: 6px; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary:hover { background: var(--surface-2); color: var(--ink-2); }
.nav-more > summary svg { transition: transform .18s ease; flex: 0 0 auto; }
.nav-more[open] > summary svg { transform: rotate(180deg); }

/* One assessment row: the two fields filled on every control get the width, the two
   filled on some of them sit beneath, and every one of them has a visible label. A
   placeholder disappears the moment somebody types, so a row half-filled becomes four
   boxes nobody can tell apart — on the screen where a customer spends more time than
   anywhere else in the product. */
.assess-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 10px; align-items: end; }
.ar-field { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ar-field.ar-wide { grid-column: span 2; }
.ar-field > label { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.ar-hint { font-weight: 400; color: var(--muted); opacity: .85; }
.ar-field select, .ar-field input { width: 100%; padding: 6px 8px; font-size: 14px; }
.ar-save { grid-column: span 2; display: flex; justify-content: flex-end; }
@media (min-width: 1100px) {
  /* Room for the reason to sit beside the status rather than under it. */
  .assess-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); }
  .ar-field.ar-wide { grid-column: span 1; }
}

/* What to go and assess, in the row where somebody is answering it. Inline, because a
   person who does not know what a control means will guess rather than click. */
.assess-hint { margin: 6px 0 0; font-size: 14px; line-height: 1.5; color: var(--ink-2); max-width: 62ch; }
.assess-evidence { margin-top: 6px; }
.assess-evidence > summary { font-size: 13px; color: var(--muted); cursor: pointer; }
.assess-evidence ul { margin: 6px 0 0; padding-inline-start: 18px; font-size: 13px; color: var(--muted); }
.assess-evidence li { margin-bottom: 2px; }

/* What the automation currently says, in the row where somebody is about to rate it.
   A verdict the machine reached is only worth anything next to the box it should fill;
   on a page of its own it is one more place to go and look.

   The colour sits on a left edge rather than a filled panel. Twenty-eight of these down
   one page, each tinted, and the screen reads as an alarm board instead of an assessment.
   The reading underneath is what actually decides the rating, so it gets the contrast and
   the edge only gets the hint. */
.auto { border-inline-start: 3px solid var(--rule); background: var(--surface-2);
  border-radius: 0 var(--radius) var(--radius) 0; padding: 8px 10px; margin: 0 0 10px; font-size: 13.5px; }
.auto.pass { border-inline-start-color: var(--ok); }
.auto.fail { border-inline-start-color: var(--danger); }
.auto.mixed { border-inline-start-color: var(--warn); }
.auto-line { display: flex; flex-wrap: wrap; gap: 2px 8px; align-items: baseline; }
.auto-line b { font-weight: 650; }
.auto.pass .auto-line b { color: var(--ok); }
.auto.fail .auto-line b { color: var(--danger); }
.auto.mixed .auto-line b { color: var(--warn); }
.auto-when { color: var(--muted); }
.auto details { margin-top: 6px; }
.auto summary { color: var(--muted); cursor: pointer; }
.auto ul { margin: 6px 0 0; padding-inline-start: 16px; list-style: none; }
.auto li { margin-bottom: 5px; color: var(--ink-2); line-height: 1.45; padding-inline-start: 14px; text-indent: -14px; }
.auto li .auto-src { color: var(--muted); }
.auto-note { margin: 6px 0 0; color: var(--ink-2); }

/* Two figures side by side where there is room, stacked where there is not. */
.org-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 22px; align-items: start; }

/* A proposed message in the evidence queue. Amber while undecided. */
.mail-card { border: 1px solid var(--rule); border-left: 3px solid var(--rule); border-radius: var(--radius);
  background: var(--surface); padding: 14px 16px 16px; margin-bottom: 14px; }
.mail-card.pending { border-left-color: var(--warn); }
.mail-card.accepted { border-left-color: var(--ok); }
.mail-snippet { margin: 8px 0 0; padding-inline-start: 10px; border-inline-start: 2px solid var(--rule); }
.mail-files { list-style: none; margin: 12px 0 0; padding: 0; }
.mail-files li { padding: 8px 10px; border: 1px solid var(--rule); border-radius: 6px; margin-bottom: 6px; }
.mail-files .mono { font-family: var(--mono, ui-monospace, monospace); font-size: 12px; }
.mail-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 16px;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--rule); }

/* The licence: a proportion, read at a glance, where a date has to be worked out. */
.lic-head { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
.lic-ring { --pct: 100; flex: 0 0 auto; width: 132px; height: 132px; border-radius: 50%;
  background: conic-gradient(var(--ring) calc(var(--pct) * 1%), var(--rule) 0);
  display: grid; place-items: center; --ring: var(--ok); }
.lic-ring.warn { --ring: var(--warn); }
.lic-ring.danger { --ring: var(--danger); }
.lic-ring-in { width: 104px; height: 104px; border-radius: 50%; background: var(--surface);
  display: grid; place-items: center; text-align: center; line-height: 1.15; }
.lic-ring-in strong { display: block; font-size: 30px; font-variant-numeric: tabular-nums; }
.lic-ring-in span { display: block; font-size: 12px; color: var(--muted); }
.lic-facts { flex: 1 1 320px; min-width: 0; }
.seat-bar { height: 8px; border-radius: 4px; background: var(--rule); overflow: hidden; margin-bottom: 18px; }
.seat-bar > span { display: block; height: 100%; background: var(--ok); }
.seat-bar > span.over { background: var(--danger); }

/* ------------------------------------------------------------ journey line ---
   A metro line, not a row of cards. The order matters, you cannot skip a station, and
   the distance left is the point — all three of which a card grid throws away. */
.jl { display: block; overflow: visible; }
.jl-rail { stroke: var(--surface-3); stroke-width: 4; stroke-linecap: round; }
.jl-run {
  stroke: var(--accent); stroke-width: 3; stroke-linecap: round;
  /* One path, dashed to its own length, offset back by the untravelled remainder. A
     dash offset cannot run across separate elements, which is why the rail and the run
     are two full-width lines rather than one segment per gap. */
  stroke-dasharray: var(--len); stroke-dashoffset: var(--len);
  animation: jl-draw .9s cubic-bezier(.22,.61,.36,1) .1s forwards;
}
@keyframes jl-draw { to { stroke-dashoffset: calc(var(--len) - var(--run)); } }

.jl-dot { fill: var(--surface); stroke: var(--rule-2); stroke-width: 2.5; }
.jl-done .jl-dot { fill: var(--accent); stroke: var(--accent); }
.jl-here .jl-dot { fill: var(--accent); stroke: var(--accent); }
.jl-halo { fill: var(--accent); opacity: .18; transform-origin: center; animation: jl-pulse 2.6s ease-in-out infinite; }
@keyframes jl-pulse { 0%,100% { r: 12; opacity: .16; } 50% { r: 15; opacity: .28; } }

/* Where you are is the whole question this line answers, so the station you are on is
   said in ink at a readable size and the ones you have not reached stay quiet. 10.5px
   grey for every station answered it in a whisper. */
.jl-num { font-size: 12px; fill: var(--muted); font-weight: 600; }
.jl-lab { font-size: 12.5px; fill: var(--muted); }
.jl-done .jl-num, .jl-done .jl-lab { fill: var(--ink-2); }
.jl-here .jl-num { fill: var(--accent); font-weight: 700; }
.jl-here .jl-lab { fill: var(--ink); font-weight: 660; font-size: 13.5px; }
/* Stations fade in along the line, so the eye travels the route in the direction of
   travel instead of landing on the highlighted one and working outwards. */
.jl-st { opacity: 0; animation: jl-in .38s ease-out forwards; animation-delay: calc(.12s + var(--i) * .07s); }
@keyframes jl-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

.stage-go { margin-top: 12px; }
/* The stage you are on gets the solid button; the rest get the quiet one, so the page
   has one obvious next action instead of twelve competing ones. */

.jl-say { margin: 2px 0 14px; font-size: 14.5px; color: var(--ink-2); max-width: 72ch; }
.jl-say strong { color: var(--ink); font-weight: 640; }

/* ------------------------------------------------------------ journey lanes ---
   Every obligation on one shared scale, furthest-behind first. The ragged left edge is
   the finding: reading six cards and comparing their level pills is not. */
.lanes { display: flex; flex-direction: column; gap: 2px; }
.lane {
  display: grid; align-items: center; gap: 0 14px; padding: 10px 12px;
  grid-template-columns: 76px minmax(120px, 1.1fr) minmax(140px, 2fr) 84px;
  border-radius: 8px; text-decoration: none; color: inherit;
  opacity: 0; animation: lane-in .34s ease-out forwards; animation-delay: calc(var(--i) * .05s);
}
a.lane:hover { background: var(--surface-2); color: inherit; }
a.lane:hover .lane-n { color: var(--accent-text); }
@keyframes lane-in { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }

.lane-k { font-size: 11px; color: var(--muted); text-transform: lowercase; }
.lane-n { font-size: 14px; font-weight: 620; letter-spacing: -.01em; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lane-track { position: relative; height: 8px; border-radius: 4px; background: var(--surface-3); }
.lane-fill {
  position: absolute; inset-block: 0; inset-inline-start: 0; border-radius: 4px;
  width: 0; animation: lane-fill .8s cubic-bezier(.22,.61,.36,1) .15s forwards;
}
@keyframes lane-fill { to { width: var(--w); } }
/* PROGRESS IS ONE COLOUR. THE LENGTH IS THE MESSAGE.
   These were amber in the middle of the scale and red at the start, so a roadmap where
   everything was moving normally rendered as a column of warnings — and being at level 3
   of 7 on a framework nobody has finished is not a problem, it is Tuesday.
   Colour now means something is wrong, and nothing on this bar is wrong; how far the fill
   reaches says how far you have got, which is the question the bar exists to answer.
   Level is also printed beside every bar, so the scale never rests on colour alone. */
.lane-fill.good { background: var(--accent); }
.lane-fill.mid  { background: var(--accent); opacity: .82; }
.lane-fill.low  { background: var(--accent); opacity: .62; }
.lane-fill.none { background: transparent; }
/* One tick per level, so the bar is readable as a POSITION on a scale and not as a
   percentage somebody has to reverse-engineer. */
.lane-tick {
  position: absolute; top: -3px; width: 1px; height: 14px; background: var(--rule-2);
  transform: translateX(-50%); opacity: .8;
}
.lane-tick.on { background: transparent; }
.lane-lv { font-size: 12.5px; color: var(--muted); text-align: end; font-variant-numeric: tabular-nums; }

@media (max-width: 720px) {
  .lane { grid-template-columns: 1fr auto; gap: 4px 10px; }
  .lane-k { grid-column: 1 / -1; }
  .lane-track { grid-column: 1 / -1; margin-top: 4px; }
}

/* Somebody who turns motion off gets the same information, immediately — the final
   state, not a missing one. */
@media (prefers-reduced-motion: reduce) {
  .jl-run { animation: none; stroke-dashoffset: calc(var(--len) - var(--run)); }
  .jl-halo { animation: none; }
  .jl-st, .lane { animation: none; opacity: 1; }
  .lane-fill { animation: none; width: var(--w); }
}

/* Inventory as one dense line, not ten boxes.
   These are reference figures — how many entities, how many records — and reference does
   not need a call to action's worth of room. The things that DO need action keep the
   tiles, and only when they are non-zero. */
.counts { display: flex; flex-wrap: wrap; gap: 0 28px; align-items: baseline; }
.count { display: flex; align-items: baseline; gap: 7px; padding: 6px 0; text-decoration: none; color: inherit; }
.count b { font-size: 22px; font-weight: 640; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.count span { font-size: 14px; color: var(--muted); }
/* Lime is a fill colour, unreadable as 22px text on a light strip. Hover darkens
   rather than tints. */
.count:hover b { color: var(--card-ink); }
.count:hover span { color: var(--card-ink-2); }

/* Nothing outstanding is a sentence, not ten zeroes. */
.all-clear {
  margin: 16px 0 0; display: flex; align-items: baseline; gap: 9px;
  font-size: 14.5px; color: var(--ink-2);
}
.all-clear span { color: var(--ok-text); font-weight: 700; }

/* An instrument's own articles, grouped by chapter.
   Two columns of short lines rather than a table: an article list is scanned for a
   number, and a table's borders add nothing to ninety-nine two-word rows. */
/* ---------------------------------------------------- dependency diagram ----
   Every value here is a token, so the diagram inherits the theme rather than
   carrying a palette of its own. The one thing that is NOT theme-dependent is the
   distinction between a solid and a dashed edge: it separates a fact from a
   proposal, so it must survive a colour-blind reader, a greyscale print and a
   screenshot pasted into a board pack. That is why it is stroke-dasharray and not
   a hue. */
.dg { margin: 0 0 20px; border: 1px solid var(--rule); border-radius: 10px; overflow: hidden; background: var(--panel); }
.dg-cap { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; padding: 9px 14px;
  border-bottom: 1px solid var(--rule); font-size: 13px; color: var(--muted); }
.dg-legend { display: inline-flex; align-items: center; gap: 7px; }
.dg-swatch { display: inline-block; width: 22px; height: 0; border-top: 2px solid var(--ink-2); }
.dg-s-solid { border-top-style: solid; }
.dg-s-dash { border-top-style: dashed; }
/* The group the script pans and zooms. It carries no paint of its own; the rule exists so
   the transform has a named owner rather than being applied to an unstyled wrapper. */
.dg-pan { transform-origin: 0 0; }
/* Hidden until the view has actually been moved, so the control appears when it becomes
   true rather than sitting there inert. */
.dg-reset { margin-inline-start: 8px; }
.dg-hint { margin-inline-start: auto; }
.dg-svg { display: block; width: 100%; height: 540px; touch-action: none; cursor: grab; }
.dg-svg.dg-dragging { cursor: grabbing; }
.dg-edge { stroke: var(--rule-2); stroke-width: 1.6; fill: none; }
.dg-proposed { stroke-dasharray: 5 4; opacity: .75; }
.dg-head { fill: var(--rule-2); }
.dg-edges, .dg-nodes { pointer-events: auto; }
.dg-node { cursor: pointer; }
/* The click target: bigger than the glyph, invisible, and the reason a node is easy to
   hit on a touch screen where the shape itself is 26 pixels across. */
.dg-hit circle { fill: transparent; }
.dg-head-proposed { opacity: .75; }
.dg-halo { fill: var(--panel); stroke: var(--rule); stroke-width: 1; }
.dg-glyph { fill: none; stroke: var(--ink-2); stroke-width: 1.7; stroke-linejoin: round; }
.dg-label { fill: var(--muted); font-size: 12.5px; text-anchor: middle; paint-order: stroke;
  stroke: var(--panel); stroke-width: 3px; }
.dg-focus .dg-halo { stroke: var(--accent); stroke-width: 2; }
.dg-focus .dg-glyph { stroke: var(--accent); stroke-width: 2.1; }
.dg-focus .dg-label { fill: var(--ink); font-weight: 650; }
.dg-missing .dg-glyph { stroke-dasharray: 3 3; opacity: .6; }
.dg-missing .dg-label { font-style: italic; }
/* Hover and keyboard focus dim everything that is not adjacent, which is how a path
   is read out of a dense graph. Driven by a class the script adds, so with scripting
   off nothing is ever dimmed. */
.dg-lit .dg-node:not(.dg-on) { opacity: .22; }
.dg-lit .dg-edge:not(.dg-on) { opacity: .12; }
.dg-node:focus-visible .dg-halo { stroke: var(--accent); stroke-width: 2.5; }
.dg-node:focus { outline: none; }
@media (prefers-reduced-motion: no-preference) {
  .dg-node, .dg-edge { transition: opacity .12s ease; }
}
@media (max-width: 780px) { .dg-svg { height: 340px; } .dg-hint { display: none; } }

.articles { columns: 2; column-gap: 34px; }
@media (max-width: 780px) { .articles { columns: 1; } }
.art-chapter { break-inside: avoid-column; margin-bottom: 18px; }
.art-chapter h3 { font-size: 13px; letter-spacing: .01em; color: var(--muted); font-weight: 650; margin: 0 0 6px; }
.art-chapter ul { list-style: none; margin: 0; padding: 0; }
.art-chapter li { display: flex; gap: 9px; padding: 3px 0; font-size: 14px; line-height: 1.45; }
.art-ref { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 46px; }
.art-title { color: var(--ink-2); min-width: 0; }

/* ---------------------------------------------------------------- the route ---
   Seven levels as one continuous track. Consecutive by construction, so the fill runs
   to a single point and the rungs sit on top of it. */
.rr {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 18px 20px 14px; margin-bottom: 20px; box-shadow: var(--shadow-md);
}
.rr-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.rr-now { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rr-now strong { font-size: 21px; font-weight: 660; letter-spacing: -.022em; }
.rr-count { font-size: 13.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.rr-meaning { margin: 3px 0 0; font-size: 14.5px; color: var(--ink-2); max-width: 70ch; line-height: 1.5; }
.rr-track {
  position: relative; height: 6px; border-radius: 3px; background: var(--surface-3);
  margin: 0 0 0 0; overflow: hidden;
}
.rr-fill { height: 100%; background: var(--accent); border-radius: 3px; }
.rr-rungs {
  list-style: none; display: grid; margin: 0; padding: 0;
  grid-auto-flow: column; grid-auto-columns: 1fr;
}
.rr-rungs li { position: relative; padding-top: 12px; text-align: center; min-width: 0; }
.rr-dot {
  position: absolute; top: -9px; inset-inline-start: 50%; transform: translateX(-50%);
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--surface-3);
}
.rr-rungs li.done .rr-dot { background: var(--accent); border-color: var(--accent); }
.rr-rungs li.here .rr-dot {
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.rr-label { font-size: 12.5px; color: var(--muted); display: block; line-height: 1.3; overflow-wrap: anywhere; }
.rr-rungs li.done .rr-label { color: var(--ink-2); }
.rr-rungs li.here .rr-label { color: var(--ink); font-weight: 640; }
.rr-next { margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--rule); font-size: 14px; color: var(--ink-2); }
.rr-next span { color: var(--muted); font-weight: 640; margin-inline-end: 6px; }
/* Below roughly a phone the seven labels stop being readable side by side. The track and
   the dots still carry the shape, so the labels step back to the current one only. */
@media (max-width: 640px) {
  .rr-rungs li .rr-label { display: none; }
  .rr-rungs li.here .rr-label { display: block; }
}

/* ------------------------------------------------------------ needs you now ---
   The only dark surface in the console. It holds what is late and nothing else, and it
   replaces a stack of pale flash boxes that competed with each other and lost to the
   white space around them. */
.now-panel {
  background: var(--alarm); color: var(--alarm-ink); border-radius: var(--radius);
  padding: 18px 20px 16px; margin-bottom: 20px; box-shadow: var(--shadow-md);
}
/* The heading is set in the display face here and nowhere else at this size. The panel
   is an interruption, and an interruption should not look like the section headings it
   is interrupting. */
.now-panel > h2 {
  font-family: var(--display); margin: 0 0 3px; font-size: 20px;
  color: var(--alarm-ink); letter-spacing: -.014em; font-weight: 600;
}
.now-panel > p { margin: 0 0 14px; font-size: 14px; color: var(--alarm-muted); max-width: 70ch; }
.now-list { display: grid; gap: 1px; background: var(--alarm-line); border-radius: var(--radius-sm); overflow: hidden; }
.now-item {
  display: flex; align-items: center; gap: 14px; background: var(--alarm);
  padding: 11px 14px; text-decoration: none; color: var(--alarm-ink);
}
.now-item:hover { background: var(--alarm-line); color: var(--alarm-ink); }
.now-panel a:focus-visible { outline: 2px solid var(--alarm-ink); outline-offset: 2px; }
.now-item .n {
  font-size: 21px; font-weight: 660; font-variant-numeric: tabular-nums;
  min-width: 2.2ch; text-align: end; letter-spacing: -.02em;
}
.now-item .t { flex: 1 1 auto; min-width: 0; font-size: 14.5px; line-height: 1.4; }
.now-item .t small { display: block; color: var(--panel-muted); font-size: 13px; margin-top: 1px; }
.now-item .go { flex: none; font-size: 13.5px; color: var(--panel-muted); }
.now-item:hover .go { color: var(--panel-ink); }
.now-item.late .n { color: #ff9f93; }
.now-item.due .n { color: #7de9d6; }
/* Nothing late is a state worth drawing rather than an absent panel: an empty console
   and a console with nothing outstanding look identical otherwise. */
.now-clear { display: flex; align-items: center; gap: 10px; font-size: 14.5px; color: var(--panel-muted); }
.now-clear strong { color: var(--panel-ink); font-weight: 600; }

/* A row kept for the record rather than in use. */
tr.muted-row > td { opacity: .62; }

/* SoA progress: which sections are answered, which are still amber. */
.soa-sections { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); gap: 12px; }
.soa-sec { display: block; text-decoration: none; color: inherit; border: 1px solid var(--rule); border-left: 3px solid var(--rule); border-radius: var(--radius); background: var(--surface); padding: 11px 13px 13px; }
.soa-sec:hover { border-color: var(--accent); }
.soa-sec.open { border-left-color: var(--warn); }
.soa-sec.done { border-left-color: var(--ok); }
.soa-sec .bar { margin-top: 9px; height: 5px; border-radius: 3px; background: var(--rule); overflow: hidden; }
.soa-sec .bar > span { display: block; height: 100%; background: var(--ok); }
.soa-sec.open .bar > span { background: var(--warn); }
/* An unfinished control, marked where the reader is already looking. */
tr.row-todo > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

/* The connector catalogue: what this can read, and from where. */
.cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(270px, 100%), 1fr)); gap: 14px; }
.cat-group { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface); padding: 13px 15px 15px; }
.cat-group h3 { font-size: 13.5px; letter-spacing: .01em; color: var(--muted); font-weight: 650; margin: 0 0 10px; }
.cat-item + .cat-item { margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--rule); }
.cat-item .note { margin-top: 3px; }
/* My work: plain lists, the late group tinted, nothing to configure. */
.mw-group { margin-top: 26px; }
.mw-group h2 { font-size: 15px; margin: 0 0 4px; }
.mw-n { font-weight: 500; color: var(--muted); margin-inline-start: 6px; font-variant-numeric: tabular-nums; }
.mw-list { list-style: none; margin: 10px 0 0; padding: 0; }
.mw-list li { display: flex; justify-content: space-between; gap: 14px; padding: 8px 0; border-top: 1px solid var(--rule); }
.mw-list li:first-child { border-top: 0; }
.mw-list code { margin-inline-end: 6px; }
.mw-meta { color: var(--muted); font-size: 13.5px; }
.mw-list.is-late li > a:first-child { color: var(--danger); }
.mw-list.is-soon li > a:first-child { color: var(--warn); }
/* Intake: what the guidance names and the document does or does not, and the refresh chosen at attachment. */
.ev-check { margin-top: 4px; }
.ev-named { color: var(--ok); }
.ev-unnamed { color: var(--warn); }
.ev-cadence { display: inline-flex; align-items: center; gap: 6px; font-size: 13.5px; color: var(--muted); }
.ev-cadence select { font-size: 13.5px; }
/* The routing review: one block per entity. */
.routing-entity { margin: 0 0 30px; padding: 0 0 18px; border-bottom: 1px solid var(--rule); }
.routing-entity h3 { font-size: 14px; margin: 18px 0 6px; }
/* The bulk row under a register: tick rows, give them an owner and a date, one post. */
.bulk-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
/* A workspace's own controls: the mappings and proposals beside each. */
.own-map { list-style: none; margin: 0; padding: 0; }
.own-map li { padding: 3px 0; }
/* The evidence state of an assessment row, under its title. */
.ev-state { margin-top: 6px; }
.ev-none { font-size: 13.5px; color: var(--muted); }
/* Connect first: a ranked list, the number doing the arguing rather than a logo. */
ol.connect-first { list-style: none; margin: 0; padding: 0; counter-reset: cf; }
ol.connect-first li { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 10px 0; border-top: 1px solid var(--rule); counter-increment: cf; }
ol.connect-first li:first-child { border-top: 0; padding-top: 2px; }
ol.connect-first li > div:first-child::before { content: counter(cf) "."; color: var(--muted); font-variant-numeric: tabular-nums; margin-inline-end: 8px; }

.entity-blocks { display: flex; flex-direction: column; gap: 26px; }
.entity-block { display: flex; flex-direction: column; gap: 12px; }
.eb-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding-bottom: 8px; border-bottom: 1px solid var(--rule);
}
.eb-head h2 { font-size: 19px; margin: 0; }

.journey-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 14px;
}
.journey-card {
  display: flex; gap: 14px; align-items: center; text-decoration: none; color: inherit;
  border: 1px solid var(--rule); border-radius: var(--radius); background: var(--surface);
  padding: 16px; transition: border-color .16s, transform .16s;
}
.journey-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.journey-card .jc-ring { flex: 0 0 auto; width: 78px; }
.journey-card .jc-ring svg { width: 78px; height: 78px; }
.journey-card .jc-body { min-width: 0; }
.journey-card h3 { font-size: 17px; margin: 2px 0 0; }

.level-banner {
  display: flex; gap: 22px; align-items: center; flex-wrap: wrap;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface); padding: 20px 22px;
}
.level-banner .lb-body { min-width: 0; flex: 1 1 320px; }
.lb-level { font-size: 22px; font-weight: 600; letter-spacing: -.01em; }
.lb-meaning { color: var(--muted); font-size: 14px; margin: 2px 0 0; }
.lb-nudge { color: var(--accent-text); font-size: 14.5px; font-weight: 500; margin: 8px 0 0; }

/* The ladder. Seven rungs, and the reached ones are filled — the point is that the
   next one is visibly one step away rather than a number on a progress bar. */
.ladder { display: flex; list-style: none; margin: 14px 0 0; padding: 0; gap: 4px; flex-wrap: wrap; }
.ladder li {
  display: flex; align-items: center; gap: 6px; flex: 1 1 auto;
  padding: 6px 9px; border-radius: var(--radius-sm); font-size: 12.5px;
  border: 1px solid var(--card-rule); color: var(--card-muted); background: var(--surface-2);
  white-space: nowrap;
}
.ladder li .n {
  display: inline-grid; place-items: center; width: 17px; height: 17px; border-radius: 50%;
  font-size: 11px; font-weight: 600; background: var(--rule); color: var(--ink-2);
}
.ladder li.passed { border-color: var(--accent-soft); background: var(--accent-soft); color: var(--accent-text); }
/* --accent-ink, not --surface. --surface is what a card is PAINTED; on the accent it
   measured 3.29:1 at 10px in dark mode, where the floor is 4.5. */
.ladder li.passed .n { background: var(--accent); color: var(--accent-ink); }
.ladder li.here {
  border-color: var(--accent); background: var(--surface); color: var(--card-ink); font-weight: 600;
}
.ladder li.here .n { background: var(--accent); color: var(--accent-ink); }

.stages { display: flex; flex-direction: column; gap: 12px; margin-top: 18px; }
.stage {
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface); padding: 16px 18px 18px;
}
.stage.is-next { border-color: var(--accent); }
.stage.is-done { background: var(--surface-2); }
.stage-head { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.stage-head h2 { font-size: 17px; margin: 0; }
/* The stage number reuses the class name of the first-run guide's step CARD, which is
   a 34px-column grid with 14px/16px padding. The more specific selector wins on the
   properties it names — and silently inherits the ones it does not. Under
   border-box that padding leaves a 26px badge a NEGATIVE content box, so the digit was
   squeezed out of its own circle. Reset explicitly rather than relying on what happens
   to be overridden. */
.stage .step {
  display: inline-grid; place-items: center; width: 26px; height: 26px; flex: 0 0 auto;
  padding: 0; gap: 0; grid-template-columns: none; align-items: center;
  border-radius: 50%; background: var(--surface-2); border: 1px solid var(--rule);
  font-size: 13px; font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums;
}
/* Same fault as .ladder li.passed .n above, and the same fix. */
.stage.is-done .step { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.stage .sub { margin: 0 0 8px; font-size: 14px; }
.stage .note + .note { margin-top: 3px; }

@media (max-width: 700px) {
  .ladder li .t { display: none; }
  .ladder li { flex: 0 0 auto; }
}

/* ---- command palette ----------------------------------------------------------
   Hidden until asked for, and absent entirely without JavaScript — the sidebar is
   still the way to everything, so this only ever adds a faster route. */
.cmdk-scrim {
  position: fixed; inset: 0; background: rgba(15, 26, 21, .38);
  display: grid; place-items: start center; padding: 12vh 16px 16px; z-index: 60;
}
.cmdk {
  width: 100%; max-width: 560px; background: var(--surface);
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  box-shadow: 0 24px 64px -24px rgba(15, 26, 21, .45), 0 0 0 1px var(--rule);
  overflow: hidden;
}
.cmdk input {
  width: 100%; border: 0; border-bottom: 1px solid var(--rule);
  padding: 15px 17px; font-size: 15.5px; background: transparent; color: var(--ink);
  border-radius: 0;
}
.cmdk input:focus { outline: none; box-shadow: none; }
.cmdk ul { list-style: none; margin: 0; padding: 6px; max-height: 46vh; overflow-y: auto; }
.cmdk li { border-radius: var(--radius-sm); }
.cmdk li a {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  padding: 9px 11px; color: var(--ink); text-decoration: none; font-size: 14px;
}
.cmdk li[aria-selected="true"] { background: var(--accent-soft); }
.cmdk li[aria-selected="true"] a { color: var(--accent-text); }
.cmdk .grp { font-size: 12.5px; color: var(--muted); flex-shrink: 0; }
.cmdk li a small { flex: 1; min-width: 0; font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk .none { padding: 16px 17px; color: var(--muted); font-size: 14.5px; }
.cmdk .foot {
  border-top: 1px solid var(--rule); padding: 8px 13px; font-size: 12.5px; color: var(--muted);
  display: flex; gap: 14px; flex-wrap: wrap;
}
.cmdk kbd {
  font: inherit; font-size: 12px; border: 1px solid var(--rule-2); border-bottom-width: 2px;
  border-radius: 4px; padding: 0 4px; background: var(--surface-2); color: var(--ink-2);
}
/* THE COMPANION. A drawer beside whatever the person is looking at, framed from this origin
   and no other, so the assistant's own page and script run unchanged inside it. */
.cmp {
  position: fixed; top: var(--top-h); inset-inline-end: 0; bottom: 0; width: min(440px, 100%);
  background: var(--surface); border-inline-start: 1px solid var(--rule-2); box-shadow: -12px 0 32px -20px rgba(11, 42, 74, .35);
  display: flex; flex-direction: column; z-index: 45;
}
.cmp[hidden] { display: none; }
.cmp-bar { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid var(--rule); font-family: var(--brand); }
.cmp-close { line-height: 1; }
.cmp-frame { flex: 1; width: 100%; border: 0; background: var(--bg); }
/* Inside the frame. */
.cmp-body { margin: 0; padding: 12px 14px 14px; background: var(--bg); color: var(--ink); min-height: 100vh; display: flex; flex-direction: column; gap: 10px; }
.cmp-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 14px; }
.cmp-ctx { color: var(--muted); }
.cmp-links { font-size: 13px; white-space: nowrap; }
.cmp-steps { flex: 1; overflow-y: auto; min-height: 0; }
.ask { font-size: 13px; color: var(--accent-text); text-decoration: none; white-space: nowrap; padding: 4px 8px; border-radius: 999px; border: 1px solid var(--accent-line); }
.ask:hover { background: var(--accent-soft); }
.cv-title-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.cv-title-row .cv-title { margin: 0; }
.cmp-next { padding: 12px 14px; border-color: var(--accent-line); }
.cmp-next p { margin: 6px 0 10px; font-size: 14.5px; }
.cmp-qs { display: grid; gap: 6px; margin-top: 12px; }
.cmp-qs .lbl { margin-bottom: 2px; }
.cmp-q button { width: 100%; text-align: start; font: inherit; font-size: 14px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--rule); background: var(--surface); color: var(--ink); cursor: pointer; }
.cmp-q button:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.cmp-live { display: flex; align-items: center; gap: 8px; }
.dots { display: inline-flex; gap: 3px; }
.dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); animation: dots 1.2s infinite ease-in-out; }
.dots i:nth-child(2) { animation-delay: .2s; }
.dots i:nth-child(3) { animation-delay: .4s; }
@keyframes dots { 0%, 80%, 100% { opacity: .25; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-2px); } }
@media (prefers-reduced-motion: reduce) { .dots i { animation: none; opacity: .7; } }
.pos-line { margin: 12px 0 4px; }
.pos-line svg { display: block; width: 100%; height: auto; overflow: visible; }
.pos-line-grid { stroke: var(--rule-2); stroke-width: 1; stroke-dasharray: 2 3; }
.pos-line-path { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.pos-line-end { fill: var(--accent); stroke: var(--surface); stroke-width: 2; }
.pos-line-axis { display: flex; justify-content: space-between; align-items: baseline; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.pos-line-axis b { font-family: var(--brand); font-size: 14.5px; color: var(--ink); }
.pos-line-axis b small { font-size: 12px; color: var(--muted); margin-inline-start: 2px; }
.pos-line-empty { margin: 10px 0 4px; }
.plan { border-color: var(--accent-line); }
.plan-steps { margin: 0; padding-inline-start: 22px; display: grid; gap: 8px; }
.plan-steps li { display: flex; align-items: center; gap: 10px; }
.plan-steps li > span { flex: 1; min-width: 0; }
.plan-steps form { display: contents; }
.cmp-ask { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: end; }
.cmp-ask textarea { width: 100%; resize: vertical; }
@media (max-width: 640px) {
  .cmp-open span { display: none; }
  .cmp-open { padding: 6px; border-radius: 50%; }
  .cmp-open svg { display: block; }
}
/* On a phone the search field is the handle; the shortcut hint is for a keyboard. */
@media (max-width: 640px) { .cmdk-hint.on { display: none; } }

/* ---- two link treatments, and where a link rule has to be written ----------------------- */

/*
 * EVERY LINK ON A CARD OR IN A TABLE IS ALREADY GOVERNED.
 *
 * ".card a:not(.btn):not(.pct), .chart-card a:not(.btn), table a:not(.btn)" - the light
 * panel inversion, about two thousand lines up - is (0,3,1), matches every link in both
 * places these two treatments are used, and is declared after them. So .what and .dim
 * were written, shipped, and did nothing: measured, the superseded row's link and the
 * live row's link came back the same colour, which is the exact defect .dim exists to
 * prevent.
 *
 * They are here, at the end, at (0,3,1) each, so that specificity ties and document order
 * decides in their favour. Anything that styles a link inside a card has to do the same:
 * the sheet has one rule that owns those links and it is not this one.
 */

/* The subject of a row: the one link that says what the row IS. */
.card a.what:not(.btn), table a.what:not(.btn) {
  color: var(--card-ink); font-weight: 600; text-decoration: none;
}
.card a.what:not(.btn):hover, table a.what:not(.btn):hover {
  text-decoration: underline; text-underline-offset: 3px;
}

/*
 * Withdrawn, superseded, retired - including its links, which is the whole point.
 *
 * Declared after .what so that a superseded row's subject reads as superseded rather than
 * as the strongest thing in the table.
 */
.dim a:not(.btn), .card .dim a:not(.btn), table .dim a:not(.btn) {
  color: var(--muted); font-weight: 400;
}
.dim a:not(.btn):hover, .card .dim a:not(.btn):hover, table .dim a:not(.btn):hover {
  color: var(--card-ink);
}
