/* ==========================================================================
   GaugeSeal — design tokens (canonical, machine-consumable)
   --------------------------------------------------------------------------
   The one source of truth for colour/type tokens that 7.C2 (design system),
   7.E3 (digest), and 7.H1 (marketing site) compose from. Rationale and the
   full written system live in docs/brand-guidelines.md; rendered marks live
   beside this file in brand_package/. If a hex here disagrees with the doc,
   this file wins (brand_package/ is source of truth) and the doc is corrected.

   2026-08-01 — the accent and the two neutrals were re-pointed at the values
   in the canonical artwork (the manually approved logo/mascot SVGs in this
   folder), which is now the highest authority: artwork > tokens > docs. The
   five health states did not move; every ratio below was re-verified against
   the new grounds and the margins are stated in the audit note at the foot of
   this file.

   Structure:
     :root            raw palette — one ink-lapis accent + the two neutrals +
                      the five health states (core / on-light text / on-dark)
     .gs-product      Porcelain & Slate semantic aliases + per-neutral tints
     .gs-brand        Graphite & Bone   semantic aliases + per-neutral tints
     .gs-*-dark       dark-ground (slate / basalt) semantic aliases

   Contrast: every value below is WCAG 2.1 verified. Cores clear 3:1 on all
   four light grounds (graphical-object bar — glyph strokes, borders, large
   fills). The -text variants clear 4.5:1 as SMALL text on both the card AND
   the soft-tint pill background, in BOTH neutrals. on-dark variants clear
   4.5:1 on slate and basalt. See brand-guidelines.md §3d for the method.
   ========================================================================== */

:root {
  /* ── Shared accent — constant across both temperatures (guidelines §3a) ── */
  --gs-ink-lapis: #2F50BC;        /* the one brand accent: links, primary action, focus, the mark's gauge arc + waterline */
  --gs-ink-lapis-hover: #2A46A8;  /* pressed/hover on light grounds */
  --gs-lapis-300: #97A8DC;        /* accent on dark grounds, the mark's outer whisker length */
  --gs-lapis-300-brand: #93A8EE;  /* warm-tuned accent tint on bone surfaces */

  /* ── Product neutral · Porcelain & Slate (guidelines §3b) ── */
  --gs-slate: #111824;
  --gs-slate-700: #1D2430;
  --gs-porcelain: #F4F5F9;
  --gs-porcelain-card: #FFFFFF;
  --gs-p-border: #E3E8EF;
  --gs-p-muted: #5A6472;

  /* ── Brand neutral · Graphite & Bone (guidelines §3c) ── */
  --gs-basalt: #1E1F23;
  --gs-basalt-700: #2A2B30;
  --gs-bone: #F3F0E9;
  --gs-bone-card: #FBFAF6;
  --gs-sand: #E0DACD;
  --gs-taupe: #6B655A;

  /* ── Health-state constitution — semantic, one hue per state, identical
        across both temperatures (guidelines §3d). Three roles per state:
          core    glyph strokes / borders / large fills   (≥3:1 light grounds)
          -text   small labels / coloured text            (≥4.5:1 card + tint)
          -ondark glyphs & text on slate / basalt         (≥4.5:1)              */
  --gs-healthy: #128A5B;        --gs-healthy-text: #107950;        --gs-healthy-ondark: #34C98A;
  --gs-at-risk: #B7791F;        --gs-at-risk-text: #926119;        --gs-at-risk-ondark: #F5B841;
  --gs-violation: #C2372D;      --gs-violation-text: #C0362D;      --gs-violation-ondark: #F87171;
  --gs-underused: #6D5BD0;      --gs-underused-text: #6A58CA;      --gs-underused-ondark: #A79BF0;
  --gs-insufficient: #64748B;   --gs-insufficient-text: #5D6C81;   --gs-insufficient-ondark: #94A3B8;

  /* ── Type (already-decided scale, tokenised; guidelines §4) ── */
  --gs-font-sans: 'Inter', system-ui, 'Helvetica Neue', Arial, sans-serif;
  --gs-font-mono: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, Menlo, monospace;
  --gs-text-xs: 12px; --gs-text-sm: 14px; --gs-text-md: 16px; --gs-text-lg: 20px;
  --gs-text-xl: 24px; --gs-text-2xl: 32px; --gs-text-3xl: 40px;
  --gs-weight-regular: 400; --gs-weight-medium: 500; --gs-weight-semibold: 600; --gs-weight-bold: 700;
  --gs-leading-body: 1.5;
  --gs-wordmark-tracking: -0.013em;   /* −1.3% on the GaugeSeal lockup */

  /* ── Radius (from the brand board; spacing scale is 7.C2's to set) ── */
  --gs-radius-sm: 8px; --gs-radius-md: 12px; --gs-radius-lg: 14px; --gs-radius-xl: 16px;
}

/* ==========================================================================
   Semantic themes — the two neutrals resolved off the one shared accent.
   Set one of these classes on a surface root; children read the aliases.
   The per-neutral status *tints* (soft pill backgrounds) are the only status
   values that differ by temperature — cooler on porcelain, warmer on bone —
   re-mixed for AA (status text on its tint clears 4.5:1 in both).
   ========================================================================== */

.gs-product {
  color-scheme: light;
  --bg: var(--gs-porcelain);
  --surface: var(--gs-porcelain-card);
  --border: var(--gs-p-border);
  --text: var(--gs-slate);
  --text-muted: var(--gs-p-muted);
  --accent: var(--gs-ink-lapis);
  --accent-hover: var(--gs-ink-lapis-hover);
  --focus-ring: var(--gs-ink-lapis);

  /* soft-tint pill backgrounds — status hue @10% over the white card (cool) */
  --tint-healthy: #E7F3EF;
  --tint-at-risk: #F8F2E9;
  --tint-violation: #F9EBEA;
  --tint-underused: #F0EFFA;
  --tint-insufficient: #F0F1F3;
}

.gs-brand {
  color-scheme: light;
  --bg: var(--gs-bone);
  --surface: var(--gs-bone-card);
  --border: var(--gs-sand);
  --text: var(--gs-basalt);
  --text-muted: var(--gs-taupe);
  --accent: var(--gs-ink-lapis);
  --accent-hover: var(--gs-ink-lapis-hover);
  --focus-ring: var(--gs-ink-lapis);

  /* soft-tint pill backgrounds — status hue @10% over the bone card (warm) */
  --tint-healthy: #E4EFE6;
  --tint-at-risk: #F4EDE0;
  --tint-violation: #F5E6E2;
  --tint-underused: #EDEAF2;
  --tint-insufficient: #ECEDEB;
}

/* Dark grounds (headers, dark cards, the mark disc). Both dark neutrals clear
   AA for the on-dark status hues and lapis-300; only bg/surface differ. */
.gs-product-dark {
  color-scheme: dark;
  --bg: var(--gs-slate);
  --surface: var(--gs-slate-700);
  --border: #2E3644;
  --text: var(--gs-porcelain);
  --text-muted: #9AA6B6;
  --accent: var(--gs-lapis-300);
  --accent-hover: #A9BBEF;
  --focus-ring: var(--gs-lapis-300);
}

.gs-brand-dark {
  color-scheme: dark;
  --bg: var(--gs-basalt);
  --surface: var(--gs-basalt-700);
  --border: #3A3B41;
  --text: var(--gs-bone);
  --text-muted: #A39C8E;
  --accent: var(--gs-lapis-300-brand);
  --accent-hover: #AEBEF1;
  --focus-ring: var(--gs-lapis-300-brand);
}

/* ==========================================================================
   Contrast audit — re-run 2026-08-01 after the accent/neutral change.
   All bars still clear; the neutral shift was <0.2% luminance.
     ink-lapis     6.44:1 on porcelain (bar 3.0 graphical / 4.5 text)
     healthy       core 4.01:1 on porcelain (bar 3.0) | text 5.42:1 on card (bar 4.5) | on-dark 8.36:1 on slate (bar 4.5)
     at-risk       core 3.34:1 on porcelain (bar 3.0) | text 5.32:1 on card (bar 4.5) | on-dark 10.01:1 on slate (bar 4.5)
     violation     core 4.97:1 on porcelain (bar 3.0) | text 5.52:1 on card (bar 4.5) | on-dark 6.43:1 on slate (bar 4.5)
     underused     core 4.75:1 on porcelain (bar 3.0) | text 5.44:1 on card (bar 4.5) | on-dark 7.28:1 on slate (bar 4.5)
     insufficient  core 4.37:1 on porcelain (bar 3.0) | text 5.35:1 on card (bar 4.5) | on-dark 6.94:1 on slate (bar 4.5)
   ========================================================================== */

/* ==========================================================================
   7.C2 — app shell + components, composed on the tokens above.
   The spacing scale below is set here (tokens.css defers it to 7.C2).
   ========================================================================== */

:root {
  --gs-space-1: 4px;  --gs-space-2: 8px;  --gs-space-3: 12px;
  --gs-space-4: 16px; --gs-space-5: 24px; --gs-space-6: 32px;
  --gs-space-7: 48px; --gs-space-8: 64px;

  /* The two layout measures (7.L1). One 960px column has served all three
     layouts since 7.C2 — a prose width, kept under the data grids too.
     Defined here and adopted by nobody yet: the first opt-in is a visual
     change and re-shoots that state's visual baseline. */
  --gs-measure-prose: 960px;  /* reading: the site, the documents, forms */
  --gs-measure-app: 1240px;   /* deciding: boards, tables, drill-downs */
}

@font-face {
  font-family: 'Inter';
  src: url('/static/inter.693b77d4.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--gs-font-sans);
  font-size: var(--gs-text-sm);
  line-height: var(--gs-leading-body);
}
h1, h2, h3 { line-height: 1.25; margin: 0 0 var(--gs-space-3); }
h1 { font-size: var(--gs-text-xl); font-weight: var(--gs-weight-semibold); }
h2 { font-size: var(--gs-text-lg); font-weight: var(--gs-weight-semibold); }
h3 { font-size: var(--gs-text-md); font-weight: var(--gs-weight-medium); }
/* A subheading inside a card opens a second section of it, so it needs
   air above; without this it sits flush against whatever it follows. */
.gs-card h3 { margin-top: var(--gs-space-5); }
p { margin: 0 0 var(--gs-space-3); }
a { color: var(--accent); }
a:hover { color: var(--accent-hover); }
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.gs-muted { color: var(--text-muted); }
/* Mono holds the strings with no spaces in them, and one wider than
   its column hands its width to the document (7.L5). `break-word` and
   not `anywhere`: it breaks the word that will not fit and lowers no
   box's min-content width, so the table columns mono also sits in are
   exactly as wide as they were. */
.gs-mono {
  font-family: var(--gs-font-mono); font-size: 0.93em;
  overflow-wrap: break-word;
}

/* ── Shell ── */
.gs-header { background: var(--surface); border-bottom: 1px solid var(--border); }
.gs-header-inner {
  max-width: 960px; margin: 0 auto;
  padding: var(--gs-space-3) var(--gs-space-5);
  display: flex; align-items: center; gap: var(--gs-space-5); flex-wrap: wrap;
}
/* The header's home link. Named `gs-logo`, not `gs-brand`: `.gs-brand`
   and `.gs-product` are the two *theme scopes* tokens.css defines, and a
   component sharing one of those names silently re-themed everything
   inside it — the wordmark in the product header was resolving `--text`
   to basalt, the marketing ink, from 7.C2 until 7.H1 made the collision
   visible. Theme classes go on a surface root and nowhere else. */
.gs-logo {
  display: flex; align-items: center; gap: var(--gs-space-2);
  text-decoration: none; color: var(--text);
}
.gs-wordmark {
  font-size: var(--gs-text-md); font-weight: var(--gs-weight-medium);
  letter-spacing: var(--gs-wordmark-tracking); color: var(--text);
}
.gs-wordmark b { font-weight: var(--gs-weight-bold); }
.gs-nav { display: flex; gap: var(--gs-space-4); margin-right: auto; }
.gs-nav-link {
  color: var(--text-muted); text-decoration: none;
  font-weight: var(--gs-weight-medium); padding: var(--gs-space-1) 0;
}
.gs-nav-link:hover { color: var(--text); }
.gs-nav-active { color: var(--text); box-shadow: 0 2px 0 var(--accent); }
.gs-session { display: flex; align-items: center; gap: var(--gs-space-3); margin-left: auto; }
.gs-session-who { display: flex; flex-direction: column; line-height: 1.3; text-align: right; }
.gs-session-email { color: var(--text-muted); font-size: var(--gs-text-xs); }
.gs-main {
  flex: 1; width: 100%; max-width: 960px; margin: 0 auto;
  padding: var(--gs-space-6) var(--gs-space-5);
}
.gs-footer {
  max-width: 960px; margin: 0 auto; width: 100%;
  padding: var(--gs-space-4) var(--gs-space-5);
  color: var(--text-muted); font-size: var(--gs-text-xs);
}

/* ── Portal (signed-out) layout ── */
.gs-portal {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; padding: var(--gs-space-6) var(--gs-space-4);
  gap: var(--gs-space-3);
}
.gs-portal .gs-wordmark { font-size: var(--gs-text-lg); }
.gs-portal-card { width: 100%; max-width: 400px; margin-top: var(--gs-space-3); }

/* ── Cards, buttons, forms ── */
.gs-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--gs-radius-lg); padding: var(--gs-space-5);
  margin-bottom: var(--gs-space-5);
}
.gs-btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--gs-space-2); border: 1px solid transparent;
  border-radius: var(--gs-radius-sm); padding: var(--gs-space-2) var(--gs-space-4);
  font: inherit; font-weight: var(--gs-weight-medium);
  background: var(--accent); color: #FFFFFF; cursor: pointer;
  /* A button is a button whether it is a <button> or an <a> (the connect
     card, the site's calls to action): without this the anchor keeps the
     link underline through the fill. */
  text-decoration: none;
}
.gs-btn:hover { color: #FFFFFF; }
.gs-btn:hover { background: var(--accent-hover); }
.gs-btn--secondary {
  background: var(--surface); color: var(--text); border-color: var(--border);
}
.gs-btn--secondary:hover { background: var(--bg); color: var(--text); }
.gs-btn--quiet { background: none; color: var(--text-muted); padding: var(--gs-space-1) var(--gs-space-2); }
.gs-btn--quiet:hover { background: none; color: var(--text); }
.gs-form-note { margin: var(--gs-space-3) 0 0; color: var(--text-muted); }
.gs-field { display: block; margin-bottom: var(--gs-space-4); }
.gs-field-label {
  display: block; margin-bottom: var(--gs-space-1);
  font-weight: var(--gs-weight-medium);
}
.gs-input {
  width: 100%; padding: var(--gs-space-2) var(--gs-space-3);
  font: inherit; color: var(--text);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--gs-radius-sm);
}
.gs-input:focus { border-color: var(--accent); outline: 2px solid var(--focus-ring); outline-offset: 0; }
/* A number set inside the sentence it belongs to (7.G2's contract
   editor): the field is the expectation's own value, not a labelled box
   under a heading, so it sits inline and sizes to the number. */
.gs-input--inline {
  width: 4.5rem; display: inline-block; padding: var(--gs-space-1) var(--gs-space-2);
  text-align: center;
}
.gs-sentence { display: inline-flex; align-items: baseline; gap: var(--gs-space-2); flex-wrap: wrap; }
/* A second, quieter way out of the same card (7.G2: lifting an override
   beside saving one) — it must not read as part of the primary action. */
.gs-form-alt { margin-top: var(--gs-space-4); }
.gs-sentence-blank { color: var(--text-muted); }

/* ── Health-state pills (the §3d constitution) ── */
.gs-state {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px; border-radius: 999px;
  font-size: var(--gs-text-xs); font-weight: var(--gs-weight-medium);
  letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap;
}
.gs-state svg { width: 12px; height: 12px; flex: none; }
.gs-state--healthy { background: var(--tint-healthy); color: var(--gs-healthy-text); }
.gs-state--at-risk { background: var(--tint-at-risk); color: var(--gs-at-risk-text); }
.gs-state--violation { background: var(--tint-violation); color: var(--gs-violation-text); }
.gs-state--underused { background: var(--tint-underused); color: var(--gs-underused-text); }
.gs-state--insufficient { background: var(--tint-insufficient); color: var(--gs-insufficient-text); }
.gs-state--degraded {
  background: repeating-linear-gradient(
    135deg, var(--tint-insufficient), var(--tint-insufficient) 5px,
    var(--surface) 5px, var(--surface) 7px);
  color: var(--gs-insufficient-text);
}

/* ── Tables (thin gridlines, no chartjunk — guidelines §5) ── */
/* The box every table is rendered inside (`ui.table`, 7.R3). A table
   cannot reflow — its widest column is a floor — so a phone either
   scrolls the table or scrolls the whole document, chrome included. The
   table still fills the box wherever it fits, so nothing changes on a
   screen wide enough to hold it. */
.gs-scroll { overflow-x: auto; }
.gs-table { width: 100%; border-collapse: collapse; }
.gs-table th {
  text-align: left; padding: var(--gs-space-2) var(--gs-space-3);
  font-size: var(--gs-text-xs); font-weight: var(--gs-weight-medium);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}
.gs-table td {
  padding: var(--gs-space-2) var(--gs-space-3);
  border-bottom: 1px solid var(--border);
}
.gs-table tr:hover td { background: var(--bg); }

/* ── Session banner (7.D2 demo ribbon) — full-bleed ground, constrained
      row, mirroring the header above it ── */
.gs-banner {
  background: var(--tint-insufficient);
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.gs-banner-inner {
  display: flex; align-items: center; gap: var(--gs-space-4); flex-wrap: wrap;
  max-width: 960px; margin: 0 auto;
  padding: var(--gs-space-2) var(--gs-space-5);
  font-size: var(--gs-text-xs);
}
.gs-banner-inner a { margin-left: auto; }

/* ── Board (7.E1): workspace tabs, meta line, status filters ── */
.gs-board-bar {
  display: flex; align-items: center; gap: var(--gs-space-4);
  flex-wrap: wrap; margin-bottom: var(--gs-space-2);
}
.gs-board-bar form { margin-left: auto; }
.gs-tabs { display: flex; gap: var(--gs-space-2); flex-wrap: wrap; }
.gs-tab {
  padding: var(--gs-space-1) var(--gs-space-3);
  border: 1px solid transparent; border-radius: var(--gs-radius-sm);
  text-decoration: none; color: var(--text-muted);
  font-weight: var(--gs-weight-medium);
}
.gs-tab:hover { color: var(--text); }
.gs-tab--active {
  color: var(--text); background: var(--surface); border-color: var(--border);
}
.gs-board-meta {
  color: var(--text-muted); font-size: var(--gs-text-xs);
  margin: 0 0 var(--gs-space-4);
}
.gs-filters {
  display: flex; align-items: center; gap: var(--gs-space-3);
  flex-wrap: wrap; margin-bottom: var(--gs-space-4);
}
.gs-filter {
  display: inline-flex; align-items: center; gap: var(--gs-space-1);
  text-decoration: none; border: 1px solid transparent;
  border-radius: 999px; padding: 2px;
}
.gs-filter--active { border-color: var(--accent); }
.gs-filter--all {
  padding: 2px var(--gs-space-2); color: var(--text);
  font-size: var(--gs-text-xs); font-weight: var(--gs-weight-medium);
  letter-spacing: 0.04em; text-transform: uppercase;
}
.gs-filter-count {
  color: var(--text-muted); font-size: var(--gs-text-xs);
  padding-right: var(--gs-space-1);
}

/* ── Drill-down (7.E2): crumb, meta strip, coverage facts ── */
.gs-crumb {
  display: inline-block; margin-bottom: var(--gs-space-3);
  font-size: var(--gs-text-xs); text-decoration: none;
  font-weight: var(--gs-weight-medium);
}
.gs-report-meta {
  display: flex; align-items: center; gap: var(--gs-space-3);
  flex-wrap: wrap; margin-bottom: var(--gs-space-5);
  color: var(--text-muted); font-size: var(--gs-text-xs);
}
.gs-facts { list-style: none; margin: 0; padding: 0; }
.gs-facts li { padding: var(--gs-space-1) 0; }
.gs-arrow { color: var(--text-muted); padding: 0 var(--gs-space-1); }

/* ── Onboarding (7.F1): numbered setup steps and the check result ── */
.gs-steps { counter-reset: gs-step; list-style: none; margin: 0; padding: 0; }
.gs-step {
  counter-increment: gs-step; position: relative;
  padding: 0 0 var(--gs-space-4) var(--gs-space-7);
  border-left: 1px solid var(--border); margin-left: 14px;
}
.gs-step:last-child { border-left-color: transparent; padding-bottom: 0; }
.gs-step::before {
  content: counter(gs-step);
  position: absolute; left: -14px; top: -2px;
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--gs-text-xs);
  font-weight: var(--gs-weight-medium);
}
.gs-step h3 { margin-bottom: var(--gs-space-1); }
.gs-step p { color: var(--text-muted); margin-bottom: 0; }
.gs-step--flagged::before {
  border-color: var(--gs-at-risk); color: var(--gs-at-risk-text);
}
.gs-step--flagged h3 { color: var(--gs-at-risk-text); }
/* What a failed connect says first (7.L7). Small — it is a
   reassurance, not the news — and unmissable, because it sits above the
   heading with the only rule on the card. No status hue: nothing here is
   a state of anything, and the constitution keeps saturation for those. */
.gs-kept {
  margin: 0 0 var(--gs-space-4); padding-bottom: var(--gs-space-3);
  border-bottom: 1px solid var(--border);
  font-size: var(--gs-text-sm); color: var(--text-muted);
}
.gs-kept strong { color: var(--text); font-weight: var(--gs-weight-medium); }
.gs-checks { list-style: none; margin: 0 0 var(--gs-space-3); padding: 0; }
.gs-check {
  display: flex; align-items: baseline; gap: var(--gs-space-2);
  padding: var(--gs-space-1) 0;
}
.gs-check-mark { font-weight: var(--gs-weight-bold); width: 1em; flex: none; }
.gs-check--ok .gs-check-mark { color: var(--gs-healthy-text); }
.gs-check--bad .gs-check-mark { color: var(--gs-violation-text); }
.gs-check--skipped { color: var(--text-muted); }
/* First scan (7.F2): a stage still arriving wears the shell's own
   in-progress signal, sized into the mark column. Calm slate, never a
   warning colour — warming up is a designed state, not a degraded one. */
.gs-check--warming .gs-check-mark { width: 12px; height: 12px; align-self: center; }
/* And a stage that will not arrive by waiting (7.F3) carries the hatch
   pill instead of a mark — the "we can't see this" rendering, which is a
   coverage statement and so must not read as a failure. */
.gs-check--degraded .gs-check-mark { color: var(--text-muted); }
.gs-field-hint {
  display: block; margin-top: var(--gs-space-1);
  color: var(--text-muted); font-size: var(--gs-text-xs);
}
/* Walkthrough figures (7.F1a). Captures are 2x and often wider than the
   column, so the box is fluid and the intrinsic size is only there to
   reserve the right aspect while the image loads. */
.gs-figure { margin: var(--gs-space-3) 0 0; }
.gs-figure img {
  display: block; max-width: 100%; height: auto;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--gs-radius-md);
}
.gs-figure figcaption {
  margin-top: var(--gs-space-1);
  color: var(--text-muted); font-size: var(--gs-text-xs);
}

/* ── Snippet (7.F4): the block a customer copies into their own page.
   Scrolls rather than wraps: a wrapped line in code that will be pasted
   into a build is a line that may be pasted wrong, and the one on the
   page is the one that must work. */
.gs-snippet {
  font-family: var(--gs-font-mono); font-size: var(--gs-text-xs);
  line-height: 1.6; color: var(--text);
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--gs-radius-md);
  padding: var(--gs-space-3) var(--gs-space-4);
  margin: 0 0 var(--gs-space-3);
  overflow-x: auto; white-space: pre; tab-size: 2;
}
/* The one value in it the customer must not paste blind. */
.gs-snippet b { font-weight: var(--gs-weight-bold); color: var(--gs-at-risk-text); }

/* ── Notices ── */
.gs-notice {
  border: 1px solid var(--border); border-radius: var(--gs-radius-md);
  background: var(--tint-insufficient); color: var(--text);
  padding: var(--gs-space-3) var(--gs-space-4); margin-bottom: var(--gs-space-4);
}
.gs-notice--error { background: var(--tint-violation); color: var(--gs-violation-text); }
/* The lead carries the alarm; the instructions under it do not. A whole
   paragraph of remedy copy set in violation red is the opposite of the
   brand voice (guidelines §6: calm, never alarmist) — and remedies are
   the helpful part of a failure, not the frightening one. */
.gs-notice--error p { color: var(--text); }

/* ── Empty / loading / error states ── */
.gs-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: var(--gs-space-7) var(--gs-space-4);
}
.gs-empty-mascot { margin-bottom: var(--gs-space-4); }
.gs-empty-title { margin-bottom: var(--gs-space-2); }
.gs-empty-body { color: var(--text-muted); max-width: 420px; }
.gs-loading {
  display: inline-flex; align-items: center; gap: var(--gs-space-2);
  color: var(--text-muted);
}
.gs-spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--gs-insufficient);
  animation: gs-spin 0.9s linear infinite;
}
@keyframes gs-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .gs-spinner { animation: none; }
}

/* ── Graphics (7.L3): the one licence a colour has to carry a state on its
   own, and the technique the policy leaves for it. Geometry lives in
   element attributes because `style=` has nowhere legal here (7.R2's CSP
   and the 7.C3 sweep); the fill is a class because a status hue may never
   be hardcoded into Python. One rule per state, every one reading a token,
   so tokens.css keeps sole authority. (Nothing to do with `.gs-fill`
   further down, which is a blank a counterparty writes in.) ── */
.gs-fill-healthy { fill: var(--gs-healthy); }
.gs-fill-at-risk { fill: var(--gs-at-risk); }
.gs-fill-underused { fill: var(--gs-underused); }
/* Orphaned shares the lifecycle hue and keeps a rule of its own: the class
   is the word the reading beside it has to say, and an estate with orphans
   in it may not be made to say "underused". */
.gs-fill-orphaned { fill: var(--gs-underused); }
.gs-fill-insufficient-data { fill: var(--gs-insufficient); }
/* Observed outcomes — evidence, not a status, which is the one job the
   constitution gives violation red. A slot we hold nothing for is neither,
   and wears the calm slate every other absence in this product wears. */
.gs-fill-succeeded { fill: var(--gs-healthy); }
.gs-fill-failed { fill: var(--gs-violation); }
.gs-fill-unobserved { fill: var(--text-muted); }
/* Evidence stages: landed, still arriving, not begun. */
.gs-fill-landed { fill: var(--gs-healthy); }
.gs-fill-arriving { fill: var(--gs-insufficient); }
.gs-fill-waiting { fill: var(--text-muted); }

/* The proportional bar. `preserveAspectRatio="none"` on the element is
   load-bearing: a 1000x10 viewBox in a 600px box otherwise scales to fit
   and letterboxes to 6px of bar in a 10px slot. No rounded ends either —
   a radius eats exactly the 2-unit segment the minimum exists to keep. */
.gs-bar { display: block; }
.gs-composition { margin-bottom: var(--gs-space-4); }
/* A bar reading the table above it takes the air a heading would have
   given it, and gives back what it does not need at the end of a card. */
.gs-scroll + .gs-composition { margin-top: var(--gs-space-4); }
.gs-composition:last-child { margin-bottom: 0; }
.gs-legend {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gs-space-1) var(--gs-space-3); margin: var(--gs-space-2) 0 0;
}
.gs-legend-item { display: inline-flex; align-items: center; gap: var(--gs-space-1); }
.gs-legend-total { color: var(--text-muted); }

/* A tick keeps its size whatever the container, so two reports are read
   against each other rather than against their own widths; a narrow screen
   scales the whole strip instead (7.R3). */
.gs-strip { margin-bottom: var(--gs-space-3); }
.gs-ticks { display: block; max-width: 100%; height: auto; }
.gs-strip-reading {
  margin: var(--gs-space-1) 0 0;
  color: var(--text-muted); font-size: var(--gs-text-xs);
}

/* An evidence stage extends the check row (7.F1): the mark box takes a
   drawn mark instead of a glyph, and the state is a word on the row. */
.gs-stage-mark { display: flex; width: 14px; height: 14px; align-self: center; }
.gs-stage-state {
  font-size: var(--gs-text-xs); text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--text-muted);
}

/* A quantity inside a sentence: Inter with steady digits, never mono —
   mono is identifiers, versions and stamps. The unit is tied to the number
   by a non-breaking space in the markup, not by a rule here. */
.gs-evidence { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   7.H1 — the public site. Everything below runs on `.gs-brand` (Graphite
   & Bone): the warm masthead temperature, marketing surfaces only. The
   shell above is reused wholesale — header, main, nav, cards, tables and
   the state pills are the product's own — so the site cannot drift from
   the screens it is describing. Only the rhythm of a page nobody is
   working *in* is new: bigger type, air between sections, one idea per
   band.
   ========================================================================== */

.gs-nav--site { gap: var(--gs-space-5); }
.gs-site-action { margin-left: auto; }
.gs-site-footer {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gs-space-3) var(--gs-space-5);
  border-top: 1px solid var(--border);
  margin-top: var(--gs-space-7);
  padding-top: var(--gs-space-5);
}
.gs-site-footer p { margin: 0; }

/* ── Hero and section rhythm ── */
.gs-hero { padding: var(--gs-space-8) 0 var(--gs-space-7); max-width: 46rem; }
.gs-hero h1 {
  font-size: var(--gs-text-3xl); letter-spacing: -0.02em;
  margin-bottom: var(--gs-space-4);
}

/* The landing hero (7.L4): the one band on this site that is not read at
   prose width, because a claim with a board beside it is two things being
   compared rather than one being read. The width is the band's own, not a
   third measure token — 7.L1 defined the two that describe *layouts*
   (960px reading, 1240px deciding) and this is neither: it is one section
   of one page, and a token nothing else may use is a token that lies about
   its scope.
   It reaches that width by bleeding symmetrically out of `.gs-main`'s
   column, and only above a viewport that can hold it with a gutter left
   over — no `100vw`, which counts the scrollbar and would hand a
   vertically-scrolling page a horizontal one (7.R3: nothing may be wider
   than the screen it is read on). Below the breakpoint it is one column at
   the page's own measure, which is what every other hero already is. */
.gs-hero--split { max-width: 70rem; }
@media (min-width: 1180px) {
  .gs-hero--split {
    display: grid; align-items: center; gap: var(--gs-space-7);
    /* No `minmax()`: an `auto-fit` floor is the thing 7.R3 polices, and
       these two tracks are named rather than fitted. `min-width: 0` is
       what lets the proof column shrink to the track — a grid item's
       automatic minimum is its content, and a quoted board's content is
       a table. */
    grid-template-columns: 5fr 6fr;
    margin-left: calc((100% - 70rem) / 2);
    margin-right: calc((100% - 70rem) / 2);
  }
  .gs-hero--split > * { min-width: 0; }
  .gs-hero-proof > .gs-panel { margin-top: 0; }
}
.gs-lede { font-size: var(--gs-text-md); max-width: 42rem; }
.gs-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gs-space-3); margin: var(--gs-space-5) 0 var(--gs-space-3);
}
.gs-section {
  border-top: 1px solid var(--border);
  padding-top: var(--gs-space-6); margin-top: var(--gs-space-7);
}
/* The section's *own* heading, not one inside a product screen it quotes
   — a card's heading belongs to the card's design, not to this page's
   measure. */
.gs-section > h2 { font-size: var(--gs-text-xl); max-width: 34rem; }
.gs-kicker {
  display: block; margin-bottom: var(--gs-space-2);
  color: var(--text-muted); font-size: var(--gs-text-xs);
  font-weight: var(--gs-weight-medium);
  letter-spacing: 0.08em; text-transform: uppercase;
}
/* `min(…, 100%)` is what makes an `auto-fit` grid honest (7.R3): a bare
   `minmax(22rem, 1fr)` is a floor the track keeps even on a screen
   narrower than 22rem, so the single column it collapses to overhangs
   the page. With the `min()` the floor gives way at the last moment and
   one column is one column, whatever it is being read on. */
.gs-cols {
  display: grid; gap: var(--gs-space-5); margin-top: var(--gs-space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}
/* Four items, not three: the wider track resolves to two columns at the
   page's measure, so a set of four reads as a square rather than as
   three and a straggler. */
.gs-cols--pairs {
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}
.gs-cols h3 { margin-bottom: var(--gs-space-2); }
.gs-cols p:last-child { margin-bottom: 0; }

/* Sentences a reader recognises as their own. Set as speech, not as a
   testimonial: nobody is credited, because nobody said them to us yet
   (research_log §6 is the day this becomes a real quote block). */
.gs-said {
  list-style: none; margin: var(--gs-space-5) 0 0; padding: 0;
  display: grid; gap: var(--gs-space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}
.gs-said li {
  border-left: 2px solid var(--accent);
  padding: var(--gs-space-1) 0 var(--gs-space-1) var(--gs-space-4);
  font-size: var(--gs-text-md);
}

/* ── A product screen quoted on a marketing page ──
   The one place the two temperatures meet. Bone is forbidden behind a
   data grid (guidelines §3c), so the panel carries `gs-product` and the
   grid inside it renders in exactly the neutral it has in the app — the
   verdict a visitor sees here is pixel-for-pixel the verdict they will
   see signed in, which is the §3d promise. Its caption sits outside,
   in the page's own temperature. */
.gs-panel {
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--gs-radius-lg);
  padding: var(--gs-space-5); margin-top: var(--gs-space-5);
  overflow-x: auto;
}
.gs-panel > :last-child { margin-bottom: 0; }
/* A quoted screen standing in a column of its own is the column's first
   thing, not something that follows a paragraph (7.L4's per-step
   fragments). */
.gs-cols > .gs-panel { margin-top: 0; }
.gs-panel-caption {
  margin-top: var(--gs-space-2);
  color: var(--text-muted); font-size: var(--gs-text-xs);
}

/* ── The trust band (7.L4) ──
   The public site's one dark moment, and the only place `.gs-brand-dark`
   is used. It is a *theme scope*, so the band redefines --bg, --surface,
   --border, --text and --accent inside itself and every component in
   there composes as usual — which is also the constraint on what may go
   in: the five status hues live in `:root` and are tuned for a light
   ground, so a pill or a proportional mark inside this band would render
   its light core on basalt (`docs/design-system.md`, "Dark grounds are
   not free"). Words and links only. */
.gs-trust {
  background: var(--bg); color: var(--text);
  border-radius: var(--gs-radius-lg);
  padding: var(--gs-space-7) var(--gs-space-6);
  margin-top: var(--gs-space-7);
}
.gs-trust > h2 { font-size: var(--gs-text-xl); max-width: 34rem; }
/* The line that hands the reader the documents closes the band, so it
   needs the air a section's own rhythm would have given it. */
.gs-trust .gs-cols + p { margin-top: var(--gs-space-5); }
.gs-trust > :last-child { margin-bottom: 0; }

/* ── The closing card ── */
.gs-cta {
  border: 1px solid var(--border); border-radius: var(--gs-radius-lg);
  background: var(--surface);
  padding: var(--gs-space-6); margin-top: var(--gs-space-7);
}
.gs-cta h2 { margin-bottom: var(--gs-space-2); }

/* ==========================================================================
   THE TRUST DOCUMENTS (7.H2)
   A document, not a screen: one measure, numbered clauses, and no funnel.
   The reader's copy is made by their own browser (print rules below), which
   is why there is no generator here and no PDF to fall a version behind.
   ========================================================================== */

.gs-footer-legal { gap: var(--gs-space-4); margin-left: auto; }

.gs-doc { max-width: 44rem; padding-bottom: var(--gs-space-7); }
.gs-doc-head { padding: var(--gs-space-7) 0 var(--gs-space-5); }
/* The letterhead. On screen it is redundant with the masthead; on paper it
   is the only thing that says whose document this is. */
.gs-doc-mark {
  font-size: var(--gs-text-xs); font-weight: var(--gs-weight-medium);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: var(--gs-space-2);
}
.gs-doc-head h1 { font-size: var(--gs-text-2xl); letter-spacing: -0.02em; }
.gs-clause {
  border-top: 1px solid var(--border);
  padding-top: var(--gs-space-4); margin-top: var(--gs-space-5);
}
.gs-clause > h2 { font-size: var(--gs-text-lg); max-width: 34rem; }
.gs-clause h3 {
  font-size: var(--gs-text-md); margin-top: var(--gs-space-5);
}
.gs-doc-table { margin: var(--gs-space-4) 0; font-size: var(--gs-text-sm); }
.gs-doc-table td > .gs-facts li { padding: 0 0 var(--gs-space-1); }
/* The personal-data table's column proportions (`product/trust.py`). Here
   rather than on the element, because the documents are served under
   `style-src 'self'` (7.R2) and a `style` attribute is inline style. */
.gs-doc-table--personal-data th:nth-child(1) { width: 18%; }
.gs-doc-table--personal-data th:nth-child(2) { width: 32%; }
.gs-doc-table--personal-data th:nth-child(3) { width: 16%; }
.gs-doc-table--personal-data th:nth-child(4) { width: 34%; }

/* A fact the world has not supplied yet. It reads as a gap on purpose: a
   plausible sentence in a legal document is worse than a blank, because a
   blank gets filled and a sentence gets signed. */
.gs-blank {
  display: inline-block;
  border-bottom: 1px dashed var(--accent);
  color: var(--text-muted); font-style: italic;
}
/* A field the reader fills in, which is not the same gap: the accent
   marks what we still owe them, this marks what the document is waiting
   for them to write. */
.gs-fill {
  display: inline-block;
  border-bottom: 1px dashed var(--border);
  color: var(--text-muted); font-style: italic;
}
.gs-rule {
  display: inline-block; min-width: 12rem;
  border-bottom: 1px solid var(--text-muted);
}
.gs-notice {
  border: 1px solid var(--accent); border-radius: var(--gs-radius-lg);
  padding: var(--gs-space-5); margin-top: var(--gs-space-7);
}
.gs-notice h2 { font-size: var(--gs-text-lg); margin-bottom: var(--gs-space-2); }
.gs-notice > :last-child { margin-bottom: 0; }

/* ── Print: the champion's attachment ──
   The reader forwards a PDF, and this is where it is made. Chrome that
   only works on screen goes; the ink is black on white whatever the
   theme; and a table or a clause is never split across a page break,
   because a signature block landing alone on page three is how a document
   looks unfinished. */
@media print {
  @page { size: A4; margin: 16mm; }
  /* Paper is a fixed budget, so the whole scale tightens: the brief has
     to be two sides, and it gets there by setting closer rather than by
     leaving anything out. */
  html, body {
    background: #fff; color: #000; font-size: 10pt; line-height: 1.4;
  }
  .gs-header, .gs-site-footer, .gs-actions, .gs-cta, .gs-nav { display: none; }
  .gs-main { padding: 0; }
  .gs-doc { max-width: none; padding: 0; }
  /* On screen the lede is set to a shorter measure than the body; on
     paper the body has no measure of its own, so a narrow standfirst
     reads as a mistake rather than as emphasis. */
  .gs-doc .gs-lede { max-width: none; font-size: 1em; }
  .gs-doc p { margin-bottom: var(--gs-space-3); }
  .gs-doc-head { padding: 0 0 var(--gs-space-3); }
  .gs-doc-head h1 { font-size: 17pt; margin-bottom: var(--gs-space-2); }
  .gs-clause {
    padding-top: var(--gs-space-3); margin-top: var(--gs-space-3);
  }
  .gs-clause > h2 { font-size: 11.5pt; margin-bottom: var(--gs-space-2); }
  .gs-clause h3, .gs-notice h2 {
    font-size: 10.5pt; margin-top: var(--gs-space-3);
  }
  /* Paper cannot be scrolled, so the box that scrolls a table on a phone
     must not be the thing that clips it here (7.R3). The documents'
     tables are proportioned to the measure and fit; this is what makes
     that fit print rather than crop. */
  .gs-scroll { overflow-x: visible; }
  .gs-doc-table { font-size: 0.9em; margin: var(--gs-space-3) 0; }
  .gs-doc-table th, .gs-doc-table td {
    padding: var(--gs-space-1) var(--gs-space-2);
  }
  .gs-doc ul { margin-bottom: var(--gs-space-2); }
  .gs-facts li { padding: 0 0 var(--gs-space-1); }
  .gs-notice { padding: var(--gs-space-4); margin-top: var(--gs-space-4); }
  .gs-blank, .gs-fill { color: #000; }
  /* What may not be split, and what must be allowed to. A table row and
     the notice are small enough to keep whole; a clause is not — an
     unbreakable 40mm table pushed to the next page takes the slack it
     left with it, and a two-page brief becomes three. Long blocks flow;
     a heading never ends a page alone. */
  .gs-notice, .gs-doc-head, tr { break-inside: avoid; }
  .gs-clause > h2, .gs-clause h3, thead { break-after: avoid; }
  .gs-notice { border-color: #000; }
  a { color: inherit; text-decoration: underline; }
  /* An emailed PDF has no address bar and no hover: a link that says
     "the DPA" and cannot be followed is a dead end on paper. */
  .gs-doc a[href^="/"]::after { content: " (" attr(href) ")"; }
}
