/* =============================================================================
   Document pages: the model page and the instructor area
   -----------------------------------------------------------------------------
   Gravitas itself is a single canvas with floating panels. These two pages are
   documents, so they need a reading column, a page header and a footer, none of
   which the app has. Everything else comes from tokens.css: the same palette,
   the same four themes, the same radii and spacing scale, so a reader arriving
   from the simulation does not feel they have left the product.

   Not styled here: buttons, panels and form fields, which reuse the app's
   existing classes.
   ============================================================================ */

@layer components {
  body.doc-page {
    margin: 0;
    min-height: 100vh;
    font-family: var(--font-sans);
    font-size: var(--text-md);
    line-height: var(--leading-normal);
    color: var(--text-primary);
    background: var(--bg-color);
    /* The app's body is a flex column with overflow hidden, because it is a
       full-screen canvas. These pages are documents and have to scroll, so
       both are reset explicitly: the production build concatenates that rule
       into the same stylesheet. */
    display: block;
    overflow-x: hidden;
    overflow-y: visible;
  }

  /* --- Page chrome ------------------------------------------------------- */
  .doc-top {
    position: sticky;
    top: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-5);
    background: color-mix(in srgb, var(--bg-color) 88%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border-subtle);
  }
  .doc-brand {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    text-decoration: none;
    letter-spacing: 0.02em;
  }
  .doc-brand span {
    font-size: var(--text-xs);
    font-weight: var(--weight-normal);
    color: var(--text-muted);
  }
  .doc-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-left: auto;
    font-size: var(--text-sm);
  }
  .doc-nav a {
    color: var(--text-primary);
    text-decoration: none;
    padding: 4px 2px;
    border-bottom: 1px solid transparent;
  }
  .doc-nav a:hover,
  .doc-nav a:focus-visible {
    color: var(--text-primary);
    border-bottom-color: var(--accent);
  }
  /* The accent alone does not clear 4.5:1 against every theme's ground, so the
     current page is marked by the rule underneath it and by weight instead. */
  .doc-nav a[aria-current='page'] {
    color: var(--text-primary);
    font-weight: var(--weight-semibold);
    border-bottom-color: var(--accent);
  }

  /* --- Reading column ---------------------------------------------------- */
  .doc-main {
    max-width: 74ch;
    margin: 0 auto;
    padding: var(--space-8) var(--space-5) var(--space-10);
  }
  .doc-main.is-wide {
    max-width: 1080px;
  }
  .doc-hero {
    padding-bottom: var(--space-6);
    margin-bottom: var(--space-6);
    border-bottom: 1px solid var(--border-subtle);
  }
  .doc-eyebrow {
    margin: 0 0 var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent);
  }
  .doc-hero h1 {
    margin: 0 0 var(--space-3);
    font-size: clamp(1.9rem, 1.2rem + 2.4vw, 3rem);
    line-height: 1.1;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .doc-lede {
    margin: 0;
    font-size: var(--text-lg);
    line-height: 1.55;
    color: var(--text-secondary);
    text-wrap: pretty;
  }

  .doc-main h2 {
    margin: var(--space-9) 0 var(--space-3);
    font-size: var(--text-xl);
    letter-spacing: -0.01em;
    scroll-margin-top: 84px;
    text-wrap: balance;
  }
  .doc-main h3 {
    margin: var(--space-6) 0 var(--space-2);
    font-size: var(--text-base);
    color: var(--text-primary);
    scroll-margin-top: 84px;
  }
  .doc-main p {
    margin: 0 0 var(--space-4);
    color: var(--text-secondary);
    text-wrap: pretty;
  }
  .doc-main li {
    margin-bottom: var(--space-2);
    color: var(--text-secondary);
  }
  .doc-main strong {
    color: var(--text-primary);
    font-weight: var(--weight-semibold);
  }
  .doc-main a {
    color: var(--accent);
    text-underline-offset: 3px;
  }
  .doc-main code,
  .doc-eq {
    font-family: var(--font-mono);
    font-size: 0.92em;
    color: var(--text-primary);
  }
  /* A displayed relation. Not MathML: these are short enough that a monospace
     line is clearer than a rendering dependency, and it copies and pastes. */
  .doc-eq-block {
    margin: 0 0 var(--space-4);
    padding: var(--space-3) var(--space-4);
    font-family: var(--font-mono);
    font-size: var(--text-base);
    color: var(--text-primary);
    background: var(--surface-2);
    border-left: 2px solid var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    overflow-x: auto;
  }

  /* --- At a glance ------------------------------------------------------- */
  .doc-glance {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--space-3);
    margin: var(--space-5) 0 var(--space-7);
    padding: 0;
    list-style: none;
  }
  .doc-glance li {
    margin: 0;
    padding: var(--space-4);
    background: var(--surface-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .doc-glance dt,
  .doc-glance .glance-key {
    display: block;
    margin-bottom: 6px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    /* Muted gray fails contrast at this size in every theme. */
    color: var(--text-secondary);
  }
  .doc-glance .glance-value {
    display: block;
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    line-height: 1.35;
  }

  /* --- Model-level badges ------------------------------------------------ */
  /* Tuned for a dark ground. The light theme needs its own set: the greens and
     oranges that read well on near-black drop to under 2:1 on white. */
  :root {
    --badge-simulated: #6fd08a;
    --badge-approximate: #f2a65a;
    --badge-illustrative: #c58cf0;
    --badge-measured: #5ec8d8;
  }
  :root[data-theme='daylight'] {
    --badge-simulated: #10714a;
    --badge-approximate: #96530b;
    --badge-illustrative: #6b3fa0;
    --badge-measured: #0e6472;
  }
  .doc-badge {
    display: inline-block;
    padding: 2px 9px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    border-radius: var(--radius-full);
    border: 1px solid currentColor;
  }
  .doc-badge.is-simulated {
    color: var(--badge-simulated);
  }
  .doc-badge.is-analytic {
    color: var(--accent-strong, var(--accent));
  }
  .doc-badge.is-approximate {
    color: var(--badge-approximate);
  }
  .doc-badge.is-illustrative {
    color: var(--badge-illustrative);
  }
  .doc-badge.is-absent {
    color: var(--text-secondary);
  }
  /* The only badge on this page that means "somebody else measured this".
     There is exactly one thing in the application it applies to - the
     published GW150914 traces - and it is worth being visibly different from
     the four kinds of thing Gravitas computes for itself. */
  .doc-badge.is-measured {
    color: var(--badge-measured);
  }

  /* --- Feature blocks ---------------------------------------------------- */
  .doc-feature {
    margin: var(--space-5) 0;
    padding: var(--space-5);
    background: var(--surface-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .doc-feature > h3 {
    margin-top: 0;
  }
  .doc-feature dl {
    display: grid;
    grid-template-columns: minmax(120px, max-content) 1fr;
    gap: var(--space-2) var(--space-4);
    margin: var(--space-3) 0 0;
    font-size: var(--text-sm);
  }
  .doc-feature dt {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
    padding-top: 3px;
  }
  .doc-feature dd {
    margin: 0;
    color: var(--text-secondary);
  }

  .doc-note {
    margin: var(--space-4) 0;
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    background: var(--accent-soft);
    border-left: 2px solid var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  }
  .doc-note p:last-child {
    margin-bottom: 0;
  }

  /* --- Tables ------------------------------------------------------------ */
  .doc-table-wrap {
    margin: var(--space-4) 0 var(--space-6);
    overflow-x: auto;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .doc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
  }
  .doc-table th,
  .doc-table td {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border-subtle);
  }
  .doc-table th {
    font-size: var(--text-2xs);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
    background: var(--panel-bg);
    white-space: nowrap;
  }
  .doc-table td {
    color: var(--text-secondary);
  }
  .doc-table tr:last-child td {
    border-bottom: none;
  }
  .doc-table td:first-child {
    color: var(--text-primary);
    font-weight: var(--weight-medium);
  }

  /* --- Contents ---------------------------------------------------------- */
  .doc-toc {
    margin: var(--space-6) 0 var(--space-8);
    padding: var(--space-4) var(--space-5);
    background: var(--surface-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .doc-toc h2 {
    margin: 0 0 var(--space-3);
    font-size: var(--text-2xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-secondary);
  }
  .doc-toc ol {
    margin: 0;
    padding-left: var(--space-5);
    columns: 2;
    column-gap: var(--space-6);
  }
  .doc-toc li {
    margin-bottom: 6px;
    break-inside: avoid;
  }

  /* --- Footer ------------------------------------------------------------ */
  .doc-foot {
    padding: var(--space-6) var(--space-5) var(--space-8);
    margin-top: var(--space-9);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    text-align: center;
    border-top: 1px solid var(--border-subtle);
  }
  .doc-foot a {
    color: var(--text-secondary);
  }
  .doc-foot-sep {
    margin: 0 var(--space-2);
    opacity: 0.5;
  }

  /* --- Skip link --------------------------------------------------------- */
  .doc-skip {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
  }
  .doc-skip:focus {
    left: var(--space-3);
    top: var(--space-3);
    padding: var(--space-2) var(--space-4);
    color: var(--accent-contrast);
    background: var(--accent);
    border-radius: var(--radius-sm);
  }

  /* --- Narrow screens ---------------------------------------------------- */
  @media (max-width: 720px) {
    .doc-top {
      flex-direction: column;
      align-items: flex-start;
      gap: var(--space-2);
      padding: var(--space-3) var(--space-4);
    }
    .doc-nav {
      margin-left: 0;
      gap: var(--space-3);
    }
    .doc-main {
      padding: var(--space-6) var(--space-4) var(--space-8);
    }
    .doc-toc ol {
      columns: 1;
    }
    .doc-feature dl {
      grid-template-columns: 1fr;
      gap: 2px var(--space-3);
    }
    .doc-feature dd {
      margin-bottom: var(--space-2);
    }
  }

  /* --- Print ------------------------------------------------------------- */
  @media print {
    .doc-top,
    .doc-foot,
    .doc-skip {
      display: none;
    }
    .doc-main {
      max-width: none;
    }
  }
}

/* =============================================================================
   Instructor portal
   ============================================================================ */

@layer components {
  /* The component layer sets `display` on every button and several containers,
     which beats the user-agent rule for [hidden]. Without this, anything hidden
     by the portal's own logic stays on screen. */
  .doc-page [hidden] {
    display: none !important;
  }

  .portal-loading {
    padding: var(--space-8) 0;
    text-align: center;
    color: var(--text-muted);
  }

  /* --- Login ------------------------------------------------------------- */
  .portal-login {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: var(--space-7);
    align-items: start;
    padding-top: var(--space-6);
  }
  .portal-login-card h1 {
    margin: 0 0 var(--space-3);
    font-size: clamp(1.7rem, 1.2rem + 1.8vw, 2.4rem);
    line-height: 1.15;
  }
  .portal-login-card .doc-lede {
    margin-bottom: var(--space-6);
    font-size: var(--text-base);
  }
  .portal-field {
    display: block;
    margin-bottom: var(--space-3);
  }
  .portal-field > span:first-child {
    display: block;
    margin-bottom: 6px;
    font-size: var(--text-sm);
    color: var(--text-secondary);
  }
  .portal-input-row {
    display: flex;
    gap: var(--space-2);
  }
  .portal-input-row input {
    flex: 1;
    min-width: 0;
    padding: var(--space-3) var(--space-3);
    font-family: inherit;
    font-size: var(--text-md);
    color: var(--text-primary);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
  }
  .portal-input-row input:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 1px;
    border-color: var(--accent);
  }
  .portal-error {
    margin: 0 0 var(--space-3);
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    border-left: 2px solid var(--danger);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  }
  .portal-error[hidden] {
    display: none;
  }
  .portal-hint {
    margin-top: var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-muted);
  }
  .portal-aside {
    padding: var(--space-5);
    background: var(--surface-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .portal-aside h2 {
    margin: 0 0 var(--space-3);
    font-size: var(--text-base);
  }
  .portal-aside p {
    font-size: var(--text-sm);
    color: var(--text-secondary);
  }
  .portal-aside p:last-child {
    margin-bottom: 0;
  }
  .portal-version {
    margin: var(--space-3) 0 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-muted);
  }

  /* --- Resource cards ---------------------------------------------------- */
  .res-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-6);
  }
  .res-grid.is-stacked {
    grid-template-columns: 1fr;
  }
  .res-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-5);
    background: var(--surface-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .res-card h3 {
    margin: 0;
    font-size: var(--text-base);
    color: var(--text-primary);
  }
  .res-card p {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
  }
  .res-card .ui-button {
    align-self: flex-start;
    text-decoration: none;
    text-align: center;
  }
  .res-card.is-investigation,
  .res-format {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(240px, max-content);
    gap: var(--space-5);
    align-items: start;
  }
  /* The subtitle is body-size text, so it needs body-text contrast rather than
     the muted gray used for labels. */
  .res-sub {
    color: var(--text-secondary) !important;
  }
  .res-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
    margin-top: var(--space-3) !important;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary) !important;
  }
  .res-meta span {
    padding: 2px 8px;
    background: var(--panel-bg);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-full);
  }
  .res-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }
  .res-actions .ui-button {
    align-self: stretch;
    text-align: center;
  }

  /* --- Filters ----------------------------------------------------------- */
  .res-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-4) 0 var(--space-5);
  }
  .res-filter {
    padding: 5px 14px;
    font-family: inherit;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    background: var(--button-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    cursor: pointer;
  }
  .res-filter:hover {
    color: var(--text-primary);
    background: var(--button-hover-bg);
  }
  .res-filter[aria-pressed='true'] {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent);
  }

  /* --- The instructor dashboard ------------------------------------------- */

  /* A document button is a touch target on a phone, and the app-wide .ui-button
     is 34px high. Raised here rather than globally: the same class draws the
     simulator's control rail, where 44px rows would cost vertical space the
     canvas needs.

     These rules are deliberately shared rather than written per element. This
     file is concatenated into css/app.css, which is the simulation's initial
     download and the tightest budget in the project - so a dashboard control
     costs the sandbox bytes even though the sandbox never draws one. Every
     declaration here is one the sandbox pays for. */
  .res-card .ui-button,
  .res-format .ui-button,
  .res-filter,
  .res-search-input,
  .res-topic-select {
    min-height: 44px;
  }

  /* The two form controls are the same control in two shapes. */
  .res-search-input,
  .res-topic-select {
    padding: 0 var(--space-3);
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text-primary);
    border: 1px solid var(--border);
  }
  .res-search-input:focus-visible,
  .res-topic-select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  .res-search-input {
    background: var(--panel-bg);
    border-radius: var(--radius-md);
  }
  .res-topic-select {
    /* Grows where there is room and shrinks where there is not; `min-width: 0`
       is what lets a flex item go below its content size, and without it the
       label plus an 18rem select push a 320px viewport into a scroll. */
    flex: 1 1 12rem;
    min-width: 0;
    max-width: 18rem;
    background: var(--button-bg);
    border-radius: var(--radius-full);
  }

  /* Labels, the status line and the "no worksheet" note are all secondary text
     at the body size; only the status line's spacing differs. */
  .res-search-label,
  .res-topic-label,
  .portal-status,
  .res-format-purpose,
  .res-none {
    font-size: var(--text-sm);
    color: var(--text-secondary) !important;
  }
  .portal-status {
    margin: var(--space-3) 0 0;
  }
  /* Empty until something happens, so it takes no room and does not read as a
     heading with nothing under it. */
  .portal-status:empty {
    display: none;
  }
  .res-format-purpose {
    margin-top: var(--space-2) !important;
  }
  .res-none {
    font-style: italic;
  }

  /* A count beside a heading, so "how much is here" is answerable without
     scrolling to the end of the list. */
  .res-count {
    margin-left: var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
    white-space: nowrap;
  }

  /* The search field stacks; the topic row sits on one line and wraps. */
  .res-search,
  .res-topic {
    display: flex;
    gap: var(--space-2);
    max-width: 100%;
  }
  .res-search {
    flex-direction: column;
    margin: var(--space-4) 0 0;
    max-width: 30rem;
  }
  .res-topic {
    align-items: center;
    flex-wrap: wrap;
  }

  /* One row per activity format. The row is the same two-column shape as an
     investigation card, so it borrows that rule rather than restating it. */
  .res-formats {
    grid-column: 1 / -1;
    display: grid;
    gap: var(--space-3);
    margin: var(--space-2) 0 0;
    padding: 0;
    list-style: none;
  }
  .res-format {
    padding: var(--space-4);
    background: var(--panel-bg);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
  }
  .res-format-name {
    margin: 0 0 var(--space-1);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-primary) !important;
  }

  @media (max-width: 860px) {
    .portal-login {
      grid-template-columns: 1fr;
      gap: var(--space-5);
    }
    .res-card.is-investigation,
    .res-format {
      grid-template-columns: 1fr;
      gap: var(--space-4);
    }
    /* The count wraps under its heading rather than pushing it out of the
       viewport at 320px. */
    .res-count {
      display: block;
      margin-left: 0;
      margin-top: var(--space-1);
      white-space: normal;
    }
  }
}

/* =============================================================================
   The validation page
   -----------------------------------------------------------------------------
   A results page, so the design work is information design rather than
   typography: the verdict has to read from across a room, the four kinds of
   check have to stay distinguishable everywhere they appear, and the amount of
   headroom each check left has to be visible without reading a column of
   exponents.

   The kind colors are semantic and deliberately separate from --accent, which
   stays the page's own voice. Pass and fail use --success and --danger, which
   is the one place on the site where color carries meaning; both are paired
   with a word so nothing depends on seeing the difference.
   ============================================================================ */

@layer components {
  /* --- Headline verdict --------------------------------------------------- */

  /* The page is wide for its tables and its chart; its prose is not. */
  .val-page-prose,
  body.doc-page .doc-main.is-wide > p,
  body.doc-page .doc-main.is-wide > ul {
    max-width: 72ch;
  }

  .val-verdict {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-6);
    margin: var(--space-6) 0 var(--space-5);
    padding: var(--space-5) var(--space-6);
    border: 1px solid var(--border);
    border-left: 3px solid var(--success);
    border-radius: var(--radius-lg);
    background: var(--surface-1);
  }

  .val-verdict[data-state='fail'] {
    border-left-color: var(--danger);
  }

  .val-verdict-main {
    flex: 0 0 auto;
  }

  .val-score {
    margin: 0;
    font-size: clamp(2.6rem, 7vw, 3.6rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
  }

  .val-score .is-pass {
    color: var(--success);
  }

  .val-score .is-fail {
    color: var(--danger);
  }

  .val-score-of {
    color: var(--text-secondary);
    font-weight: 500;
  }

  .val-score-label {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
  }

  .val-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    gap: var(--space-4);
    flex: 1 1 320px;
    margin: 0;
  }

  .val-facts dt {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary, var(--text-secondary));
  }

  .val-facts dd {
    margin: 2px 0 0;
    font-size: var(--text-md);
    font-variant-numeric: tabular-nums;
  }

  /* --- Run it yourself ----------------------------------------------------- */

  .val-run {
    margin: var(--space-5) 0;
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--accent-line);
    border-radius: var(--radius-lg);
    background: var(--accent-soft);
  }

  .val-run-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
  }

  .val-run h2 {
    margin: 0;
    font-size: var(--text-lg);
    border: 0;
    padding: 0;
  }

  .val-run p {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    max-width: 52ch;
  }

  .val-run-btn {
    flex: 0 0 auto;
    padding: var(--space-2) var(--space-5);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--accent-contrast);
    background: var(--accent);
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
  }

  .val-run-btn:hover:not(:disabled) {
    background: var(--accent-strong);
  }

  .val-run-btn:disabled {
    opacity: 0.6;
    cursor: progress;
  }

  .val-run-btn:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 3px;
  }

  .val-run-bar {
    height: 3px;
    margin-top: var(--space-3);
    overflow: hidden;
    border-radius: 2px;
    background: var(--accent-line);
  }

  /* Indeterminate on purpose. The suite reports nothing until it is done, and
     a bar that invented a percentage would be describing a guess. */
  .val-run-fill {
    width: 35%;
    height: 100%;
    background: var(--accent);
    animation: val-sweep 1.1s ease-in-out infinite;
  }

  @keyframes val-sweep {
    0% {
      transform: translateX(-100%);
    }
    100% {
      transform: translateX(340%);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .val-run-fill {
      width: 100%;
      animation: none;
      opacity: 0.55;
    }
  }

  .val-run-result {
    margin: var(--space-3) 0 0;
    font-size: var(--text-sm);
    font-weight: 600;
  }

  .val-run-result.is-pass {
    color: var(--success);
  }

  .val-run-result.is-fail {
    color: var(--danger);
  }

  /* --- The four kinds ------------------------------------------------------ */

  .val-kinds {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--space-3);
    margin: var(--space-4) 0 var(--space-6);
  }

  .val-kind {
    padding: var(--space-4);
    border: 1px solid var(--border);
    border-top: 2px solid var(--val-kind, var(--border));
    border-radius: var(--radius-md);
    background: var(--surface-1);
  }

  .val-kind h3 {
    margin: 0 0 var(--space-2);
    font-size: var(--text-md);
  }

  .val-kind p {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    line-height: 1.5;
  }

  .val-kind-count {
    margin: 0 0 var(--space-1) !important;
    font-size: var(--text-2xl, 1.6rem);
    font-weight: 700;
    line-height: 1;
    color: var(--val-kind, var(--text-primary)) !important;
    font-variant-numeric: tabular-nums;
  }

  /* One hue per kind, used by the cards, the chart lanes, the row chips and the
     filter buttons, so a reader learns the mapping once. */
  [data-kind='analytic'] {
    --val-kind: var(--accent);
  }

  [data-kind='integration'] {
    --val-kind: var(--success);
  }

  /* Not --warning, though it is the natural third choice. Amber is reserved on
     this page for a check that used more than half its tolerance, and for the
     band those checks land in; a Published dot in that band was indistinguishable
     tinguishable from a flagged one. */
  [data-kind='data'] {
    --val-kind: #f472b6;
  }

  [data-kind='approximation'] {
    --val-kind: #c084fc;
  }

  /* The fifth kind. Indigo rather than amber, which is reserved above, and
     rather than a second green or cyan, which --accent already becomes in two
     themes. It sits near the approximation purple, which is acceptable here
     because every chip, lane and card carries its label as text: the hue is a
     shortcut for a reader who has learned it, never the only thing saying
     which kind this is. */
  [data-kind='empirical'] {
    --val-kind: #818cf8;
  }

  /* --- Margin chart -------------------------------------------------------- */

  .val-chart-wrap {
    margin: var(--space-4) 0 var(--space-3);
  }

  .val-chart {
    padding: var(--space-4) var(--space-4) var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface-1);
  }

  .val-lane {
    display: grid;
    grid-template-columns: 92px 1fr;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-2);
  }

  .val-lane-label {
    font-size: var(--text-xs);
    text-align: right;
    color: var(--text-secondary);
  }

  /* Decade gridlines, and one tinted band for the last decade: a check using
     more than a tenth of its tolerance is the only region worth flagging. The
     first version washed the whole track in a warning gradient from 10^-4
     onward, which is where almost every check sits, so a set of results with a
     median of 0.02% of its allowance looked alarming. */
  .val-lane-track {
    position: relative;
    display: block;
    height: 26px;
    border-radius: var(--radius-sm);
    background-color: var(--surface-2);
    background-image:
      linear-gradient(
        to right,
        transparent 0 87.5%,
        color-mix(in srgb, var(--warning) 16%, transparent) 87.5% 100%
      ),
      repeating-linear-gradient(
        to right,
        var(--border) 0 1px,
        transparent 1px 12.5%
      );
  }

  /* Semi-transparent so a cluster reads as denser rather than as one blob:
     with 74 analytic checks on one lane, several land within a pixel. */
  .val-dot {
    position: absolute;
    top: 50%;
    width: 8px;
    height: 8px;
    margin: -4px 0 0 -4px;
    border-radius: 50%;
    background: color-mix(
      in srgb,
      var(--val-kind, var(--accent)) 72%,
      transparent
    );
    border: 1px solid var(--surface-1);
  }

  /* Over half its allowance. Rare, and the one thing on the chart a reader
     should be able to find without reading the caption. */
  .val-dot.is-tight {
    background: var(--warning);
    border-color: var(--warning);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 30%, transparent);
  }

  /* White rather than var(--focus-ring): these dots are drawn in the accent
     and warning colors, so the ring has to be something neither of them is. */
  .val-dot:focus-visible {
    outline: 2px solid var(--text-primary);
    outline-offset: 2px;
  }

  .val-axis {
    position: relative;
    height: 18px;
    margin-left: calc(92px + var(--space-3));
    border-top: 1px solid var(--border);
  }

  .val-tick {
    position: absolute;
    top: 2px;
    transform: translateX(-50%);
    font-size: var(--text-xs);
    color: var(--text-tertiary, var(--text-secondary));
    white-space: nowrap;
  }

  .val-axis-title {
    margin: var(--space-3) 0 0;
    margin-left: calc(92px + var(--space-3));
    font-size: var(--text-xs);
    color: var(--text-secondary);
  }

  .val-axis-title span {
    display: block;
    color: var(--text-tertiary, var(--text-secondary));
  }

  .val-chart-wrap figcaption {
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    color: var(--text-secondary);
  }

  .val-exact-line {
    padding: var(--space-3) var(--space-4);
    border-left: 3px solid var(--border-strong, var(--border));
    background: var(--surface-1);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
  }

  /* --- Filters and tables --------------------------------------------------- */

  .val-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-4) 0;
  }

  .val-filter {
    padding: var(--space-1) var(--space-3);
    font-family: inherit;
    font-size: var(--text-xs);
    color: var(--text-secondary);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
  }

  .val-filter.is-on {
    color: var(--text-primary);
    border-color: var(--val-kind, var(--accent));
    box-shadow: inset 0 0 0 1px var(--val-kind, var(--accent));
  }

  .val-filter:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
  }

  .val-group {
    margin: var(--space-6) 0;
  }

  .val-group h3 {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
    font-size: var(--text-lg);
  }

  .val-group-count {
    font-size: var(--text-xs);
    font-weight: 400;
    color: var(--text-secondary);
  }

  /* Seven columns, four of them numbers held on one line, and a name column
     that has to hold "Energy error does not grow with run length (6 vs 60
     orbits)". Without a floor the browser gives the names whatever is left,
     which was one word per line. The wrapper scrolls; the table does not
     compress. */
  /* Six columns, four of them numbers held on one line, and a name column
     that has to hold "Energy error does not grow with run length (6 vs 60
     orbits)". Without a floor the browser gives the names whatever is left,
     which was one word per line. The wrapper scrolls; the table does not
     compress. */
  .val-table {
    min-width: 760px;
    font-size: var(--text-sm);
  }

  .val-table th {
    white-space: nowrap;
  }

  .val-table .val-n {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .val-unit {
    display: block;
    font-size: var(--text-xs);
    color: var(--text-tertiary, var(--text-secondary));
  }

  .val-cell-name {
    width: 42%;
    min-width: 34ch;
  }

  .val-cell-name summary {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    cursor: pointer;
    list-style: none;
  }

  .val-cell-name summary::-webkit-details-marker {
    display: none;
  }

  .val-name {
    flex: 1 1 auto;
    font-weight: 500;
  }

  .val-cell-name summary::after {
    content: 'why';
    flex: 0 0 auto;
    font-size: var(--text-xs);
    color: var(--accent);
  }

  .val-cell-name details[open] summary::after {
    content: 'hide';
  }

  .val-cell-name summary:focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
  }

  .val-why {
    max-width: 60ch;
    margin-top: var(--space-2);
    padding-left: var(--space-3);
    border-left: 2px solid var(--border);
    font-size: var(--text-xs);
    color: var(--text-secondary);
    line-height: 1.55;
  }

  .val-why p {
    margin: 0 0 var(--space-2);
  }

  .val-source {
    color: var(--text-primary);
  }

  .val-chip {
    flex: 0 0 auto;
    padding: 1px var(--space-2);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--val-kind, var(--accent));
    border: 1px solid var(--val-kind, var(--accent));
    border-radius: 999px;
    white-space: nowrap;
  }

  /* The track carries a hairline so it still reads as a measuring bar when the
     fill is a sliver, which for most of these checks it is: a bar showing 0.02%
     of a tolerance looks like a rendering failure without one. */
  .val-bar {
    display: inline-block;
    width: 54px;
    height: 8px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-3);
    overflow: hidden;
    vertical-align: middle;
  }

  .val-bar > span {
    display: block;
    min-width: 2px;
    height: 100%;
    background: var(--success);
  }

  .val-bar.is-tight > span {
    background: var(--warning);
  }

  .val-bar-exact {
    font-size: var(--text-xs);
    color: var(--text-tertiary, var(--text-secondary));
  }

  .val-pill {
    display: inline-block;
    padding: 1px var(--space-2);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    border-radius: 999px;
  }

  .val-pill.is-pass {
    color: var(--success);
    border: 1px solid var(--success);
  }

  .val-pill.is-fail {
    color: var(--danger);
    border: 1px solid var(--danger);
  }

  @media (max-width: 640px) {
    .val-lane {
      grid-template-columns: 66px 1fr;
    }

    .val-axis,
    .val-axis-title {
      margin-left: calc(66px + var(--space-3));
    }

    /* The name column claims 34ch on a desktop, which on a phone is most of the
       screen and leaves the numbers entirely off it. The table still scrolls,
       but it starts with a measurement visible. */
    .val-cell-name {
      min-width: 20ch;
    }

    .val-table {
      min-width: 620px;
    }

    /* Error and tolerance are only meaningful next to each other, and the Used
       bar already says what their ratio is. Dropping Error keeps the sideways
       scroll short. */
    .val-table th:nth-child(4),
    .val-table td:nth-child(4) {
      display: none;
    }
  }
}
