/* ============================================================================
   Fix It or Risk It — main stylesheet
   Loaded on every page. Order: fonts → tokens → element base → components.

   Rules that govern this file live in /CLAUDE.md and docs/build/02-DESIGN-SYSTEM.md.
   In short: tokens never literals, classes never IDs, and no !important.
   ============================================================================ */


/* ----------------------------------------------------------------------------
   1. Fonts

   Barlow and Barlow Condensed, SIL Open Font License 1.1, self-hosted.
   Replaces DIN 2014 — see docs/adr/0004-barlow-replaces-din-2014.md.
   Five weights each: 400 / 500 / 600 / 700 / 800. No italics — the site uses none.
   ---------------------------------------------------------------------------- */

@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/barlow-v13-latin_latin-ext-regular.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../assets/fonts/barlow-v13-latin_latin-ext-500.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/barlow-v13-latin_latin-ext-600.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../assets/fonts/barlow-v13-latin_latin-ext-700.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("../assets/fonts/barlow-v13-latin_latin-ext-800.woff2") format("woff2");
}

@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/barlow-condensed-v13-latin_latin-ext-regular.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../assets/fonts/barlow-condensed-v13-latin_latin-ext-500.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../assets/fonts/barlow-condensed-v13-latin_latin-ext-600.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../assets/fonts/barlow-condensed-v13-latin_latin-ext-700.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("../assets/fonts/barlow-condensed-v13-latin_latin-ext-800.woff2") format("woff2");
}


/* ----------------------------------------------------------------------------
   2. Tokens
   ---------------------------------------------------------------------------- */

:root {

  /* -- The three, plus ink ------------------------------------------------- */
  --fixit-white:  #FCFCF7;   /* default page ground. NOT #fff */
  --fixit-grey:   #404040;   /* body copy, marker strike */
  --fixit-yellow: #FFE400;   /* highlight only. Never a large field, never a ground */
  --fixit-ink:    #231F20;   /* headlines, dark grounds. NOT #000 */

  /* -- State variants (not palette colours — hover/press only) -------------- */
  --yellow-press: #E8CF00;
  --yellow-veil:  rgba(255, 228, 0, .22);   /* highlight wash behind small text */
  --yellow-edge:  rgba(255, 228, 0, .70);   /* the rating ramp's Medium border */

  /* -- Neutral grey ramp, anchored on FIXIT WHITE and FIXIT GREY ------------ */
  --grey-000: #FCFCF7;   /* = FIXIT WHITE */
  --grey-050: #F5F4EE;
  --grey-100: #EBEAE3;
  --grey-200: #D8D7D0;
  --grey-300: #B0AFA9;
  --grey-400: #87867F;
  --grey-500: #5C5C58;
  --grey-600: #404040;   /* = FIXIT GREY */
  --grey-700: #333333;
  --grey-800: #262626;
  --grey-900: #1C1C1C;
  --grey-950: #111111;

  /* -- Sam ----------------------------------------------------------------
     Sam's colour marks WHO is speaking; yellow marks WHAT to do. They never
     appear in the same element, and yellow never carries Sam. Allowed on the
     avatar, Sam's eyebrow, a Sam card's top bar, a follow-Sam underline and
     the route's milestone dots — never a button, CTA, headline or ground.
     Literals in the prototype; tokenised here.
     ------------------------------------------------------------------------ */
  --sam:         #AB4289;   /* fills and bars */
  --sam-on-dark: #D98CC0;   /* Sam's words and outlined marks on ink grounds */
  --sam-tint:    #F5E1EE;   /* hover washes */
  --sam-on-fill: #FCFCF7;   /* glyphs on Sam fills */

  /* -- Section grounds — the four-step ladder ------------------------------
     No two adjacent sections may share one. Sunken and muted may not sit
     adjacent either — the step is too small to read as intentional. Dark is
     capped at two per page; a <footer> is chrome and does not count.
     ------------------------------------------------------------------------ */
  --bg-page:      var(--fixit-white);
  --bg-sunken:    var(--grey-050);   /* a whisper off the page */
  --bg-muted:     var(--grey-100);   /* clearly a separate panel */
  --bg-dark:      var(--fixit-ink);  /* the loud one — two per page maximum */
  --bg-highlight: var(--fixit-yellow);

  /* Cards carry a 1px rule on the page ground — no fill, no shadow. This token
     is the rating strip's Low state, not a card fill. Campaign white, not #fff. */
  --surface-card:      var(--fixit-white);
  /* A plate under a partner's mark, and the one place pure white is right:
     several marks are drawn for white and a warm ground tints them. The
     prototype states #fff literally on every one of these. */
  --surface-plate:     #FFFFFF;
  --surface-card-dark: var(--grey-800);
  --surface-hover:     var(--grey-050);   /* the step the palette was missing */

  /* -- Text ---------------------------------------------------------------- */
  --text-strong:        var(--fixit-ink);
  --text-body:          var(--fixit-grey);
  --text-muted:         var(--grey-400);
  --text-on-dark:       var(--fixit-white);
  --text-on-dark-prose: var(--grey-200);   /* lead and body prose on ink */
  --text-on-dark-muted: var(--grey-300);   /* the qualifier, and captions */
  --text-on-yellow:     var(--fixit-ink);

  /* -- Borders ------------------------------------------------------------- */
  --border-subtle:  var(--grey-100);

  /* NETWORK COLOURS — the share tiles only, and not part of the campaign
     palette: they belong to the networks, not to us. Each is the network's
     own mark, unmodified.

     WHITE ON WHATSAPP'S GREEN MEASURES 1.98:1, and that is fine. 1.4.11
     exempts a presentation that is "essential to the information being
     conveyed", which is exactly what a brand colour is — the green IS how you
     recognise WhatsApp, and white-on-#25D366 is the asset WhatsApp itself
     ships. This was briefly #128C7E, their darker teal, to clear the ratio;
     Ward asked why, and the answer was that the rule had been applied where it
     does not govern. Checked on screen at 48px: the glyph reads instantly.
     Do not "fix" it again.

     For reference: LinkedIn 5.69:1, Facebook 4.23:1, X 21:1. */
  --net-linkedin:   #0A66C2;
  --net-facebook:   #1877F2;
  --net-x:          #000000;
  --net-whatsapp:   #25D366;
  /* SMS has no brand. --grey-500 rather than ink: white on it is 6.72:1, and
     it is 3.13:1 against the X tile it sits beside, where ink would be 1.29
     and read as a second black square. */
  --net-sms:        #5C5C58;
  --border-default: var(--grey-200);
  --border-strong:  var(--grey-400);
  --border-ink:     var(--fixit-ink);
  --border-on-dark: rgba(252, 252, 247, .18);
  --border-on-dark-lit: rgba(252, 252, 247, .35);   /* role chips: readable on a photograph */
  --surface-on-dark:    rgba(252, 252, 247, .05);  /* a panel lifted off an ink ground */
  --rule-on-photo:   rgba(252, 252, 247, .30);      /* list rules over artwork */
  --text-on-photo:   rgba(252, 252, 247, .90);      /* body copy over artwork */
  --focus-ring:     var(--fixit-ink);

  /* -- Type ---------------------------------------------------------------- */
  --font-sans:    "Barlow", Arial, Helvetica, sans-serif;
  --font-display: "Barlow Condensed", "Arial Narrow", Arial, sans-serif;

  /* Weights. Only these five exist as real cuts — anything else synthesises.
     500 is the nav's resting weight; 700 marks the active item.

     --fw-regular IS 500, NOT 400, AND THAT IS THE POINT. The prototype is set in
     DIN 2014; the build substitutes Barlow (ADR 0004), and Barlow's 400 reads
     markedly thinner than DIN's at the same nominal weight. Body copy that was
     comfortable in the prototype came out faint here — worst at 12 and 14px in
     muted grey, but present throughout. Ward's call, 28 Aug 2026, after finding
     it page after page.

     This is a substitution artefact, so it is corrected here rather than in the
     prototype: there is nothing wrong with the prototype's 400.

     One token, one edit: --fw-regular is used exactly once, on body, and every
     element without its own weight inherits it. The two literal 400s in this
     file are @font-face declarations naming which file IS the 400 cut, and must
     stay. 400 remains reachable as --fw-light for anything that should sit
     genuinely lighter than the body. */
  --fw-regular:  500;
  --fw-light:    400;
  --fw-book:     400;
  --fw-nav:      500;
  --fw-medium:   600;
  --fw-semibold: 600;
  --fw-bold:     700;
  --fw-black:    800;   /* the ceiling. There is no 900 */

  /* Scale (px) — condensed caps carry more size than a normal-width face */
  --fs-display-xl: 96px;
  --fs-display-lg: 68px;
  --fs-display-md: 48px;
  --fs-h1:         38px;
  --fs-h2:         29px;
  --fs-h3:         22px;
  --fs-lead:       19px;
  --fs-body:       16px;
  --fs-sm:         14px;
  --fs-xs:         12px;
  --fs-eyebrow:    12px;

  /* Line-heights — caps set tight, body stays generous */
  --lh-tight: 0.92;
  --lh-snug:  1.08;
  --lh-body:  1.55;

  /* Tracking — condensed caps need none; small-caps labels need some */
  --ls-display: 0;
  --ls-tight:   0;
  --ls-normal:  0;
  --ls-eyebrow: 0.12em;
  --ls-chip:    0.06em;   /* between display tracking and eyebrow tracking */

  /* -- Prose measures — the four-step scale -------------------------------- */
  --measure-display:  1040px;   /* uppercase display headline */
  --measure-prose:    1000px;   /* standalone prose / section intro */
  --measure-group:    1000px;   /* a group head over a full-width table */
  --measure-body:     900px;    /* body prose with no sibling column */
  --measure-column:   800px;    /* prose beside something else */
  --measure-qualifier: 560px;   /* hero qualifier line */
  --photo-copy:        620px;   /* Home's photograph band: the copy column */

  /* -- Spacing — NOT continuous. There is no 7, 9, 11, 14 ------------------ */
  --sp-1:   4px;
  --sp-2:   8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;
  --sp-20: 80px;
  --sp-24: 96px;
  --sp-32: 128px;

  /* -- Shape — everything is square ---------------------------------------- */
  --radius-sm:  0;
  --radius-md:  0;
  --radius-lg:  0;
  --radius-xl:  0;
  --radius-pill: 999px;   /* circular dots and avatars ONLY — never buttons or cards */

  /* Shadows are for genuinely floating UI: menus, dialogs, sticky bars.
     Separate ordinary surfaces with a 1px rule instead. */
  --shadow-xs: none;
  --shadow-sm: none;
  --shadow-md: 0 6px 20px rgba(35, 31, 32, .08);
  --shadow-lg: 0 16px 44px rgba(35, 31, 32, .12);

  /* -- Layout -------------------------------------------------------------- */
  --container:        1240px;
  --container-narrow:  760px;
  --gutter:            24px;

  /* Control heights. Below 1130px header controls take the touch floor. */
  --control-h-sm: 38px;
  --control-h-md: 48px;
  --control-h-lg: 58px;
  --touch-min:    44px;

  /* -- Breakpoints — the canonical set --------------------------------------
     Custom properties cannot be used in media queries, so this comment is the
     single reference. Use ONLY these seven. Each exists because a specific
     layout breaks there, which is why they are not device widths.

       1130px  Desktop boundary. Nine nav links will not sit beside the lockup
               and two CTAs below this, so the header collapses to a hamburger
               and its controls take the 44px touch floor. The Sam hero card's
               overhang begins here too — above 1130 is one desktop state, not
               two overlapping ones.
               Written as max-width:1129.98px / min-width:1130px so a fractional
               viewport cannot fall between them.
       1020px  Start Here timeline: three columns, or a single stack. Never two —
               a two-column wrap breaks the rail into a false second row.
       1000px  Toolkit column header hides and rows stack.
        880px  Fund Your Fix drops its middle columns.
        620px  Small-screen tier: the type scale steps down and section padding
               tightens. Below here the prototype has no design at all.
        480px  Narrow phones. The lockup, the toolkit button and the burger stop
               fitting on one row.

     The prototype used 1129, 1120, 1100, 1020, 1000, 900, 880 and 620, decided
     independently over months. 1120 and 1129 left a 10px band where the header
     was in its mobile state while the Sam card was in its desktop one.
     ------------------------------------------------------------------------ */

  /* -- Motion — minimal. 120–260ms, linear or ease-out, no bounce ---------- */
  --ease-out:    cubic-bezier(.2, .7, .3, 1);
  --ease-in-out: cubic-bezier(.5, 0, .2, 1);
  --dur-fast: 120ms;
  --dur:      200ms;
  --dur-slow: 340ms;

  /* -- The marker strike ---------------------------------------------------
     A hand-cut brush mark that stretches to any width without distorting.
     Never redraw this as a straight rule or a CSS line-through.
     ------------------------------------------------------------------------ */
  --strike-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1000 62' preserveAspectRatio='none'%3E%3Cpath fill='%23404040' d='M4 47c22-7 55-9 92-11 44-3 89-4 133-6 61-3 121-6 182-8 74-3 148-5 222-6 63-1 126 0 189 3 54 3 108 6 161 12 8 1 17 2 17 6 0 5-11 5-18 6-31 3-62 2-93 1-58-2-116-6-174-7-71-2-143-1-214 2-79 3-158 8-237 12-49 2-98 4-147 5-31 1-62 2-93 0-9-1-24-1-24-5 0-3 3-3 4-4z'/%3E%3Cpath fill='%23404040' opacity='.55' d='M28 33c40-6 81-8 122-10 76-4 153-7 229-9 88-3 176-4 264-2 56 1 112 4 168 9 7 1 15 2 15 6 0 4-9 4-15 4-38-2-76-6-114-7-79-3-158-3-237-1-92 3-184 9-276 12-49 2-98 3-147 2-6 0-14 0-14-3 0-1 3-1 5-1z'/%3E%3C/svg%3E");
}


/* ----------------------------------------------------------------------------
   3. Element base
   ---------------------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

/* The column layout docks the footer to the bottom of the viewport on a page
   too short to scroll. The tagline band and the footer proper are both inside
   the one <footer> mount, so this covers both, and `main` taking the slack is
   the whole mechanism — no viewport units on the footer itself.

   100vh, not 100dvh: on mobile Safari dvh shrinks when the URL bar appears,
   which would slide the footer up into the middle of a short page. */
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-page);
  color: var(--text-body);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* flex-grow only. `flex-shrink: 0` keeps a long page from being compressed, and
   `flex-basis: auto` keeps content sizing normal. */
main { flex: 1 0 auto; }

/* Headlines are Barlow Condensed, uppercase, tight, and 800 at every level.

   THE OLD NOTE HERE SAID 600–700 FOR HEADINGS THAT WRAP, and that reasoning was
   about DIN: at display scale, long DIN copy at the heaviest weight goes dense
   and dark. Barlow Condensed is a lighter, narrower face, so it never did — the
   number was carried over from a typeface the build does not use, and h3 to h6
   came out visibly thin beside the h1s and h2s that had already been corrected
   to 800. See ADR 0004 and the _SUBSTITUTION rules in style-deviations.json.

   h1 and h2 still restate 800 below. Redundant now, and kept: each carries the
   reasoning for its own size, and a bare size rule invites someone to wonder
   what weight it was meant to have. */
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-strong);
  text-wrap: pretty;
}

/* The two shortest headlines on the site take the tier above: "Campaign
   Resources" on Media and "Fund your fix". A three-word headline at the body
   tier reads as a label rather than a title. */
.h1--lg { font-size: var(--fs-display-lg); }

/* --measure-display on every h1, because the prototype caps all eleven of them
   at 1040px. It only binds on a page whose headline runs the full container,
   but it is the reason --measure-display exists. */
/* --fw-black, though the prototype says --fw-semibold on nine of its eleven
   h1s. Those 600s were set in DIN 2014 Narrow, and Barlow Condensed at the same
   weight is visibly lighter — see ADR 0004. Keeping the number inverted the
   hierarchy: on the Toolkit the h1 and the group h2s are both 48px, so a 600
   headline read lighter than the 800 section headings beneath it.

   The reasoning this rule used to carry — that a long headline at 700+ goes
   dense and dark — was about DIN, and does not hold for a lighter, narrower
   face. Ward called it from a side-by-side, 28 Aug 2026. */
h1 {
  max-width: var(--measure-display);
  font-size: var(--fs-display-md);
  font-weight: var(--fw-black);
}

/* Every section heading in the prototype is --fs-h1 at --fw-black, on every
   page. --fs-h2 is the step below it, used inside cards and bands — those
   places set it themselves. So the base h2 takes the section-heading size, not
   the token that shares its name. */
h2 { font-size: var(--fs-h1); font-weight: var(--fw-black); }
h3 { font-size: var(--fs-h3); }

p { margin: 0; text-wrap: pretty; }

a {
  color: inherit;
  text-decoration: none;
}

img,
svg,
video { display: block; max-width: 100%; }

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

ul, ol { margin: 0; padding: 0; list-style: none; }

hr {
  border: 0;
  border-top: 1px solid var(--border-default);
  margin: 0;
}

/* Focus is visible and square, like everything else. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
}

/* Motion is minimal by design; honour the preference completely. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms;
    animation-iteration-count: 1;
    transition-duration: 0.01ms;
    scroll-behavior: auto;
  }
}


/* ----------------------------------------------------------------------------
   4. Layout primitives
   ---------------------------------------------------------------------------- */

/* Every section is a full-bleed band with a capped inner column. Register any
   measurement that must stay aligned to --col, not to a viewport percentage:
   percentage padding resolves against the containing block's width, which
   differs between a full-bleed band and the column inside it. */
.section {
  --col: min(100%, var(--container));
  padding: var(--sp-16) var(--sp-10);
  background: var(--bg-page);
}

.section__inner {
  max-width: var(--container);
  margin: 0 auto;
}

/* Grounds. No two adjacent sections may share one. */
/* Five sections in the prototype breathe wider than the other twenty-two:
   About's tour, both of Build Your Policies', Home's film and Media's. */
.section--tall { padding-top: var(--sp-20); padding-bottom: var(--sp-20); }
/* And the step below --sp-16, for a band that belongs with the section above it
   — the Toolkit's intro under its hero. */
.section--short { padding-top: var(--sp-12); padding-bottom: var(--sp-12); }
/* The Toolkit's hero closes tighter than it opens: the intro band below it is
   part of the same thought. */
.section--short-bottom { padding-bottom: var(--sp-12); }
/* And opens tighter: the Toolkit's list sits directly under the filter band, so
   --sp-10 above it rather than the default --sp-16. */
.section--tight-top { padding-top: var(--sp-10); }

.section--sunken { background: var(--bg-sunken); }
.section--muted  { background: var(--bg-muted); }
.section--dark   { background: var(--bg-dark); color: var(--text-on-dark); }
/* A dark band with nothing overhanging it clips at its own edge. The pages that
   DO host an overhanging Sam card carry .hero--hosts-card instead and must not
   clip — see the hero placement contract in section 19. */
.hero--clips     { position: relative; overflow: hidden; }
.section--dark h1,
.section--dark h2,
.section--dark h3 { color: var(--text-on-dark); }

/* ...except inside the Sam card, which is a LIGHT island in that dark band.

   .sam-card sets its own cream ground and ink text, but the rule above reaches
   any heading inside a dark section, so the card's story heading was painted
   --text-on-dark: white on cream, invisible. Not blank space, not a missing
   element — five headings of real copy that were being rendered and could not
   be seen, on Toolkit, Vet Your Vendors, Fund Your Fix, Find Your Gaps and
   Build Your Policies.

   The prototype's component sets no colour on that h2 at all; it simply inherits
   the card's ink, which is what this restores. Ward spotted it in a side-by-side
   on 28 Aug 2026 — the gap above "The team was doing many of the right things"
   is where "SAM NEEDED POLICIES" should have been.

   Scoped to .sam-card rather than fixing .section--dark, because that rule is
   right for every heading that really is on the dark ground. */
.section--dark .sam-card h1,
.section--dark .sam-card h2,
.section--dark .sam-card h3 { color: var(--text-strong); }


/* ----------------------------------------------------------------------------
   5. Site header

   Injected by site.js into <header data-site-header>. Two tiers, because nine
   nav links will not sit beside the lockup and two CTAs on one row.

   Height is reserved below so injection causes no layout shift.
   ---------------------------------------------------------------------------- */

/* MEASURED with the fonts loaded, and ROUNDED DOWN. site.js injects the header
   after the HTML parses, so this reserves its height and stops the page
   dropping when it lands.

   ROUND DOWN, NEVER UP. .sh carries the background and the border; the <header>
   around it is transparent. Reserve more than .sh occupies and the surplus sits
   BELOW that border as a stripe of page colour — against the dark hero that
   reads as a white line under the yellow nav strip. Reserve slightly less and
   the content simply sets the height, costing a sub-pixel shift nobody sees.

   .sh is 128.69px: 62 bar + 46.69 nav + 20 strip. The fraction is font metrics,
   which is why this is not a round number — re-measure after any change to the
   header's tiers, the nav's type, or the logo's height. It was 132px between
   31 Aug and 1 Sep 2026 and showed a 2.31px line.

   The background is repeated on the <header> as a second line of defence: with
   the border gone, any future overshoot spills header-coloured pixels rather
   than page-coloured ones, so getting this number slightly wrong stops being
   something a reader can see. */
[data-site-header] {
  display: block;
  min-height: 128px;
  background: var(--bg-page);
}

/* NO BOTTOM BORDER — a deliberate deviation from the prototype, 1 Sep 2026.

   The prototype's header carries border-bottom:1px solid var(--border-subtle),
   which is rgb(235,234,227). Every page on this site opens with a dark section,
   so that border never once separates the header from light content: it lands
   between the yellow nav strip and a rgb(35,31,32) hero, at 13.5:1 contrast,
   and reads as a thin white line. Ward reported it as exactly that.

   Checked before removing — all 15 pages open dark, and the mobile panel is
   position:absolute with its own background, so it overlays the hero rather
   than relying on this border to end the header.

   If a page is ever built that opens on a light section, it needs the border
   back, conditionally. */
.sh {
  position: relative;
  z-index: 101;   /* above the panel scrim — the burger stays lit */
  background: var(--bg-page);
}

/* Dims the page behind the open menu. Without it the cream panel meets a cream
   page and only the shadow separates them. Ink at 55%, not black — #000 is not
   in the palette, and the existing modals scrim with the same ink. */
.sh-scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(35, 31, 32, .55);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-out);
}
.sh-scrim[data-open="true"] { opacity: 1; pointer-events: auto; }

/* -- Top tier: lockup + controls ----------------------------------------- */

.sh-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  padding: var(--sp-3) var(--sp-10);
}

.sh-logo { display: inline-flex; align-items: center; }
/* 30px, not 40px: the file changed from the padded lockup export to the tight
   crop in assets/logos/, whose transparent margin is gone. 40 x 0.7414 = the
   same wordmark on screen. Ink measured, not eyeballed. */
.sh-logo img { height: 30px; width: auto; }

.sh-controls { display: flex; align-items: center; gap: var(--sp-3); }

/* One 38px box across the whole control row, so Share, the alert link and the
   toolkit button share a baseline. */
.sh-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: var(--control-h-sm);
  padding: 0 var(--sp-4);
  border: 2px solid var(--grey-300);
  background: transparent;
  color: var(--text-strong);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.sh-btn:hover {
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  border-color: var(--fixit-yellow);
}

/* The alert signup: yellow hairline at rest, filling on hover. Outlined, so the
   solid toolkit button still leads. */
.sh-btn--alert { border-color: var(--fixit-yellow); padding: 0 var(--sp-5); }

/* The one solid control. Filled beats outlined, so the toolkit leads. */
.sh-btn--solid {
  height: var(--control-h-sm);
  padding: 0 var(--sp-5);
  border: 2px solid var(--fixit-yellow);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  white-space: nowrap;
}
.sh-btn--solid:hover {
  background: var(--yellow-press);
  border-color: var(--yellow-press);
  color: var(--text-on-yellow);
}

.sh-burger {
  display: none;
  width: var(--touch-min);
  height: var(--touch-min);
  align-items: center;
  justify-content: center;
  border: 2px solid var(--fixit-ink);
  color: var(--fixit-ink);
}
.sh-burger span { display: flex; flex-direction: column; gap: 5px; width: 20px; }
.sh-burger i { display: block; height: 2px; background: currentColor; }

/* -- Share menu ----------------------------------------------------------- */

.sh-share { position: relative; display: inline-flex; }

/* [hidden] must beat the display rule below, or the menu is open on load: the
   UA stylesheet's display:none loses to any author display declaration. */
.sh-menu[hidden] { display: none; }

.sh-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  min-width: 280px;
  background: var(--fixit-white);
  border: 1px solid var(--border-ink);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
}
.sh-menu button,
.sh-menu a.sh-menu__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: 14px var(--sp-5);
  border-bottom: 1px solid var(--border-subtle);
  text-align: left;
  color: var(--text-strong);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
}
.sh-menu button:hover,
.sh-menu a.sh-menu__row:hover { background: var(--fixit-yellow); color: var(--text-on-yellow); }

/* -- The popover's head and its network tiles ----------------------------- */

.sh-menu__head { padding: var(--sp-4) var(--sp-5) 0; }
.sh-menu__eyebrow {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--text-muted);
}
/* The page being shared, named so the reader knows what the link will be. */
.sh-menu__title {
  margin-top: 2px;
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  color: var(--text-strong);
  text-wrap: pretty;
}

/* The campaign, under the page name. Quieter than the title — the page is
   what is being shared; this says where it is from. */
.sh-menu__sub {
  margin-top: 1px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--text-muted);
}

/* Asymmetric on purpose. The hover label already sits 15px below the tiles —
   3px of column gap and its own 12px line, both reserved whether or not it is
   showing — and the row beneath adds another 14px of its own padding. A
   symmetric --sp-4 here put 45px between the squares and "Copy link". --sp-1
   takes it to 30px and the two halves of the menu read as one block. */
.sh-nets {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5) var(--sp-1);
}

/* The menu is 280px minimum and anchors to its button's right edge. On a phone
   that pushes its left edge off screen, so below 480px it stops tracking the
   button and sits in the viewport instead. */
@media (max-width: 479.98px) {
  .td-share .sh-menu {
    position: fixed;
    top: auto;
    left: var(--sp-3);
    right: var(--sp-3);
    min-width: 0;
  }
}

/* The link is the column; the coloured square is the child. Keeping the
   background off the anchor is what lets the label sit outside it. */
.sh-net {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
/* THIS HAD NO FOCUS INDICATOR AT ALL. It said `outline: none; box-shadow:
   var(--shadow-focus)`, and --shadow-focus has never been defined — so the
   box-shadow was invalid and resolved to none while the `outline: none` applied
   perfectly well. Keyboard focus on the five share tiles was invisible, a
   WCAG 2.4.7 failure. Found 3 Sep 2026 by scanning every var() against the
   defined tokens, after the same mistake broke the modal close X.

   The ring goes OUTSIDE the tile via outline-offset, deliberately: the tiles
   carry the networks' own colours and an ink ring drawn on the X tile's black
   would be invisible. Offset, it lands on the menu's near-white ground at
   15.8:1 whichever tile has focus. Same 2px ink ring as the site's base rule. */
.sh-net:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* 48px, white glyph on the network's own colour. No border-radius: nothing on
   this site has one. */
.sh-net__tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  color: #FFFFFF;
  transition: opacity var(--dur-fast) linear;
}
.sh-net:hover .sh-net__tile { opacity: .82; }

/* RESERVED, NOT INSERTED. The label holds its line at all times and only its
   opacity changes, so revealing it cannot grow the popover under the pointer —
   the same reason the header reserves its own height. */
.sh-net__label {
  font-size: 10px;
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  letter-spacing: .01em;
  color: var(--text-body);
  opacity: 0;
  transition: opacity var(--dur-fast) linear;
}
.sh-net:hover .sh-net__label,
.sh-net:focus-visible .sh-net__label { opacity: 1; }

.sh-net--linkedin .sh-net__tile { background: var(--net-linkedin); }
.sh-net--facebook .sh-net__tile { background: var(--net-facebook); }
.sh-net--x        .sh-net__tile { background: var(--net-x); }
.sh-net--whatsapp .sh-net__tile { background: var(--net-whatsapp); }
.sh-net--sms      .sh-net__tile { background: var(--net-sms); }

/* TEXT IS FOR PHONES. sms: opens nothing on most desktops, so the tile is
   hidden unless the device reports a coarse pointer. Not a width query — a
   narrow browser window on a laptop is not a phone, and would get a tile that
   does nothing. The trade is that a Mac with Messages could have handled it
   and will not be offered it. */
.sh-net--sms { display: none; }
@media (pointer: coarse) {
  .sh-net--sms { display: inline-flex; }
}

/* -- The detail page's own share control ---------------------------------- */

/* The prototype puts this in the hero's eyebrow row, pushed to the right.
   position and display are its own rather than .sh-share's: that class hides
   below 1130px for the header's benefit, and this control must not. */
.td-share {
  position: relative;
  display: inline-flex;
  margin-left: auto;
}
.td-share__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 0;
  border: 0;
  background: transparent;
  color: var(--text-on-dark);
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: var(--fw-bold);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  cursor: pointer;
  transition: color var(--dur) var(--ease-out);
}
.td-share__btn:hover { color: var(--fixit-yellow); }
.sh-menu svg { flex: 0 0 auto; }
.sh-menu p {
  padding: 10px var(--sp-5) 12px;
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--text-muted);
}

/* -- Second tier: the nav ------------------------------------------------- */

.sh-nav {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  column-gap: 2px;
  padding: 0 var(--sp-10);
  border-top: 1px solid var(--border-subtle);
}

/* Every item is wrapped whether or not it has children: the description strip
   maps nav children onto keys by index, so the 1:1 mapping must hold. */
.sh-item { position: relative; display: flex; align-items: stretch; }

.sh-link {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: var(--sp-3) var(--sp-4);
  color: var(--text-strong);
  font-size: var(--fs-sm);
  font-weight: var(--fw-nav);
  white-space: nowrap;
  transition: box-shadow var(--dur-fast) linear;
}

/* Hover paints a 2px yellow rule as a background layer, held 6px clear of the
   description strip so the two yellows never touch. */
.sh-item:hover .sh-link {
  background: linear-gradient(var(--fixit-yellow), var(--fixit-yellow))
              no-repeat 0 calc(100% - 6px) / 100% 2px;
}

.sh-link[aria-current="page"] {
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  font-weight: var(--fw-bold);
  box-shadow: inset 0 -1px 0 var(--fixit-white);
}
.sh-item:hover .sh-link[aria-current="page"] { background: var(--fixit-yellow); }

/* -- Third tier: the description strip -------------------------------------
   A 20px yellow band whose text centres under the link it describes. It appears
   instantly — a slide read as too much movement for a 12px helper line.
   -------------------------------------------------------------------------- */

.sh-strip {
  position: relative;
  height: 20px;
  overflow: hidden;
  background: var(--fixit-yellow);
  color: var(--fixit-ink);
  font-size: 12px;
  line-height: 20px;
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.sh-strip span { position: absolute; top: 0; left: 0; }

/* -- Mobile panel ---------------------------------------------------------- */

/* The panel floats over the page rather than reflowing it — a menu that pushes
   the content down moves whatever the reader was looking at. It is genuinely
   floating UI, which is the one case where a shadow is allowed. */
.sh-panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 100;
  max-height: 80vh;
  overflow-y: auto;
  background: var(--bg-page);
  border-top: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-lg);
}
.sh-panel a,
.sh-panel button {
  display: block;
  width: 100%;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-subtle);
  border-left: 8px solid transparent;
  text-align: left;
  color: var(--text-strong);
  font-size: var(--fs-body);
  font-weight: var(--fw-nav);
  transition: background var(--dur-fast) linear, border-color var(--dur-fast) linear;
}
/* Grey on hover, not yellow — the active item already owns yellow here. */
.sh-panel a:hover,
.sh-panel button:hover { background: var(--grey-100); border-left-color: var(--fixit-yellow); }
.sh-panel a[aria-current="page"] { border-left-color: var(--fixit-yellow); font-weight: var(--fw-bold); }
/* The two CTAs. The highlight behind the text carries the emphasis, so the row
   keeps only the sunken ground that separates them from the nav above. */
.sh-panel .sh-panel-cta {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--bg-sunken);
}
.sh-panel .sh-panel-cta:hover { background: var(--grey-200); }

/* Wider than the default .16em so the mark reads as a hand pass that overshoots
   the words at both ends, rather than a tight box around them.

   inline-flex rather than inline-block because the share glyph sits inside the
   mark: an SVG in an inline-block aligns to the text baseline and sits low. */
.sh-panel .sh-panel-cta .fx-highlight {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding-left: .42em;
  padding-right: .42em;
  font-weight: var(--fw-bold);
}

.sh-panel .sh-panel-cta .fx-highlight svg { flex: 0 0 auto; }

/* -- The one breakpoint the header owns -----------------------------------
   At 1024–1129px the nav was already wrapping against the CTAs, so the whole
   second tier collapses into a menu and controls take the 44px touch floor.
   -------------------------------------------------------------------------- */

@media (max-width: 1129.98px) {
  [data-site-header] { min-height: 68px; }   /* .sh is 68px here — see the base rule */
  .sh-bar { padding-left: var(--sp-5); padding-right: var(--sp-5); }
  .sh-nav, .sh-strip { display: none; }
  /* HIDE THE TRIGGER, NOT THE WRAPPER. `.sh-share { display: none }` also took
     the share MENU out of layout, because the menu is its child. The mobile
     panel's "Share this site" forwards its click to this button, so the
     handler ran, the menu unhid — and then measured 0x0 inside a
     display:none parent. Nothing appeared, which is the bug Ward reported on
     3 Sep 2026. The wrapper is inline-flex and collapses to zero width with no
     button inside it, so the bar's layout is unchanged. */
  .sh-share > .sh-btn, .sh-btn--alert { display: none; }
  /* Anchored to that zero-width wrapper the menu lands 154px off-screen to the
     left, measured at 375px. So at this width it stops being a popover and
     becomes a sheet under the bar, which also gives the tiles room. */
  .sh-share .sh-menu {
    position: fixed;
    top: 68px;                  /* the bar's height at this breakpoint */
    left: var(--sp-5);
    right: var(--sp-5);
    width: auto;
    min-width: 0;
  }
  .sh-burger { display: inline-flex; }
  .sh-btn--solid { height: var(--touch-min); }
  .sh-panel[data-open="true"] { display: block; }
}

/* Below ~480px the lockup, the toolkit button and the burger stop fitting on one
   row — the button wrapped to two lines. The lockup gives 30px and the button
   tightens its padding; the label itself is approved copy and does not change. */
@media (max-width: 479.98px) {
  /* No min-height override here. This block used to reserve 61px on the
     assumption that the 32px lockup set the header's height below 480px. It
     does not — the Toolkit button does, at the 44px touch minimum, so the
     header stays 69px all the way down and the 61px reserve dropped every
     narrow page by 8px on load. The 69px above applies. */
  .sh-bar { gap: var(--sp-3); padding-left: var(--sp-4); padding-right: var(--sp-4); }
  .sh-logo img { height: 24px; }   /* 32 x 0.7414 — see the base rule */
  .sh-btn--solid { padding: 0 var(--sp-3); }
}


/* ----------------------------------------------------------------------------
   6. Buttons, eyebrows
   ---------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 15px var(--sp-8);
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  transition: background var(--dur-fast) linear, color var(--dur-fast) linear;
}

.btn--solid { background: var(--fixit-yellow); color: var(--text-on-yellow); }
.btn--solid:hover { background: var(--yellow-press); }
.btn--solid:disabled {
  background: var(--grey-200);
  color: var(--grey-500);
  cursor: not-allowed;
}

.btn--ghost {
  padding: 15px var(--sp-6);
  border: 1px solid var(--grey-400);
  color: var(--text-body);
}
.btn--ghost:hover { background: var(--grey-100); color: var(--fixit-ink); }

/* Ink, not muted: an eyebrow that labels a section heading is ink in the
   prototype. Muted is for the caption case — a label annotating something
   rather than introducing it. */
.eyebrow {
  /* THE BODY FACE, NOT THE DISPLAY FACE, and this is the whole reason eyebrows
     read undersized.

     The prototype's eyebrows set no font-family at all, so they inherit the body
     face — din-2014, normal width. This rule named --font-display, which is the
     condensed cut. At an identical 12px with identical tracking, a condensed
     face is visibly narrower and reads as smaller type, which is exactly how
     Ward described it: "the font sizes are off".

     Checked across the prototype rather than from one page: 29 eyebrow-tracked
     elements on home, about, toolkit and where-to-begin set no family. Five on
     vet-your-vendors do set --font-display, which is the prototype contradicting
     itself — recorded in OPEN-ITEMS 125 rather than averaged away here. */
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  /* 700, as the prototype has it. This was briefly 800, to compensate for the
     condensed face above going thin under the eyebrow's tracking — a fix for a
     problem that only existed because the face was wrong. Normal-width Barlow
     carries the tracking at 700. */
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--text-strong);
}
/* The caption role: a label annotating something rather than introducing it.
   Partner names, filter labels, table column heads, modal captions. Ten users,
   so this names a role rather than preserving a one-off. */
.eyebrow--muted { color: var(--text-muted); }
/* Sam's own colour, for the eyebrow that introduces his route. */
.eyebrow--sam { color: var(--sam); }


/* ----------------------------------------------------------------------------
   7. Modal shell

   Shared by the alert signup and the footer's document modal. Floating UI, so
   this is one of the few places a shadow is allowed.
   ---------------------------------------------------------------------------- */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--sp-6);
  background: rgba(35, 31, 32, .86);
}
.modal-overlay[data-open="true"] { display: flex; }

.modal-panel {
  position: relative;
  width: 100%;
  max-width: 900px;
  max-height: 100%;
  overflow: auto;
  background: var(--fixit-white);
  /* Not --shadow-lg. That is the card shadow, 44px at 12%, and under a modal on
     an 86% scrim it does nothing. The prototype throws a real one. */
  box-shadow: 0 32px 90px rgba(0, 0, 0, .55);
}

/* 860px for the email template. The signup keeps the shared 900 and the
   footer's document modal is 880 — three widths, each stated in the prototype. */
.modal-panel--tpl { max-width: 860px; }

/* The yellow edge that marks a panel as the campaign's own. */
.modal-rule { height: 10px; background: var(--fixit-yellow); }

.modal-close {
  position: absolute;
  top: 10px;
  right: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  color: var(--fixit-ink);
  transition: background var(--dur-fast) linear;
}
.modal-close:hover { background: var(--fixit-yellow); }


/* ----------------------------------------------------------------------------
   8. Alert signup

   Email-only, in a modal rather than a page — the ask is one field, and sending
   people to a page loses them.
   ---------------------------------------------------------------------------- */

.signup { display: flex; flex-wrap: wrap; align-items: stretch; }

.signup__aside {
  flex: 0 0 288px;
  min-width: 240px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: var(--sp-10) var(--sp-8);
  background: var(--fixit-ink);
}
.signup__aside img { width: 100%; max-width: 167px; height: auto; }   /* 188 x 0.8883 */
.signup__aside p {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--grey-400);
}

.signup__body {
  flex: 1;
  min-width: 320px;
  display: flex;
  flex-direction: column;
  padding: var(--sp-10) var(--sp-10) var(--sp-8);
}
.signup__body h2 {
  margin: var(--sp-3) var(--sp-8) var(--sp-5) 0;
  font-size: var(--fs-h1);
  font-weight: var(--fw-black);
}
.signup__lede {
  margin: 0 0 var(--sp-8);
  padding-bottom: var(--sp-8);
  border-bottom: 1px solid var(--border-subtle);
  line-height: var(--lh-body);
}

.signup form { display: flex; flex-direction: column; gap: var(--sp-6); }
.signup__field { display: flex; flex-direction: column; gap: var(--sp-2); }
.signup__field span {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--fixit-ink);
}
.signup__field input {
  width: 100%;
  padding: 14px var(--sp-4);
  border: 1px solid var(--grey-400);
  background: var(--fixit-white);
  color: var(--fixit-ink);
  font-size: var(--fs-body);
  line-height: 1.4;
}
.signup__field input:focus {
  outline: none;
  border-color: var(--fixit-ink);
  box-shadow: inset 0 -3px 0 var(--fixit-yellow);
}

/* Autofill, in the campaign's own wash rather than the browser's blue.

   `background` cannot do this. Chrome paints the autofill ground itself and
   ignores the property, so the only lever is an inset box-shadow spread wide
   enough to cover the field — hence the 100px, which is a fill, not a shadow.

   Two shadows because --yellow-veil is a 22% wash and a shadow composites over
   whatever the browser painted, not over our white. Laying the veil on a solid
   --fixit-white beneath it reproduces the intended colour without inventing a
   flattened literal. First shadow paints on top, so the order reads backwards.

   --fixit-yellow at full strength is ruled out by the palette itself: "never a
   large field, never a ground". --yellow-veil is the token for exactly this.

   The prefixed and standard selectors are SEPARATE RULES on purpose. In one
   selector list, a browser that does not know :autofill would discard the whole
   rule, taking the working prefixed half with it. */
.signup__field input:-webkit-autofill {
  box-shadow: inset 0 0 0 100px var(--yellow-veil),
              inset 0 0 0 100px var(--fixit-white);
  -webkit-text-fill-color: var(--fixit-ink);
}
.signup__field input:autofill {
  box-shadow: inset 0 0 0 100px var(--yellow-veil),
              inset 0 0 0 100px var(--fixit-white);
  -webkit-text-fill-color: var(--fixit-ink);
}

/* Focused and autofilled at once: the yellow underline still has to read, so it
   is laid over the wash rather than replaced by it. */
.signup__field input:-webkit-autofill:focus {
  box-shadow: inset 0 -3px 0 var(--fixit-yellow),
              inset 0 0 0 100px var(--yellow-veil),
              inset 0 0 0 100px var(--fixit-white);
}
.signup__field input:autofill:focus {
  box-shadow: inset 0 -3px 0 var(--fixit-yellow),
              inset 0 0 0 100px var(--yellow-veil),
              inset 0 0 0 100px var(--fixit-white);
}

.signup__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
.signup__note { font-size: var(--fs-xs); line-height: var(--lh-body); color: var(--text-muted); }

/* A failed signup, said out loud rather than left to a disabled button.

   Borrows .doc-section--warn's treatment — yellow ground, bold ink — instead of
   introducing a red. The design system has no error colour, and one added here
   for a single message would be a new token nobody chose. Yellow on ink is the
   campaign's own way of raising its voice. */
.signup__error {
  padding: var(--sp-4) var(--sp-5);
  background: var(--fixit-yellow);
  color: var(--fixit-ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: var(--lh-body);
}

/* Confirmation replaces the form in place, so the submit is never silent. */
.signup__done { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-5); }
.signup__done p { line-height: var(--lh-body); }
.signup__done strong { color: var(--fixit-ink); }


/* ----------------------------------------------------------------------------
   9. Toast

   Confirms a copy. role="status" + aria-live so it is announced without
   stealing focus.
   ---------------------------------------------------------------------------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: var(--sp-8);
  z-index: 9100;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 14px var(--sp-6);
  background: var(--fixit-ink);
  color: var(--fixit-white);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateX(-50%) translateY(12px);
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.toast[data-open="true"] { opacity: 1; transform: translateX(-50%) translateY(0); }

.toast__mark {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
}
/* A failure keeps the ink bar but drops the yellow badge: yellow is the
   campaign's affirmative mark, and a cross sitting in it reads as a success
   someone forgot to update. Outlined instead, so the shape carries the meaning
   rather than the colour — which also survives being read in greyscale. */
.toast--failed .toast__mark {
  background: none;
  box-shadow: inset 0 0 0 2px var(--fixit-white);
  color: var(--fixit-white);
}
.toast__text { font-size: var(--fs-body); font-weight: var(--fw-semibold); line-height: 1.2; white-space: nowrap; }

@media (max-width: 620px) {
  .signup__aside { flex: 1 1 100%; }
  /* min-width with the padding: a 320px floor plus --sp-6 either side is wider
     than a 375px screen, and the modal cannot scroll sideways out of it. */
  .signup__body { padding: var(--sp-8) var(--sp-6) var(--sp-6); min-width: 0; }

  /* THE CLOSE X WAS INVISIBLE, NOT MISSING. The aside is full-width above the
     body here, so the control at top-right sits on ink instead of on the light
     panel — and its colour is ink. Ward reported the button as absent, which is
     exactly how an ink X on ink reads. Measured at 15.84:1 now.

     THE TOKEN NAME MATTERS. This first said var(--fixit-cream), which does not
     exist — an unresolved var() on `color` is invalid at computed-value time,
     so it fell back to an inherited grey and measured 1.57:1. It looked fixed
     on screen; only reading the computed colour caught it.

     UNSCOPED ON PURPOSE. .modal-close belongs to the alert signup modal and
     nothing else — the legal-document modal has its own .doc-modal__close. If
     a light-panelled modal ever reuses .modal-close, this rule will need a
     modifier to sit behind.

     The hover returns to ink, because the base :hover fills the button yellow
     and white on yellow is 1.28:1 — the trap the consent-banner comment
     describes. Ink on yellow is 12.69:1. */
  .modal-close { color: var(--fixit-white); }
  .modal-close:hover { color: var(--fixit-ink); }
}


/* ----------------------------------------------------------------------------
   10. The two devices

   Highlight marks the RIGHT action; strike marks the WRONG choice. Never both on
   the same phrase, never more than one highlight per headline, and never redraw
   the strike as a straight rule or a CSS line-through.
   ---------------------------------------------------------------------------- */

.fx-highlight {
  background: var(--fixit-yellow);
  color: var(--fixit-ink);
  padding: .06em .16em .04em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* The tilt imitates a hand pass of a highlighter; level reads as a UI chip.
   Rotation needs inline-block, so a tilted highlight is for short phrases. */
.fx-highlight--tilt { display: inline-block; transform: rotate(-0.7deg); }

/* The unrotated wash, for long body runs where a solid block would shout. */
.fx-highlight--soft { background: var(--yellow-veil); }

.fx-strike { position: relative; display: inline-block; color: var(--fixit-ink); }
.fx-strike::after {
  content: "";
  position: absolute;
  left: -3%;
  right: -3%;
  top: 52%;
  height: .34em;
  transform: translateY(-50%);
  background-image: var(--strike-svg);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  pointer-events: none;
}
.fx-strike--light::after { filter: invert(1) brightness(2.4); }

.eyebrow--on-dark { color: var(--fixit-yellow); }


/* ----------------------------------------------------------------------------
   11. Site footer

   Injected into <footer data-site-footer>. A footer is chrome — a utility bar of
   marks and links with no argument of its own — so its dark ground does not
   count against the two-dark-sections-per-page ceiling.
   ---------------------------------------------------------------------------- */

[data-site-footer] { display: block; }

/* The tagline band. Hidden on Home, where the hero already carries the line. */
.sf-tagline {
  padding: 52px var(--sp-10);
  margin-bottom: 12px;
  background: var(--bg-dark);
  color: var(--text-on-dark);
  border-top: 1px solid var(--border-on-dark);
}
.sf-tagline p {
  max-width: var(--container);
  margin: 0 auto;
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
}

.sf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-6);
  padding: var(--sp-8) var(--sp-10);
  background: var(--bg-dark);
  color: var(--grey-300);
  border-top: 1px solid var(--border-on-dark);
  font-size: var(--fs-sm);
}

/* Clear of OneTrust's floating icon.

   That icon is position: fixed at the bottom-left of the VIEWPORT, 50px wide and
   16px from the edge, so it reaches x=66 — and the footer's own 40px gutter put
   the TPN mark straight underneath it. Ward saw it sitting on the logo.

   Only the marks move, not the footer's padding: the links on the right are
   nowhere near it, and widening the whole bar would pull them in for no reason.
   --sp-12 puts the first mark at 88px, comfortably past the icon rather than
   just touching it, since the icon grows a tooltip on hover.

   The real fix is the icon's position, which is a setting in MPA's tenant — ask
   them on Monday. If they move or disable it, drop this back to 0. */
.sf-marks {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  flex-wrap: wrap;
  padding-left: var(--sp-12);
}
.sf-mark { display: block; width: auto; opacity: .85; }

.sf-links { display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap; }
.sf-link { color: var(--grey-300); }
.sf-link:hover { color: var(--text-on-dark); }
/* ----------------------------------------------------------------------------
   12. Legal document pages

   Privacy and Terms are real pages rather than a modal: the cookie consent
   banner needs a URL to link to, and these need to be printable and citable.
   Generated by scripts/build-legal.py — never edited by hand.

   THESE PAGES ARE THEIR OWN STYLE SCOPE. Ward, 4 Sep 2026, on the new privacy
   policy: "You can treat the Privacy Policy with it's own set of styles and
   guidelines. We don't want to modify the main site UI to accomodate the the
   privacy policies."

   So the rule for this section is: every selector carries a .doc, .doc-section
   or .doc-table hook, and nothing here reaches a shared component. That is
   also what lets this section do things the rest of the site does not — the
   h3 below opts out of the site-wide `h1..h6 { text-transform: uppercase }`,
   which would be a much bigger conversation anywhere else.

   The two exceptions in the 620px query at the end are .sf and .sf-tagline,
   which are SITE FOOTER rules that were already filed here before any of this.
   They are left where they are rather than moved, because relocating a rule
   changes its source order and both of those win ties on it. They are not part
   of the legal-page scope.
   ---------------------------------------------------------------------------- */

.doc { max-width: var(--measure-body); }
.doc__meta {
  margin-bottom: var(--sp-8);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--text-muted);
}

.doc-section + .doc-section { margin-top: var(--sp-6); }
.doc-section h2 {
  margin-bottom: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--fs-body);
  font-weight: var(--fw-black);
  line-height: 1.15;
}
.doc-section p + p,
.doc-section ul { margin-top: var(--sp-3); }
.doc-section p { font-size: var(--fs-sm); line-height: var(--lh-body); color: var(--text-body); }
.doc-section li {
  position: relative;
  padding-left: var(--sp-5);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--text-body);
}
.doc-section li + li { margin-top: var(--sp-2); }
.doc-section li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .6em;
  width: 6px;
  height: 6px;
  background: var(--fixit-ink);
}

/* Bold ink on solid yellow, not yellow type: yellow text would be illegible on
   the cream ground and would break the highlight-only rule. */
.doc-section--warn {
  padding: var(--sp-5) var(--sp-6);
  background: var(--fixit-yellow);
}
.doc-section--warn p { color: var(--fixit-ink); font-weight: var(--fw-bold); }

/* -- SUB-HEADINGS ---------------------------------------------------------
   Added 4 Sep 2026 with the new TPN privacy policy, which is the first legal
   document on the site to have a third level: "Information We Obtain By
   Automated Means" under "Information We Obtain", and four more under the
   California statement.

   NO RULE ABOVE IT, unlike h2. The border on h2 is what separates one section
   from the next, and repeating it here would make a subsection look like a
   peer.

   text-transform: none, WHICH OVERRIDES THE SITE-WIDE `h1..h6 { uppercase }`.
   This is the one place that rule is opted out of, and it is a deliberate
   deviation rather than an oversight — worth reading before reverting it.

   WHY. Every heading on this site is uppercased in CSS, so an h3 arrived
   looking almost exactly like an h2: both caps, both weight 800, 14px against
   16px, separated only by the h2's border. On a document that genuinely nests
   three levels deep, that made the nesting invisible. The other reason is
   length — the longest sub-heading here is "Disclosure of Personal Information
   for a Business Purpose", 57 characters, and 57 characters of 14px caps is a
   wall rather than a label.

   Sentence case under a caps heading is unambiguous at a glance and costs
   nothing else. The alternative considered was keeping caps and dropping to
   12px with letter-spacing, matching .doc__meta and .doc-table th — rejected
   because it would then look like a table header, which is a different thing
   sitting directly beside it on this very page.

   THE BLAST RADIUS IS ONE PAGE. .doc-section h3 exists only on the legal
   pages, and only privacy has any today. */
.doc-section h3 {
  margin-top: var(--sp-5);
  margin-bottom: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-black);
  line-height: 1.2;
  text-transform: none;
}
.doc-section h3:first-child { margin-top: 0; }

/* -- INLINE EMPHASIS IN LEGAL PROSE --------------------------------------
   The source document leans on bold lead-ins ("Identifiers:", "How to Submit
   a Request.") and on italics for defined terms. Both arrive as real elements
   from build-legal.py, so they only need weights that match the page.

   THE DOCUMENT'S UNDERLINES DO NOT ARRIVE AS UNDERLINES, deliberately — see
   render_runs() in scripts/build-legal.py. .inline-link is a 2px yellow
   underline because this palette has no link colour, so an underlined defined
   term would read as a link on the one page where a visitor is hunting for
   something to click. Italic+underline becomes <em>; a bare underline becomes
   <strong>. Do not "restore" underlining here. */
.doc-section strong { font-weight: var(--fw-bold); }
.doc-section em { font-style: italic; }

/* -- REFERENCE TABLES ----------------------------------------------------
   The California statement carries three: purposes against data categories,
   sources against categories, and third parties against categories.

   THE WRAPPER SCROLLS, THE TABLE DOES NOT SHRINK. Between the stacked layout
   below and the full two-column one, a long purpose cell would otherwise
   force the table wider than .doc's measure and push the whole page sideways.
   Overflow on a wrapper keeps that contained to the table. */
.doc-table__scroll {
  margin-top: var(--sp-4);
  overflow-x: auto;
}
.doc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  text-align: left;
}
.doc-table th,
.doc-table td {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-subtle);
  vertical-align: top;
  color: var(--text-body);
}
/* AUTO LAYOUT GETS THIS BADLY WRONG HERE. Left to itself the browser gave the
   first column 696px and the second 203px, because column one is one long
   sentence and column two is a list of short items — so the categories wrapped
   four lines deep beside a half-empty paragraph. Measured. A width hint on the
   header rebalances it without table-layout: fixed, which would clip
   "Cal. Civ. Code § 1798.80" rather than wrap it. */
.doc-table th:first-child { width: 58%; }
.doc-table th {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--fixit-ink);
  background: var(--bg-sunken);
}
/* The first column names the thing; the second lists categories against it.
   Weighting the first stops two columns of grey prose reading as one block. */
.doc-table td:first-child { font-weight: var(--fw-bold); color: var(--fixit-ink); }
.doc-table p { font-size: inherit; }
.doc-table ul { margin-top: 0; }
.doc-table li { font-size: inherit; }
.doc-table p + ul,
.doc-table ul + p { margin-top: var(--sp-2); }

@media (max-width: 620px) {
  .sf-tagline { padding: var(--sp-8) var(--sp-5); }
  .sf { padding: var(--sp-6) var(--sp-5); }

  /* EACH ROW BECOMES A CARD. Two columns of this content cannot be read at
     375px — the widest first cell is 280 characters. The header row is hidden
     and each cell prints its own column heading from the data-label that
     build-legal.py copies out of <thead>, so the pairing that makes the table
     mean anything survives losing the columns. */
  .doc-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .doc-table,
  .doc-table tbody,
  .doc-table tr,
  .doc-table td { display: block; width: auto; }
  .doc-table tr + tr { margin-top: var(--sp-4); }
  .doc-table tr {
    border: 1px solid var(--border-subtle);
    padding: var(--sp-4);
  }
  .doc-table td { border: 0; padding: 0; }
  .doc-table td + td { margin-top: var(--sp-3); }
  .doc-table td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: var(--sp-1);
    font-family: var(--font-display);
    font-size: var(--fs-xs);
    font-weight: var(--fw-black);
    text-transform: uppercase;
    letter-spacing: var(--ls-eyebrow);
    color: var(--text-muted);
  }
  /* The desktop rule weights the first column to separate it from the second.
     Stacked, the data-label already does that, and bold 280-character prose
     is harder to read than plain. */
  .doc-table td:first-child { font-weight: var(--fw-regular); color: var(--text-body); }
}


/* ----------------------------------------------------------------------------
   13. Legal document modal

   Progressive enhancement over the real pages. The footer links are ordinary
   anchors to /privacy.html, /terms.html and /cookies.html; JavaScript intercepts
   a plain left-click and shows the document in place. Modified clicks, a failed
   fetch and no JavaScript all fall through to the page itself.
   ---------------------------------------------------------------------------- */

.doc-modal {
  position: fixed;
  inset: 0;
  z-index: 9200;
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding: var(--sp-10) var(--sp-6);
  background: rgba(35, 31, 32, .72);
}
.doc-modal[data-open="true"] { display: flex; }

.doc-modal__backdrop { position: absolute; inset: 0; }

.doc-modal__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 880px;
  max-height: 100%;
  background: var(--fixit-white);
  border: 1px solid var(--border-ink);
  box-shadow: var(--shadow-lg);
}

.doc-modal__head {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-6);
  padding: var(--sp-6) var(--sp-8);
  background: var(--bg-dark);
  color: var(--text-on-dark);
}
.doc-modal__titles { display: flex; flex-direction: column; gap: var(--sp-2); }
.doc-modal__title {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
}
.doc-modal__meta {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--grey-300);
}

.doc-modal__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 2px solid var(--border-on-dark);
  color: var(--text-on-dark);
  transition: background var(--dur-fast) linear, border-color var(--dur-fast) linear;
}
.doc-modal__close:hover {
  background: var(--fixit-yellow);
  border-color: var(--fixit-yellow);
  color: var(--text-on-yellow);
}

.doc-modal__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-8);
}
/* The document renders exactly as it does on its own page — same markup, same
   rules — so the two can never drift apart. */
.doc-modal__body .doc { max-width: none; }
.doc-modal__body .doc__meta { display: none; }   /* already shown in the head */

.doc-modal__loading {
  padding: var(--sp-8);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}


/* ----------------------------------------------------------------------------
   14. Accordion

   Built on <details>/<summary>, so it opens without JavaScript and the answers
   are always in the DOM to be found and indexed. JavaScript adds only the
   one-open-at-a-time rule where a page asks for it.

   The marker is a 36px square +/– filling yellow when open or hovered. No
   rotation: the system's motion is minimal, and a rotating chevron is a
   generic-UI tell this site does not speak.
   ---------------------------------------------------------------------------- */

.accordion { border-top: 1px solid var(--border-default); }
.accordion__item { border-bottom: 1px solid var(--border-default); }

.accordion__q {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: var(--sp-6) 0;
  cursor: pointer;
  list-style: none;
}
.accordion__q::-webkit-details-marker { display: none; }
.accordion__q:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; }

.accordion__label {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  color: var(--text-strong);
  text-wrap: pretty;
}

.accordion__mark {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 2px solid var(--border-ink);
  background: transparent;
  color: var(--text-strong);
  font-size: 22px;
  font-weight: var(--fw-bold);
  line-height: 1;
  transition: background var(--dur-fast) linear,
              color var(--dur-fast) linear,
              border-color var(--dur-fast) linear;
}
.accordion__q:hover .accordion__mark,
[open] > .accordion__q .accordion__mark {
  background: var(--fixit-yellow);
  border-color: var(--fixit-yellow);
  color: var(--text-on-yellow);
}

/* The glyph swaps rather than rotating. */
.accordion__mark::before { content: "+"; }
[open] > .accordion__q .accordion__mark::before { content: "–"; }

.accordion__a {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
  padding: 0 var(--sp-16) var(--sp-8) 0;
}
/* Answers are set narrower than the question column: a line that runs the full
   width of a question is hard to scan back from. */
.accordion__a p { max-width: 760px; line-height: var(--lh-body); }

.accordion__link {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  border-bottom: 2px solid var(--fixit-yellow);
}

@media (max-width: 620px) {
  .accordion__q { gap: var(--sp-4); }
  .accordion__a { padding-right: 0; }
}


/* ----------------------------------------------------------------------------
   15. Prose

   The four-step measure scale. At --fs-lead a 1000px measure runs long, and that
   is a deliberate call: a half-width block reads as broken layout on a wide
   monitor, which is the worse problem. If long lines become the complaint, the
   dial is the standalone-prose step, not the scale.
   ---------------------------------------------------------------------------- */

/* --measure-body, not --measure-prose: 15 of the prototype's 19 lead
   paragraphs are capped at 900px. --measure-prose is for the wrapper of a
   section intro, which is a different measurement. */
.lead {
  max-width: var(--measure-body);
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  color: var(--text-body);
}
/* --grey-200, not --grey-300. --grey-300 is the qualifier's colour; prose on
   an ink ground sits one step brighter. */
.lead--on-dark { color: var(--text-on-dark-prose); }
.lead--muted { color: var(--text-muted); }

/* Body-size prose stepped back to the muted grey. A section intro is muted on
   About and not on Build Your Policies, so it is a class rather than a rule on
   .stack--intro p. */
.muted { color: var(--text-muted); }

/* The heading that opens a page's main content section, one tier above an
   ordinary section heading. Eight instances across seven pages in the
   prototype, all at --fs-display-md, and all capped at --measure-prose — a
   48px headline running the full 1240px container is four words a line. */
.h2--display { font-size: var(--fs-display-md); max-width: var(--measure-prose); }

/* Body-size prose on an ink ground. Not a lead — a hero that runs to three
   paragraphs steps the last one down so the argument still has a shape. Same
   colour as the lead: the step is in the size, not the brightness. */
.body-on-dark {
  max-width: var(--measure-body);
  line-height: var(--lh-body);
  color: var(--text-on-dark-prose);
}
/* And a step louder: full white and semibold, for the line that says who the
   page is for — "If you touch content, this is for you".

   NOT AN EXCEPTION ANY MORE. It reads on all three pages that carry that
   sentence, as of 4 Sep 2026. Home reaches the same place through .hero__who,
   which is identical output under a different name because it also carries a
   hero's margin and measure. About was the odd one out — lead size, regular
   weight, --grey-200 — and Ward's call was to match: OPEN-ITEMS 106. */
.body-on-dark--strong { font-weight: var(--fw-semibold); color: var(--text-on-dark); }
/* One step back from --grey-200. Find Your Gaps states --grey-300 for the line
   under its hero lead, and only there — the other four .body-on-dark paragraphs
   on the site are --grey-200, so this is the exception rather than the rule. */
.body-on-dark--dim { color: var(--grey-300); }

/* Measures as utilities, for a paragraph that carries no component class of its
   own. The prototype states them inline on the element itself. */
.measure-body { max-width: var(--measure-body); }

.eyebrow--lead { font-size: var(--fs-lead); }

/* A section heading takes space beneath it; an eyebrow takes space above it. */
.eyebrow + h1,
.eyebrow + h2 { margin-top: var(--sp-4); }
h1 + .lead,
h2 + .lead { margin-top: var(--sp-4); }

/* FAQ's column is 900px and its answers 760px — a one-line question spanning the
   full 1240px container is hard to scan back from. */
.faq { max-width: var(--measure-body); }


/* ----------------------------------------------------------------------------
   16. Small-screen type scale

   The scale is authored in fixed pixels for desktop. Below 620px a 48px
   condensed headline runs to five lines and the hero swallows the viewport
   before any content appears.

   Scoped to 620px deliberately: that is where the prototype's design stops, so
   nothing at or above it changes. Desktop and tablet render exactly as before.
   ---------------------------------------------------------------------------- */

@media (max-width: 620px) {
  :root {
    --fs-display-xl: 44px;
    --fs-display-lg: 38px;
    --fs-display-md: 32px;
    --fs-h1:         28px;
    --fs-h2:         24px;
    --fs-h3:         20px;
    --fs-lead:       17px;
  }

  .section { padding: var(--sp-12) var(--sp-5); }
}


/* ----------------------------------------------------------------------------
   17. Rating strip

   ONE implementation. The prototype kept three, hand-synced. This owns the L/M/H
   scale words, the level-to-tone map and the tooltip format, and nothing outside
   it decides what yellow means.

   The ramp tracks the LEVEL, not desirability: colour means more of the thing
   measured, not "good". A solid-yellow Time means it takes a lot; a solid-yellow
   Impact means it is worth a lot. The reader gets the judgement from the label
   and the word, never from the fill.
   ---------------------------------------------------------------------------- */

.rating { display: flex; gap: var(--sp-3); }

.rating__cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.rating__key {
  font-size: 9px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--text-muted);
  white-space: nowrap;
}

.rating__level {
  padding: 3px var(--sp-2);
  border: 1px solid var(--border-default);
  background: var(--surface-card);
  color: var(--fixit-ink);
  font-size: 10px;
  font-weight: var(--fw-black);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  text-align: center;
  white-space: nowrap;
}

/* Three visibly distinct weights in one hue. */
.rating__level--med  { background: var(--yellow-veil); border-color: var(--yellow-edge); }
.rating__level--high { background: var(--fixit-yellow); border-color: var(--fixit-yellow); color: var(--text-on-yellow); }

/* A missing rating is never guessed. It renders as a dash with a tooltip. */
.rating__level--none { color: var(--text-muted); border-color: var(--border-subtle); }

/* On ink grounds Low is a hairline and Medium stays a wash, so both keep
   their legibility without becoming a fourth colour. */
.rating--on-dark .rating__key { color: var(--text-on-dark-muted); }
.rating--on-dark .rating__level {
  background: transparent;
  border-color: var(--border-on-dark);
  color: var(--text-on-dark);
}
.rating--on-dark .rating__level--med { background: var(--yellow-veil); border-color: var(--yellow-edge); }
.rating--on-dark .rating__level--high {
  background: var(--fixit-yellow);
  border-color: var(--fixit-yellow);
  color: var(--text-on-yellow);
}

/* Layout: rows share the width evenly inside a card. */
.rating--row .rating__cell { flex: 1 1 0; }

/* Layout: four fixed columns, so 22 items align down the toolkit page. Cards
   structurally cannot do this, which is why that page is a row list. */
.rating--grid { gap: 0; }
.rating--grid .rating__cell { flex: 0 0 84px; width: 84px; }


/* ----------------------------------------------------------------------------
   18. Sam

   Sam's colour marks WHO is speaking; yellow marks WHAT to do. They never appear
   in the same element, and yellow never carries Sam.
   ---------------------------------------------------------------------------- */

/* Sam's mark IS the disc: the glyph is reversed out of a filled circle, which
   is how the prototype draws it in all five places it appears. Sized by the
   caller through --sam-disc, because those five run from 16px to 68px and the
   glyph-to-disc ratio is not constant. */
.sam-mark {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--sam-disc, 48px);
  height: var(--sam-disc, 48px);
  border-radius: var(--radius-pill);
  background: var(--sam);
  color: var(--sam-on-fill);
}

/* -- Hero card ------------------------------------------------------------ */

.sam-card {
  width: 100%;
  padding: var(--sp-6);
  background: var(--bg-page);
  color: var(--text-strong);
  border: 1px solid var(--border-default);
  border-top: 8px solid var(--sam);
}

.sam-card__id { display: flex; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-4); }
.sam-card__eyebrow {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--sam);
}
.sam-card__disclaimer { margin-top: 2px; font-size: var(--fs-xs); line-height: 1.4; color: var(--text-muted); }
/* 1.4 is the hero card's. Both of the situation panels set this line on
   --lh-body instead. */
.tk-sam .sam-card__disclaimer,
.sam-panel .sam-card__disclaimer { line-height: var(--lh-body); }

/* Reviewers found the full multi-paragraph card too heavy in a hero, so it opens
   collapsed: identity and ratings only, the story behind a disclosure. */
.sam-card__toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--sam);
}
.sam-card__toggle svg { transition: transform 180ms var(--ease-out); }
/* [open] on the <details>, NOT aria-expanded on the toggle.
   The toggle is a <summary>, and a summary never gets aria-expanded — the
   browser tracks the state on its <details> parent. So this rule matched
   nothing and the chevron stayed pointing down while the panel was open, on
   every Sam card. The label swap two hundred lines below already keys off
   [open] correctly, which is what this now matches. */
[open] > .sam-card__toggle svg { transform: rotate(180deg); }

/* No hover wash. The toggle is a full-width summary, so a background would
   paint a band across the card; the prototype gives it cursor:pointer and
   nothing else. --sam-tint belongs to the up-next link, which is inline. */

.sam-card__story { margin-bottom: var(--sp-2); }
.sam-card__story h2 {
  margin-bottom: var(--sp-3);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
}
.sam-card__paras { display: flex; flex-direction: column; gap: var(--sp-3); }
.sam-card__paras p { font-size: var(--fs-sm); line-height: var(--lh-body); color: var(--text-body); }
.sam-card__paras p:first-child { color: var(--text-strong); }
.sam-card__paras a { font-weight: var(--fw-bold); text-decoration: underline; text-underline-offset: 3px; }

.sam-card__rule { margin: var(--sp-5) 0 var(--sp-4); border-top: 1px solid var(--border-default); }

.sam-card__resource {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}
.sam-card__resource-label {
  margin-bottom: var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--text-muted);
}
.sam-card__resource-name {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  color: var(--text-strong);
}

/* Sam's colour on the underline, never yellow — yellow never carries Sam. */
.sam-card__link {
  display: inline-block;
  margin-top: var(--sp-4);
  padding-bottom: 2px;
  border-bottom: 2px solid var(--sam);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
}

/* -- Hero placement contract ----------------------------------------------
   A page opts in with three hooks: .hero--hosts-card on the hero section,
   .sam-hero-copy on its copy column, and .sam-hero-clear on the section below.

   The card has exactly TWO states and 1130px is the switch between them, so
   there is no in-between width where it sits beside the copy in normal flow:

   - Above 1130px it leaves the flow, hangs past the dark band at a fixed 380px,
     and the copy takes the freed width. The overhang is measured rather than
     fixed — a fixed padding was wrong on every page — and written to
     --sam-clearance.
   - At 1130px and below it takes its own full-width row under the copy.

   `flex: 1 1 100%` is what makes the second state hold. Without a basis the
   card is `flex: 0 1 auto` and sizes to its content, so it rendered narrow
   while the story was collapsed and jumped to full width when the reader
   opened it — the width changing as a side effect of a disclosure.
   -------------------------------------------------------------------------- */

.sam-hero-card { flex: 1 1 100%; width: 100%; }

.sam-hero-clear { padding-top: var(--sam-clearance, var(--sp-16)); }

@media (min-width: 1130px) {
  .sam-hero-card { position: absolute; top: 0; right: 0; width: 380px; flex: none; }
  .sam-hero-copy { width: calc(100% - 420px); max-width: calc(100% - 420px); }
  .sam-hero-copy h1,
  .sam-hero-copy p { max-width: none; }
  .sam-hero-clear { padding-top: var(--sam-clearance, var(--sp-32)); }
}

/* -- Up next --------------------------------------------------------------- */

.sam-next { background: var(--bg-muted); padding: var(--sp-16) var(--sp-10); }
.sam-next__inner { max-width: var(--container); margin: 0 auto; }
.sam-next__card {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-6);
  padding: var(--sp-8) var(--sp-8) var(--sp-8) var(--sp-6);
  background: var(--bg-page);
  border-left: 8px solid var(--sam);
}
.sam-next__body {
  flex: 1;
  min-width: 280px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
}
.sam-next__chip {
  padding: var(--sp-2) var(--sp-3);
  background: var(--sam);
  color: var(--sam-on-fill);
  font-family: var(--font-display);
  font-size: var(--fs-lead);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
}
.sam-next__body h2 { font-size: var(--fs-h2); font-weight: var(--fw-black); }
.sam-next__body p { max-width: var(--measure-body); line-height: var(--lh-body); }
.sam-next__link {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  text-decoration-color: var(--sam);
}
.sam-next__link:hover { background: var(--sam-tint); }

/* The same link at the eyebrow tier, drawn as a rule under the text rather than
   an underline — the Toolkit's "See Sam's journey". Sam's purple either way, so
   a link into his story always looks like one. */
.sam-link {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-strong);
  border-bottom: 2px solid var(--sam);
  padding-bottom: 2px;
}
.sam-link:hover { background: var(--sam-tint); }

@media (max-width: 620px) {
  .sam-next { padding: var(--sp-12) var(--sp-5); }
  .sam-next__card { padding: var(--sp-6); }
}


/* The disclosure label swaps entirely rather than being rewritten by script, so
   the card opens and closes with no JavaScript at all. */
.sam-card__toggle { list-style: none; cursor: pointer; }
.sam-card__toggle::-webkit-details-marker { display: none; }
.sam-card__toggle-open { display: none; }
[open] > .sam-card__toggle .sam-card__toggle-open { display: inline; }
[open] > .sam-card__toggle .sam-card__toggle-closed { display: none; }


/* ----------------------------------------------------------------------------
   19. Cards, chips, list markers
   ---------------------------------------------------------------------------- */

/* A card is a 1px rule on the page ground. No fill, no shadow — and hover is an
   ink border, never an elevation lift. */
/* --sp-5. The only real users are Build Your Policies' four benefit cards, and
   that is what the prototype gives them. */
.card {
  padding: var(--sp-5);
  border: 1px solid var(--border-default);
  background: transparent;
  transition: border-color var(--dur-fast) linear;
}
a.card:hover,
.card--hover:hover { border-color: var(--border-ink); }

/* A card heading sits one step under a section heading — the card is already
   the unit of attention, so the type does not have to shout. */
.card h3 { font-size: var(--fs-lead); }
.card p { margin-top: var(--sp-3); font-size: var(--fs-sm); line-height: var(--lh-body); }

/* A 4px yellow top edge and a sunken ground — the benefit card sits ON the page
   rather than being cut out of it, which is why it has a fill and the toolkit's
   rows do not. */
.card--top-rule {
  border-top: 4px solid var(--fixit-yellow);
  background: var(--bg-sunken);
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 3px var(--sp-3);
  border: 1px solid var(--border-default);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  white-space: nowrap;
}
.chip--solid { background: var(--fixit-yellow); border-color: var(--fixit-yellow); color: var(--text-on-yellow); }
.chip--on-dark { border-color: var(--border-on-dark); color: var(--text-on-dark); }

/* Row hover, site-wide: the ground never changes, a yellow bar appears on the
   left edge. A grey fill says nothing in a three-colour system. The transition
   is on box-shadow, not background, so nothing repaints the ground. */
.row-hover { transition: box-shadow var(--dur-fast) linear; }
.row-hover:hover { box-shadow: inset 8px 0 0 -5px var(--fixit-yellow); }
.row-hover--thin:hover { box-shadow: inset 3px 0 0 0 var(--fixit-yellow); }
.row-hover--wash:hover { background: var(--yellow-veil); }

/* List markers. A 26px slot is kept whether or not there is a number, so the
   text column starts in the same place either way — a blank 26px square read as
   a missing number, which is why the unnumbered marker is smaller inside it. */
.marked-list { display: flex; flex-direction: column; gap: var(--sp-4); }
.marked-list li { display: flex; align-items: flex-start; gap: var(--sp-4); }
.marked-list li::before {
  content: "";
  flex: 0 0 26px;
  width: 26px;
  height: 16px;
  margin-top: 2px;
  background: var(--fixit-yellow);
}
.marked-list--numbered { counter-reset: step; }
.marked-list--numbered li { counter-increment: step; }
.marked-list--numbered li::before {
  content: counter(step);
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-on-yellow);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-black);
}

/* Unnumbered markers are 16x16 inside the 26px slot. */
.marked-list:not(.marked-list--numbered) li::before {
  width: 16px;
  flex-basis: 26px;
  background: linear-gradient(var(--fixit-yellow), var(--fixit-yellow)) no-repeat left top / 16px 16px;
}


/* ----------------------------------------------------------------------------
   20. Partner mark

   background-size:contain reproduces the prototype's hand-tuned per-logo heights
   automatically: the 188x80 content box puts a 6:1 wordmark at ~31px and a 1:1
   square at 80px, which is the range the design describes. A new partner sizes
   itself correctly on arrival with no code change.

   Partner logos render in full colour, at rest, unmodified — one of only two
   sanctioned exceptions to the three-colour palette. A greyscale-at-rest
   convention was tried and abandoned: partners are lending their brands to a
   public service campaign, and hover-to-reveal hid them entirely on touch.
   ---------------------------------------------------------------------------- */

.pm-cell {
  position: relative;
  flex: 0 0 236px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 236px;
  height: 128px;

  /* The padding IS the sizing rule, and it is the only one — any logo, any
     aspect ratio, contained. It leaves a 172x64 content box, which against the
     prototype's own 23 marks gives a smallest of 25.5px (the prototype's own
     smallest is 26) and caps the largest at 64.

     Not the narrower box that shipped first. 108x80 minimises the average error
     against those 23 heights, and minimising that average is exactly what made
     Televisual render at 16px — `contain` is a WIDTH rule for anything wide, so
     a narrow box punishes wordmarks. This box matches the prototype's RANGE
     instead and runs about a third larger than its average. See OPEN-ITEMS 102:
     no box can do both, because the prototype normalises optical area and its
     numbers are not self-consistent anyway.

     The two paddings are independent levers and it is worth knowing which is
     which. Horizontal sets the WIDTH, which is what a wide wordmark is bound by
     — it alone decides whether Televisual reads at 16px or 26px. Vertical sets
     the height CAP, which is what every near-square mark lands on: at --sp-6
     eleven marks sat at 80px where the prototype gives them 52-68, so the row
     read heavy. --sp-8 brings that cap to 64 and moves nothing else — the
     width-bound marks are identical either way. --sp-10 would take it to 48,
     which is closest to the prototype's average but pulls a true square mark to
     48 where the prototype gives it 80. */
  padding: var(--sp-8);

  /* A white plate: partner marks are drawn for white, and several are white-on-
     colour lockups that vanish on a warm ground. Pure white, not the campaign
     cream — on --bg-sunken the cream barely reads as a plate at all. */
  background-color: var(--surface-plate);

  /* content-box is the whole point. `background-origin` defaults to padding-box,
     which means the padding above does NOT inset the image — `contain` would fit
     the full 236x128 and the box above would size nothing. */
  background-origin: content-box;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;

  /* An outline, not a border: it takes no layout space, so with the track's 1px
     gap the outlines of adjacent cells meet and read as a single hairline grid.
     A border would add 2px to every cell and break the 236px module. */
  outline: 1px solid var(--border-default);
}

/* The probe supplies the accessible name and fires onerror; the visible mark is
   the cell's background-image. opacity:0 keeps it in the accessibility tree
   where display:none and visibility:hidden would both remove it. Positioned
   absolutely because at zero opacity it still occupies layout space. */
.pm-probe {
  position: absolute;
  width: 20px;
  height: 20px;
  opacity: 0;
  pointer-events: none;
}

/* The fallback: a partner is never silently uncredited. */
.pm-cell--text {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  text-align: center;
  color: var(--text-body);
}


/* ----------------------------------------------------------------------------
   21. Page layout primitives

   Named because they recur. The split is the commonest content shape on the
   site: a labelled heading column beside a prose column.
   ---------------------------------------------------------------------------- */

/* Hero with a copy column and, on some pages, a Sam card beside it. */
.hero-row {
  display: flex;
  gap: var(--sp-12);
  flex-wrap: wrap;
  align-items: flex-start;
  position: relative;
}
.hero__copy { flex: 1 1 560px; min-width: 340px; }

/* Hero rhythm. The prototype carries it as a margin-bottom on every element;
   one pair of sibling rules replaces all of it. The eyebrow and the qualifier
   belong to the headline, so they sit tighter than the paragraphs under it.
   Two classes deep to beat the global `h1 + .lead` pair rule. */
.hero-row .hero__copy > * + * { margin-top: var(--sp-5); }
.hero-row .hero__copy > .eyebrow + *,
.hero-row .hero__copy > .qualifier + * { margin-top: var(--sp-4); }

/* Where to Begin's hero has no card beside it, so it is a plain column and not
   a .hero-row — which meant none of the hero rhythm rules reached it and the
   closing line sat hard against the lead. The prototype puts 16px between every
   element in this hero. */
.hero-stack > * + * { margin-top: var(--sp-4); }

/* The small line above a hero headline. Capped tighter than body prose — it is
   a qualifier, not an argument. */
/* The yellow left bar is the point of it — it marks the line as the campaign
   speaking rather than the page describing itself, and it was missing on all
   three hero pages. --lh-snug because it is a single tight statement. */
.qualifier {
  max-width: var(--measure-qualifier);
  padding-left: var(--sp-4);
  border-left: 2px solid var(--fixit-yellow);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--text-on-dark-muted);
}

/* Uppercase yellow marks under a hero CTA. Not chips — no border, no ground. */
.hero-marks { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-5); }

/* The eyebrow and the share control on one line, the control pushed to the
   right edge of the copy column. Not the page edge — the Sam card owns that,
   and there is a 40px gutter between the two. .td-share carries the
   margin-left:auto, so this only has to be a row. */
.hero-eyebrow-row { display: flex; align-items: center; gap: var(--sp-4); }
.hero-marks span {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--fixit-yellow);
}

/* Eyebrow-plus-heading column beside a prose column. */
.split {
  display: flex;
  gap: var(--sp-16);
  flex-wrap: wrap;
  align-items: flex-start;
}
/* Copy beside a card, rather than an aside beside a body: the row closes to
   --sp-12 and the copy column runs on a 16px rhythm. About's hero and the
   Toolkit's intro; Find Your Gaps and Build Your Policies keep the wider pair. */
.split--tight { gap: var(--sp-12); }
.split--tight .split__body { gap: var(--sp-4); }
/* The gap is the whole rhythm. Margins do not collapse into a flex gap, so the
   global `.eyebrow + h1` and `h1 + .lead` pairs would add a second 16px to each
   boundary and the column would run 32px where the prototype runs 16 --- the
   same reason `.stack` zeroes these two pairs further down. */
.split--tight .split__body > .eyebrow + h1,
.split--tight .split__body > h1 + .lead { margin-top: 0; }
.split__aside { flex: 0 0 340px; min-width: 280px; }
.split__body {
  flex: 1;
  min-width: 340px;
  max-width: var(--measure-body);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* A left-aligned vertical stack — the shape of most closing bands. */
.stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
}

/* A button with its qualifying note beside it, not under it — the note answers
   the objection the button raises ("what will this cost me?"), so it has to be
   readable in the same glance. Wraps under on a narrow screen. */
.action-row {
  display: flex;
  gap: var(--sp-4);
  align-items: center;
  flex-wrap: wrap;
}
/* Find Your Gaps sets its CTA beside the reassurance marks rather than above
   them, on a --sp-6 gap. The marks qualify the button, so they read as one
   object. */
.action-row--wide { gap: var(--sp-6); }
.action-row__note { font-size: var(--fs-sm); color: var(--text-muted); }

/* A stack that opens a section — heading plus one paragraph of setup. Capped at
   the prose measure so a long heading wraps where the prototype wraps it.

   --sp-3, not the stack's --sp-4: the prototype sets 12px below a section
   heading on eight of its thirteen h2s, and 12px again on the gap of Where to
   Begin's route column. A heading and its setup line belong together. */
.stack--intro { max-width: var(--measure-prose); gap: var(--sp-3); }
.stack--gap-4 { gap: var(--sp-4); max-width: var(--measure-body); }
.stack--gap-8 { gap: var(--sp-8); }

/* Inside a stack the gap owns the rhythm; the prose margin would double it. */
.stack > h1 + .lead,
.stack > h2 + .lead { margin-top: 0; }

/* Sibling blocks within one section — an intro, the content, a closing action.
   Named for the relationship rather than the measurement, so the rhythm can be
   retuned in one place.

   --sp-8 because that is what the prototype uses between an intro and the
   content it introduces, and between the content and its closing action. It is
   not entirely consistent there — one page carries 8, 10 and 12 for the same
   relationship — so this takes the commonest value rather than reproducing
   three. --wide is for a block that closes a section and wants the extra air. */
.section__inner > * + .block { margin-top: var(--sp-8); }
.section__inner > * + .block--wide { margin-top: var(--sp-12); }
/* A film frame takes more air above it than a card grid does: the prototype
   opens the lead above both players with `margin: 0 0 var(--sp-10)`. About's
   tour and Home's campaign film, and only those two. */
.section__inner > * + .block--airy { margin-top: var(--sp-10); }

/* One line lifted above body copy without becoming a heading. */
.emphasis {
  font-size: var(--fs-lead);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}

/* The large CTA, at the --control-h-lg 58px tier. */
.btn--lg { padding: 19px var(--sp-8); font-size: var(--fs-lead); }

@media (max-width: 620px) {
  .split { gap: var(--sp-8); }
  .split__aside, .split__body { flex-basis: 100%; min-width: 0; }
  .hero__copy { flex-basis: 100%; min-width: 0; }
}


/* An outlined yellow button — the "ghost-amber" variant. On a dark ground the
   label is cream; on a light ground it must take the darker yellow step, because
   --fixit-yellow fails contrast at this size. */
/* The border is --border-on-dark at rest and only goes yellow on hover. A
   yellow outline at rest reads as a primary action, which this is not — it is
   the way back. Geometry is carried here because the prototype states it once,
   on this button, and the tool-detail hero reuses the same control. */
.btn--amber {
  gap: 10px;
  padding: 15px var(--sp-8);
  border: 2px solid var(--border-on-dark);
  background: transparent;
  font-size: var(--fs-lead);
  color: var(--text-on-dark);
}
.btn--amber:hover { background: var(--fixit-yellow); color: var(--text-on-yellow); }
.btn--amber-on-light { color: var(--yellow-press); }
.btn--amber-on-light:hover { color: var(--text-on-yellow); }

/* A smaller tier for in-content actions. */
.btn--md { padding: 13px var(--sp-6); font-size: var(--fs-body); }


/* ----------------------------------------------------------------------------
   22. Numbered question list (Vet Your Vendors)

   Each question is its own bordered card: a numbered square, the question, why
   it matters, and the two answers. The two answers are grounds, not a shared
   rule — sunken for what good looks like, a yellow wash with a yellow edge for
   what to ask for if the answer disappoints. The yellow marks the action, which
   is the one thing the reader might have to do.
   ---------------------------------------------------------------------------- */

.qlist { display: flex; flex-direction: column; gap: var(--sp-4); }

.qlist__item {
  display: flex;
  gap: var(--sp-6);
  align-items: flex-start;
  flex-wrap: wrap;
  padding: var(--sp-6);
  border: 1px solid var(--border-default);
}

/* 40px, and yellow rather than the prototype's ink ground — Ward's call,
   27 Aug 2026. Bigger than the 26px marker the tool-detail lists use, because
   here the number is the card's only landmark. */
.qlist__n {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--sp-10);
  height: var(--sp-10);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-black);
}

.qlist__body { flex: 1; min-width: 280px; }

.qlist__q {
  margin-bottom: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  text-transform: uppercase;
  color: var(--text-strong);
  text-wrap: pretty;
}
/* The parenthetical carries examples, so it drops out of caps. */
.qlist__q span { text-transform: none; }

.qlist__why {
  max-width: var(--measure-body);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

.qlist__answers {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: var(--measure-body);
}
.qlist__answers div {
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--text-body);
}
.qlist__answers strong { font-weight: var(--fw-bold); color: var(--text-strong); }

.qlist__good { background: var(--bg-sunken); }
.qlist__ifnot { background: var(--yellow-veil); border-left: 2px solid var(--fixit-yellow); }


/* ----------------------------------------------------------------------------
   23. Copyable template block

   A long block of copy the reader is meant to lift, with a copy button per
   field. Same modal shell as the alert signup; single column, because the
   payload is the point.
   ---------------------------------------------------------------------------- */

.tpl { padding: var(--sp-10); }

/* The lockup sits centred against the two lines beside it, and the row stops
   short of the close button rather than running under it. */
.tpl__head {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  flex-wrap: wrap;
  margin: 0 var(--sp-8) var(--sp-5) 0;
}
.tpl__head img { width: 110px; height: auto; }   /* 124 x 0.8883 */
/* Condensed, like .tpl__field-head span below and like the prototype. This is a
   modal label, not a section eyebrow — the same distinction the prototype draws
   by setting --font-display here and nowhere on the ordinary eyebrows. */
.tpl__head .eyebrow { font-family: var(--font-display); }
.tpl__head h2 { font-size: var(--fs-h1); font-weight: var(--fw-black); }

/* The intro closes with a rule: it is the last thing before the payload. */
.tpl__intro {
  margin-bottom: var(--sp-8);
  padding-bottom: var(--sp-8);
  max-width: var(--measure-column);
  border-bottom: 1px solid var(--border-subtle);
  line-height: var(--lh-body);
}

.tpl__fields { display: flex; flex-direction: column; gap: var(--sp-8); }

/* A field is a label row over a white box, not a bordered container with a
   strip inside it. The box is the thing the reader is being handed. */
.tpl__field-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-2);
}
.tpl__field-head span {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--fixit-ink);
}

.tpl__copy {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 7px var(--sp-3);
  border: 1px solid var(--grey-400);
  line-height: 1;
  color: var(--text-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  transition: background var(--dur-fast) linear;
}
.tpl__copy:hover { background: var(--fixit-yellow); color: var(--text-on-yellow); }
/* Confirmation is a state on the button itself, not a toast — the reader is
   mid-task inside a modal and a toast at the foot of the page would be missed. */
.tpl__copy[data-copied="true"] { background: var(--fixit-yellow); }

/* White, not the panel's cream: this is a field, and it has to read as the
   thing being copied rather than part of the page around it. */
.tpl__value {
  margin: 0;
  padding: var(--sp-4);
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--grey-400);
  background: var(--surface-plate);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--fixit-ink);
  white-space: pre-wrap;
}
/* The email body is longer and steps down a size, with more room around it. */
.tpl__value--body { padding: var(--sp-5); font-size: var(--fs-sm); }

@media (max-width: 620px) {
  .qlist__answers { padding-left: var(--sp-4); }
  .tpl { padding: var(--sp-6) var(--sp-5); }
  .tpl__head { margin-right: 0; }
}


/* An inline link inside prose. Underlined so it is not colour-only — the palette
   has no link colour, since yellow is reserved for the highlight device. The
   underline is yellow and 2px at rest, and hover fills the whole run of text
   yellow, which is the prototype's values exactly. */
.inline-link {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  text-decoration-color: var(--fixit-yellow);
}
.inline-link:hover { background: var(--fixit-yellow); }
/* On an ink ground the link is yellow — the underline alone does not separate
   it from the prose around it. */
.section--dark .inline-link { color: var(--fixit-yellow); }
/* On an ink ground the fill needs the ink label, or cream-on-yellow disappears. */
.section--dark .inline-link:hover { color: var(--text-on-yellow); }

.tpl__note {
  margin-top: var(--sp-6);
  font-size: var(--fs-xs);
  line-height: var(--lh-body);
  color: var(--text-muted);
}


/* ----------------------------------------------------------------------------
   24. Card grids

   Two grids, two different rules, because the card COUNT decides what a good
   step is — not the card width.

   The policy grid has nine, so auto-fit is right: 3 / 2 / 1 always divides
   evenly or leaves a short last row that reads as a list continuing.

   The benefits grid has four, where auto-fit's 3 is wrong: a row of three with
   one card orphaned underneath reads as a mistake when four is the whole set.
   So it steps 4 -> 2 -> 1 on explicit breakpoints instead.
   ---------------------------------------------------------------------------- */

.cardgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cardgrid-col, 300px), 1fr));
  gap: var(--sp-4);
}

/* Four cards, never three. Both switches are editorial, not a measured failure
   point: the headings still hold one line at 224px and the cards are usable
   there. 1020px is simply the last canonical breakpoint at which a 4-up row of
   one-sentence cards still looks deliberate, and 620px is the small-screen
   tier. Ward's call, 27 Aug 2026. */
.cardgrid--quarters { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 1019.98px) {
  .cardgrid--quarters { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 619.98px) {
  .cardgrid--quarters { grid-template-columns: 1fr; }
}


/* ----------------------------------------------------------------------------
   25. Policy template card

   A file glyph, the policy name, what the template is for, and a download. The
   deep-linked card is picked out with the loudest treatment the system allows
   short of a yellow field: an ink frame, a solid yellow edge bar, and lift off
   the grid. Yellow marks the thing you asked for; it does not say "good".
   ---------------------------------------------------------------------------- */

.pcard {
  display: flex;
  gap: var(--sp-5);
  align-items: stretch;
  padding: var(--sp-5);
  border: 1px solid var(--border-default);
  background: var(--bg-page);
  /* Toolkit policy rows deep-link to #policy-<slug>; the header is 150px at
     desktop, so a card scrolled to the exact top lands underneath it. One
     number for both routes: scrollIntoView honours this, so the script does no
     offset arithmetic and a plain #hash jump lands in the same place. The
     prototype had two — scroll-margin-top:120px and a hand-rolled -160. */
  scroll-margin-top: 160px;
  transition: box-shadow var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
/* The prototype authored this hover as `.tpl-row:hover` and never put the class
   on the card, so it never fired. Kept — every other row and card on the site
   gives hover feedback, and 4px is the site's row-hover bar. */
.pcard:hover { border-color: var(--border-ink); box-shadow: inset 4px 0 0 var(--fixit-yellow); }

.pcard[data-picked="true"],
.pcard[data-picked="true"]:hover {
  padding-left: calc(var(--sp-5) + 12px);
  border-color: var(--border-ink);
  box-shadow: inset 12px 0 0 var(--fixit-yellow), var(--shadow-lg);
}

.pcard__glyph {
  flex: 0 0 auto;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border-subtle);
  background: var(--bg-page);
}

.pcard__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }

.pcard__title {
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  color: var(--text-strong);
}
.pcard__desc { font-size: var(--fs-sm); line-height: var(--lh-body); color: var(--text-body); }

/* margin-top:auto pins the action to the foot, so cards of unequal copy length
   still line their buttons up across a row. */
.pcard__foot { display: flex; align-items: center; margin-top: auto; padding-top: var(--sp-4); }

/* A third button tier: 32px, for an action repeated nine times in a grid. */
.btn--sm {
  gap: var(--sp-2);
  padding: 0 var(--sp-4);
  height: 32px;
  font-size: var(--fs-xs);
}

/* An ink-outlined button. The ghost variant is grey-400 on grey-500 text, which
   recedes; this one has to read as the primary action inside its own card. */
.btn--ink {
  border: 2px solid var(--border-ink);
  background: transparent;
  color: var(--text-strong);
  transition: background var(--dur-fast) linear, color var(--dur-fast) linear,
              border-color var(--dur-fast) linear;
}
.btn--ink:hover { background: var(--fixit-yellow); border-color: var(--fixit-yellow); color: var(--text-on-yellow); }


/* ----------------------------------------------------------------------------
   26. Callout band

   A sunken band with a heavy yellow left edge, closing a section with one
   question and the action that answers it.
   ---------------------------------------------------------------------------- */

.callout {
  display: flex;
  gap: var(--sp-10);
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-8);
  border-left: 8px solid var(--fixit-yellow);
  background: var(--bg-sunken);
}
.callout__copy { flex: 1; min-width: 340px; max-width: var(--measure-column); }
/* An h3, not an h2: the section already has one, and this band closes it. It
   takes the --fs-h2 step so it still reads as the loudest thing in the band. */
.callout__copy h3 {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-h2);
  font-weight: var(--fw-black);
}
.callout__copy p { line-height: var(--lh-body); }
.callout__copy p + p { margin-top: var(--sp-3); }

@media (max-width: 620px) {
  .callout { padding: var(--sp-6); }
  .callout__copy { min-width: 0; }
}


/* ----------------------------------------------------------------------------
   27. Home hero

   The only hero on the site with the lockup in it, and the only one that leads
   with the mark rather than an eyebrow. Less vertical padding than the standard
   hero because its content is the tallest on the site.
   ---------------------------------------------------------------------------- */

.hero--home { padding: var(--sp-12) var(--sp-10); position: relative; overflow: hidden; }

/* Tighter than the shared .hero-row's --sp-12. Home's two columns are a lockup
   and a headline that belong together; the Sam-card pages want the wider gutter
   because the card is a separate object. */
.hero--home .hero-row { gap: var(--sp-8); }

.hero__mark {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-6);
}
.hero__mark img { width: auto; height: 167px; }   /* 240 x 0.6953 — tight crop */

.hero__copy--home { flex: 1 1 0; min-width: 320px; }
/* The floor stands down on a phone, as it does for every other text column —
   see .split__body, .callout__copy, .td-hero__copy. Without this the column
   holds 320px and the page scrolls sideways. */
@media (max-width: 620px) {
  .hero__copy--home { min-width: 0; }
}

/* clamp() rather than the display scale: this line is the first thing anyone
   sees and it has to fill the column at any width. 80px is above every step in
   the scale, which is why it is stated here and not tokenised. */
/* --fw-black for the same reason as h1: this is the largest type on the site,
   and Barlow Condensed at 600 does not carry it. */
.hero__headline {
  margin-bottom: var(--sp-4);
  font-family: var(--font-display);
  font-size: clamp(48px, 5vw, 80px);
  font-weight: var(--fw-black);
  line-height: 1;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-on-dark);
}

/* Bold and full cream, unlike .lead--on-dark: on Home this line carries the
   offer, so it sits above the paragraph that follows rather than beside it. */
.hero__pitch {
  margin-bottom: var(--sp-6);
  max-width: var(--measure-body);
  font-size: var(--fs-lead);
  font-weight: var(--fw-bold);
  line-height: var(--lh-body);
  color: var(--fixit-white);
}
.hero__who {
  margin-bottom: var(--sp-4);
  max-width: var(--measure-body);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-body);
  color: var(--text-on-dark);
}

/* The nine roles. Not .chip — these are on ink, at a size and tracking of their
   own, and they are labels rather than controls. */
.roles { display: flex; flex-wrap: wrap; gap: 8px; }
.roles span {
  padding: 6px 12px;
  border: 1px solid var(--border-on-dark-lit);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  letter-spacing: var(--ls-chip);
  text-transform: uppercase;
  color: var(--text-on-dark-muted);
}

/* One tier above --lg, for the single largest action on the site. */
.btn--hero {
  gap: 14px;
  padding: 18px 30px;
  border: 2px solid var(--fixit-yellow);
  background: var(--fixit-yellow);
  font-size: 20px;
  color: var(--text-on-yellow);
  transition: background var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}
.btn--hero:hover { background: var(--yellow-press); border-color: var(--yellow-press); }


/* ----------------------------------------------------------------------------
   28. The photograph band — the site's only one

   Copy sits over artwork, so everything here is about keeping it legible: a
   left-to-right scrim carries the text side, and the image is anchored right so
   the subject survives the crop.

   Below 1130px the photograph becomes a 420px cell that still carries the
   eyebrow and the headline — on a white plate, in ink — while the lead, the
   three moments and the closing paragraphs take their own muted ground below.
   Ward's call, 27 Aug 2026. The prototype's fallback moved the image into a band
   of its own above the copy, which read as two unrelated slabs; giving the cell
   the headline means the artwork always holds content.

   The two regions are the same two divs in both layouts. Only their grounds and
   their stacking change, so there is no second layout to keep in step with the
   first, and no !important at all against the prototype's sixteen.

   Two other departures, both Ward's calls the same day:

   - **The switch is 1130px, not 1100px.** 1101-1129 was the photo ground at its
     worst: a fixed 620px copy column took 56% of the width, squeezing the
     photograph to 42% and cropping into its subject. 1130 is also where the
     header collapses and the Sam card changes state, so "desktop" is now one
     boundary site-wide rather than two thirty pixels apart. This retires 1100
     from the canonical breakpoints — it existed for this section alone.

   - **The copy column keeps its 620px measure when stacked.** The prototype
     released it to 100%, which at 1100px put the heading at 1005px while the
     lead and closing paragraphs stayed capped at 540px and the item text sat at
     665px — four different column edges in one block. Holding 620px reproduces
     the photo ground's own proportions (620 / 584 / 540) at every width, so the
     text never reflows: only the artwork moves.
   ---------------------------------------------------------------------------- */

.photo-band {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  min-height: 760px;
  padding: var(--sp-16) var(--sp-10);
  /* The flat colour shows while the photograph loads, and behind it if the
     crop leaves any edge uncovered. */
  background: var(--grey-400) url("../assets/keyart-update-crack-right.jpg") right center / cover no-repeat;
}

/* Opaque over the copy, clear by 68% so the artwork's subject stays visible. */
.photo-band__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to right,
    rgba(35, 31, 32, .62) 0%,
    rgba(35, 31, 32, .45) 42%,
    rgba(35, 31, 32, 0) 68%);
}

.photo-band__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
}
/* Above 1130px both regions are plain blocks in one 620px column, and the plate
   is a transparent pass-through — visually there is no plate at all. */
.photo-band__title,
.photo-band__detail { max-width: var(--photo-copy); }

.ss-head {
  margin-bottom: var(--sp-6);
  font-size: var(--fs-h1);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  color: var(--fixit-white);
}
/* Two stacked lines so the highlight sits on the second alone — a highlight
   spanning a wrap would break across both lines. */
.ss-head span {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
}
.ss-head .fx-highlight { transform: rotate(-0.7deg); transform-origin: left center; }

.ss-lead {
  margin-bottom: var(--sp-8);
  max-width: 540px;
  font-size: var(--fs-lead);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
  color: var(--fixit-white);
}

.ss-list { display: flex; flex-direction: column; }
.ss-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-5);
  padding: var(--sp-5) 0;
  border-top: 1px solid var(--rule-on-photo);
}
.ss-num { flex: 0 0 auto; display: inline-flex; margin-top: 6px; color: var(--fixit-yellow); }
.ss-item > div { display: flex; flex-direction: column; gap: 6px; }
.ss-title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--fixit-white);
}
.ss-body { font-size: var(--fs-sm); line-height: var(--lh-body); color: var(--text-on-photo); }

.ss-close {
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  max-width: 540px;
  border-top: 1px solid var(--rule-on-photo);
  line-height: var(--lh-body);
  color: var(--text-on-photo);
}
.ss-close + .ss-close {
  margin-top: var(--sp-4);
  padding-top: 0;
  border-top: 0;
}
/* Not .inline-link: a yellow fill on hover would sit on artwork, and the rule
   under the word already reads as a link over a busy ground. */
.ss-close a {
  padding-bottom: 1px;
  border-bottom: 2px solid var(--fixit-yellow);
  font-weight: var(--fw-bold);
}

@media (max-width: 1129.98px) {
  /* Two full-bleed regions rather than one column over a ground. The section's
     own padding goes to zero and each region takes its own, so the artwork
     reaches both edges. */
  .photo-band { display: block; min-height: 0; padding: 0; }
  .photo-band__inner { max-width: none; }
  .photo-band__title,
  .photo-band__detail { max-width: none; padding: var(--sp-10); }

  /* The image cell carries the photograph ITSELF, rather than showing through to
     the section's background.

     This matters more than it looks. `cover` fits the image to whatever box it
     is painted on: on the section, that box is the cell plus the detail region
     below it — around 1230px tall — so the image scaled to 0.66 and the 420px
     cell showed only the top third of a 2x-zoomed crop. Painted on the cell, the
     box is 420px, the image scales to 0.34, and the full width of the artwork
     reads at half the magnification. Same file, same `right top / cover` as the
     prototype's own band.

     min-height, not height: a headline that wraps further on a narrow screen
     grows the cell instead of overflowing it. */
  .photo-band { background: var(--bg-muted); }
  /* `right bottom`, not the prototype band's `right top`. The subject sits low in
     the frame — a pixel scan puts the laptop's dark mass between 31% and 93% of
     the height, centred at 63% — so anchoring to the top cropped it to its upper
     third and cut away the cracked dialog, which the artwork's own source note
     calls "the whole point". Anchoring to the bottom keeps essentially all of it
     at every width in this range. */
  .photo-band__title {
    min-height: 420px;
    border-bottom: 1px solid var(--border-subtle);
    background: url("../assets/keyart-update-crack-right.jpg") right bottom / cover no-repeat,
                var(--grey-400);
  }

  /* No scrim: the plate carries the type now, so there is nothing to keep legible
     over the artwork. The prototype's band hid it for the same reason. */
  .photo-band__scrim { display: none; }

  /* The white plate. Campaign white, not pure white — and ink type on it, so the
     eyebrow drops its on-dark yellow and the headline its cream. The yellow
     highlight behind "before anyone notices" is then the ordinary light-ground
     treatment, which is what it is everywhere else on the site. */
  .photo-band__plate {
    display: inline-block;
    max-width: var(--photo-copy);
    padding: var(--sp-5) var(--sp-6);
    background: var(--bg-page);
  }
  .photo-band__plate .eyebrow { color: var(--text-strong); }
  .photo-band__plate .ss-head { margin-bottom: 0; color: var(--fixit-ink); }

  /* The detail region's ground is opaque, so it covers both the photograph and
     the scrim below the image cell. That is the whole mechanism — no second
     background image, and nothing to keep in step. */
  .photo-band__detail { background: var(--bg-muted); }
  .photo-band__detail .ss-lead,
  .photo-band__detail .ss-title { color: var(--fixit-ink); }
  .photo-band__detail .ss-body,
  .photo-band__detail .ss-close { color: var(--fixit-grey); }
  .photo-band__detail .ss-num { color: var(--fixit-ink); }
  .photo-band__detail .ss-item,
  .photo-band__detail .ss-close { border-top-color: var(--grey-300); }

  /* Reinstate the measure the 620px column used to give this. */
  .photo-band__detail .ss-list { max-width: var(--photo-copy); }
}

@media (max-width: 620px) {
  .photo-band__title,
  .photo-band__detail { padding: var(--sp-8) var(--sp-5); }
  .photo-band__plate { padding: var(--sp-4) var(--sp-5); }
}


/* ----------------------------------------------------------------------------
   29. Alerts band (Home)

   Two highlight marks, the statistic, the STAR Report argument, two actions.
   ---------------------------------------------------------------------------- */

.alerts__marks { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; margin-bottom: var(--sp-4); }
.alerts__marks .fx-highlight {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}

/* A statistic, not a heading — the page's headings are the h1 and the two h2s,
   and this line is the number talking. */
.alerts__stat {
  margin-bottom: var(--sp-4);
  max-width: var(--measure-body);
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
}
.alerts__body {
  margin-bottom: var(--sp-8);
  max-width: var(--measure-body);
  font-size: var(--fs-lead);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
}
.alerts__body strong { font-weight: var(--fw-bold); }

/* Ghost on ink. Hover takes the label and outline to yellow rather than filling
   — a filled button here would put two yellow blocks in a quiet ink band. */
.btn--ghost-on-dark {
  padding: 19px var(--sp-8);
  border: 2px solid var(--border-on-dark);
  background: transparent;
  font-size: var(--fs-lead);
  color: var(--text-on-dark);
  transition: color var(--dur-fast) linear, border-color var(--dur-fast) linear;
}
.btn--ghost-on-dark:hover { border-color: var(--fixit-yellow); color: var(--fixit-yellow); }
/* The detail hero's is the hand-authored ghost, not the design system's Button:
   the prototype writes `padding:15px var(--sp-8)` there, which is .btn's own
   value, where Home's size="lg" Button is 58px tall and needs the 19px above. */
.td-actions .btn--ghost-on-dark { padding: 15px var(--sp-8); }


/* ----------------------------------------------------------------------------
   30. Film player

   A 16:9 frame holding a real <video> with native controls. The prototype drew
   its own control bar — pause, elapsed time, volume, fullscreen, overflow — but
   it was imitating exactly the native set, and the native one is keyboard
   operable, localised, and honours the reader's captions and playback-rate
   preferences without any of it being built.

   object-fit: contain, not cover. The file is exactly 16:9 so today the two are
   identical, but if a re-cut arrives at another ratio, letterboxing a film frame
   is right and cropping it is not.
   ---------------------------------------------------------------------------- */

.player {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--border-default);
  background: var(--grey-950);
}
.player video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The centre play control, injected by js/site.js for any frame carrying
   data-player — Home's film and About's site tour. Solid yellow: this is the
   one thing the section wants you to do, and yellow marks the right action.
   Square like everything else — a round play button is the one shape the design
   system does not have.

   It hides while the film runs and comes back on hover or keyboard focus, so it
   is never covering the frame you are watching. Everything is driven off a
   data-playing attribute on the frame rather than a class, so the state is
   legible in dev tools. */
.player__play {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 1;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  /* The stroke is not decoration. The film's closing frame is the lockup on a
     yellow highlight, so a yellow button sitting on it loses its edges entirely
     — and any frame the campaign's own artwork appears in will do the same. Ink
     defines it against light and yellow content; against a dark frame the yellow
     fill is already doing that job, which is why the stroke is ink and not
     cream. */
  border: 2px solid var(--border-ink);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  transition: opacity var(--dur) var(--ease-out), background var(--dur-fast) linear;
}
.player__play:hover { background: var(--yellow-press); }
.player__play-icon { display: inline-flex; }

.player[data-playing="true"] .player__play { opacity: 0; pointer-events: none; }
.player[data-playing="true"]:hover .player__play,
.player[data-playing="true"] .player__play:focus-visible { opacity: 1; pointer-events: auto; }

/* Focus has to clear the yellow it sits on, so the ring goes outside the square
   rather than on its edge. */
.player__play:focus-visible { outline-offset: 4px; }

@media (max-width: 620px) {
  .player__play { width: 64px; height: 64px; }
  .player__play svg { width: 30px; height: 30px; }
}

/* The no-<video> fallback, which only ever paints in a browser that cannot play
   the file at all. Centred on the ink ground so it does not read as an error. */
.player p {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-8);
  text-align: center;
  color: var(--text-on-dark);
}
.player p a { font-weight: var(--fw-bold); text-decoration: underline; text-underline-offset: 3px; }


/* Home's hero stacks its two columns early: the lockup is 240px tall and the
   headline needs a column wide enough not to break mid-phrase. */
@media (max-width: 880px) {
  .hero__mark { flex-basis: 100%; align-items: flex-start; }
  .hero__mark img { height: 111px; }   /* 160 x 0.6953 */
}

/* Phone layout for Home's hero. Ward's calls, 27 Aug 2026 — nothing below
   620px was ever designed, so these are decisions rather than transcription. */
@media (max-width: 620px) {
  /* clamp()'s 48px floor bypasses the small-screen scale entirely: at 390px the
     headline ran to five lines and the hero to 1.2x the viewport, so a phone
     showed nothing but the headline on first paint. --fs-display-lg is 38px
     here, which keeps Home's headline the largest on the site while letting the
     pitch and the CTA above the fold. */
  .hero__headline { font-size: var(--fs-display-lg); }

  /* Centred lockup with the action full width under it: on a phone the mark and
     the one action read as the page's masthead rather than a left column. */
  .hero__mark { align-items: center; width: 100%; }
  .hero__mark .btn--hero { width: 100%; }

  /* The nine role pills come off. Nothing is lost: the sentence above them
     already says "whether you're a freelancer, a small studio, an exhibitor, or
     a facility of hundreds", so the pills were reinforcement, and three rows of
     them pushed the whole argument below the fold. */
  .roles { display: none; }
}


/* ----------------------------------------------------------------------------
   31. Data states — loading and failed

   Any region api.js renders into passes through three states. Both of the
   non-final ones are styled here so every page behaves the same way.
   ---------------------------------------------------------------------------- */

/* The skeleton stands in for rows, not for a spinner: a spinner says "wait", a
   skeleton says "a list is coming and roughly this long". No pulse animation —
   the site has almost no motion, and a shimmer on a security PSA reads as an
   advert loading. */
.skeleton { display: flex; flex-direction: column; gap: var(--sp-3); }
.skeleton__row {
  height: 56px;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border-subtle);
}

/* Rows fade out down the list, so the eye reads it as continuing rather than as
   a finished block of grey. */
.skeleton__row:nth-child(2) { opacity: .85; }
.skeleton__row:nth-child(3) { opacity: .7; }
.skeleton__row:nth-child(4) { opacity: .55; }
.skeleton__row:nth-child(5) { opacity: .4; }
.skeleton__row:nth-child(n+6) { opacity: .28; }

/* Failure. No red: the palette has none, and a failed fetch is not an
   emergency. An ink frame with a yellow edge is the same device the site uses
   for anything that wants attention. */
.datafail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
  max-width: var(--measure-body);
  padding: var(--sp-6);
  border: 1px solid var(--border-default);
  border-left: 8px solid var(--fixit-yellow);
  background: var(--bg-sunken);
}
.datafail__head {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  color: var(--text-strong);
}
.datafail__body { line-height: var(--lh-body); }


/* ----------------------------------------------------------------------------
   32. Toolkit — filters, rows, sticky mirror, back to top

   A row list, not cards. The page's job is comparing 22 items across four
   ratings, and cards cannot align a column, so nothing can be read across.
   Four fixed 84px rating columns do the aligning; below 1000px the header row
   goes and each row stacks.
   ---------------------------------------------------------------------------- */

/* -- The two primary filters ----------------------------------------------- */

.tk-sorts { display: flex; gap: var(--sp-4); flex-wrap: wrap; }

.tk-sort {
  flex: 1;
  min-width: 240px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-6) var(--sp-8);
  border: 2px solid var(--border-on-dark);
  background: transparent;
  text-align: left;
  color: var(--text-on-dark);
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.tk-sort:hover { border-color: var(--fixit-yellow); }
.tk-sort[aria-pressed="true"] {
  border-color: var(--fixit-yellow);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
}
.tk-sort__label {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  /* Sub-header tier, so 800 like the h3s it sits level with. */
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
}
/* The filter band opens with an eyebrow, not a paragraph, and the prototype
   spaces it from the controls with --sp-4. */
.tk-filters .eyebrow { margin-bottom: var(--sp-4); }

.tk-sort__hint {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
}
/* Inherits from the button, so it dims on the unselected state and reads as ink
   on the selected one without a second colour rule. */
.tk-sort:not([aria-pressed="true"]) .tk-sort__hint { color: var(--text-on-dark-muted); }

/* -- The secondary filters ------------------------------------------------- */

/* A panel, not a loose row: the secondary filters are a set, and on the ink
   ground a hairline and a 5% tint are what say so. */
.tk-subs {
  margin-top: var(--sp-5);
  padding: var(--sp-5) var(--sp-6);
  border: 1px solid var(--border-on-dark);
  background: var(--surface-on-dark);
}
.tk-subs__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-5);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}
/* Yellow, and in the sans: it names what the checkboxes below it control, so it
   is the campaign speaking rather than a quiet caption. */
.tk-subs__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--fixit-yellow);
}
.tk-all {
  padding-bottom: 2px;
  border-bottom: 2px solid var(--fixit-yellow);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--grey-200);
}
.tk-all:hover { color: var(--text-on-dark); }

/* A GRID, NOT A WRAPPING FLEX ROW. As flex each checkbox took its natural
   width — 116px for "Training (1)" against 169px for "Policy template (9)" —
   so the moment the row wrapped, the second row's columns started at different
   x positions from the first. Measured at 935px: row one at 65/245/424/626,
   row two at 65/241. Ragged at every width where it wraps, which is everything
   below about 1260px.

   auto-fit rather than a fixed count, so the number of columns follows the
   space and they are always equal: six columns at 1440, three at 700, two at
   471, one at 375. 160px is the smallest track that keeps the longest label,
   "Policy template (9)" at 169px, off a second line once 1fr has grown it.

   The column gap comes down to --sp-4. At --sp-8 two 160px tracks plus the gap
   did not fit a 381px list, so 471px collapsed to a single column. */
.tk-subs__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--sp-4);
}

/* role=checkbox on a <button>, so the tick is a state rather than a label. */
/* The base is the sticky mirror's compact size; the filter block scales it up
   below. Both sets are stated in the prototype. */
.tk-check {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-align: left;
  color: var(--text-on-dark);
}
.tk-check__box {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border-on-dark-lit);
  font-weight: var(--fw-black);
  line-height: 1;
  color: var(--fixit-ink);
}
.tk-check[aria-checked="true"] .tk-check__box {
  border-color: var(--fixit-yellow);
  background: var(--fixit-yellow);
}

.tk-check__count {
  margin-left: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-on-dark-muted);
}

/* The filter block's checkboxes are the primary control: a step larger, with a
   17px tick that fills its 26px box. The sticky mirror keeps the base size. */
.tk-subs .tk-check { gap: var(--sp-3); font-size: var(--fs-body); }
.tk-subs .tk-check__box { width: 26px; height: 26px; }

/* A share of the box, so one rule serves the filter block's 26px and the sticky
   mirror's 20px. */
.tk-check__box svg { width: 68%; height: 68%; }

/* -- Group headers --------------------------------------------------------- */

/* The rule goes ABOVE a group, and only between groups — the filter band
   already separates the first one from what is above it. Sibling selector
   rather than a per-group custom property, so "not the first" is structural
   and cannot be got wrong by the data. */
.tk-group + .tk-group {
  margin-top: var(--sp-12);
  padding-top: var(--sp-5);
  border-top: 6px solid var(--fixit-ink);
}

.tk-group__head { margin-bottom: var(--sp-5); }
/* flex-start, not space-between: the free chip belongs beside the heading, not
   at the far edge of a 1160px row. */
.tk-group__title-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}
/* --fs-display-md, not the base h2's --fs-h1: a horizon is the largest thing on
   the page after the hero, and the eye is meant to find it while scrolling. */
.tk-group__title-row h2 { font-size: var(--fs-display-md); }
.tk-group__note-free {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The claim itself is a yellow chip, not muted text. It was rendering as grey
   with no ground at all — the one piece of the line that has to be read. */
.tk-group__note-free > span:first-child {
  padding: 2px var(--sp-2);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
}

/* Both lines sit at --fs-lead and are told apart by weight and colour, not by
   size: the lede is the claim and the note qualifies it. Setting the note
   smaller made it read as small print, which is not what it is.

   1000px, not --measure-body: the group head runs wider than prose because the
   table beneath it is the full container. */
.tk-group__lede {
  margin-bottom: var(--sp-3);
  max-width: var(--measure-group);
  font-size: var(--fs-lead);
  font-weight: var(--fw-bold);
  line-height: var(--lh-body);
  color: var(--text-strong);
}
.tk-group__note {
  max-width: var(--measure-group);
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

/* -- The rows -------------------------------------------------------------- */

/* The list is a panel, not loose rows: the page ground is --bg-sunken and this
   sits a step lighter on it inside a hairline. Without it the rows float and the
   column heads above them have nothing to align to. */
.tk-rows {
  background: var(--bg-page);
  border: 1px solid var(--border-default);
}

/* One grid template, declared once and shared by the header row and every item
   row. If these ever diverge the columns stop lining up, which is the entire
   point of the page. */
.tk-head,
.tk-row {
  display: grid;
  grid-template-columns: 154px minmax(220px, 1fr) 128px auto 18px;
  column-gap: var(--sp-5);
}

.tk-head {
  align-items: end;
  padding: 0 calc(var(--sp-5) + 1px) var(--sp-2);
}
/* 9px, and not the display face. These are the smallest type on the site and
   they are meant to disappear until you look for them — the row values are the
   content. A literal because there is no 9px step in the scale and this is the
   only thing at that size. */
.tk-head span {
  font-size: 9px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
/* Two classes deep on purpose: `.tk-head span` above is (0,1,1) and would
   otherwise win. Time and Skill are rated against Sam's situation, so they
   carry Sam's colour. Cost and Impact are absolute, and the prototype puts them
   in ink rather than leaving them muted with Category, Tool and Effort. */
.tk-head .tk-head__sam { color: var(--sam); }
.tk-head .tk-head__absolute { color: var(--fixit-ink); }

.tk-row {
  align-items: center;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border-subtle);
  transition: background var(--dur-fast) var(--ease-out);
}
/* The one place on the site where a row hover fills rather than showing an edge
   bar: these rows are wide and the whole row is the target. */
.tk-row:hover { background: var(--yellow-veil); }
.tk-row:hover .tk-row__arrow { opacity: 1; transform: translateX(2px); }

.tk-row__kind {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.tk-row__title {
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: 1.05;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-strong);
}

/* Four fixed columns, and this is the page. Ratings sized to their own content
   cannot be read down a column, which is the only reason the toolkit is a row
   list rather than cards. The header labels take the same template, so the
   labels sit over the values they name. */
.tk-ratings {
  display: grid;
  grid-template-columns: repeat(4, 84px);
  column-gap: var(--sp-3);
}
.tk-ratings > * {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The effort chip. The 7px ink cap appears only on this-week items — the one
   piece of urgency the list carries that is not a rating. */
.tk-row__effort {
  display: inline-flex;
  align-self: center;
  justify-self: start;
  align-items: stretch;
  border: 1px solid var(--border-default);
  font-size: 10px;
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fixit-ink);
}
/* :last-child, not every span: the cap is a 7px bar and 24px of padding beat
   its flex-basis, so the chip opened with a black block instead of a sliver.
   With no cap the label is still the last child, so one rule covers both. */
.tk-row__effort > span:last-child { display: block; padding: 3px var(--sp-3); }
.tk-row__effort-cap { flex: 0 0 7px; width: 7px; background: var(--fixit-ink); }

.tk-row__effort--week    { border-color: var(--fixit-yellow); background: var(--fixit-yellow); color: var(--text-on-yellow); }
.tk-row__effort--month   { border-color: var(--yellow-edge); background: var(--yellow-veil); }
.tk-row__effort--quarter { background: var(--bg-page); }
.tk-row__effort--none    { background: var(--bg-page); color: var(--text-muted); }

.tk-row__arrow {
  font-size: var(--fs-body);
  line-height: 1;
  color: var(--text-strong);
  opacity: .35;
  transition: opacity var(--dur-fast) linear, transform var(--dur-fast) var(--ease-out);
}

/* 1120, NOT 1000, AND THE NUMBER IS MEASURED. The five-column template has a
   hard minimum: 154 + 220 + 128 + 372 for the ratings + 18 for the arrow, plus
   four 20px gaps, is 972px of content. At a 1025px viewport the container gives
   903px, so the row ran 48px past its own border and the page scrolled 8px
   sideways — the overflow Ward reported. The template needs about 1094px of
   viewport to fit, so the switch happens at 1120 with room to spare rather than
   at 1000, where it left a 90px-wide band of broken layout.

   None of those widths are arbitrary: .tk-ratings is repeat(4, 84px) because
   the four values have to be comparable across rows, and the comment below
   explains why they stay a row rather than wrapping. */
@media (max-width: 1120px) {
  /* The header row cannot survive a single column, so it goes and each row
     becomes a small stacked record instead.

     TWO COLUMNS, NOT ONE. The effort chip rides up beside the category on the
     first line instead of taking a line of its own — Ward's call, 3 Sep 2026,
     to buy back the vertical space. Everything else spans the full width. */
  .tk-head { display: none; }

  /* EACH ROW ITS OWN BLOCK. Ward's call, 3 Sep 2026. A shared frame with
     hairline dividers is right for a table, where the columns are the point;
     once every row is a stacked record instead, that frame reads as one long
     object and the records run together. So the container's border and fill go
     and each row takes its own, with 12px between them — the gap shows the
     section ground through, which is what separates them.

     border, not border-bottom: this overrides the base rule's single hairline
     on all four sides, and wins on source order rather than specificity. */
  .tk-rows {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    border: 0;
    background: none;
  }
  .tk-row {
    grid-template-columns: 1fr auto;
    row-gap: 10px;
    padding: var(--sp-4) var(--sp-3);
    border: 1px solid var(--border-default);
    background: var(--bg-page);
  }
  .tk-row__kind { grid-column: 1; grid-row: 1; }

  /* A SWATCH AND A LABEL, NOT A PILL. Ward's call, 3 Sep 2026. At the top right
     of a stacked record the filled pill read as a button you could press, on a
     row that is itself already a link — and the four states differ only by
     fill, so the label was carrying the meaning regardless. Same information,
     far less ink.

     The swatch is a ::before, not the existing .tk-row__effort-cap. That cap is
     the 7px urgency mark and only exists on this-week items, so it cannot carry
     all four states; it is hidden here instead.

     These declarations beat the .tk-row__effort--* variants above on SOURCE
     ORDER, not specificity — both are one class deep, and this block is later
     in the file. Moving this media query above them would silently restore the
     pill's fills. --none re-mutes its label because the same ordering would
     otherwise darken it back to ink. */
  .tk-row__effort {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    align-items: center;
    gap: var(--sp-2);
    border: 0;
    background: none;
    color: var(--fixit-ink);
  }
  .tk-row__effort > span:last-child { padding: 0; }
  .tk-row__effort-cap { display: none; }
  .tk-row__effort::before {
    content: "";
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    border: 1px solid var(--border-default);
    background: var(--bg-page);
  }
  .tk-row__effort--week::before {
    border-color: var(--fixit-yellow);
    background: var(--fixit-yellow);
  }
  .tk-row__effort--month::before {
    border-color: var(--yellow-edge);
    background: var(--yellow-veil);
  }
  .tk-row__effort--none { color: var(--text-muted); }

  .tk-row__title { grid-column: 1 / -1; grid-row: 2; }
  .tk-row__arrow { display: none; }
  /* Ratings stay a four-column grid, just narrower: read across a single row is
     still how you compare them, and 84px each will not fit a phone. */
  .tk-ratings {
    grid-column: 1 / -1;
    grid-row: 3;
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* -- The empty state ------------------------------------------------------- */

/* A plain bordered box at lead size in the muted grey. The build had given this
   a yellow left bar, a page-ground fill and a 900px measure, none of which the
   prototype has — an invention, and it made an empty result look like a callout. */
.tk-none {
  padding: var(--sp-8);
  border: 1px solid var(--border-default);
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

/* -- Sam's situation panel -------------------------------------------------- */

/* A horizontal panel under the copy, not a column beside it: the label sits at
   the left and the six facts read across in three columns. Bordered on all four
   sides in Sam's purple, because the whole panel is Sam's and a top bar alone
   reads as a card heading. */
.tk-sam {
  max-width: 860px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  border: 2px solid var(--sam);
  background: var(--bg-page);
}
.tk-sam__id { flex: 0 0 auto; display: flex; flex-direction: column; gap: 2px; }
.tk-sam__facts {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3) var(--sp-5);
}
/* The rule divides one fact from the next, so it goes on the left of each. */
.tk-sam__fact {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding-left: var(--sp-5);
  border-left: 1px solid var(--border-subtle);
}
.tk-sam__fact span:first-child {
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.tk-sam__fact span:last-child {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text-strong);
}

@media (max-width: 880px) {
  .tk-sam__facts { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  /* STACKED, NOT SPLIT. As a row here the label took its natural 175px and left
     the six facts 92px, so every one of them wrapped to three or four lines —
     "ANNUAL GROSS REVENUE" over three, "Pre-release Movies & TV" over four,
     against a tall empty gap beside the centred label.

     align-self MATTERS AS MUCH AS flex-direction. The parent .section__inner
     .stack is align-items: flex-start, so this panel shrink-to-fits: once
     stacked its max-content width is just the longest fact, and it collapsed
     from 335px to 232px — narrower than the page column, which looked like a
     second bug. Stretching it takes the full width and gives the facts 291px,
     enough for every label on one line. */
  .tk-sam {
    flex-direction: column;
    align-items: stretch;
    align-self: stretch;
  }
  .tk-sam__facts { grid-template-columns: 1fr; }
}

/* The same facts as a column beside the copy — Where to Begin. A plain bordered
   panel: the purple is in the label, not the frame. Separate from .tk-sam
   because the two pages give this genuinely different shapes, and one class for
   both meant one of them was always wrong. */
.sam-panel {
  flex: 0 0 320px;
  min-width: 280px;
  padding: var(--sp-6);
  border: 1px solid var(--border-default);
  background: var(--bg-page);
}

/* THE PANEL FILLS ITS LINE ONCE IT HAS WRAPPED. `flex: 0 0 320px` never grows,
   so from the moment the split stacked, Sam's situation sat 320px wide in a
   620px column with 300px of empty space beside it. Ward reported it at about
   512px; it was in fact wrong at every width below the wrap, including the
   whole 620-800 band that the site's existing `.split__aside` rule does not
   reach.

   803.98 IS MEASURED, NOT PICKED. The split wraps when the inner column cannot
   hold .split__body's 340px minimum plus the 64px gap plus this panel's 320px
   basis — 724px. .section__inner is the viewport less 80px of section padding,
   so 724 + 80 = 804. Below that the panel is alone on its line and should fill
   it; at and above, the two sit side by side and 320px is the design.

   THE OTHER THREE SAM SURFACES ALREADY DO THIS, checked at 700px on 4 Sep 2026
   rather than assumed: .sam-hero-card is flex: 1 1 100% below 1130
   (OPEN-ITEMS 81), .sam-next__card is a section-level block, and .tk-sam fills
   its 860px max and was given align-self: stretch below 620 earlier today.
   This was the only one left. */
@media (max-width: 803.98px) {
  .sam-panel { flex-basis: 100%; min-width: 0; }
}
.sam-panel .sam-card__eyebrow { margin-bottom: var(--sp-2); }
.sam-panel .sam-card__disclaimer {
  margin-top: 0;
  margin-bottom: var(--sp-4);
  line-height: var(--lh-body);
}
.sam-panel__facts { display: flex; flex-direction: column; gap: var(--sp-3); }
/* The rule sits under each fact, so the last one closes the list. */
.sam-panel__fact {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--fs-sm);
}
.sam-panel__fact span:first-child { color: var(--text-muted); }
.sam-panel__fact span:last-child { font-weight: var(--fw-bold); text-align: right; }

/* -- The sticky filter mirror ---------------------------------------------- */

/* A compact copy of the filter block, shown once the real one scrolls past the
   top. Hidden from assistive technology and taken out of the tab order when
   invisible — a focusable control the reader cannot see is worse than none. */
.tk-sticky {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 50;
  border-bottom: 2px solid var(--fixit-yellow);
  background: var(--bg-dark);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(-100%);
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.tk-sticky[data-on="true"] { opacity: 1; transform: translateY(0); pointer-events: auto; }

.tk-sticky__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-6) var(--sp-8);
  flex-wrap: wrap;
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-3) var(--sp-10);
}
.tk-sticky__sorts { display: flex; gap: var(--sp-2); flex: 0 0 auto; }
.tk-sticky__sort {
  padding: var(--sp-2) var(--sp-4);
  border: 2px solid var(--border-on-dark);
  background: transparent;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  /* Matches .tk-sort__label, which this mirrors when the band scrolls away. */
  font-weight: var(--fw-black);
  line-height: 1.1;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-on-dark);
}
.tk-sticky__sort[aria-pressed="true"] {
  border-color: var(--fixit-yellow);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
}
.tk-sticky__subs {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-3) var(--sp-6);
  flex-wrap: wrap;
  padding-left: var(--sp-6);
  border-left: 1px solid var(--border-on-dark);
}

@media (max-width: 880px) {
  /* Below this the mirror would take three rows and cover the list it filters. */
  .tk-sticky { display: none; }
}

/* -- Back to top ----------------------------------------------------------- */

.to-top {
  position: fixed;
  right: var(--sp-8);
  bottom: var(--sp-8);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-6);
  background: var(--bg-dark);
  color: var(--text-on-dark);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
              background var(--dur) var(--ease-out);
}
.to-top[data-on="true"] { opacity: 1; transform: translateY(0); pointer-events: auto; }
/* Near the document end, lift clear of the footer's links rather than hiding —
   this button is most useful at the bottom of a long list. */
.to-top[data-lifted="true"] { bottom: calc(var(--sp-8) + 64px); }
.to-top:hover { background: var(--grey-950); }


/* ----------------------------------------------------------------------------
   33. Start Here timeline (Where to begin)

   Three horizons, three columns. One rail, three identical points: every
   horizon gets the same yellow marker on the same ink rule, because the
   difference between them is WHEN, never how important.

   Three columns above 1020px or one stacked column below it. Never two — a
   two-column wrap breaks the rail into a false second row.
   ---------------------------------------------------------------------------- */

/* The one headline on the site with a yellow word in it rather than a yellow
   highlight behind it. On an ink ground a highlight would put a yellow block in
   the middle of the hero, which §3.5 does not allow — colouring the words says
   the same thing quietly. */
.hl-yellow { color: var(--fixit-yellow); }

/* Sam's mark beside a heading, sized to sit on the cap height. */
.sam-lockup { display: flex; align-items: center; gap: var(--sp-5); }
/* --fs-h2: the lockup names a person's journey, it does not open the section. */
.sam-lockup h2 { font-size: var(--fs-h2); }


/* A section that continues the one above it rather than opening a new subject,
   so it keeps its bottom padding and drops its top. */
.section--flush-top { padding-top: 0; }

/* The chevron belongs to the sentence, so it sits on the text baseline. */
.begin-hint {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: var(--lh-body);
  color: var(--text-strong);
}
.begin-hint [data-chevron-right] { flex: 0 0 auto; display: inline-flex; color: var(--text-strong); }

.tl { margin-top: var(--sp-10); }

/* -- The rail -------------------------------------------------------------- */

.tl-rail { display: none; margin-bottom: var(--sp-10); }

/* Row one: the three horizons, on the same column grid as the cards below. */
.tl-rail__points {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--sp-6);
  margin-bottom: var(--sp-6);
}
.tl-rail__point { display: flex; align-items: center; gap: var(--sp-4); }
.tl-rail__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--border-ink);
  color: var(--fixit-ink);
}
.tl-rail__label {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: var(--fw-black);
  line-height: 1;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-strong);
}

/* Row two: the axis. One weight, one colour, and dashed at both ends — the
   journey started before this page and continues after it. */
.tl-rail__axis { position: relative; height: 16px; }
.tl-rail__axis > span { position: absolute; opacity: 0.32; }
.tl-rail__lead { top: 7px; left: 0; width: 56px; border-top: 1px dashed var(--fixit-ink); }
.tl-rail__rule { top: 7px; left: 56px; right: 56px; height: 1px; background: var(--fixit-ink); }
.tl-rail__tail { top: 7px; right: 8px; width: 48px; border-top: 1px dashed var(--fixit-ink); }
.tl-rail__end  { top: 3px; right: 0; width: 9px; height: 9px; background: var(--fixit-ink); }

/* The markers ride the same grid as the points above, so each one sits under
   the middle of its icon. */
.tl-rail__marks {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--sp-6);
}
.tl-rail__marks span {
  width: 16px;
  height: 16px;
  margin-left: 15px;
  background: var(--fixit-yellow);
}

/* -- The columns ----------------------------------------------------------- */

/*
   The three dark card heads have to bottom out on the same line, and their
   content is different lengths. The prototype solved that with a ResizeObserver
   measuring all three and writing a min-height; `subgrid` is the same result
   from two declarations, with no observer, no resize listener and no layout
   flash on load.

   Each card spans both rows of the outer grid and adopts its row track sizing,
   so all three heads share one auto row and all three bodies share the 1fr row.
   Without subgrid support the heads simply size to their own content, which is
   what the stacked layout does anyway — so the fallback is the mobile design
   rather than a broken one.
 */
.tl-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto;
  column-gap: var(--sp-6);
  row-gap: var(--sp-10);
  align-items: stretch;
}

.tl-col {
  display: grid;
  grid-template-rows: auto 1fr;
  border: 1px solid var(--border-default);
}

@media (min-width: 1020px) {
  .tl-rail { display: block; }
  .tl-cols { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: auto 1fr; }
  .tl-col { grid-row: span 2; grid-template-rows: subgrid; }
}

/* -- A column's head ------------------------------------------------------- */

.tl-col__head {
  padding: var(--sp-8);
  background: var(--grey-700);
  color: var(--fixit-white);
}
/* The horizon name, shown only when stacked — in three columns the rail above
   already labels them, and repeating it inside each card is noise. */
.tl-col__horizon {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.tl-col__horizon span:first-child {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  background: var(--fixit-yellow);
}
.tl-col__horizon span:last-child {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  line-height: 1;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--fixit-yellow);
}
@media (min-width: 1020px) {
  .tl-col__horizon { display: none; }
}

.tl-col__when {
  margin-bottom: var(--sp-3);
  font-size: var(--fs-h2);
  font-weight: var(--fw-black);
  color: var(--fixit-white);
}
/* pre-line because the prototype sets it: no note carries a newline today, so
   nothing moves, but a two-line note would collapse into one without it. */
.tl-col__note {
  margin-bottom: var(--sp-5);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  white-space: pre-line;
  color: var(--grey-200);
}

/* Sam speaking, so Sam's colour and Sam's mark — never yellow. */
.tl-col__sam { display: flex; gap: var(--sp-4); align-items: flex-start; }
.tl-col__sam p {
  flex: 1;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
  color: var(--sam-on-dark);
}

/* -- A column's item list -------------------------------------------------- */

.tl-col__body { padding: var(--sp-8); }
.tl-groups { display: flex; flex-direction: column; border-top: 1px solid var(--border-subtle); }

.tl-group__label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-sunken);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-strong);
}

.tl-item {
  display: flex;
  gap: var(--sp-4);
  align-items: center;
  padding: var(--sp-4) var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  transition: box-shadow var(--dur-fast) linear;
}
/* The site's thin row-hover bar: the ground never changes. */
.tl-item:hover { box-shadow: inset 3px 0 0 var(--fixit-yellow); }
.tl-item__tick { flex: 0 0 auto; display: inline-flex; color: var(--text-strong); }
.tl-item__title {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: var(--lh-body);
  color: var(--text-strong);
}

/* -- And then keep going --------------------------------------------------- */

/* A fourth card in the timeline's language — same dark head, no list, because
   there is no end state. It is a card, so it takes the timeline's border and
   its own ground rather than sitting on the page. */
.tl-ongoing { border: 1px solid var(--border-default); }
.tl-ongoing__card {
  display: flex;
  gap: var(--sp-10);
  align-items: stretch;
  flex-wrap: wrap;
  padding: var(--sp-8);
  background: var(--grey-700);
  color: var(--fixit-white);
}
/* SHRINKABLE. flex-shrink was 0, so this column held 380px however narrow the
   screen got. It does not grow, so nothing changes above the breakpoint. */
.tl-ongoing__aside {
  flex: 0 1 380px;
  min-width: 300px;
  display: flex;
  flex-direction: column;
}
.tl-ongoing__eyebrow { display: flex; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-5); }
.tl-ongoing__eyebrow span:first-child {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  background: var(--fixit-yellow);
}
/* Not the --fs-xs eyebrow: this one is a card label at --fs-h3, and the yellow
   square beside it is the same height. */
.tl-ongoing__eyebrow .eyebrow {
  /* Condensed, restated. .eyebrow now defaults to the body face because that is
     what the prototype's section eyebrows inherit — but this one sets
     --font-display explicitly in the prototype
     (prototype/pages/where-to-begin.html:829), because it is a card label at
     display size rather than a section eyebrow. */
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
  line-height: 1;
  /* Display tracking, not the eyebrow's 0.12em: at 22px the wide tracking reads
     as a caption rather than a card label. */
  letter-spacing: var(--ls-display);
  color: var(--fixit-yellow);
}
.tl-ongoing__aside h3 {
  font-size: var(--fs-h1);
  font-weight: var(--fw-black);
  color: var(--fixit-white);
}
/* The claim sits centred in whatever height the body column leaves. */
.tl-ongoing__well {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  min-height: var(--sp-16);
  padding: var(--sp-8) 0 var(--sp-2);
}
.tl-ongoing__claim {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-h2);
  font-weight: var(--fw-semibold);
  line-height: 1.3;
  color: var(--fixit-white);
  text-align: center;
}
.tl-ongoing__body {
  flex: 1;
  min-width: 320px;
  max-width: var(--measure-column);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
/* The floor stands down on a phone — this column was missing the override the
   other text columns have, and it was forcing where-to-begin wider than the screen. */
@media (max-width: 620px) {
  .tl-ongoing__body { min-width: 0; }
}
.tl-ongoing__body p { line-height: var(--lh-body); color: var(--text-on-dark-prose); }
.tl-ongoing__body > p:first-child { font-size: var(--fs-lead); }
/* The card is dark without being a .section--dark, so the link needs the
   on-dark treatment named here. The prototype brightens it to white on hover
   rather than filling it yellow. */
.tl-ongoing__card .inline-link { color: var(--fixit-yellow); }
.tl-ongoing__card .inline-link:hover { background: none; color: var(--fixit-white); }


/* ----------------------------------------------------------------------------
   34. About — stat seal, partner marquee, partner contributions
   ---------------------------------------------------------------------------- */

/* -- The About aside ------------------------------------------------------- */

/* 400px, not the shared .split__aside's 340px: this column carries the seal at
   its full 180px plus two blocks of prose, and 340px wraps all three badly. */
.about-aside {
  /* Shrinkable — flex-shrink was 0 here too. */
  flex: 0 1 400px;
  min-width: 300px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}

/* Each block is topped by a 2px rule. Yellow on the first, grey on the second —
   the campaign's claim about itself gets the campaign's colour; the disclaimer
   under it does not. */
.about-block { padding-top: var(--sp-4); border-top: 2px solid var(--grey-500); }
.about-block--lead { border-top-color: var(--fixit-yellow); }

.about-block h2 { margin-bottom: var(--sp-3); font-size: var(--fs-h2); color: var(--text-on-dark); }

/* --grey-200, and stated here rather than reached for with .lead. `.lead` sets
   --text-body, which is #404040 — dark grey on an ink ground. It rendered as
   near-invisible washed-out text, which is how this was caught. */
.about-block p { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text-on-dark-prose); }
/* The second paragraph is the claim's consequence, so it takes full cream and
   bold. */
.about-block p + p { margin-top: var(--sp-3); font-weight: var(--fw-bold); color: var(--text-on-dark); }

/* The one stat seal on the site. Home's was removed, so this carries the
   alerts figure alone — see docs/OPEN-ITEMS.md #18, which is still open on what
   the figure should actually say. */
.seal {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 180px;
  height: 180px;
  padding: var(--sp-4);
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  text-align: center;
}
.seal__figure {
  display: flex;
  align-items: baseline;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 56px;
  font-weight: var(--fw-black);
  line-height: 1;
  letter-spacing: var(--ls-display);
}
.seal__figure svg { width: .56em; height: .72em; fill: currentColor; }
/* Same size as the numeral, just lighter — not a superscript. */
.seal__times { font-weight: var(--fw-semibold); }
.seal__label {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}

/* -- The partner marquee --------------------------------------------------- */

/*
   Two tracks moving in opposite directions. Each holds half the partners twice
   over, so translateX(-50%) lands exactly where it started and the loop is
   seamless — which is why the duplicate is generated from the same array rather
   than authored, and why it is aria-hidden.

   The negative margins pull the tracks past the section's padding so marks
   enter and leave off-screen; the mask fades both ends so nothing pops.
 */
.mq-wrap {
  display: flex;
  flex-direction: column;
  gap: 1px;
  overflow: hidden;
  margin-left: calc(-1 * var(--sp-10));
  margin-right: calc(-1 * var(--sp-10));
  -webkit-mask-image: linear-gradient(to right, transparent, #000 90px,
                                      #000 calc(100% - 90px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 90px,
                              #000 calc(100% - 90px), transparent);
}

/* The bleed must equal the padding it bleeds past. --sp-10 cancels the section
   padding exactly at full width, but that padding drops to --sp-5 at 620px and
   this did not follow — the track overhung 20px each side and pushed 5px of
   itself beyond the right edge, which was the whole of About's sideways scroll
   on a phone.

   IT LIVES HERE, NOT WITH THE OTHER 620px RULES near the top of the file. Same
   specificity as the rule above, so it only wins by coming after it. Placed
   earlier it silently lost and the 5px stayed. */
@media (max-width: 620px) {
  .mq-wrap {
    margin-left: calc(-1 * var(--sp-5));
    margin-right: calc(-1 * var(--sp-5));
  }
}
.mq-track { display: flex; gap: 1px; width: max-content; will-change: transform; }
.mq-left { animation: mq-shift 56s linear infinite; }
.mq-right { animation: mq-shift 56s linear infinite reverse; }

@keyframes mq-shift {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Motion off means the marquee stops. A stopped marquee hides most of its
   marks, so it becomes a scrollable strip instead — the partners are the point,
   and a partner nobody can see is a relationship problem. */
@media (prefers-reduced-motion: reduce) {
  .mq-track { animation: none; }
  .mq-wrap { overflow-x: auto; }
}

/* -- Become a partner ------------------------------------------------------ */

/* A yellow left bar and a hairline round the other three sides. The build had
   the bar alone, so the box had no right or bottom edge against the sunken
   ground it sits on. */
.partner-cta {
  display: flex;
  gap: var(--sp-8);
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  margin-top: var(--sp-8);
  padding: var(--sp-6) var(--sp-8);
  border: 1px solid var(--border-default);
  border-left: 8px solid var(--fixit-yellow);
  background: var(--bg-page);
}
.partner-cta__copy {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 340px;
  max-width: var(--measure-column);
}
/* The floor stands down on a phone — this column was missing the override the
   other text columns have, and it was forcing about wider than the screen. */
@media (max-width: 620px) {
  .partner-cta__copy { min-width: 0; }
}
.partner-cta__copy h3 { font-size: var(--fs-h2); font-weight: var(--fw-black); }
.partner-cta__copy p { line-height: var(--lh-body); }
.partner-cta__contact { font-weight: var(--fw-bold); }
/* The prototype's only underlined link that is not an .inline-link: ink and
   bold with a plain underline, no yellow and no hover fill. Raised with Ward
   as a one-off. */
.partner-cta__contact a {
  color: var(--text-strong);
  font-weight: var(--fw-bold);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* -- Partner contributions ------------------------------------------------- */

.pr-rows { display: flex; flex-direction: column; gap: var(--sp-5); }

.pr-row {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) auto;
  gap: var(--sp-8);
  align-items: center;
  padding: var(--sp-8);
  border: 1px solid var(--border-subtle);
  background: var(--bg-sunken);
}

/* A white plate behind the logo, because a partner's mark is reproduced
   unmodified and several are drawn for white. */
.pr-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 200px;
  height: 84px;
  padding: var(--sp-4);
  border: 1px solid var(--border-subtle);
  background: var(--surface-plate);
}
/* 60px, not capped at the plate's 52px content box — the prototype sets the
   height and lets the mark run into the padding. max-width still holds a wide
   wordmark inside the plate. */
.pr-logo img { height: 60px; max-width: 100%; width: auto; object-fit: contain; }

.pr-body { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.pr-body__org {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.pr-body__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-strong);
}
/* 70ch, not a px measure: the prototype states it in characters here, and the
   row's middle column is already a minmax(0, 1fr). */
.pr-body__desc {
  max-width: 70ch;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-body);
}

.pr-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  align-self: center;
  padding: 14px var(--sp-6);
  border: 1.5px solid var(--border-ink);
  background: transparent;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text-strong);
  transition: color var(--dur-fast) linear, background var(--dur-fast) linear,
              border-color var(--dur-fast) linear;
}
/* The stroke stays on hover. The prototype's rule says otherwise --
   `border-color:var(--fixit-yellow)` -- but it loses to the inline
   `border:1.5px solid var(--border-ink)` on the element, so what the prototype
   RENDERS is a yellow fill inside an ink stroke. Without it the button dissolves
   into the highlight behind it, which is the same note Ward gave on Home's play
   button. */
.pr-cta:hover { background: var(--fixit-yellow); color: var(--text-on-yellow); }

.pr-note {
  /* --sp-8, as the prototype writes it: the note sits away from the rows rather
     than reading as a caption belonging to the last one. */
  margin-top: var(--sp-8);
  max-width: var(--measure-body);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

@media (max-width: 900px) {
  .pr-row { grid-template-columns: 1fr; gap: var(--sp-5); align-items: start; }
  .pr-logo { width: 180px; }
}

/* -- Two stacked highlights at different tilts ---------------------------- */

/*
   The loudest thing on About, and it has to be: it is the campaign's whole
   argument in two lines. --fs-h2 in the display face, uppercase.

   line-height 1.6 rather than the usual tight display leading — the two
   highlight blocks would otherwise touch, and a marker stroke that collides
   with the one below it reads as a printing fault rather than two hand passes.
 */
.claim-pair {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  line-height: 1.6;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
}
.claim-pair .fx-highlight:first-child { transform: rotate(-0.7deg); transform-origin: left center; }
.claim-pair .fx-highlight:last-child { transform: rotate(0.5deg); transform-origin: left center; }


/* ----------------------------------------------------------------------------
   35. Fund Your Fix — grouped, filterable grant programmes

   An accordion, not a modal. Thirty-five programmes where the reader's job is
   comparison — a modal would hide the list they are comparing against.

   Five columns only in the head row. Eligibility, status, recurrence and next
   action all live in the expanded panel: putting nine facts in a row makes a
   table nobody can read at any width.
   ---------------------------------------------------------------------------- */

/* -- The grouping chips ---------------------------------------------------- */

/* "Last checked July 2026" under the hero copy. Dimmer than the prose it
   follows: it is a provenance note, not part of the argument. */
.fund-checked { font-size: var(--fs-sm); color: var(--text-on-dark-muted); }

/* The standing disclaimer at the foot of the list. */
/* --fs-xs: this is the small print under the table, a step below the page's
   other quiet copy. */
.fund-note {
  margin-top: var(--sp-8);
  max-width: var(--measure-body);
  font-size: var(--fs-xs);
  line-height: var(--lh-body);
  color: var(--text-muted);
}

/* The live count, which changes with the filter — so it is a heading that
   reports rather than one that labels. */
.g-shown { margin: var(--sp-3) 0 var(--sp-8); }

.g-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-6) var(--sp-8);
  flex-wrap: wrap;
}
.g-chips { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Sentence case, sans, 1.5px border. Not the display face and not uppercase —
   these are controls the reader scans quickly, and the prototype keeps them
   quieter than a heading deliberately. */
.g-chip {
  padding: 9px var(--sp-5);
  border: 1.5px solid var(--border-default);
  background: transparent;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: 1.1;
  color: var(--text-strong);
  transition: background var(--dur-fast) linear, color var(--dur-fast) linear,
              border-color var(--dur-fast) linear;
}

/* The label before the chips. Authored copy. */
.g-by {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

.g-spacer { flex: 1; min-width: 0; }
.g-chip:hover { border-color: var(--text-strong); background: var(--bg-sunken); }
.g-chip[aria-pressed="true"] {
  border-color: var(--text-strong);
  background: var(--bg-dark);
  color: var(--text-on-dark);
}
.g-chip[aria-pressed="true"]:hover { background: var(--grey-950); border-color: var(--grey-950); }

/* The open-only filter is a checkbox, not a fifth grouping chip — it answers a
   different question, so it must not look like one of the four. */
/* Also a chip, so it sits on the same visual tier as the four axes while the
   spacer keeps it clearly apart from them. */
.g-only { display: inline-flex; align-items: center; gap: var(--sp-3); }
.g-only__box {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--border-default);
  font-size: 11px;
  font-weight: var(--fw-black);
  line-height: 1;
  color: var(--fixit-ink);
}
.g-only[aria-checked="true"] .g-only__box { border-color: var(--fixit-yellow); background: var(--fixit-yellow); }

/* -- Groups ---------------------------------------------------------------- */

.g-group + .g-group { margin-top: var(--sp-10); }
.g-group__head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 2px solid var(--border-ink);
}
.g-group__head h3 { font-size: var(--fs-h2); font-weight: var(--fw-black); line-height: 1.1; }

/* -- Rows ------------------------------------------------------------------ */

/* One template, shared by the head row and every row button, so the columns
   line up. Same reasoning as the Toolkit's rating columns. */
.g-head,
.g-grid {
  display: grid;
  grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr) minmax(0, 1.15fr) minmax(0, 1fr) 22px;
  gap: var(--sp-5);
  align-items: center;
}

.g-head {
  padding: 0 var(--sp-6) var(--sp-3);
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

.g-rows { display: flex; flex-direction: column; gap: 6px; }

.g-card {
  overflow: hidden;
  border: 1px solid var(--border-subtle);
  background: transparent;
}
/* Yellow frame while open — the loudest the system allows without a yellow
   field, and it marks which row the panel below belongs to. */
.g-card[data-open="true"] { border-color: var(--fixit-yellow); }

.g-row {
  width: 100%;
  padding: var(--sp-5) var(--sp-6);
  background: var(--bg-page);
  text-align: left;
  transition: box-shadow var(--dur-fast) linear;
}
.g-row:hover { box-shadow: inset 8px 0 0 var(--fixit-yellow); }

.g-row__program { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

/* Name and status on one line, wrapping together. */
.g-row__top { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

/* Sans and sentence case, not the display face. A programme's own name is a
   proper noun the reader is matching against a website, so it is set as written
   rather than shouted. */
.g-row__name {
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  color: var(--text-strong);
  text-wrap: pretty;
}
.g-row__body { font-size: var(--fs-xs); color: var(--text-muted); }

.g-row__cell { min-width: 0; font-size: var(--fs-sm); line-height: var(--lh-snug); text-wrap: pretty; }
/* What kind of help it is — recedes, because it repeats across a group. */
.g-row__cell--muted { color: var(--text-muted); }
/* What you actually get — the reason to read the row. */
.g-row__cell--strong { font-weight: var(--fw-bold); }

/* Status. No red and no green — the palette has neither, so an open round reads
   through yellow and a closed one through recession, never through hue. */
.g-status {
  display: inline-flex;
  padding: 2px 8px;
  border: 1px solid var(--border-subtle);
  font-size: 9px;
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--bg-page);
  color: var(--text-muted);
}
.g-status--open    { border-color: var(--fixit-yellow); background: var(--fixit-yellow); color: var(--fixit-ink); }
.g-status--ending  { border-color: var(--grey-100); background: var(--grey-100); color: var(--grey-600); }
.g-status--watch   { border-color: var(--border-default); }

.g-row__chevron { display: inline-flex; justify-self: end; color: var(--text-strong); }
.g-row__chevron svg { transition: transform var(--dur-fast) var(--ease-out); }
.g-row[aria-expanded="true"] .g-row__chevron svg { transform: rotate(180deg); }

/* -- The expanded panel ---------------------------------------------------- */

.g-panel {
  padding: var(--sp-6);
  border-top: 1px solid var(--border-subtle);
  background: var(--bg-page);
}
.g-panel[hidden] { display: none; }

.g-kv { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4) var(--sp-10); }
.g-kv > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
}
.g-kv dt {
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.g-kv dd { font-size: var(--fs-sm); line-height: var(--lh-body); text-wrap: pretty; }

.g-panel__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  flex-wrap: wrap;
  margin-top: var(--sp-5);
}
.g-panel__caveat { font-size: var(--fs-xs); color: var(--text-muted); }

.g-empty__head {
  margin-bottom: var(--sp-2);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  color: var(--text-strong);
}

.g-empty {
  max-width: var(--measure-body);
  padding: var(--sp-6);
  border: 1px solid var(--border-default);
  border-left: 8px solid var(--fixit-yellow);
  background: var(--bg-page);
  line-height: var(--lh-body);
}

@media (max-width: 880px) {
  /* The three middle columns move into the panel, where they already are as
     key/value rows. The head row cannot survive two columns, so it goes. */
  .g-head { display: none; }
  .g-grid { grid-template-columns: 1fr auto; gap: var(--sp-3); }
  .g-cell-hide { display: none; }
  .g-kv { grid-template-columns: 1fr; }
}


/* ----------------------------------------------------------------------------
   36. Tool detail — the eight bespoke pages and the generic template

   One structure, not eight. Every authored entry carries the same twelve
   fields and differs only by which of six optional blocks it fills, so the
   pages are generated from one shape by scripts/build-tool-pages.py.

   Split by volatility per ADR 0002: the title, description, category and all
   body copy are static in each file so the page is indexable; the effort pill,
   the four ratings and the access action are fetched on load by js/tool.js so a
   retune in Django admin never needs a deploy.
   ---------------------------------------------------------------------------- */

/* The hero opens tighter than a normal section and closes at the default.
   Mirrors --short-bottom, which does the same thing at the other edge. */
.section--short-top { padding-top: var(--sp-12); }


/* -- Breadcrumb ------------------------------------------------------------ */

.td-crumbs {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--sp-10);
  /* No font-family: the prototype's crumbs inherit the sans face. I had put
     them in --font-display, and a condensed face at the same 12px reads as a
     smaller breadcrumb, which is how Ward saw it. */
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}
.td-crumbs a { color: var(--text-on-dark); }
.td-crumbs a:hover { color: var(--fixit-yellow); }
.td-crumbs__sep { color: var(--grey-400); }
.td-crumbs__here { color: var(--grey-300); }


/* -- Hero ------------------------------------------------------------------ */

.td-hero { display: flex; gap: var(--sp-10); align-items: flex-start; flex-wrap: wrap; }

/* The category's graphic, on a yellow plate. aria-hidden — the category is
   already named in the eyebrow beside it, so the mark is decoration. */
.td-mark {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 112px;
  height: 112px;
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
}

.td-hero__copy { flex: 1; min-width: 340px; }

/* min-height holds the row open where the share control will land, so adding it
   later does not shift the headline down. Share is deferred — OPEN-ITEMS 24. */
.td-hero__marks {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  min-height: 34px;
  margin-bottom: var(--sp-4);
}

.td-hero__copy h1 { margin-bottom: var(--sp-5); max-width: var(--measure-display); }
.td-hero__lead { margin-bottom: var(--sp-8); }


/* -- The meta strip: effort, then Sam's four ratings ----------------------- */

.td-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-3) var(--sp-6);
  flex-wrap: wrap;
  margin-bottom: var(--sp-8);
  padding: var(--sp-5) 0;
  border-top: 1px solid var(--border-on-dark);
  border-bottom: 1px solid var(--border-on-dark);
}

/* Two segments — "Fix it" then the window. The Toolkit's row chip is this same
   pill with the first segment collapsed to a 7px cap for density. */
.td-effort {
  display: inline-flex;
  align-items: stretch;
  overflow: hidden;
  border: 1px solid var(--border-on-dark-lit);
}
.td-effort span {
  padding: 5px var(--sp-4);
  font-size: 11px;
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
}
/* The lead segment is the same on all three windows; only the trailing one and
   the border carry the urgency. */
.td-effort span:first-child { background: var(--fixit-white); color: var(--fixit-ink); }
.td-effort span:last-child  { color: var(--fixit-white); }

.td-effort--week { border-color: var(--fixit-yellow); }
.td-effort--week span:last-child { background: var(--fixit-yellow); color: var(--text-on-yellow); }
.td-effort--none { border-color: var(--border-on-dark); }
.td-effort--none span { background: transparent; color: var(--grey-400); }

/* The fourth surface 02-DESIGN-SYSTEM.md names: "tool detail on-ink".
   The shared base stacks the label over the pill and runs small, because it is
   sized for the Toolkit's 22-row table and the 380px Sam card. Here the label
   sits BESIDE its pill on one line and both run a size up — there is a 1033px
   column to spend. Letter-spacing is .12em, so it follows the font-size. */
.td-meta .rating { flex-wrap: wrap; gap: var(--sp-3) var(--sp-6); }
.td-meta .rating__cell { flex: 0 0 auto; flex-direction: row; align-items: center; gap: var(--sp-2); }
.td-meta .rating__key { font-size: 10px; }
.td-meta .rating__level { padding: 4px var(--sp-4); font-size: 11px; }

/* Time and Skill describe Sam's situation; Cost and Impact describe the tool.
   The prototype colours the first two in his purple and leaves the others grey,
   which is what makes "Sam's situation" beside them mean something. */
.rating__key--sam,
.rating--on-dark .rating__key--sam { color: var(--sam); }

.td-meta__sam {
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--sam);
}


/* -- The action row -------------------------------------------------------- */

.td-actions { display: flex; gap: var(--sp-4); align-items: flex-start; flex-wrap: wrap; }

/* Bigger than .btn and only used here: this is the one thing the page is for,
   and it sits under a 48px headline rather than in a row of peers. */
.td-dl {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 16px var(--sp-8);
  border: 0;
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  font-family: inherit;
  font-size: 18px;
  font-weight: var(--fw-bold);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.td-dl:hover { background: var(--yellow-press); color: var(--text-on-yellow); }
.td-dl:active { transform: translateY(1px); }

/* The note is authored with line breaks that carry meaning — the host, then the
   cost, then whether an account is needed — so they are preserved. */
.td-dl-group { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.td-dl-note {
  align-self: stretch;
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  text-align: center;
  white-space: pre-line;
  color: var(--grey-300);
}


/* -- Body: authored copy, with the glance panel beside it ------------------ */

.td-body {
  display: flex;
  gap: var(--sp-12);
  align-items: flex-start;
  flex-wrap: wrap;
}
.td-main {
  flex: 1;
  min-width: 0;
  max-width: var(--measure-body);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
}

/* --fs-h1, a tier above the section headings inside it: on a page this narrow
   the reader needs one clear entry point into the prose. */
.td-main h2 { margin-bottom: var(--sp-5); font-size: var(--fs-h1); }
.td-prose { display: flex; flex-direction: column; gap: var(--sp-4); }
.td-prose p { line-height: var(--lh-body); color: var(--text-strong); }

.td-inside__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-bottom: var(--sp-5);
}
.td-inside__head h3 { font-size: var(--fs-h2); }

.td-copy {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px var(--sp-3);
  border: 1px solid var(--grey-400);
  background: transparent;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  line-height: 1;
  text-transform: uppercase;
  color: var(--text-body);
  cursor: pointer;
  transition: background var(--dur-fast) linear;
}
.td-copy:hover { background: var(--fixit-yellow); color: var(--text-on-yellow); }

.td-steps { display: flex; flex-direction: column; gap: var(--sp-3); list-style: none; }
.td-steps li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  line-height: var(--lh-body);
}

/* Numbered where the list is a sequence to work through, square-bulleted where
   it describes what something contains. The authored data says which. */
.td-steps__n {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-top: 2px;
  background: var(--fixit-yellow);
  color: var(--text-on-yellow);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  line-height: 1;
}
.td-steps__dot {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 7px 10px 0 0;
  background: var(--fixit-yellow);
}

/* Provider credit. One user — Train Your Team — but the shape is a component in
   03-COMPONENTS.md, so it is named rather than inlined. */
.td-credit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
  padding-top: var(--sp-8);
  border-top: 1px solid var(--border-default);
}
.td-credit h3 { font-size: var(--fs-h2); }
.td-credit__logos {
  display: flex;
  align-items: center;
  gap: var(--sp-10);
  flex-wrap: wrap;
  margin-top: var(--sp-3);
}
.td-credit__logos img { display: block; width: auto; }


/* -- At a glance: removed --------------------------------------------------

   Ward, 28 Aug 2026: "We got rid of the 'AT A GLANCE' card on all toolkit
   items." So `hasGlance: false` in the prototype was deliberate after all, and
   the panel I restored is gone again — with the facts and the sidebar note it
   carried. The authored `facts` and `sidebarNote` stay in scripts/authored.json
   rather than being deleted, because they are edited copy and the decision is
   about placement.

   One consequence, recorded in OPEN-ITEMS: TPN 2026 STAR Report carries
   `heroDownload: false`, which the prototype's own comment explains as keeping
   the action in the sidebar only. With no sidebar it has nowhere to go, so the
   hero now shows the action unconditionally rather than losing the button.
   -------------------------------------------------------------------------- */

/* -- Keep going ------------------------------------------------------------ */

/* Four cards: 2x2, then 4-across. auto-fit gives a 3+1 orphan at mid widths,
   which is the prototype's own note on this rule. */
.rel-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-5); }

.rel-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6);
  border: 1px solid var(--border-default);
  color: inherit;
  transition: border-color var(--dur) var(--ease-out);
}
.rel-card:hover { border-color: var(--border-ink); color: inherit; }

.rel-card__head { display: flex; align-items: center; gap: var(--sp-3); }
.rel-card__head svg { flex: 0 0 auto; color: var(--text-strong); }
.rel-card__kind {
  /* Sans, like the hero's crumbs — the prototype sets no family here. */
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.rel-card__title {
  font-family: var(--font-display);
  font-size: var(--fs-lead);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-tight);
  text-transform: uppercase;
  text-wrap: pretty;
}
.rel-card__blurb {
  flex: 1;
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--text-muted);
  text-wrap: pretty;
}
.rel-card__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-subtle);
}
.rel-card__open {
  margin-left: auto;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  white-space: nowrap;
  color: var(--text-strong);
}
.rel-card__why { font-size: var(--fs-xs); color: var(--text-muted); text-wrap: pretty; }

/* The card's effort pill is the light-ground twin of the hero's. */
.rel-card .td-effort { border-color: var(--border-ink); background: var(--fixit-white); }
.rel-card .td-effort span { padding: 3px var(--sp-3); font-size: 10px; }
.rel-card .td-effort span:first-child { background: var(--fixit-ink); color: var(--fixit-white); }
.rel-card .td-effort span:last-child { background: var(--fixit-white); color: var(--fixit-ink); }
.rel-card .td-effort--week { border-color: var(--fixit-ink); }
.rel-card .td-effort--week span:last-child { background: var(--fixit-yellow); color: var(--text-on-yellow); }
.rel-card .td-effort--none { border-color: var(--border-default); }
.rel-card .td-effort--none span { background: var(--fixit-white); color: var(--text-muted); }


/* TWO ACROSS AT EVERY WIDTH ABOVE 620px, and the prototype's own four-column
   rule is deliberately not reproduced.

   prototype/pages/tool.html:610 does carry
   `@media (min-width:1100px){ .rel-grid{grid-template-columns:repeat(4,1fr)} }`
   — but it is dead code there. The element also has an inline
   `grid-template-columns:repeat(2,1fr)`, and an inline style beats any
   stylesheet rule, media query or not. Measured rather than assumed: at a
   1586px viewport the query matches and the computed value is still two
   columns of 610px.

   So the prototype has never once rendered four across, and the layout the team
   reviewed and approved is two. Lifting the rule into a real stylesheet, where
   it does fire, gave 295px cards with blurbs running to five lines. Ward chose
   two. See docs/OPEN-ITEMS.md item 124 for the other dead rules like this.

   The 620px rule below is the same kind of dead prototype rule, and IS kept —
   without it the prototype renders two 140px columns on a phone, which is
   unusable and plainly not what anyone intended. */

@media (max-width: 620px) {
  .rel-grid { grid-template-columns: 1fr; }
  .td-mark { width: 72px; height: 72px; }
  .td-mark svg { width: 36px; height: 36px; }
  /* Stacked, not side by side. Beside a 72px mark the copy column is 245px of a
     390px screen, which costs the headline a line and the lead three. */
  .td-hero { flex-direction: column; gap: var(--sp-6); }
  .td-hero__copy { min-width: 0; }
  /* A full-width action beats one that wraps mid-label. */
  .td-actions > * { flex: 1 1 100%; }
  .td-dl { justify-content: center; }
}


/* ----------------------------------------------------------------------------
   38. OneTrust consent banner and preference centre

   COLOUR IS THE BANNER'S ONLY. The preference centre's palette comes from
   Ian's theme in MPA's tenant, and we never won that fight anyway: OneTrust
   defends its buttons with a rule scoring (4, 7, 1) — two ids inside :not()
   exclusions, which count — so overriding it took five repeated ids and still
   left four unreachable buttons blue. The banner needs three, matching their
   two, which is ordinary. Ward called it: yellow where it shows, blue where it
   does not.

   LAYOUT IS A SEPARATE QUESTION, and the preference centre does have some of
   ours, at the bottom of this section — the header's logo, title and close
   button. Their specificity is ordinary and the tenant has no setting for it,
   so neither half of that reasoning applies.

   THE COLOUR RULES ARE TEMPORARY. Their right home is the theme editor in
   MPA's OneTrust tenant — one setting, covering banner, preference centre and
   cookie list together, surviving OneTrust's updates, with no CSS of ours on a
   compliance component. Ian has applied it, which is why most of them are
   already gone; delete the rest as more of it lands. The layout rules are not
   temporary in the same way — the tenant cannot express them, so they stay
   until OneTrust's own markup changes under them.

   THIS USES ID SELECTORS, against the rule at the top of this file. The
   exception is narrow: these are ids in third-party markup we do not author and
   cannot add classes to, and OneTrust's own rules are two ids deep. Nothing
   less reaches them. The banner rules also need no !important, because there
   we out-specify theirs three ids to two. The preference-centre layout rules
   at the end of the section are the exception and say why: their selectors and
   ours tie, and their stylesheet is injected after our <link>, so a tie is a
   loss.

   YELLOW FORCES THE TEXT COLOUR TO CHANGE WITH IT. Their button text is white,
   and white on #FFE400 measures 1.28:1 — unreadable, on the one dialog a
   visitor cannot dismiss without reading it. Ink on yellow is 12.69:1. The two
   always move together.

   ACCEPT AND REJECT ARE STYLED IDENTICALLY, as OneTrust already had them. A
   consent banner that makes Accept louder than Reject is a dark pattern, and
   this campaign has no business shipping one.
   ---------------------------------------------------------------------------- */

/* THE BASE COLOURS ARE NO LONGER OURS. Ian applied the theme in MPA's OneTrust
   tenant on 2 Sep 2026 and it lands: their own injected #onetrust-style now
   carries #FFE400 on #231F20 for both buttons. Measured by deleting these rules
   from the CSSOM and re-reading the computed values — identical either way,
   12.69:1 with and without. So the declarations came out, which is the whole
   point of OPEN-ITEMS 123: no CSS of ours on a compliance component.

   Their earlier green (rgb(104,182,49), white text) is still in that stylesheet
   above the yellow, losing on source order. If the banner ever goes green, the
   theme has been reverted in the tenant — it is not a regression here.

   WHAT STAYS is the press state, and only because OneTrust declares none: the
   buttons had no hover feedback at all. One rule, on the interaction rather than
   the brand. */
#onetrust-consent-sdk #onetrust-banner-sdk #onetrust-accept-btn-handler:hover,
#onetrust-consent-sdk #onetrust-banner-sdk #onetrust-reject-all-handler:hover {
  background-color: var(--yellow-press);
  border-color: var(--yellow-press);
  color: var(--fixit-ink);
}

/* -- THE THIRD BANNER ACTION ----------------------------------------------
   "Cookies Settings", which opens the preference centre. Outlined rather than
   a third yellow block, so the two consent choices stay the loudest things in
   the bar and neither of them is louder than the other.

   RESTORED 2 Sep 2026. This, the floating icon and the banner itself were all
   display: none from 29 Aug, because MPA had published no cookie categories
   and the preference centre opened with nothing in it — indistinguishable,
   from the visitor's side, from broken. Categories are now published: six
   groups, with Strictly Necessary and Performance each carrying two mapped
   cookies, verified against OneTrust.GetDomainData() before this was undone.
   See docs/OPEN-ITEMS.md item 123. */
#onetrust-consent-sdk #onetrust-banner-sdk #onetrust-pc-btn-handler,
#onetrust-consent-sdk #onetrust-banner-sdk #onetrust-pc-btn-handler.cookie-setting-link {
  background-color: var(--fixit-white);
  border-color: var(--fixit-ink);
  color: var(--fixit-ink);
}
#onetrust-consent-sdk #onetrust-banner-sdk #onetrust-pc-btn-handler:hover {
  background-color: var(--fixit-yellow);
  border-color: var(--fixit-ink);
  color: var(--fixit-ink);
}

/* -- THE FLOATING BUTTON'S CLOSE FACE -------------------------------------
   The bottom-left cookie button is a flip card: a .__front showing a cookie,
   and a .__back showing an X once the preference centre is open. Ward asked
   for the X face to be yellow with a dark X, 2 Sep 2026, and its X was also
   sitting off-centre.

   THE MISALIGNMENT WAS 13px. The 24px SVG sat 26px from the left edge of a
   50px button — flush right — while its top offset was already the correct
   13px. `margin: 0 auto` centres it and is the whole fix.

   NOT display: flex, which was the first thing tried and silently did
   nothing. OneTrust sets `display` as an INLINE style on that button and
   toggles it between none and block to run the flip, so no stylesheet rule
   can win without !important — and forcing it would pin the button open and
   show the X on the front face too. Centring the child avoids the fight
   entirely, which is why there is no !important anywhere in this block.

   THE FRONT FACE CANNOT BE DONE HERE. It looks green because it IS green: a
   100x100 PNG on OneTrust's CDN, 55% #6CC04A with a white cookie glyph, sized
   to cover the whole circle. Its background-color underneath is OneTrust's
   blue and is never visible. Recolouring it means MPA replacing the icon in
   the tenant — see docs/OPEN-ITEMS.md item 141. Until they do, the button
   flips from a green cookie to a yellow X. */
#onetrust-consent-sdk #ot-sdk-btn-floating .ot-floating-button__back {
  background-color: var(--fixit-yellow);
}
#onetrust-consent-sdk #ot-sdk-btn-floating .ot-floating-button__back svg {
  margin: 0 auto;
}
/* Their own fill is #292B2E as a presentation attribute on the <g>. Any CSS
   rule beats a presentation attribute, so this needs no !important either. */
#onetrust-consent-sdk #ot-sdk-btn-floating .ot-floating-button__svg-fill {
  fill: var(--fixit-ink);
}

/* -- THE PREFERENCE CENTRE HEADER -----------------------------------------
   Ward, 4 Sep 2026, from a screenshot: "The X is not in the top right and the
   'Privacy Preference Center' should be left aligned under the logo. I think
   we should also shrink down the logo."

   THIS IS LAYOUT, NOT COLOUR, which is why it is here and not waiting on the
   theme editor. Ian's theme covers the palette; the OneTrust tenant has no
   setting for where the close button sits.

   WHAT OneTrust SHIPS. The header is one block containing .ot-pc-logo
   (inline-flex, 200x40) and .ot-title-cntr (inline-block, calc(100% - 250px),
   padding-left: 65px). The two do not fit on one line at 730px, so the title
   wrapped to a second line still carrying that 65px indent — sitting neither
   beside the logo nor under it. The close button lives INSIDE .ot-title-cntr,
   absolutely positioned against it at top: 50%, so it tracked the title's line
   box: measured at 188px in from the panel's right edge, halfway down.

   FOUR FIXES, and each one is doing something:

   1. .ot-pc-header gets border-box. Theirs is content-box at width
      calc(100% - 20px) with padding 10px, which sums to exactly 100% at
      desktop — but their own mobile rule changes the padding to 10px 20px
      10px 10px without changing the width, so the header runs 10px past the
      panel. Border-box makes the sum right at every width, and is what lets
      fix 3 use right: 0 unconditionally. Desktop geometry is unchanged: 710px
      of content either way, measured.

   2. .ot-title-cntr becomes a plain full-width block with no left padding, so
      the title starts on the logo's left edge. Its position: relative also has
      to go — that is what was acting as the close button's containing block.

   3. .ot-close-cntr moves to top: 0; right: 0, now resolving against the
      header, which puts the 44px button in the panel's corner. transform:
      none is not optional: their translateY(-50%) survives a top override and
      would lift the button 23px above the panel.

   4. The logo goes 200x40 -> 150x31, and the img gets max-height so it scales
      to the shorter box rather than overflowing it. justify-content is theirs
      (centre); left is what "under the logo" means once the title is flush.

   EVERY DECLARATION NEEDS !important, AND THE REASON IS SOURCE ORDER, NOT
   SPECIFICITY. OneTrust's rules for this header are `#onetrust-pc-sdk
   .ot-pc-logo` and friends — (1, 1, 0), the same as ours. But their
   #onetrust-style is INJECTED INTO THE HEAD BY THEIR SCRIPT AT RUNTIME, which
   puts it after our <link>, so a tie always resolves their way. This block was
   written without !important first and shipped doing nothing but shrinking the
   logo — the one declaration that happened to carry it. Two of their rules
   also out-specify a plain match on their own: `.ot-pc-logo.ot-pc-logo` for
   the height, and `width: 180px !important` inside their (max-width: 640px)
   query, which nothing but !important can reach at all.

   So: !important throughout, uniformly, rather than a mix of repeated ids and
   carve-outs that a reader would have to reverse-engineer declaration by
   declaration. Repeated ids would work for four of the nine and not the fifth,
   which is the worst of both.

   NOT TOUCHED. Below 640 OneTrust hides #ot-pc-title outright and renders
   #ot-pc-title-mobile instead, which is already full-width and left-aligned —
   so the phone layout needed nothing beyond fixes 1, 3 and 4, and the rules
   here are no-ops against the hidden element rather than special cases.

   ONE THING BELOW 640 DELIBERATELY LOSES. Their `width: 180px !important` in
   that query ties with ours on specificity and wins on order, so the logo's
   flex CONTAINER stays 180px wide on a phone. The image inside does not: our
   height and its max-height bound it to 151x31, measured, left-aligned, with
   nothing sharing its line — so the 29px of empty box is invisible. Beating it
   would take a doubled id purely to change a number nobody can see, on a
   compliance component this section is trying to hold less CSS over, not more.
   If you are here because the logo looks wrong at 375px, measure the img and
   not the div. */
#onetrust-pc-sdk .ot-pc-header {
  box-sizing: border-box !important;
  width: 100% !important;
}
#onetrust-pc-sdk .ot-title-cntr {
  display: block !important;
  position: static !important;
  width: auto !important;
  padding-left: 0 !important;
}
#onetrust-pc-sdk .ot-close-cntr {
  top: 0 !important;
  right: 0 !important;
  transform: none !important;
}
#onetrust-pc-sdk .ot-pc-logo {
  width: 150px !important;
  height: 31px !important;
  justify-content: flex-start !important;
}
#onetrust-pc-sdk .ot-pc-logo img {
  max-height: 100% !important;
}

