/* Pinetop Capital v2 — "Institutional", band rhythm A1.
   Tony picked direction A from three comps but called it too dark, so navy is an ANCHOR used
   twice (hero and "how it works") rather than the whole page. Everything else is white or a
   pale blue tint.

   THE ONE IDEA IN THIS FILE: a band redefines the same custom properties every component
   already reads (--bg / --ink / --muted / --line / --surface / --accent). A card, a rule, an
   eyebrow or a table therefore inherits whatever ground it happens to sit on, and there is no
   `.band-dark .card` variant anywhere. Adding a band is a token block, not a sweep through
   every component.

   Accent changes per band ON PURPOSE. Gold clears 4.5:1 on navy and fails badly on white, so
   light bands use their own text-weight accent and the dark ground keeps gold for buttons. Every
   pairing was measured; the lowest is 4.87:1 against a 4.5 threshold. Do not "simplify" this
   to a single accent colour — that is how the disclosure text ends up unreadable.

   ---- 2026-08: LIGHTER SCHEME. No dark band carries content any more. ----
   Dark survives only as CHROME — header, footer, breadcrumb, mega-menu, mobile panel, sticky CTA
   — which never scrolled as a band anyway and keeps gold at 7.91:1 unchanged. The rule above is
   intact, not overruled: there is still one accent per ground, it is just that the light grounds'
   accent changed and the dark ground is no longer something you scroll through.

   THE LIGHT ACCENT IS NOW COPPER #9F5527, and the reason is not taste. Every logo in brand/ is
   drawn in copper #B5642F; the CSS gold #D6A24A appears in none of them. The chrome and the mark
   have been different colours since the beginning. #9F5527 is that copper darkened just enough to
   clear body text on every light ground (5.52 white / 5.13 surface / 4.87 tint / 4.91 warm), so
   the site finally matches its own logo instead of the logo being an orphan.

   The deep blue #1B4E7A it replaces was never a decision — it was the fallback that existed
   because gold failed on white. It also happens to be the exact colour every other mortgage site
   in the category uses, which is a poor thing to inherit by accident.

   RHYTHM WITHOUT DARKNESS. White, tint and warm sit within 1.12:1 of each other, so luminance can
   no longer separate sections. Hue does it instead — cool tint against warm ivory — backed by the
   card-on-ground border pattern and section geometry, both of which already existed. */

:root {
  /* Two faces, used for different jobs. The serif carries every display heading; the sans
     carries UI, labels and body. Before this the build LOADED Source Serif on all 33 pages
     and referenced it nowhere — a wasted font request, and the reason the page read as
     inert: one typeface at four similar sizes has no voice. */
  /* Claude Design v3 faces. Bodoni Moda carries the display type; Didot/Georgia are the
     fallbacks the design itself named. Space Grotesk is the sans throughout. */
  --serif: 'Bodoni Moda', Didot, Georgia, 'Times New Roman', serif;
  --sans: 'Space Grotesk', system-ui, -apple-system, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, monospace;

  --r-sm: 3px;
  --r-md: 4px;
  --e-2: 0 2px 8px rgba(14, 22, 32, .07);
  --e-4: 0 10px 30px rgba(14, 22, 32, .13);
  --ease: cubic-bezier(.2, .7, .3, 1);
  /* Spring-ish curves (kinetics.colorion.co). A slight overshoot on the way out reads as
     weight; the "in" curve does NOT overshoot, so nothing wobbles on the way to rest. */
  --spring: cubic-bezier(.34, 1.56, .64, 1);
  --spring-in: cubic-bezier(.4, 0, .2, 1);

  /* Page default = the light band, so anything outside a band still renders sanely. */
  --bg: #FFFFFF; --surface: #F4EEE1; --ink: #2B2622; --body-c: #2B2622;
  /* THE LIGHT GROUNDS ARE WARM. Tony, 2026-08-23: the cards read as light blue boxes, and they did
     — --surface was #F4F7F9 and the ink was #0E1620, a cool near-black. Every value here is now a
     documented light-ground colour from site/palette.js, and every one MEASURES BETTER than the
     cool value it replaced except --ink, which had contrast to spare:
       --surface  #F4F7F9 -> #F4EEE1  Cream, the palette's named card ground
       --ink      #0E1620 -> #2B2622  18.19 -> 14.97 on white; the palette gives Ink both jobs
       --body-c   #26333F -> #2B2622  12.90 -> 14.97
       --muted    #4E5D6C -> #5F574F   6.76 ->  7.09 on white, 6.13 on a cream card
       --faint    #6B7A88 -> #6E6558   4.41 ->  5.73 on white, 4.96 on cream
     --faint was previously retained but unread, because at 4.41:1 it failed 4.5 and everything
     using it carried real content at 9.5-13px. The warm value clears 4.5 on all four grounds, so
     it is usable again — but --muted is still the right reach for anything that must recede. */
  --muted: #5F574F; --faint: #6E6558; --line: #E4DCCB; --line-strong: #B9AE99;
  --accent: #9F5527;
  --warn: #8E1F1F; --warn-bg: #FBF0EC; --warn-line: #E4C3B5;
  --sage: #4A5D50;

  /* Form-field borders, and ONLY form-field borders. Every input on this site also sets
     background:var(--bg), so the border is the sole thing marking where the field is — which makes
     it a UI component boundary under WCAG 1.4.11, subject to 3:1, rather than the decorative
     hairline --line is. The old #7E8C99 was chosen against the COOL grounds and does not survive
     the warm ones: re-measured it gives 2.98 on the cream card and 2.73 on sand, i.e. under 3:1 on
     two of the three surfaces a field actually sits on. #6E6558 measures 5.73 white / 4.96 cream /
     4.54 sand / 5.10 warm. That is heavier than the hairline this used to be — deliberately, since
     the alternative is a field boundary that fails the 3:1 it exists to satisfy. Not redefined per
     band: it is one grey chosen to work on all of them. */
  --line-field: #6E6558;

  /* ---------- type scale ----------
     There was none. 96 hardcoded font-sizes across 26 distinct px values, including 13/13.5/14 all
     doing "small muted caption", 10/10.5/11/11.5 all doing "mono micro-label", and 14.5/15/15.5 all
     doing "secondary body". Sizes half a pixel apart are not a decision anyone made; they are what
     happens when each component is styled on its own.
     Steps are anchored where the weight already was — the value each cluster used most often — so
     folding onto them moves nothing by more than 0.5px, with one exception noted at .compare. */
  --fs-mark: 8px;       /* decorative glyphs */
  --fs-label: 10.5px;   /* mono uppercase micro-labels: eyebrow, tier heads, stat labels */
  --fs-meta: 12.5px;    /* metadata, table guides, breadcrumbs */
  --fs-caption: 13.5px; /* small body: notes, card copy, table cells */
  --fs-sm: 15px;        /* secondary body and buttons */
  --fs-base: 16px;      /* body */
  --fs-md: 17px;        /* h3, leads */
  --fs-lg: 18px;
  --fs-xl: 19px;
  --fs-2xl: 21px;
  --fs-3xl: 22px;
  --fs-4xl: 24px;
  --fs-5xl: 30px;       /* inline stat figures — NOT the hero display numbers */

  /* ---------- spacing scale ----------
     The section rhythm (.band > section.wrap, 66px dropping to 44px) is deliberate and stays
     literal; these are the steps for everything inside a section.

     ADOPTED, NOT ENFORCED, and that is a decision rather than an unfinished job. Snapping every
     off-step value to its nearest step was tried and rejected: it moved 19 declarations by 4px,
     including the horizontal padding on every .btn on the site (26 -> 22) and .tool-feature's
     oversize (which the comment on that rule exists specifically to protect). Font sizes half a
     pixel apart are self-evidently nobody's decision; paddings four pixels apart usually are
     somebody's. So values already on a step now say so, and the ones off it keep their number.
     Use these for new work; do not sweep the file to match them. */
  --sp-1: 14px; --sp-2: 16px; --sp-3: 18px; --sp-4: 20px; --sp-5: 22px;
  --sp-6: 24px; --sp-7: 32px; --sp-8: 40px; --sp-9: 48px; --sp-10: 56px;
}

/* ---------- bands ---------- */
/* position:relative is LOAD-BEARING, not decoration. .hero-art is position:absolute; inset:0, so
   without a positioned band it resolves against the initial containing block: measured on
   /programs/dscr/ it rendered 1265x720 inside a 441px band, painting the hero photograph over the
   whole page and leaving the body copy dark-on-dark. On heroes with no photo the same escape made
   the .topo ridgelines sprawl diagonally across /programs/.
   .band-dark carried this rule on its own, which is why the dark bands looked right and every warm
   hero did not. It belongs on the base class — every band is a containing block. */
.band { background: var(--bg); color: var(--ink); position: relative; }
.band + .band { border-top: 1px solid var(--line); }
.band-dark {
  --bg: #1C1815; --surface: #2A2421; --ink: #EDE4D3; --body-c: #C9BFAE;
  --muted: #C9BFAE; --faint: #A79A86; --line: #38312B; --line-strong: #5F574F;
  --accent: #D6A24A;
  --warn: #F08A80; --warn-bg: #2A2421; --warn-line: #5F574F;
  --sage: #7C8B6F;
}
/* There is deliberately NO .band-light token block. It used to exist and restated :root
   byte for byte — the light band IS the page default, so the class needs only to exist for the
   markup's symmetry with dark and tint, and inherits everything. Bands are never nested (they are
   siblings under <main> on all 46 pages), so nothing needs it to reassert the light palette.
   If a light band is ever nested inside a dark one, this is the block to bring back. */
.band-tint {
  --bg: #EDE4D3; --surface: #FFFFFF; --ink: #2B2622; --body-c: #2B2622;
  --muted: #5F574F; --faint: #6E6558; --line: #E4DCCB; --line-strong: #B9AE99;
  --accent: #9F5527;
  --warn: #8E1F1F; --warn-bg: #FBF0EC; --warn-line: #E4C3B5;
  --sage: #4A5D50;
}
/* The hero's ground, and its only user. Warm ivory against the cool tint is the one differentiation
   left once every band is light — measured 1.12:1 from white, which is nothing as luminance and
   plenty as hue. Keeping it exclusive to the hero is what makes it mean "top of the page". */
.band-warm {
  --bg: #F7F1E6; --surface: #FFFFFF; --ink: #2B2622; --body-c: #2B2622;
  --muted: #5F574F; --faint: #6E6558; --line: #E4DCCB; --line-strong: #B9AE99;
  --accent: #9F5527;
  --warn: #8E1F1F; --warn-bg: #FBF0EC; --warn-line: #E4C3B5;
  --sage: #4A5D50;
}
/* A gold hairline marks every dark→light transition so the change reads as deliberate. */
.band-dark + .band { border-top: 3px solid #D6A24A; }

/* ---------- base ---------- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: #FFFFFF; color: var(--ink);
  font-family: var(--sans); font-size: var(--fs-base); line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
img, svg { max-width: 100%; }
a { color: var(--accent); }
/* Display headings are serif at a much larger scale than the body. The size jump is the
   point: 50px serif against 16px sans is a voice, 31px sans against 16px sans is a document. */
h1, h2 { margin: 0; font-family: var(--serif); font-weight: 600; letter-spacing: -.014em; line-height: 1.1; }
h1 { font-size: clamp(36px, 5.2vw, 62px); line-height: 1.02; letter-spacing: -.02em; max-width: 19ch; }
h2 { font-size: clamp(27px, 3.2vw, 40px); }
h3, h4 { margin: 0; font-family: var(--sans); font-weight: 700; letter-spacing: -.008em; line-height: 1.25; }
h3 { font-size: var(--fs-md); }
p { margin: 0; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- breadcrumbs ----------
   A dark strip between the header and the hero, which is also dark on every page type — so it
   reads as the bottom edge of the header rather than as a light seam cutting between two dark
   bands. Colours are literal for the same reason header/footer are: this sits outside the band
   system entirely. */
/* Light. It was navy, and with a sage bar above and a dark hero below it made three dark
   bands stacked before any content. --accent MUST move with the ground: #E09456 measures
   1.95:1 on sand, while the light-ground copper is 5.28:1. */
.crumbs { background: #EDE4D3; border-bottom: 1px solid #DCD2C0; --accent: #8E4A20; }
.crumbs ol {
  list-style: none; display: flex; flex-wrap: wrap; margin: 0; padding: 11px 0;
  font-family: var(--mono); font-size: var(--fs-meta); color: #5F574F;
}
.crumbs li:not(:last-child)::after { content: '/'; margin: 0 9px; color: #9A8F82; }
.crumbs a { color: #5F574F; text-decoration: none; }
.crumbs a:hover { color: #2B2622; text-decoration: underline; }
.crumbs li[aria-current] { color: #2B2622; }

/* Visually hidden, still announced. Clip-path rather than display:none or width:0 — both of those
   remove it from the accessibility tree too, which defeats the point. */
.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* A fieldset exists here for grouping semantics only; the h2 inside it carries the visible label. */
.wiz-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }

/* Off-screen until focused. First tab stop on every page, so a keyboard or screen-reader user can
   get past the header without walking the whole nav on all 46 pages. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: #D6A24A; color: #17202B; padding: 12px 20px; font-weight: 600; text-decoration: none;
}
.skip-link:focus { left: 12px; top: 12px; }
/* Restating the resting colour, because the homepage inlines design/v3.css and its global
   a:hover{color:#EDE4D3} is (0,1,1) against this rule's (0,1,0). Cream on the gold measures 1.82:1,
   so a keyboard user who reached the skip link and then moved the mouse lost it. #17202B on gold is
   7.14:1 and was chosen deliberately — see the note at .hcalc-mode below for why copper was not. */
.skip-link:hover, .skip-link:focus { color: #17202B; }

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 30px; }
.band > section.wrap { padding-top: 66px; padding-bottom: 66px; }

/* A short accent rule above every section eyebrow. Cheap, but it gives each band a visible
   starting edge instead of text floating in whitespace. */
.eyebrow {
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .11em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 var(--sp-1);
  padding-top: var(--sp-2); border-top: 2px solid var(--accent); display: inline-block;
}
.eyebrow a { border: 0; }
.eyebrow a { color: inherit; text-decoration: none; }
.eyebrow a:hover { text-decoration: underline; }
.mono-note { font-family: var(--mono); font-size: var(--fs-label); color: var(--muted); }
.sec-h { margin-bottom: var(--sp-2); }
.sec-lead { font-size: var(--fs-base); line-height: 1.65; color: var(--muted); max-width: 62ch; }
.mini-h { font-size: var(--fs-sm); margin-bottom: 8px; }
.page-lead { font-size: var(--fs-md); line-height: 1.6; color: var(--body-c); max-width: 62ch; margin-top: var(--sp-2); }
.back-link { margin-top: var(--sp-8); }

/* ---------- buttons ---------- */
/* Form controls do NOT inherit font-family — the browser gives them its own, which here meant
   every <button class="btn"> rendered in Arial while every <a class="btn"> beside it rendered in
   Public Sans. Invisible until a button and a link sit next to each other, which is exactly what
   the term-sheet tool and the unlock form do. */
button, input, select, textarea { font-family: inherit; }

/* Was var(--btn-bg)/var(--btn-ink) — a pair of tokens declared in all four palette blocks and
   read by this one rule. On light and tint they resolved to the block's own --ink and --bg, so
   those two tokens say the same thing with fewer moving parts. Only the dark band genuinely
   differed (gold on navy, not white on light ink), so it keeps an override below. */
.btn {
  display: inline-block; background: var(--ink); color: var(--bg);
  text-decoration: none; font-weight: 600; font-size: var(--fs-sm);
  padding: var(--sp-1) 26px; border-radius: var(--r-sm); border: 1px solid transparent;
  transition: translate .12s var(--ease), box-shadow .12s var(--ease);
}
/* `translate`, not `transform` — a transform here would outrank the hover declaration if a
   reveal animation is ever added, and the lift would silently stop working. */
/* The one band where the button is NOT ink-on-background: gold on navy, so it reads as the
   primary action against a dark ground instead of disappearing into it. */
.band-dark .btn { background: #D6A24A; color: #17202B; }
.btn:hover { translate: 0 -1px; box-shadow: var(--e-2); }

/* ---------- the card shell ----------
   Nine components on this site are the same box: a hairline border, a 4px radius and the surface
   tone. Each one used to restate those three declarations itself, so "cards look like this" was a
   fact you could only learn by reading nine rules and noticing they agreed.
   Declared here as a selector list rather than a .card class the markup opts into: the outcome is
   identical, but a class means nine template edits and a component silently losing its chrome the
   day someone forgets one. Each component still owns its PADDING below, which is the part that
   genuinely differs by role — a 250px rail widget and the Deal Desk's hero card should not share
   a number. */
.prog-card, .side-panel, .panel, .embed, .rail-cta, .scope, .ts-panel, .wiz-out, .tool-feature {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
}
.btn-ghost {
  display: inline-block; text-decoration: none; font-weight: 600; font-size: var(--fs-sm);
  padding: 12px var(--sp-5); border-radius: var(--r-sm);
  border: 1px solid var(--line-strong); color: var(--ink); background: none;
}
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; margin-top: 26px; }

/* ---------- header ---------- */
/* position + z-index so the mega-menu anchors to the header and overlays the page below it. */
/* --accent is redeclared because the focus ring reads it. header, footer and .sticky-cta are
   painted navy with literal hex rather than carrying .band-dark, so they never picked up the
   dark-band palette and :focus-visible fell through to the LIGHT band's #1B4E7A — a 2.09:1 ring
   on a #0E1620 bar, on the three elements that appear on every page and at every scroll position.
   The dark-band gold measures 7.91:1 there. Nothing else in these blocks reads --accent, so this
   moves the focus ring and only the focus ring. */
/* The ring reads --accent. On a copper bar the copper ring is invisible, so it goes dark:
   #1C1815 measures 7.17:1 there. .crumbs is still navy and keeps the copper ring. */
header, .sticky-cta { --accent: #1C1815; }
.crumbs { --accent: #E09456; }
/* Copper, to match the nav and sticky bars. 7.40:1 on the #0E1620 footer against the gold's
   7.91, the same trade the nav made. The gold that remains is .band-dark's accent and its
   buttons, which are the dark CONTENT band palette rather than chrome — a separate decision,
   and one that would restyle every dark band on the site. */
footer { --accent: #E09456; }
/* STICKY, site-wide. It was `relative`, so the nav scrolled away — survivable on a normal page and
   not on the homepage, where the hero alone is 600vh of scroll with no way back to navigation
   without returning to the top.
   Sticky rather than fixed: fixed leaves the flow and every page would need top padding to stop its
   first element hiding underneath. z-index 50 already outranked the page; it now also has to
   outrank the hero's sticky stage, which shares top:0 with it and would otherwise paint over the
   nav simply by coming later in the document. */
/* COPPER -> SAGE. The gradient means every link sits on a different luminance, so the text
   has to clear AA at the WORST point rather than on average. Dark #1C1815 runs 7.17:1 at the
   copper end down to 4.85:1 at #7C8B6F — legal the whole way, but with only ~0.35 of
   headroom, so darkening the sage end breaks it.
   Deep sage #4A5D50 was the other candidate and cannot be used: from the midpoint dark text
   falls to 2.50:1 while cream does not reach 4.5:1 until near the end, leaving a band where
   NEITHER passes. That is the mid-tone trap the palette page exists to catch. */
/* Everything inside had to invert: the old light nav links measure 2.09:1 on
   #E09456 and white is 2.46:1, both hard fails, while brand charcoal is 7.17:1. The border is
   a darkened copper rather than the navy hairline, which disappeared. */
header { background: #7C8B6F;
  border-bottom: 1px solid #6E7A63; position: sticky; top: 0; z-index: 50; }
/* The brand is the least-established thing on this site — it is a pending DBA nobody has seen
   before — so the lockup carries more weight than it would for a known name. At 34px the
   wordmark and the "CAPITAL" line were barely legible. */
header .wrap { display: flex; align-items: center; gap: 20px; padding-top: var(--sp-3); padding-bottom: var(--sp-3); }
header .brand { display: flex; align-items: center; gap: 0; text-decoration: none; }
header img { display: block; height: 64px; width: auto; }
/* .nav-desktop, not every nav in the header. The mobile panel is also a <nav>, so this rule was
   turning it into a flex ROW: measured at 500px wide, the menu list came out 224px and the
   "Send your scenario" button sat beside it at x=278 instead of below it. */
header .nav-desktop { margin-left: auto; display: flex; align-items: center; gap: 24px; }
.navlist { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; gap: 24px; }
header .navlink { color: #1C1815; text-decoration: none; font-size: var(--fs-sm); font-weight: 600; }
header .navlink:hover { color: #1C1815; text-decoration: underline; text-underline-offset: 4px; }
header .navlink[aria-current] { color: #1C1815; text-decoration: underline;
  text-underline-offset: 4px; text-decoration-thickness: 2px; }
/* Literal: a single decorative caret with no shared role to join. */
.navcaret { font-size: 9px; margin-left: 5px; opacity: .7; }

/* ---------- Programs mega-menu ----------
   Opens on hover OR focus-within. The focus-within half is what makes it keyboard-usable:
   focusing the always-visible trigger reveals the panel, which makes the panel's links
   focusable so tabbing continues into them. Do NOT swap this for a display:none toggle —
   a panel that is display:none or visibility:hidden can never receive focus, so the menu
   would become unreachable without a pointer.
   `visibility` (not display) so it animates, and so it leaves the tab order when closed. */
.has-mega { position: static; }
.mega {
  position: absolute; left: 0; right: 0; top: 100%;
  background: #2A2421; border-top: 1px solid #38312B; border-bottom: 3px solid #E09456;
  padding: 26px 0 var(--sp-4); z-index: 60;
  opacity: 0; visibility: hidden; translate: 0 -6px;
  transition: opacity .16s var(--ease), translate .16s var(--ease), visibility .16s;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .35);
}
.has-mega:hover .mega,
.has-mega:focus-within .mega { opacity: 1; visibility: visible; translate: 0 0; }

.mega-grid {
  max-width: 1180px; margin: 0 auto; padding: 0 30px;
  /* Sized so all six tiers fit one row at 1180px: 6×160 + 5×20 = 1060 inside 1120 of content.
     At 190px they wrapped to a second row and the menu lost its at-a-glance quality. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: 22px 20px;
}
/* .mega-wide is gone: the guides menu used to print full article headlines, which needed a wider
   column and a smaller type size to fit. It now labels each guide with its program name — the same
   short strings the programs menu uses — so both menus share one grid. */
.mega-h {
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em; text-transform: uppercase;
  color: #E09456; margin-bottom: 10px; line-height: 1.5;
}
.mega ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.mega ul a {
  display: block; padding: 5px 0; font-size: var(--fs-caption); line-height: 1.4;
  color: #C9BFAE; text-decoration: none; border-bottom: 1px solid transparent;
}
.mega ul a:hover { color: #EDE4D3; border-bottom-color: #5F574F; }
.mega-foot {
  max-width: 1180px; margin: 18px auto 0; padding: var(--sp-1) 30px 0; border-top: 1px solid #38312B;
  display: flex; gap: 12px 24px; flex-wrap: wrap; align-items: baseline;
  font-size: var(--fs-meta); color: #A79A86;
}
.mega-foot a { color: #E09456; text-decoration: none; font-weight: 600; display: inline-block; padding: 3px 0; }
.mega-foot a:hover { text-decoration: underline; }
.mega-foot span { margin-left: auto; }

/* ---------- lane panels ----------
   The panel this replaces printed all 17 programs across six tier columns. These carry a lane's
   SITUATIONS — seven and five — as a card each, label plus the blurb that already answers "is this
   me". Same copy as the homepage panels, from the same homeLanes derivation.
   Three columns at full width; the grid collapses on its own below that. */
.lane-grid { grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 2px; }
.lane-item {
  display: block; padding: 12px 14px; text-decoration: none; background: #1C1815;
  border: 1px solid transparent; transition: border-color .13s var(--ease), background .13s var(--ease);
}
.lane-item:hover, .lane-item:focus-visible { background: #38312B; border-color: #5F574F; }
.lane-item b { display: block; font-size: var(--fs-sm); color: #EDE4D3; line-height: 1.3; }
.lane-item span {
  display: block; margin-top: 4px; font-size: var(--fs-meta); line-height: 1.5; color: #C9BFAE;
}

header .btn { background: #1C1815; color: #EDE4D3; padding: 10px var(--sp-3); font-size: var(--fs-caption); }

/* ---------- mobile nav ----------
   There was no hover on touch and, for a long time, nothing in its place: below 900px the mega
   panels were simply hidden and the reasoning was that tapping "Programs" navigates to the index.
   That holds for the two items that HAVE an index. It did not hold for the 32 leaf pages, which
   became unreachable from the header entirely on the device most visitors arrive on.

   The panel is a native <details>. It is closed by default, so it costs nothing on desktop where
   it is display:none anyway, and it needs no script to open. */
/* position:static, deliberately — same as .has-mega above and for the same reason. It keeps the
   header as the panel's containing block so left:0/right:0 span the full bar. Making .mnav itself
   relative anchors the panel to the 75px-wide toggle instead. */
.mnav { display: none; position: static; margin-left: auto; }
.mnav-toggle {
  display: flex; align-items: center; gap: 9px; cursor: pointer;
  /* 44px tall including padding — the toggle is the one control the whole menu depends on. */
  /* #1C1815, not the old #C6D1DC. The bar is sage: the light value measured 2.35:1 on it and this
     is the ONE control the whole menu depends on below the desktop breakpoint. Dark measures
     4.85:1 and matches the desktop navlinks, which were already dark on this bar. */
  padding: 11px 4px; color: #1C1815; font-size: var(--fs-sm); font-weight: 500;
  list-style: none; /* kills the default disclosure triangle in Firefox */
}
.mnav-toggle::-webkit-details-marker { display: none; }
/* Three bars drawn from one element so there is no extra markup to keep aria-hidden. */
.mnav-bars, .mnav-bars::before, .mnav-bars::after {
  display: block; width: 20px; height: 2px; background: #1C1815; content: '';
}
.mnav-bars { position: relative; }
.mnav-bars::before { position: absolute; top: -6px; }
.mnav-bars::after { position: absolute; top: 6px; }
.mnav[open] .mnav-bars { background: transparent; }
.mnav[open] .mnav-bars::before { top: 0; rotate: 45deg; }
.mnav[open] .mnav-bars::after { top: 0; rotate: -45deg; }

/* Breaks out of the header .wrap to full width, the same trick .mega uses and for the same
   reason — header is already position:relative to anchor it. */
.mnav-panel {
  position: absolute; left: 0; right: 0; top: 100%;
  background: #2A2421; border-top: 1px solid #38312B; border-bottom: 3px solid #E09456;
  padding: 8px 0 22px; z-index: 60;
  max-height: calc(100vh - 120px); overflow-y: auto;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .35);
}
.mnav-list { list-style: none; margin: 0; padding: 0; }
.mnav-list > li { border-bottom: 1px solid #38312B; }
.mnav-link, .mnav-group > summary {
  display: block; padding: 15px 30px; color: #EDE4D3; text-decoration: none;
  font-size: var(--fs-sm); font-weight: 600; cursor: pointer; list-style: none;
}
.mnav-group > summary::-webkit-details-marker { display: none; }
/* The caret is generated, so an open group reads as open without a second element or a script. */
.mnav-group > summary::after { content: '▾'; float: right; opacity: .7; transition: rotate .15s var(--ease); }
.mnav-group[open] > summary::after { rotate: 180deg; }
.mnav-body { padding: 2px 30px 18px; }
.mnav-h {
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em; text-transform: uppercase;
  color: #E09456; margin: 14px 0 6px; line-height: 1.5;
}
.mnav-sub { list-style: none; margin: 0; padding: 0; }
.mnav-sub a {
  display: block; padding: 10px 0; color: #C9BFAE; text-decoration: none;
  font-size: var(--fs-caption); line-height: 1.35; border-bottom: 1px solid #38312B;
}
.mnav-sub a:hover { color: #EDE4D3; }
.mnav-all { display: inline-block; margin-top: 10px; padding: 10px 0; color: #E09456; text-decoration: none; font-weight: 600; font-size: var(--fs-caption); }
.mnav-cta { display: block; margin: 20px 30px 0; text-align: center; background: #E09456; color: #1C1815; }

/* 1150, not 900. The bar is lane-first now — "Investment property" and "A home to live in" are
   163px and 131px against the 60-90px of the content-type labels they replaced — and the logo grew
   to 64px, which widened the lockup to 251px. Measured: the six links need 826px and the brand 251,
   so below roughly 1150 the list wrapped to a second row and the header doubled in height. The
   hamburger already carries every destination, so handing over sooner is better than a bar that
   wraps. Everything else keyed to 900px (the program rail, the two-column shapes) stays there. */
@media (max-width: 1150px) {
  .mega { display: none; }
  .navcaret { display: none; }
  .nav-desktop { display: none; }
  .mnav { display: block; }
}

/* ---------- hero ---------- */
/* EVERY hero section must be listed here. .hero-art is position:absolute, so a positioned photo
   layer paints above any in-flow sibling — a hero section left off this list renders its content
   underneath the photograph and looks like an empty band. That is not visible to scripted checks:
   the elements still report correct rects and computed colours while being completely obscured. */
.page-hero, .hero, .hero2 { position: relative; z-index: 1; }
.hero { display: flex; flex-wrap: wrap; gap: 56px; align-items: flex-start; }
.hero-main { flex: 1 1 480px; min-width: 300px; }
.hero .lead { font-size: var(--fs-md); line-height: 1.6; color: var(--body-c); margin: var(--sp-4) 0 0; max-width: 54ch; }

/* ---------- hero ----------
   ONE COLUMN. This was a 2fr/1fr grid for the investor/consumer split, and the split is gone —
   the consumer offer has its own band and the calculator has its own band. Left as a two-column
   grid it silently laid the eyebrow, headline, lead, slot, links and carousel into two columns,
   which is not something contrast or geometry checks would have caught.

   The .lane / .lane-inv / .lane-con rules that lived here went with it; nothing emits those
   classes any more. */
.hero2 { display: block; }
.hero-slot { min-height: 260px; }
@media (max-width: 640px) { .hero-slot { min-height: 180px; } }

/* Smaller than the site h1. That clamp was set for a full-width hero; in a 2fr column it wraps to
   five lines and spends the fold before the calculator starts. The lead tightens for the same
   reason — the two lanes have to share the height the headline used to own alone. */
/* The global `h1 { max-width: 19ch }` was measured against the old 62px full-width hero. At this
   hero's 40px it resolves to 363px and breaks an 84-character headline over FIVE lines — which
   went unnoticed while the h1 sat inside a 2fr column and was narrow anyway. Now the hero is one
   full-width column, so the measure has to be set for this size. */
.hero2 h1 { font-size: clamp(30px, 3.2vw, 40px); line-height: 1.12; margin-bottom: 4px; max-width: 28ch; }
/* The capitalised variant of the differentiator's first letter; only shown once the category
   clause is hidden at ≤640px. */
.h1-cap { display: none; }
.hero2 .lead { font-size: var(--fs-sm); line-height: 1.5; margin-top: 10px; max-width: 48ch; }
/* The hero band keeps the site's 66px bottom rhythm but loses some of the top: the header sits
   directly above it, so the full gap buys nothing and costs fold. Measured against the sticky CTA
   at a 720px viewport — a 1366x768 laptop — which is where the answer was being covered. */
.band:first-child > .hero2 { padding-top: 34px; }

/* ---------- fold calculator ---------- */
.hcalc {
  display: grid; grid-template-columns: 1.35fr 1fr; gap: 10px 20px;
  margin: var(--sp-2) 0 var(--sp-3); align-items: start;
}
/* Both of these span, or they consume a column each: the label would sit beside the fields and
   push the result panel onto a second row UNDER them — which drops the number, the one thing the
   fold exists to show, below the fold. */
.hcalc-h { grid-column: 1 / -1; }
.hcalc-h, .hcalc-l {
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 8px;
}
/* Real <button role="radio">s, not divs with click handlers — they are focusable, operable by
   Enter and Space for free, and announce their checked state. hero-calc.js adds arrow-key movement
   because role="radio" promises it. */
.hcalc-top {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 16px; margin-bottom: 2px;
}
.hcalc-top .hcalc-h { margin: 0; }
.hcalc-modes { display: flex; flex-wrap: wrap; gap: 0; }
.hcalc-mode {
  font: 600 var(--fs-meta)/1 var(--mono); letter-spacing: .06em;
  padding: 9px 13px; cursor: pointer;
  background: transparent; color: var(--muted);
  border: 1px solid var(--line-field); border-right-width: 0;
}
.hcalc-mode:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.hcalc-mode:last-child { border-right-width: 1px; border-radius: 0 var(--r-sm) var(--r-sm) 0; }
.hcalc-mode:hover { color: var(--ink); }
/* WHITE on the fill, not the navy that was here. That pairing was written when --accent was gold
   (#17202B on gold = 7.14:1); on copper it measures 2.98:1 and fails outright. Copper is a dark
   fill and takes light text — 5.52:1. The five other #17202B rules all sit on the gold CHROME,
   which did not change and still passes. */
.hcalc-mode[aria-checked='true'] { background: var(--accent); color: #FFFFFF; border-color: var(--accent); }
/* The neighbour's left edge would otherwise sit over the selected chip's own border. */
.hcalc-mode[aria-checked='true'] + .hcalc-mode { border-left-color: var(--accent); }

.hcalc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.hcalc-grid label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: var(--fs-meta); color: var(--muted);
}
/* MUST come after the rule above. `display:flex` on a class beats the UA stylesheet's
   `[hidden] { display: none }`, so the hidden field kept rendering — a phantom fifth input in
   every mode. Testing `el.hidden` does not catch this: the property is true while the element is
   plainly on screen. Check computed display. */
.hcalc-grid label[hidden] { display: none; }
/* 16px exactly — under it iOS zooms the page on focus. Same rule as every other input here.
   --line-field was chosen last phase for white and tint grounds; measured 5.09:1 against this
   dark field, so it carries to the hero unchanged. */
.hcalc-grid input {
  font: 16px/1.3 var(--sans);
  padding: 9px 11px; border: 1px solid var(--line-field); border-radius: var(--r-sm);
  background: var(--bg); color: var(--ink); width: 100%;
}
.hcalc-grid input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.hcalc-out {
  background: var(--surface); border: 1px solid var(--line-field);
  border-radius: var(--r-md); padding: 13px 16px;
}
.hcalc-v {
  font-family: var(--serif); font-size: clamp(26px, 2.9vw, 33px); font-weight: 600;
  letter-spacing: -.02em; line-height: 1.05; color: var(--accent);
  font-variant-numeric: tabular-nums; margin: 0 0 6px;
}
/* The arithmetic behind the headline number. Tabular figures so the column of amounts aligns —
   the point of showing the working is that it reads as a sum. */
.hcalc-rows { margin: 8px 0 0; }
.hcalc-row {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: var(--fs-meta); line-height: 1.5; padding: 2px 0;
}
.hcalc-row dt { color: var(--muted); }
.hcalc-row dd { margin: 0; color: var(--body-c); font-variant-numeric: tabular-nums; }
.hcalc-rows:not(:empty) + .hcalc-bind:not(:empty) { margin-top: 8px; }

/* :empty so the margin does not collapse into dead space in the two modes that never set it. */
.hcalc-bind:not(:empty) {
  font-size: var(--fs-meta); line-height: 1.45; color: var(--ink);
  font-weight: 600; margin: 0 0 6px;
}
.hcalc-note { font-size: var(--fs-meta); line-height: 1.5; color: var(--muted); margin: 0; }
.hcalc-out .btn { display: block; text-align: center; margin-top: 12px; }
.hcalc-more { grid-column: 1 / -1; margin: 0; font-size: var(--fs-caption); }
.hcalc-more a { color: var(--accent); text-decoration: none; }
.hcalc-more a:hover { text-decoration: underline; }

/* The homepage hide is GONE. It existed because the hero showed both lanes side by side, so the
   bar beneath was asking a question already answered. The hero is a single-column carousel now and
   the consumer lane is its own band further down — the bar duplicates nothing and is once again
   the only lane router in the fold. .sticky-home is still emitted; it just has no rule. */

@media (max-width: 900px) {
  .hero2 { grid-template-columns: 1fr; }
  /* The gold rule becomes the seam BETWEEN the stacked lanes rather than beside them. */
  .lane-inv { padding-right: 0; border-right: 0; border-bottom: 2px solid var(--accent); padding-bottom: 28px; }
  .lane-con { margin-left: 0; margin-top: 28px; }
  .hcalc { grid-template-columns: 1fr; gap: 14px; }
  /* ANSWER ABOVE THE FIELDS once this is a single column. Beside them on desktop it is level with
     the first field; stacked under them on a phone it lands ~200px below the fold, and no amount
     of trimming the headline recovers that without gutting copy that earns its place.
     Above them it is on screen at load, and it stays on screen while typing because the field
     being edited scrolls into view just underneath it. Result-then-inputs is the ordinary shape
     for a phone calculator anyway. */
  .hcalc-top { order: 1; }
  .hcalc-out { order: 2; }
  .hcalc-grid { order: 3; }
  .hcalc-more { order: 4; }
}
@media (max-width: 640px) {
  /* On a phone the headline and lead were spending the whole fold between them and the first
     field landed below it — which defeats a fold calculator entirely. The copy is unchanged;
     only its scale is. Two columns of fields fit at 375px because they are short numeric inputs,
     so the single-column stack below is dropped to 430px where it is genuinely needed. */
  /* Drop the category clause, promote the differentiator's first letter. See heroH1() in build.js
     — display:none, so the unused clause leaves the accessibility tree rather than being read. */
  .h1-lede { display: none; }
  .h1-cap { display: inline; }
  .lead-tail { display: none; }
  /* The panel's own "Send this scenario" goes on a phone: the sticky bar is pinned on screen
     right below it offering the same action, and the ~50px it costs is the difference between the
     input fields being visible at load and not. Desktop keeps it — there the sticky bar is hidden
     on this page, so the panel button is the only ask in the fold. */
  .hcalc-out .btn { display: none; }
  .hero2 h1 { font-size: 27px; line-height: 1.15; }
  .hero2 .lead { font-size: var(--fs-caption); line-height: 1.5; margin-top: 10px; }
  .hcalc { margin-top: var(--sp-3); }
  .hcalc-grid { gap: 8px 10px; }
}
/* The fields stay two-across all the way down. They are short numeric inputs with short labels,
   and stacking four of them costs ~200px of fold — which is the difference between the estimate
   being on screen and not. Verified at 375px: 162px per column, no label wrap, no overflow. */

/* Seeded contour artwork — unique per program, which is one of the three things stopping the
   14 program pages being interchangeable. pointer-events:none so it can never intercept a
   click on the CTA sitting above it. */
.hero-art { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.topo { position: absolute; right: -6%; top: -16%; width: 76%; height: 146%; }
/* Depth on the ivory band. These were warm-gold and navy pools tuned to glow on near-black; at
   those values they are invisible on #F7F1E6, because gold's luminance sits far closer to white
   than to navy. Rebuilt much fainter and in-hue with the two light grounds — this is depth, not
   contrast, and nothing legible depends on it. */
.hero-art::before,
.hero-art::after { content: ''; position: absolute; inset: -10%; }
.hero-art::before {
  background:
    radial-gradient(1100px 620px at 78% 12%, rgba(159, 85, 39, .07), transparent 62%),
    radial-gradient(900px 520px at 8% 96%, rgba(27, 78, 122, .06), transparent 60%);
}

/* Home hero photograph. 40KB JPEG (from a 2.4MB source — the image is smooth gradient, so it
   compresses hard). Sits UNDER two dark layers so the headline's contrast never depends on
   what the photograph happens to contain: a flat navy wash, then a left-weighted gradient
   over the text column. Measured behind the H1 after this, not assumed.
   The seeded contour SVG is hidden here — the photo already carries the ridgeline motif and
   both together read as noise. Inner pages keep the SVG. */
.has-photo { background: #1C1815; }
.has-photo .topo { display: none; }
/* The photo gets its OWN layer so it can be translated for parallax. inset -14% vertically is
   the safety margin: the layer is taller than its clip box, so translating it can never expose
   an edge. It also cannot overflow the band — .hero-art clips it. */
/* .hero-photo carried url(assets/hero-ridge.jpg) and .band-art.has-photo carried
   url(assets/sec-why.jpg). Both files are deleted: no page has emitted .hero-photo, .has-photo or
   .band-art since variant D replaced the photo-backed heroes, and a url() pointing at a missing
   file is invisible to the build's dead-link check, which only reads href/src in HTML. */
.hero-photo { position: absolute; inset: -14% 0; z-index: 0; }
.has-photo::after {
  background:
    /* Measured behind the actual text with check-contrast.mjs: 17:1 on the headline and lead.
       Kept deliberately light on the right so the ridgeline still reads. An earlier pass
       darkened this chasing a 4.34:1 reading that turned out to be the measuring tool sampling
       the white glyphs, not the background — re-measure before ever deepening it again. */
    linear-gradient(100deg, rgba(14, 22, 32, .94) 0%, rgba(14, 22, 32, .88) 38%, rgba(14, 22, 32, .66) 66%, rgba(14, 22, 32, .40) 100%),
    linear-gradient(to bottom, rgba(14, 22, 32, .55), rgba(14, 22, 32, .15) 45%, rgba(14, 22, 32, .70));
  inset: 0;
}

/* Ambient drift. The seeded ridgelines and the light pools move on different, deliberately
   non-matching periods (54s / 37s) so the loop never visibly "restarts" — a matched period
   is what makes a background loop read as a GIF. Amplitude is tiny; at a glance the hero
   just feels alive rather than animated.
   Transform-only, so it stays on the compositor and costs no layout.
   This is the ONLY looping motion on the site, it sits behind the hero, and it is nowhere
   near the footer disclosures — motion beside required disclosure text is the thing to
   avoid, not motion as such. Killed entirely by prefers-reduced-motion below. */
@media (prefers-reduced-motion: no-preference) {
  .topo { animation: drift 54s ease-in-out infinite alternate; }
  .hero-art::before { animation: pools 37s ease-in-out infinite alternate; }
  /* No Ken Burns on the photograph. Animating background-size repaints a full-viewport
     element every frame, and scaling .hero-art instead would push it past the band (it is
     the ambient layer's clipping context) — the same overflow class of bug the timeline
     connector caused. The drifting light pools above already give the hero life. */
}
@keyframes drift {
  from { translate: 0 0; scale: 1; }
  to   { translate: -34px 14px; scale: 1.045; }
}
@keyframes pools {
  from { translate: 0 0; opacity: .85; }
  to   { translate: 26px -18px; opacity: 1; }
}

/* Parallax. Scroll-driven, so no JS and no scroll listener. `transform`, not `translate` —
   the site's convention is reveals/scroll effects on transform and hovers on translate, so the
   two can never silently outrank each other (see the top of the motion section).
   Graceful by construction: if the timeline never advances, the photo is simply static. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-photo {
      animation: parallax linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
  }
}
@keyframes parallax {
  from { transform: translateY(-7%); }
  to   { transform: translateY(7%); }
}

.cta-row { display: flex; flex-direction: column; align-items: flex-start; gap: 18px; margin-top: var(--sp-7); }
.cta-alts { display: flex; align-items: baseline; gap: 8px 20px; flex-wrap: wrap; }
.cta-alt {
  font-size: var(--fs-sm); font-weight: 500; color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--line-strong); padding-bottom: 2px;
}
.cta-alt:hover { color: var(--accent); border-bottom-color: var(--accent); }
.cta-note { margin-top: var(--sp-1); font-size: var(--fs-caption); color: var(--muted); }

/* ---------- footprint panel ----------
   "Can you even lend to me here" is the first qualifying question. In v1 it was buried in a
   scope line one click deep; here it is in the hero on every page that has a side panel. */
.footprint { max-width: 62ch; }
.footprint-h { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .1em; color: var(--muted); margin: 0 0 12px; }
.footprint dl { margin: 0; }
.footprint dt { font-size: var(--fs-caption); color: var(--muted); }
.footprint dd { margin: 2px 0 var(--sp-1); font-size: var(--fs-sm); font-weight: 600; line-height: 1.35; }
.footprint dd:last-child { margin-bottom: 0; }
.fp-excl { color: var(--warn); font-weight: 500; }

.proof { list-style: none; margin: var(--sp-6) 0 0; padding: var(--sp-6) 0 0; border-top: 1px solid var(--line); display: grid; gap: 12px; }
.proof li { font-size: var(--fs-caption); line-height: 1.5; color: var(--muted); padding-left: var(--sp-2); position: relative; }
.proof li::before { content: ''; position: absolute; left: 0; top: .55em; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }

.side-panel {
  flex: 1 1 300px; min-width: 280px; padding: 26px 28px; align-self: start;
}

/* ---------- hero carousel: "is this you?" ----------
   CSS scroll-snap, so it works with zero JavaScript: real scrolling, real snapping, keyboard
   scrollable, and the arrows/dots are genuine anchors. The inline script only ADDS
   auto-advance and stops those anchors scrolling the page. Nothing is hidden behind JS. */
/* align-self:start, NOT stretch — stretching matched the panel to the tall hero column and
   left most of it empty around one short slide. */
.scenes-wrap {
  flex: 1 1 340px; min-width: 290px; align-self: start;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--sp-5) 0 var(--sp-3); display: flex; flex-direction: column;
}
.scenes-head { display: flex; align-items: center; gap: 16px; padding: 0 var(--sp-6) var(--sp-1); }
.scenes-head .eyebrow { margin: 0; }
.scenes-nav { margin-left: auto; display: flex; gap: 6px; }
.scenes-arrow {
  width: 30px; height: 30px; display: grid; place-items: center; text-decoration: none;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); color: var(--ink);
  font-size: var(--fs-caption); line-height: 1;
}
.scenes-arrow:hover { border-color: var(--accent); color: var(--accent); }

.scenes {
  list-style: none; margin: 0; padding: 0; display: flex; flex: 1;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  scrollbar-width: none;
}
.scenes::-webkit-scrollbar { display: none; }
/* min-height keeps every slide the same height, so the panel does not jump as it advances —
   the scenarios vary in length by a line or two. */
.scene {
  flex: 0 0 100%; scroll-snap-align: center; padding: 0 var(--sp-6); min-height: 150px;
  display: flex; flex-direction: column; gap: 8px; justify-content: flex-start;
}
/* Which lane the slide belongs to. The carousel carries BOTH lanes now, so without this a reader
   has no way to tell an owner-occupied scenario from a business-purpose one. */
.scene-lane {
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; color: var(--accent);
}
.scene-t { font-size: var(--fs-md); font-weight: 700; letter-spacing: -.008em; line-height: 1.3; }
.scene-b { font-size: var(--fs-caption); line-height: 1.6; color: var(--muted); }
.scene-p { margin-top: 6px; font-size: var(--fs-caption); }
.scene-p a { color: var(--accent); text-decoration: none; font-weight: 600; }
/* The arrow is a real aria-hidden span, not ::after content — Chrome folds generated content
   into the accessible name, so a pseudo-element arrow made the link read "DSCR rental loans →". */
.scene-p a:hover { text-decoration: underline; }

.scenes-dots { display: flex; gap: 7px; justify-content: center; padding: 16px 24px 0; }
/* Padded to a 24px-square tap target (WCAG 2.5.8). The visual dot stays 22x3 — only the hit area
   grows, which is the whole point: it measured 26x13 and these are the controls for the hero. */
.scenes-dots a { display: block; padding: 11px 2px; }
.scenes-dots span {
  display: block; width: 22px; height: 3px; border-radius: 2px;
  background: var(--line-strong); transition: background .2s var(--ease);
}
.scenes-dots a:hover span { background: var(--accent); }
.scenes-dots a[aria-current='true'] span { background: var(--accent); }

/* ---------- footprint strip (moved out of the hero) ---------- */
.footprint-strip {
  display: flex; align-items: baseline; gap: 14px 40px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line); padding-bottom: var(--sp-5); margin-bottom: 36px;
}
.footprint-strip .footprint-h { margin: 0; }
.footprint-strip dl { margin: 0; display: flex; gap: 14px 40px; flex-wrap: wrap; }
.footprint-strip dt { font-size: var(--fs-meta); color: var(--muted); }
.footprint-strip dd { margin: 3px 0 0; font-size: var(--fs-sm); font-weight: 600; line-height: 1.3; }

/* ---------- section shapes ----------
   Every band used to be the same archetype: centred column, eyebrow, heading, one full-width
   row of equal boxes — five times down the page. These give each band its own geometry.
   `.sec-split` is an asymmetric 2-col: heading rail left, content right. */
.sec-split { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 56px; align-items: start; }
.sec-split-head { max-width: 46ch; }
.sec-split-head.is-sticky { position: sticky; top: 30px; }

/* Full-bleed split: escapes .wrap so the two halves run edge to edge. Nothing else on the
   site touches the viewport edges, which is exactly why this reads as a different section. */
.bleed { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); align-items: stretch; }
.bleed-copy { background: var(--surface); display: flex; align-items: center; padding: 66px var(--sp-8); }
.bleed-copy-inner { max-width: 52ch; margin-left: auto; }
.bleed-form { padding: var(--sp-8); display: flex; align-items: center; }
.bleed-form iframe { display: block; width: 100%; border: 0; }

/* ---------- v3 catalogue: the Capabilities Guide's page 3 ----------
   Four headed categories of `Name — one line`. No cards, no photos, no rail. The whole point
   of v3 is that this reads as one scannable list instead of a stack of components. */
/* Homepage teaser: one line per category, names only. */
/* FOUR ROWS, one per category, each a single link into /programs/. The names of all 23 programs
   used to live here as ·-joined paragraphs; see catalogueTeaser() in build.js for why they left.
   Two columns to match the grid on /programs/ rather than a four-deep stack. */
.cat-teaser { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 24px; margin-top: 30px; }
.cat-jump {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 14px 0; border-bottom: 1px solid var(--line); text-decoration: none; color: var(--ink);
}
.cat-jump:hover { border-bottom-color: var(--accent); }
.cat-jump:hover .cat-count { color: var(--accent); }
/* .cat-h is a <p> on /programs/ and a <span> here, so it needs to lay out as a block either way. */
.cat-teaser .cat-h { display: block; margin: 0; padding: 0; border: 0; }
.cat-count { font-family: var(--mono); font-size: var(--fs-label); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
@media (max-width: 760px) { .cat-teaser { grid-template-columns: 1fr; gap: 0; } }

/* Two columns, as the guide has it — at 320px min it resolved to three and pushed "Special
   situations" onto an orphan third row. The guide's 2x2 is the layout being copied. */
.cat-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(430px, 100%), 1fr));
  gap: 40px 64px; margin-top: 34px;
}
.cat-h {
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); margin-bottom: var(--sp-1); padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}
.cat-h span { font-size: var(--fs-mark); vertical-align: 2px; margin-right: 4px; }
.cat ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.cat li { font-size: var(--fs-sm); line-height: 1.5; }
.cat li a { color: var(--ink); font-weight: 700; text-decoration: none; border-bottom: 1px solid var(--line-strong); }
.cat li a:hover { color: var(--accent); border-bottom-color: var(--accent); }
/* Items with no page — the guide lists them, so the site does too, but they aren't funnels. */
.cat-plain { font-weight: 700; color: var(--ink); }
.cat-d { color: var(--muted); }
.cat-x { color: var(--warn); font-weight: 600; font-size: var(--fs-caption); }
.cat-foot {
  margin-top: 34px; padding-top: var(--sp-2); border-top: 1px solid var(--line);
  font-size: var(--fs-caption); font-style: italic; color: var(--muted);
}

/* The dark "how it works" band reuses the hero's photo treatment. .band-art drops the
   left-heavy gradient the hero needs (that band's text starts at the left edge of a much
   shorter block) in favour of an even wash. */
.band-art { z-index: 0; }
.band-art.has-photo { background-position: center 62%; }
.band-art.has-photo::after {
  background: linear-gradient(to bottom, rgba(14, 22, 32, .90), rgba(14, 22, 32, .80) 55%, rgba(14, 22, 32, .93));
}

/* Small numeric facts beside the section heading — fills the asymmetric column with
   something true rather than padding it out with decoration. */
.facts { display: flex; gap: 30px; margin: 0 0 var(--sp-5); flex-wrap: wrap; }
.facts dt { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.facts dd { margin: 4px 0 0; font-size: var(--fs-5xl); font-weight: 700; letter-spacing: -.02em; line-height: 1; }
.flag {
  display: inline-block; font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  padding: 4px 8px; border-radius: var(--r-sm); margin-bottom: 10px;
  background: var(--accent); color: #FFFFFF;
}
.grid-foot { margin-top: 36px; }

/* ---------- reasons: a numbered LIST, not a 4-up box grid ---------- */
.reasons { list-style: none; margin: 0; padding: 0; }
.reasons li { display: grid; grid-template-columns: auto 1fr; gap: 20px; padding: var(--sp-5) 0; border-bottom: 1px solid var(--line); }
.reasons li:first-child { padding-top: 0; }
.reasons li:last-child { border-bottom: 0; padding-bottom: 0; }
.reasons-n { font-family: var(--mono); font-size: var(--fs-meta); color: var(--accent); padding-top: 3px; }
.reasons h3 { font-size: var(--fs-md); margin-bottom: 6px; }
.reasons p { font-size: var(--fs-sm); line-height: 1.65; color: var(--muted); max-width: 60ch; }

/* ---------- track: a connected timeline, not three boxes ---------- */
.track-head { max-width: 46ch; }
.track { list-style: none; margin: 46px 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 32px; position: relative; }
.track li { position: relative; padding-top: 30px; }
/* The connecting line spans each item only — it does NOT bridge the gutter. Reaching across
   with `right: -32px` (the gap width) pushed 32px past the last item in every row, which at
   20px page padding put 12px outside the viewport and made the whole page scroll sideways on
   every phone width. A segmented track reads the same and cannot overflow. */
.track li::before {
  content: ''; position: absolute; top: 7px; left: 0; right: 0; height: 1px; background: var(--line);
}
.track-dot { position: absolute; top: 2px; left: 0; width: 11px; height: 11px; border-radius: 50%; background: var(--accent); }
.track-n { display: block; font-family: var(--mono); font-size: var(--fs-meta); color: var(--accent); margin-bottom: 8px; }
.track h3 { font-size: var(--fs-md); margin-bottom: 8px; }
.track p { font-size: var(--fs-sm); line-height: 1.65; color: var(--muted); }

/* ---------- program pages ---------- */
.prog-body { display: flex; flex-wrap: wrap; gap: 48px; align-items: flex-start; }
.prog-main { flex: 1 1 560px; min-width: 300px; }
.lede { margin-bottom: 44px; }
.lede p { font-size: var(--fs-md); line-height: 1.65; color: var(--body-c); }

/* Sticky in-page nav. The page is six long sections in one column and v1 gave no sense of
   its shape. Hidden under 900px, where .mobile-cta owns the bottom of the screen and a
   second sticky element would fight it. */
.rail { flex: 0 0 220px; position: sticky; top: 24px; display: grid; gap: 26px; }
.jump { display: flex; flex-direction: column; gap: 2px; border-left: 2px solid var(--line); padding-left: var(--sp-2); }
.jump a { font-size: var(--fs-caption); color: var(--muted); text-decoration: none; padding: 6px 0; line-height: 1.4; }
.jump a:hover { color: var(--accent); }

/* CTA inside the rail. On a 3,000px program page the only "send your scenario" was at the
   very bottom, so a reader convinced by paragraph two had to scroll past everything to act. */
/* The accent stripe has to come after the shared border, or the shorthand overwrites it. */
.rail-cta { border-left: 3px solid var(--accent); padding: var(--sp-3); }
.rail-cta p { font-size: var(--fs-caption); font-weight: 600; margin-bottom: 12px; }
.rail-cta .btn { display: block; text-align: center; font-size: var(--fs-caption); padding: 11px var(--sp-1); }
.rail-call { display: block; margin-top: 10px; text-align: center; font-size: var(--fs-caption); color: var(--muted); text-decoration: none; }
.rail-call:hover { color: var(--accent); }

.fit-pair { display: grid; gap: 8px; margin: var(--sp-4) 0 0; max-width: 62ch; }
.fit { font-size: var(--fs-sm); line-height: 1.55; color: var(--body-c); }
.fit b { color: var(--ink); font-weight: 600; }
.fit-no b { color: var(--warn); }

/* Keeps the tighter --r-sm radius: this is an inline disclosure inside prose, not a standalone
   card, and the larger corner made it read as one. */
.scope {
  margin-top: var(--sp-5); font-size: var(--fs-caption); line-height: 1.5; color: var(--muted);
  border-radius: var(--r-sm); padding: 12px var(--sp-2); max-width: 74ch;
}
.scope-excl { margin-top: 10px; background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn); font-weight: 600; }

.prog-h2 { margin: 52px 0 var(--sp-3); font-size: var(--fs-4xl); scroll-margin-top: var(--sp-6); }
.prog-h2.first { margin-top: 0; }
.prose p { font-size: var(--fs-base); line-height: 1.7; color: var(--body-c); margin-bottom: var(--sp-2); }
.prose p:last-child { margin-bottom: 0; }
.article .prose { max-width: 68ch; }

.prog-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); gap: 14px; }
.prog-card {
  /* var(--ink) not inherit — flipping a token only reaches properties that read it. */
  display: block; padding: var(--sp-4); text-decoration: none; color: var(--ink);
}
a.prog-card { transition: border-color .14s var(--ease), translate .14s var(--ease); }
a.prog-card:hover { border-color: var(--accent); translate: 0 -2px; }
.prog-card h3 { margin-bottom: 8px; }
.prog-card p { font-size: var(--fs-caption); line-height: 1.55; color: var(--muted); }
.prog-card .slug { display: block; margin-top: 12px; font-family: var(--mono); font-size: var(--fs-label); color: var(--muted); }

/* Cross-links generated by crosslink() in build.js — the first mention of another program in
   body copy. Underlined rather than coloured-only, because inside a paragraph colour alone is
   a weak affordance and fails for anyone who can't distinguish it from the body text. */
.xlink {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid var(--accent); padding-bottom: 1px;
}
.xlink:hover { color: var(--accent); border-bottom-width: 2px; }

/* ---------- Capabilities Guide voice ----------
   The guide closes every segment page with this line, set in gold italic serif. Repeating it
   verbatim is deliberate: a referral partner who read the PDF should recognise the same voice
   on the site. Copy lives in config.signatureLine, not here. */
.signature {
  font-family: var(--serif); font-style: italic; font-size: var(--fs-lg);
  /* Sage. This line is voice, not an action — on the accent it read as something to click.
     Deep sage carries it at 7.06:1 on white and keeps the accent meaning one thing only. */
  color: var(--sage); margin: 0 0 var(--sp-5);
}

/* The guide marks capability lines with a gold diamond rather than a bullet. */
.fit-pair, .proof, .scene-b { --mark: '◆'; }
.proof li::before {
  content: var(--mark); background: none; width: auto; height: auto; border-radius: 0;
  color: var(--accent); font-size: var(--fs-mark); top: .35em;
}

.how { margin: 0; padding: 0; list-style: none; counter-reset: s; display: grid; gap: 14px; }
.how li { counter-increment: s; position: relative; padding-left: 44px; font-size: var(--fs-sm); line-height: 1.65; color: var(--body-c); }
.how li::before {
  content: counter(s, decimal-leading-zero); position: absolute; left: 0; top: 1px;
  font-family: var(--mono); font-size: var(--fs-meta); color: var(--accent);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); padding: 2px 6px;
}

.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; padding: var(--sp-2) 0; font-weight: 600; font-size: var(--fs-sm); list-style: none;
  display: flex; justify-content: space-between; gap: 16px; align-items: baseline;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; color: var(--accent); font-weight: 400; font-size: var(--fs-xl); }
.faq details[open] summary::after { content: '\2013'; }
.faq p { padding: 0 0 var(--sp-3); font-size: var(--fs-sm); line-height: 1.65; color: var(--muted); max-width: 74ch; }

.panel { padding: var(--sp-5) var(--sp-6); }
.panel h3 { margin-bottom: 10px; }
.panel p { font-size: var(--fs-caption); line-height: 1.55; color: var(--muted); margin-bottom: var(--sp-2); }
.panel-accent { border-left: 3px solid var(--accent); }
.note-panel { margin: var(--sp-5) 0 0; font-size: var(--fs-sm); line-height: 1.6; color: var(--muted); }
.cta-block { margin-top: var(--sp-6); }

.two-col { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 32px; align-items: start; }
.split { display: flex; flex-wrap: wrap; gap: 40px; }
.embed { padding: 8px; }
.embed iframe { display: block; width: 100%; border: 0; border-radius: var(--r-sm); }

/* ---------- guides hub ---------- */
.prog-group { margin-bottom: var(--sp-9); }
.prog-group:last-child { margin-bottom: 0; }
.prog-group-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.prog-group-head h2 { font-size: var(--fs-3xl); }
.prog-group .sec-lead { margin: 8px 0 var(--sp-3); font-size: var(--fs-sm); }

/* ---------- compare table ----------
   Real <table>, row header is the program name. Under 760px each row restacks into a
   labelled block via data-label rather than scrolling sideways. */
.compare-scroll { overflow-x: auto; }
.compare { width: 100%; border-collapse: collapse; font-size: var(--fs-caption); margin-top: 8px; }
.compare thead th {
  text-align: left; font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em;
  color: var(--muted); font-weight: 400; padding: 0 var(--sp-1) 10px 0;
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.compare tbody th, .compare td {
  text-align: left; font-weight: 400; vertical-align: top;
  padding: 15px var(--sp-1) 15px 0; border-bottom: 1px solid var(--line); line-height: 1.5;
}
.compare tbody th { width: 22%; }
.compare tbody th a { font-weight: 600; color: var(--ink); display: block; text-decoration: none; }
.compare tbody th a:hover { color: var(--accent); }
.compare .row-guide { display: block; margin-top: 4px; font-size: var(--fs-meta); font-weight: 400; color: var(--muted); }
.compare .row-guide:hover { color: var(--accent); }
.compare td { color: var(--muted); }
/* CONTRAST ON A 17-ROW TABLE. Rows were separated by a 1px hairline and nothing else, which is
   enough to draw a table and not enough to track one row across four columns. Two changes, both
   scoped above 780px because below that the table restacks into cards and neither applies.

   Zebra by row. The tint is mixed from the existing tokens rather than being a new hex, so it
   follows the palette and the dark theme without a second value to maintain. Every tbody starts
   with a .tier-row group header, so nth-child parity is consistent across all six groups, and the
   header itself is excluded — striping it would fight the accent rule under it.

   A vertical rule after the Program column. Zebra alone helps you hold a row; the rule tells you
   where the identifier ends and the comparison starts, which is the other half of scanning this.

   Hover is rewritten with the SAME specificity as the stripe. The old rule was (0,2,3) against the
   stripe's (0,3,3), so the stripe would have won and hovering a striped row would have done
   nothing visible. */
@media (min-width: 781px) {
  .compare tbody tr:not(.tier-row):nth-child(even) > * {
    background: color-mix(in srgb, var(--surface) 55%, var(--bg));
  }
  .compare tbody tr:not(.tier-row) > th { border-right: 1px solid var(--line); padding-right: var(--sp-2); }
  .compare thead th:first-child { border-right: 1px solid var(--line); padding-right: var(--sp-2); }
  .compare tbody tr:not(.tier-row):hover > * { background: var(--surface); }
}
.compare .where-alt { color: var(--muted); font-size: var(--fs-caption); }
.compare .where-excl { color: var(--warn); font-size: var(--fs-meta); font-weight: 600; }

.compare .tier-row th {
  padding: 34px 0 10px; border-bottom: 2px solid var(--accent);
  font-weight: 400; vertical-align: baseline; scroll-margin-top: var(--sp-4);
}
.compare .tier-group:first-of-type .tier-row th { padding-top: 10px; }
.compare .tier-name { display: block; font-size: var(--fs-xl); font-weight: 700; letter-spacing: -.012em; }
.compare .tier-blurb { display: block; margin-top: 3px; font-size: var(--fs-caption); color: var(--muted); max-width: 66ch; }
.compare .tier-row:hover th { background: none; }

/* ---------- footer ----------
   GROUND IS CHARCOAL #1C1815, not the old navy #0E1620. Tony, 2026-08-21: the navy at the bottom of
   every page read off-brand, and it was — the homepage hero, the one-screen hero and all 17 program
   heroes sit on charcoal, so the footer was the one dark surface disagreeing with the rest.
   Every replacement is a palette-documented colour for this ground and every one MEASURES BRIGHTER
   than what it replaced: body 11.74 -> 13.97, disclosure 6.98 -> 9.70. The note below still holds.
   Carries the full NEXA required set: both Equal Housing marks as real artwork, the corporate
   address, NEXALending.com, tel/mailto, NMLS Consumer Access and the Generic Verbiage
   verbatim. build.js asserts all six on all 33 pages. The disclosure text is deliberately at
   #93A2B3 on navy (6.98:1) — setting a legally required line below 3:1 is the pattern a
   regulator reads as burying it. Do not dim these further. */
footer { background: #1C1815; color: #C9BFAE; border-top: 3px solid #E09456; }
footer .wrap { padding-top: 44px; padding-bottom: var(--sp-8); }
.foot-top { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 40px; }
.foot-top img { height: 68px; width: auto; display: block; margin-bottom: 8px; }
/* Scoped to .foot-top so the colour below actually lands. `.foot-top p` sets #C9BFAE at (0,1,1) for
   the disclaimer paragraph beside this one, and a bare `.foot-brand` is (0,1,0), so the wordmark had
   silently rendered in the disclaimer's muted tone since this block was written — the declared cream
   never shipped. Same cascade shape as the a:hover bugs: a general rule quietly beating a specific
   intention, visible only if you measure. .foot-brand appears exactly once and always inside
   .foot-top, so scoping it costs nothing. */
.foot-top .foot-brand {
  font-family: var(--serif); font-size: var(--fs-sm); color: #EDE4D3; letter-spacing: .01em;
  margin-bottom: var(--sp-1);
}
/* EQUAL, and everything here inherits rather than restating a value, so the two can never drift
   apart when the wordmark's size or colour is next touched. The NEXA checklist wants "NEXA" at a
   size >= the team name; inheriting makes that exactly equal by construction rather than by two
   numbers someone has to keep in step. font-weight:inherit because <b> would otherwise bold it and
   read as MORE prominent, which is a different claim from equal. */
/* Sized against the LOGO, not against a text caption. The mark renders 68px tall including the
   tree and the CAPITAL sub-line; its dominant wordmark is roughly half that, so 26px puts
   "Empowered by NEXA" at the wordmark's own weight in the lockup rather than at footnote size.
   Tony's standard, 2026-08-29. */
.foot-top .foot-brand b { display: block; font-size: 26px; font-weight: inherit; color: inherit;
  letter-spacing: .005em; }
.foot-brand span { color: #A79A86; font-family: var(--sans); font-size: var(--fs-meta); display: block; margin-top: 2px; }
.foot-top p { font-size: var(--fs-caption); line-height: 1.65; color: #C9BFAE; max-width: 54ch; }
.foot-right { display: grid; gap: 18px; }
footer a { color: #E09456; }
.eh-marks { display: flex; gap: 12px; align-items: flex-start; }
.eh-marks img { height: 46px; width: auto; margin: 0; background: #FFFFFF; padding: 3px; border-radius: 2px; }
.foot-contact { font-size: var(--fs-caption); line-height: 1.75; color: #C9BFAE; }
/* inline-block + padding so each stacked contact link clears the 24px target floor; they sit in
   divs, not a sentence, so the inline-link exemption does not apply to them. */
.foot-contact a { color: #E09456; display: inline-block; padding: 4px 0; }
/* Sits above the legal block, not inside it — these are navigation, and mixing them into the
   disclosure text would make both harder to read. */
.foot-nav {
  display: flex; flex-wrap: wrap; gap: 10px 26px;
  margin-top: 30px; padding-top: 22px; border-top: 1px solid #38312B;
}
.foot-nav a { color: #C9BFAE; text-decoration: none; font-size: var(--fs-caption); font-weight: 500; padding: 4px 0; }
.foot-nav a:hover { color: #EDE4D3; }

.foot-legal {
  margin-top: 30px; padding-top: var(--sp-6); border-top: 1px solid #38312B;
  font-family: var(--mono); font-size: 11px; line-height: 1.7; letter-spacing: .04em; color: #C9BFAE;
}
.foot-verbiage { font-size: 11.5px; line-height: 1.75; color: #C9BFAE; margin-top: var(--sp-1); }
.foot-legal.dim { border-top: 0; padding-top: 0; margin-top: var(--sp-1); color: #A79A86; letter-spacing: 0; }

/* ---------- sticky branch CTA ----------
   On every page but /apply/, at every width. Two paths, because they are different products:
   investment goes to a no-credit-pull scenario form, owner-occupied goes to a real 1003.
   `body { padding-bottom }` below reserves the space so the bar can never cover the footer
   disclosures — a fixed bar sitting on top of the required NMLS / Equal Housing text is worse
   than having no bar at all. */
.sticky-cta {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  background: #7C8B6F;
  border-top: 1px solid #6E7A63;
  box-shadow: 0 -6px 24px rgba(14, 22, 32, .28);
}
.sticky-cta .wrap { display: flex; align-items: center; gap: 12px 20px; padding-top: 10px; padding-bottom: 10px; }
.sticky-q { font-size: var(--fs-caption); font-weight: 700; color: #1C1815; flex: none; }
.sticky-opts { margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; }
.sticky-cta .btn {
  display: block; text-align: left; padding: 8px var(--sp-3); font-size: var(--fs-sm); line-height: 1.25;
  background: #1C1815; color: #EDE4D3;
}
.sticky-cta .btn-alt { background: none; color: #1C1815; border: 1px solid #8A5A2B; }
.sticky-cta .btn-alt:hover { border-color: #1C1815; background: #1C1815; color: #EDE4D3; }
.sticky-sub { display: block; font-size: 11px; font-weight: 500; opacity: .8; margin-top: 1px; }
.sticky-note { flex: none; margin-left: 12px; }
/* Reserve the bar's height at every width so it can never sit on top of the footer
   disclosures. Two values because the bar restacks to two rows under 780px. */
body { padding-bottom: 84px; }
/* 104, not 86. The bar wraps to two rows below 780px and measures 99px at 375px, so the old
   reserve left 13px of footer disclosure sitting underneath it — the exact thing this padding
   exists to prevent. Measured in a browser rather than derived from the padding values. */
@media (max-width: 780px) { body { padding-bottom: 104px; } }

@media (max-width: 780px) {
  .sticky-cta .wrap { flex-direction: column; align-items: stretch; gap: 8px; padding-top: 9px; padding-bottom: 9px; }
  .sticky-q { display: none; }             /* the two labels already say it */
  .sticky-opts { margin-left: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .sticky-cta .btn { text-align: center; padding: 9px 10px; font-size: var(--fs-caption); }
  .sticky-sub { font-size: var(--fs-label); }
  .sticky-note { display: none; }
}

/* 900, not its own 1000: .sec-split was the only consumer left once the program rail was
   deleted, and it collapses happily at the same width the mega-menu and the program rail do. */
@media (max-width: 900px) {
  /* All the asymmetric two-column shapes collapse to one column together, so the page keeps
     a single breakpoint story rather than three staggered ones. */
  .sec-split { grid-template-columns: 1fr; gap: 30px; }
  .sec-split-head.is-sticky { position: static; }
  .bleed-copy, .bleed-form { padding: 44px var(--sp-4); }
  .bleed-copy-inner { margin-left: 0; }
}


/* ---------- program spec rail (variant D) ----------
   B's spec rail on C's palette: the rail inverts to dark on a light page, which is what makes it
   read as a spec block rather than a sidebar. It carries the four facts a visitor arrives needing —
   scope, best if, not for, first step — and the jump nav below them.

   THE SCOPE LINE AND THE STATE EXCLUSION LIVE IN HERE, so this rail must never be display:none.
   The old .rail was hidden outright under 900px, which was survivable when it held nothing but
   section links; hiding a licence footprint and "Not available in NY" from every phone is not.
   It goes static and full-width instead — see the media query further down.

   Colours are the documented dark-ground set from site/palette.js (charcoal ground, #A79A86 small
   caps, #EDE4D3 body, #7C8B6F sage, #E09456 copper). #F08A80 is the dark-ground warn already used
   by .band-dark; the light-ground --warn is #8E1F1F and would be invisible here. */
.rail-spec {
  flex: 0 0 300px; position: sticky; top: 24px; display: block;
  background: #1C1815; color: #EDE4D3; overflow: hidden;
}
.spec { margin: 0; padding: 18px 20px; }
.spec dt {
  font: 700 9.5px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
  color: #A79A86; margin: 0 0 5px;
}
.spec dd { margin: 0 0 15px; font-size: var(--fs-sm); line-height: 1.55; color: #EDE4D3; }
.spec dd:last-of-type { margin-bottom: 0; }
.spec-yes { color: #7C8B6F; }
.spec-no { color: #F08A80; }
.spec-excl { display: block; margin-top: 7px; color: #F08A80; font-weight: 600; }
.rail-spec .jump {
  border-left: 0; border-top: 1px solid rgba(237, 228, 211, .18); padding: 12px 20px;
}
.rail-spec .jump a { color: #C9BFAE; }
.rail-spec .jump a:hover { color: #E09456; }
.spec-go {
  display: block; padding: 15px; text-align: center; background: #E09456; color: #1C1815;
  font-size: var(--fs-caption); font-weight: 700; letter-spacing: .05em; text-decoration: none;
}
.spec-go:hover { background: #EDE4D3; }
.rail-spec .rail-call { display: block; padding: 11px; text-align: center; color: #A79A86; }
.rail-spec .rail-call:hover { color: #E09456; }

/* The program hero is a dark band now, so its own type has to be light. The eyebrow is set
   explicitly rather than inheriting .band-dark's --accent, which is the last gold on the site. */
.prog-hero .eyebrow { color: #E09456; }
.prog-hero-sub {
  margin: var(--sp-4) 0 0; max-width: 58ch;
  font-size: var(--fs-md); line-height: 1.62; color: #C9BFAE;
}

@media (max-width: 900px) {
  .rail { display: none; }
  /* NOT hidden: it carries the scope line and the state exclusion. Stacked above the body. */
  .rail-spec { display: block; position: static; flex-basis: auto; margin-bottom: 30px; }
  .prog-body { display: block; }
}

/* 780 rather than its own 760: that value is measured against the footer disclosure overlap
   (see the sticky-cta block above) and must not move, so the table joins it. Merging upward
   restacks the table 20px earlier, which gives it more room rather than less. */
@media (max-width: 780px) {
  .compare, .compare tbody, .compare tr, .compare tbody th, .compare td { display: block; width: auto; }
  .compare thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .compare tbody tr {
    border: 1px solid var(--line); border-radius: var(--r-md);
    background: var(--surface); padding: var(--sp-2) var(--sp-3); margin-bottom: 12px;
  }
  .compare tbody th, .compare td { border: 0; padding: 6px 0; }
  .compare tbody th { width: auto; font-size: var(--fs-base); }
  .compare td::before {
    content: attr(data-label); display: block;
    font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em;
    color: var(--muted); margin-bottom: 3px;
  }
  .compare tbody tr:hover td, .compare tbody tr:hover th { background: none; }
  /* The tier row is a heading, not a card — undo the card chrome the restack applies. */
  .compare tbody.tier-group tr.tier-row { border: 0; background: none; padding: 26px 0 2px; margin: 0; border-radius: 0; }
  .compare .tier-row th { padding: 0 0 8px; border-bottom: 2px solid var(--accent); }
}

@media (max-width: 640px) {
  .wrap { padding-left: 20px; padding-right: 20px; }
  .band > section.wrap { padding-top: 44px; padding-bottom: 44px; }
  .hero { gap: 34px; }
  .prog-h2 { margin-top: 40px; font-size: var(--fs-2xl); }
  header .wrap { gap: 12px; }
  /* The desktop nav and its CTA are already display:none from 900px down, so the rules that used
     to shrink them here (nav gap, navlink size, and hiding header .btn) had nothing left to act
     on. The CTA now lives at the foot of the mobile panel instead of being dropped. */
}

/* ---------- motion ----------
   Zero JavaScript. Scroll reveals use CSS scroll-driven animations behind @supports, so a
   browser without them simply shows the content — the content is never hidden by default,
   which is the failure mode of every JS reveal library when the script doesn't run.

   CRITICAL: reveals animate `transform`, hovers animate `translate`. They are different
   properties, so they compose instead of fighting. Animating `transform` here would outrank
   a `transform` hover declaration and silently kill every card lift on the site — that
   exact bug shipped in v1. Do not "tidy" these onto the same property.

   Motion is deliberately restrained: no TEXT animates, and nothing moves near the footer
   disclosures. This is a mortgage advertisement — required disclosures have to read as
   clear and conspicuous, not as something that moved. The one looping animation on the
   site is the ambient hero background, which sits behind the fold's headline and well
   away from any disclosure. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* MOVEMENT ONLY — deliberately no opacity.
       A fade-in reveal means the resting state is invisible, so anything that stops the
       timeline advancing (a compositor that isn't producing frames, a bug, an unusual
       embedding) leaves real copy blank while the HTML, the link checker and a
       top-of-page screenshot all still look correct. Verified: with an opacity keyframe,
       26 of 35 revealed elements measured under 0.9 opacity after a full scroll in
       headless Chrome. Translating only, the worst case is content sitting 18px low —
       visible, readable, and legally still disclosed. Do not add opacity back. */
    @keyframes rise {
      from { transform: translateY(18px); }
      to   { transform: none; }
    }
    .prog-card, .reasons li, .track li, .facts > div, .panel {
      animation: rise .5s var(--spring-in) both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }
    /* The hero is above the fold: no scroll range exists to drive it, so it plays on load. */
    /* .hero-main is the inner-page hero; .lane-inv/.lane-con are the homepage's two. Both listed,
       or the homepage hero simply stops animating when the markup changes under it. */
    .hero-main > *, .lane-inv > *, .lane-con { animation: rise .6s var(--spring-in) both; animation-timeline: auto; }
    .hero-main > *:nth-child(2), .lane-inv > *:nth-child(2) { animation-delay: .05s; }
    .hero-main > *:nth-child(3), .lane-inv > *:nth-child(3) { animation-delay: .1s; }
    .hero-main > *:nth-child(4), .lane-inv > *:nth-child(4) { animation-delay: .15s; }
    .lane-con { animation-delay: .2s; }
  }
}

/* Spring on the things you actually touch. */
a.prog-card { transition: border-color .18s var(--ease), translate .28s var(--spring), box-shadow .28s var(--spring); }
a.prog-card:hover { translate: 0 -3px; box-shadow: var(--e-4); }
.btn { transition: translate .26s var(--spring), box-shadow .26s var(--spring); }
.btn:hover { translate: 0 -2px; box-shadow: var(--e-4); }
.jump a { transition: translate .22s var(--spring), color .16s var(--ease); }
.jump a:hover { translate: 3px 0; }
.faq summary { transition: color .16s var(--ease); }
.faq summary::after { transition: rotate .26s var(--spring); display: inline-block; }
.faq details[open] summary::after { rotate: 180deg; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ---------- Deal Desk: Tier 1 unlock ---------- */
/* One field. Every extra field on a gate costs completions, and the email is the only thing
   actually wanted here — deliberately no phone, which would drag TCPA consent in for no gain. */
.wrap.narrow { max-width: 560px; }
.unlock-form { display: flex; flex-direction: column; gap: 10px; margin: 0 0 var(--sp-3); }
.unlock-form label { font: 600 13px/1.3 var(--sans); letter-spacing: .02em; color: var(--muted); }
.unlock-form input {
  font: 16px/1.4 var(--sans);          /* 16px min, or iOS zooms the page on focus */
  padding: 12px var(--sp-1);
  border: 1px solid var(--line-field);
  border-radius: var(--r-md);
  background: var(--bg);
  color: var(--ink);
}
.unlock-form input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(27, 78, 122, .18);
}
.unlock-form .btn { align-self: flex-start; }
/* One warn box, two users. .unlock-error and .ts-implied were the same stripe-and-tint shape
   written twice with different padding and no shared name. The other two warn-tinted things on
   the site (.scope-excl, .ts-check.bad) are NOT folded in: they are colour modifiers of
   differently-shaped parents, and reshaping them into a left-stripe box would be a redesign
   rather than a consolidation. They already share the warn tokens, which is the part that
   matters. */
.notice {
  padding: 12px var(--sp-1); border-left: 3px solid var(--warn-line); border-radius: var(--r-sm);
  background: var(--warn-bg); font-size: var(--fs-caption); line-height: 1.5; color: var(--ink);
}
.unlock-error { margin: 0 0 var(--sp-1); }
.ts-implied { margin: var(--sp-2) 0 0; }
/* Locked tools read as locked on the index rather than failing after the click. */
.prog-card .slug { display: inline-block; }

/* ---------- Term-sheet reconciler ---------- */
.ts-intro { max-width: 62ch; color: var(--muted); margin: 0 0 var(--sp-5); }
.ts-cols { display: grid; grid-template-columns: 1.15fr 1fr; gap: 26px; align-items: start; }
.ts-panel { padding: var(--sp-4) var(--sp-4) var(--sp-6); }
.ts-panel h3 { margin: 0 0 4px; font-size: var(--fs-md); }
/* h2, not h4. These three are the form's own sections and had nothing above them, so the page ran
   h1 -> h4 and skipped two levels. The small uppercase treatment is what the design wants them to
   LOOK like; the level is what they are. */
.ts-panel h2 { margin: var(--sp-4) 0 8px; font-size: var(--fs-meta); text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); font-weight: 600; }
.ts-hint { text-transform: none; letter-spacing: 0; font-weight: 400; }
.ts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.ts-grid label { display: flex; flex-direction: column; gap: 4px;
  font-size: var(--fs-meta); color: var(--muted); }
.ts-grid input {
  /* 16px exactly. Not "about 16" — iOS zooms the whole page on focus for anything under 16px,
     and on a form with 24 fields that zoom-and-reflow on every tap is the difference between
     usable and abandoned. Was 15px, which triggered it. */
  font: 16px/1.3 var(--sans);
  padding: 8px 10px; border: 1px solid var(--line-field); border-radius: var(--r-sm);
  background: var(--bg); color: var(--ink); width: 100%;
  font-variant-numeric: tabular-nums;
}
.ts-grid input:focus-visible { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(27, 78, 122, .16); }
.ts-actions { display: flex; gap: 10px; margin: var(--sp-4) 0 0; }

.ts-out { position: sticky; top: 20px; background: #F7F9FB; }
.ts-figs { margin: 0; }
.ts-fig { display: flex; justify-content: space-between; gap: 16px; padding: 5px 0;
  font-size: var(--fs-caption); }
.ts-fig dt { color: var(--muted); }
.ts-fig dd { margin: 0; font-variant-numeric: tabular-nums; }
.ts-split { height: 1px; background: var(--line); margin: 10px 0; }


.ts-checks { display: grid; gap: 8px; }
.ts-check { padding: 10px 12px; border-radius: var(--r-sm); border: 1px solid var(--line);
  display: grid; gap: 2px; font-size: var(--fs-caption); background: var(--bg); }
/* Never colour alone: each state is labelled in words too, so it survives a colour-blind reader
   and a monochrome print. */
.ts-check.bad { border-color: var(--warn-line); background: var(--warn-bg); }
.ts-check-h { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .08em;
  font-weight: 700; color: var(--muted); }
.ts-check.bad .ts-check-h { color: #A6412A; }
.ts-check-l { font-weight: 600; }
.ts-check-v { color: var(--muted); font-variant-numeric: tabular-nums; }
.ts-check-n { color: var(--muted); font-style: italic; margin-top: 2px; }
.ts-empty { color: var(--muted); font-size: var(--fs-caption); margin: 0; }

@media (max-width: 860px) {
  .ts-cols { grid-template-columns: 1fr; }
  .ts-out { position: static; }
}
@media (max-width: 560px) {
  .ts-grid { grid-template-columns: 1fr; }
}

/* Display treatment for the two figures anyone actually asks for. The site's own rule: a large
   serif against small sans is a voice; the same size sans everywhere is a spreadsheet. */
.ts-heroes { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin: 4px 0 var(--sp-5); }
.ts-hero { display: flex; flex-direction: column; gap: 2px; }
.ts-hero-l { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted); font-weight: 600; }
.ts-hero-v { font-family: var(--serif); font-size: clamp(28px, 3.4vw, 38px); font-weight: 600;
  line-height: 1.05; letter-spacing: -.02em; color: var(--accent);
  font-variant-numeric: tabular-nums; }
/* --muted, not --faint. At 12px the AA threshold is 4.5:1 and --faint measures 4.18 on this
   panel; --muted is 6.41. These sub-lines carry the payment split, which is information, not
   decoration. The file header says the lowest measured pairing on this site is 6.33 — --faint at
   small sizes was quietly below the standard the rest of the sheet holds to. */
.ts-hero-s { font-size: var(--fs-meta); color: var(--muted); font-variant-numeric: tabular-nums; }
.ts-dim { color: var(--muted); font-weight: 400; }
.ts-panel .eyebrow { margin-bottom: var(--sp-2); }
.ts-eyebrow-2 { margin-top: var(--sp-6); }
@media (max-width: 560px) { .ts-heroes { grid-template-columns: 1fr; gap: 14px; } }

/* ---------- Deal Desk index ---------- */
/* The public tool gets a band to itself. It is the only page here that can be found from a search,
   so it carries the page rather than queueing behind four locked cards. */
.tool-feature {
  display: grid; grid-template-columns: 1.6fr 1fr; gap: 40px; align-items: center;
  text-decoration: none; color: inherit;
  /* Deliberately oversized — this card carries the Deal Desk index. Not snapped to a shared
     padding step. */
  padding: 34px 36px;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.tool-feature:hover { border-color: var(--accent); box-shadow: var(--e-2); }
.tool-feature h2 { margin: 0 0 12px; }
.tool-feature-go {
  display: inline-block; margin-top: var(--sp-4); font-weight: 600; font-size: var(--fs-sm); color: var(--accent);
}
.tool-feature:hover .tool-feature-go { text-decoration: underline; }
/* The proof. Two numbers beat a sentence claiming coverage. */
.tool-feature-stat {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 12px; align-items: baseline;
  border-left: 2px solid var(--accent); padding-left: var(--sp-5);
}
.tool-stat-v {
  font-family: var(--serif); font-size: 40px; font-weight: 600; line-height: 1;
  color: var(--accent); font-variant-numeric: tabular-nums;
}
.tool-stat-l { font-size: var(--fs-caption); color: var(--muted); }
.tool-stat-note { grid-column: 1 / -1; margin-top: 12px; font-size: var(--fs-meta); color: var(--muted); }

/* Cards carry the lock state at the TOP, where it is read before the title rather than after the
   pitch — the reader should know it is gated before deciding they want it. */
.tool-grid .prog-card { position: relative; padding-top: var(--sp-5); }
.tool-lock {
  display: inline-block; margin-bottom: 10px;
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .1em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px;
  border: 1px solid var(--line-strong); color: var(--muted);
}
.tool-lock[data-tier='premium'] { border-color: var(--accent); color: var(--accent); }

@media (max-width: 860px) {
  .tool-feature { grid-template-columns: 1fr; gap: 28px; padding: 26px var(--sp-6); }
  .tool-feature-stat { border-left: 0; border-top: 2px solid var(--accent); padding: var(--sp-3) 0 0; }
}

/* ---------- FHA tools: give the answer a voice ---------- */
/* These two calculators were re-skinned by remapping their colour tokens, which fixed the palette
   and nothing else. house-hack rendered SEVEN results at an identical 21px, so the two a
   house-hacker actually asks — what it costs me to live there, and what it earns once I move out —
   sat level with intermediate workings like back-end DTI. The reader had to do the ranking.
   Promoting them costs no markup change: each result carries a stable id.
   ID selectors are required, not stylistic. The tools' own <style> is inlined in the page BELOW
   the stylesheet link, so it wins any equal-specificity contest. */
#o_nethousing, #o_cashflow {
  font-family: var(--serif) !important;
  font-size: clamp(30px, 3.4vw, 40px) !important;
  font-weight: 600; line-height: 1.02; letter-spacing: -.02em;
  color: var(--accent) !important;
}
.tool-body .stats .stat:has(#o_nethousing),
.tool-body .stats .stat:has(#o_cashflow) {
  background: var(--surface); border-radius: var(--r-md); padding: var(--sp-2) var(--sp-3);
  border-left: 2px solid var(--accent);
}
/* The workings stay legible but stop competing. */
.tool-body .stats .stat .v { font-size: var(--fs-lg); }

/* The comparison tool already leads with one number; it was just set in the UI face at 30px,
   which reads as a big label rather than a headline. */
.tool-body .big .n {
  font-family: var(--serif); font-size: clamp(32px, 4vw, 46px);
  font-weight: 600; line-height: 1.05; letter-spacing: -.02em; color: var(--accent);
}

/* Provenance lifted out of each tool's own <footer>. Those footers said "Internal reference —
   not client-facing as-is" on pages that are now published, and set it at ~3.2:1 on a dark ground.
   The substance is worth keeping; the framing and the contrast were not. */
.tool-related {
  margin: 26px 0 0; font-size: var(--fs-caption); line-height: 1.6; color: var(--muted);
}
.tool-related strong { color: var(--ink); }
.tool-related a { color: var(--accent); }

.tool-sources {
  margin: 26px 0 0; max-width: 74ch;
  font-size: var(--fs-caption); line-height: 1.6; color: var(--muted);
  padding-left: var(--sp-1); border-left: 2px solid var(--line);
}
.tool-sources strong { color: var(--ink); }

/* The DSCR finder's table header is the tool's own navy, and .rentline asks for --muted. That
   token resolves to the SITE's muted, which is a dark grey for use on white — 1.28:1 here, i.e.
   invisible. The re-skin is right in general and wrong on a dark ground, so the dark ground states
   its own colour. Specificity beats the tool's inlined <style>, which loads after this file. */
.tool-body thead .rentline,
.tool-body th .rentline { color: rgba(255, 255, 255, .72); }

/* house-hack's own hardcoded colours, pre-existing from when it was a file on a drive. All three
   measured below AA once it became a published page. Fixed here rather than in the tool source so
   the standalone copy keeps working unchanged. */
/* White on darkgoldenrod measured 3.25:1. Darkened the ground rather than the text, which would
   have cost the tag its contrast against the lane it labels. Lane B's green already passes at
   5.3:1 and is left alone, so the two lanes stay visually distinct.
   Selector needs the leading `main` and it is not decoration. scopeCss() prefixes the tool's OWN
   rules with .tool-body as well, so its `.lane.a .lane-tag` becomes exactly what mine was — a
   four-class tie, decided by document order, and the tool's <style> is inlined after this file.
   The type selector breaks the tie. Same trap will catch the next override written here. */
main .tool-body .lane.a .lane-tag { background: #7A5A0A; }
/* The step arrows were #B8C2D0 on white — 1.8:1. They are decorative (order already conveys the
   sequence) but they are still text nodes, so they get a colour that passes rather than an
   exemption argument. */
.tool-body .steps .arrow { color: var(--muted); }
/* 4.46:1, which fails by 0.04. Not worth arguing about at 11px. */
.tool-body .badge.b-green { color: #1A6B43; }

/* ---------- DSCR wizard ---------- */
.wiz-rail { display: flex; flex-wrap: wrap; gap: 6px 18px; list-style: none; margin: 0 0 28px; padding: 0; }
.wiz-rail-i { display: flex; align-items: center; gap: 7px; font-size: var(--fs-meta); color: var(--muted); }
.wiz-rail-n {
  display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid var(--line-strong); font-size: var(--fs-label); font-weight: 600;
}
/* State is carried by more than colour — the current step is also bolded and the done ones take a
   tick — so it survives a colour-blind reader and a monochrome print. */
.wiz-rail-i[data-state='now'] { color: var(--ink); font-weight: 600; }
.wiz-rail-i[data-state='now'] .wiz-rail-n { border-color: var(--accent); background: var(--accent); color: #fff; }
.wiz-rail-i[data-state='done'] .wiz-rail-n { border-color: var(--accent); color: var(--accent); }
.wiz-rail-i[data-state='done'] .wiz-rail-n::after { content: '\2713'; }
.wiz-rail-i[data-state='done'] .wiz-rail-n { font-size: 0; }
.wiz-rail-i[data-state='done'] .wiz-rail-n::after { font-size: var(--fs-label); }

.wiz-cols { display: grid; grid-template-columns: 1.25fr 1fr; gap: 34px; align-items: start; }
.wiz-step h2 { margin: 0 0 8px; font-size: clamp(22px, 2.6vw, 30px); }
.wiz-step .sec-lead { margin-bottom: var(--sp-3); }
.wiz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
.wiz-grid label, .wiz-unit label { display: flex; flex-direction: column; gap: 4px;
  font-size: var(--fs-meta); color: var(--muted); }
.wiz-grid input, .wiz-grid select, .wiz-unit input {
  font: 16px/1.3 var(--sans);   /* 16px exactly — under it iOS zooms on focus */
  padding: 9px 11px; border: 1px solid var(--line-field); border-radius: var(--r-sm);
  background: var(--bg); color: var(--ink); width: 100%;
}
.wiz-grid input:focus-visible, .wiz-grid select:focus-visible, .wiz-unit input:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(27, 78, 122, .16);
}

.wiz-choice { display: grid; gap: 10px; }
.wiz-choice label { display: flex; gap: 11px; align-items: flex-start; padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--line-field); border-radius: var(--r-md); cursor: pointer; }
.wiz-choice label:hover { border-color: var(--accent); }
.wiz-choice span { display: flex; flex-direction: column; gap: 2px; }
.wiz-choice em { font-style: normal; font-size: var(--fs-caption); color: var(--muted); }

.wiz-units { display: grid; gap: 10px; }
.wiz-unit { display: grid; grid-template-columns: 62px 1fr 1fr auto; gap: 12px; align-items: end;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-sm); }
.wiz-unit-n { font-size: var(--fs-meta); font-weight: 600; padding-bottom: 9px; }
.wiz-vac { flex-direction: row !important; align-items: center; gap: 6px !important;
  padding-bottom: 9px; white-space: nowrap; }
.wiz-vac input { width: auto; }

.wiz-note { margin: var(--sp-2) 0 0; font-size: var(--fs-caption); line-height: 1.55; color: var(--muted);
  padding-left: 13px; border-left: 2px solid var(--line); max-width: 68ch; }
.wiz-nav { display: flex; gap: 12px; margin-top: 28px; }

/* ---------- The fork: what are you financing? ------------------------------------------------
   Two lanes, equal weight, immediately under the hero. Equal is the point — the page used to answer
   "an investment property" for five bands before admitting the other lane existed. */
.fork-q { text-align: center; margin-bottom: var(--sp-3); }
.fork-lanes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); align-items: start; }
/* A rule between them rather than a box around each: the fork is one question with two answers, and
   two bordered cards would read as two unrelated offers sitting next to each other. */
.fork-lanes > .fork-lane + .fork-lane { border-left: 1px solid var(--line); padding-left: var(--sp-4); }
.fork-lane .sec-h { margin-top: 0; }
.fork-go { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; margin: var(--sp-2) 0 0; }
/* Stacks below 760px. Side by side at phone width the lanes are ~150px wide and the lead wraps to
   seven or eight lines each, which is worse than simply reading one then the other. */
@media (max-width: 760px) {
  .fork-lanes { grid-template-columns: 1fr; gap: var(--sp-3); }
  .fork-lanes > .fork-lane + .fork-lane { border-left: 0; padding-left: 0;
    border-top: 1px solid var(--line); padding-top: var(--sp-3); }
  .fork-q { text-align: left; }
}

/* ---------- Forest hero ----------------------------------------------------------------------
   Written so the page is CORRECT WITHOUT the .forest-on class and the walk layers on top of it.
   hero-forest.js adds that class only after WebGL has initialised and reduced-motion is off.

   Default state — no JS, no WebGL, reduced motion, or a driver blocklist — is the site as it was:
   hero copy in normal flow, the six scenarios as the carousel below it, no canvas. The failure mode
   is a page without a hero animation, never a blank screen with the content trapped in a canvas.
   The inverse (ship the walk, hide it on failure) is how that happens, so it is written this way
   round deliberately. */
.hero-walk { position: relative; }
.hero-stage > canvas, .hero-scrim, .hero-skip { display: none; }
.hero-fallback { display: block; }
/* Belt and braces. hero-forest.js never injects the station cards unless WebGL initialised, so in a
   real fallback these do not exist — but if the class is ever removed at runtime, or the stylesheet
   lands before the module and something goes wrong between, unstyled cards would drop into normal
   flow and print the six scenarios a second time under the carousel. Hidden unless the walk is on. */
.card { display: none; }
html.forest-on .card { display: block; }

/* ---------- the two lanes ----------
   Both geography eyebrows are server-rendered and CSS picks which shows, because the consumer one
   names the states Tony may originate in (12 CFR 1008.103) and must stay where the build's
   stale-licence guard can read it. Never rebuild these strings in JS.

   With NO lane chosen — which includes every no-JS, no-WebGL and reduced-motion visitor, since the
   gate is wired by hero-forest.js — BOTH lines show. That pairing has to be honest on its own: the
   carousel underneath lists both lanes' scenarios, so showing only the business-purpose
   "nearly every state" line there would sit a nearly-every-state claim directly above six
   owner-occupied cards. */
.lane-only { display: block; }
html[data-lane="investor"] .lane-only[data-lane="home"],
html[data-lane="home"] .lane-only[data-lane="investor"] { display: none; }

/* The gate only appears where it can work — hero-forest.js attaches the listener, and it bails
   before that on no-WebGL or reduced motion. Rendered always so the markup is one shape; shown
   only when the walk is live and undecided. */
.lane-gate, .lane-switch { display: none; }
html.forest-on.lane-pending .lane-gate { display: block; margin-top: var(--sp-3); }
/* .hero-overlay is pointer-events:none so the page scrolls through the walk — anything inside it
   that a visitor must actually press has to opt back in, which is why .cta-alt and .hero-skip do.
   The gate is the one control the walk cannot proceed without, so missing this made it dead to a
   real mouse while still responding to a scripted .click(). */
html.forest-on.lane-pending .lane-gate,
html.forest-on.lane-pending .lane-gate .btn { pointer-events: auto; }
/* The hero copy fades as the walk moves past it. While the gate is up there is nothing to move on
   to, so fading it out would leave a visitor scrolling an empty forest with no way to choose. */
html.forest-on.lane-pending .hero-walk[data-past="1"] .hero2 { opacity: 1; translate: none; }
/* .hero2 is absolutely positioned at top:15vh, so it does not push the stage taller — it just runs
   off the bottom of it. Two eyebrows, the h1, the lead, the gate AND the secondary links measured
   669px against a 720px viewport, putting the gate buttons at y=755: off-screen, on a laptop, on
   the one control the walk cannot start without.
   While gated the gate IS the call to action, so the secondary links stand down and the block
   sits higher. Both come back the moment a lane is chosen. */
html.forest-on.lane-pending .hero-overlay .hero2 { top: 8vh; }
html.forest-on.lane-pending .hero-overlay .cta-alts { display: none; }
/* Two buttons need 404px in a 44ch column that offers 371, so they wrapped and put the second one
   below the fold. Share the row and wrap the label instead of the row. */
.lane-gate-opts > .btn { flex: 1 1 0; min-width: 0; text-align: center; }
/* The sticky bar asks the IDENTICAL question ("What are you financing?"), is fixed at z-index 40
   across the bottom 72px, and therefore rendered on top of the gate it duplicates — the gate was
   unclickable because of this, not only because of pointer-events. Its homepage hide was removed
   deliberately when the hero stopped routing lanes; the hero routes lanes again, so it stands down
   for exactly as long as the gate is up and returns as the router for the rest of the page. */
html.forest-on.lane-pending .sticky-cta.sticky-home { display: none; }

/* ONCE A LANE IS CHOSEN, on the homepage only. Measured in the fold at 1280x720: six CTAs and the
   licensed-states list printed twice, one copy here and one in the hero eyebrow two lines above —
   while the bar asked "What are you financing?" of someone who had just answered it.
   So the question goes and the bar keeps only the lane's own CTA. Every rule is scoped to
   .sticky-home, which is emitted on the homepage alone; the other 47 pages keep the full router,
   because there the bar is the ONLY lane choice on the page. */
html[data-lane] .sticky-home .sticky-q { display: none; }
html[data-lane="investor"] .sticky-home [data-lane-cta="home"],
html[data-lane="home"] .sticky-home [data-lane-cta="investor"] { display: none; }
/* One button left, so it should not sit in a layout built for two. Desktop is flex (:1079), the
   mobile breakpoint switches it to a 1fr 1fr grid (:1099) — left alone that strands the surviving
   button at half width beside an empty column. */
html[data-lane] .sticky-home .sticky-opts { justify-content: flex-start; grid-template-columns: 1fr; }
/* SHORT VIEWPORTS. The gated block does not shrink with the window: two eyebrows, the h1, the lead
   and the gate measured 568px, and the overlay already starts 85px down to clear the header. On a
   600px-tall laptop that puts the buttons at y=637 — off-screen again, with no top offset able to
   recover it, because 85 + 568 already exceeds 600. So content has to go, not position.
   The lead is what goes: the h1 names both audiences on its own and the gate is the only action.
   It returns as soon as a lane is picked, and on any viewport tall enough to hold it. */
@media (max-height: 760px) {
  html.forest-on.lane-pending .hero-overlay .hero2 { top: 3vh; }
  html.forest-on.lane-pending .hero-overlay .lead { display: none; }
}
/* Phone in landscape, ~375-500px of height. With the lead already gone the h1 is the only thing
   left big enough to cut — measured 45px of overflow at 500px tall, which is the gate hanging off
   the bottom again. Shrinking the headline is the last thing to try and the right one: the gate
   has to be reachable, and a smaller h1 still reads. */
@media (max-height: 560px) {
  html.forest-on.lane-pending .hero-overlay h1 { font-size: clamp(20px, 2.2vw, 26px); }
  html.forest-on.lane-pending .hero-overlay .eyebrow { margin-bottom: 2px; }
}
.lane-gate-q {
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 10px;
}
.lane-gate-opts { display: flex; flex-wrap: wrap; gap: 12px; }
/* The cards are empty until a lane fills them, so nothing may light while gated. showStation()
   already refuses, this is the belt-and-braces half. */
html.lane-pending .card { display: none; }

/* TOP right, not bottom. The bottom 72px of every page belongs to the fixed sticky bar at z-index
   40, which is where .hero-skip also sits — it gets away with it only by being invisible until
   focused. 97px clears the 85px sticky header above. */
html.forest-on:not(.lane-pending) .lane-switch {
  display: flex; align-items: center; gap: 10px;
  position: absolute; right: 6vw; top: 97px; pointer-events: auto; z-index: 41;
  font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink); background: rgba(255,255,255,.96);
  border: 1px solid var(--accent); border-radius: var(--r-sm); padding: 10px 12px; cursor: pointer;
  box-shadow: 0 2px 10px rgba(23,32,43,.10);
}
/* The two lane names sit inside a line of text, so they must not be block like the eyebrows are.
   The hide rule that drives them is more specific than this, so it still wins. */
.lane-switch .lane-only { display: inline; }
.lane-switch-now { color: var(--muted); font-weight: 500; }
.lane-switch-go { color: var(--accent); border-left: 1px solid var(--line); padding-left: 10px; }
/* On a phone the control spans almost the full width, so it stops being a corner badge and lands
   ON the eyebrow — which is the licensed-states line, the one thing on the hero that may not be
   obscured. The copy starts below it instead. */
@media (max-width: 760px) {
  html.forest-on:not(.lane-pending) .hero-overlay .hero2 { top: 146px; }
}
html.forest-on:not(.lane-pending) .lane-switch:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

html.forest-on .hero-walk { height: 600vh; }
html.forest-on .hero-stage { position: sticky; top: 0; height: 100vh; overflow: hidden; }
html.forest-on .hero-stage > canvas { display: block; width: 100%; height: 100%; }
/* Text ground. Over the photographic backdrop the hero copy lands on the picture's dark trunks —
   measured at 1.14:1 before this existed, against a 3:1 floor. Contrast cannot depend on what the
   photograph happens to be doing behind a letter, which is why every image on this site sits under
   a scrim. Ivory rather than navy because v3 is a light scheme. */
html.forest-on .hero-scrim {
  display: block; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(101deg, rgba(247,241,230,1) 0%, rgba(247,241,230,1) 45%,
    rgba(247,241,230,.55) 57%, rgba(247,241,230,0) 71%);
  transition: opacity .4s ease;
}
html.forest-on .hero-walk[data-past="1"] .hero-scrim { opacity: 0; }
/* Narrow viewports put the copy across ~78% of the width rather than ~26%, so a left-to-right wash
   would not cover it. Vertical instead: solid where the text is, clearing below. */
@media (max-width: 760px) {
  html.forest-on .hero-scrim {
    background: linear-gradient(180deg, rgba(247,241,230,1) 0%, rgba(247,241,230,1) 48%,
      rgba(247,241,230,.5) 64%, rgba(247,241,230,0) 78%);
  }
}
html.forest-on .hero-overlay { position: absolute; inset: 0; pointer-events: none; }
html.forest-on .hero-overlay .hero2 { position: absolute; left: 6vw; top: 15vh; max-width: 44ch;
  transition: opacity .4s ease, translate .4s ease; }
html.forest-on .hero-overlay .cta-alt, html.forest-on .hero-overlay .scene a { pointer-events: auto; }
html.forest-on .hero-walk[data-past="1"] .hero2 { opacity: 0; translate: 0 -20px; }
/* The walk replaces the carousel; showing both would be the same six scenarios twice. */
html.forest-on .hero-fallback { display: none; }

/* A scenario card, PINNED. The forest walks past it; the card itself never moves while it is being
   read. Placement is static CSS — the frame loop sets one attribute and never touches layout, so
   there is no per-frame reflow and no link target sliding out from under the cursor (WCAG 2.2.2). */
html.forest-on .card {
  position: absolute; width: min(300px, 72vw); bottom: 14vh;
  background: rgba(255,255,255,.94); border: 1px solid #C9A98F; border-radius: var(--r-sm);
  padding: 14px 16px; box-shadow: 0 10px 30px rgba(31,20,10,.13);
  opacity: 0; translate: 0 12px; visibility: hidden;
  transition: opacity .3s ease, translate .3s ease, visibility .3s;
  pointer-events: auto;
}
html.forest-on .card[data-side="-1"] { left: 6vw; }
html.forest-on .card[data-side="1"] { right: 6vw; }
html.forest-on .card[data-on="1"] { opacity: 1; translate: 0 0; visibility: visible; }
/* currentColor, so the artwork takes the accent from the card rather than baking a hex into the
   path data — the copper moves once, in the palette, and every figure follows. Sits on its own
   tinted ground so it reads as a plate rather than loose line-work floating on the card. */
html.forest-on .card .card-fig {
  display: block; width: 100%; height: auto; margin: -2px 0 10px;
  color: var(--accent); background: rgba(159,85,39,.055);
  border: 1px solid rgba(159,85,39,.14); border-radius: var(--r-sm);
}
html.forest-on .card .n { font-size: var(--fs-label); font-weight: 600; letter-spacing: .1em;
  color: var(--accent); font-family: var(--mono); }
html.forest-on .card h2 { font-family: var(--serif); font-size: 19px; line-height: 1.2;
  margin: 7px 0 5px; letter-spacing: -.01em; }
html.forest-on .card p { font-size: var(--fs-caption); line-height: 1.5; color: var(--muted); margin: 0 0 10px; }
/* Per-program state exclusion. Same --warn treatment as .cat-x elsewhere, so a restriction reads
   the same wherever it appears rather than looking like ordinary body copy on this one surface. */
html.forest-on .card .card-x { color: var(--warn); font-weight: 600; font-size: var(--fs-caption); margin: -4px 0 10px; }
html.forest-on .card a { font-size: var(--fs-caption); font-weight: 600; color: var(--accent); text-decoration: none; }
html.forest-on .card a:hover { text-decoration: underline; }
/* Below 640px the copy and a card cannot both fit, so the copy stands down once a card is live. */
@media (max-height: 640px) {
  html.forest-on .hero-walk[data-card="1"] .hero2 { opacity: 0; translate: 0 -20px; }
}

/* The walk hijacks 600vh of scroll. prefers-reduced-motion is honoured, but a visitor who simply
   does not want it needs an escape that is not "keep scrolling". Visible on keyboard focus, which
   is the standard skip-link pattern. */
html.forest-on .hero-skip {
  display: block; position: absolute; left: 6vw; bottom: 26px; pointer-events: auto;
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; color: var(--accent); background: rgba(255,255,255,.92);
  border: 1px solid var(--accent); border-radius: var(--r-sm); padding: 8px 10px;
  text-decoration: none; opacity: 0; transform: translateY(6px);
  transition: opacity .2s, transform .2s;
}
html.forest-on .hero-skip:focus-visible { opacity: 1; transform: none;
  outline: 2px solid var(--accent); outline-offset: 2px; }

/* Keeps its own literal ground: this panel sits on a light band where var(--surface) would be
   too close to the page to read as a panel at all. */
.wiz-out { position: sticky; top: 20px; background: #F4EEE1; padding: var(--sp-4) var(--sp-4) var(--sp-6); }
.wiz-hero { display: flex; flex-direction: column; gap: 2px; }
.wiz-hero-l { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted); font-weight: 600; }
.wiz-hero-v { font-family: var(--serif); font-size: clamp(32px, 4vw, 44px); font-weight: 600;
  line-height: 1.02; letter-spacing: -.02em; color: var(--accent);
  font-variant-numeric: tabular-nums; }
.wiz-hero-s { font-size: var(--fs-meta); color: var(--muted); font-variant-numeric: tabular-nums; }
/* Which constraint binds is the finding, so it gets a panel rather than a line in a list. */
.wiz-binding:not(:empty) { margin: var(--sp-2) 0 4px; padding: 12px var(--sp-1); font-size: var(--fs-caption);
  line-height: 1.55; background: var(--bg); border-left: 2px solid var(--accent);
  border-radius: var(--r-sm); }
.wiz-figs { margin: var(--sp-1) 0 0; }
.wiz-fig { display: flex; justify-content: space-between; gap: 14px; padding: 4px 0; font-size: var(--fs-caption); }
.wiz-fig dt { color: var(--muted); }
.wiz-fig dd { margin: 0; font-variant-numeric: tabular-nums; }

/* The qualifying payment as a worksheet, amortising against interest-only.
   A GRID rather than two stacked lists, so the two columns share one set of row labels and the
   figures line up horizontally — comparing them is the entire reason both are shown, and two
   independent lists make the reader do that alignment in their head. */
.wiz-cols { display: grid; grid-template-columns: 1fr auto auto; gap: 3px 14px;
  margin: var(--sp-1) 0 0; font-size: var(--fs-caption); align-items: baseline; }
.wiz-col-h { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 600; text-align: right; padding-bottom: 4px; }
.wiz-row-l { color: var(--muted); }
.wiz-row-v { text-align: right; font-variant-numeric: tabular-nums; }
/* The two summed rows carry the answer, so they stop looking like more inputs. */
.wiz-row-l.is-sum, .wiz-row-v.is-sum { color: var(--ink); font-weight: 600;
  border-top: 1px solid var(--line); padding-top: 5px; margin-top: 2px; }
/* Sits under the hero figures, so a deal that is too small to place says so before the reader
   takes it to a lender rather than after. */
.wiz-thin { margin: var(--sp-2) 0 0; padding: 10px var(--sp-1); font-size: var(--fs-meta);
  line-height: 1.5; background: var(--bg); border-left: 2px solid var(--line);
  border-radius: var(--r-sm); color: var(--muted); }
.wiz-note-inline { font-size: var(--fs-meta); line-height: 1.5; color: var(--muted);
  margin: 0; grid-column: 1 / -1; }
.wiz-bases { margin-top: var(--sp-3); }
.wiz-bases-h { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 600; margin: 0 0 8px; }
.wiz-basis { display: flex; justify-content: space-between; gap: 12px; padding: 6px 9px;
  font-size: var(--fs-caption); border-radius: var(--r-sm); font-variant-numeric: tabular-nums; }
.wiz-basis.is-on { background: var(--bg); font-weight: 600; }
.wiz-empty { font-size: var(--fs-caption); color: var(--muted); margin: 0; }

@media (max-width: 900px) {
  .wiz-cols { grid-template-columns: 1fr; }
  .wiz-out { position: static; }
}
@media (max-width: 560px) {
  .wiz-grid { grid-template-columns: 1fr; }
  .wiz-unit { grid-template-columns: 1fr 1fr; }
  .wiz-unit-n { grid-column: 1 / -1; padding-bottom: 0; }
}

/* A disabled in-place field on a vacant unit. Muted enough to read as unavailable, but the
   placeholder still has to clear 4.5:1 — "you cannot type here" is information, not decoration,
   and --faint would land under the threshold at this size. */
.wiz-unit input:disabled {
  background: var(--surface); color: var(--muted); cursor: not-allowed;
  border-style: dashed;
}
.wiz-unit input:disabled::placeholder { color: var(--muted); opacity: 1; }

/* ---------- hero badge row ----------
   Boomerang's location chips, doing the job two stacked prose eyebrows used to do in a third of the
   hero's words. Both footprints are labelled and shown together rather than swapped by lane, so
   neither can appear beside the wrong set of cards.
   The states come from config.licensedStates via the build, never typed here. */
.hero-badges {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0 0 var(--sp-3);
}
.badge-label {
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; color: var(--muted); margin-right: 2px;
}
/* A little air before the second group so the two footprints read as two facts, not one list. */
.badge-label-2 { margin-left: 10px; }
.chip {
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 700; letter-spacing: .06em;
  color: var(--ink); background: rgba(255,255,255,.86); border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); padding: 3px 7px; white-space: nowrap;
}
.chip-wide { font-weight: 600; letter-spacing: .04em; text-transform: none; color: var(--muted); }
/* On the walk the chips sit over the scrim, which is ivory — the border alone would disappear. */
html.forest-on .chip { background: rgba(255,255,255,.94); }
@media (max-width: 760px) {
  .badge-label-2 { margin-left: 0; }
  .hero-badges { gap: 5px 6px; }
}
/* Separator between the two lanes and the product row now that they share a band. */
.cat-intro {
  margin: var(--sp-4) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; color: var(--muted);
}

/* ---------- /learn/ hub: filter list ----------
   ShieldKey-inspired, not ShieldKey-copied. Borrowed: pills, a lead card ahead of a grid, a tag
   and a read time on every card, no dates. Left behind: the dark ground, per-category colour tints,
   and the hover veil that hides the dek until you mouse over — on a light, quiet page those read as
   noise, and a dek you must hover to see is one most visitors never see.
   One accent, one border weight, one card. That is the whole system. */
/* The section index. Real navigation rather than a filter: it is a list of links to headings on
   the same page, so it works with scripting off, is crawlable, and shows the shape of the library
   before you scroll into it. The counts come from the same arrays that build the sections and the
   build asserts they add up to the article count. */
.hub-index {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 var(--sp-5);
  padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line);
}
.hub-index a {
  display: inline-flex; align-items: baseline; gap: 7px; padding: 8px 14px;
  border: 1px solid var(--line); font-size: var(--fs-caption); font-weight: 600;
  color: var(--body-c); text-decoration: none; transition: border-color .15s, color .15s;
}
.hub-index a:hover, .hub-index a:focus-visible { border-color: var(--accent); color: var(--accent); }
.hub-index span { font-family: var(--mono); font-size: var(--fs-label); color: var(--muted); }

.hub-sec { margin-bottom: clamp(34px, 5vh, 58px); scroll-margin-top: 96px; }
.hub-sec:last-child { margin-bottom: 0; }
.hub-sec .sec-lead { margin-bottom: var(--sp-3); }
/* A sub-label only appears where a section holds more than one situation, so it is always
   distinguishing something rather than restating the heading above it. */
.hub-sub + .hub-sub { margin-top: var(--sp-4); }
.hub-sub-h {
  margin: 0 0 10px; font-family: var(--mono); font-size: var(--fs-label);
  letter-spacing: .11em; text-transform: uppercase; color: var(--muted);
}

.hub-card {
  display: block; text-decoration: none; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface);
  padding: 18px 20px; transition: border-color .15s var(--ease);
}
.hub-card:hover { border-color: var(--accent); }
.hub-card h3 { font-size: var(--fs-md); line-height: 1.25; margin: 9px 0 7px; }
.hub-card p { font-size: var(--fs-caption); line-height: 1.55; color: var(--muted); margin: 0; }
.hub-meta {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em; text-transform: uppercase;
}
/* SAGE, not accent. This is a category label — 'Myth · Foreign national', 'Phoenix, AZ' —
   and it is not clickable. On the accent it was a non-action wearing the action colour, the
   same mistake the old caution colour made. Sage is the secondary role: present, legible,
   clearly not a call to action. 7.06:1 on white. */
.hub-tag { color: var(--sage); font-weight: 600; }
.hub-read { color: var(--muted); white-space: nowrap; }

/* The lead card is only a lead in the unfiltered view. Inside a filter it is just the first card of
   that group, and .is-plain drops it back to grid size — otherwise one arbitrary article stays
   double-height in every view. */

.hub-grid { display: grid; gap: 14px; grid-template-columns: repeat(3, 1fr); }
.hub-empty { margin: var(--sp-4) 0 0; color: var(--muted); font-size: var(--fs-sm); }
@media (max-width: 980px) { .hub-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .hub-grid { grid-template-columns: 1fr; } }

/* ---------- Claude Design v3 homepage ----------
   The design ships as a canvas export: every element inline-styled, no classes, no stylesheet.
   These rules are its system rebuilt as CSS. Its palette maps onto the tokens already here rather
   than introducing a fourth copper — the design's #C1703A measures 2.95:1 on its own sand ground,
   below even the 3:1 UI floor, so accent stays token-driven and stays legible on both grounds.
   Sand, charcoal and sage are the design's and are already in the brand palette. */

.dh-why-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-4) var(--sp-5); margin-top: var(--sp-4); }
.dh-num {
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 8px;
}
.dh-reason h2 { font-size: clamp(19px, 1.9vw, 23px); line-height: 1.2; margin: 0 0 8px; }
/* :not(.dh-num) — these selectors outrank .dh-num on specificity, so without the exclusion the
   10.5px mono eyebrow silently rendered at 15px body size. */
.dh-reason p:not(.dh-num), .dh-list li { font-size: var(--fs-sm); line-height: 1.6; color: var(--muted); }
/* The two list cards are woven INTO the reason grid rather than stacked after it — that stagger is
   what stops the column rhythm reading as four identical boxes. */
.dh-list { border-left: 2px solid var(--accent); padding-left: var(--sp-3); }
.dh-list ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }

.dh-sig { margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--line); }
.dh-sig .signature { margin-bottom: 6px; }
.dh-sig-id { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin: 0; }

/* TWO LANES, AND THEY DO NOT LOOK ALIKE. The design makes the legal split a visual one by giving
   each lane its own ground. That is the single best idea in it and the reason it beats the two
   identical cards this replaced. */
.dh-lane-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); margin-top: var(--sp-4); }
.dh-lane { padding: var(--sp-4); border-radius: var(--r-sm); }
.dh-lane-biz { background: var(--ink); color: var(--bg); }
.dh-lane-biz .dh-num { color: var(--accent-on-dark, #D6A24A); }
.dh-lane-biz p:not(.dh-num), .dh-lane-biz li { color: #C9BFAE; }
.dh-lane-con { background: #EDE4D3; color: #2B2622; }
.dh-lane-con p:not(.dh-num), .dh-lane-con li { color: #5F574F; }
.dh-lane h3 { font-size: clamp(20px, 2vw, 25px); line-height: 1.2; margin: 0 0 10px; }
.dh-lane > p:not(.dh-num) { font-size: var(--fs-sm); line-height: 1.6; }
.dh-chips { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.dh-chips li { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .05em;
  border: 1px solid currentColor; border-radius: var(--r-sm); padding: 3px 8px; opacity: .75; }
/* The six states, larger than the product chips — they are a licensing statement, not a feature list. */
.dh-states { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.dh-states li {
  font-family: var(--mono); font-size: var(--fs-caption); font-weight: 700; letter-spacing: .06em;
  background: #2B2622; color: #EDE4D3; border-radius: var(--r-sm); padding: 4px 9px;
}
.dh-lane-go { margin: var(--sp-3) 0 0; }

.dh-step-grid { list-style: none; margin: var(--sp-4) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-4); }
.dh-step-grid h3 { font-size: var(--fs-md); line-height: 1.25; margin: 0 0 7px; }
.dh-step-grid p:last-child { font-size: var(--fs-sm); line-height: 1.6; color: var(--muted); margin: 0; }

.dh-who-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); margin-top: var(--sp-4); }
.dh-who-grid ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.dh-who-grid li { font-size: var(--fs-sm); line-height: 1.55; color: var(--muted);
  padding-left: 16px; position: relative; }
.dh-who-grid li::before { content: '—'; position: absolute; left: 0; color: var(--sage); }

.dh-rates-body { max-width: 68ch; margin-top: var(--sp-3); }
.dh-rates-body p { font-size: var(--fs-sm); line-height: 1.65; color: var(--muted); margin: 0 0 var(--sp-2); }
.dh-rates-go { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 20px; margin: var(--sp-4) 0 0; }

@media (max-width: 860px) {
  .dh-why-grid, .dh-lane-grid, .dh-who-grid { grid-template-columns: 1fr; }
  .dh-step-grid { grid-template-columns: 1fr; }
}
/* The design's image slot: a 4:3 frame above each reason card. Filled with generated contour art
   rather than a photo — see the note at the render site in build.js. */
.dh-fig {
  aspect-ratio: 4 / 3; overflow: hidden; border-radius: var(--r-sm); margin-bottom: var(--sp-3);
  background: var(--surface); border: 1px solid var(--line);
}
.dh-fig svg { width: 100%; height: 100%; display: block; object-fit: cover; }
/* The stagger: the second column drops, which is what stops four cards reading as a plain grid. */
@media (min-width: 861px) { .dh-why-grid > .dh-reason:nth-child(even) { margin-top: 54px; } }

/* ---- Route Planner ---------------------------------------------------------------------------
   Every program card ships in the HTML and route-planner.js only hides the non-matching ones, so
   these styles have to look right in three states: nothing picked, a lane open, and JS off (where
   the whole catalogue shows). Tokens only - no literal hexes, or this breaks the moment the
   palette moves. */
.rp-wrap { max-width: 900px; margin: 0 auto; padding: var(--sp-6, 40px) var(--sp-5, 24px) var(--sp-8, 72px); }
.rp-eyebrow { margin: 0 0 10px; font-size: 10px; letter-spacing: .3em; color: var(--accent); font-weight: 600; }
.rp-lead { max-width: 62ch; color: var(--muted); }
.rp-lanes { display: flex; flex-wrap: wrap; gap: 12px; margin: var(--sp-5, 24px) 0 var(--sp-6, 36px); }
.rp-pick {
  flex: 1 1 220px; padding: 16px 20px; font: inherit; font-weight: 600; cursor: pointer;
  background: var(--bg); color: var(--ink); border: 1px solid var(--line-strong); border-radius: var(--r-md);
  transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
.rp-pick:hover { border-color: var(--accent); color: var(--accent); }
.rp-pick[aria-pressed="true"] { background: var(--ink); color: var(--bg); border-color: var(--ink); }

.rp-lane > h2 { margin: 0 0 var(--sp-4, 18px); }
.rp-q { border: 0; border-top: 1px solid var(--line); margin: 0; padding: var(--sp-4, 18px) 0 var(--sp-5, 24px); }
.rp-q legend { padding: 0; font-weight: 600; color: var(--ink); }
.rp-q legend i { font-style: normal; font-weight: 400; color: var(--muted); font-size: .85em; }
.rp-note { margin: 8px 0 12px; font-size: 14px; color: var(--muted); max-width: 60ch; }
.rp-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 8px; margin-top: 12px; }
.rp-opts label {
  display: flex; gap: 10px; align-items: flex-start; padding: 11px 14px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r-md); transition: border-color .15s var(--ease);
}
.rp-opts label:hover { border-color: var(--line-strong); }
.rp-opts input { margin-top: 3px; flex: none; }
.rp-opts input:checked + span { font-weight: 600; }
.rp-state { padding: 11px 14px; font: inherit; border: 1px solid var(--line-field); border-radius: var(--r-md); background: var(--bg); color: var(--ink); }
.rp-referral { margin: var(--sp-4, 18px) 0 0; padding: 14px 16px; border-left: 3px solid var(--accent); background: var(--surface); max-width: 62ch; }

.rp-out { margin-top: var(--sp-6, 36px); border-top: 2px solid var(--ink); padding-top: var(--sp-5, 24px); }
.rp-cards { list-style: none; margin: 0 0 var(--sp-5, 24px); padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 10px; }
/* Result cards need contrast in two directions: from the page they sit on, and from each other.
   Previously they had neither - no fill at all (measured 1.00 against the panel, i.e. invisible as
   a surface) and every card styled identically, so a ranked list read as an unordered one. */
.rp-card a {
  display: block; padding: 14px 16px; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong); border-radius: var(--r-md);
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.rp-card a:hover { background: var(--bg); border-color: var(--line-strong); border-left-color: var(--accent); }
/* The strongest match is the point of ranking, so it is the one card that looks different. */
.rp-card.is-top a { background: var(--bg); border-left-color: var(--accent); box-shadow: var(--e-2); }
/* Two selectors deep, same reason as .rp-why: `.rp-card span` outranks a single class and was
   rendering this as ordinary muted body text. */
.rp-card a .rp-rank { display: block; margin: 0 0 6px; font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); font-weight: 700; }
.rp-card b { display: block; font-size: 16px; line-height: 1.25; color: var(--ink); }
.rp-card span { display: block; margin-top: 5px; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
.rp-none { padding: 14px 16px; border-left: 3px solid var(--accent); background: var(--surface); max-width: 62ch; }
.rp-act { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
.rp-cta { display: inline-block; padding: 14px 22px; background: var(--ink); color: var(--bg); border-radius: var(--r-md); text-decoration: none; font-weight: 600; }
.rp-cta:hover { background: var(--accent); }

/* The skip link. It carries its own tag, so self-selection out of the planner is measurable. */
.rp-skip { margin: 10px 0 0; font-size: 14px; color: var(--muted); }
/* Two selectors deep so it actually beats `.rp-card span`, which was silently overriding this and
   rendering "Matches:" identical to "Best if" - same colour, same size, no hierarchy inside the
   card at all. The hairline is what separates the site's description of the program from the
   reason THIS scenario surfaced it. */
.rp-card a .rp-why {
  display: block; margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--line);
  font-size: 12px; line-height: 1.45; color: var(--accent); font-weight: 500;
}

/* ---- Route Planner: two-column desktop -------------------------------------------------------
   Questions left, results right and sticky, so the list visibly reacts as you tick rather than
   living below the fold. Below 900px it collapses back to one column and the results simply sit
   under the questions - a sticky panel on a phone would eat the screen it is trying to inform. */
@media (min-width: 900px) {
  .rp-wrap { max-width: 1240px; }
  .rp-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(330px, 400px); gap: clamp(28px, 3.5vw, 52px); align-items: start; }
  .rp-out { position: sticky; top: 20px; margin-top: 0; max-height: calc(100vh - 40px); overflow: auto; }
  /* A vertical list, not a carousel. The whole point of ranking is seeing the order, and a
     one-at-a-time control hides exactly that. The panel scrolls inside itself if the list is long. */
  .rp-cards { grid-template-columns: 1fr; }
}
.rp-reset { flex: 0 0 auto; align-self: center; padding: 10px 16px; font: inherit; font-size: 13px; cursor: pointer; background: none; color: var(--muted); border: 1px solid var(--line); border-radius: var(--r-md); }
.rp-reset:hover { color: var(--ink); border-color: var(--line-strong); }
.rp-trim { margin: 0 0 var(--sp-4, 18px); padding: 10px 13px; font-size: 12.5px; line-height: 1.5; color: var(--muted); background: var(--surface); border-radius: var(--r-md); }
/* The label only belongs on the leader. */
.rp-card:not(.is-top) a .rp-rank { display: none; }

/* ---- palette reference (/preview/palette/) --------------------------------------------------- */
.pal { max-width: 1080px; margin: 0 auto; padding: var(--sp-7, 56px) var(--sp-5, 24px) var(--sp-8, 72px); }
.pal-eyebrow { margin: 0 0 10px; font-size: 10px; letter-spacing: .3em; color: var(--accent); font-weight: 600; }
.pal-lead { max-width: 76ch; color: var(--muted); }
.pal h2 { margin: 32px 0 4px; font-size: 17px; }
.pal .gn { margin: 0 0 8px; max-width: 80ch; font-size: 13.5px; color: var(--accent); }
.pal table { border-collapse: collapse; width: 100%; margin-top: 8px; background: var(--bg); }
.pal th, .pal td { border: 1px solid var(--line); padding: 8px 10px; text-align: left; vertical-align: middle; font-size: 13px; }
.pal th { background: var(--surface); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; vertical-align: bottom; }
.pal th code { font-size: 10px; letter-spacing: 0; color: var(--muted); }
.pal td.sw { width: 56px; }
.pal td.sw span { display: block; width: 40px; height: 40px; border-radius: 5px; border: 1px solid var(--line-strong); }
.pal td.n { text-align: right; font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.pal td.n i { display: block; font-style: normal; font-weight: 400; font-size: 9.5px; letter-spacing: .05em; text-transform: uppercase; }
.pal td.n.ok { color: #2F6B45; }
.pal td.n.lg { color: #8E4A20; }
.pal td.n.no { color: #8E1F1F; }
.pal td.note { font-size: 12.5px; color: var(--muted); }
.pal tr.ret td { opacity: .72; }
.pal-key { margin-top: 18px; font-size: 12.5px; color: var(--muted); max-width: 82ch; }

/* --- Article layout (Variant C) ------------------------------------------------------------
   Chosen 2026-08-21 from three mockups at /preview/article-a|b|c/. Skimming-first: a split hero,
   a stepper that is a route through the piece, then alternating media and text.

   IN styles.css AND NOT INLINE because it is 43 pages. Same reasoning as nav.js — one cached
   request beats 43 copies of the same bytes. The mockups keep their own inline copies; they are
   throwaway previews and are allowed to drift.

   MEDIA IS GENERATED, NOT PHOTOGRAPHED. No article declares an image and there is no budget of
   cleared photography to draw on — 43 articles of stock would be 43 new CREDITS rows and a
   licensing surface. (The VERIFY backlog that line used to cite is closed as of 2026-08-23.) The
   hero slot takes ridgeScene() and the in-body slots take contours(), both seeded — the hero on
   the article SLUG, so every article is unique to itself rather than to its program. `.art-media img` is here so a
   real photo can drop into the same slot later without touching this layout. */
/* Articles use heroBand like every other page type, so the split hero this file used to describe
   (.art-hero / .art-hero-t / .art-kick / .art-dek and the .art-media panel beside it) is gone.
   MEDIA IS STILL GENERATED, NOT PHOTOGRAPHED: heroBand seeds ridgeScene() per page, so each
   article gets a hero unique to itself and costs nothing. */
/* Stepper. Derived from the article's own sections so it cannot drift from the headings it
   names, and flex rather than grid because sections run 3 to 6 across the library. The connector
   is per-step so it stops at the last dot instead of trailing off the edge. */
.art-steps{position:relative;display:flex;flex-wrap:wrap;gap:0;background:#FCFAF6;
  border-bottom:1px solid var(--rule,#E2DACE);padding:26px clamp(14px,3vw,28px) 24px}
.art-step{position:relative;flex:1 1 150px;min-width:0;display:flex;flex-direction:column;
  align-items:flex-start;gap:11px;padding:0 16px 0 0;border-bottom:0;text-decoration:none}
.art-step:not(:last-child)::after{content:'';position:absolute;top:17px;left:36px;right:0;
  height:1px;background:var(--rule,#E2DACE)}
.art-dot{position:relative;z-index:1;display:grid;place-items:center;width:36px;height:36px;
  border-radius:50%;background:#fff;border:1px solid var(--rule,#E2DACE);
  font:700 11px/1 'Space Grotesk',system-ui,sans-serif;color:var(--accent,#9F5527);
  transition:background .15s,border-color .15s,color .15s}
.art-lbl{font-size:13.5px;line-height:1.4;max-width:22ch;color:#5F574F;transition:color .15s}
.art-step:hover .art-dot,.art-step:focus-visible .art-dot{background:var(--accent,#9F5527);
  border-color:var(--accent,#9F5527);color:#fff}
.art-step:hover .art-lbl,.art-step:focus-visible .art-lbl{color:#2B2622;text-decoration:underline;
  text-underline-offset:3px}
.art-step-end .art-dot{border-style:dashed;font-size:15px}
.art-step-end .art-lbl{color:var(--accent,#9F5527);font-weight:600}

.art-body{scroll-behavior:smooth;max-width:70ch;margin:0 auto}
@media (prefers-reduced-motion:reduce){.art-body{scroll-behavior:auto}}
.art-sec{display:block;
  padding:clamp(30px,5vh,58px) 0;border-bottom:1px solid var(--rule,#E2DACE);scroll-margin-top:96px}
.art-num{font:700 11px/1 'Space Grotesk',system-ui,sans-serif;letter-spacing:.16em;
  color:var(--accent,#9F5527);display:block;margin-bottom:12px}
.art-sec h2{font-family:'Bodoni Moda',Didot,Georgia,serif;font-weight:400;font-size:25px;
  line-height:1.2;margin:0 0 .55em}
.art-sec:target h2{text-decoration:underline;text-decoration-color:var(--accent,#9F5527);
  text-underline-offset:6px;text-decoration-thickness:2px}
.art-sec p{margin:0 0 .9em;font-size:15.5px;line-height:1.7;color:var(--body-c)}
.art-media{position:relative;min-height:240px;overflow:hidden;background:#1C1815}
.art-media .topo{position:absolute;inset:0;width:100%;height:100%}
.art-media img{width:100%;height:100%;object-fit:cover;display:block}
@media (max-width:780px){
  .art-steps{flex-direction:column;gap:16px}
  .art-step{flex:0 0 auto;flex-direction:row;align-items:center;gap:14px;padding:0}
  .art-step:not(:last-child)::after{top:36px;bottom:-16px;left:17px;right:auto;width:1px;height:auto}
  .art-lbl{max-width:none}
}

/* Section artwork sits on a LIGHT ground; the hero stays dark.
   contours() is drawn for a dark band — #7E9BB8 steel and the dark coppers — and all three are
   illegal on sand (the steel lands near 2:1). So both stroke groups are re-coloured to the
   light-ground pair from palette.js: deep sage for the far range, #8E4A20 copper for the near one.
   This is the ground-aware copper rule the palette page exists to enforce, and the reason the
   panels read as empty boxes before: dark-ground strokes at 0.26 opacity on near-black.
   CSS properties beat SVG presentation attributes, so no !important is needed. */
.art-media .pc-scene{position:absolute;inset:0;width:100%;height:100%;display:block}
/* THE PROGRAM CARD. Replaces five repeated ridge panels down each article, which were decoration
   that said nothing — Tony's read was "a weird blob, but staggered blobs", and he was right.
   This carries copy that already exists and has already been reviewed, so it earns the space the
   art did not. Dark on the light article body, the same inversion the program rail uses, because
   it is the same content in the same voice; it reuses .spec / .spec-go outright. */
.art-prog {
  background: #1C1815; color: #EDE4D3; margin: clamp(26px, 4vh, 40px) 0 0;
  overflow: hidden;
}
.art-prog-k {
  margin: 0; padding: 14px 20px 0; font-family: var(--mono); font-size: var(--fs-label);
  letter-spacing: .11em; text-transform: uppercase; color: #E09456;
}
.art-prog h3 {
  margin: 4px 0 0; padding: 0 20px; font-family: var(--serif); font-weight: 400; font-size: 21px;
  line-height: 1.2; color: #EDE4D3;
}

/* The program hero: warm charcoal, not the navy .band-dark. #1C1815 is the palette's dark ground —
   the homepage hero, the one-screen hero and the footer all sit on it, and a navy hero between a
   sage nav and a charcoal footer was the odd one out. Every value here is documented in
   site/palette.js for this ground. */
.band-charcoal {
  --bg: #1C1815; --surface: #2A2421; --ink: #EDE4D3; --body-c: #C9BFAE;
  --muted: #C9BFAE; --faint: #A79A86; --line: #38312B; --line-strong: #5F574F;
  --accent: #E09456;
  --warn: #F08A80; --warn-bg: #2A2421; --warn-line: #5F574F;
  --sage: #7C8B6F;
}
/* The hero art is ridgeScene(): filled silhouettes and a treeline, seeded per program. contours()
   was here and read as stray lines — .topo is sized 76% x 146% and offset, so the 1200x520 drawing
   was scaled 1.24x inside a 961x644 box and you saw a few enormous strokes rather than terrain.
   ridgeScene draws for a wide band, which is what a hero is, and it fills rather than strokes, so
   nothing depends on a hairline surviving a crop. */
.prog-hero-art .pc-scene { position: absolute; inset: 0; width: 100%; height: 100%; display: block; opacity: .9; }

/* The FAQ card. Same shell as the program card — these questions are the article's own and are
   already mirrored in its FAQPage block, so they anchor the end of the piece the way the program
   card anchors the top. .faq is styled for a light band, so this restates only the colours. */
/* The card's heading. On the program pages it is also the h2 the heading-set guard reads, so it
   keeps .prog-h2 and only the colour and spacing are restated for the dark ground. */
.art-faq-h {
  margin: 0; padding: 15px 20px 2px; font-family: var(--serif); font-weight: 400;
  font-size: 21px; line-height: 1.2; color: #EDE4D3;
}
.art-prog { scroll-margin-top: 96px; }
.art-faq { margin-top: clamp(30px, 5vh, 54px); }
.art-faq .faq { padding: 4px 20px 18px; }
.art-faq .faq details { border-bottom: 1px solid #38312B; }
.art-faq .faq summary { color: #EDE4D3; }
.art-faq .faq summary:hover { color: #E09456; }
.art-faq .faq p { color: #C9BFAE; }

/* ---------- US coverage map ----------
   Tile grid rather than a geographic outline: no 50-path SVG to maintain, and Rhode Island gets
   the same tap target as Texas. Covered tiles are links; the rest are inert and aria-hidden, so a
   keyboard user tabs 12 stops rather than 51. */
.cmap { margin: 0 0 var(--sp-4); }
.cmap svg { display: block; width: 100%; max-width: 720px; height: auto; }
.cmap text {
  font-family: var(--mono); font-size: 12px; fill: var(--faint);
  /* the label is decoration over its own tile; the link's <title> is what gets announced */
  pointer-events: none; user-select: none;
}
/* An uncovered state is an OUTLINE, not a filled tile. Filling all 51 made the twelve that matter
   compete with thirty-nine that do not, which is what made the map read as a grey grid with some
   green in it rather than as coverage. */
.cmap .ctile rect { fill: none; stroke: var(--line); }
.cmap .ctile.is-on rect { fill: var(--accent); stroke: var(--accent); }
.cmap .ctile.is-on text { fill: #FFFFFF; font-weight: 700; letter-spacing: .04em; }
.cmap .ctile.is-on:hover rect,
.cmap .ctile.is-on:focus-visible rect { fill: var(--ink); stroke: var(--ink); }
.cmap .ctile.is-on:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* ---- first-party contact form (/contact/) ---------------------------------------------------
   Plain markup posting to /api/intake, so it needs real styles rather than browser defaults.
   Tokens throughout, so it follows the palette and the dark theme without a second set of values. */
.cf { max-width: 34rem; }
.cf-row { margin: 0 0 var(--sp-2); }
.cf-row label {
  display: block; margin-bottom: 6px;
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
}
.cf-opt { text-transform: none; letter-spacing: 0; font-family: var(--sans); opacity: .75; }
.cf input, .cf textarea {
  width: 100%; padding: 11px 13px; font: inherit; font-size: var(--fs-base); color: var(--ink);
  background: var(--bg); border: 1px solid var(--line-strong); border-radius: var(--r-sm);
}
.cf textarea { resize: vertical; line-height: 1.55; }
.cf input:focus-visible, .cf textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
/* The consent box is a sentence with a control in it, not a labelled field, so it does not take
   the uppercase treatment above. */
.cf-consent { margin: 0 0 var(--sp-2); }
.cf-consent label {
  display: flex; gap: 9px; align-items: flex-start;
  font-size: var(--fs-caption); line-height: 1.55; color: var(--muted);
}
.cf-consent input { width: auto; margin-top: 3px; flex: none; }
.cf-actions { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin: 0; }
.cf-status { font-size: var(--fs-caption); color: var(--muted); }
.cf-status.cf-bad { color: var(--warn); font-weight: 600; }
.cf-done { font-size: var(--fs-base); line-height: 1.6; }

/* ---- header lockup --------------------------------------------------------------------------
   Empowered by NEXA under the mark in the header as well as the footer, because the lockup should
   travel with the logo rather than appear only at the bottom of the page.
   26px, the same as the footer, because Tony's standard is NEXA at the size of the logo and there
   is no reason to make an exception here. I first set it to 15px on the assumption that 26 would
   push the nav off: the 500px width I was reasoning from is the FOOTER measurement, where the line
   is block-level in a wide column and stretches. In the header it is an inline-flex column and
   renders 261px, the width of the mark above it. Measured at 1440, 1150, 980 and narrow: the nav
   fits and nothing overflows at any of them. */
header .brand, .one-nav .brand { display: inline-flex; flex-direction: column; gap: 3px; text-decoration: none; }
/* color:inherit, NOT var(--ink). The mark appears on two grounds: the sage header bar on 76 pages,
   and the transparent overlay over the dark hero on the homepage. Pinning it to --ink rendered it
   at 1.18:1 on the homepage, invisible, which is the third time this site has shipped text pinned
   to a colour that only suited one of the two grounds it appears on. Inheriting lets each header
   give it the colour that header already uses. */
.brand-nexa {
  display: block; font-family: var(--serif); font-size: 26px; line-height: 1;
  letter-spacing: .005em; color: inherit; white-space: nowrap;
}
/* The brand owns its hover rather than inheriting one written for something else, which is the
   rule the anchor-hover test exists to enforce. It matches the nav links either side of it. */
header .brand:hover, header .brand:focus-visible { color: var(--accent); }
.one-nav .brand:hover, .one-nav .brand:focus-visible { color: #E09456; }
