/* ============================================================================
   CMD Constructions — the content pages
   /custom-homes/ · /renovations/ · /early-input/ · /projects/ · /cade/ · /questions/

   WHY THIS FILE EXISTS SEPARATELY FROM site.css
   The homepage is a filter. It is held to the measured category spec in
   brief/BENCHMARK.md — roughly five screens, image-led, words in caption
   register — and it is frozen. These pages have the opposite job: they are what
   a search engine and an AI answer engine actually read, so they are text-led,
   long, and structured. Julian's call, 9 Sep 2026: "you're not restricted by
   the rules we followed on the homepage. SEO first on those pages."

   They still have to read as the same website, so the token block below is
   COPIED VERBATIM from mockup/css/site.css.
   ⚠️ NOT an @import: deploy.sh stamps ?v=<sha> onto href/src attributes and
   never onto an @import inside a stylesheet, so an imported token file would go
   stale in every browser cache on the next deploy. See
   [[deploy-stamps-asset-cache-bust]]. The cost of that is one duplicated block:
   if a token changes in site.css it must be changed here too. Nothing else in
   this file is shared, so that block is the only place the two can drift.
   ========================================================================== */

/* ⚠️ THIS @font-face IS THE WHOLE REASON THESE PAGES LOOKED WRONG, and it was
   missing for a day. I copied site.css's :root token block across — including
   `--sans: 'Montserrat', ui-sans-serif, …` — but NOT the @font-face above it, so
   Montserrat was never declared and every one of these six pages rendered in the
   system fallback. The pages even preload the woff2 in their <head>, so the file
   was fetched on every visit and then never used, which is the worst of both.

   Julian spotted it in the nav, because the nav is the one element that sits
   directly beside the homepage's own in a comparison. It was the entire page.

   ⚠️ A TOKEN THAT NAMES A FAMILY IS NOT A DECLARATION OF IT. If a stylesheet in
   this repo is ever forked from another, the @font-face has to come with the
   tokens. Path is relative to THIS file: css/pages.css -> ../fonts/.
   ⚠️ Variable font, 100-900 in one file — do not add static weight faces
   alongside it (see variable-font-pdf-type3 for the PDF-side caveat). */
@font-face {
  font-family: 'Montserrat';
  src: url('../fonts/montserrat-var-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --paper:     #F6F1E9;
  --paper-2:   #EDE7DC;
  --ground:    #2B3630;
  --ground-2:  #333F38;
  --deep:      #1E2723;

  --ink:       #232B26;
  --muted:     #5C685F;
  --on-dark:   #F6F1E9;
  --muted-dark:#AFB9AE;

  --clay:      #9C5339;   /* text-safe clay on paper */
  --clay-dark: #D5926F;   /* text-safe clay on the olive ground: 4.88:1 */
  --sage:      #6B7C5C;
  --slate:     #46697B;

  --rule:      #D2CABB;
  --rule-dark: rgba(246,241,233,.18);
  --edge:      #A99E8C;

  --sans: 'Montserrat', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --t-xs:   0.6875rem;
  --t-sm:   0.8125rem;
  --t-md:   0.9375rem;
  --t-lg:   1.0625rem;

  --d1: clamp(2.05rem, 4.15vw, 3.35rem);
  --d2: clamp(1.4rem, 2.4vw, 2rem);
  --d3: clamp(1.1rem, 1.5vw, 1.38rem);

  --track: 0.15em;

  --s1:.25rem; --s2:.5rem; --s3:.75rem; --s4:1rem; --s5:1.5rem; --s6:2rem; --s7:3rem;
  --band: clamp(2.75rem, 5.4vw, 4.75rem);
  --wrap: 1200px;
  --pad: clamp(1.25rem, 4vw, 3.25rem);
  --headh: 4.5rem;
}

/* ── tokens this file adds ─────────────────────────────────────────────────
   A reading measure, which the homepage never needed because it has no long
   prose on it. 68ch is the top of the comfortable band for body copy; the
   pages that run to 1,200 words need it or the lines get unreadable at 1440. */
:root {
  /* ⚠️ min(), NOT a bare 68ch, AND THE 640 IS THE IMAGE LADDER not a round
     number. 68ch of Montserrat at 15px measures 649px, which lands 9px above
     the 640 step every derivative in assets/img/ is cut to, so a figure at the
     full measure asked for a 649px box, got the 640 file and was upscaled by
     scripts/shot-pages.py's reckoning. Clamping the measure to the ladder step
     costs 0.9 of a character and makes every in-column figure a pixel-exact
     match. If build-images.py ever changes its widths, change this with it. */
  --measure: min(68ch, 640px);
  --measure-tight: 58ch;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* the header is fixed, so an in-page anchor has to clear it */
:target { scroll-margin-top: calc(var(--headh) + var(--s5)); }
section[id], h2[id], h3[id] { scroll-margin-top: calc(var(--headh) + var(--s5)); }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-md);
  font-weight: 300;
  line-height: 1.72;
  -webkit-font-smoothing: antialiased;
}

/* ⚠️ THESE THREE VALUES ARE site.css's, TO THE DECIMAL — 400 / 1.08 / -.02em.
   They were 400 / 1.14 / -.018em, which is close enough to look deliberate and
   is not: there is no reason for a content heading to track 0.002em looser than
   the homepage's. "Match the main site" means match it, not approximate it.
   (The hero's own h1 is weight 300; that is a hero-specific override in
   site.css, so 400 is right for a content page.) */
h1, h2, h3, h4 { font-weight: 400; line-height: 1.08; letter-spacing: -.02em; margin: 0; }
p, ul, ol, dl, figure, blockquote, table { margin: 0; }
img, video { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
strong, b { font-weight: 600; }

a, button, input, textarea, select, label, summary { font-family: inherit; }
:focus-visible { outline: 2px solid var(--clay); outline-offset: 3px; border-radius: 2px; }
.close :focus-visible, .foot :focus-visible, .cta :focus-visible { outline-color: var(--clay-dark); }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }

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

.skip {
  position: absolute; left: 50%; top: 0; transform: translate(-50%, -120%);
  z-index: 200; padding: .8rem 1.2rem; background: var(--ground); color: var(--on-dark);
  font-size: var(--t-sm); font-weight: 600; text-decoration: none;
}
.skip:focus { transform: translate(-50%, 0); }

/* small caps label, the site's one recurring device */
.lab {
  font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--track);
  text-transform: uppercase; color: var(--muted);
}
.lab--dark { color: var(--muted-dark); }
.lab--clay { color: var(--clay); }

/* ── header ───────────────────────────────────────────────────────────────
   ⚠️ REBUILT 9 September 2026 TO MATCH THE MAIN SITE. Julian: "The SEO pages
   arent styled the same as the main site and their unique menu is styled
   differently."

   He was right and the fault was mine. The homepage's header starts
   transparent over the hero photograph and turns into `.head.solid` on
   scroll — and `.head.solid` is **paper with ink text and the DARK mark**.
   These pages open on paper with no hero to be transparent over, so their
   resting state should have been that solid state from the start. Instead I
   gave them a permanently dark olive bar with a different nav weight, which
   made every content page look like a different website to the one it hangs
   off.

   So: same height, same paper ground, same --rule bottom hairline, same ink
   nav at weight 500 and the same clamp gap as site.css, same clay hover, same
   phone treatment. The one thing that differs is the mark file — the dark
   `cmd-mark-*` rather than the light `cmd-mark-light-*`, because the ground is
   paper. ⚠️ Keep this block and site.css's `.head.solid` in step; if one moves
   the other has to. */
.head {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--headh); display: flex; align-items: center;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}
.head__in { display: flex; align-items: center; gap: var(--s5); width: 100%; }
.mark { display: inline-flex; align-items: center; min-height: 44px; flex: 0 0 auto; text-decoration: none; }
.mark img { width: 132px; }

.nav {
  margin-left: auto;
  display: flex; align-items: center; gap: clamp(.85rem, 2vw, 1.9rem);
  /* ⚠️ Six page links plus a button plus a phone number does not fit a phone.
     The row scrolls rather than collapsing into a burger and the JS to run
     one — these pages load no JavaScript at all and that is worth keeping. */
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
}
.nav::-webkit-scrollbar { display: none; }
.nav a {
  flex: 0 0 auto;
  font-size: var(--t-sm); font-weight: 500; color: var(--ink);
  text-decoration: none; white-space: nowrap;
  min-height: 44px; display: inline-flex; align-items: center;
  border-bottom: 1px solid transparent;
  transition: color .18s ease, border-color .18s ease;
}
.nav a:hover { color: var(--clay); }
/* the page you are on, said with colour AND a rule so it does not rest on
   colour alone. aria-current carries it for a screen reader. */
.nav a[aria-current="page"] { color: var(--clay); border-bottom-color: var(--clay); }

/* ── the back-to-home button ─────────────────────────────────────────────
   Julian: "Should have a back to home button in a different style."
   Deliberately NOT a nav link. The nav is six peers you move between; this
   is the way out of the set and back to the site, so it is a bordered
   control with an arrow, sitting after the links and before the phone. The
   mark also goes home, but a wordmark is not a signpost and on a sub-page
   people look for a button. */
.head__home {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: .45rem;
  min-height: 40px; padding: 0 var(--s4);
  border: 1px solid var(--clay); color: var(--clay);
  font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--track);
  text-transform: uppercase; text-decoration: none; white-space: nowrap;
  transition: background .16s ease, color .16s ease;
}
.head__home:hover { background: var(--clay); color: var(--paper); }
.head__home svg { width: 11px; height: 11px; flex: none; }
@media (prefers-reduced-motion: reduce) { .head__home, .nav a { transition: none; } }

.head__tel {
  flex: 0 0 auto;
  font-size: var(--t-sm); font-weight: 600; color: var(--ink);
  text-decoration: none; min-height: 44px;
  display: inline-flex; align-items: center;
  border-bottom: 1px solid var(--clay);
}
.head__tel:hover { color: var(--clay); }
/* ⚠️ The phone goes before the nav does. It is repeated as the first thing in
   the CTA band and again in the footer, so it is never more than one screen
   away; the six page links are not repeated anywhere above the footer. */
@media (max-width: 900px) { .head__tel { display: none; } }
@media (max-width: 560px) { .head__home { padding-inline: var(--s3); } }

/* ── page masthead ────────────────────────────────────────────────────────
   No photograph. These pages are read, not looked at, and a hero image at the
   top of a 1,400-word page pushes the first sentence below the fold for no
   gain. The imagery sits inside the text where it illustrates something. */
main { padding-top: var(--headh); }

.top { padding-block: clamp(2.5rem, 6vw, 4.5rem) var(--band); }
.crumb { margin-bottom: var(--s5); font-size: var(--t-xs); color: var(--muted); }
.crumb ol { list-style: none; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; }
.crumb a {
  display: inline-flex; align-items: center; min-height: 32px;
  text-decoration: none; border-bottom: 1px solid var(--rule);
}
.crumb a:hover { border-bottom-color: var(--clay); color: var(--clay); }
.crumb li[aria-current="page"] { color: var(--ink); }
.crumb svg { width: 9px; height: 9px; opacity: .5; flex: 0 0 auto; }

.top h1 { font-size: var(--d1); max-width: 22ch; margin-top: var(--s4); }
.top .lede {
  margin-top: var(--s5); max-width: var(--measure-tight);
  font-size: var(--t-lg); font-weight: 300; line-height: 1.62; color: var(--muted);
}

/* ── prose ────────────────────────────────────────────────────────────────
   One column, one measure, generous space between sections. Everything in here
   is set for reading a screenful of text rather than glancing at a screenful of
   photograph, which is the whole difference between these pages and the home. */
.sec { padding-block: var(--band); border-top: 1px solid var(--rule); }
.sec:first-of-type { border-top: 0; }
.sec > .wrap > .lab { display: block; margin-bottom: var(--s4); }
.sec h2 { font-size: var(--d2); max-width: 26ch; }
.sec h3 {
  font-size: var(--d3); margin-top: var(--s6); max-width: 34ch;
  letter-spacing: -.012em;
}
.sec h4 {
  font-size: var(--t-md); font-weight: 600; margin-top: var(--s5);
  letter-spacing: 0; line-height: 1.5;
}

/* ⚠️ THE MEASURE IS ON THE TEXT, NOT ON THE COLUMN, and that is deliberate.
   .prose used to be max-width:--measure, which meant .fig--wide could not
   actually be wide: a full-bleed figure nested inside a 625px column is a
   625px figure, and its `sizes` then told the browser to fetch a 1096px file
   for a 649px box on every wide figure on the site. scripts/shot-pages.py
   caught it. Capping each text child instead lets .prose span the whole wrap,
   so a wide figure fills the wrap and its `sizes` is true.
   ⚠️ h2 and h3 are NOT in this list on purpose: .sec h2/h3 above cap them
   tighter, at 26ch and 34ch, and those rules come first in the file, so
   naming them here would silently widen every heading to 68ch. */
.prose { max-width: none; }
.prose > p, .prose > ul, .prose > ol, .prose > h4, .prose > dl:not(.spec) {
  max-width: var(--measure);
}
.prose > * + * { margin-top: var(--s5); }
.prose h2 + p, .prose h3 + p, .prose h4 + p { margin-top: var(--s4); }
.prose ul, .prose ol { padding-left: 1.15rem; }
.prose li + li { margin-top: var(--s3); }
.prose li::marker { color: var(--clay); }
.prose a:not(.btn):not(.card) {
  text-decoration: none; border-bottom: 1px solid var(--edge);
}
.prose a:not(.btn):not(.card):hover { color: var(--clay); border-bottom-color: var(--clay); }
.prose strong { font-weight: 600; }

/* a lead sentence that carries a section, one step up from body */
.stand { font-size: var(--t-lg); font-weight: 300; line-height: 1.6; max-width: var(--measure-tight); }

/* ── the aside panel ──────────────────────────────────────────────────────
   Used for the one thing on a page that is a limit rather than a selling point:
   "we will not print a rating Cade has not built to", "ask what sits inside the
   guarantee". It is inset on paper rather than reversed out, because a dark
   panel reads as a pull quote and these are footnotes with teeth. */
.only {
  margin-top: var(--s6); padding: var(--s5) var(--s5) var(--s5) calc(var(--s5) + 3px);
  background: var(--paper-2); border-left: 3px solid var(--clay);
  max-width: var(--measure);
}
.only p { font-size: var(--t-sm); line-height: 1.66; }
.only p + p { margin-top: var(--s3); }
.only .lab { display: block; margin-bottom: var(--s2); color: var(--clay); }

/* ── specification / definition list ─────────────────────────────────────
   Two columns on a wide screen, stacked on a phone. Used for the BAL table and
   the guarantee terms: a reader scanning for one number finds it without
   reading the paragraph around it, and the markup stays a real <dl>. */
.spec { margin-top: var(--s6); border-top: 1px solid var(--rule); max-width: var(--measure); }
.spec > div {
  display: grid; grid-template-columns: minmax(8rem, 12rem) 1fr; gap: var(--s2) var(--s5);
  padding-block: var(--s4); border-bottom: 1px solid var(--rule);
}
.spec dt { font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--track); text-transform: uppercase; color: var(--muted); }
.spec dd { margin: 0; font-size: var(--t-sm); line-height: 1.65; }
@media (max-width: 620px) { .spec > div { grid-template-columns: 1fr; } }

/* ── the stage list ──────────────────────────────────────────────────────
   How a build runs. Numbered, because the sequence is the content. */
.stages { margin-top: var(--s6); list-style: none; padding: 0; counter-reset: st; max-width: var(--measure); }
.stages li {
  counter-increment: st; position: relative;
  padding-left: 3.1rem; padding-block: var(--s5);
  border-top: 1px solid var(--rule);
}
.stages li::before {
  content: counter(st, decimal-leading-zero);
  position: absolute; left: 0; top: calc(var(--s5) + .1em);
  font-size: var(--t-xs); font-weight: 600; letter-spacing: .08em; color: var(--clay);
}
.stages h3 { margin-top: 0; font-size: var(--t-md); font-weight: 600; letter-spacing: 0; line-height: 1.5; }
.stages p { margin-top: var(--s3); font-size: var(--t-sm); line-height: 1.7; color: var(--ink); }
.stages .lab { display: block; margin-top: var(--s2); }

/* ── figures ─────────────────────────────────────────────────────────────
   ⚠️ img { height:auto } is set globally at the top of this file and it matters
   here: these figures are width-constrained and the width/height attributes on
   the <img> are the NATIVE ratio, so without height:auto a 2048x1341 file in a
   700px box renders 1341px tall. Same trap as [[banner-crop-double-crop-trap]]. */
.fig { margin-top: var(--s6); max-width: var(--measure); }
.fig--wide { max-width: none; }   /* = the full .wrap content width */
/* ⚠️ A PORTRAIT FRAME NEEDS ITS OWN CAP. At the full 625px measure a 2:3 frame
   renders 938px tall, which is taller than the viewport it sits in and reads as
   a mistake rather than a picture. It also made the browser ask for a 649px box
   from a ladder whose widest useful step is 640, so scripts/shot-pages.py
   reported it as an upscale. 420px is the widest step below the ladder's 640
   and gives a 630px-tall frame. Pair it with SIZES["port"]. */
.fig--port { max-width: 420px; }
.fig img { width: 100%; background: var(--paper-2); }
.fig figcaption {
  margin-top: var(--s3); font-size: var(--t-xs); line-height: 1.6; color: var(--muted);
}
.fig figcaption b { color: var(--ink); font-weight: 600; }

/* ── quote ───────────────────────────────────────────────────────────────
   The three client testimonials and Jamie's line. Verbatim, always. */
.quote { margin-top: var(--s6); max-width: var(--measure); }
.quote blockquote {
  font-size: var(--d3); font-weight: 300; line-height: 1.5; letter-spacing: -.008em;
  padding-left: var(--s5); border-left: 2px solid var(--clay);
}
.quote figcaption { margin-top: var(--s4); padding-left: calc(var(--s5) + 2px); font-size: var(--t-sm); color: var(--muted); }
.quote figcaption b { color: var(--ink); font-weight: 600; }

/* ── question and answer ─────────────────────────────────────────────────
   Open by default and not a <details>. An accordion hides the answer from a
   reader who is skimming and, more to the point here, some AI crawlers do not
   expand one — and the whole purpose of this page is being quotable. */
.qa { margin-top: var(--s6); max-width: var(--measure); }
.qa > div { padding-block: var(--s6); border-top: 1px solid var(--rule); }
.qa h3 { margin-top: 0; font-size: var(--d3); max-width: 40ch; }
.qa h3 + * { margin-top: var(--s4); }
.qa p + p, .qa ul + p, .qa p + ul { margin-top: var(--s4); }
.qa ul { padding-left: 1.15rem; }
.qa li + li { margin-top: var(--s3); }
.qa li::marker { color: var(--clay); }
.qa a { text-decoration: none; border-bottom: 1px solid var(--edge); }
.qa a:hover { color: var(--clay); border-bottom-color: var(--clay); }

/* ── cards, for the projects index and the "where next" row ─────────────── */
.deck {
  margin-top: var(--s6);
  display: grid; gap: var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.card {
  display: flex; flex-direction: column; gap: var(--s3);
  padding: var(--s5); background: var(--paper-2);
  text-decoration: none; border: 1px solid transparent;
  transition: border-color .16s ease, background .16s ease;
}
.card:hover { border-color: var(--edge); background: #E7E0D3; }
.card .lab { color: var(--clay); }
.card h3, .card h2 { margin-top: 0; font-size: var(--t-md); font-weight: 600; letter-spacing: 0; line-height: 1.45; }
.card p { font-size: var(--t-sm); line-height: 1.65; color: var(--muted); }
.card__go { margin-top: auto; padding-top: var(--s3); font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--track); text-transform: uppercase; color: var(--clay); }
@media (prefers-reduced-motion: reduce) { .card { transition: none; } }

/* a project card that leads with its photograph */
.card--img { padding: 0; overflow: hidden; }
.card--img img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.card--img > div { padding: var(--s5); display: flex; flex-direction: column; gap: var(--s3); flex: 1; }

/* ── film ────────────────────────────────────────────────────────────────
   ⚠️ aspect-ratio on the WRAPPER, so the block holds its space before the
   player loads and the text under it does not jump. Same as /brand-story/. */
.film { margin-top: var(--s6); max-width: 56rem; }
.film__frame { position: relative; aspect-ratio: 16 / 9; background: var(--deep); }
.film__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.film figcaption { margin-top: var(--s4); font-size: var(--t-sm); color: var(--muted); }
.film figcaption b { color: var(--ink); font-weight: 600; }

/* ── buttons ─────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: 48px; padding: 0 var(--s5);
  font-size: var(--t-sm); font-weight: 600; letter-spacing: .02em;
  text-decoration: none; border: 1px solid currentColor; background: transparent;
  color: var(--ink); cursor: pointer;
  transition: background .16s ease, color .16s ease, border-color .16s ease;
}
.btn:hover { background: var(--ink); color: var(--paper); }
.btn--solid { background: var(--clay); border-color: var(--clay); color: var(--paper); }
.btn--solid:hover { background: #8A4830; border-color: #8A4830; color: var(--paper); }
.btn--dark { color: var(--on-dark); }
.btn--dark:hover { background: var(--on-dark); color: var(--ground); }
@media (prefers-reduced-motion: reduce) { .btn { transition: none; } }

.btns { margin-top: var(--s6); display: flex; flex-wrap: wrap; gap: var(--s4); }

/* ── the close band ──────────────────────────────────────────────────────
   Every page ends the same way: the phone number, then the way on to the rest
   of the site. Internal linking that is useful to a reader rather than a
   footer full of keywords. */
.cta { background: var(--ground); color: var(--on-dark); padding-block: var(--band); }
.cta h2 { font-size: var(--d2); max-width: 26ch; }
.cta p { margin-top: var(--s5); max-width: var(--measure-tight); color: var(--muted-dark); }
.cta__tel {
  display: inline-flex; align-items: center; min-height: 48px; margin-top: var(--s5);
  font-size: var(--d3); font-weight: 400; letter-spacing: .01em;
  color: var(--on-dark); text-decoration: none;
  border-bottom: 1px solid var(--clay-dark);
}
.cta__tel:hover { color: var(--clay-dark); }
.cta .deck { margin-top: var(--s7); }
/* ⚠️ RECESSED ONTO --deep, NOT RAISED ONTO --ground-2, AND IT IS A CONTRAST
   FIX RATHER THAN A TASTE ONE. --clay-dark #D5926F measures 4.28:1 on
   --ground-2 #333F38 and fails AA for the 13px label and "Read on" line inside
   these cards. On --deep #1E2723 the same accent measures 5.97:1 and the
   secondary text 7.57:1. This is the same trap /hub/ hit and the same fix it
   used: sink the card instead of inventing a lighter clay. --clay-lift
   #DA9575, which exists for exactly this panel, still only reaches 4.47:1 here
   and does NOT solve it. Measured with scripts/contrast.py. Check any new panel
   colour against clay before putting small text on it. */
.cta .card { background: var(--deep); border-color: transparent; }
.cta .card:hover { background: #232B26; border-color: var(--rule-dark); }
.cta .card h3 { color: var(--on-dark); }
.cta .card p { color: var(--muted-dark); }
.cta .card .lab, .cta .card .card__go { color: var(--clay-dark); }

/* ── footer ──────────────────────────────────────────────────────────────
   Carries the full page list. This is the sitemap a crawler follows and the
   thing /being-found/ promised Cade: "the new pages sit behind the homepage,
   linked from the footer". */
.foot {
  background: var(--deep); color: var(--muted-dark);
  padding-block: var(--s7); border-top: 1px solid var(--rule-dark);
  font-size: var(--t-sm);
}
.foot__top { display: grid; gap: var(--s6) var(--s7); grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
.foot__mark { display: grid; gap: .55rem; justify-items: start; align-content: start; }
.foot img { width: 124px; opacity: .9; }
.foot__sig {
  font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--track);
  text-transform: uppercase; color: var(--muted-dark); opacity: .8;
}
.foot__col .lab { display: block; margin-bottom: var(--s3); }
.foot__col ul { list-style: none; padding: 0; }
.foot a {
  display: inline-flex; align-items: center; min-height: 44px;
  text-decoration: none; border-bottom: 1px solid transparent;
}
.foot a:hover { color: var(--on-dark); border-bottom-color: var(--clay-dark); }
.foot__base {
  margin-top: var(--s7); padding-top: var(--s5); border-top: 1px solid var(--rule-dark);
  display: flex; flex-wrap: wrap; gap: var(--s3) var(--s6); justify-content: space-between; align-items: center;
  font-size: var(--t-xs); color: var(--muted-dark);
}
.foot__base a { border-bottom: 1px solid var(--rule-dark); }

@media print {
  .head, .cta, .skip { display: none; }
  main { padding-top: 0; }
  body { background: #fff; }
}
