/* ============================================================================
   5500RADAR — marketing site stylesheet
   ----------------------------------------------------------------------------
   This file exists as a separate stylesheet rather than an inline <style> block
   for one reason: the Content-Security-Policy in staticwebapp.config.json can
   then be `style-src 'self'` with no 'unsafe-inline' and no per-edit hashes.
   That is only true while NOTHING in the HTML carries a style="..." attribute.
   If you add one, you have quietly widened the CSP for the whole site.

   Cache-busting is manual (there is no build step): bump ?v=N on the <link> in
   all four page shells. /assets/* is served immutable for a year.

   Surfaces, ink, lines, radii and shadows are copied from app/static/css/app.css
   so the site and the product do not drift. The accent now matches the product
   sign-in action blue — see the block below.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   Instrument Serif -- the display face, shared with the home page.
   SIL OFL 1.1; licence ships at /assets/fonts/OFL.txt. Same files and same URLs
   home.css uses, so this costs no extra download.

   One weight only, which is the point: the face has no bold, so "never bold the
   serif" is a property of the binary. font-synthesis-weight:none stops the
   browser inventing one.
   --------------------------------------------------------------------------- */
@font-face{
  font-family:'Bodoni Moda';
  src:url('/assets/fonts/bodoni-moda-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Bodoni Moda';
  src:url('/assets/fonts/bodoni-moda-400i.woff2') format('woff2');
  font-weight:400; font-style:italic; font-display:swap;
}
@font-face{
  font-family:'Radar Serif Fallback';
  src:local('Palatino Linotype'), local('Palatino'), local('Georgia'),
      local('Times New Roman');
  size-adjust:74%; ascent-override:92%; descent-override:24%; line-gap-override:0%;
}

:root {
  color-scheme: light;

  /* ---- THE PALETTE IS DESIGN.md's, MEASURED -----------------------------
     Every value below is a named colour from DESIGN.md's front matter, which is
     the canon for this brand, or a step from app.css §1 whose endpoints are.
     home.css (the home page) already implemented all 17 canon colours exactly;
     this file implemented none of them, so /  and every interior page were two
     different products and a visitor crossed the seam on the first click.

     The header comments here used to claim "verbatim from app.css §1" for
     surfaces, ink, lines, radii and shadows. Not one of the five was: surfaces
     were a warm cream, ink was a neutral grey ramp, lines were neutral, radii
     were 6-14px against app's 1-2px. A comment that sends the next author to
     "restore parity" against values that never matched is worse than none, so
     the ratios below are recomputed and dated rather than inherited.

     Measured 2026-08-24, sRGB relative luminance, WCAG 2.1. Text floor 4.5,
     non-text UI edge floor 3.0 (1.4.11).                                    */

  /* ---- Surfaces: DESIGN.md porcelain-canvas / paper-white / mineral ---- */
  --canvas:        #F2F3EF;   /* porcelain-canvas */
  --canvas-raised: #FCFDFC;   /* paper-white   - the card that lifts off it */
  --canvas-sunken: #E5EAE7;   /* mineral-surface */

  /* ---- Ink: app.css §1's four steps. Its endpoints ARE canon -- ink-1 is
     DESIGN.md primary-ink, ink-4 is DESIGN.md muted-ink -- so the ramp is the
     canon pair with two measured steps between.

     ink-4 was #9CA1AE, which measured 2.46:1 on the old canvas: below AA, and
     the old comment excused it as "a hairline/chrome colour, not a text
     colour" while conceding the site used it for meaning. At #53666B it is
     4.96-5.92:1 on every surface here, so the excuse is no longer needed.

              paper  canvas  sunken  brand-50
     ink-1     15.31   14.00   12.82
     ink-2     10.06    9.21    8.43
     ink-3      7.24    6.63    6.07      6.95
     ink-4      5.92    5.41    4.96      5.68                              */
  --ink-1: #15262C;
  --ink-2: #2F444A;
  --ink-3: #45595E;
  --ink-4: #53666B;

  /* ---- Lines: DESIGN.md technical-rule, with app.css §1's two neighbours.
     These are DECORATIVE hairlines (1.21 / 1.54 / 2.29 on canvas) and carry no
     contrast obligation. WCAG 1.4.11's 3.0 floor is for the visible edge of a
     CONTROL, which is --field-line below -- do not reach for --line there. */
  --line-subtle: #D8E0DD;
  --line:        #BBC8C7;   /* technical-rule */
  --line-strong: #91A6A8;

  /* ---- Control edges. #7D9192 (home.css's value) measures 3.25 on paper but
     2.97 on canvas and 2.72 on mineral: it was chosen against paper white
     while the audit form sits on canvas, so it misses 1.4.11 by a hair on the
     surface it is actually used on. #6B7F80 holds 3.47-4.14 across all three. */
  --field-line: #6B7F80;

  /* ---- Accent: DESIGN.md signal-blue for action, instrument-teal for text.
     This is canon's own split, not an invention -- DESIGN.md assigns Signal
     Blue to "the primary Sign-in action" and Instrument Teal to identifying
     "source coordinates on light surfaces", i.e. the on-light-surface teal.

     It also happens to be the only split that measures. Signal blue as TEXT is
     4.60 on paper but 4.21 on canvas, 3.85 on mineral and 3.99 on brand-100 --
     it fails AA on three of the four surfaces this site puts text on. So
     --brand-500/--accent-solid are FILLS, BORDERS AND RULES ONLY. Never set
     `color:` to them; use --accent-ink, which is 4.83-5.77 everywhere.

     A white label on a signal-blue fill is 4.60, which passes.              */
  --brand-500:    #0E7E9B;   /* signal-blue    - fills/borders, never text   */
  --accent-solid: #0E7E9B;   /* [4.60 w/ paper label] filled button          */
  --accent-ink:   #0B6E83;   /* instrument-teal [4.83-5.77] accent TEXT      */
  --accent-active:#0B6E83;   /* hover and pressed                            */
  --brand-glow:   rgba(14,126,155,.60);
  --brand-ring:   rgba(14,126,155,.18); /* focus ring                        */

  /* ---- Dark-surface pair, both canon.
     --brand-bright was #42A9BE, a fourth teal in a palette that already had
     three. DESIGN.md luminous-ice is the reserved colour for beams, focus and
     registration lights, which is what this token marks: a status dot and a
     highlight. 9.68 on graphite, 11.48 on the navy /who-its-for/ uses.
     Paragraph text on a dark section is not a light -- that is
     --on-graphite-muted, canon's token for exactly this (9.23 / 10.95).    */
  --brand-bright:      #70D7ED;   /* luminous-ice      - dots, beams, edges  */
  --on-graphite-muted: #B8C7C8;   /* on-graphite-muted - text on dark        */

  /* ---- Signal-blue tints. DESIGN.md names no blue tint, so these are a
     documented extension rather than canon. --brand-50 already equalled
     app.css --accent-50; the other two are deliberately LIGHTER than app's
     (#DBF2FA / #32B5D8) because here they are the border and wash of an info
     chip, and app's 200 is a mid-saturation cyan that would shout at that
     size. ink-3 and accent-ink both clear AA on both tints.               */
  --brand-50:     #EFFAFD;
  --brand-100:    #D9F1F5;
  --brand-200:    #B7DFE8;

  /* Canvas colour as channels, so a scrim can fade it to transparent. Must
     track --canvas or the fade lands on the wrong colour. */
  --canvas-rgb: 242 243 239;

  /* ---- Radii ----
     sm..2xl are verbatim from app.css §1. --radius-3xl is NOT: app.css stops at
     2xl, and 24px was added here for the hero stage's bezel. Flagged because the
     block used to claim the whole set was copied, which would send anyone
     "restoring parity" to delete a token the hero depends on. */
  /* DESIGN.md's radii: micro 1px, instrument 2px, control 2px, signal 999px.
     "Default corners are square or two pixels." These were 6/8/10/10/12/14px,
     which is the one part of the old palette that read as generic rounded-card
     UI, and it disagreed with home.css (square) on every page. The 3px top step
     is app.css §1's --radius-2xl. --radius-3xl was 14px for the dashboard
     bezel; canon has no 14px step, so the bezel takes the top one. Pills keep
     999px and circles keep 50% -- canon reserves round for the radar dot,
     status indicators and small registration targets. */
  --radius-sm: 1px;  --radius-md: 2px;  --radius-lg: 2px;
  --radius-xl: 2px;  --radius-2xl: 3px;
  --radius-3xl: 3px;

  /* ---- Shadows (verbatim from app.css §1; rgba base matches --ink-1) ---- */
  --shadow-xs: 0 1px 1px rgba(11,13,20,.04), 0 0 0 1px rgba(11,13,20,.02);
  --shadow-sm: 0 1px 2px rgba(11,13,20,.05), 0 2px 4px rgba(11,13,20,.04);
  --shadow-md: 0 4px 6px -1px rgba(11,13,20,.08), 0 2px 4px -2px rgba(11,13,20,.05), 0 0 0 1px rgba(11,13,20,.02);
  --shadow-lg: 0 12px 24px -8px rgba(11,13,20,.14), 0 6px 12px -4px rgba(11,13,20,.06);
  --shadow-xl: 0 24px 48px -12px rgba(11,13,20,.20), 0 8px 16px -6px rgba(11,13,20,.08);

  /* ---- Type ---- */
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-serif: 'Bodoni Moda', 'Radar Serif Fallback',
                "Palatino Linotype", Georgia, serif;
  /* app.css's mono stack minus 'JetBrains Mono' — that is a Google Font in the
     app's base.html, and this site loads no external fonts. */
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
               "Liberation Mono", monospace;

  /* The display sizes were timid: 60px max where the reference opens at 80. Going
     big is not free, though — a 57-character headline at 76px needs the full
     1056px to wrap to three lines instead of seven, which is why .hero-copy now
     gives the h1 its own full-width row. Size and measure had to move together.
     The em-based tracking below scales with these automatically. */
  /* 72px is not a taste pick, it is the measured ceiling. This headline sets in
     two lines up to 72px and breaks to three at 76px, and that third line pushes
     the dashboard's KPI row from y=846 to y=933 — past the fold on a 900px
     viewport, which defeats the point of the hero. The clamp MAX matters as much
     as the value: above a 1440px viewport --w-page caps the wrap at 1056px while
     5vw keeps growing, so without the ceiling a wide monitor would wrap it to
     three lines again. Re-measure this if the headline text changes. */
  --fs-display: clamp(34px, 4.7vw, 66px);
  --fs-h1:      clamp(33px, 4.5vw, 60px);
  /* 42px, not 50. .split puts h2 in a 364px column beside its body copy, and at
     50px headings like "Reading the attachments, and looking forward." ran to
     four lines of nine characters and crowded the content under them. The column
     is the binding constraint, not the measure cap, so the size is what gives.
     42 is still a real step up from the old 38, and a clear step down from the
     hero's 72 — which is the hierarchy this pass wanted. */
  --fs-h2:      clamp(26px, 3.3vw, 44px);
  --fs-h3:      clamp(20px, 1.6vw, 23px);
  /* Floor raised from 17 to 19 when body went to 17. .lede is --ink-3 and body
     is --ink-2, so at equal size the lede would render LIGHTER than the copy it
     introduces and read as less important rather than more. It needs a size
     lead at every width, not just at desktop. */
  --fs-lede:    clamp(19px, 2vw, 21px);
  /* 17px, not 16.5. The half-pixel was arbitrary; 17 is the iOS reading size and
     what the reference uses for body. See --fs-lede below: raising this one
     forced that one up too. */
  --fs-body:    17px;
  --fs-sm:      14px;
  --fs-micro:   12.5px;

  /* ---- Space ---- */
  /* Was clamp(72px, 9vw, 128px). With the display type this much larger the old
     gaps read as crowding — big type needs proportionally more air around it or
     the page looks shouted rather than composed. */
  --space-section: clamp(84px, 11vw, 168px);
  --w-page:  1120px;
  --w-prose: 68ch;
  --gutter:  clamp(20px, 5vw, 32px);

  /* ---- Motion (interaction tokens verbatim from app.css §1) ---- */
  --ease-out:      cubic-bezier(.16, 1, .3, 1);
  --ease-in-out:   cubic-bezier(.65, 0, .35, 1);
  --dur-fast: 120ms; --dur-base: 160ms; --dur-slow: 280ms;
  /* marketing-scale additions */
  --ease-emphasis: cubic-bezier(.22, 1, .36, 1);
  --ease-blip:     cubic-bezier(.66, 0, .2, 1);  /* from brand_5500radar_preview */
  --dur-reveal:  620ms;
  --dur-stagger:  60ms;
  --dur-ambient: 4500ms;
}

/* Windows High Contrast / forced-colors: let the OS win, but keep structure. */
@media (prefers-contrast: more) {
  :root { --ink-3: var(--ink-2); --line: var(--line-strong); }
}

/* ============================================================================
   Reset + base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;   /* sticky header height + breathing room */
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink-2);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.62;
  font-variant-numeric: proportional-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { color: var(--ink-1); margin: 0; text-wrap: balance; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; }
img, svg { max-width: 100%; }
strong, b { color: var(--ink-1); font-weight: 650; }

a { color: var(--accent-ink); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-active); }

:focus-visible {
  outline: 2px solid transparent;      /* forced-colors draws this one */
  outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--brand-ring), 0 0 0 1px var(--brand-500);
  border-radius: var(--radius-sm);
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--canvas-raised); color: var(--ink-1);
  padding: 12px 20px; border: 1px solid var(--line-strong);
  border-radius: var(--radius-md); font-weight: 600;
}
.skip:focus { left: 12px; top: 12px; }

.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ============================================================================
   Layout primitives
   ========================================================================== */

.wrap { max-width: var(--w-page); margin-inline: auto; padding-inline: var(--gutter); }
.prose { max-width: var(--w-prose); }

section { padding-block: var(--space-section); }

/* Alternate bands. The blue wash that used to be here was written to tie these
   sections to a blue hero, and once the canvas went warm it read as a cold
   panel dropped into warm paper -- the two halves of the site stopped looking
   like one product. Now the same flat warm band the home page uses, and the
   blue is spent on instrument surfaces only. */
.band {
  position: relative;
  background: #F5F1EA;
  border-block: 1px solid var(--line);
}

.sec-label {
  font-size: var(--fs-micro);
  font-weight: 650;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: 14px;
}

h1 { font-family: var(--font-serif); font-weight: 400; font-synthesis-weight: none;
     font-size: var(--fs-h1);      line-height: 1.06; letter-spacing: -.010em; }
h2 { font-family: var(--font-serif); font-weight: 400; font-synthesis-weight: none;
     font-size: var(--fs-h2);      line-height: 1.10; letter-spacing: -.009em; max-width: 24ch; }
h3 { font-size: var(--fs-h3);      line-height: 1.30; letter-spacing: -.01em; }
.display { font-family: var(--font-serif); font-weight: 400; font-synthesis-weight: none;
           font-size: var(--fs-display); line-height: 1.04; letter-spacing: -.012em; }

.lede    { font-size: var(--fs-lede); line-height: 1.55; color: var(--ink-3); max-width: 60ch; }
.sec-lede{ font-size: 17px; color: var(--ink-3); max-width: 64ch; }
.small   { font-size: var(--fs-sm); color: var(--ink-3); }
.fine {
  font-size: 13px; line-height: 1.6; color: var(--ink-3);
  max-width: 78ch;
}

/* ============================================================================
   Brand lockup — radar rings + wordmark + blip
   Ported from app/static/brand_5500radar_preview.html, light-first.
   ========================================================================== */

.mark { display: inline-flex; align-items: center; gap: 11px; text-decoration: none; }
.mark:focus-visible { border-radius: var(--radius-md); }
@keyframes ping {
  0%   { box-shadow: 0 0 0 0 var(--brand-glow); }
  70%  { box-shadow: 0 0 0 9px rgba(14,126,155,0); }
  100% { box-shadow: 0 0 0 0 rgba(14,126,155,0); }
}

/* Wordmark — Michroma outlines, inlined once per page as <symbol id="wordmark">
   and referenced by <use> in the header and footer.

   Do NOT add `.wm .wm-num { fill: … }` rules here: <use> builds a shadow tree
   that document descendant selectors cannot reach, so they would silently do
   nothing. The symbol sets fill="var(--ink-1)" / var(--brand-500) directly, and
   custom properties DO inherit across the shadow boundary — which is what makes
   the mark retint itself in dark mode.

   aspect-ratio matches the generated viewBox (1778 x 160). If the wordmark is
   ever regenerated at different bounds, update it here too. */
.wm { display: block; height: 15px; width: auto; aspect-ratio: 1778 / 160; }

.wm-text {
  font-size: 17px; font-weight: 700; letter-spacing: .015em;
  color: var(--ink-1); line-height: 1; white-space: nowrap;
}
.wm-text b { color: var(--brand-500); font-weight: 700; }

/* ---- The brand lockup, top-left -------------------------------------------
   The real mark is the wordmark PLUS the persona strip, with a blip that
   travels between CPA FIRMS / ADVISORS / RECORDKEEPERS. It lives in the nav,
   left-aligned. There is no radar-circle icon — the blip IS the radar. */
.nav-brand {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  justify-content: center;
  /* The wordmark alone is a 14px-tall target. The hamburger beside it is 46px
     and the home page's equivalent is 44px. */
  min-height: 44px;
  gap: 7px; text-decoration: none;
  border-radius: var(--radius-md);
}
.nav-brand .wm { height: 19px; }
.nav-brand .personas {
  font-size: 9.5px; letter-spacing: .19em; gap: 9px;
  padding-bottom: 11px;
}
.nav-brand .personas .sep { font-size: .58em; }
.nav-brand .pblip { width: 5px; height: 5px; bottom: 2px; }

/* Footer keeps a quiet wordmark only — one lockup per page is the point. */
.foot .wm { height: 15px; }

/* The persona strip is ~300px wide — wider than the wordmark itself — so on a
   narrow screen it pushes the CTA pill off the edge. Wordmark only below 760px. */
@media (max-width: 760px) {
  .nav-brand .personas { display: none; }
  .nav-brand .wm { height: 17px; }
}

/* Radar range rings behind the hero lockup */

/* Persona strip with the sliding blip */
.personas {
  position: relative;
  display: inline-flex; align-items: center;
  gap: clamp(12px, 2.2vw, 26px);
  font-size: clamp(10px, 1.4vw, 13px);
  font-weight: 500;
  letter-spacing: .26em; text-transform: uppercase;
  padding-bottom: 26px;
}
.personas .sep { color: var(--brand-500); opacity: .45; font-size: .62em; }
.persona {
  color: var(--ink-3); white-space: nowrap;
  transition: color .5s var(--ease-out);
}
.persona.active { color: var(--ink-1); }
/* Moved with `transform` rather than `left` so it stays compositor-only.
   `translate` handles the centring independently, so the two compose. */
.personas .pblip {
  position: absolute; bottom: 8px; left: 0;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--brand-500);
  translate: -50% 0;
  box-shadow: 0 0 10px 2px var(--brand-glow);
  transition: transform .8s var(--ease-blip);
}

/* ============================================================================
   Header / nav
   ========================================================================== */

.site-header {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--canvas) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out);
}

/* The tax a sticky header charges: anything an in-page link targets scrolls to
   y=0 and lands UNDERNEATH it — including the skip link, which is the one that
   matters most because a keyboard user lands on content they cannot see.

   That tax is already paid, once, by scroll-padding-top on html above. These
   targets also carried scroll-margin-top: 96px, and the two ADD: 88 + 96 put
   #sources 184px down the page under a 73px header, a 111px hole. The inset
   belongs in exactly one place — the scrollport — which is what the home page
   does and why its anchors land correctly. */
.site-header.is-scrolled {
  border-bottom-color: var(--line);
  background: color-mix(in srgb, var(--canvas) 94%, transparent);
}
.navbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; min-height: 62px;
}
.nav-links { display: flex; align-items: center; gap: 4px; }
.nav-links a:not(.btn) {
  color: var(--ink-2); text-decoration: none;
  font-size: 14px; font-weight: 400;
  padding: 10px 12px; border-radius: 2px;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.nav-links a:not(.btn):hover { color: var(--ink-1); background: var(--canvas-sunken); }
/* Active nav item. secondary.css restates this for .secondary-header and
   loads after, so THAT is the one that renders on the 18 interior pages --
   these two used to set different colours and only the later one was real.
   Both now carry an underline as well: colour alone is a weak affordance,
   and the attribute selector covers "true" too, which is what an article
   sets on its section link (it is in the set, it is not that URL). */
.nav-links a[aria-current] {
  color: var(--ink-1);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
}
.nav-links .btn { margin-left: 8px; }

@media (max-width: 720px) {
  .nav-links a:not(.btn) { display: none; }
}

/* ============================================================================
   Buttons
   ========================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px;                       /* touch target floor */
  padding: 11px 18px;
  border: 1px solid transparent;
  border-radius: 2px;
  background: var(--ink-1); color: #fff;
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  text-decoration: none; white-space: nowrap; cursor: pointer;
  /* colour and border-color are in here so the nav CTA's fill-in on scroll
     animates rather than snapping. Compositor-only would be nicer, but there is
     no way to cross-fade a background without a second layer, and this is two
     properties on one small element. */
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover  { background: var(--accent-active); color: #fff; }
.btn:active { transform: translateY(1px); }

.btn-ghost {
  background: var(--canvas-raised); color: var(--ink-1);
  border-color: var(--line);
  box-shadow: var(--shadow-xs);
}
/* Without this, .btn-ghost's resting box-shadow overwrites the global
   :focus-visible ring and the secondary CTA on every page focuses invisibly. */
.btn-ghost:focus-visible {
  box-shadow: 0 0 0 3px var(--brand-ring), 0 0 0 1px var(--brand-500), var(--shadow-xs);
}
.btn-ghost:hover {
  background: var(--canvas-raised); color: var(--ink-1);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-sm);
}

/* Near-black pill. Ink-1 on white is 18.9:1. */
.btn-dark { background: var(--ink-1); color: var(--canvas-raised); }
.btn-dark:hover { background: #1B2030; color: var(--canvas-raised); }

/* The nav CTA and the hero CTA were two filled buttons reading "Request access"
   about 400px apart in the opening viewport — the same words, twice, competing.
   At the top the hero button is the action, so the nav one steps back to an
   outline; once the hero has scrolled away it fills in and becomes the
   persistent CTA. This reuses the .is-scrolled state site.js already toggles
   from the top sentinel, so it costs no new JS.

   Scoped to .site-header on purpose: 404.html uses .btn-dark for its "Home"
   button, which is that page's primary action and must stay filled. */
.site-header .btn-dark {
  background: transparent; color: var(--ink-1);
  border-color: var(--line-strong);
}
.site-header .btn-dark:hover {
  background: var(--canvas-sunken); color: var(--ink-1); border-color: var(--ink-3);
}
/* .cta-away, not .is-scrolled. See the note in site.js: .is-scrolled flips after
   one pixel of scroll, so hanging the fill off it made the one-filled-button
   rule true only at scrollY 0. .cta-away tracks the hero's action row leaving
   the viewport, which is the thing that actually matters.

   No-JS: the class never arrives on the home page, so the nav button stays an
   outline — still bordered and 18.9:1, still obviously a button. On interior
   pages site.js adds it immediately, because there is no hero CTA to defer to. */
.site-header.cta-away .btn-dark {
  background: var(--ink-1); color: var(--canvas-raised); border-color: var(--ink-1);
}
.site-header.cta-away .btn-dark:hover { background: #1B2030; color: var(--canvas-raised); }
.btn-lg { min-height: 50px; padding: 14px 24px; font-size: 15.5px; }

.actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

/* .eyebrow and .eyebrow .pip lived here and no markup on any of the five pages
   used them — only .eyebrow-mono (index.html:107) is real. Deleted rather than
   left as a component someone might reach for by mistake. */

/* ============================================================================
   Hero — the scanned Form 5500 that becomes the app
   ----------------------------------------------------------------------------
   Two states share one stage card. State A is a Form 5500 with a scan sweeping
   down it, values populating as the scan passes, and data traces feeding in
   from the edges. State B is the 5500RADAR dashboard. Scroll drives A -> B via
   a single custom property `--p` (0..1) set by site.js on the hero element;
   every rule below reads it through calc(), so there is one source of truth and
   no per-frame layout.

   Built from CSS and inline SVG only — no image file. The LCP element stays the
   H1 text, so there is nothing to decode on the critical path.
   ========================================================================== */

.hero {
  --p: 0;                                   /* scroll progress, set by site.js */
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* The card sits BESIDE the copy now, so it is visible without scrolling and
     the padding no longer has to be starved to keep it above the fold. */
  min-height: min(86vh, 880px);
  display: grid; align-items: center;
  padding-block: clamp(20px, 2.4vw, 34px) clamp(32px, 4vw, 60px);
  text-align: left;
  background:
    radial-gradient(120% 90% at 50% -10%, #FFFFFF 0%, rgba(255,255,255,0) 55%),
    radial-gradient(90% 70% at 82% 8%,  rgba(66,169,190,.16), transparent 60%),
    radial-gradient(80% 60% at 12% 4%,  rgba(14,126,155,.10), transparent 62%),
    linear-gradient(180deg, #E8F0FF 0%, #F2F6FE 44%, var(--canvas) 88%);
}

/* Soft cloud-like banding, drawn rather than photographed. */
.hero::before {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(60% 34% at 22% 26%, rgba(255,255,255,.85), transparent 70%),
    radial-gradient(48% 26% at 74% 18%, rgba(255,255,255,.75), transparent 72%),
    radial-gradient(70% 30% at 52% 40%, rgba(255,255,255,.55), transparent 74%);
}

/* Copy above, stage below, both full width. The split hero kept the card above
   the fold but capped it at ~667px, and everything added afterwards — the source
   rail, the tilt, five finding cards — had to fit inside that cap. Finding titles
   ended up at 6px. The scan is the product's whole argument, so it gets the width
   and the copy gets the row above it. */
.hero .wrap {
  position: relative; z-index: 2;
  /* Tight enough that the KPI row clears the fold at 1440x900 with the headline
     still fully above it. The stage runs ~250px past the fold by design: a
     dashboard cropped by the viewport edge invites the scroll, whereas one
     shrunk to fit entirely would be too letterboxed to read as a real screen. */
  display: grid; gap: clamp(24px, 3vw, 42px);
}

/* The headline gets its own full-width row, and the lede and CTAs sit beneath it
   in a narrower column. It used to share a 7fr/5fr row with them, which capped it
   at ~574px — fine at 50px, impossible at 76px, where 574px wraps a 57-character
   headline to seven characters a line. The whole band is still asymmetric (the
   lede column stops well short of the right edge); the asymmetry just moved down
   a row so the headline could have the width its size needs. */
.hero-copy { display: grid; gap: clamp(18px, 2.2vw, 26px); }
@media (min-width: 900px) {
  .hero-copy { gap: clamp(24px, 2.8vw, 34px); }
  .hero-lead { grid-column: 1 / -1; }
  .hero-aside {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: clamp(28px, 4vw, 56px);
    align-items: start;
  }
  .hero-aside .actions { margin-top: 4px; justify-content: flex-start; }
}
/* No measure cap. At 20ch the headline was held to 861px and broke to three
   lines, which pushed the dashboard's KPI row from y=737 to y=913 — below the
   fold on a 900px viewport, undoing the one thing the hero exists to do. Across
   the full 1056px it sets in two lines and the figures stay visible on arrival.
   Display type this size is measured by line count, not by ch. */
.hero-copy h1 { margin: 12px 0 0; font-size: var(--fs-display); }
.hero-copy .lede { max-width: 46ch; }
.hero-copy .actions { margin-top: 24px; justify-content: flex-start; }
.hero-note { font-size: 13.5px; color: var(--ink-3); margin-top: 18px; }

.eyebrow-mono {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-mono);
  font-size: 12.5px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent-ink);
}
.eyebrow-mono .pip {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand-500); flex: 0 0 6px;
  animation: ping var(--dur-ambient) ease-out infinite;
}

/* Trust chips. Content must stay factually true — no ratings, no customers.
   They span the full band, so all three sit on one line and read as a rule
   under the copy. The band's own gap does the spacing; a margin here would
   add to it. */
.hero-chips {
  display: flex; flex-wrap: wrap; justify-content: flex-start;
  gap: 10px 26px; margin-top: 0;
  font-size: 13px; color: var(--ink-3);
}
.hero-chips span { display: inline-flex; align-items: center; gap: 7px; }
.hero-chips svg {
  width: 14px; height: 14px; stroke: var(--brand-500); fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}

/* ---- The stage ---------------------------------------------------------- */

/* The container lives on the WRAPPER, not on .stage, so that .stage's own
   aspect-ratio can be switched by a container query. An element cannot be
   styled by a query against itself, and doing this with viewport @media instead
   is unreliable here: the gutter is clamp(20px, 5vw, 32px), so the container is
   0.9x the viewport across the phone range and no fixed viewport breakpoint
   lines up with the 520px container threshold the layout actually switches on.
   Same width either way, so every cqw below is unchanged. */
.stage-wrap { position: relative; z-index: 2; container-type: inline-size; }

/* One state, not two. The hero used to hold a Form 5500 being scanned that
   crossfaded into the dashboard on scroll, and the dashboard lost: by the time
   it was opaque the reader had scrolled past it, so the product itself was the
   one thing on the page nobody actually saw. The scan was an illustration of the
   product; this is the product. It is legible on arrival with no scroll. */
.stage {
  position: relative;
  margin-inline: auto;
  aspect-ratio: 8 / 5;                   /* 1056x660 at 1440; reserves the box: CLS 0 */
  /* Perspective lives HERE, not on .stage-wrap. The wrapper carries
     container-type: inline-size, which implies contain: layout style
     inline-size; keeping the two on separate elements avoids reasoning about
     how containment and a 3D rendering context interact. .stage has no
     containment, and it is already the containing block for .stage-app. */
  perspective: 1600px;

  /* The tilt angles are declared HERE, on the element site.js writes to, and
     .stage-app inherits them. They were originally on .stage-app itself, which
     silently broke the whole effect: a custom property declared on an element
     beats the value inherited from its parent, so .stage-app kept reading its
     own 0deg while .stage carried the real angle. The rule matched, the JS ran,
     the numbers were right, and nothing moved. */
  --rx: 0deg;
  --ry: 0deg;
}

/* The device bezel. The dashboard IS a screen, so it reads as one — this is the
   difference between a mockup and a diagram. */
.stage-app {
  position: absolute; inset: 0;
  border-radius: var(--radius-3xl);
  background: linear-gradient(160deg, #2A3142, #171C28 60%, #10141E);
  box-shadow:
    0 2px 4px rgba(11,13,20,.06),
    0 30px 60px -20px rgba(11,13,20,.34),
    0 80px 140px -50px rgba(46,72,150,.42);

  /* NO pointer-events: none. It was here from when two layers were stacked and
     the invisible one would have intercepted clicks meant for the other; there
     is one layer now, so it was only making the hero inert. The effect was that
     every figure in the dashboard — 629,747, the table rows, the headings — was
     unreachable by the pointer: no text cursor, no selection, no copying. A
     product screenshot that refuses the cursor reads as a broken image rather
     than as a screen.

     The tilt is unaffected: pointermove bubbles from these children up to
     .stage, and pointerleave does not fire when moving onto a descendant. */

  /* The tilt. site.js writes --rx/--ry onto .stage from pointer position; they
     inherit down to here. Transform only, so this composites — the browser does
     not re-lay-out or repaint the dashboard to turn it, it turns a texture it
     already has.

     Ceilings are deliberately low. 4 degrees of yaw and 3 of pitch on an object
     this size is enough to read as a physical screen catching the light; at 10
     it becomes a toy, and this page is asking accountants to trust its numbers.
     Pitch is smaller than yaw because vertical tilt on a wide landscape object
     reads as the thing falling over. */
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  /* Slow, eased return when the pointer leaves. Overridden to a near-immediate
     follow while tracking — see .stage.is-tilting below. */
  transition: transform 520ms var(--ease-out);
}
/* While the pointer is over the stage the dashboard tracks it almost directly.
   A 520ms ease here would lag the cursor badly; 90ms linear still smooths the
   per-frame steps without feeling detached. */
.stage.is-tilting .stage-app { transition: transform 90ms linear; }

/* ---- The dashboard ------------------------------------------------------
   Every figure is the product's own. Sponsor and auditor rows are anonymised:
   the rankings are public data, but naming firms on a marketing page reads as a
   customer list, and there are no customers.

   Sizes are cqw against the 1056px stage and floor at a readable minimum. The
   previous version sized rows at .7cqw, which was 7.4px here — fine when this
   was texture behind a filing, useless now that it is the thing being looked
   at. Nothing in this mockup is below 11px. */
/* Inset inside the bezel that .stage-app draws. Inset OR padding on the parent,
   never both — the two would stack and the screen would sit off-centre. */
.app {
  position: absolute; inset: clamp(7px, .9cqw, 11px);
  border-radius: var(--radius-xl); overflow: hidden;
  display: grid; grid-template-columns: 20% 1fr; background: var(--canvas);
}

.app-side {
  background: #12161F; padding: 3% 2.2%;
  display: flex; flex-direction: column; gap: 1.4%;
}
.app-side b {
  font-size: clamp(11px,1.25cqw,14px); font-weight: 700; color: #fff;
  letter-spacing: .01em; margin-bottom: 6%;
}
.app-side b em { font-style: normal; color: var(--brand-bright); }
.app-side span {
  display: flex; align-items: center; gap: 8px;
  font-size: clamp(11px,1.14cqw,12.5px); color: #99A2B4;
  padding: 5.5% 8%; border-radius: var(--radius-sm);
}
.app-side span::before {
  content: ""; width: 6px; height: 6px; border-radius: 2px;
  background: #39415A; flex: 0 0 6px;
}
.app-side span.on { background: #1E2637; color: #fff; }
.app-side span.on::before { background: var(--brand-bright); }
.app-side .sysbox {
  margin-top: auto; border: 1px solid #232B3C; border-radius: var(--radius-md); padding: 8%;
  font-size: clamp(11px,1.04cqw,11.5px); color: #99A2B4; line-height: 1.55;
}
.app-side .sysbox i { font-style: normal; color: #3DD68C; }

.app-main { display: flex; flex-direction: column; min-width: 0; }
.app-top {
  display: flex; align-items: center; gap: 1.6%;
  padding: 1.7% 2.2%; border-bottom: 1px solid var(--line);
  background: var(--canvas-raised);
}
/* A real search field with the product's own placeholder and shortcut, rather
   than a grey rectangle standing in for one. */
.app-search {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 7px;
  padding: 1.1% 1.4%; border-radius: var(--radius-md);
  border: 1px solid var(--line); background: var(--canvas);
  font-size: clamp(11px,1.1cqw,12.5px); color: var(--ink-3);
  white-space: nowrap; overflow: hidden;
}
.app-search svg {
  width: 13px; height: 13px; flex: 0 0 13px;
  stroke: var(--ink-3); fill: none; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.app-search kbd {
  margin-left: auto; font-family: var(--font-mono); font-size: 11px;
  color: var(--ink-3); border: 1px solid var(--line); border-radius: 1px;
  padding: 1px 5px; background: var(--canvas-raised);
}
/* Status pills. The green dot pings on the same ambient rhythm as the eyebrow
   pip, which is the only thing on the whole mockup that loops. */
.app-pill {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 4px 10px; border-radius: 999px;
  font-size: clamp(11px,1.04cqw,11.5px); font-weight: 600;
  border: 1px solid var(--line); background: var(--canvas-raised); color: var(--ink-2);
}
.app-live { color: #067647; border-color: #ABEFC6; background: #F6FEF9; }
.app-live i {
  font-style: normal; width: 6px; height: 6px; border-radius: 50%;
  background: #17B26A; flex: 0 0 6px;
  animation: ping var(--dur-ambient) ease-out infinite;
}
.app-av { width: clamp(20px,2.1cqw,24px); aspect-ratio: 1; border-radius: 50%; background: var(--brand-200); flex: 0 0 auto; }

.app-body { padding: 2.1% 2.2%; display: flex; flex-direction: column; gap: 1.9%; min-height: 0; flex: 1; }
.app-h b { display: block; font-size: clamp(14px,1.75cqw,19px); font-weight: 650; color: var(--ink-1); letter-spacing: -.02em; }
.app-h span { display: block; font-size: clamp(11px,1.14cqw,12.5px); color: var(--ink-3); margin-top: 3px; }

.app-kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 1.5%; }
.app-kpi {
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--canvas-raised); padding: 3.4% 4%;
  display: flex; flex-direction: column;
}
.app-kpi > span { display: block; font-size: clamp(11px,1.09cqw,12px); color: var(--ink-3); }
/* The accent KPI reads in --accent-ink so semantic text and decorative brand
   roles remain separate even though both now clear AA at 4.69:1. */
.app-kpi b {
  display: block; margin-top: 5px; font-size: clamp(19px,2.55cqw,29px); font-weight: 650;
  color: var(--ink-1); letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.app-kpi.hot b { color: var(--accent-ink); }
.app-kpi i { font-style: normal; display: block; margin-top: 4px; font-size: clamp(11px,1.05cqw,11.5px); color: var(--ink-3); }
/* The sparkline under each figure. Drawn with stroke-dashoffset, which repaints
   rather than composites — but it is four 1.5px polylines drawn once, not a
   loop, so the cost is bounded. Do not copy this onto anything large. */
.app-spark { display: block; width: 100%; height: 18px; margin-top: auto; padding-top: 6px; }
.app-spark path {
  fill: none; stroke: var(--brand-500); stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 200; stroke-dashoffset: 200;
  animation: sparkDraw 1.1s var(--ease-out) forwards;
}
.app-kpi.hot .app-spark path { stroke: #F79009; }
@keyframes sparkDraw { to { stroke-dashoffset: 0; } }

.app-cols { display: grid; grid-template-columns: 1.75fr 1fr; gap: 1.5%; flex: 1; min-height: 0; }
.app-panel {
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--canvas-raised);
  padding: 2.8% 3.2%; display: flex; flex-direction: column; gap: 3%; min-height: 0;
}
.app-panel > b { font-size: clamp(12px,1.28cqw,14px); font-weight: 650; color: var(--ink-1); }
.app-tr { display: grid; grid-template-columns: 1fr auto auto; gap: 4%; align-items: center; font-size: clamp(11px,1.14cqw,12.5px); }
.app-tr .nm { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-tr .pp { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.app-tr .rk { padding: 2px 7px; border-radius: 999px; font-size: clamp(11px,1.06cqw,12px); font-weight: 700; }
.rk.hi { background: #FEE4E2; color: #B42318; }
.rk.md { background: #FEF0C7; color: #B54708; }
.rk.lo { background: #DCFAE6; color: #067647; }
.app-rail { display: flex; flex-direction: column; gap: 1.5%; min-height: 0; }
.app-line { display: flex; align-items: center; justify-content: space-between; font-size: clamp(11px,1.14cqw,12.5px); color: var(--ink-3); }
.app-line b { color: var(--ink-1); font-variant-numeric: tabular-nums; font-weight: 650; }
.app-line s { text-decoration: none; display: flex; align-items: center; gap: 6px; }
.app-line s::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--brand-500); }
/* A marker dot rather than a left accent border. The border was the single
   thing the design-floor detector flagged, and it was also inconsistent: the
   pipeline panel right beside this one already marks its rows with a dot. */
.app-alert {
  display: flex; align-items: center; gap: 6px;
  font-size: clamp(11px,1.09cqw,12px); color: var(--ink-3);
}
.app-alert::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: #F79009; flex: 0 0 5px;
}

/* ---- The dashboard assembling itself ------------------------------------
   The chrome is already there and the data arrives into it, which is what the
   product does on a nightly refresh. Compositor-only: opacity and transform.

   The digits deliberately do NOT count up. The pitch of this whole site is that
   the numbers are not inflated, and a number spinning up to its value is the
   single most common way a marketing page signals the opposite. */
.js .app-kpi,
.js .app-tr,
.js .app-line,
.js .app-alert { opacity: 0; animation: appIn .52s var(--ease-emphasis) forwards; }

.js .app-kpi:nth-child(1) { animation-delay: .30s; }
.js .app-kpi:nth-child(2) { animation-delay: .40s; }
.js .app-kpi:nth-child(3) { animation-delay: .50s; }
.js .app-kpi:nth-child(4) { animation-delay: .60s; }
.app-spark path { animation-delay: .95s; }

.js .app-tr:nth-of-type(1) { animation-delay: .86s; }
.js .app-tr:nth-of-type(2) { animation-delay: .95s; }
.js .app-tr:nth-of-type(3) { animation-delay: 1.04s; }
.js .app-tr:nth-of-type(4) { animation-delay: 1.13s; }
.js .app-tr:nth-of-type(5) { animation-delay: 1.22s; }
.js .app-tr:nth-of-type(6) { animation-delay: 1.31s; }
.js .app-line:nth-of-type(1) { animation-delay: 1.00s; }
.js .app-line:nth-of-type(2) { animation-delay: 1.09s; }
.js .app-line:nth-of-type(3) { animation-delay: 1.18s; }
.js .app-line:nth-of-type(4) { animation-delay: 1.27s; }
.js .app-alert:nth-of-type(1) { animation-delay: 1.24s; }
.js .app-alert:nth-of-type(2) { animation-delay: 1.33s; }

@keyframes appIn { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }

/* ---- The dashboard on smaller screens -----------------------------------
   A dashboard cannot simply be scaled down: below about 760px the type would
   have to drop under its readable floor to keep every panel. So panels are
   dropped instead, in the order of what a stranger needs least. What survives
   at 375px is the chrome, the four figures and one working list — still
   recognisably the product, and every word of it still legible.

   These blocks MUST come after the base .app rules above. A selector inside
   @container carries the same specificity as the same selector outside it, so
   when this sat earlier in the file the plain `.app { grid-template-columns:
   20% 1fr }` simply won on source order and the whole mobile layout silently
   did nothing — the query was matching the entire time. */
/* Aspect per step, each one measured rather than estimated. The dashboard's
   minimum height does not scale with its width — the sidebar, the four KPI
   cards and the two panels each have a floor — so the box has to get relatively
   taller as it narrows. Measured minimum ratios: 1.52 at 696px, 1.43 at 596px,
   1.35 at 558px. Each rule below sits under the measured floor for its whole
   range, not just at the breakpoint. */
@container (max-width: 880px) {
  .stage { aspect-ratio: 16 / 10.5; }        /* 1.524 — verified to 696px */
}
@container (max-width: 660px) {
  /* 1.2, not 1.25. At 1.25 the 521px container — the last px before the mobile
     rules take over — came out 0.2px short, which is a pass by luck rather than
     by margin. This leaves ~18px. */
  .stage { aspect-ratio: 6 / 5; }
}
@container (max-width: 760px) {
  /* The placeholder and the shortcut stop fitting alongside the pills long
     before the search field itself runs out of room. */
  .app-search { font-size: 0; gap: 0; }
  .app-search kbd { display: none; }
  .app-pill:not(.app-live) { display: none; }
}
@container (max-width: 520px) {
  /* Portrait, because the sidebar and the right rail are gone below here and
     what is left stacks. This is a container query, not @media, so it switches
     on exactly the same threshold as the layout rules beneath it. */
  .stage { aspect-ratio: 1 / 1.3; }
  .app { grid-template-columns: 1fr; }
  .app-side { display: none; }
  .app-pill { display: none; }
  .app-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.4%; }
  /* The pipeline and deadline panels go rather than shrink; the threshold list
     is the one carrying the product's actual argument. */
  .app-rail { display: none; }
  .app-cols { grid-template-columns: 1fr; }
  .app-tr:nth-of-type(n+5) { display: none; }
  .app-body { gap: 2.6%; }
  /* The KPI captions wrap to three lines in a 136px card and cost ~60px of the
     box. The label and the figure are the point; the caption is the footnote. */
  .app-kpi i { display: none; }
  .app-spark { height: 14px; }
}


/* ============================================================================
   Cards + grids
   ========================================================================== */

.grid { display: grid; gap: 18px; }
/* Every other block that stacks inside a section wrap carries its own top
   margin — .deflist, .filters, .features, .readouts and .surfaces all do. .grid
   and the footnote after .datarow did not, so each butted straight against
   whatever preceded it. Measured on the live site: 0px between the #difference
   heading and the sheets beneath it, and 0px between the #data figures and
   their fine print. */
.wrap > * + .grid,
.wrap > * + .split { margin-top: clamp(34px, 4.5vw, 52px); }
.datarow + .fine   { margin-top: clamp(20px, 2.6vw, 30px); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }

.card {
  background: var(--canvas-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  padding: 24px;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
.card-hover:hover { border-color: var(--brand-200); box-shadow: var(--shadow-sm); }

/* Stat cards */
.stat .n {
  font-size: clamp(30px, 4vw, 40px); font-weight: 680;
  color: var(--ink-1); letter-spacing: -.03em; line-height: 1.05;
  font-variant-numeric: tabular-nums slashed-zero lining-nums;
}
.stat .l { font-size: var(--fs-sm); color: var(--ink-3); margin-top: 10px; }

.stat-hero {
  grid-column: 1 / -1;
  border-color: var(--brand-200);
  background: linear-gradient(180deg, var(--brand-50), var(--canvas-raised) 70%);
}
.stat-hero .n { color: var(--accent-ink); font-size: clamp(38px, 6vw, 56px); }

/* The one honest bar: its width IS the number. */
.meter {
  margin-top: 18px; height: 6px; border-radius: 999px;
  background: var(--brand-100); overflow: hidden;
}
/* scaleX, not width: animating width relayouts the bar every frame. The scale
   factor IS the figure (0.986 = 98.6%), so the bar stays an honest encoding. */
.meter i {
  display: block; height: 100%; width: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--brand-500), var(--brand-bright));
  transform: scaleX(.986); transform-origin: left center;
  transition: transform 1100ms var(--ease-emphasis) 120ms;
}
.js .meter i { transform: scaleX(0); }
.js .is-in .meter i { transform: scaleX(.986); }

/* Feature cards */
.feat h3 { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; font-size: 17.5px; }
.feat p  { color: var(--ink-3); font-size: 15.5px; }
.ic {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: var(--radius-md);
  background: var(--brand-50); border: 1px solid var(--brand-200);
  display: grid; place-items: center; color: var(--accent-ink);
}
.ic svg {
  width: 16px; height: 16px; stroke: currentColor; fill: none;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
}

/* Audience cards */
.aud strong { display: block; color: var(--ink-1); font-size: 15.5px; margin-bottom: 6px; }
.aud span   { font-size: var(--fs-sm); color: var(--ink-3); }
.aud .primary { border-color: var(--brand-200); background: var(--brand-50); }
.tag {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-ink); margin-left: 8px;
  border: 1px solid var(--brand-200); border-radius: 999px; padding: 2px 7px;
}

/* ============================================================================
   Editorial sections
   ----------------------------------------------------------------------------
   These exist so the page does not repeat one 4-up card grid three times. Each
   section gets its own shape: an asymmetric split, a hairline data row, a
   full-width claim, a typographic definition list. Cards are reserved for the
   places where a card is genuinely the right container.
   ========================================================================== */

/* Asymmetric split: heading holds the left rail, body sits right of it. */
.split { display: grid; gap: clamp(20px, 4vw, 56px); align-items: start; }
@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
  .split.narrow-first { grid-template-columns: minmax(0, 4fr) minmax(0, 7fr); }
  /* Inside the media query, not outside it. The 14ch cap exists to stop the
     heading running the full width of its own column in the two-column layout —
     but below 900px .split is a single stacked column, and the cap was still
     applying there, squeezing every section heading on mobile into a 14-character
     measure for no reason. */
  .split > :is(h2, h3):first-child { max-width: 14ch; }
}

/* A statement set larger than body copy — carries a section on its own. */
.statement {
  font-size: clamp(19px, 2.3vw, 24px);
  line-height: 1.48;
  color: var(--ink-2);
  max-width: 46ch;
  letter-spacing: -.011em;
}
.statement em { font-style: normal; color: var(--ink-1); font-weight: 600; }

/* Small quiet label — a kicker used sparingly, not an eyebrow on every band. */
.label {
  font-family: var(--font-mono);
  font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: 14px;
}

/* Hairline data row: figures in a line, divided, no tiles. */
.datarow {
  display: grid; gap: 0;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  border-top: 1px solid var(--line);
  margin-top: 40px;
}
.datarow > div {
  padding: 26px 26px 26px 0;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 780px) {
  .datarow > div + div { padding-left: 26px; border-left: 1px solid var(--line); }
}
.datarow .n {
  display: block;
  font-size: clamp(30px, 3.6vw, 42px); font-weight: 600;
  color: var(--ink-1); letter-spacing: -.035em; line-height: 1;
  font-variant-numeric: tabular-nums slashed-zero lining-nums;
}
.datarow .l { display: block; margin-top: 12px; font-size: 13.5px; color: var(--ink-3); max-width: 26ch; }

/* The one claim that gets full width and its own weight. */
.claim { margin-top: 56px; display: grid; gap: 22px; }
@media (min-width: 900px) { .claim { grid-template-columns: minmax(0, 4fr) minmax(0, 7fr); align-items: center; } }
.claim .n {
  font-size: clamp(56px, 8vw, 104px); font-weight: 600;
  color: var(--accent-ink); letter-spacing: -.045em; line-height: .92;
  font-variant-numeric: tabular-nums slashed-zero lining-nums;
}
.claim p { font-size: clamp(16px, 1.8vw, 18px); color: var(--ink-2); max-width: 52ch; }

/* Typographic definition list — replaces icon-heading-text cards. */
.deflist { display: grid; margin-top: clamp(44px, 6vw, 76px); }
.deflist > div {
  display: grid; gap: 8px;
  padding: 28px 0;
  border-top: 1px solid var(--line);
}
@media (min-width: 860px) {
  .deflist > div { grid-template-columns: minmax(0, 4fr) minmax(0, 7fr); gap: clamp(20px, 4vw, 48px); align-items: baseline; }
}
.deflist h3 { font-size: clamp(19px, 2.1vw, 22px); letter-spacing: -.015em; }
.deflist p { color: var(--ink-3); font-size: 15.5px; max-width: 58ch; }
.deflist .idx {
  /* accent-ink, not brand-500: this is 11.5px text, and brand-500 measures
     4.09:1 here — below the AA floor and against the note at the top of this
     file. Same rule that governs every other accent text run. */
  font-family: var(--font-mono); font-size: 11.5px; color: var(--accent-ink);
  letter-spacing: .1em; display: block; margin-bottom: 6px;
}

/* Audience row: one lead, three compact. */
.aud-lead { padding-block: 30px; border-top: 1px solid var(--line-strong); }
.aud-lead h3 { font-size: clamp(21px, 2.4vw, 26px); margin-bottom: 10px; }
.aud-lead p { color: var(--ink-3); max-width: 56ch; }
.aud-rest {
  display: grid; gap: 0;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  border-top: 1px solid var(--line);
}
.aud-rest > div { padding: 24px 24px 24px 0; border-bottom: 1px solid var(--line); }
@media (min-width: 780px) {
  .aud-rest > div + div { padding-left: 24px; border-left: 1px solid var(--line); }
}
.aud-rest strong { display: block; color: var(--ink-1); margin-bottom: 6px; font-size: 15.5px; }
.aud-rest span { font-size: 14px; color: var(--ink-3); }

/* ---- Filter chips -------------------------------------------------------
   The filters that only exist because the attachments were read. Deliberately
   described as extracted from the statements rather than as "AI features" —
   the brief sells correctness of the data, never the method. */
.filters {
  display: flex; flex-wrap: wrap; gap: 9px;
  margin-top: clamp(26px, 3.4vw, 38px);
}
.filters span {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; border-radius: 999px;
  border: 1px solid var(--brand-200); background: var(--brand-50);
  font-size: 13.5px; color: var(--ink-2);
}
.filters span::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--brand-500); flex: 0 0 5px;
}
.filters span.plain {
  border-color: var(--line); background: var(--canvas-raised); color: var(--ink-3);
}
.filters span.plain::before { background: var(--line-strong); }

/* ---- Trajectory ---------------------------------------------------------
   A plan's filing history, which is the evidence for the claim beside it: you
   only get a trajectory if you analyse every year rather than the latest
   return. Figures are illustrative of the shape, not a specific plan. */
/* Opinion history: one chip per filed year, with the auditor change marked.
   This is the evidence for analysing every filing year — you cannot draw it
   from the latest return. The "disclaimer" note matters: pre-2021 that is the
   standard ERISA limited-scope form, and a tool that flags it as a red flag is
   telling you it does not understand what it is reading. */
.oph { margin-top: 20px; }
.oph-firms {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  font-size: 13px; color: var(--ink-2); margin-bottom: 12px;
}
.oph-firms .firm { display: inline-flex; align-items: baseline; gap: 7px; white-space: nowrap; }
.oph-firms .arrow { color: var(--ink-4); }
.oph-firms b {
  font-weight: 600; padding: 4px 10px; border-radius: 999px;
  background: var(--brand-50); border: 1px solid var(--brand-200);
  color: var(--accent-ink); font-size: 12.5px;
}
.oph-firms i { font-style: normal; color: var(--ink-3); }
.oph-years { display: flex; flex-wrap: wrap; gap: 5px; }
.oph-years span {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 4px 9px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--canvas-raised);
  font-size: 11px; color: var(--ink-3);
}
.oph-years span em {
  font-style: normal; font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-3);
}
.oph-years span.unq { border-color: var(--brand-200); background: var(--brand-50); color: var(--accent-ink); }
.oph-years span.sw { border-color: #FEC84B; background: #FFFAEB; color: #B54708; }
.oph-note { margin-top: 12px; font-size: 12.5px; color: var(--ink-3); max-width: 54ch; }

/* ---- Pipeline board -----------------------------------------------------
   The workspace half of the product. A board reads as "CRM" instantly and is
   visually unlike anything else on the page, which is why it earns the space
   rather than another row of cards. Decorative: aria-hidden, with the real
   claim carried by the copy beside it. */
.board {
  display: grid; gap: clamp(7px, 1vw, 11px);
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin-top: clamp(34px, 4.5vw, 52px);
}
.kan-col {
  background: var(--canvas-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: clamp(7px, 1vw, 11px);
  display: grid; gap: clamp(5px, .8vw, 8px); align-content: start;
  min-height: clamp(120px, 16vw, 178px);
}
.kan-hd {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  /* Was clamp(7.5px, .82vw, 10px) — 7.5px at 375px and never above 10px, on
     uppercase text with .07em tracking, in a board that is NOT hidden on mobile.
     Same defect class as the 6px finding titles and the 7.4px dashboard rows
     this site has already shipped twice. Nothing readable goes below 11px. */
  font-size: clamp(11px, .95vw, 12.5px); font-weight: 650;
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3);
  padding-bottom: 3px;
}
.kan-hd em {
  font-style: normal; font-family: var(--font-mono);
  color: var(--ink-3); font-weight: 400;
}
.kan-col.won .kan-hd { color: var(--accent-ink); }

.kan-card {
  background: var(--canvas-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: clamp(6px, .9vw, 9px);
  display: grid; gap: 4px;
  box-shadow: var(--shadow-xs);
}
.kan-card i { display: block; height: 4px; border-radius: 2px; background: var(--line); }
.kan-card i:first-child { background: var(--line-strong); width: 84%; }
.kan-card i.t { width: 34%; background: var(--brand-200); }
.kan-col.won .kan-card i.t { background: var(--brand-500); }

@media (max-width: 860px) {
  .board { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .kan-col:nth-child(n+4) { display: none; }
}
@media (max-width: 520px) {
  .board { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kan-col:nth-child(n+3) { display: none; }
}

/* ---- Plan record preview -------------------------------------------------
   What a partner knows before dialling. Modelled on the product's own plan
   review screen: opinion and scope up top, the incumbents, then the money.
   Decorative (aria-hidden) — the claim lives in the copy beside it. */
.record {
  border: 1px solid var(--line); border-radius: var(--radius-xl);
  background: var(--canvas-raised); box-shadow: var(--shadow-sm);
  overflow: hidden; margin-top: clamp(30px, 4vw, 44px);
}
.record-top {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, #F5F9FF, var(--canvas-raised));
}
.rec-tag {
  font-size: 11.5px; font-weight: 600; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--canvas-raised); color: var(--ink-2);
}
.rec-tag.ok   { border-color: #ABEFC6; background: #ECFDF3; color: #067647; }
.rec-tag.info { border-color: var(--brand-200); background: var(--brand-50); color: var(--accent-ink); }

/* Four columns, not auto-fit: there are eight fields, and auto-fit resolves to
   five at desktop width, orphaning three on a second row. */
.record-grid { display: grid; gap: 0; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 700px)  { .record-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.record-grid > div {
  padding: 16px 18px;
  border-right: 1px solid var(--line-subtle);
  border-bottom: 1px solid var(--line-subtle);
}
.record-grid dt {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 6px;
}
.record-grid dd {
  margin: 0; font-size: 15px; color: var(--ink-1); font-weight: 550;
  font-variant-numeric: tabular-nums lining-nums;
}
.record-grid dd.muted { color: var(--ink-3); font-weight: 400; }
.record-note {
  padding: 13px 18px; font-size: 12.5px; color: var(--ink-3);
  background: var(--canvas-sunken);
}

/* ---- Readouts — annotating the dashboard the hero just showed ------------
   Each figure is one the visitor has literally just seen on the stage card, so
   this section is an explanation rather than a second recital of the stats. */
.readouts {
  display: grid; gap: 0; margin-top: clamp(34px, 4.5vw, 52px);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  border-top: 1px solid var(--line);
}
.readouts > div { padding: clamp(22px, 3vw, 32px) clamp(20px, 2.5vw, 28px) clamp(22px, 3vw, 32px) 0; }
@media (min-width: 820px) {
  .readouts > div + div { padding-left: clamp(20px, 2.5vw, 28px); border-left: 1px solid var(--line); }
}
.readouts b {
  display: block;
  font-size: clamp(28px, 3.4vw, 40px); font-weight: 620;
  color: var(--accent-ink); letter-spacing: -.035em; line-height: 1;
  font-variant-numeric: tabular-nums slashed-zero lining-nums;
}
.readouts h3 { font-size: 16.5px; margin: 14px 0 7px; letter-spacing: -.012em; }
.readouts p { font-size: 14.5px; color: var(--ink-3); }

/* ---- Feature rows -------------------------------------------------------
   One capability per row, visual large on one side and copy on the other,
   alternating. Replaces a six-up grid of small cards: at that size the mini-UIs
   could not carry enough detail to read as product, and six identical tiles is
   the templated look the whole page is trying to avoid. The card floats on a
   tinted backdrop so it reads as a screen rather than a diagram. */
/* Row separation is now whitespace rather than a card edge, so it has to be
   much larger than the old 22px — at the previous gap the rows ran together
   the moment their borders came off. */
.features { display: grid; gap: clamp(44px, 6vw, 84px); margin-top: clamp(38px, 5vw, 60px); }

/* No background, border, shadow or padding. This row used to wrap one
   screenshot in four nested containers — card, tinted panel, a decorative ghost
   card, then the screen itself — which is three visible borders and two shadows
   around a single image. Two remain, and they each do a job: the tint is the
   stage, .surface-ui is the object standing on it. */
.feature {
  display: grid; gap: clamp(18px, 2.6vw, 44px); align-items: center;
}
@media (min-width: 900px) {
  .feature { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
  .feature:nth-child(even) > .feature-visual { order: 2; }
}

/* The stage. Now the outermost container in the row, so it carries the larger
   radius the outer card used to. The tint is doing the job pure black does on a
   hardware showroom page: give the screen something to stand on. Without it the
   mockup reads as a loose image dropped on the page. */
.feature-visual {
  position: relative;
  border-radius: var(--radius-2xl);
  padding: clamp(22px, 3.2vw, 44px);
  background:
    radial-gradient(120% 90% at 20% 0%, rgba(255,255,255,.55), transparent 60%),
    linear-gradient(145deg, #DCE6FE 0%, #EEF3FF 45%, #E7EEFF 100%);
  display: grid; place-items: center;
  min-height: clamp(220px, 26vw, 300px);
}
.feature-visual .surface-ui {
  position: relative; z-index: 1;
  /* border-bottom was re-declared here to the same value the shorthand above
     already sets. Dead declaration, removed. */
  width: 100%; border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--canvas-raised);
  box-shadow: 0 18px 40px -18px rgba(11,13,20,.28), 0 2px 6px rgba(11,13,20,.06);
  min-height: 0;
}

/* No inset padding any more. It existed to hold the copy off the old card's
   edge; with the card gone it just pushed the text out of alignment with every
   other section on the page. */
.feature-body { padding: 0; }
.feature-body h3 { font-size: clamp(21px, 2.5vw, 30px); letter-spacing: -.02em; margin-bottom: 12px; }
.feature-body p { color: var(--ink-3); font-size: 15.5px; max-width: 46ch; }
.pill {
  display: inline-block; margin-bottom: 14px;
  font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  padding: 6px 12px; border-radius: 999px;
  background: var(--brand-50); color: var(--accent-ink); border: 1px solid var(--brand-200);
}

/* ---- App surfaces -------------------------------------------------------
   Stylised representations of screens that exist in the product. Decorative
   (aria-hidden) with the real claim carried by the heading and copy beneath —
   never a chart that implies a number we have not published. */
.surfaces {
  display: grid; gap: 18px; margin-top: clamp(38px, 5vw, 60px);
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
}
/* Pinned to three at desktop: auto-fit resolves to four here, which orphans
   the last of six cards on a row by itself. */
@media (min-width: 900px) { .surfaces { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* The mini-UIs carry real values now. Grey placeholder bars read as loading
   skeletons to this audience, and a skeleton says nothing about the product.
   Every figure below is illustrative of SHAPE, not a specific plan. */
.ui-line { display: flex; align-items: baseline; gap: 8px; font-size: 11.5px; }
.ui-line .k { color: var(--ink-2); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-line .v {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums; flex: 0 0 auto;
}
.ui-line .v.hot { color: var(--accent-ink); font-weight: 600; }
/* 11px, not 9-10. These are not texture: .ui-cap carries six real captions
   ("Beginning-of-year balances, not headcount", "Median $41K · the number for
   your size, your state"), .ui-yr carries the ten filing years the auditor-change
   mockup is about, and .ui-badge says "Sch H 3c" and "New". All of it is meant
   to be read, so all of it clears the same 11px floor as the rest of the site.

   Safe to grow: measured first, every .surface-ui sizes to its content with no
   overflow, and each sits inside a .feature-visual with ~88px of slack. .ui-yr
   keeps its 30px track — "2024" in the mono face is ~26px at 11px. */
.ui-badge {
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 999px; flex: 0 0 auto;
  background: var(--brand-50); color: var(--accent-ink); border: 1px solid var(--brand-200);
}
.ui-yr { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); flex: 0 0 30px; }
.ui-cap { font-size: 11px; color: var(--ink-3); margin-top: auto; padding-top: 10px; }

/* Peer benchmark: label, percentile bar, traffic light. */
.ui-metric { display: flex; align-items: center; gap: 8px; }
.ui-metric .lb { flex: 1; height: 5px; border-radius: 3px; background: var(--line); }
.ui-metric:nth-child(3) .lb { flex: 0 1 74%; }
.ui-metric:nth-child(4) .lb { flex: 0 1 88%; }
.ui-metric:nth-child(5) .lb { flex: 0 1 46%; }
.ui-metric .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: #17B26A; }
.ui-metric .dot.mid { background: #F79009; }
.ui-metric .dot.bad { background: #F04438; }

/* Fund holdings: name bar plus a share figure. */
.ui-fund { display: flex; align-items: center; gap: 9px; }
.ui-fund .fb { flex: 1; height: 5px; border-radius: 3px; background: var(--line); }
.ui-fund .pc {
  font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-3);
  flex: 0 0 auto; font-variant-numeric: tabular-nums;
}
.surface {
  display: flex; flex-direction: column;
  background: var(--canvas-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.surface:hover { border-color: var(--brand-200); box-shadow: var(--shadow-md); transform: translateY(-2px); }

.surface-ui {
  min-height: 170px; padding: 16px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, #F5F9FF, var(--canvas-raised) 78%);
  display: flex; flex-direction: column; gap: 9px;
}
.ui-hd { height: 7px; width: 42%; border-radius: 3px; background: var(--line-strong); margin-bottom: 3px; }
.ui-row { display: flex; align-items: center; gap: 9px; }
.ui-bar { flex: 1; height: 6px; border-radius: 3px; background: var(--line); }
.ui-row:nth-child(3) .ui-bar { width: 80%; flex: 0 1 80%; }
.ui-row:nth-child(4) .ui-bar { width: 64%; flex: 0 1 64%; }
.ui-pill { width: 32px; height: 13px; border-radius: 999px; background: var(--brand-100); flex: 0 0 32px; }
.ui-pill.hot { background: var(--brand-500); }
.ui-arrow { width: 13px; height: 13px; flex: 0 0 13px; color: var(--brand-500); }
.ui-arrow svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

.ui-cols { display: flex; align-items: flex-end; gap: 5px; height: 96px; flex: 0 0 96px; }
.ui-cols i { flex: 1; border-radius: 2px 2px 0 0; background: var(--brand-100); }
.ui-cols i:nth-child(1) { height: 26%; } .ui-cols i:nth-child(2) { height: 44%; }
.ui-cols i:nth-child(3) { height: 62%; } .ui-cols i:nth-child(4) { height: 88%; }
.ui-cols i:nth-child(5) { height: 70%; } .ui-cols i:nth-child(6) { height: 48%; }
.ui-cols i:nth-child(7) { height: 30%; }
.ui-cols i.med { background: var(--brand-500); }

.surface-body { padding: 20px; flex: 1; }
.surface-body h3 { font-size: 17px; margin-bottom: 7px; letter-spacing: -.015em; }
.surface-body p { font-size: 14.5px; color: var(--ink-3); }

/* Pull-quote. Emphasis comes from measure, size and surface — not from a
   colored bar down the left edge. */
.quote {
  border: 1px solid var(--brand-200);
  background: var(--brand-50);
  border-radius: var(--radius-xl);
  padding: 22px 26px;
  color: var(--ink-1);
  font-size: clamp(17px, 2vw, 19px);
  line-height: 1.5;
  max-width: 56ch;
}

/* Callout / limitations block */
.callout {
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--canvas-sunken);
  padding: 24px;
}

/* Prose list */
.list { display: grid; gap: 12px; list-style: none; }
.list li { position: relative; padding-left: 26px; color: var(--ink-2); }
.list li::before {
  content: ""; position: absolute; left: 6px; top: .62em;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand-500);
}

/* Numbered steps */
.steps { display: grid; gap: 18px; counter-reset: step; list-style: none; }
.steps li { position: relative; padding-left: 52px; counter-increment: step; }
.steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--brand-50); border: 1px solid var(--brand-200);
  color: var(--accent-ink); font-size: 14px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.steps strong { display: block; margin-bottom: 4px; }

/* ============================================================================
   Bespoke #2 — the attachment stack (home page only)
   The Form 5500 sheet, the audited FS sliding out from behind it, and the
   fields lifted off it. Decorative: aria-hidden, with real text alongside.
   ========================================================================== */

.stack { position: relative; min-height: 268px; }
.sheet {
  position: absolute; border-radius: var(--radius-lg);
  background: var(--canvas-raised);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-md);
}
.sheet-a { left: 0; top: 18px; width: 172px; height: 220px; z-index: 1; }
.sheet-b {
  left: 42px; top: 0; width: 172px; height: 220px; z-index: 2;
  border-color: var(--brand-200);
  transform: rotate(2.5deg);
  transition: transform 760ms var(--ease-emphasis) 160ms,
              opacity 620ms var(--ease-emphasis) 160ms;
}
.js .sheet-b { transform: translate(-30px, 12px) rotate(-3deg); opacity: 0; }
.is-in .sheet-b { transform: translate(0, 0) rotate(2.5deg); opacity: 1; }

/* The sheets read like the documents they stand for: the 5500 is a line-numbered
   government form (Part bands, items 5 / 6a(1) / 6g), the audited FS is prose
   under a report title. Generic bars would say nothing to a CPA. */
.sheet .doc-hd {
  display: block; margin: 18px 14px 10px; height: 7px; width: 62%;
  border-radius: 2px; background: var(--line-strong);
}
.sheet .doc-band {
  display: block; margin: 0 14px 10px; padding: 3px 6px;
  background: var(--ink-1); color: var(--canvas-raised);
  font-size: 7.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  border-radius: 2px;
}
.sheet .row { display: flex; align-items: center; gap: 6px; margin: 0 14px 7px; }
.sheet .ln {
  font-family: var(--font-mono); font-size: 7px; color: var(--ink-3);
  min-width: 22px; text-align: right; font-variant-numeric: tabular-nums;
}
.sheet .fill { height: 5px; flex: 1; border-radius: 2px; background: var(--line); }
.sheet .fill.w70 { flex: 0 0 70%; }
.sheet .fill.w50 { flex: 0 0 50%; }
.sheet .fill.w35 { flex: 0 0 35%; }
.sheet .row.hot .fill { background: var(--brand-200); }
.sheet .row.hot .ln  { color: var(--accent-ink); font-weight: 700; }

.sheet-b .doc-hd { background: var(--brand-200); width: 74%; }
.sheet-b .bar {
  display: block; height: 4px; border-radius: 2px;
  background: var(--brand-100); margin: 0 14px 7px;
}
.sheet-b .bar.short { width: 42%; }
.sheet-b .bar.mid   { width: 68%; }

.sheet .tab {
  position: absolute; top: -1px; right: 16px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent-ink); background: var(--brand-50);
  border: 1px solid var(--brand-200); border-top: 0;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  padding: 3px 7px;
}
.sheet-a .tab { color: var(--ink-3); background: var(--canvas-sunken); border-color: var(--line); }

.chips {
  position: absolute; right: 0; top: 8px;
  display: grid; gap: 8px; width: min(240px, 52%);
}
.chip {
  display: flex; align-items: center; gap: 8px;
  background: var(--canvas-raised);
  border: 1px solid var(--brand-200);
  border-radius: 999px;
  padding: 7px 13px;
  font-size: 12.5px; color: var(--ink-2);
  box-shadow: var(--shadow-xs);
  transition: opacity 460ms var(--ease-emphasis),
              transform 460ms var(--ease-emphasis),
              border-color 460ms var(--ease-emphasis);
}
.js .chip { opacity: 0; transform: translateX(10px); border-color: var(--line); }
.chip::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand-500); flex: 0 0 6px;
}
.is-in .chip { opacity: 1; transform: none; border-color: var(--brand-200); }
.is-in .chip:nth-child(1) { transition-delay: 620ms; }
.is-in .chip:nth-child(2) { transition-delay: 700ms; }
.is-in .chip:nth-child(3) { transition-delay: 780ms; }
.is-in .chip:nth-child(4) { transition-delay: 860ms; }
.is-in .chip:nth-child(5) { transition-delay: 940ms; }

/* 989px, not 700px. `.grid-2` (minmax(300px,1fr)) splits into two columns at
   ~682px, but the absolute collage needs ~446px of column to lay out. Between
   701 and ~988px the opaque .sheet-b (z-index 2) painted straight over the
   chips — "§103(a)(3)(C) election" rendered as "3)(a)(3)(C) election" — on the
   one illustration carrying the site's core claim. Verified with
   elementFromPoint at 820px: 45px of overlap. */
@media (max-width: 989px) {
  .stack { min-height: 0; display: grid; gap: 18px; }
  .sheet { position: relative; left: auto; top: auto; width: 100%; max-width: 260px; height: 180px; }
  /* `.js` prefix is required: `.js .sheet-b` (0,2,0) outranks a bare
     `.sheet-b` (0,1,0), so without it the card keeps its -30px offset here and
     hangs off the left edge. */
  .js .sheet-b, .sheet-b { transform: none; opacity: 1; }
  .js .is-in .sheet-b, .is-in .sheet-b { transform: none; }
  .chips { position: relative; right: auto; top: auto; width: 100%; }
}

/* ============================================================================
   Methodology page — sticky table of contents + the 80–120 carry-forward
   ========================================================================== */

.doc { display: grid; gap: clamp(28px, 5vw, 56px); grid-template-columns: 1fr; }
@media (min-width: 960px) {
  .doc { grid-template-columns: 220px minmax(0, 1fr); align-items: start; }
  .toc { position: sticky; top: 96px; }
}
.toc ol { list-style: none; display: grid; gap: 2px; }
.toc a {
  /* Explicit leading. Without it these inherit body's 1.62, which is tuned for
     prose paragraphs, not for a list of one-line jump links — each item came out
     ~36px tall and the seven-item rail ran 28px longer than it needed to. */
  display: block; padding: 7px 12px; border-radius: var(--radius-md);
  font-size: 13.5px; line-height: 1.35; color: var(--ink-3); text-decoration: none;
  border-left: 1px solid transparent;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.toc a:hover { color: var(--ink-1); }
.toc a.is-active { color: var(--accent-ink); border-left-color: var(--brand-500); font-weight: 600; }

/* More space above a heading than below it — the gap belongs to the section
   it opens, not the paragraph it follows. */
.doc-body > * + * { margin-top: 18px; }
.doc-body h2 { margin-top: 56px; margin-bottom: -4px; font-size: clamp(24px, 3vw, 32px); }
.doc-body h2:first-child { margin-top: 0; }
.doc-body p { color: var(--ink-2); }

/* The 80–120 carry-forward diagram */
.carry { display: grid; gap: 10px; }
.carry-row {
  display: grid; grid-template-columns: 92px minmax(0, 1fr) auto;
  align-items: center; gap: 14px;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--canvas-raised); padding: 12px 16px;
}
.carry-yr {
  font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.carry-band { font-size: 14px; color: var(--ink-2); }
.carry-out {
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  border-radius: 999px; padding: 4px 10px; white-space: nowrap;
}
.carry-out.large { color: var(--accent-ink); background: var(--brand-50); border: 1px solid var(--brand-200); }
.carry-out.small { color: var(--ink-3); background: var(--canvas-sunken); border: 1px solid var(--line); }
.carry-note { font-size: 13px; color: var(--ink-3); }
@media (max-width: 560px) {
  .carry-row { grid-template-columns: 1fr; gap: 6px; }
}

/* ============================================================================
   CTA panel + footer
   ========================================================================== */

.cta {
  background: var(--canvas-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-2xl);
  padding: clamp(28px, 4vw, 44px);
  box-shadow: var(--shadow-sm);
}
.cta p { color: var(--ink-3); max-width: 62ch; }
.cta .actions { margin-top: 26px; }

.site-footer { border-top: 1px solid var(--line); padding-block: 40px; }
.foot {
  display: flex; flex-wrap: wrap; gap: 18px;
  align-items: center; justify-content: space-between;
}
.foot-links { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; }
/* Padded to a 44px hit area rather than the 23px the text alone gives — the
   negative inline margin keeps the visual gap the same as the bare text had. */
.foot-links a {
  color: var(--ink-3); text-decoration: none; font-size: var(--fs-sm);
  display: inline-flex; align-items: center; min-height: 44px;
  padding-inline: 6px; margin-inline: -6px; border-radius: var(--radius-sm);
}
.foot-links a:hover { color: var(--ink-1); }
.foot-fine { margin-top: 22px; }

/* Horizontally scrolling regions. Anything with overflow-x:auto needs to be
   focusable or its off-screen columns cannot be reached from the keyboard at
   all -- and once it is focusable the ring has to be visible. */
.table-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.table-scroll:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--brand-ring), 0 0 0 1px var(--brand-500);
}

/* ============================================================================
   404
   ========================================================================== */

.err { min-height: 62vh; display: grid; place-items: center; text-align: center; }
/* text-align centres text; it does NOT centre flex items. .actions is a
   block-level flex container filling this column with justify-content defaulting
   to flex-start, so on the 404 the two buttons sat hard left under a centred
   headline, lede and callout. */
.err .actions { justify-content: center; }
.err .code {
  font-family: var(--font-mono); font-size: 13px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 14px;
}

/* ============================================================================
   Motion — the reveal primitive
   ----------------------------------------------------------------------------
   Everything that starts hidden is gated behind `.js`, which site.js adds to
   <html> as its very first statement while the head is still parsing. So the
   hidden state only ever exists when the script that can un-hide it has
   already run. If site.js is disabled, blocked, or 404s, the class is never
   added and the whole site renders fully visible — there is no way to end up
   with permanently invisible content. site.js also try/catches its own DOM
   work and reveals everything if anything throws.
   ========================================================================== */

.reveal {
  transition: opacity var(--dur-reveal) var(--ease-emphasis),
              transform var(--dur-reveal) var(--ease-emphasis);
}
.js .reveal { opacity: 0; transform: translateY(14px); }
.js .reveal.is-in { opacity: 1; transform: none; }

/* Stagger. No inline style="--i:n" is possible under this CSP, so the delays
   are nth-child rules. Capped at 8 — beyond that the last card feels late. */

/* Hero choreography — bespoke #1.
   Headline and lede are opaque by 300ms: motion never gates reading. */

/* ============================================================================
   Hover and micro-interaction
   ----------------------------------------------------------------------------
   Every rule here animates ONLY opacity and transform, which the compositor can
   run without a layout or paint pass. Nothing animates width, height, padding,
   margin or box-shadow geometry.

   All of it is inside @media (hover: hover) and (pointer: fine). A touch device
   has no hover, and without the guard a phone applies the :hover state on tap
   and leaves it stuck there until you tap elsewhere.

   The underlines are scaleX on a pseudo-element rather than a border or a
   background-size transition: those repaint on every frame, scaleX composites.
   ========================================================================== */

@media (hover: hover) and (pointer: fine) {

  /* Links: the rule grows from the leading edge rather than fading in. */
  .nav-links a:not(.btn),
  .foot-links a,
  .toc a {
    position: relative;
  }
  .nav-links a:not(.btn)::after,
  .foot-links a::after,
  .toc a::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 2px; height: 1px;
    background: currentColor; opacity: .55;
    transform: scaleX(0); transform-origin: left;
    transition: transform var(--dur-base) var(--ease-out);
  }
  .nav-links a:not(.btn):hover::after,
  .foot-links a:hover::after,
  .toc a:hover::after { transform: scaleX(1); }

  /* Buttons: a small rise, and the press already translates down 1px. */
  .btn { will-change: transform; }
  .btn:hover { transform: translateY(-1px); }
  .btn:active { transform: translateY(1px); }

  /* Surfaces that represent a screen lift very slightly, as though picked up.
     -3px, not -8px: this is a document about audit data, not a game. */
  .feature-visual .surface-ui,
  .surface,
  .lin,
  .card {
    transition: transform var(--dur-slow) var(--ease-out);
  }
  .feature:hover .surface-ui,
  .surface:hover,
  .lin:hover,
  .card:hover { transform: translateY(-3px); }

  /* The stage tint brightens fractionally under the pointer. Opacity on a
     pseudo-element, so no gradient is recomputed. */
  .feature-visual::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    background: radial-gradient(60% 50% at 50% 0%, rgba(255,255,255,.5), transparent 70%);
    opacity: 0; transition: opacity var(--dur-slow) var(--ease-out);
    pointer-events: none;
  }
  .feature:hover .feature-visual::after { opacity: 1; }

  /* Chips and pills acknowledge the pointer without moving the line they sit on. */
  .chip, .filters span { transition: transform var(--dur-base) var(--ease-out); }
  .chip:hover, .filters span:hover { transform: translateY(-1px); }
}

/* ============================================================================
   Reduced motion. site.js ALSO checks this and reveals everything immediately,
   so nothing depends on a 0.01ms transition actually firing.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  .sheet-b { opacity: 1; transform: rotate(2.5deg); }
  .chip { opacity: 1; transform: none; }
  .meter i { transform: scaleX(.986); }
  /* The hover layer: the blanket rule above already cuts its durations to
     0.01ms, but these carry a transform in the hovered state, so they are
     pinned to none. The underline is left at scaleX(0) — a rule that appears
     instantly under the pointer is a flicker, not an affordance. */
  .btn:hover, .btn:active,
  .feature:hover .surface-ui, .surface:hover, .lin:hover, .card:hover,
  .chip:hover, .filters span:hover { transform: none; }
  .nav-links a::after, .foot-links a::after, .toc a::after { display: none; }
  .feature:hover .feature-visual::after { opacity: 0; }
  /* The dashboard does not tilt. site.js also skips binding the listener at all
     under reduced motion, so this is belt and braces for the case where the
     preference changes after load without a reload. */
  .stage-app { transform: none; }
  /* The dashboard holds its end state instead of assembling itself: every panel,
     row, KPI and sparkline is simply present.

     The `.js ` prefix is load-bearing, not decoration. The hiding rules are
     `.js .app-kpi { opacity: 0 }` at specificity (0,2,0); writing the reveal as
     a bare `.app-kpi` would be (0,1,0) and LOSE, leaving this block relying
     entirely on the blanket animation-duration:.01ms above to force the
     forwards-filling animation to its end state. That works, but it means one
     edit to the blanket rule turns the hero blank under reduced motion. Matched
     specificity plus later source order makes the reveal win on its own. */
  .js .app-kpi,
  .js .app-tr,
  .js .app-line,
  .js .app-alert { opacity: 1; transform: none; }
  .app-spark path { stroke-dashoffset: 0; }
  .app-live i { animation: none; }
  .eyebrow-mono .pip { animation: none; }
  .famgraph .pulse { animation: none; }
}

/* ============================================================================
   Print — the app has an audit-firm-grade print stylesheet; the site gets a
   small one so a partner can print the methodology page for a file.
   ========================================================================== */

@media print {
  .site-header, .toc, .skip, .stage, .board { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  section { padding-block: 18pt; }
  .card, .callout, .cta { border: 1px solid #bbb; box-shadow: none; break-inside: avoid; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ============================================================================
   Flow and measure
   ----------------------------------------------------------------------------
   The reset zeroes every block margin and only .doc-body ever restored it, so
   .callout, .cta and .aud-lead were rendering headings flush against body copy
   at 0px. And --w-prose / .prose were declared but used nowhere, leaving the
   two blocks people actually read line-by-line running at 95-120 characters.
   ========================================================================== */

.callout > * + *,
.cta > * + *,
.aud-lead > * + * { margin-top: 14px; }
.callout > h2 + *,
.callout > h3 + *,
.cta > h2 + *,
.aud-lead > h3 + * { margin-top: 12px; }
h1 + .lede, h1 + .sec-lede { margin-top: 18px; }
/* `.cta .actions { margin-top: 26px }` used to be repeated here; it is already
   declared once above and the duplicate said nothing different. */

.doc-body p,
.doc-body ul,
.callout p,
.callout .list li,
.aud-lead p { max-width: var(--w-prose); }

/* --ink-4 is 2.59:1; under prefers-contrast it must not stay decorative. */
@media (prefers-contrast: more) {
  :root { --ink-4: var(--ink-3); }
}

/* ============================================================================
   The plan universe — /about
   ----------------------------------------------------------------------------
   /about argued in prose that a smaller, correct universe beats a padded one,
   and then showed nothing. This shows it.

   THE PROPORTIONAL BAR IS GONE, and this note is why, so it is not rebuilt.

   It encoded its figures as flex-grow values (the only way to size segments
   under a CSP with no 'unsafe-inline'), which meant a published statistic lived
   in a stylesheet where nobody greps for one. It had already gone stale once
   that way: the weights outlived the retired 85721/15786/222505 audit-required
   split because the sweep that pulled those figures from the HTML never looked
   here.

   It was removed when its remaining figure did not survive checking. The bar
   claimed ~46,500 "audit reports read and structured"; of 46,496 rows in
   fs_extracted_facts, 45,559 are tier-1 rules-v1 rows that
   scripts/fs_rules_backfill.py leaves with every money field NULL and describes
   as "STILL PENDING", 937 carry financial detail, 12,373 carry an auditor name,
   and 3 retain the report text. There is a real and interesting number in there;
   there was no way to label the published one truthfully. Backlog 0084 holds the
   measurements. The remainder weight was also 629,747 minus the labelled counts,
   i.e. exactly the arithmetic the old note claimed this site does not print.

   What is left states scope without a proportion: the measured total, and the
   In/Out lists that are the page's actual argument.
   ========================================================================== */
.universe {
  margin-top: clamp(34px, 4.5vw, 52px);
  border: 1px solid var(--line); border-radius: var(--radius-2xl);
  background: var(--canvas-raised); padding: clamp(22px, 3vw, 34px);
  box-shadow: var(--shadow-sm);
}
.uni-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px 24px; flex-wrap: wrap;
}
.uni-total { font-size: clamp(14px, 1.6vw, 16.5px); color: var(--ink-3); }
.uni-total b {
  font-size: clamp(30px, 4vw, 44px); font-weight: 620; letter-spacing: -.03em;
  color: var(--ink-1); font-variant-numeric: tabular-nums; margin-right: 9px;
}
.uni-note { font-size: 13px; color: var(--ink-3); }



.uni-scope {
  display: grid; gap: clamp(18px, 2.5vw, 34px);
  margin-top: clamp(24px, 3vw, 34px);
  padding-top: clamp(20px, 2.6vw, 28px);
  border-top: 1px solid var(--line);
}
@media (min-width: 760px) { .uni-scope { grid-template-columns: 1fr 1fr; } }
.uni-scope h3 {
  font-family: var(--font-mono);
  font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 12px;
}
.uni-scope .list { gap: 10px; }
.uni-scope .list li { font-size: 15px; }

/* ============================================================================
   Lineage — what goes in, what is derived, what you can then ask
   ----------------------------------------------------------------------------
   /methodology is the page a sceptic reads before trusting a number, and it was
   pure prose. This shows the path from filing to queryable fact in one object.
   ========================================================================== */
.lineage {
  display: grid; gap: clamp(10px, 1.4vw, 16px);
  margin: clamp(22px, 3vw, 32px) 0;
}
@media (min-width: 820px) {
  .lineage { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; }
}
.lin {
  position: relative;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--canvas-raised); padding: 16px 18px;
  display: flex; flex-direction: column; gap: 9px;
}
/* Chevrons in the gaps. Without them these are three parallel lists that happen
   to sit beside each other; the whole point is that the second column is derived
   FROM the first and the third is only answerable BECAUSE of the second. Only at
   the width where the columns actually sit side by side. */
@media (min-width: 820px) {
  .lin + .lin::before {
    content: ""; position: absolute; top: 50%; left: -12px;
    width: 7px; height: 7px;
    border-top: 1.5px solid var(--line-strong);
    border-right: 1.5px solid var(--line-strong);
    transform: translateY(-50%) rotate(45deg);
  }
}
.lin.derived { border-color: var(--brand-200); background: var(--brand-50); }
.lin h3 {
  font-size: 11.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-3); margin: 0 0 2px;
}
.lin.derived h3 { color: var(--accent-ink); }
.lin span {
  display: flex; align-items: baseline; gap: 7px;
  font-size: 13px; color: var(--ink-2);
}
.lin span::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%; flex: 0 0 4px;
  background: var(--line-strong); transform: translateY(-2px);
}
.lin.derived span::before { background: var(--brand-500); }
.lin em {
  font-style: normal; font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-3); margin-left: auto; flex: 0 0 auto;
}
.lin-note { font-size: 12.5px; color: var(--ink-3); margin-top: -6px; }

/* Sponsor family: a labelled relationship map. Unconnected dots said nothing —
   the point is that these plans all hang off ONE employer EIN, so the edges and
   the plan types have to be visible. */
.famgraph { width: 100%; height: auto; display: block; }
.famgraph .edge { stroke: var(--brand-200); stroke-width: 1.2; fill: none; }
.famgraph .edge.on { stroke: var(--brand-500); stroke-width: 2; }
.famgraph .node { fill: var(--canvas-raised); stroke: var(--brand-200); stroke-width: 1.4; }
.famgraph .node.on { fill: var(--brand-500); stroke: var(--brand-500); }
.famgraph .hub { fill: var(--ink-1); }
/* SVG font-size is in USER UNITS, not screen pixels, so what these render at
   depends on the viewBox-to-display scale — and that changes with the viewport.
   Measured before the fix: at 1440 the graphic rendered 375px wide against a
   300-unit viewBox (scale 1.251), so 8.5 units was 10.63px; at 375 it rendered
   258px wide (scale 0.859) and the same declaration was 7.3px, with the hub
   label at 6.87px. The declared number told you almost nothing.

   Fixed at both ends: the viewBox was cropped to 248 units (see index.html), and
   these are sized so the SMALL end clears the floor. At 375 the scale is now
   ~1.04, putting both labels just over 11px; at 1440 the scale is ~1.51 and they
   render around 16px. A scaling SVG cannot hold a constant pixel size, so the
   mobile end is what gets pinned and desktop is allowed to be generous. */
.famgraph text { font-family: var(--font-sans); font-size: 11px; fill: var(--ink-3); text-anchor: middle; }
.famgraph text.hubtext { fill: var(--ink-1); font-weight: 650; font-size: 11px; }
.famgraph text.ontext { fill: var(--accent-ink); font-weight: 650; }
.famgraph .pulse { animation: famPulse 2.8s var(--ease-in-out) infinite; }
@keyframes famPulse { 0%,100% { r: 6.5; } 55% { r: 8.6; } }


/* ---------------------------------------------------------------------------
   Harmonised with the home page (docs/backlog/0038).

   The personas strip under the wordmark has no counterpart in the home page
   nav. A masthead that changes shape when you click a link reads as a bug, not
   as variety, so it is hidden here rather than deleted from the markup -- the
   four shells are hand-maintained and a markup edit across all of them is the
   thing that historically drifts.
   --------------------------------------------------------------------------- */
.nav-brand .personas { display: none; }

/* Section rhythm, matching home: one modifier that breathes and one that
   tightens, so nine consecutive blocks stop reading as a metronome. */
.sec-breath { padding-block: 120px; }
.sec-tight  { padding-block: 64px; }
