/* ============================================================================
   DeviceSignal design tokens - the ONLY file in src/ allowed to contain a
   colour literal. `npm run check:tokens` enforces that.
   ========================================================================= */

:root {
  /* ---- Colour ------------------------------------------------------------
     Three jobs, and a hue may only ever do one of them.

       INTERACTIVE (blue)  Anything you can click: links, buttons, focus rings,
                           the current nav item. If it is blue, it does something.

       BRAND (teal)        Personality on marketing surfaces: eyebrows, check
                           marks, hero accents, and chart marks on series that
                           carry no severity. Never on an FDA record.

       SEVERITY (red,      How serious an FDA record is, and nothing else. A
       amber)              recall class or a complaint spike above a code's own
                           baseline. This is the rule that actually matters: a
                           reader who has learned that red means "recall" must
                           never be shown red for a full inbox or a failed form,
                           because the next red thing they skip might be a Class I.

     An earlier pass deleted the first two categories entirely, on the theory
     that colour should mean severity and nothing else. That is a real design
     position and it produced a monochrome product - the discipline that makes a
     page feel considered is confining each hue to one job, not removing hues.
     Apple's own language is neutral surfaces plus ONE vivid accent on anything
     tappable plus semantic status colours; it is restrained, not colourless.

     `scripts/check-tokens.mjs` enforces the one rule a stylesheet can be checked
     against: severity tokens may not appear in page chrome or marketing
     components. Nav, Pricing, Footer and friends carry no FDA data, so a red
     there is always a mistake.

     Bright variants are for fills, borders, icons and chart bars ONLY. Coloured
     TEXT must use the -text variant; the bright ones fail WCAG contrast on
     white. And colour is never the sole carrier of severity - see
     SEVERITY_GLYPHS in packages/core/src/severity.ts; every severity mark ships
     a glyph and a word so it survives a greyscale print and a colour-blind
     reader. */
  --ink: #0b1b2b;          /* headings, primary text */
  --ink-soft: #4a5764;     /* body text, secondary - 7.4:1 on white */
  --ink-faint: #7a8794;    /* NON-TEXT only: strike lines, hairlines, icons */
  /* Grey used as TEXT. The specified --ink-faint measures 3.67:1 on white,
     which fails WCAG 2.2 AA (4.5:1) for anything under 24px - and this page
     puts 13–15px captions in it. Same bright/-text split the design system
     already uses for amber and red. 5.08:1 on white, 4.54:1 on --mist. */
  --ink-faint-text: #62707d;
  --paper: #ffffff;        /* page background */
  --mist: #edf3f7;         /* section backgrounds, cards */
  --rule: #d9e2e9;         /* hairline borders */

  /* INTERACTIVE. Specified as #2F6BFF, which puts white 16px/600 button text at
     4.4979:1 - 0.0021 short of AA. One step on the blue channel clears it at
     4.51:1 and is not perceptibly different. */
  --blue: #2f6bfe;         /* primary buttons, links, focus rings (fills) */
  --blue-text: #2558d6;    /* blue used as TEXT - contrast safe */

  /* BRAND. Named "signal" historically; it is the brand accent, never a grade. */
  --signal: #0fb5a0;       /* fills, chart marks, accents ONLY */
  --signal-text: #0b7a6c;  /* teal used as TEXT */

  /* SEVERITY. FDA records only. */
  --alert: #f2a63b;        /* fills only - complaint spikes */
  --alert-text: #8a5a05;
  --risk: #e2574c;         /* fills only - recalls */
  --risk-text: #b3261e;

  /* ---- Severity, graded ---------------------------------------------------
     The two hues above say WHICH signal (a recall, a spike). These say HOW
     SERIOUS, which is what a reader actually scans for, and they are what
     components reference. A Class II recall and a complaint spike get the same
     treatment because they warrant the same amount of attention - under the old
     names that had to be spelled --alert for one and --risk for the other,
     which read as a contradiction.

     Class III is deliberately greyscale. "Not likely to cause adverse health
     consequences" is FDA's own wording; giving it a hue would spend the reader's
     alarm on the least serious thing on the page. Same for an unclassified
     recall, which is a NULL from the enforcement endpoint rather than a
     judgement - see the LEFT JOIN in apps/app/src/routes/insights.ts. */
  --sev-1: var(--risk);              /* Class I · elevated predicate risk */
  --sev-1-text: var(--risk-text);
  --sev-2: var(--alert);             /* Class II · complaint spike above band */
  --sev-2-text: var(--alert-text);
  --sev-3: var(--ink-faint);         /* Class III - non-text (glyph strokes) */
  --sev-3-text: var(--ink-soft);
  --sev-none-text: var(--ink-faint-text);  /* class not published */

  /* ---- Derived: interaction states -------------------------------------- */
  --blue-hover: #2b62eb;         /* --blue darkened 8% */
  --blue-tint: #f0f4ff;          /* outline-button hover wash, status pills */
  /* Faint washes behind status pills. Fills only - the pill's text uses the
     matching -text variant, checked at 5.3:1 or better over each wash. */
  --risk-wash: rgba(226, 87, 76, 0.1);
  --alert-wash: rgba(242, 166, 59, 0.14);
  --sev-1-wash: var(--risk-wash);
  --sev-2-wash: var(--alert-wash);
  --sev-3-wash: var(--mist);
  /* The focus ring is blue, and it is backed by paper.
     Blue alone measures 4.5:1 on white but only ~2.6:1 on the dark --ink
     sections, which fails WCAG 2.2 SC 1.4.11 exactly where a keyboard user most
     needs to find themselves. A 3px paper ring inside the blue one fixes that
     without a per-section override: on white the blue carries it, on ink the
     paper does, and the two never both disappear. */
  --focus-ring: var(--blue);
  --focus-backing: var(--paper);
  --nav-paper: rgba(255, 255, 255, 0.85);

  /* ---- Derived: text and surfaces on the dark (--ink) sections ----------- */
  --on-ink: #ffffff;
  --on-ink-body: rgba(255, 255, 255, 0.75);
  --on-ink-lead: rgba(255, 255, 255, 0.7);
  --on-ink-faint: rgba(255, 255, 255, 0.6);
  --on-ink-rule: rgba(255, 255, 255, 0.25);
  --on-ink-surface: rgba(255, 255, 255, 0.06);
  --on-ink-field-rule: rgba(255, 255, 255, 0.3);
  --risk-on-ink-text: #ff8078;   /* contrast-safe red ON DARK only */
  --risk-on-ink-fill: rgba(226, 87, 76, 0.12);
  --sev-1-on-ink-text: var(--risk-on-ink-text);
  --sev-1-on-ink-fill: var(--risk-on-ink-fill);
  --ink-fade-0: rgba(11, 27, 43, 0);
  --ink-fade-1: rgba(11, 27, 43, 1);
  --paper-fade-0: rgba(255, 255, 255, 0);

  /* ---- Shadows - two levels only. Never coloured. Never glow. ----------- */
  --shadow-sm: 0 1px 2px rgba(11, 27, 43, 0.06);
  --shadow-md: 0 4px 16px rgba(11, 27, 43, 0.08);

  /* ---- Type families ---------------------------------------------------- */
  --font-sans: "Inter", "Inter Fallback", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Arial, sans-serif;
  /* Identifiers only: product codes, K-numbers, CFR citations. */
  --font-mono: "JetBrains Mono", "JetBrains Mono Fallback", ui-monospace,
    SFMono-Regular, Menlo, Consolas, monospace;

  /* ---- Type scale (desktop; mobile step is at the 768px breakpoint) ------
     Headlines always tight. Body never tracked. That contrast is what makes
     type feel expensive. */
  --fs-display: 56px;
  --lh-display: 1.08;
  --ls-display: -0.025em;

  --fs-h2: 40px;
  --lh-h2: 1.15;
  --ls-h2: -0.02em;

  --fs-h3: 24px;
  --lh-h3: 1.3;

  --fs-card-title: 18px;
  --lh-card-title: 1.35;

  --fs-lead: 21px;
  --lh-lead: 1.5;

  --fs-body: 17px;
  --lh-body: 1.6;

  /* Slightly larger body, used only for the flagship section's paragraph. */
  --fs-body-lg: 19px;
  /* One-line card body in "Who it's for". */
  --fs-body-compact: 16px;

  --fs-small: 15px;
  --lh-small: 1.5;

  --fs-eyebrow: 13px;
  --ls-eyebrow: 0.12em;

  --fs-legal: 14px;

  --fs-price: 44px;
  --fs-price-old: 24px;
  --fs-price-talk: 28px;
  --fs-cadence: 17px;

  --fs-brand: 20px;
  --fs-nav: 16px;
  --fs-mono: 15px;
  --fs-mono-chip: 14px;

  /* ---- Spacing - 8px base scale only. No arbitrary values. -------------- */
  --s-4: 4px;
  --s-8: 8px;
  --s-12: 12px;
  --s-16: 16px;
  --s-24: 24px;
  --s-32: 32px;
  --s-48: 48px;
  --s-64: 64px;
  --s-96: 96px;
  --s-128: 128px;
  --s-160: 160px;

  --section-y: var(--s-128);   /* 72px on mobile - see breakpoint below */

  /* ---- Radius ----------------------------------------------------------- */
  --radius: 8px;
  --radius-pill: 999px;

  /* ---- Layout ----------------------------------------------------------- */
  --max-w: 1200px;
  --max-text: 720px;
  --max-hero: 860px;
  --measure: 62ch;             /* ≈68 characters */
  --gutter: var(--s-24);
  --nav-h: 64px;
  --tap-min: 44px;             /* minimum interactive target */

  /* ---- Motion ----------------------------------------------------------- */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur-hover: 180ms;
  --dur-reveal: 400ms;
  --stagger: 60ms;
}

/* Mobile type step. Body, small, eyebrow and legal sizes never shrink -
   nothing on this page goes below 16px except the 14px legal footer and the
   13px uppercase eyebrow label, which is a label, not reading text. */
@media (max-width: 767px) {
  :root {
    --fs-display: 36px;
    --fs-h2: 30px;
    --fs-h3: 22px;
    --fs-lead: 19px;
    --fs-price: 40px;
    --section-y: 72px;
  }
}

/* ============================================================================
   Base layer: self-hosted fonts, reset, focus, shared type + layout classes.
   Every colour here comes from tokens.css.
   ========================================================================= */

/* ---- Self-hosted fonts (copied out of @fontsource by scripts/copy-fonts) --
   Never a Google CDN request: the visitor is behind a corporate VPN and a
   third-party font host is both a latency tax and a procurement question. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/inter-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/inter-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/inter-latin-600-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/inter-latin-700-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

/* Metric-matched fallbacks. font-display: swap only costs layout stability if
   the fallback has different metrics - these overrides make the swap invisible,
   which is how CLS stays at exactly 0 rather than merely "low". */
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  size-adjust: 107.12%;
  ascent-override: 90.49%;
  descent-override: 22.56%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "JetBrains Mono Fallback";
  src: local("Courier New");
  size-adjust: 100%;
  ascent-override: 102%;
  descent-override: 30%;
  line-gap-override: 0%;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Tabular numerals everywhere: prices, counts and dates must align. */
  font-feature-settings: "tnum" 1, "cv05" 1;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-soft);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

img,
svg {
  display: block;
  max-width: 100%;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
}

/* ---- Links -------------------------------------------------------------
   Blue and underlined, both. Blue makes a link findable at a glance; the
   underline is what makes it findable to a reader who cannot see the blue, which
   is why WCAG 1.4.1 forbids the common inverse - a coloured link with no
   underline. Components that draw their own obvious affordance (a filled button,
   a bordered filter chip, a table row) opt out locally. */
a {
  color: var(--blue-text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

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

/* ---- Focus: visible on everything, always. `outline: none` appears nowhere
   in this codebase. -------------------------------------------------------
   A blue ring at 3px, backed by a 3px paper ring inside it.

   The blue is the product's interactive colour, so the focus state says the same
   thing as every other blue on the page. The paper backing is not decoration: on
   a dark --ink section the blue drops to about 2.6:1, which fails WCAG 2.2 SC
   1.4.11 exactly where a keyboard user most needs to find themselves. Those
   sections use hashed CSS-module class names, so no global selector can reach in
   and swap the colour - the ring has to work without knowing its background. It
   does: on white the blue carries it, on ink the paper does, and neither
   disappears at the same time as the other.

   The box-shadow does replace a component's own resting shadow while focused.
   That is deliberate - --shadow-sm is 6% ink and would read as a smudge behind
   the ring rather than as elevation. */
:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--focus-backing);
}

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

.container {
  width: 100%;
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--section-y);
}

.measure {
  max-width: var(--measure);
}

.eyebrow {
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  margin: 0 0 var(--s-16);
  color: var(--ink-faint-text);
}

.h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--ls-h2);
  font-weight: 700;
  color: var(--ink);
}

.h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  font-weight: 600;
  color: var(--ink);
}

.lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  font-weight: 400;
  color: var(--ink-soft);
}

.mono {
  font-family: var(--font-mono);
  font-feature-settings: "tnum" 1;
}

/* Scroll targets sit below the 64px sticky nav. */
[id] {
  scroll-margin-top: calc(var(--nav-h) + var(--s-24));
}

/* ---- Scroll reveal ------------------------------------------------------
   Gated on the `js` class (set synchronously by the entry module before first
   paint), so with JavaScript disabled every section is simply visible. */
/* The 16px rise uses `translate`, not `transform`, so a card's hover lift and
   its reveal never fight over the same property. */
html.js [data-reveal] {
  opacity: 0;
  translate: 0 16px;
  transition: opacity var(--dur-reveal) var(--ease),
    translate var(--dur-reveal) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, translate;
}

html.js [data-reveal="in"] {
  opacity: 1;
  translate: none;
  will-change: auto;
}

/* Printing never scrolls, so the observer never fires for most of the page.
   Without this, a printed copy comes out with only the hero on it. */
@media print {
  html.js [data-reveal] {
    opacity: 1 !important;
    translate: none !important;
  }
}

/* ---- Reduced motion: mandatory, and it must never hide content ---------- */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
    transform: none !important;
  }

  /* Reduced motion must never mean hidden content. */
  html.js [data-reveal] {
    opacity: 1 !important;
    translate: none !important;
  }
}

/* ---- Forced colours (Windows High Contrast and friends) -----------------
   The reader has told the operating system which colours they can see. That
   instruction outranks every decision in tokens.css, so this block hands the
   palette over rather than defending it.

   It exists because an axe run under forced-colors emulation put --ink
   (#0b1b2b) on a forced black canvas at 1.2:1 - near-black text on black. Any
   place this stylesheet names an ink is a place that can happen, and there are
   too many to enumerate, so the reset is deliberately broad. In this mode blunt
   is correct: the OS palette is meant to win.

   Real Windows High Contrast has NOT been verified - this was written and
   reviewed against Playwright's emulation, on macOS, which is not the same
   thing. Treat it as a considered starting point, not a tested result. */
@media (forced-colors: active) {
  html {
    color: CanvasText;
    background: Canvas;
  }

  /* Anything that named an ink gets the system's instead. Links are excluded so
     they keep LinkText below and stay distinguishable from body copy.
     `!important` for the same reason the reduced-motion block above uses it: a
     bare `*:not(a)` carries the specificity of a type selector and loses to every
     `.brand` and `.rail-link` in the stylesheets, which is precisely the rule set
     that has to give way here. */
  *:not(a) {
    color: CanvasText !important;
  }

  a {
    color: LinkText !important;
  }

  /* Highlight is the one colour guaranteed to contrast with Canvas in every
     high-contrast theme. The paper backing ring is dropped: box-shadow is not
     painted in forced-colors mode, so leaving it in would imply a second ring
     that is not there. */
  :focus-visible {
    outline: 3px solid Highlight;
    outline-offset: 3px;
    box-shadow: none;
  }

  /* Severity loses its hue here, which is exactly the case the glyph and the
     label were designed for. The border thickens so the pill still reads as a
     distinct object once its wash is gone. */
  .sev {
    border: 2px solid CanvasText;
  }

  /* Buttons lose their ink fill, so they need a drawn edge to still look
     pressable. ButtonText/ButtonFace are the system's own pair for this. */
  button,
  .button,
  .button-quiet {
    border: 1px solid ButtonText;
    color: ButtonText !important;
    background: ButtonFace;
  }
}

/* ---- Print ---------------------------------------------------------------
   The marketing site had almost no print handling: two narrow rules and
   nothing else, so a printed code page came out with the sticky nav floating
   over the first section, the footer nav in the middle of the paper, and every
   link's destination invisible. A regulatory reader prints these and files
   them, so this is a real output medium rather than a courtesy.

   apps/app/public/report.css is the purpose-built A4 stylesheet for the
   Client-Ready PDF; this is the general case for every other page. -------- */
@media print {
  @page {
    /* Generous, because a printed page gets annotated in the margin. */
    margin: 16mm 14mm 18mm;
  }

  html {
    /* Points, not pixels: a printer has no CSS pixel and browsers guess. */
    font-size: 10.5pt;
  }

  body {
    background: var(--paper);
    color: var(--ink);
  }

  /* Chrome is navigation, and paper cannot navigate. */
  nav[aria-label="Primary"],
  header[role="banner"],
  footer nav,
  .skip {
    display: none;
  }

  /* Where a link went is the whole value of a link on paper. Only absolute and
     root-relative hrefs - expanding an in-page #anchor would print "(#faq)",
     which is noise, and a javascript: or mailto: reads fine as its own text. */
  a[href^="http"]::after,
  a[href^="/"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    font-weight: 400;
    word-break: break-all;
    color: var(--ink-soft);
  }

  /* A heading alone at the foot of a page is the clearest tell of a document
     nobody laid out. */
  h1,
  h2,
  h3 {
    break-after: avoid;
    page-break-after: avoid;
  }

  /* Two lines of a paragraph stranded either side of a page break. */
  p,
  li,
  figure,
  table {
    orphans: 3;
    widows: 3;
  }

  table {
    break-inside: auto;
  }

  tr {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* A long table's header repeated on page four, which is where it is needed. */
  thead {
    display: table-header-group;
  }

  /* Anything that scrolls sideways on screen is clipped at the paper edge.
     Unwrapping it lets the content wrap instead of vanishing. */
  [class*="scroller"],
  [class*="tableWrap"],
  [class*="chart-wrap"] {
    overflow: visible !important;
  }

  /* Severity is the one thing on the page that still gets colour, so it is the
     one thing that must not be dropped to save toner. */
  [class*="sev"],
  [class*="tone1"],
  [class*="tone2"],
  [class*="tone3"] {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

/*
 * App styles.
 *
 * Colour literals live only in packages/tokens/src/tokens.css, which is concatenated
 * ahead of this file at request time - the repo-wide token guard checks that rule, so
 * a hex code here would fail the build. Everything below is layout and type.
 */

.skip {
  position: absolute;
  top: var(--s-8);
  left: var(--s-8);
  z-index: 30;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--s-8) var(--s-16);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow-md);
  color: var(--ink);
  font-size: var(--fs-nav);
  font-weight: 600;
  text-decoration: none;
  clip-path: inset(50%);
}

.skip:focus-visible {
  clip-path: none;
}

.container {
  width: 100%;
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

.chrome {
  border-bottom: 1px solid var(--rule);
  background: var(--nav-paper);
  position: sticky;
  top: 0;
  z-index: 20;
}

.chrome-inner {
  display: flex;
  align-items: center;
  gap: var(--s-24);
  min-height: var(--nav-h);
}

.brand {
  font-size: var(--fs-brand);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  margin-right: auto;
}

.nav {
  display: flex;
  align-items: center;
  gap: var(--s-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: 0 var(--s-12);
  border-radius: var(--radius);
  font-size: var(--fs-nav);
  font-weight: 600;
  color: var(--ink-soft);
  text-decoration: none;
}

.nav-link:hover {
  background: var(--mist);
  color: var(--ink);
}

/* The current page is marked by weight, colour and a rule - and by
   `aria-current="page"` in the markup for anyone who cannot see any of the three. */
.nav-link-current {
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--blue);
}

.chrome-link {
  font-size: var(--fs-nav);
  font-weight: 600;
  color: var(--blue-text);
}

.signout {
  margin: 0;
}

/* ------------------------------------------------------------------ text */

main {
  display: block;
  padding-block: var(--s-48) var(--s-96);
  min-height: 60vh;
}

.page-head {
  display: flex;
  align-items: flex-start;
  gap: var(--s-24);
  flex-wrap: wrap;
  margin-bottom: var(--s-32);
}

.page-head-text {
  max-width: var(--max-text);
}

/* Actions sit beside the heading rather than under it, so a code page's exports are
   reachable without scrolling past the paragraph that explains the code. */
.page-actions {
  display: flex;
  gap: var(--s-8);
  flex-wrap: wrap;
  margin-left: auto;
  padding-top: var(--s-4);
}

.h1 {
  margin: 0 0 var(--s-12);
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  font-weight: 700;
  letter-spacing: var(--ls-h2);
  color: var(--ink);
}

.h2 {
  margin: 0 0 var(--s-16);
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  font-weight: 700;
  color: var(--ink);
}

.h3 {
  margin: var(--s-24) 0 var(--s-8);
  font-size: var(--fs-card-title);
  line-height: var(--lh-card-title);
  font-weight: 700;
  color: var(--ink);
}

.lead {
  margin: 0;
  max-width: var(--measure);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--ink-soft);
}

.body {
  margin: 0 0 var(--s-16);
  max-width: var(--measure);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink-soft);
}

.small {
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--ink-faint-text);
}

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

.legal {
  margin: 0 0 var(--s-8);
  font-size: var(--fs-legal);
  line-height: var(--lh-small);
  color: var(--ink-faint-text);
}

.legal a {
  color: var(--ink-soft);
}

.foot {
  border-top: 1px solid var(--rule);
  padding-block: var(--s-32);
  background: var(--mist);
}

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

.panel {
  padding: var(--s-24);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow-sm);
}

.panel + .panel {
  margin-top: var(--s-16);
}

/* A finding. One accent edge, the same device the marketing site uses.
 *
 * The edge used to default to brand blue and turn red when severe, which meant
 * the severity contrast on this page was blue-versus-red. That was the weakest
 * part of the old palette: blue was also every button and every link, so a red
 * edge only read as "not the usual colour" rather than as "recall". The default
 * is now --rule - the same hairline as the rest of the card, i.e. no edge at
 * all - so a coloured edge is genuinely exceptional.
 *
 * The edge is never the only carrier. Every finding also renders a .sev pill
 * with a glyph and a word; see packages/core/src/severity.ts. */
.finding {
  padding: var(--s-24);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--rule);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--paper);
  box-shadow: var(--shadow-sm);
}

.finding + .finding {
  margin-top: var(--s-12);
}

.finding-sev-1 {
  border-left-color: var(--sev-1);
}

.finding-sev-2 {
  border-left-color: var(--sev-2);
}

/* Class III and unclassified keep the hairline. Both are real findings and both
   are listed; neither earns a hue it would then have to share with a Class I. */
.finding-sev-3 {
  border-left-color: var(--sev-3);
}

.finding-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-8) var(--s-12);
  margin: 0 0 var(--s-8);
}

.finding-code {
  font-family: var(--font-mono);
  font-size: var(--fs-card-title);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}

.finding-device {
  font-size: var(--fs-body-compact);
  color: var(--ink-soft);
}

/* A neutral pill. Carries NO severity meaning - it is also "Your plan" on the
   account page and "Muted" on the watch list, so it cannot be trusted to mean
   anything on its own. Severity uses .sev below. */
.band {
  display: inline-block;
  padding: 2px var(--s-8);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-faint-text);
  background: var(--mist);
  white-space: nowrap;
}

/* ------------------------------------------------------------- severity pill
 * Glyph + words + tone, and the words are not optional. See
 * packages/core/src/severity.ts for why: a mark that renders the shape and the
 * hue without the label would be colour alone, which fails WCAG 2.2 SC 1.4.1 and
 * fails a reader on a projector, on a greyscale printout, or with the ~8%
 * prevalence of red-green colour blindness in this audience.
 *
 * The glyph is aria-hidden. It is a duplicate of the label, not a supplement to
 * it, so announcing both would just say everything twice.
 */
.sev {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px var(--s-8);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
}

.sev-glyph {
  width: 14px;
  height: 14px;
  flex: none;
  fill: currentColor;
}

/* Class III and unclassified. Outlined rather than filled, so the grade is still
   legible once both hues have flattened to the same grey on paper. */
.sev-glyph-outline {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.25;
  stroke-linecap: round;
}

/* Text on wash, measured: #b3261e on the 10% red wash is 5.6:1, #8a5a05 on the
   14% amber wash is 5.4:1. Both clear AA for 13px. */
.sev-1 {
  color: var(--sev-1-text);
  background: var(--sev-1-wash);
  border-color: var(--sev-1);
}

.sev-2 {
  color: var(--sev-2-text);
  background: var(--sev-2-wash);
  border-color: var(--sev-2);
}

.sev-3 {
  color: var(--sev-3-text);
  background: var(--sev-3-wash);
  border-color: var(--rule);
}

.finding-summary {
  margin: 0;
  max-width: var(--measure);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
}

.records {
  margin: var(--s-12) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-chip);
  line-height: 1.7;
  color: var(--ink-faint-text);
  word-break: break-word;
}

/* ------------------------------------------------------------------ grid */

.grid {
  display: grid;
  gap: var(--s-16);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.stat {
  padding: var(--s-16);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
}

.stat dt {
  margin: 0 0 var(--s-4);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-faint-text);
}

.stat dd {
  margin: 0;
  font-size: var(--fs-card-title);
  font-weight: 700;
  color: var(--ink);
}

/* ----------------------------------------------------------------- forms */

.field {
  display: block;
  margin-bottom: var(--s-16);
  max-width: 26rem;
}

.field label {
  display: block;
  margin-bottom: var(--s-8);
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
}

.field input {
  display: block;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--s-12);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  /* 16px minimum, so iOS does not zoom the viewport on focus. */
  font-size: var(--fs-body);
  font-family: inherit;
  color: var(--ink);
}

/* Only the border takes the accent. The ring itself is the global blue-on-paper
   indicator in base.css - overriding `outline` here would replace it with a
   weaker one, which is what this rule used to do. */
.field input:focus-visible {
  border-color: var(--blue);
}

.field-hint {
  margin: var(--s-8) 0 0;
  font-size: var(--fs-small);
  color: var(--ink-faint-text);
}

.code-input {
  font-family: var(--font-mono);
  font-size: var(--fs-h3);
  letter-spacing: 0.32em;
  text-align: center;
}

.button,
.button-quiet,
button.button,
button.button-quiet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: 0 var(--s-24);
  border: 1px solid transparent;
  border-radius: var(--radius);
  /* Primary is a solid blue fill; .button-quiet below is a hairline outline. Two
     axes carry the hierarchy, fill-versus-outline and colour, so it survives a
     greyscale print as well as a screen. `text-decoration: none` is deliberate -
     the underline is the link affordance and a button already looks like one. */
  background: var(--blue);
  color: var(--paper);
  font-size: var(--fs-nav);
  font-family: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.button:hover {
  background: var(--blue-hover);
}

.button-quiet {
  background: transparent;
  border-color: var(--rule);
  color: var(--ink-soft);
}

.button-quiet:hover {
  background: var(--mist);
  color: var(--ink);
}

/* Disabled has to differ from enabled, and it no longer can by losing a hue -
   an ink fill at reduced weight would just read as the enabled button. So it
   inverts: mist fill, hairline border, faint text. It also fixes a contrast bug
   this rule had before, which put --ink-faint-text (#62707d) on a solid ink
   fill at 3.1:1; on --mist the same grey measures 4.54:1. */
.button[aria-disabled="true"] {
  background: var(--mist);
  border-color: var(--rule);
  color: var(--ink-faint-text);
  cursor: not-allowed;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-8);
  align-items: center;
}

/* --------------------------------------------------------------- notices */

/* Notices and errors are greyscale, which is the part of the severity-only rule
 * that costs something and is still right.
 *
 * A red validation box teaches the reader that red means "you did something
 * wrong". A red recall badge means "this device hurt someone". Those cannot both
 * be true on the same page, and only one of them is worth a hue. Amber on a
 * "watch list updated" confirmation had the same problem: it spends the alarm
 * the amber on a complaint spike needs.
 *
 * What carries urgency instead: position (immediately above or beside the thing
 * it concerns), an ink edge, a mist fill against the white page, and weight. An
 * inline error is unambiguous from where it sits and what it says - it never
 * needed the colour to be found. */
.notice {
  margin: 0 0 var(--s-24);
  padding: var(--s-12) var(--s-16);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--signal);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--paper);
  font-size: var(--fs-body-compact);
  color: var(--ink);
  max-width: var(--max-text);
}

.notice-warn {
  border-left-color: var(--ink);
  background: var(--mist);
}

.error {
  margin: 0 0 var(--s-16);
  padding: var(--s-12) var(--s-16);
  border: 1px solid var(--ink);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--mist);
  font-size: var(--fs-body-compact);
  font-weight: 600;
  color: var(--ink);
  max-width: 26rem;
}

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

.scroller {
  overflow-x: auto;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
}

.table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  text-align: left;
}

.table caption {
  padding: var(--s-16) var(--s-24);
  text-align: left;
  font-size: var(--fs-small);
  color: var(--ink-faint-text);
  border-bottom: 1px solid var(--rule);
}

.table th,
.table td {
  padding: var(--s-12) var(--s-24);
  font-size: var(--fs-body-compact);
  line-height: var(--lh-body);
  vertical-align: top;
  border-bottom: 1px solid var(--rule);
}

.table thead th {
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-faint-text);
  background: var(--mist);
  white-space: nowrap;
}

.table tbody th {
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

.table td {
  color: var(--ink-soft);
}

.table tbody tr:last-child th,
.table tbody tr:last-child td {
  border-bottom: 0;
}

/* --------------------------------------------------------------- empties */

.empty {
  padding: var(--s-48) var(--s-24);
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  background: var(--mist);
  text-align: center;
}

.empty-title {
  margin: 0 0 var(--s-8);
  font-size: var(--fs-card-title);
  font-weight: 700;
  color: var(--ink);
}

.empty-body {
  margin: 0 auto var(--s-24);
  max-width: 34rem;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink-soft);
}

/* ----------------------------------------------------------------- chain */

.chain {
  margin: var(--s-16) 0 0;
  padding: 0;
  list-style: none;
}

.chain li {
  padding: var(--s-12) var(--s-16);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  font-size: var(--fs-body-compact);
  color: var(--ink-soft);
}

.chain li + li {
  margin-top: var(--s-8);
}

.chain-recalled {
  border-left: 4px solid var(--sev-1);
}

@media (max-width: 40rem) {
  .chrome-inner {
    flex-wrap: wrap;
    gap: var(--s-12);
    padding-block: var(--s-12);
  }

  .panel,
  .finding {
    padding: var(--s-16);
  }
}

@media print {
  /* The rail is navigation, and paper cannot navigate - it was printing the whole
     sidebar down the left of every page. Also the skip link and the export buttons,
     which are the same category of thing. */
  .chrome,
  .rail,
  .skip,
  .foot,
  .actions,
  .page-actions {
    display: none;
  }

  /* The shell is a CSS grid with a rail column. With the rail gone the content must
     take the full width rather than leaving a 240px hole where it used to be. */
  body.shell {
    display: block;
  }

  .pane {
    max-width: none;
    padding: 0;
  }

  /* A scroll container is clipped at the paper edge; unwrapped it wraps instead. */
  .scroller,
  .chart-wrap {
    overflow: visible;
    border: 0;
  }

  .table {
    min-width: 0;
    font-size: 9pt;
  }

  .table thead {
    display: table-header-group;
  }

  .table tr {
    break-inside: avoid;
  }

  /* Severity is the one thing that must survive a monochrome office laser, so it
     asks for its colour explicitly rather than being dropped to save toner. */
  .sev,
  .finding {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .finding,
  .panel {
    break-inside: avoid;
  }
}

/* ---------------------------------------------------------------- utilities
 *
 * A small set of single-purpose classes, added because the app's own
 * Content-Security-Policy forbids inline styles - `style-src 'self'` with no
 * 'unsafe-inline'. A browser silently dropped every style="" attribute and the
 * layout broke, which is exactly the right outcome: the alternative was widening
 * the policy, and a strict CSP is worth more than the convenience of an inline
 * declaration. The email templates are unaffected - they are not served under a
 * policy and cannot use classes anyway.
 */

.mt-0 { margin-top: 0; }
.mt-12 { margin-top: var(--s-12); }
.mt-16 { margin-top: var(--s-16); }
.mt-24 { margin-top: var(--s-24); }
.mt-32 { margin-top: var(--s-32); }
.mt-48 { margin-top: var(--s-48); }
.mb-24 { margin-bottom: var(--s-24); }
.mb-32 { margin-bottom: var(--s-32); }

.narrow { max-width: 30rem; }
.narrower { max-width: 32rem; }

.text-body { font-size: var(--fs-body); }
.text-title { font-size: var(--fs-card-title); }

/* Product codes are uppercase and monospaced wherever they are typed or shown. */
.input-code {
  text-transform: uppercase;
  font-family: var(--font-mono);
}

/* A radio or checkbox beside its label, without the full-width field styling. */
.choice {
  display: flex;
  gap: var(--s-8);
  align-items: center;
}

.choice input {
  width: auto;
  min-height: auto;
}

.feature-list {
  padding-left: 1.1rem;
  line-height: 1.9;
}

/* ================================================================== shell
   The signed-in layout: a fixed left rail and a scrolling content pane.

   CSS grid on the body, which is what lets the rail be full-height and sticky with no
   JavaScript and no absolute positioning. `.plain` (sign-in, errors) keeps the old
   single-column chrome, so a signed-out page is unaffected.
   ------------------------------------------------------------------------ */
body.shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  grid-template-areas: "rail main" "rail foot";
  grid-template-rows: 1fr auto;
  min-height: 100vh;
}

body.shell > .rail { grid-area: rail; }
body.shell > main { grid-area: main; min-width: 0; }
body.shell > .foot { grid-area: foot; }

.rail {
  display: flex;
  flex-direction: column;
  gap: var(--s-24);
  padding: var(--s-24) var(--s-16);
  border-right: 1px solid var(--rule);
  background: var(--nav-paper);
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  overflow-y: auto;
}

.rail .brand { padding-inline: var(--s-8); }

.rail-nav {
  display: flex;
  flex-direction: column;
  gap: var(--s-16);
}

.rail-group {
  margin: 0 0 var(--s-4);
  padding-inline: var(--s-8);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-faint-text);
}

.rail-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rail-link {
  display: flex;
  align-items: center;
  gap: var(--s-8);
  padding: var(--s-8);
  border-radius: var(--radius);
  font-size: var(--fs-nav);
  font-weight: 500;
  color: var(--ink-soft);
  text-decoration: none;
  /* A left border reserved on every item, transparent until current, so the label does
     not shift by 3px when a page becomes active. */
  border-left: 3px solid transparent;
}

.rail-link:hover { background: var(--mist); color: var(--ink); }

/* Selected. The blue tint distinguishes it from .rail-link:hover, which is the
   plain mist - without that, hovering any item looked identical to the page you
   are already on. Weight and the 3px edge carry it as well as the colour, and
   `aria-current="page"` carries it for anyone seeing none of the three. */
.rail-link-current {
  background: var(--blue-tint);
  border-left-color: var(--blue);
  color: var(--blue-text);
  font-weight: 600;
}

.rail-icon {
  width: 17px;
  height: 17px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.rail-foot { margin-top: auto; padding-inline: var(--s-8); }

.rail-signout {
  border: 0;
  background: none;
  padding: var(--s-8) 0;
  font: inherit;
  font-size: var(--fs-small);
  color: var(--ink-faint-text);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.rail-signout:hover { color: var(--ink); }

/* The content pane. Wider than .container because the rail already spends 232px, and a
   dashboard with charts needs the room a prose page does not. */
.pane {
  width: 100%;
  max-width: 1080px;
  margin-inline: auto;
  padding: var(--s-32) var(--gutter) var(--s-64);
}

/* Narrow: the rail becomes a horizontal strip above the content. No drawer, no toggle,
   no JavaScript - see the comment in layout.ts. */
@media (max-width: 860px) {
  body.shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "rail" "main" "foot";
    grid-template-rows: auto 1fr auto;
  }

  .rail {
    position: static;
    max-height: none;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--s-12);
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    padding: var(--s-12) var(--gutter);
  }

  .rail-nav { flex-direction: row; gap: var(--s-8); flex: 1 1 100%; }
  .rail-group { display: none; }
  .rail-list { flex-direction: row; flex-wrap: wrap; gap: var(--s-4); }

  .rail-link {
    border-left: 0;
    border-bottom: 2px solid transparent;
    border-radius: var(--radius) var(--radius) 0 0;
    padding: var(--s-8) var(--s-12);
  }
  .rail-link-current { border-bottom-color: var(--ink); }

  .rail-foot { margin-top: 0; margin-left: auto; padding: 0; }
  .pane { padding-top: var(--s-24); }
}

/* ================================================================= insights
   Charts and the comparison table. Every colour comes from packages/tokens, and
   every meaning that a colour carries is also carried by a word - the tables beside
   these charts say "above expected", the bars only show it.
   ------------------------------------------------------------------------ */

/* A stat card that is carrying bad news. The left border is the signal; the number
   itself stays ink, because a red number reads as an error rather than a count. */
.stat-risk { border-left: 3px solid var(--sev-1); }
.stat-warn { border-left: 3px solid var(--sev-2); }

.stat-note {
  /* Inside the <dd>, not a sibling of it: a <dl> may only contain dt/dd (or a div
     wrapping those), and a <p> alongside them is a WCAG 1.3.1 failure that axe catches
     as `only-dlitems`. Nesting it means inheriting the dd's 700-weight card type, so
     everything it needs is reset here rather than assumed. */
  margin: var(--s-4) 0 0;
  font-size: var(--fs-small);
  font-weight: 400;
  line-height: var(--lh-small);
  color: var(--ink-faint-text);
}

.chart-wrap {
  margin: var(--s-16) 0;
  overflow-x: auto;
}

.chart {
  display: block;
  width: 100%;
  /* Fixed height so nothing reflows when the page paints. */
  height: 200px;
  min-width: 520px;
}

.chart-grid { stroke: var(--rule); stroke-width: 1; }
.chart-axis { stroke: var(--ink-faint); stroke-width: 1; }
.chart-tick {
  fill: var(--ink-faint-text);
  font-family: var(--font-mono);
  font-size: 10px;
  text-anchor: end;
}
/* The band is the plot's own background, not a finding, so it takes the neutral
   interactive wash rather than a severity hue. */
.chart-band { fill: var(--blue-tint); }
/* A month inside its expected range is the ordinary case, so it stays ink - the
   bars are data, and data is not decorated. */
.chart-bar { fill: var(--ink); }
/* A month above the band. --sev-2 (#f2a63b) against a --mist band measures about
   1.8:1, which is under the 3:1 WCAG 2.2 SC 1.4.11 wants for a graphic that
   carries meaning - so the fill alone would not be a legitimate signal. The
   stroke in --sev-2-text (#8a5a05) is what makes it one. The bar is also never
   the only carrier: the table beside the chart says "Above expected (n–m)" in
   words for the same month. */
.chart-bar-high {
  fill: var(--sev-2);
  stroke: var(--sev-2-text);
  stroke-width: 1;
}
/* A month with no baseline behind it: hatched rather than filled, so it cannot be
   mistaken for a measured comparison - and so the distinction survives a
   greyscale print, where a pale fill and a pale band look identical. */
.chart-bar-unknown { stroke: var(--rule); stroke-width: 1; }
.chart-hatch-line { stroke: var(--ink-faint); stroke-width: 1; }

.spark {
  display: block;
  width: 84px;
  height: 22px;
  overflow: visible;
}
.spark-line {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The last point, marked so the eye knows which end is now. The brand accent,
   not a severity hue - a trend's latest value is not a grade. */
.spark-dot { fill: var(--signal); }

.meter {
  display: block;
  width: 72px;
  height: 8px;
}
.meter-track { fill: var(--mist); }
.meter-fill { fill: var(--signal); }

/* A second line inside a table cell: the device name under a code, the sample size
   under a trend. Keeps the cell scannable without a second column. */
.cell-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-small);
  line-height: 1.35;
  color: var(--ink-faint-text);
}

/* Direction always carries a word as well as an arrow.
   "Faster" takes the brand accent because it is good news about a queue. "Slower"
   deliberately does NOT take red: FDA taking longer to clear a submission is a
   fact about a queue, not a severity, and a red arrow here would compete for
   attention with a Class I recall two rows down. Ink plus the word plus the arrow
   is enough. */
.trend-slower { color: var(--ink); font-weight: 600; }
.trend-faster { color: var(--signal-text); font-weight: 600; }
.trend-flat { color: var(--ink-faint-text); }

/* ================================================================= the chain
   The predicate chain diagram. Its twin is
   apps/web/src/components/PredicateChain.module.css, and the two must match:
   the same ancestry appears on the public code page and on this one.
   ------------------------------------------------------------------------ */

.chain-figure { margin: 0 0 var(--s-16); }

.chain-caption {
  margin: 0 0 var(--s-12);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-faint-text);
}

/* Scrolls sideways on a narrow viewport. tabindex and role="group" are in the
   markup, because a region that scrolls with a mouse and not a keyboard is the
   axe failure scrollable-region-focusable. */
.chain-scroll {
  overflow-x: auto;
  padding-bottom: var(--s-8);
}

.chain-diagram { display: block; height: auto; min-width: 100%; }

.chain-box { fill: var(--paper); stroke: var(--rule); stroke-width: 1; }

/* A recalled ancestor: hue, heavier stroke, the class in words inside the box,
   and the same class again in the list below. Four carriers, because this is the
   one mark on the page a reader must not miss. */
.chain-box-recalled {
  fill: var(--sev-1-wash);
  stroke: var(--sev-1);
  stroke-width: 2;
}

/* 1976 is a boundary, not a device - dashed, because there is no further ancestor
   to find rather than one we failed to read. */
.chain-box-terminal { fill: var(--mist); stroke-dasharray: 4 3; }

.chain-k {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 500;
  fill: var(--ink);
}
.chain-node-sev { font-size: 12px; font-weight: 600; fill: var(--sev-1-text); }
.chain-node-detail { font-size: 12px; fill: var(--ink-faint-text); }
.chain-connector { stroke: var(--ink-faint); stroke-width: 1.5; }
.chain-arrowhead {
  fill: none;
  stroke: var(--ink-faint);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Visually hidden, but never display:none - it is the accessible copy of the
   diagram and the printed one. clip-path rather than a transform, because the
   reduced-motion block in base.css sets `transform: none !important`. */
.chain-list {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  margin: 0;
  padding: 0;
}

@media print {
  .chain-scroll { display: none; }
  .chain-list {
    position: static;
    width: auto;
    height: auto;
    clip-path: none;
    white-space: normal;
  }
  .chain-list li { display: block; margin-bottom: 2mm; }
}
