/* ============================================================================
   JOSEPH JAFFE · "Ink & Citrus"
   Hand-written, zero dependencies. This file is the DEFENSIVE SPINE first and
   a stylesheet second: every §N comment below maps to a numbered section of
   references/defensive-spine.md, baked in on day one rather than patched late.
   ========================================================================= */

/* ---------- fonts (self-hosted; render-blocking Google CSS tanks mobile LCP) */
@font-face{
  font-family:'Archivo';
  src:url('../fonts/archivo-var-latin.woff2') format('woff2-variations');
  font-weight:100 900; font-stretch:62% 125%; font-display:swap;
}
@font-face{
  font-family:'Newsreader';
  src:url('../fonts/newsreader-var-latin.woff2') format('woff2-variations');
  font-weight:200 800; font-display:swap;
}

/* ============================ TOKENS ==================================== */
:root{
  /* ---- ground ---- */
  --ink:#14110F;          /* hero · shows · archive · closing */
  --ink-2:#1D1916;        /* raised surface ON ink */
  --ink-3:#2A2421;        /* hairline on ink */
  --bone:#F7F4EF;         /* coaching · books · about: the CANVAS default */
  --bone-2:#EFEAE2;       /* raised SURFACE on bone (cards/tags/filters). NOT a section ground */
  --sand:#F1E5D4;         /* the second light SECTION ground: warm, 1.13:1 off bone */
  --line-sand:#D8C9B3;    /* --line re-derived on sand: 1.31:1, matching --line's 1.30:1 on bone */
  --white:#FFFFFF;

  /* ---- accent (single, loud) ---- */
  --citrus:#FF7A00;
  --ember:#C63C12;
  /* §16: the bright accent fails AA as small text on light grounds.
     Measured on --bone #F7F4EF:  --citrus 2.53:1 (FAIL) · --citrus-text 4.83:1 (PASS).
     Use --citrus for large/decorative/on-ink, --citrus-text for small copy on bone. */
  --citrus-text:#A33F00;
  /* craft trap 27. A token is a claim about a PAIR. Measured on --ink #14110F:
     --citrus 6.42:1 (PASS) · --muted 3.41:1 (FAIL) → on-ink gets its own token. */
  --on-ink:#F2EDE6;
  --on-ink-dim:#B9AFA4;   /* 7.34:1 on --ink */

  /* ---- type ---- */
  --type:#171310;         /* 15.1:1 on bone */
  --muted:#5E5750;        /* 6.31:1 on bone */
  --line:#DED7CC;

  --display:'Archivo',system-ui,-apple-system,'Segoe UI',sans-serif;
  --body:'Newsreader',Georgia,'Times New Roman',serif;
  --util:'Archivo',system-ui,-apple-system,'Segoe UI',sans-serif;

  /* ui-ux-pro-max §6: one modular scale, no arbitrary sizes */
  --fs-xs:0.75rem; --fs-sm:0.875rem; --fs-base:1.0625rem; --fs-md:1.1875rem;
  --fs-lg:1.375rem; --fs-xl:1.75rem; --fs-2xl:2.25rem; --fs-3xl:3rem;
  --fs-4xl:clamp(2.5rem,5.2vw,4.5rem);
  /* The hero size is the ONE step on this scale that answers to BOTH axes.
     A width-only clamp pins at its 7.5rem max on every screen wider than
     ~1620px, including short ones, and the hero became a CONSTANT 1323px:
     at 1920x950 both CTAs sat 306px below the fold, and had since day one.
     A fold is a HEIGHT budget, so the preferred term carries a height. `svh`
     not `dvh`: dvh changes as mobile browser chrome collapses, which would
     resize the headline mid-scroll. Measured 2560/1920/1600/1440/1366. See
     _build/_probe-herofix.mjs. Below ~590px of height the 2.75rem floor takes
     over, so mobile is untouched (there 7.4vw wins the min anyway). */
  /* 2026-09-08: ceiling lowered 7.5rem -> 3.6rem. MEASURED, not chosen: the
     headline is now a two-clause mirror whose longer clause is 12.658em, and
     the hero copy column tops out at 736px, so at any size above 58.1px that
     clause cannot hold its own line and the mirror breaks. 3.6rem is 57.6px.
     The vw and svh terms are untouched, so every viewport below the ceiling
     computes exactly what it did before and _gate-herofold stays satisfied. */
  --fs-hero:clamp(2.75rem,min(7.4vw,7.6svh),3.6rem);

  /* ui-ux-pro-max §5: 4/8pt rhythm */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px;
  --s7:48px; --s8:64px; --s9:96px; --s10:128px;

  --wrap:1280px;
  --measure:34rem;        /* craft trap "width is earned by density", so bound the text */
  --r:4px;                /* radius: near-square. This brand is not soft. */
  --r-lg:8px;

  /* ui-ux-pro-max §5: declared z-index scale, no ad-hoc values */
  --z-base:1; --z-sticky:100; --z-scrim:390; --z-drawer:400; --z-toast:500;

  /* ui-ux-pro-max §5 `fixed-element-offset`. `.hd` is position:sticky and so
     IN FLOW: it occupies real height and everything below it starts that far
     down. Anything sized to "one screen" must subtract this or it overflows by
     exactly this much, at every viewport, invisibly.
     ⚠ This is a PRE-JS FALLBACK ONLY. `.hd`'s real height is content-driven
     (padding + nav), so it is MEASURED at runtime and written back over this
     value. Never trust the literal for anything that must be exact. */
  --hd-h:77px;

  --ease:cubic-bezier(.22,.61,.36,1);
  --dur:200ms;            /* §7: 150-300ms */
}

/* ============================ RESET ===================================== */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:96px}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  margin:0; background:var(--bone); color:var(--type);
  font-family:var(--body); font-size:var(--fs-base); line-height:1.65;
  font-optical-sizing:auto;
  -webkit-font-smoothing:antialiased;
}
/* §25: body children get a stacking context, but overlays are appended as
   DIRECT body children and their own class rules (0,1,0) outrank this (0,0,1).
   ⛔ Never add `body>.scrim{z-index:auto}`. That recreates the bug it fixes (§34). */
body>*{position:relative;z-index:var(--z-base)}

img,svg,video{display:block;max-width:100%}
/* §11: the FULL sizing set, never a partial one */
.media img,.frame img,.cover img,.tile__img img{
  max-width:100%;max-height:100%;width:auto;height:auto;min-width:0;min-height:0;
}
/* §1: every inline SVG is born small, so an icon dropped into a new container
   can never balloon it. Components that need a fixed px size say so themselves. */
svg{width:1.1em;height:1.1em;flex:none;vertical-align:-.15em}
.btn svg,.icon-btn svg{width:18px;height:18px}
.drawer svg,.sp-top svg{width:20px;height:20px}
/* craft trap 40: never pad an <svg> itself (padding moves the viewport, the UA's
   overflow:hidden then clips the drawing away). Pad a wrapper span. */
.i-pad{display:inline-grid;place-items:center;padding:9px}

h1,h2,h3,h4{margin:0;font-family:var(--display);font-weight:800;line-height:1.02;
  letter-spacing:-.025em;text-wrap:balance}
p{margin:0 0 1em}
a{color:inherit}
button{font:inherit;color:inherit}
ul,ol{margin:0;padding:0;list-style:none}   /* craft trap 41: UA padding-left:40px
   paints the moment a list gets a background. House reset, applied everywhere. */

/* §1 a11y: focus must always be visible */
:focus-visible{outline:3px solid var(--citrus);outline-offset:3px;border-radius:2px}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) :focus-visible{outline-color:var(--citrus)}
.skip{position:absolute;left:-9999px;top:0;z-index:600;background:var(--citrus);
  color:#fff;padding:14px 20px;font-family:var(--util);font-weight:700}
.skip:focus{left:8px;top:8px}

::selection{background:var(--citrus);color:#fff}

/* ====================== VISIBILITY UTILITIES ============================ */
/* §5: utilities ONLY ever hide, and are DOUBLED so they beat any bare
   component class declared later in the cascade (the Sorbet lesson). */
.hide-mob.hide-mob{}
.only-mob.only-mob{display:none}
@media(max-width:900px){
  .hide-mob.hide-mob{display:none}
  .only-mob.only-mob{display:revert}
  .icon-btn.only-mob.only-mob{display:grid}
}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}

/* ============================ LAYOUT ==================================== */
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--s5)}
@media(min-width:900px){.wrap{padding-inline:var(--s7)}}

.sec{padding-block:var(--s9)}
@media(max-width:900px){.sec{padding-block:var(--s8)}}
.sec--tight{padding-block:var(--s8)}

/* The CANVAS is the default ground. A tinted band is a decision, not a habit
   (build-standards §7 scope note + composition-gate ceiling). */
.sec--ink{background:var(--ink);color:var(--on-ink)}

/* ---- THE CLOSING BLOCK -------------------------------------------------
   🔴 This was `.sec--ink`, which is the SAME TOKEN as the footer
   (--ink #14110F). Measured identical on 7 of 13 pages: the last conversion
   ask on the site dissolved straight into utility navigation with no seam.
   Two correct rules producing one defect. Neither block is wrong alone.
   It is the final ask, so it takes the accent. The palette says citrus is
   "single, loud", and this is the moment worth spending it on, and the
   footer keeps the ink, so the boundary is unmissable.
   Type is INK on citrus: measured 7.13:1. White would be 2.56:1 and fail, so
   everything here inverts. No `opacity` is used for recession anywhere in
   this block; recession is weight and size (the .flip__from lesson). */
/* ---- ICON SYSTEM --------------------------------------------------------
   `.jj-i` is (0,1,0) and beats the global `svg{width:1.1em}` (0,0,1), which
   is the whole reason the icons ship with NO width/height attributes: a
   presentation attribute is (0,0,0) and would have lost to that global,
   rendering every mark at 17.6px instead of 24. ⛔ Never put padding on the
   <svg>: padding moves the viewport, not the drawing, and the UA's
   overflow:hidden then clips the glyph away completely, which looks like a
   deliberate gap rather than a missing icon. Pad the wrapper span. */
.jj-i{width:24px;height:24px;flex:none;display:block}
/* Card marks run larger. At 24px inside a card this size the geometry stops
   reading as an idea and starts reading as a fragment. The 45° language only
   works when the angles are big enough to be seen as angles. */
:is(.cmp__ic,.kn__ic,.door__ic) .jj-i{width:30px;height:30px}

/* Six Key Components: mark and numeral share the top line. */
.cmp__top{display:flex;align-items:center;justify-content:space-between;gap:var(--s3)}
.cmp__ic{display:block;color:var(--citrus-text)}
.cmp__n{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.1em;font-variant-numeric:tabular-nums;color:var(--citrus-text)}

/* Keynote marks. */
.kn__top{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s3);margin-bottom:14px}
.kn__ic{display:block;color:var(--citrus-text)}

/* Homepage door marks use the same treatment as the component and keynote marks
   above, deliberately: the two doors open onto those two pages, so the mark is
   an echo rather than a third icon idiom. `align-self:flex-start` because
   .card__body is a flex COLUMN and a block child would otherwise stretch to the
   full card width, putting a 30px glyph in the middle of a 400px box. */
.door__ic{display:block;align-self:flex-start;color:var(--citrus-text);
  margin-bottom:10px}

/* ---- ARCHIVE TEASER -----------------------------------------------------
   Counts as display numerals + the archive's own topic list as links. The
   numerals are the point: they are the one thing on a citrus band that can
   carry weight without another photograph, and they give the page a section
   archetype it did not have (design-presence scores a "display numeral" bit
   that this build never set anywhere). Ink on citrus throughout. */
/* 🔴 REBUILT 2026-08-08. Was five centred blocks stacked on one axis with the
   22 topics as outlined pills: ink outlines at .28 alpha on citrus, so the
   pills read as ghosts and the whole band came across as "just text and
   orange". Three things changed and only one of them is paint:

   1. COMPOSITION: an asymmetric split replaces the centred stack, so the band
      has an entry point instead of five things competing on one axis.
   2. GROUND: the topic index sits on an INK PANEL. That is not a new device:
      `.route` already puts ink cards on this exact citrus band, so the band now
      has two grounds using the language the page already speaks.
   3. INFORMATION: the topics carry their real item COUNT and are ordered by
      it, so the list says what the archive is ABOUT (EOS and Leadership carry
      it) instead of being 22 decorative words in alphabetical order. Counts are
      COUNTED from JJ.content at render time. See initArcTeaser().

   ⚠ `.sec--citrus :is(h1,h2,h3,h4)` and `.sec--citrus p` (0,1,1) force INK on
   every heading and paragraph in this section: correct on the citrus ground,
   invisible on the ink panel. Every panel rule below is (0,2,0) or better for
   exactly that reason, the same way `.sec--citrus .route h3` is. Do not
   "simplify" the doubled selectors away. */
.arct{display:grid;grid-template-columns:minmax(0,1fr) min(46%,600px);
  gap:var(--s8);align-items:start}

/* ---- left rail ---- */
.arct__rail .h1{max-width:16ch;margin-bottom:var(--s4)}
.arct__rail .lede{max-width:40ch;margin:0 0 var(--s7)}

/* the three counts as a ruled ledger rather than three floating numerals.
   Hairlines in ink at .22 read on citrus without becoming a second accent. */
.arct__stats{display:flex;flex-wrap:wrap;gap:var(--s6);margin-bottom:var(--s7);
  padding-block:var(--s5);
  border-top:1.5px solid rgba(20,17,15,.24);
  border-bottom:1.5px solid rgba(20,17,15,.24)}
.arct__stat{display:grid;gap:1px}
.arct__stat b{font-family:var(--display);font-weight:800;
  font-size:clamp(2.4rem,4.2vw,3.4rem);line-height:.88;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;color:var(--ink)}
.arct__stat span{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:rgba(20,17,15,.66)}

/* ---- the ink index panel ---- */
.arct__idx{background:var(--ink);border-radius:var(--r);padding:var(--s5) var(--s5) var(--s4)}
.sec--citrus .arct__idx-h{display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--s4);margin:0 0 var(--s3);padding-bottom:var(--s3);
  border-bottom:1px solid var(--ink-3);
  font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:var(--citrus)}
.sec--citrus .arct__idx-h em{font-style:normal;letter-spacing:.06em;
  color:var(--on-ink-dim);font-weight:600}          /* 7.34:1 on ink */

.arct__topics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:1px var(--s5);padding:0;margin:0;list-style:none}
.sec--citrus .arct__topics a{
  position:relative;display:flex;align-items:baseline;justify-content:space-between;
  gap:10px;padding:7px 9px;border-radius:3px;overflow:hidden;
  text-decoration:none;color:var(--on-ink);                 /* 15:1 on ink */
  font-family:var(--util);font-size:.8125rem;font-weight:600;
  transition:background var(--dur) var(--ease)}
/* the proportional bar. Width is the topic's real share of the deepest topic,
   set per row by initArcTeaser. Decorative only. Nothing reads AGAINST it,
   the label sits on the ink panel either way. */
.sec--citrus .arct__topics a::before{
  content:'';position:absolute;left:0;top:0;bottom:0;
  /* floor of 5px so a 1-item topic reads as the SMALLEST bar rather than as a
     rendering artefact. On a ~260px track that is under 2% of distortion and
     the exact count is printed at the end of every row, so the bar never has
     to carry a number on its own. */
  width:max(var(--w,0%),5px);
  background:rgba(255,122,0,.17);border-radius:3px;pointer-events:none;
  transition:background var(--dur) var(--ease)}
.sec--citrus .arct__topics a:hover{background:rgba(255,255,255,.05)}
.sec--citrus .arct__topics a:hover::before{background:rgba(255,122,0,.34)}
.sec--citrus .arct__topics b{position:relative;font-weight:600}
.sec--citrus .arct__topics span{position:relative;flex:none;
  font-variant-numeric:tabular-nums;font-size:var(--fs-xs);font-weight:700;
  color:var(--on-ink-dim)}                                  /* 7.34:1 on ink */

@media(max-width:1000px){
  .arct{grid-template-columns:1fr;gap:var(--s7)}
  .arct__rail .h1,.arct__rail .lede{max-width:none}
}
@media(max-width:560px){
  .arct__topics{grid-template-columns:1fr}
  .arct__stats{gap:var(--s5)}
}

/* ---- CLOSING ROUTES -----------------------------------------------------
   Three ink cards on the citrus band. White-on-ink inside them is ~15:1, and
   the cards sit ON the citrus rather than tinting anything new, so the band's
   share of the page is unchanged (index has little tint headroom).
   ⚠ `.sec--citrus :is(h1,h2,h3,h4)` forces ink on every heading in this
   section: correct for the headline, wrong inside a dark card. `.sec--citrus
   .route h3` (0,2,1) beats it (0,1,1); do not "simplify" that away. */
/* ⚠ `ch` resolves against the ELEMENT'S OWN font. A cap on this wrapper is
   measured in body-size characters while the headline inside renders at
   display size, so a wrapper cap of 30ch shredded the h2 to one word per
   line. Cap the heading and the lede on THEMSELVES, each in its own units. */
/* The headline sat left with the lede beneath it and the whole upper-RIGHT of
   the band, roughly 40% of the loudest ground on the site, carrying nothing.
   Split, not stacked: the sentence holds the left, the lede sits bottom-right
   against it. Same words, no dead field. */
.closing__hd{display:grid;gap:var(--s5) var(--s7);margin-bottom:var(--s7);
  grid-template-columns:minmax(0,1fr);align-items:end}
@media(min-width:900px){
  .closing__hd{grid-template-columns:minmax(0,1.22fr) minmax(0,.78fr)}
  .closing__hd .lede{padding-bottom:8px}
}
.closing__hd .h1{max-width:20ch}
.closing__hd .lede{max-width:54ch}

/* ==== CLOSING CITRUS BAND WITH A FIGURE ==================================
   Allen 2026-08-09: *"on every page you've got this bottom orange bar … do you
   think we need a graphic on all of these … it's gonna look and just complete
   the design."* He was describing real debt: the homepage's closing band was
   rebuilt with imagery on 08-08 and the page-level citrus bands were not, so
   they read as unfinished beside it.

   ⚠ FOUR bands, not six. `contact` already carries three social cards and
   `shows` carries the archive ledger + topic index rebuilt 08-08. Both are
   already the most designed thing on their page, and a figure there would
   compete with what is already working rather than complete it.

   🔑 THE PHOTOGRAPH NEVER TOUCHES THE CITRUS. Allen's own constraint was "they
   don't need a clash", and a photograph on a saturated orange ground is exactly
   that. This build already solved it once: `.sec--citrus .route` seats its
   photography in an INK CARD sitting ON the citrus. This is that same form at a
   different size, not a new device (a solved component has a form; look at the
   real one first).

   The copy column leads and holds the buttons. The figure is SUPPORT, never the
   subject: this band's entire job is its CTA, and the one thing a decorative
   image must not do here is outrank it. */
.cband{display:grid;grid-template-columns:1.08fr .92fr;gap:var(--s7);
  align-items:center;text-align:left}
.cband__copy{min-width:0}
.cband__copy .h1{margin:0 0 16px;max-width:20ch}
.cband__copy .lede{margin:0 0 var(--s7);max-width:52ch}
.cband__fig{position:relative;aspect-ratio:16/11;overflow:hidden;
  border-radius:var(--r);background:var(--ink);border:2px solid var(--ink)}
.cband__fig>img{width:100%;height:100%;object-fit:cover;display:block}
/* the same seat the route cards use, so the photo ends on a graded edge rather
   than a hard line against the orange */
.cband__fig::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(20,17,15,0) 62%,rgba(20,17,15,.34) 100%)}
@media(max-width:900px){
  .cband{grid-template-columns:1fr;gap:var(--s5);text-align:center}
  .cband__copy .h1,.cband__copy .lede{margin-inline:auto}
  .cband__copy .lede{margin-bottom:var(--s5)}
  /* copy first on a phone: the CTA must not sit under a decorative image */
  .cband__fig{order:2;aspect-ratio:16/10}
}

.routes{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s4)}
/* Each route now SHOWS the thing it routes to: a real photograph of the room
   he speaks to, and his six real jackets. The card is a figure over a body,
   never text over photography (craft trap: text on photography). */
.route{display:grid;grid-template-rows:auto 1fr;align-content:start;
  background:var(--ink);border-radius:var(--r);text-decoration:none;
  border:2px solid var(--ink);overflow:hidden;
  transition:transform var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.route:hover{transform:translateY(-3px);border-color:var(--white)}
.sec--citrus .route h3{color:var(--white);font-family:var(--display);
  font-weight:800;font-size:var(--fs-lg);letter-spacing:-.02em;margin:0}

.route__fig{position:relative;aspect-ratio:16/10;overflow:hidden;background:#0E0C0B}
.route__fig>img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform 480ms var(--ease)}
.route:hover .route__fig>img{transform:scale(1.035)}
/* seats the photograph into the card instead of ending it on a hard edge */
.route__fig::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(20,17,15,0) 45%,rgba(20,17,15,.9) 100%)}
.route__body{padding:var(--s5);display:grid;gap:10px;align-content:start}

/* the mark straddles the seam. It keeps the 12-icon system present and stops the
   card reading as two stacked rectangles. Ink on citrus = 7.20:1. */
/* TOP-left, not bottom: the jackets in route 3 are bottom-aligned, so a mark on
   the lower seam sat on top of a book cover. The top edge is empty on all three
   figures by construction. */
.route__ic{position:absolute;left:14px;top:14px;bottom:auto;z-index:2;
  display:grid;place-items:center;width:42px;height:42px;
  background:var(--citrus);color:var(--ink)}
.route__ic svg{width:22px;height:22px}

/* THE JACKETS: his six, bottom-aligned and overlapped like a hand of cards.
   Rendered from the six real cover files; `_gate-routefan.mjs` pins this list
   to JJ.books so it can never drift and can never admit Traction, which is
   Gino Wickman's book and not one of the six. */
/* WIDTH ARITHMETIC, not eyeballing: the row is 6*w - 5*overlap.
     6*20.5 - 5*3.2 = 107%  → outer jackets sliced off the card.
     6*18.5 - 5*5.4 =  84%  → fitted, but each jacket showed only ~13% of the
                              card and the six merged into one striped smear.
     6*23   - 5*8.5 =  95.5% → fitted, but only 1-2px of clearance either side.
     6*22   - 5*8.4 =  90%   → SHIPPED: ~19px clearance, jackets still countable.
   The band claims SIX books; the picture has to be countable, so legibility of
   the individual jacket is the constraint, not tidiness of the row. */
.route__fan{position:absolute;inset:0;z-index:1;display:flex;
  align-items:flex-end;justify-content:center;padding:30px 8px 0}
.route__fan img{width:22%;height:auto;flex:none;align-self:flex-end;
  margin-left:-8.4%;transform-origin:bottom center;
  box-shadow:0 8px 20px rgba(0,0,0,.6);
  transition:transform 420ms var(--ease)}
.route__fan img:first-child{margin-left:0}
.route__fan img:nth-child(1){transform:rotate(-7deg) translateY(4px)}
.route__fan img:nth-child(2){transform:rotate(-4deg) translateY(1px)}
.route__fan img:nth-child(3){transform:rotate(-1.5deg)}
.route__fan img:nth-child(4){transform:rotate(1.5deg)}
.route__fan img:nth-child(5){transform:rotate(4deg) translateY(1px)}
.route__fan img:nth-child(6){transform:rotate(7deg) translateY(4px)}
.route:hover .route__fan img:nth-child(1){transform:rotate(-10deg) translateY(2px)}
.route:hover .route__fan img:nth-child(6){transform:rotate(10deg) translateY(2px)}
@media(prefers-reduced-motion:reduce){
  .route__fan img,.route__fig>img{transition:none}
  .route:hover .route__fan img:nth-child(1){transform:rotate(-7deg) translateY(4px)}
  .route:hover .route__fan img:nth-child(6){transform:rotate(7deg) translateY(4px)}
  .route:hover .route__fig>img{transform:none}
}
.route__d{color:rgba(255,255,255,.72);font-family:var(--body);
  font-size:var(--fs-sm);line-height:1.5}
.route__go{display:flex;align-items:center;gap:8px;margin-top:6px;
  font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--citrus)}
.route__go svg{width:17px;height:17px;flex:none;
  transition:transform var(--dur) var(--ease)}
.route:hover .route__go svg{transform:translateX(4px)}
@media(max-width:900px){.routes{grid-template-columns:1fr}}
.sec--citrus{background:var(--citrus);color:var(--ink);border-top:1px solid var(--ember)}
.sec--citrus :is(h1,h2,h3,h4),.sec--citrus .lede,.sec--citrus p{color:var(--ink)}
.sec--citrus .eyebrow{color:var(--ink)}
/* §34: doubled so no context rule can repaint the label */
.sec--citrus .btn--ghost.btn--ghost{color:var(--ink);border-color:rgba(20,17,15,.5)}
.sec--citrus .btn--ghost.btn--ghost:hover{border-color:var(--ink);background:rgba(20,17,15,.09)}
.sec--citrus .tlink{color:var(--ink);border-bottom-color:var(--ink)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) :is(h1,h2,h3,h4){color:var(--white)}
/* ---- THE SECOND LIGHT GROUND ------------------------------------------
   Allen, live review: "differentiation between the sections of colour".
   The page had ONE light ground, so five of coaching's ten sections were the
   identical canvas and three of them ran consecutively. `--bone-2` was tried
   here first and is the wrong tool twice over: it is the RAISED-SURFACE token
   (cards, tags, filters, selected tabs all use it), and at 1.06:1 against
   --bone the step is not visible across a full-width band anyway.
   --sand is a real step and it steps by TEMPERATURE, which is the brand's own
   logic: "Ink & Citrus" separates the two halves of Joseph by warmth, not by
   two visual systems. Cool ivory -> warm sand -> ink.
   craft trap 39/41: a ground is a contrast change, so every token in it is
   re-derived, not assumed:
     --type 14.86:1 · --muted 5.72:1 · --citrus-text 5.17:1 (all PASS)
     --citrus 2.10:1, decorative/large ONLY here too, same as on bone
     white .card 1.24:1, cards read BETTER on sand than on bone (1.10:1)
     --line 1.15:1, FAILS to hold the hairline weight it has on bone (1.30:1),
       so the section REDEFINES --line locally to --line-sand (1.31:1). Every
       child that borders with var(--line) inherits the corrected value; there
       is no per-component patching. */
.sec--sand{background:var(--sand);--line:var(--line-sand)}

/* §14: a 1fr track is minmax(auto,1fr) and refuses to shrink. min-width:0 is
   the default on every grid/flex child, sitewide. NEVER html,body{overflow-x} (§4). */
.grid,.row,.split,.cards{min-width:0}
.grid>*,.row>*,.split>*,.cards>*{min-width:0;overflow-wrap:anywhere}

/* §18: any rule declaring grid tracks declares display:grid in the SAME block */
/* minmax(380px,..) forces a 380px MINIMUM track, which cannot fit a 360px
   viewport: the track overflows and, worse, widens the mobile layout viewport
   itself (measured: innerWidth 405 at a requested 360). min(Npx,100%) lets the
   track collapse to the container on small screens. */
.cards{display:grid;gap:var(--s5);grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr))}
.cards--2{display:grid;gap:var(--s5);grid-template-columns:repeat(auto-fill,minmax(min(380px,100%),1fr))}
.cards--3{display:grid;gap:var(--s5);grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr))}
.cards--4{display:grid;gap:var(--s4);grid-template-columns:repeat(auto-fill,minmax(min(230px,100%),1fr))}
/* FIXED-TRACK grids, added 2026-08-25 for coaching §4.3 and §4.4.
   ⚠ These exist because `auto-fill` is the wrong tool for a KNOWN item count.
   `.cards--3` fits FOUR tracks at 1280, so a three-item row rendered
   left-crowded against an empty right quarter, the same defect the homepage
   through-line row hit on 2026-08-25. `auto-fill` is correct when the count
   varies; when the count is fixed by the brief, name the tracks. */
.trio{display:grid;gap:var(--s5);grid-template-columns:repeat(3,minmax(0,1fr))}
.five{display:grid;gap:var(--s4);grid-template-columns:repeat(5,minmax(0,1fr))}
@media(max-width:1180px){.five{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:900px){.trio{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:760px){.five{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:620px){.trio,.five{grid-template-columns:1fr}}
/* The 8 EOS tools. Allen 2026-08-08: "otherwise those boxes are naked."
   design-presence had the same row at muteCardRows=1: a row of >=3 identical
   boxes carrying no icon, photo or figure. The mark is the fix for both. */
.tool{padding:var(--s5);display:grid;gap:10px;align-content:start}
.tool__ic{display:block;color:var(--citrus)}
.tool__ic .jj-i{width:28px;height:28px}
.tool__t{margin:0;font-size:var(--fs-base)}
.tool__d{margin:0;font-size:var(--fs-sm)}
/* a decorative full-width plate. `aspect-ratio` + width/height attrs on the
   <img> keep the box definite before decode, the shrink-to-fit trap that
   measured 0px wide on the home hero. */
.band{position:relative;width:100%;aspect-ratio:2000/848;overflow:hidden;
  border-radius:var(--r);background:var(--ink)}
.band img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
@media(max-width:700px){.band{aspect-ratio:16/9}}
/* A full-bleed variant that lives BETWEEN two sections instead of inside one,
   so the plate is the seam rather than an illustration. No radius: a rounded
   box reads as an object on a ground, and a seam has to read as the ground
   changing. Shallower than .band because it is spending full viewport width.
   Declared after the .band media query so the ratio is not silently inherited. */
.band--bleed{border-radius:0;aspect-ratio:2000/700}
@media(max-width:700px){.band--bleed{aspect-ratio:16/9}}

/* build-standards §7(a): asymmetric split, 1.35fr 1fr. Symmetric is the
   exception and needs a reason. Capped at TWO on any one page. */
.split{display:grid;gap:var(--s8);align-items:center;grid-template-columns:1.35fr 1fr}
.split--flip{direction:rtl}
.split--flip>*{direction:ltr}
@media(max-width:900px){
  .split{grid-template-columns:1fr;gap:var(--s6)}
  .split--flip{direction:ltr}
}
.split__copy{max-width:var(--measure)}   /* (b) bounded measure */

/* §32: a horizontal scroll strip still widens the mobile LAYOUT VIEWPORT.
   contain:inline-size is the ONLY thing that stops the ICB expanding (8 other
   candidates measured, all no-ops). Kept even on wrapping sets, and costs nothing. */
/* ⚠ ONLY on horizontally-SCROLLING strips. Applying it to the wrapping .chips
   row broke it badly: .chips is a flex ITEM inside .filters__row, and
   contain:inline-size stops it taking its width from the flex container, so
   every chip wrapped to its own line at min-content width and rendered as a
   column of ovals. A rule added to help a component can break the component. */
/* All six covers, always, at every width: 6-up, then 3, then 2. Fractions, so
   there is no constant to out-grow its container and clip the last card. */
.bookrow{display:grid;gap:var(--s4);grid-template-columns:repeat(6,minmax(0,1fr))}
.bookmini{background:transparent;border:0;padding:0;min-width:0}
@media(max-width:1100px){.bookrow{grid-template-columns:repeat(3,minmax(0,1fr));
  row-gap:var(--s5)}}
@media(max-width:560px){.bookrow{grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--s4)}}
.rail{contain:inline-size}
/* A strip that clips its last card with no affordance reads as BROKEN, not as
   scrollable. Users do not discover a horizontal scroll they were given no
   sign of. The mask fade is the sign. */
.rail{display:flex;gap:var(--s4);overflow-x:auto;scroll-snap-type:x mandatory;
  padding-bottom:var(--s3);-webkit-overflow-scrolling:touch;
  -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 56px),transparent 100%);
          mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 56px),transparent 100%)}
.rail>*{scroll-snap-align:start;flex:none}
.rail::-webkit-scrollbar{height:6px}
.rail::-webkit-scrollbar-thumb{background:var(--line);border-radius:99px}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .rail::-webkit-scrollbar-thumb{background:var(--ink-3)}

/* ============================ TYPE ====================================== */
.h-hero{font-size:var(--fs-hero);font-stretch:112%;text-transform:uppercase;
  letter-spacing:-.035em;line-height:.92;
  /* 2026-09-08: the headline became a REPEATING CLAUSE ("If nothing changes,
     nothing changes."), and it only works if the two halves stack, so the
     second reads as an echo of the first.
     ⛔ NOT a hardcoded <br>, and NOT a `ch` measure. Both were tried and
     MEASURED: `ch` in this face reports 0.216em at 44px and 0.097em at 98px,
     so a measure set in it is inert, and all four combinations of
     max-inline-size and text-wrap:balance produced byte-identical line counts.
     The clauses are block spans instead, which is the only construction that
     cannot put half of one clause on a line with half of the other.
     The size cap is measured, not chosen: clause one is 12.658em wide and the
     copy column tops out at 736px, so above 58.1px the clause cannot hold its
     own line. 3.6rem is that ceiling. Above it the hero went to a four-line
     wall of caps that pushed the two CTAs toward the fold, which is the exact
     defect _gate-herofold exists to catch. */
  max-inline-size:none}
.h-hero>span{display:block}
.h1{font-size:var(--fs-4xl);font-stretch:105%}
.h2{font-size:var(--fs-3xl)}
.h3{font-size:var(--fs-xl)}
.h4{font-size:var(--fs-lg)}
.lede{font-size:var(--fs-md);line-height:1.6;max-width:var(--measure);color:var(--muted)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .lede{color:var(--on-ink-dim)}
.prose{max-width:var(--measure)}
.prose p{margin-bottom:1.15em}
.prose>:last-child{margin-bottom:0}

/* eyebrow: a label, in the utility face, never the display face */
.eyebrow{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.16em;text-transform:uppercase;color:var(--citrus-text);
  margin:0 0 var(--s3);display:block}

/* THREE credentials from 2026-09-08, where there were two. At 390 the string
   is 71 characters of tracked-out caps and must wrap; the only question is
   WHERE. Each credential is nowrap and carries its own trailing separator, so
   a wrap can only fall BETWEEN credentials - never inside one, which is the
   compact-label failure ui-ux-pro-max rates High. The separator stays with the
   credential it follows so no line ever opens with a stray mark. */
.eyebrow--cred>span{white-space:nowrap}
.eyebrow--cred>span:not(:last-child)::after{content:" ·"}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .eyebrow{color:var(--citrus)}

.meta{font-family:var(--util);font-size:var(--fs-sm);color:var(--muted);
  font-variant-numeric:tabular-nums}   /* ui-ux-pro-max §6: tabular figures */
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .meta{color:var(--on-ink-dim)}

/* ---------------- THE FLIP: the signature device -----------------------
   Joseph's entire body of work is inversions: Built to Suck · the key to
   success is to suck less · Ban the Employee · Flip the Funnel · retention is
   the new acquisition. So the recurring mark is a struck conventional term
   resolving into his inversion. Used in TWO places only (hero + speaking).
   The boldness is spent once. */
.flip{font-family:var(--display);font-weight:800;text-transform:uppercase;
  font-stretch:108%;letter-spacing:-.02em;line-height:1.06;display:grid;gap:2px}
/* 🔴 NO `opacity` HERE. It used to carry the recession (opacity:.55) and that is
   why the line was illegible: getComputedStyle reports the DECLARED colour, so
   every contrast check ratioed an ink nobody ever saw. Composited, .55 of
   --on-ink-dim over the hero plate measured 2.15:1 @390 and 3.37:1 @1920, the
   second only "passing" because 25.6px clears the 3.0 large-text bar.
   The recession now comes from WEIGHT (600 against the payoff's 800) and from
   the ember strike itself, which is what carries the "cancelled" meaning anyway.
   Both lines stay legible; the contrast between them is rhetorical, not
   perceptual. Applies to the light variant too: the opacity was on the base
   rule, so it was dimming .sec--bone .flip__from as well. */
.flip__from{color:var(--on-ink-dim);font-weight:600;text-decoration:line-through;
  text-decoration-thickness:.09em;text-decoration-color:var(--ember)}
.flip__to{color:var(--citrus)}
.sec--bone .flip__from,.flip--light .flip__from{color:var(--muted)}
.flip--light .flip__to{color:var(--citrus-text)}

/* ============================ BUTTONS =================================== */
/* §34: variant selectors are DOUBLED (0,2,0) so no `.context a{color:…}`
   rule (0,1,1) can ever repaint a CTA's label. Two correct rules combining
   into a defect is the shape this prevents. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  font-family:var(--util);font-weight:700;font-size:var(--fs-sm);
  letter-spacing:.04em;text-transform:uppercase;text-decoration:none;
  padding:15px 26px;min-height:48px;border:2px solid transparent;border-radius:var(--r);
  cursor:pointer;transition:background var(--dur) var(--ease),
    color var(--dur) var(--ease),border-color var(--dur) var(--ease),
    transform var(--dur) var(--ease);
  touch-action:manipulation;
}
.btn:active{transform:translateY(1px)}
/* 🔴 2026-08-08: WAS `color:#fff`, WHICH IS 2.61:1 ON --citrus AND FAILS AA (4.5).
   Every primary CTA on all 13 pages, the site's ONE conversion element, shipped
   below AA, and no gate ever said so. contrast.mjs resolves a node's ground by
   walking ANCESTORS to the first non-transparent background, so on `.phero` it
   walked straight PAST the button's own citrus fill to the dark band behind and
   reported 18.15:1. Craft trap 42: an "effective background" walk is a PROXY, and
   the one element whose ground is its OWN fill is exactly where it breaks.
   Found because a hero-exploration agent measured citrus+white independently and
   chose ink for its own CTA, i.e. by a second opinion, not by the gate.
   ✅ Ink on citrus = 7.20:1, and it is already the site's own pairing (the selected
   timeline node). The ORANGE IS UNCHANGED. Only the label colour moves.
   ⚠ Hover could not stay --ember: ink on #C63C12 is 3.93:1, ALSO a fail. A slightly
   deeper citrus keeps the hover in the same family at 6.01:1 with ink. */
.btn--cta.btn--cta{background:var(--citrus);color:var(--ink);border-color:var(--citrus)}
.btn--cta.btn--cta:hover{background:#E86F00;border-color:#E86F00;color:var(--ink)}
.btn--ink.btn--ink{background:var(--ink);color:var(--white);border-color:var(--ink)}
.btn--ink.btn--ink:hover{background:#000;border-color:#000}
.btn--ghost.btn--ghost{background:transparent;color:var(--type);border-color:var(--line)}
.btn--ghost.btn--ghost:hover{border-color:var(--type)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .btn--ghost.btn--ghost{
  color:var(--white);border-color:rgba(255,255,255,.34)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .btn--ghost.btn--ghost:hover{border-color:#fff;background:rgba(255,255,255,.06)}
.btn--sm{padding:10px 16px;min-height:40px;font-size:var(--fs-xs)}
/* ui-ux-pro-max §8: disabled is semantic + visible, never a dead-looking live control */
.btn[disabled],.btn[aria-disabled="true"]{opacity:.45;cursor:not-allowed}
.btn-row{display:flex;flex-wrap:wrap;gap:var(--s3);align-items:center}

.icon-btn{display:grid;place-items:center;width:48px;height:48px;border:0;
  background:transparent;cursor:pointer;border-radius:var(--r);
  transition:background var(--dur) var(--ease)}
.icon-btn:hover{background:rgba(0,0,0,.06)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .icon-btn:hover{background:rgba(255,255,255,.1)}

/* link with an underline that means something */
.tlink{font-family:var(--util);font-weight:700;font-size:var(--fs-sm);
  text-transform:uppercase;letter-spacing:.06em;text-decoration:none;
  color:var(--citrus-text);border-bottom:2px solid var(--citrus);
  padding-bottom:2px;transition:color var(--dur) var(--ease)}
.tlink:hover{color:var(--ember)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .tlink{color:var(--citrus)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .tlink:hover{color:#fff}

/* ============================ HEADER ==================================== */
/* §4: the mount is display:contents so the sticky header's containing block
   is the body, not a 0-height wrapper. */
#hdr-mount{display:contents}
/* 🔴 CLS FIX: measured 1.0 on /books and /shows, 0.16 on /content, all from a
   single source: `main` jumping 0 -> 77px the moment the header is injected.
   The mount is `display:contents` on purpose (§4 above: the sticky header's
   containing block must be the body, not a 0-height wrapper), so the space
   cannot be reserved on the mount once it is populated.

   `:empty` resolves that exactly. Before JS the div has no children, so it
   matches, becomes a real box, and holds the header's height open. The instant
   the header is injected it stops being empty, this rule stops applying, and
   `display:contents` takes over unchanged. No JS timing, no double-counted
   space, and it self-corrects if the header ever fails to mount at all.
   Height is the same --hd-h token the hero-lab measures at runtime. */
#hdr-mount:empty{display:block;height:var(--hd-h)}

/* Same defect, second instance: `#bookList` is empty in the HTML and JS injects
   all six books into it: 3231px at desktop, 5501px at 390. The citrus band
   directly below it therefore started at y≈789 (on screen) and was shoved to
   y≈4020, which is the whole of /books' 0.1233 CLS.

   ⚠ Reserving the TRUE height is the wrong fix: it would leave a 5500px blank
   column on mobile before JS runs, which is worse than the shift it cures. One
   viewport is the MINIMUM reserve that puts the band off-screen before and
   after, so nothing visible ever moves.

   🔑 This is a patch, not the cure. The cure is pre-rendering the list at build
   time, which also fixes the bigger problem that none of these books exist in
   the HTML for a crawler that does not run JS. See the SEO report. */
#bookList:empty{min-height:100svh}
.hd{position:sticky;top:0;z-index:var(--z-sticky);background:var(--bone);
  border-bottom:1px solid var(--line);
  transition:box-shadow var(--dur) var(--ease),background var(--dur) var(--ease)}
.hd.is-stuck{box-shadow:0 1px 24px rgba(20,17,15,.10)}
.hd__in{display:flex;align-items:center;gap:var(--s5);
  max-width:var(--wrap);margin-inline:auto;padding:14px var(--s5)}
@media(min-width:900px){.hd__in{padding-inline:var(--s7)}}
.hd__logo{display:flex;align-items:baseline;gap:3px;text-decoration:none;flex:none;
  font-family:var(--display);font-weight:800;font-stretch:110%;font-size:1.1rem;
  letter-spacing:-.03em;text-transform:uppercase;color:var(--type)}
.hd__logo i{font-style:normal;color:var(--citrus)}
.hd__nav{display:flex;gap:var(--s5);margin-inline:auto}
.hd__nav a{font-family:var(--util);font-size:var(--fs-sm);font-weight:600;
  text-decoration:none;color:var(--type);padding:6px 0;position:relative;
  transition:color var(--dur) var(--ease)}
.hd__nav a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:var(--citrus);transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur) var(--ease)}
.hd__nav a:hover::after{transform:scaleX(1)}
/* ui-ux-pro-max §9: the current location is always visibly marked */
.hd__nav a[aria-current="page"]{color:var(--citrus-text)}
.hd__nav a[aria-current="page"]::after{transform:scaleX(1)}
.hd__actions{display:flex;align-items:center;gap:var(--s2);flex:none;margin-left:auto}
/* The header CTA's "illuminate on and off". An OUTER ring only: it never
   crosses the label, so the button's own contrast is a constant and cannot be
   changed by a frame of the animation. Long hold, short pulse: this sits in
   the chrome on every page and a fast loop would be a nag, not an accent. */
.hd__actions .btn.is-lit{animation:jjGlow 5.5s var(--ease) infinite}
@keyframes jjGlow{
  0%,64%,100%{box-shadow:0 0 0 0 rgba(255,122,0,0)}
  32%{box-shadow:0 0 0 6px rgba(255,122,0,.26)}
}
@media(prefers-reduced-motion:reduce){
  .hd__actions .btn.is-lit{animation:none}
}

/* ============================ DRAWER ==================================== */
.scrim{position:fixed;inset:0;background:rgba(20,17,15,.55);z-index:var(--z-scrim);
  opacity:0;visibility:hidden;transition:opacity var(--dur) var(--ease),
    visibility 0s linear var(--dur)}
.scrim.open{opacity:1;visibility:visible;transition-delay:0s}
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(420px,88vw);
  background:var(--ink);color:var(--on-ink);z-index:var(--z-drawer);
  transform:translateX(100%);transition:transform 260ms var(--ease);
  display:flex;flex-direction:column;overflow-y:auto}
.drawer.open{transform:none}
.drawer__top{display:flex;align-items:center;justify-content:space-between;
  padding:14px var(--s5);border-bottom:1px solid var(--ink-3)}
.drawer__body{padding:var(--s5);display:grid;gap:var(--s1)}
/* §34: this context rule would otherwise repaint any .btn inside the drawer */
.drawer__body a:not(.btn){display:block;padding:14px 0;text-decoration:none;
  font-family:var(--display);font-weight:700;font-size:var(--fs-lg);
  text-transform:uppercase;font-stretch:105%;color:var(--white);
  border-bottom:1px solid var(--ink-3)}
.drawer__body a:not(.btn):hover{color:var(--citrus)}
.drawer__cta{padding:var(--s5);display:grid;gap:var(--s3);margin-top:auto}

/* ============================ CARDS ===================================== */
.card{background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;display:flex;flex-direction:column;min-width:0;
  transition:transform var(--dur) var(--ease),box-shadow var(--dur) var(--ease),
    border-color var(--dur) var(--ease)}
a.card{text-decoration:none;color:inherit}
a.card:hover{transform:translateY(-3px);box-shadow:0 12px 32px rgba(20,17,15,.10);
  border-color:var(--citrus)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .card{background:var(--ink-2);border-color:var(--ink-3);color:var(--on-ink)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) a.card:hover{box-shadow:0 12px 32px rgba(0,0,0,.5)}
.card__body{padding:var(--s5);display:flex;flex-direction:column;gap:var(--s2);flex:1}
.card__t{font-family:var(--display);font-weight:700;font-size:var(--fs-md);
  line-height:1.2;letter-spacing:-.015em}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .card__t{color:var(--white)}

/* §29: a `1fr` implicit row track GROWS to fit, so max-height:100% on a grid
   child resolves against itself. Name the track and clear the auto minimum.
   This class has shipped four times in four components; it is generic here. */
.thumb{position:relative;aspect-ratio:16/9;overflow:hidden;background:var(--ink-2);
  display:grid;grid-template-columns:1fr;grid-template-rows:1fr}
.thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.thumb--sq{aspect-ratio:1/1}
.thumb--4x5{aspect-ratio:4/5}
.thumb--book{aspect-ratio:2/3;background:transparent;place-items:end center;padding:0}
.thumb--book img{position:static;width:auto;height:100%;max-width:100%;object-fit:contain;
  filter:drop-shadow(0 14px 28px rgba(20,17,15,.28))}
/* a duration/type pill sitting on a thumbnail */
.thumb__tag{position:absolute;right:8px;bottom:8px;z-index:2;
  background:rgba(20,17,15,.86);color:#fff;font-family:var(--util);
  font-size:var(--fs-xs);font-weight:700;padding:4px 8px;border-radius:3px;
  font-variant-numeric:tabular-nums;letter-spacing:.04em}
.thumb__play{position:absolute;inset:0;z-index:1;display:grid;place-items:center;
  background:linear-gradient(180deg,rgba(20,17,15,0) 40%,rgba(20,17,15,.55) 100%);
  transition:background var(--dur) var(--ease)}
.thumb__play span{width:56px;height:56px;border-radius:50%;background:var(--citrus);
  display:grid;place-items:center;transition:transform var(--dur) var(--ease)}
.thumb__play svg{width:20px;height:20px;color:#fff;margin-left:3px}
a.card:hover .thumb__play span{transform:scale(1.09)}

/* ====================== COVER (.cvr): the archive card head =============
   Replaces `.artcap`, which was a 74px orange gradient strip with a quote
   glyph in it. Fourteen articles all drew the SAME strip, so a third of the
   archive read as unfinished. Allen, on the live URL: "we need covers designed
   here across all of it, you've got too many blanks."

   Two variants, one system, a magazine standfirst block:
     .cvr            typographic cover (articles, and any video with no still)
     .cvr--split     the same block with a REAL vertical still pinned beside it
   The line is the piece's own summary, NOT its title. The title is already
   the `h3` directly below, and printing it twice reads as a bug.

   ⛔ FLAT, on the ground itself. Craft trap 37: when the photograph a slot
   wants is missing, the rule's INGREDIENT is missing, so do not substitute a
   chromed panel into it. No border, no radius, no inner card, no nesting.
   ⚠ Named `.cvr`, not `.cover`: `.cover img` already exists (line 90) and
   forces width:auto/height:auto, which would collapse the pinned still.       */
.cvr{position:relative;aspect-ratio:16/9;overflow:hidden;display:grid;
  grid-template-columns:1fr;background:var(--bone-2);color:var(--type)}
/* 31.64%, not `auto`. A 9:16 still inside a 16:9 box occupies (9/16)x(9/16) =
   81/256 of the box WIDTH. Sizing the column `auto` let the file's intrinsic
   405px drive it instead: wider than the whole 380px card, so the still went
   full-bleed and the type was squeezed to nothing. A ratio holds at every
   card width; an intrinsic pixel size does not. */
.cvr--split{grid-template-columns:31.64% 1fr}
.cvr__still{width:100%;height:100%;object-fit:cover;object-position:50% 30%;
  background:var(--ink-2)}
.cvr__body{min-width:0;display:flex;flex-direction:column;justify-content:space-between;
  gap:var(--s3);padding:var(--s4) var(--s5) var(--s4) var(--s4)}
.cvr--split .cvr__body{padding-left:var(--s4)}
.cvr__eye{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.12em;text-transform:uppercase;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Newsreader here, against the Archivo of the card body below it: the cover
   is the editorial voice, the body is the index entry. */
/* Set LARGE on purpose. At --fs-base the line used about a quarter of the box
   and the cover read as a mostly-empty panel, the same "blank" complaint in a
   new shape. A cap does not hide a content shortfall (craft trap 69): the box
   is fixed at 16:9, so the type has to grow to fill it. */
.cvr__line{font-family:var(--body);font-size:var(--fs-lg);line-height:1.24;
  font-weight:500;margin:0;letter-spacing:-.005em;
  display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:4;line-clamp:4;overflow:hidden}
/* the split body is only ~68% of the card, so it takes a step down the scale */
.cvr--split .cvr__line{font-size:var(--fs-base);line-height:1.3;
  -webkit-line-clamp:5;line-clamp:5}
.cvr__rule{width:44px;height:3px;background:var(--ember);flex:none;
  transform-origin:left center;transition:transform var(--dur) var(--ease)}
.card:hover .cvr__rule,.card:focus-visible .cvr__rule{transform:scaleX(1.9)}

/* Grounds rotate deterministically by item id. See coverGround() in pages.js.
   Light-weighted on purpose: content.html is a bone page and the tint gate
   caps tinted ground at 52.7% of it. */
.cvr--ink{background:var(--ink);color:var(--on-ink)}
.cvr--ink2{background:var(--ink-2);color:var(--on-ink)}
.cvr--ink .cvr__eye,.cvr--ink2 .cvr__eye{color:var(--on-ink-dim)}
.cvr--ink .cvr__rule,.cvr--ink2 .cvr__rule{background:var(--citrus)}

/* the play affordance sits ON the still, so a split cover still reads as video */
.cvr__play{position:absolute;left:var(--s3);bottom:var(--s3);z-index:2;
  width:34px;height:34px;border-radius:50%;background:var(--citrus);
  display:grid;place-items:center;transition:transform var(--dur) var(--ease)}
.cvr__play svg{width:14px;height:14px;color:#fff;margin-left:2px}
.card:hover .cvr__play{transform:scale(1.08)}
@media (max-width:520px){
  .cvr__body{padding:var(--s3) var(--s4)}
  .cvr__line{-webkit-line-clamp:3;line-clamp:3}
}

.tag{display:inline-block;font-family:var(--util);font-size:var(--fs-xs);
  font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  padding:4px 9px;border-radius:3px;background:var(--bone-2);color:var(--muted)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .tag{background:var(--ink-3);color:var(--on-ink-dim)}
.tag--citrus{background:var(--citrus);color:#fff}

/* ============================ HERO ====================================== */
/* §7: the hero is a MEASURED band with the media absolutely positioned inside
   it, so the image's intrinsic height can never drive the row. */
/* ==== HERO LAB: REVIEW-ONLY SCAFFOLDING, REMOVE BEFORE LAUNCH ============
   Paired with js/hero-lab.js + the _hero-lab/ folder + its <script> tag in
   index.html. Strip all four together with the password gate and the noindex.
   Bottom-LEFT was the instruction and it held only while the hero OVERFLOWED
   the fold. Once the fold fix landed and the hero FIT, its credential row rose
   into the toggle's band and "6 books" was covered at 1440, 1280 and 390,
   the 4th time a fixed element on this build has painted over content, and
   this time the overlay was the thing we intend to demo.
   No bottom-corner position can fix it: `.hero__cred` starts at x=48 at 1280
   and x=24 at 390, so there is no left gutter to sit in, and the chat FAB owns
   bottom-right. TOP-left is free by construction: the hero's content is
   `align-items:flex-end`, so the top of the band is the one place nothing is
   ever laid out. Proven at 9 viewports by _build/_gate-hlab-overlap.mjs, which
   hit-tests the VICTIM classes rather than the overlay. */
.hlab-off{display:none !important}

/* ── 2026-08-08 (late night 3): OUT OF THE OVERLAY, INTO THE HEADER ───────
   Allen: "maybe have it in the header on the top right, 'cause it's now over
   the logo … or subtly in the header on the far left of the top logo, we can
   just have A B C D to toggle."

   He was right and the overlap gate could not see it. TOP-left put the chip at
   16,93 while the logo sits at 48,23 (24,23 at 390), and the rects do NOT
   intersect, so `_gate-hlab-overlap.mjs` was honestly green. The defect is
   ADJACENCY: a citrus-ringed pill 41px under the wordmark, in the same gutter
   and near enough the same width, reads as part of the brand lockup.
   🔑 A collision gate tests INTERSECTION; "reads as one thing" is a different
   measurement and no hit-test performs it.

   In the header it is in FLOW, so it cannot paint over anything, ever. The
   whole class of defect (4 occurrences on this build) is designed out rather
   than re-tested.

   TOP-LEFT, NOT TOP-RIGHT: the one deliberate departure from the first option
   Allen offered. Top-right is `.hd__actions`, which holds "Book a call": the
   site's ONLY conversion element, the one that shipped as a 1.1:1 ghost on 13
   pages from a scoping bug and failed AA site-wide the same week. A review
   instrument does not go there (ui-ux-pro-max §4 `primary-action`: one primary
   CTA, everything else visually subordinate).

   ── WHY THE STRIP CHANGES SHAPE TWICE, measured not guessed ──────────────
   `_probe-hdspace.mjs`, free space in the header row (viewport − logo − nav −
   actions − padding):
       ≥1280   394px   nav shown, roomy
        1180   294px
        1024   138px   ← nav at its tightest: the 5-chip set CANNOT fit
      ≤1023   462-546px  nav hidden, roomiest of all
        620    314px
        560    254px   ← too tight again
        390     84px
   So the set shows in two bands and collapses to one chip in between and at
   the bottom. That is not indecision: the thing stealing the room is the SITE'S
   OWN NAV appearing and disappearing, and the strip yields to it every time.

   ⚠ It DOES shift the logo right by its own width, and the nav (margin-inline
   :auto) re-centres in the leftover. That is a real change to the header being
   reviewed, and accepted because the control is stripped before launch and the
   alternative was an overlay on the artwork. Header HEIGHT is unchanged (44px
   chips inside the existing 48px content band) so `--hd-h` and every hero-fold
   number computed from it are untouched, asserted by _gate-hlab-header.mjs. */
/* ── 2026-08-09: IT FOLDS TO ONE NOTCH ────────────────────────────────────
   Allen: "where you have Hero you have hide … like a plus versus a minus, that
   way we can show the design without it."
   At rest the instrument is a single 40x44 square and nothing else: no word, no
   strip, no dashed rule. The dashed separator and the padding that carries it
   are what mark this as foreign to the header, so they arrive WITH the strip
   and leave with it, in the same two media blocks that reveal it. A dashed rule
   fencing off a lone 40px notch would be the furniture this change removes.
   The `Hero` label is gone rather than moved: its job was to name the
   instrument, and `aria-label` + `title` + the dropdown's own heading already
   do that without spending header width on it. */
.hlab{position:relative;display:flex;align-items:center;gap:var(--s2);flex:none;
  font-family:var(--util)}
/* The disclosure AND the current-state indicator, in one element. They were
   two (`__mini` + the strip's active chip) and could disagree. */
.hlab__t{display:inline-flex;align-items:center;justify-content:center;
  min-width:40px;height:44px;padding:0 6px;cursor:pointer;
  background:none;border:1px solid var(--line);color:var(--muted);
  font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.04em;font-variant-numeric:tabular-nums;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease),
             border-color var(--dur) var(--ease)}
.hlab__t:hover{background:var(--bone-2);color:var(--type)}
/* ⛔ DRAWN, NOT TYPED. "+" and "−" are two glyphs at two optical weights, and
   in most faces the minus is not the length of the plus's own bar, so the
   fold would change width and colour-weight as well as state. Two 1px rules on
   one 13px box make the minus literally the plus with a stroke retracted.
   13 and not 12: an odd box has an integer centre, and a 1px rule landing on a
   half-pixel renders as a soft 2px rule on every non-retina screen this gets
   presented on. */
.hlab__glyph{position:relative;display:block;width:13px;height:13px}
.hlab__glyph::before,.hlab__glyph::after{content:"";position:absolute;
  background:currentColor}
.hlab__glyph::before{left:0;right:0;top:6px;height:1px}
.hlab__glyph::after{top:0;bottom:0;left:6px;width:1px;transform:scaleY(1);
  transition:transform var(--dur) var(--ease)}
.hlab.is-open .hlab__glyph::after{transform:scaleY(0)}
.hlab__cur{display:none}
/* 🔑 FOLDED OVER A SUBSTITUTED HERO, THE NOTCH MUST NOT READ AS "NOTHING IS ON".
   A neutral "+" there would present someone else's exploration as the live
   design with nothing on screen saying so. So: "+" only while the real hero is
   up; the variant's letter, on the ink ground, the moment it is not.
   While OPEN the strip already says which is active, so the notch goes back to
   being a plain minus: one job at a time. */
.hlab.has-v:not(.is-open) .hlab__glyph{display:none}
.hlab.has-v:not(.is-open) .hlab__cur{display:block}
.hlab.has-v:not(.is-open) .hlab__t{background:var(--ink);color:var(--on-ink);
  border-color:var(--ink);box-shadow:inset 0 -2px 0 var(--citrus)}
.hlab__set{display:none;align-items:center}
/* Square, hairlined, sharing one border: a gauge, not a pill. The pill and the
   citrus FILL are the CTA's language and are not borrowed here. */
.hlab__c{display:inline-flex;align-items:center;justify-content:center;
  min-width:40px;height:44px;padding:0 4px;cursor:pointer;
  background:none;border:1px solid var(--line);color:var(--muted);
  font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.04em;font-variant-numeric:tabular-nums;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease)}
.hlab__c+.hlab__c{margin-left:-1px}
.hlab__c:hover{background:var(--bone-2);color:var(--type)}
/* Active = ink ground + the citrus as a 2px RULE under the letter. Never white
   on citrus: that pair is 2.61:1 and is exactly what failed site-wide on the
   CTA. --on-ink on --ink is the pair used everywhere else on this build. */
.hlab__c[aria-checked="true"]{background:var(--ink);color:var(--on-ink);
  border-color:var(--ink);box-shadow:inset 0 -2px 0 var(--citrus)}
/* 🔴 THERE IS NO STAGGERED ENTRANCE ON THE CHIPS, AND THE REASON IS WORTH THE
   LINES. It was written (`animation:hlabIn 180ms backwards` with 25ms delays)
   and it stranded every chip at `opacity:0`. The chips go from `display:none`
   to `display:flex` in the SAME style recalculation that hands them the
   animation, the animation never gets a start time, and `backwards` holds them
   at the `from` keyframe: 0% opacity, forever.
   🔑 AND EVERY GATE STAYED GREEN. `_gate-hlab-header.mjs` scored the strip
   `strip(5)` with all five chips at 40x44 and the box 261px wide, because its
   `vis()` is `display !== 'none' && width > 0`, so an opacity-0 element passes
   both. The screenshot is what caught it. A metric that tests DISPLAY cannot
   see OPACITY, and the strip was measured, sized, contrast-checked and
   photographed as present while being invisible on screen.
   The gate now reads computed opacity too. The animation is simply gone: the
   glyph's plus→minus already carries the cause-effect (§7 motion-meaning), a
   second entrance on five 40px squares was decoration, and decoration is not
   worth a state in which the control silently does not appear. */
@media(prefers-reduced-motion:reduce){
  .hlab__glyph::after{transition:none}
}
.hlab__list{display:none;position:absolute;top:calc(100% + 10px);left:0;
  background:var(--ink);border:1px solid var(--ink-3);
  padding:8px;min-width:250px;max-width:min(300px,calc(100vw - 32px));
  box-shadow:0 18px 44px rgba(0,0,0,.42)}
/* ⛔ NOT an unqualified `.hlab.is-open .hlab__list{display:block}`. Both views
   are now behind ONE class, so a global open-rule would show the dropdown on
   top of the inline strip at every wide width: two radiogroups for one state,
   which is precisely what the gate's set-XOR-list rule exists to catch. Which
   view opens is decided per band, below, and nowhere else. */
.hlab__hd{margin:2px 6px 8px;font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:#B9AFA4}                      /* 6.78:1 on --ink, measured not assumed */
.hlab__o{display:block;width:100%;text-align:left;cursor:pointer;background:none;
  border:0;border-left:2px solid transparent;padding:9px 12px;color:#F2EDE6;
  min-height:44px}
.hlab__o b{display:block;font-size:var(--fs-sm);font-weight:700}
.hlab__o em{display:block;font-style:normal;font-size:11px;color:#B9AFA4;margin-top:1px}
.hlab__o:hover{background:var(--ink-3)}
.hlab__o.is-on{border-left-color:var(--citrus);background:var(--ink-3)}
.hlab__o.is-on b{color:var(--citrus)}
/* 🔴 NOT `100vh`. The frame sits BELOW the in-flow sticky `.hd`, so a full
   screen of height overflows the screen by exactly the header's height at every
   viewport size. JS overwrites this with the MEASURED available space on load;
   this value only has to be right before that runs, so the header height is
   carried as a token rather than repeated as a literal. */
.hlab__frame{display:block;width:100%;border:0;background:var(--ink);
  height:calc(100dvh - var(--hd-h))}
/* ⚠ THE BANDS BELOW ARE THE SITE'S BREAKPOINT, NOT ONE OF MINE. The first cut
   guessed the nav vanished at 1024; it is `hide-mob`, which is **900px**, and
   the CTA goes with it. Guessing put the five chips at 1023 where the nav is
   still laid out, and the header overflowed by 107px.
   🔑 A control dropped into someone else's component inherits THAT component's
   breakpoints; inventing your own puts a boundary where nothing changes.

   Room for the five chips exists only where the nav is gone (621-900) or where
   the row is genuinely wide (≥1280). Between 901 and 1279 the nav is present
   and 901 is already over-subscribed WITHOUT this control, so the strip shows
   its single chip and drops the label. */
@media(min-width:621px) and (max-width:900px){
  .hlab.is-open .hlab__set{display:flex}
  .hlab.is-open{padding-right:var(--s4);border-right:1px dashed var(--line)}
}
/* 🔴 901-1023 THE CONTROL IS NOT RENDERED AT ALL, and that is the site's
   constraint rather than a choice. Measured free space in the header row with
   the control REMOVED (`/tmp/jjband.mjs`, reproduced in the note):
       901-940   slack 0   · the header is already 87px, i.e. the nav has
                             ALREADY wrapped without any of this. Pre-existing.
       950       16px      975  41px      1000  66px
       1010      76px      1024  90px  ← first width that holds the 75px chip
   A review instrument does not get to be the thing that tips a header that is
   already at zero. Below 1024 with the nav present, it stands down.
   ⚠ The 901-940 wrap is a REAL SITE DEFECT found by this sweep and is logged
   separately: it is not caused by, and is not fixed by, this control. */
@media(min-width:901px) and (max-width:1023px){
  .hlab{display:none}
}
/* 1024-1279 the row has 138-394px of slack and the open strip needs ~244, so
   at the bottom of that band it would overflow. It opens as the dropdown here
   instead. The boundary is 1280 because that is `--wrap` and the site's own
   breakpoint. The first cut of this control invented boundaries and put one
   at 1024 where nothing changes. */
@media(min-width:1024px) and (max-width:1279px){
  .hlab.is-open .hlab__list{display:block}
}
@media(min-width:1280px){
  .hlab.is-open .hlab__set{display:flex}
  .hlab.is-open{padding-right:var(--s4);border-right:1px dashed var(--line)}
}
/* ≤620 the control MOVES. It is not just restyled. Measured true slack in the
   header row (free space MINUS the row's own 24px gap): 60px at 390 and 30px at
   360, and a third flex child costs its width PLUS another 24px gap. So even a
   36px chip cannot sit left of the logo at 360.
   The right of the header can take it, and only at these widths: `.btn--cta` is
   `hide-mob`, so below 620 the top-right holds nothing but the search and menu
   icons: utilities. The instrument belongs among utilities; it must never sit
   beside the CTA, which is why it does NOT do this on desktop.
   JS re-parents the SAME element (see hero-lab.js): one control, one state. */
/* ⚠ DISPLAY IS THE MEDIA QUERY'S CALL, PLACEMENT IS THE CLASS'S. `.hlab--act`
   is applied by JS off its own matchMedia; if the two ever disagreed about 620
   and BOTH decided visibility, the disagreement would show as no control at all
   at one width and nothing would notice. CSS owns which view opens; the class
   only says which end of the header it is sitting at. */
@media(max-width:620px){
  .hlab.is-open .hlab__list{display:block}
}
.hlab--act .hlab__list{left:auto;right:0}
@media print{.hlab{display:none}}
/* ==== end hero lab ======================================================= */

.hero{position:relative;background:var(--ink);color:var(--on-ink);overflow:hidden;
  min-height:min(88vh,760px);display:flex;align-items:flex-end}
.hero__bg{position:absolute;inset:0;z-index:0}
.hero__bg img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:65% 45%}
/* build-standards §7: a DIRECTIONAL wash, never flat opacity. Tuned to the
   plate and re-measured on rendered pixels after any ratio/crop change
   (craft trap 31: a scrim is tuned to a CROP). */
.hero__veil{position:absolute;inset:0;z-index:1;
  background:linear-gradient(96deg,rgba(12,10,9,.95) 0%,rgba(12,10,9,.88) 38%,
    rgba(12,10,9,.45) 66%,rgba(12,10,9,.30) 100%)}
/* 🔴 The width MUST be definite. `width:auto` here is shrink-to-fit around the
   image, so before the image decodes the box is 0px wide and the figure simply
   is not painted: an intermittent, decode-timing-dependent disappearance that
   looked exactly like a missing asset (measured: 0x1225 at vh=900, 875x1225
   everywhere else). `aspect-ratio` derives the width from the height without
   consulting the image at all. The <img> also carries width/height attributes
   so the ratio is known before the first byte arrives. */
.hero__figure{position:absolute;right:0;bottom:0;z-index:2;height:96%;
  aspect-ratio:1200/1679;pointer-events:none}
.hero__figure img{height:100%;width:100%;object-fit:contain;object-position:bottom right;
  filter:drop-shadow(-24px 0 60px rgba(0,0,0,.55))}
.hero__in{position:relative;z-index:3;width:100%;max-width:var(--wrap);
  margin-inline:auto;
  /* 2026-09-08: the TOP padding gained a viewport-HEIGHT term, for the same
     reason --fs-hero got one on 2026-08-08: the viewports that lose the CTA row
     are SHORT, not narrow, and a fixed 128px top pad is a sixth of a 768px
     screen. ⛔ The BOTTOM pad is deliberately NOT height-scaled. `.hero` is
     `align-items:flex-end`, so the copy hangs off the BOTTOM of the band:
     shrinking the bottom pad moves the CTA row DOWN, not up. Measured, after
     getting it backwards once - 5svh cost every desktop viewport 24px. */
  padding:min(var(--s10),9svh) var(--s5) var(--s8)}
@media(min-width:900px){.hero__in{padding-inline:var(--s7)}}
.hero__copy{max-width:min(46rem,66%)}
.hero h1{color:#fff;margin-bottom:var(--s5)}
.hero__flip{margin:var(--s5) 0 var(--s6);font-size:clamp(1.05rem,2vw,1.6rem)}
.hero__cta{display:flex;flex-wrap:wrap;gap:var(--s3)}
/* One line, above the fold, answering the brief's Journey 5 now that the full
   "still making things" module has moved low as credibility. Deliberately a
   line and not a band: a band in this slot is what pushed the two conversion
   doors below the fold in the first place. */
.hero__now{display:flex;align-items:center;margin:var(--s5) 0 0;
  font-family:var(--util);font-size:var(--fs-sm);color:var(--on-ink-dim)}
.hero__cred{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5);margin-top:var(--s7);
  padding-top:var(--s5);border-top:1px solid rgba(255,255,255,.16);
  font-family:var(--util);font-size:var(--fs-sm);color:var(--on-ink-dim);
  font-variant-numeric:tabular-nums}
.hero__cred b{color:var(--white);font-weight:700}
@media(max-width:1000px){
  .hero{min-height:auto;align-items:stretch}
  .hero__copy{max-width:none}
  /* the gaps, not the type: at 360 the eyebrow costs three lines and the
     headline four, and it is the rhythm between them that pushed the CTA row
     off the screen. Each value below is the next step DOWN the existing
     spacing scale, so the hero stays on the same system. */
  /* 🔴 On a phone the new headline is FOUR lines where the old one was two, and
     no amount of gap-trimming buys that back: at 360x780 the CTA row finished
     97px below the fold with every gap already at its next step down. So the
     order changes rather than the content. `.hero__arch` restates in words what
     the two buttons directly beneath it already say ("The Session Room", "The
     Stage"), and it is the one element in the hero that can follow the action
     instead of preceding it. Nothing is hidden and nothing is cut - on a narrow
     screen the reader reaches the two doors first and the framing line after. */
  .hero__copy{display:flex;flex-direction:column}
  .hero__arch{order:2}
  .hero__cta{order:1}
  .hero__now{order:3}

  .hero .eyebrow{margin-bottom:var(--s2)}
  .hero h1{margin-bottom:var(--s4)}
  .hero__lede{margin-top:var(--s4)}
  .hero__arch{margin:var(--s4) 0 var(--s5);padding-top:var(--s3)}
  .hero__figure{height:52%;opacity:.5}
  .hero__veil{background:linear-gradient(180deg,rgba(12,10,9,.86) 0%,
    rgba(12,10,9,.90) 55%,rgba(12,10,9,.97) 100%)}
  .hero__in{padding-block:var(--s8) var(--s7)}

  /* 🔴 THE SCRIM SITS UNDER THE SUBJECT AND SO CANNOT GRADE IT.
     .hero__veil is z-1 but .hero__figure is z-2, so the portrait paints OVER
     the wash. At ≤1000px .hero__copy went full-width and the type landed
     directly on the un-graded cutout. Measured ground rgb(121,92,79) under the
     wrapped credential, i.e. skin and hair, not ink. That single layering fact
     caused BOTH open contrast failures; they were never two colour bugs.

     A local wash above the figure DID fix the contrast (8.4:1) and was wrong:
     it dropped him to a ghost behind his own headline. Legible and absent is
     not better than present and illegible: his face IS the hero here.

     So mobile stops overlaying altogether. The hero becomes a STACK: copy on
     clean ink, then the portrait in flow below it at FULL opacity, in its own
     band. Nothing overlaps, so no scrim is negotiating between them, and he
     reads at full presence instead of 50% behind type. */
  .hero{flex-direction:column;align-items:stretch}
  .hero__in{order:1}
  .hero__figure{order:2;position:relative;inset:auto;z-index:2;
    height:clamp(240px,42vw + 130px,340px);width:100%;aspect-ratio:auto;
    opacity:1;margin-top:calc(var(--s6) * -1)}
  .hero__figure img{object-fit:contain;object-position:bottom center;
    filter:drop-shadow(0 -18px 50px rgba(12,10,9,.55))}
}

/* ---- the LIVE strip: proof he is creating today (the brief's key journey) */
.live{background:var(--ink);color:var(--on-ink);
  border-top:1px solid var(--ink-3);padding-block:var(--s7)}
.live__hd{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s3) var(--s5);
  margin-bottom:var(--s5)}
/* The pulse is a CLAIM: "this is live, this updates." It earns its keep on the
   two lines whose content really is timestamped: the homepage hero's "last
   piece <date>" and the "Still making things" band, both driven by real data.
   It was ALSO reused on coaching's static line ("Professional EOS Implementer ·
   Westport CT, Hartford CT, New York NY & virtual"), which never changes, so
   it read as a stuck loading spinner on a credential. Same mark, same colour,
   no animation: `.now__dot` keeps the bullet and drops the false claim.
   🔑 an ambient animation is a statement about the CONTENT it sits next to. */
.live__dot,.now__dot{width:8px;height:8px;border-radius:50%;background:var(--citrus);
  display:inline-block;margin-right:8px}
.live__dot{animation:pulse 2.4s var(--ease) infinite}
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.78)}}
@media(prefers-reduced-motion:reduce){.live__dot{animation:none}}

/* ============ PAGE HERO BANNER ==========================================
   Interior pages opened on a text block with a small image beside it, which
   gave the most important page on the site less presence than a blog post.
   This is a MEASURED band (build-standards §7) with the plate absolutely
   positioned inside it, so the image's intrinsic height can never drive the
   row, and a directional wash (never flat opacity) carries the type.
   The generated plate is a SETTING with generic people in it. Joseph's own
   face is never generated; where he appears it is his real photograph. */
.phero{position:relative;overflow:hidden;background:var(--ink);color:var(--on-ink);
  min-height:clamp(380px,46vw,560px);display:flex;align-items:flex-end}
.phero__bg{position:absolute;inset:0;z-index:0}
.phero__bg img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:62% 50%}
.phero__veil{position:absolute;inset:0;z-index:1;
  background:linear-gradient(96deg,rgba(12,10,9,.95) 0%,rgba(12,10,9,.88) 34%,
    rgba(12,10,9,.55) 62%,rgba(12,10,9,.34) 100%)}
.phero__in{position:relative;z-index:2;width:100%;max-width:var(--wrap);
  margin-inline:auto;padding:var(--s9) var(--s5) var(--s8)}
@media(min-width:900px){.phero__in{padding-inline:var(--s7)}}
.phero__copy{max-width:min(44rem,58%)}
/* Not `.h1`'s display size: at --fs-4xl this headline ran FIVE lines and
   pushed its own CTAs out of the banner entirely. A page hero is a band, not
   the home hero. */
.phero h1{color:#fff;margin:0 0 16px;font-size:clamp(2.3rem,4.6vw,4rem)}
/* ⚠ `sup` defaults to `font-size:smaller`, which on a 4rem headline is still
   ~3rem: the ® on "powered by EOS®" rendered nearly as tall as the capitals.
   A legal mark should be legible and small, never a design element. */
sup,sub{font-size:.46em;line-height:0;font-weight:600}
sup{vertical-align:super}
.meta sup,.tag sup{font-size:.72em}
.phero .lede{color:var(--on-ink-dim);margin-bottom:var(--s6)}
@media(max-width:900px){
  .phero{min-height:auto}
  .phero__copy{max-width:none}
  .phero__veil{background:linear-gradient(180deg,rgba(12,10,9,.80) 0%,
    rgba(12,10,9,.88) 52%,rgba(12,10,9,.96) 100%)}
}

/* ============ THE JOURNEY: vertical tabs that become an accordion =======
   Six sequential EOS sessions. A HORIZONTAL tab strip would need six labels
   in a row and would either clip or scroll at 390, the exact defect that
   the content rail was just rebuilt to avoid. So the tablist runs VERTICALLY
/* ---- SECTION HEADER -----------------------------------------------------
   Six section intros were authored as `max-width:36rem` blocks. At >=1440 that
   parks the heading in the left half and leaves the entire right half empty,
   the "too wide, too much white space" Allen called out on the EOS process, and
   it was in five other places too. The lede moves into a second column and sits
   on the heading's baseline: the width gets used, the lede gets a real measure
   (ui-ux-pro-max line-length-control: 60-75ch desktop), and the page gains a
   section archetype it did not have (design-presence archetypes=3 of 8).
   Two of the six carry no lede, so they stay single-column: nothing to move. */
.sechd{margin-bottom:var(--s7);max-width:36rem}
.sechd .h1{margin-bottom:16px}
.sechd .lede{margin:0}
@media(min-width:1100px){
  .sechd--split{max-width:none;display:grid;
    grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
    column-gap:var(--s8);row-gap:var(--s4);align-items:end}
  .sechd--split .eyebrow{grid-column:1;grid-row:1}
  .sechd--split .h1{grid-column:1;grid-row:2;margin-bottom:0}
  .sechd--split .lede{grid-column:2;grid-row:2;max-width:46ch;padding-bottom:.4em}
}

/* ---- THE JOURNEY --------------------------------------------------------
   on desktop (which also reads as the sequence it is) and collapses to a
   stacked accordion below 860, where each header is its own tab button.
   WAI-ARIA tabs pattern: roving tabindex, arrow/Home/End keys, aria-selected. */
/* 2026-08-08: the tablist became a real TIMELINE. Full rationale sits with the
   renderer in pages.js; the geometry note that matters here: the node row is a
   FIXED 34px, so the connector is at a known 17px and a label wrapping to two
   lines cannot drag the line off the dots. The fill is a scaleX TRANSFORM of a
   fraction, not a computed width, so it stays correct at every breakpoint. */
.jrn{display:block}
.tl__track{position:relative;display:grid;
  grid-template-columns:repeat(var(--tln,6),minmax(0,1fr));
  gap:var(--s3);margin-bottom:var(--s7)}
/* ⚠ FIRST ATTEMPT WAS ONE ABSOLUTE LINE ACROSS THE TRACK, spanning half a
   column in from each end. That assumed the nodes were CENTRED in their
   columns; they are `justify-items:start`, so every node sits at its column's
   left edge and the line rendered ~100px right of node 01, visibly detached.
   Drawing the segment per-step instead makes it gap-agnostic and removes the
   arithmetic entirely: each step draws the run to its NEIGHBOUR, the last one
   draws nothing, and "progress" is just which segments are citrus. */
.tl__step::after{content:'';position:absolute;left:34px;top:16px;height:2px;
  right:calc(-1 * var(--s3));background:var(--line);
  transition:background var(--dur) var(--ease)}
.tl__step:last-child::after{display:none}
.tl__step.is-done::after{background:var(--citrus)}
.tl__step{appearance:none;background:none;border:0;padding:0 0 2px;
  cursor:pointer;text-align:left;position:relative;z-index:1;
  display:grid;grid-template-rows:34px auto auto;gap:10px 0;
  align-content:start;justify-items:start;
  font-family:var(--util);color:var(--muted)}
.tl__node{width:34px;height:34px;display:grid;place-items:center;
  background:var(--bone);border:2px solid var(--ink-3);
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease)}
.tl__n{font-variant-numeric:tabular-nums;font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.06em;color:var(--citrus-text)}
.tl__t{font-size:var(--fs-sm);font-weight:600;line-height:1.25}
.tl__when{font-size:var(--fs-xs);font-weight:500;color:var(--muted)}
.tl__step:hover .tl__node{border-color:var(--citrus)}
.tl__step:hover .tl__t{color:var(--type)}
.tl__step[aria-selected="true"] .tl__node{background:var(--citrus);border-color:var(--citrus)}
.tl__step[aria-selected="true"] .tl__n{color:var(--ink)}
.tl__step[aria-selected="true"] .tl__t{color:var(--type);font-weight:700}
/* the objectives: the panel's spine, and the answer to "what is this session
   FOR". Marks come from the icon system, injected, never pasted. */
.tl__objh{font-family:var(--util);font-size:var(--fs-xs);letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);font-weight:700;
  margin:var(--s5) 0 var(--s4)}
.tl__obj{list-style:none;margin:0;padding:0;display:grid;gap:12px var(--s6)}
@media(min-width:900px){.tl__obj{grid-template-columns:repeat(3,minmax(0,1fr))}}
.tl__obj li{display:grid;grid-template-columns:auto 1fr;gap:var(--s3);align-items:start}
.tl__mk{display:block;color:var(--ember);margin-top:2px}
.tl__mk .jj-i{width:20px;height:20px}
.tl__cta{margin-top:var(--s6);display:flex;flex-wrap:wrap;align-items:center;
  gap:var(--s4) var(--s5)}
.tl__ctan{font-family:var(--util);font-size:var(--fs-xs);color:var(--muted)}
/* The panel used the whole remaining track, so its serif body ran ~840px, far
   past a readable measure, while the tall tablist left the bottom-right empty.
   Splitting the panel caps the prose AND spends the leftover width on the rail
   instead of leaving it as void. No new tinted ground: coaching sits at 50.1%
   against a 52.7% tint ceiling, so this had to be solved with structure. */
.jrn__cols{display:grid;gap:var(--s6);margin-top:var(--s4)}
.jrn__body p{margin:0;max-width:62ch}
.jrn__rail>:first-child{margin-top:0}
@media(min-width:1200px){
  .jrn__cols{grid-template-columns:minmax(0,62ch) minmax(210px,1fr);
    column-gap:var(--s7);align-items:start}
  .jrn__rail{border-left:1px solid var(--line);padding-left:var(--s6)}
}
.jrn__panel{min-width:0}
.jrn__panel[hidden]{display:none}
.jrn__hd{font-family:var(--display);font-weight:800;font-size:var(--fs-2xl);
  letter-spacing:-.025em;line-height:1.05;margin:0 0 4px}
.jrn__sub{font-family:var(--util);font-size:var(--fs-sm);color:var(--citrus-text);
  font-weight:700;letter-spacing:.06em;text-transform:uppercase;margin:0 0 var(--s4)}
.jrn__out{margin-top:var(--s5);padding-top:var(--s5);border-top:1px solid var(--line)}
.jrn__out b{display:block;font-family:var(--util);font-size:var(--fs-xs);
  letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.jrn__tools{display:flex;flex-wrap:wrap;gap:8px;margin-top:var(--s4);padding:0;list-style:none}
/* Below 860 it stacks: the six sessions as a full-width list, then the open
   panel beneath. ⚠ NOT `display:contents` on the list: the tabs all precede
   the panels in DOM (they must, to stay inside role="tablist"), so contents
   would render six headers and then one panel anyway, only with the grid
   guarantees thrown away. Stacking is the honest version of the same thing.
   The point either way: no horizontal axis, so nothing can clip. */
@media(max-width:860px){
  /* One column, node on the left: no horizontal axis, so nothing can clip.
     The connector is hidden rather than rotated: a vertical rule through a
     stacked list adds a second edge next to the row borders and reads as noise. */
  .tl__track{grid-template-columns:1fr;gap:0;margin-bottom:var(--s5);
    border-top:1px solid var(--line)}
  .tl__step::after{display:none}
  .tl__step{width:100%;grid-template-columns:34px minmax(0,1fr);
    grid-template-rows:auto auto;column-gap:var(--s4);row-gap:2px;
    padding:11px 0;min-height:48px;border-bottom:1px solid var(--line);
    align-items:center;justify-items:stretch}
  .tl__node{grid-column:1;grid-row:1 / 3;align-self:center}
  .tl__t{grid-column:2;grid-row:1}
  .tl__when{grid-column:2;grid-row:2}
  .tl__step[aria-selected="true"]{background:var(--bone-2);padding-inline:var(--s3)}
  .jrn__panel{padding-bottom:var(--s4)}
}

/* ---- THE INDEX: recent work as an archive index, not a card rail -------
   Replaced a horizontal `.rail` of 4 cards. Three things were wrong with it
   and only the first was cosmetic:
     1. it CLIPPED: 144px of overflow at 1920, so the 4th card was sliced
        mid-word against the viewport edge. A scroller that cuts off at the
        WIDEST breakpoint is not a scroller, it is a broken grid.
     2. ragged card heights let the tallest set the rail height, leaving ~120px
        of dead ground under the card at 390.
     3. these records are ARTICLES, with no imagery, so each got a generated
        orange gradient + quote glyph. Four identical orange blocks in a row
        read as placeholder, not as content.
   An index needs no picture to look like content, and it has no axis to clip:
   it is a vertical list, so it cannot cut off at any width. (The earlier draft
   of this note justified the index by quoting the archive's old internal
   positioning line. §11.3 retires that line; the argument above never depended
   on it. ⛔ Do not reinstate the quote: it is banned copy now.) */
.idx{border-top:1px solid var(--ink-3)}
.idx__row{display:grid;grid-template-columns:92px 1fr auto 18px;
  align-items:baseline;gap:var(--s2) var(--s5);
  /* ⚠ NO negative inline margin for a hover "bleed". It made every row 24px
     wider than its container and produced 12px of horizontal overflow at both
     1920 and 390, a scrollbar earned for a decorative effect. Rows sit flush,
     so the titles also line up with the section heading above them. */
  padding-block:var(--s4);
  border-bottom:1px solid var(--ink-3);text-decoration:none;color:inherit;
  transition:box-shadow var(--dur) var(--ease)}
/* the hover ground is painted with a spread shadow, which cannot widen the box */
.idx__row:hover,.idx__row:focus-visible{box-shadow:0 0 0 12px var(--ink-2);
  background:var(--ink-2);border-radius:2px}
.idx__kind{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:var(--citrus)}
.idx__t{font-family:var(--display);font-weight:700;font-size:var(--fs-md);
  line-height:1.25;letter-spacing:-.015em;color:#fff}
.idx__row:hover .idx__t{color:var(--citrus)}
.idx__src{font-family:var(--util);font-size:var(--fs-sm);color:var(--on-ink-dim);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.idx__arw{color:var(--on-ink-dim);transition:transform var(--dur) var(--ease),color var(--dur) var(--ease)}
.idx__row:hover .idx__arw{color:var(--citrus);transform:translateX(4px)}
@media(prefers-reduced-motion:reduce){.idx__arw{transition:none}}
/* Below 760 the row folds to two lines: label+source, then the title. The
   arrow goes: on a touch target the whole row is the affordance. */
@media(max-width:760px){
  .idx__row{grid-template-columns:auto 1fr;gap:4px var(--s3);padding-block:var(--s4)}
  .idx__kind{grid-area:1/1}
  .idx__src{grid-area:1/2;justify-self:end;white-space:normal;text-align:right}
  .idx__t{grid-area:2/1/3/3;font-size:var(--fs-base)}
  .idx__arw{display:none}
}

/* ============================ ARCHIVE =================================== */
.chips{display:flex;flex-wrap:wrap;gap:var(--s2);flex:1 1 auto;min-width:0}
.chip{font-family:var(--util);font-size:var(--fs-sm);font-weight:600;
  padding:9px 15px;min-height:40px;display:inline-flex;align-items:center;
  border:1px solid var(--line);background:var(--white);border-radius:99px;
  cursor:pointer;transition:all var(--dur) var(--ease);color:var(--type)}
.chip:hover{border-color:var(--type)}
.chip[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:#fff}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .chip{background:var(--ink-2);border-color:var(--ink-3);color:var(--on-ink)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .chip[aria-pressed="true"]{background:var(--citrus);border-color:var(--citrus);color:#fff}

.filters{display:grid;gap:var(--s4);padding:var(--s5);background:var(--bone-2);
  border-radius:var(--r-lg);margin-bottom:var(--s6)}
.filters__row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3)}
.filters__lab{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.13em;text-transform:uppercase;color:var(--muted);
  min-width:78px}

/* ui-ux-pro-max §8: an empty state is an invitation to act, not a dead end */
.empty{text-align:center;padding:var(--s9) var(--s5);border:1px dashed var(--line);
  border-radius:var(--r-lg)}

/* ============================ FORMS ===================================== */
.field{display:grid;gap:6px;margin-bottom:var(--s4);min-width:0}
.field>label{font-family:var(--util);font-size:var(--fs-sm);font-weight:700;
  color:var(--type)}          /* §8: a VISIBLE label, never placeholder-only */
.field .hint{font-family:var(--util);font-size:var(--fs-xs);color:var(--muted)}
.field input,.field select,.field textarea{
  font-family:var(--util);font-size:16px;   /* 16px min: anything less zooms iOS */
  padding:13px 14px;min-height:48px;        /* §2: 44px+ touch target */
  border:1.5px solid var(--line);border-radius:var(--r);background:var(--white);
  color:var(--type);width:100%;
  transition:border-color var(--dur) var(--ease),box-shadow var(--dur) var(--ease)}
.field textarea{min-height:130px;resize:vertical;line-height:1.55}
.field input:focus,.field select:focus,.field textarea:focus{
  border-color:var(--citrus);box-shadow:0 0 0 3px rgba(255,122,0,.16);outline:none}
.field .err{font-family:var(--util);font-size:var(--fs-sm);color:#B3261E;
  display:none}                       /* §8: the error sits BELOW its own field */
.field.invalid .err{display:block}
.field.invalid input,.field.invalid select,.field.invalid textarea{border-color:#B3261E}
.field--req>label::after{content:" *";color:var(--ember)}
.fgrid{display:grid;gap:0 var(--s4);grid-template-columns:1fr 1fr}
@media(max-width:640px){.fgrid{grid-template-columns:1fr}}

/* radio/checkbox cards: the tap target is the LABEL, never the bare input
   (craft trap: measuring raw inputs produced 64 phantom WCAG failures) */
.opt{display:flex;gap:var(--s3);align-items:flex-start;padding:15px;
  border:1.5px solid var(--line);border-radius:var(--r);cursor:pointer;
  background:var(--white);transition:border-color var(--dur) var(--ease),
    background var(--dur) var(--ease);min-height:48px}
.opt:hover{border-color:var(--muted)}
.opt input{margin:2px 0 0;width:20px;height:20px;flex:none;accent-color:var(--citrus)}
.opt.on{border-color:var(--citrus);background:#FFF6EE}
.opt b{font-family:var(--util);font-size:var(--fs-sm);display:block}
.opt span{font-size:var(--fs-sm);color:var(--muted);display:block;line-height:1.45}
.opts{display:grid;gap:var(--s2)}

/* §8 / ui-ux-pro-max: a multi-step flow ALWAYS shows where you are and how
   far is left, and always offers a way back. Built in from the start, because
   auditing a funnel after building it is backwards (Willow Pays). */
.steps{display:flex;gap:var(--s2);margin-bottom:var(--s6);counter-reset:s}
.step{flex:1;min-width:0;display:grid;gap:6px}
.step__bar{height:4px;border-radius:99px;background:var(--line)}
.step.done .step__bar,.step.now .step__bar{background:var(--citrus)}
.step__n{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.step.now .step__n{color:var(--citrus-text)}
.step.done .step__n{color:var(--type)}

/* error summary: anchored links to each invalid field (WCAG) */
.summary{display:none;padding:var(--s4);border:1.5px solid #B3261E;
  border-radius:var(--r);background:#FDF3F2;margin-bottom:var(--s5)}
.summary.on{display:block}
.summary b{font-family:var(--util);display:block;margin-bottom:6px;color:#8C1D18}
.summary a{color:#8C1D18;font-size:var(--fs-sm);display:block;padding:3px 0}

.done-panel{display:none;text-align:center;padding:var(--s8) var(--s5);
  border:2px solid var(--citrus);border-radius:var(--r-lg);background:#FFF6EE}
.done-panel.on{display:block}
.done-panel .tick{width:60px;height:60px;border-radius:50%;background:var(--citrus);
  display:grid;place-items:center;margin:0 auto var(--s4)}
.done-panel .tick svg{width:28px;height:28px;color:#fff}

/* ============================ QUOTES ==================================== */
/* Three rows, and the MIDDLE one takes the slack: mark / quote / attribution.
   Cards in a row already stretch to a common height, but before 2026-08-27 the
   quote text pushed the attribution wherever it happened to land, so three
   cards showed three different attribution baselines and the row read as
   ragged rather than as a set. Pinning the caption to the card floor is the
   established form for this component, and it costs a little white space under
   the shortest quote, which is the right thing to spend it on.
   ⚠ Depends on quoteBlock() emitting exactly these three children. It has an
   explicit row count now, so a fourth child would land in an implicit row
   rather than silently redistributing the other three. */
.quote{display:grid;grid-template-rows:auto 1fr auto;
  gap:var(--s4);padding:var(--s6);background:var(--white);
  border:1px solid var(--line);border-radius:var(--r-lg);min-width:0}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .quote{background:var(--ink-2);border-color:var(--ink-3)}
.quote__t{font-family:var(--body);font-size:var(--fs-lg);line-height:1.5;
  font-weight:500;color:var(--type)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .quote__t{color:var(--white)}
.quote__by{font-family:var(--util);font-size:var(--fs-sm);line-height:1.45}
/* Attribution mark. See attrMark() in pages.js for WHY these are logos and
   initials rather than faces. Both variants occupy the same 26px optical band
   so a wordmark and a monogram sit on one line together. The logo files are
   unmodified originals; brightness(0) preserves their alpha and renders the
   artwork as a single ink silhouette, which is what lets three logos with
   wildly different palettes (a blue-green gradient, a copper script, a flat
   green) belong to an ink-and-citrus page. */
.quote__logo{display:block;height:var(--logo-h,26px);width:auto;max-width:140px;
  object-fit:contain;object-position:left center;margin-bottom:var(--s3);
  filter:brightness(0);opacity:.82}
/* ⚠ First draft was 10px at .72 alpha and measured 2.88:1 against a 4.5 bar,
   caught only because the selector was ADDED to contrast.mjs in the same round.
   Opacity on small text is the recurring way this palette fails: the glyph
   colour is fine, the alpha is what kills it. Full --type, no alpha on the
   text, and the recession moved onto the BORDER where it costs nothing. */
.quote__mono{display:grid;place-items:center;width:26px;height:26px;
  margin-bottom:var(--s3);border:1.5px solid rgba(20,17,15,.3);border-radius:50%;
  font-size:11px;font-weight:800;letter-spacing:.03em;color:var(--type)}
.quote__by b{display:block;color:var(--type)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .quote__by b{color:var(--white)}
.quote__by span{color:var(--muted)}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .quote__by span{color:var(--on-ink-dim)}
.quote__mark{font-family:var(--display);font-size:3rem;line-height:.6;
  color:var(--citrus);font-weight:800}
/* the source note on a truncated quote: these are cut off at EOS Worldwide
   and we must never complete the sentence */
.quote__src{font-family:var(--util);font-size:var(--fs-xs);color:var(--muted);
  font-style:normal}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .quote__src{color:var(--on-ink-dim)}

/* ============================ STATS ===================================== */
.stats{display:grid;gap:var(--s5);grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
.stat b{display:block;font-family:var(--display);font-weight:800;font-stretch:112%;
  font-size:clamp(2.2rem,4.4vw,3.4rem);line-height:1;letter-spacing:-.035em;
  color:var(--citrus);font-variant-numeric:tabular-nums}
.stat span{display:block;font-family:var(--util);font-size:var(--fs-sm);
  color:var(--muted);margin-top:6px;line-height:1.35}
:is(.sec--ink,.hero,.phero,.live,.drawer,.ft) .stat span{color:var(--on-ink-dim)}

/* ============================ FOOTER ==================================== */
.ft{background:var(--ink);color:var(--on-ink-dim);padding-block:var(--s8) var(--s6)}
.ft a{color:var(--on-ink-dim);text-decoration:none}
.ft a:hover{color:var(--citrus)}
.ft__grid{display:grid;gap:var(--s7);grid-template-columns:1.6fr 1fr 1fr 1fr;
  padding-bottom:var(--s7);border-bottom:1px solid var(--ink-3)}
@media(max-width:900px){.ft__grid{grid-template-columns:1fr 1fr;gap:var(--s6)}}
@media(max-width:560px){.ft__grid{grid-template-columns:1fr}}
.ft .ft__h{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:var(--white);
  margin-bottom:var(--s3)}
/* §44: a standalone nav link is NOT "inline in a sentence": these get a real
   44px target, they are not exempt from WCAG 2.5.8 just because they sit in an <li>. */
.ft__grid li a{display:inline-block;padding:9px 0;font-family:var(--util);
  font-size:var(--fs-sm);min-height:44px}
.ft__base{display:flex;flex-wrap:wrap;gap:var(--s3) var(--s5);
  justify-content:space-between;padding-top:var(--s5);
  font-family:var(--util);font-size:var(--fs-xs)}

/* ============================ CHAT / SEARCH ============================= */
.fab{position:fixed;right:20px;bottom:20px;z-index:var(--z-sticky);
  width:56px;height:56px;border-radius:50%;border:0;background:var(--citrus);
  color:#fff;cursor:pointer;display:grid;place-items:center;
  box-shadow:0 8px 28px rgba(20,17,15,.34);
  transition:transform var(--dur) var(--ease)}
.fab:hover{transform:scale(1.06)}
.fab svg{width:24px;height:24px}
/* §45/craft trap 43: a fixed element paints over content and NO overflow gate
   sees it. Reserve the band in the page's own padding rather than negotiating. */
body{padding-bottom:0}
@media(max-width:640px){.pad-fab{padding-bottom:92px}}

.panel{position:fixed;z-index:var(--z-drawer);background:var(--white);
  display:flex;flex-direction:column;box-shadow:0 24px 70px rgba(20,17,15,.3);
  opacity:0;visibility:hidden;transform:translateY(14px);
  transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease),
    visibility 0s linear var(--dur)}
.panel.open{opacity:1;visibility:visible;transform:none;transition-delay:0s}
.panel--chat{right:20px;bottom:88px;width:min(390px,calc(100vw - 40px));
  height:min(560px,calc(100vh - 130px));border-radius:var(--r-lg);overflow:hidden}
.panel--search{inset:0;height:100dvh;width:100%;border-radius:0}
.panel__top{display:flex;align-items:center;gap:var(--s3);padding:12px var(--s4);
  background:var(--ink);color:#fff;flex:none}
.panel__top b{font-family:var(--util);font-size:var(--fs-sm);letter-spacing:.03em}
.panel__top .icon-btn{color:#fff}
.panel__body{flex:1;overflow-y:auto;padding:var(--s4);display:flex;
  flex-direction:column;gap:var(--s3);background:var(--bone)}
.panel__foot{flex:none;padding:var(--s3);border-top:1px solid var(--line);
  display:flex;gap:var(--s2);background:var(--white)}
.panel__foot input{flex:1;min-width:0;font-family:var(--util);font-size:16px;
  padding:12px;border:1.5px solid var(--line);border-radius:var(--r)}
.panel__foot input:focus{border-color:var(--citrus);outline:none}
.bub{max-width:86%;padding:11px 14px;border-radius:12px;font-size:var(--fs-sm);
  line-height:1.55}
.bub--bot{background:var(--white);border:1px solid var(--line);align-self:flex-start;
  border-bottom-left-radius:3px}
.bub--me{background:var(--ink);color:#fff;align-self:flex-end;
  border-bottom-right-radius:3px}
.bub a{color:var(--citrus-text);font-weight:600}
.bub--me a{color:var(--citrus)}
.sugg{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--s2)}
.sugg button{font-family:var(--util);font-size:var(--fs-xs);padding:8px 11px;
  min-height:36px;border:1px solid var(--line);background:var(--white);
  border-radius:99px;cursor:pointer;text-align:left}
.sugg button:hover{border-color:var(--citrus);color:var(--citrus-text)}

.search-in{display:flex;align-items:center;gap:var(--s3);
  max-width:760px;margin:0 auto;width:100%}
.search-in input{flex:1;min-width:0;font-family:var(--display);font-weight:600;
  font-size:clamp(1.2rem,3vw,2rem);border:0;border-bottom:2px solid var(--line);
  padding:12px 0;background:transparent;color:var(--type)}
.search-in input:focus{outline:none;border-color:var(--citrus)}
.sres{max-width:760px;margin:var(--s6) auto 0;width:100%;display:grid;gap:var(--s2)}
.sres a{display:grid;gap:3px;padding:14px;border:1px solid var(--line);
  border-radius:var(--r);text-decoration:none;background:var(--white);
  transition:border-color var(--dur) var(--ease)}
.sres a:hover{border-color:var(--citrus)}
.sres b{font-family:var(--util);font-size:var(--fs-sm)}
.sres span{font-size:var(--fs-sm);color:var(--muted)}

/* ============================ TOAST ===================================== */
.toast-wrap{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);
  z-index:var(--z-toast);display:grid;gap:8px;pointer-events:none}
.toast{background:var(--ink);color:#fff;padding:13px 20px;border-radius:var(--r);
  font-family:var(--util);font-size:var(--fs-sm);box-shadow:0 10px 34px rgba(0,0,0,.34);
  animation:tin 240ms var(--ease)}
@keyframes tin{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}

/* ============================ REVEAL ==================================== */
/* §15: threshold 0.01, initialised AFTER injection, with a force-reveal
   backstop. Nothing on this page may be able to stay stuck invisible. */
.reveal{opacity:0;transform:translateY(14px);
  transition:opacity 520ms var(--ease),transform 520ms var(--ease)}
.reveal.in{opacity:1;transform:none}
@media(prefers-reduced-motion:reduce){
  .reveal{opacity:1;transform:none;transition:none}
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

/* ============================ PRINT ===================================== */
@media print{.hd,.ft,.fab,.panel,.drawer,.scrim{display:none!important}}

/* ========================================================================== */
/* V2: the brief's homepage architecture (FINAL PRESCRIPTIVE MASTER, §3).      */
/* Nothing below introduces a new colour, face or spacing step: every value    */
/* resolves to a token already in this file. The V2 work is STRUCTURE.         */
/* ========================================================================== */

/* --- inline arrow ---------------------------------------------------------
   `.jj-i--arrow` was already in V1 markup with no rule behind it, so every
   terminal link carried a 24px block-level glyph on its own line. It is an
   inline trailing mark, so it is sized and baselined as one. */
.jj-i--arrow{width:18px;height:18px;display:inline-block;vertical-align:-3px;
  margin-left:6px;transition:transform .18s ease}
a:hover .jj-i--arrow,.door:hover .jj-i--arrow,.route:hover .jj-i--arrow{transform:translateX(3px)}

/* --- 3.1 hero ------------------------------------------------------------- */
.hero__lede{margin:var(--s5) 0 0;max-width:46ch;text-wrap:balance}

/* The architecture line. NOT a claim about the business: it names the two
   doors the page is built from, so it is set as structure: the two counts in
   display weight, the outcome in citrus behind a hairline. */
.hero__arch{margin:var(--s5) 0 var(--s6);padding-top:var(--s4);
  border-top:1px solid var(--ink-3);
  display:flex;flex-wrap:wrap;gap:var(--s2) var(--s4);
  font-family:var(--util);font-size:var(--fs-sm);letter-spacing:.06em;
  text-transform:uppercase;color:var(--on-ink-dim)}
.hero__arch b{color:var(--on-ink);font-weight:700}
.hero__arch em{font-style:normal;color:var(--citrus);font-weight:700}

/* --- 3.2 the two doors ----------------------------------------------------
   EQUAL by construction: one grid, two identical tracks, `align-items:start`
   so neither card is stretched to the other's height and opens a void inside
   its own border (craft trap 74). The terminal link is pinned with
   `margin-top:auto` inside the body grid so both CTAs sit on one line. */
.doors{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s5);align-items:stretch}
@media(max-width:900px){.doors{grid-template-columns:1fr}}

.door{display:grid;grid-template-rows:auto 1fr;height:100%;
  background:var(--bone-2);border:1px solid var(--line);color:inherit;
  text-decoration:none;overflow:hidden;
  transition:border-color .18s ease,transform .18s ease}
.door:hover{border-color:var(--citrus);transform:translateY(-2px)}
.door:focus-visible{outline:3px solid var(--citrus);outline-offset:3px}

.door__fig{position:relative;aspect-ratio:16/10;overflow:hidden;background:#0E0C0B;display:block}
.door__fig>img{width:100%;height:100%;object-fit:cover;display:block}
/* the mark straddles the seam, matching .route__ic exactly: same system, same
   position, so a door and a route are recognisably the same family.
   Ink on citrus = 7.20:1. */
.door__ic{position:absolute;left:14px;top:14px;z-index:2;
  display:grid;place-items:center;width:42px;height:42px;
  background:var(--citrus);color:var(--ink)}

.door__body{padding:var(--s6);display:flex;flex-direction:column;gap:var(--s3)}
.door__k{font-family:var(--display);font-weight:800;font-stretch:112%;
  text-transform:uppercase;letter-spacing:-.02em;line-height:1;
  font-size:clamp(1.6rem,2.6vw,2.25rem);margin:0}
.door__p{display:block;margin:0;max-width:52ch}
.door__p--tight{color:var(--muted)}
.door__go{display:block;margin-top:auto;padding-top:var(--s3);
  font-family:var(--util);font-size:var(--fs-sm);font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:var(--citrus-text)}

/* THE VIRTUE RAIL: the device that makes the two doors read as counterparts
   before a word of body copy is read. Six for the room, five for the stage;
   the brief's own words, in the brief's own order. A citrus separator is drawn
   BETWEEN items only, so the rail never ends on a dangling mark. */
.virtues{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2) 0;
  font-family:var(--util);font-size:var(--fs-sm);font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.virtues>span{display:inline-flex;align-items:baseline;margin-right:var(--s4)}
.virtues>span:last-child{margin-right:0}
.virtues>span::before{content:"";flex:none;
  width:4px;height:4px;margin-right:8px;transform:translateY(-3px);background:var(--citrus)}

/* --- 3.3 the room changes -------------------------------------------------
   The one band on the page with a single column and no figure: the two doors
   collapsing back into one person is the point, so the layout says it too. */
.oneperson{max-width:60rem}
.oneperson__h{margin:0 0 var(--s4);max-width:18ch}
.oneperson__l{margin:0 0 var(--s6);max-width:54ch}

/* Four imperatives, not a paragraph, so they are a ruled stack, and the rule
   is the structure carrying them, not an ornament between them. */
.jobs{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));gap:0 var(--s6)}
@media(max-width:760px){.jobs{grid-template-columns:1fr}}
.jobs li{border-top:1px solid var(--ink-3);padding:var(--s4) 0;margin:0;
  font-family:var(--display);font-weight:700;font-stretch:112%;
  font-size:clamp(1.05rem,1.7vw,1.45rem);line-height:1.25;color:var(--on-ink)}
.jobs li::before{content:"";display:inline-block;width:14px;height:2px;
  margin-right:12px;vertical-align:.32em;background:var(--citrus)}

/* --- 3.4 through-line proof ----------------------------------------------- */
.throughline{margin:var(--s5) 0 var(--s6);grid-template-columns:repeat(4,1fr);gap:var(--s5)}
@media(max-width:760px){.throughline{grid-template-columns:repeat(2,1fr);gap:var(--s6) var(--s5)}}
.throughline__p{margin:0 0 var(--s5);max-width:54ch}
.throughline__k{margin:0;padding-top:var(--s5);border-top:1px solid var(--line);
  font-family:var(--display);font-weight:800;font-stretch:112%;
  text-transform:uppercase;letter-spacing:-.015em;line-height:1.15;
  font-size:clamp(1.15rem,2.1vw,1.75rem);max-width:34ch;text-wrap:balance}
.throughline__k em{font-style:normal;color:var(--citrus-text)}

/* --- 3.5 client proof: the video facade -----------------------------------
   No request reaches YouTube until the visitor presses play. The poster is a
   local file with an intrinsic ratio, so the swap to an <iframe> cannot move
   the page (CLS 0 by construction, not by measurement). */
.vtest{display:grid;grid-template-columns:1.3fr 1fr;gap:var(--s6);align-items:center}
@media(max-width:900px){.vtest{grid-template-columns:1fr;gap:var(--s5)}}

/* ================= §9 SPEAKING, 2026-08-25 ================================
   Keynote cards are a FLEX COLUMN so the two feet ("Description to come" and
   "Based on <book>") can be pushed to the bottom with `margin-top:auto`.
   ⚠ In a grid row, cards stretch to the tallest sibling. Three of the six
   keynotes have a title and nothing else, so without an anchored foot a thin
   card renders as a title floating over dead space and reads as broken rather
   than as awaiting copy. (Same mechanic as trap 78, and `margin-top:auto`
   does NOT push inside a grid whose tracks are packed to `align-content:start`,
   which is why this is flex and not grid.) */
.kn{display:flex;flex-direction:column}
.kn__hook{font-size:1.0625rem;margin-bottom:14px}
.kn__beats{display:grid;gap:8px;margin-bottom:16px}
.kn__beats li{display:flex;gap:10px;align-items:flex-start}
.kn__beats li span:first-child{color:var(--citrus);font-weight:800;flex:none}
.kn__book{margin:auto 0 0}
/* The honest state. Quiet, but not hidden: a citrus mark so the eye lands on
   it, muted type so it never competes with a real description. */
.kn__todo{display:flex;gap:10px;align-items:baseline;margin:auto 0 0;
  padding-top:var(--s4);border-top:1px solid var(--line);
  font-size:var(--fs-sm);color:var(--muted)}
.kn__todo span:first-child{width:7px;height:7px;flex:none;border-radius:50%;
  background:var(--citrus);transform:translateY(-1px)}

/* §9.2's replacement for the strikethrough flip. Two rows, one rule between
   them; the label is a fixed-width caps key so "Instead of" and "Try" align
   and the two arguments start on the same x. */
.swap{margin:0 0 16px;border-left:2px solid var(--citrus);padding:2px 0 2px 14px}
.swap__r{margin:0;font-size:var(--fs-sm);line-height:1.5}
.swap__r + .swap__r{margin-top:6px}
.swap__k{display:block;font-family:var(--display);font-weight:700;font-size:.6875rem;
  letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.swap__r--try .swap__k{color:var(--citrus-text)}
.swap__r--try{font-weight:600;color:var(--type)}

/* §9.4 video cards. The grid is FIXED tracks for the same reason `.five` is:
   an eight-item set whose count the brief fixes should not be left to
   auto-fill's arithmetic. */
.vcard{margin:0;display:flex;flex-direction:column;gap:var(--s3)}
.vcard__cap{display:flex;flex-direction:column;gap:6px;align-items:flex-start}
.vcard__t{font-family:var(--display);font-weight:700;line-height:1.25;
  font-size:var(--fs-sm)}
.vcard--lead .vcard__t{font-size:var(--fs-xl);line-height:1.15}
.vcard--feat .vcard__t{font-size:var(--fs-lg)}
.vgrid{display:grid;gap:var(--s5);grid-template-columns:repeat(4,minmax(0,1fr))}
@media(max-width:1100px){.vgrid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:820px){.vgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:520px){.vgrid{grid-template-columns:1fr}}
.vfeatrow{display:grid;gap:var(--s6);grid-template-columns:1.6fr 1fr;align-items:start}
@media(max-width:900px){.vfeatrow{grid-template-columns:1fr}}
.vfacade{position:relative;display:block;width:100%;padding:0;margin:0;
  aspect-ratio:16/9;overflow:hidden;background:#0E0C0B;
  border:1px solid var(--line);cursor:pointer}
.vfacade>img{width:100%;height:100%;object-fit:cover;display:block;
  /* POSTER TREATMENT, not a correction. The real thumbnail is a high-chroma
     YouTube plate - rainbow gradient, gold outlined caps - and dropped in raw
     it reads as a pasted foreign object against Ink & Citrus. It is a POSTER
     for the video, so it is framed like one: pulled back a little and seated
     on ink. Hover and focus restore it fully, so the untreated frame is always
     one gesture away and nothing about the video is misrepresented. */
  filter:saturate(.78) contrast(1.04);transition:filter .25s ease}
.vfacade:hover>img,.vfacade:focus-visible>img{filter:none}
/* the seat: a vignette anchored to the BAND, holding the plate's corners down
   so the frame has an edge instead of bleeding into the page. */
.vfacade::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:radial-gradient(120% 90% at 50% 45%,transparent 42%,rgba(20,17,15,.42) 100%)}
.vfacade[data-loaded="1"]::after{display:none}
/* iframe = a YouTube record; video = a file the client gave us (brief 3.5).
   Both land in the SAME box the poster already reserved, so the swap cannot
   move a pixel either way. `object-fit:contain` on the video because a client
   film is not ours to crop, and the letterbox sits on the band's own ink. */
.vfacade>:is(iframe,video){position:absolute;inset:0;width:100%;height:100%;border:0}
.vfacade>video{object-fit:contain;background:#0E0C0B}
.vfacade:hover .vfacade__play{transform:translate(-50%,-50%) scale(1.06)}
.vfacade:focus-visible{outline:3px solid var(--citrus);outline-offset:3px}
/* citrus ground, INK glyph. White on citrus is 2.61:1 and is banned sitewide.
   The button's ground is its own fill, which an ancestor-walking contrast
   check cannot see (craft trap 42). */
/* ---- §9.4 additions, 2026-08-25: BOTH FOUND BY LOOKING, NOT BY A GATE -----
   ① THE PLAY CONTROL DOES NOT SCALE ITSELF. It is a fixed 76px, which is right
      on a 1.6fr hero poster and swallows a 640x360 grid card: eight thumbnails
      each wearing a citrus square the size of a third of the image. Nothing
      measures "the control is too big for its card": no overflow, no contrast
      failure, no broken image. Only the screenshot.
   ② `.vfacade--none` LOST TO `.vfacade` ON SOURCE ORDER. Equal specificity
      (one class each) and it was written ABOVE, so `display:block` beat
      `display:flex` and the "awaiting" label sat top-left instead of centred.
      🔑 A modifier that does not out-specify its base is only a suggestion.
      It now carries `.vfacade.vfacade--none` and sits after the base block.
   ⛔ Do not move either rule back above `.vfacade`. */
.vgrid .vfacade__play{width:52px;height:52px}
.vgrid .vfacade__play svg{width:20px;height:20px;margin-left:2px}
.vcard--feat .vfacade__play{width:62px;height:62px}
/* A video we have a TITLE for and no file. ⛔ Deliberately not a `.vfacade`
   with a play control. See the renderer. No button, no pointer, no promise. */
.vfacade.vfacade--none{display:flex;align-items:center;justify-content:center;
  aspect-ratio:16/9;border:1px dashed var(--line);border-radius:6px;
  background:rgba(20,17,15,.05);cursor:default}
.vfacade.vfacade--none::after{display:none}
.vfacade--none .vfacade__none{font-size:var(--fs-sm);color:var(--type);
  text-align:center;padding:0 var(--s4);max-width:22ch;opacity:.72}

.vfacade__play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  z-index:2;display:grid;place-items:center;width:76px;height:76px;
  background:var(--citrus);color:var(--ink);transition:transform .18s ease;
  box-shadow:0 0 0 10px rgba(20,17,15,.55)}
.vfacade__play svg{width:30px;height:30px;margin-left:3px}

.vtest__body{display:grid;gap:var(--s3);align-content:center;justify-items:start}
.vtest__t{margin:0;max-width:22ch}
.vtest__m{margin:0}
.vtest__d{margin:0;max-width:44ch;color:var(--muted)}
/* Runtime, set in the utility face so it reads as a fact about the file and
   not as another line of the description. */
.vtest__run{margin:0;font-family:var(--util);font-size:var(--fs-xs);
  letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}

/* --- 3.7 growth zone ------------------------------------------------------
   SECONDARY BY CONSTRUCTION. The brief forbids it becoming a third primary
   door, so it is denied every device the two doors use: no card, no figure,
   no coloured ground, no button pair. One ruled band, one question, one
   control. Removing those devices IS the demotion: a smaller heading alone
   would not have been. */
.gz{border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  padding-block:var(--s7)}
.gz__in{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s5) var(--s7)}
.gz__copy{flex:1 1 28rem;min-width:0}
.gz__h{margin:0 0 var(--s2);font-family:var(--display);font-weight:800;
  font-stretch:112%;text-transform:uppercase;letter-spacing:-.02em;
  line-height:1.05;font-size:clamp(1.35rem,2.4vw,2rem)}
.gz__p{margin:0;max-width:52ch;color:var(--muted)}
.gz__cta{flex:none}

/* --- 3.8 closing --------------------------------------------------------- */
.routes--2{grid-template-columns:repeat(2,1fr)}
@media(max-width:760px){.routes--2{grid-template-columns:1fr}}
.closing__sub{margin:var(--s6) 0 0;font-family:var(--util);font-size:var(--fs-sm);
  font-weight:700;letter-spacing:.06em;text-transform:uppercase}

/* ========================================================================== */
/* GROWTH ZONE: the nine-screen diagnostic (§5).                              */
/*                                                                            */
/* §5 asks for it to feel "like a mirror, not a form". So this is the one     */
/* place on the site that runs as a single narrow column with one thing on    */
/* screen at a time: no sidebar, no cards competing for attention, no second  */
/* CTA anywhere. The marketing pages persuade; this one asks. Everything      */
/* below still resolves to a token already in this file.                      */
/* ========================================================================== */

/* Visually hidden but announced. Used for the "required" word beside the
   asterisk: an asterisk alone is a convention, not a label. */
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

.gzwrap{padding-block:var(--s8)}
.gzapp{max-width:46rem;margin-inline:auto}

/* --- progress ------------------------------------------------------------
   §8 of the UX rules: show progress for a multi-step process. The count
   deliberately starts at the first QUESTION, because labelling the landing screen
   "step 1 of 9" would be claiming progress the visitor has not made. */
.gzp{margin-bottom:var(--s6)}
.gzp__t{margin:0 0 8px;font-family:var(--util);font-size:var(--fs-xs);
  letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.gzp__bar{height:3px;background:var(--line);overflow:hidden}
.gzp__bar>span{display:block;height:100%;background:var(--citrus);
  transition:width .35s cubic-bezier(.2,.7,.3,1)}
@media(prefers-reduced-motion:reduce){.gzp__bar>span{transition:none}}

/* --- screen shell -------------------------------------------------------- */
.gzs{display:grid;gap:var(--s5);justify-items:start}
.gzs :focus-visible{outline:3px solid var(--citrus);outline-offset:3px}
.gzs__h{margin:0;font-family:var(--display);font-weight:800;font-stretch:112%;
  text-transform:uppercase;letter-spacing:-.025em;line-height:1;
  font-size:clamp(1.9rem,5vw,3.1rem);text-wrap:balance}
.gzs__h--res{color:var(--ink)}
.gzs__lead{margin:0;font-size:var(--fs-md);line-height:1.5}
.gzs__lead2{margin:0;max-width:48ch;color:var(--muted)}
/* the positioning line (§5.1) is the one sentence on the screen that argues,
   so it is the one thing set against a rule. */
.gzs__pos{margin:0;padding-left:var(--s4);border-left:3px solid var(--citrus);
  font-family:var(--display);font-weight:700;font-size:clamp(1.05rem,2vw,1.35rem);
  line-height:1.3;max-width:34ch}
.gzs__why{display:grid;gap:10px;margin-top:var(--s5);padding-top:var(--s5);
  border-top:1px solid var(--line);max-width:46ch;color:var(--muted)}
.gzs__why p{margin:0}
.gzs__why-n{font-family:var(--util);font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--ink)}
.gzs__why-q{font-family:var(--display);font-weight:700;color:var(--ink);
  font-size:var(--fs-md);line-height:1.35}
.gzs__nextq{margin:0;font-family:var(--display);font-weight:700;
  font-size:clamp(1.05rem,2vw,1.35rem);line-height:1.3;max-width:34ch}
.gzs__restart{margin:var(--s5) 0 0}
.gzs__restart .tlink{background:none;border:0;border-bottom:1px solid currentColor;
  padding:0;font:inherit;cursor:pointer}

/* --- questions ----------------------------------------------------------- */
.gzq{border:0;padding:0;margin:0;display:grid;gap:var(--s3);justify-items:start;width:100%}
.gzq__h{margin:0;padding:0;font-family:var(--display);font-weight:800;
  font-stretch:112%;letter-spacing:-.02em;line-height:1.1;
  font-size:clamp(1.5rem,3.4vw,2.3rem);max-width:20ch}
.gzq__h label{cursor:default}
.gzq__sub{margin:0 0 var(--s3);max-width:52ch;color:var(--muted)}

/* Binary answers as full-width rows rather than two side-by-side buttons:
   each one carries a sentence of its own, and two long sentences in two
   columns is a reading problem the visitor has to solve before answering. */
.gzopts{display:grid;gap:var(--s3);width:100%}
.gzopt{display:grid;grid-template-columns:auto 1fr;gap:4px var(--s4);
  align-items:start;padding:var(--s5);border:1px solid var(--line);
  background:var(--bone-2);cursor:pointer;transition:border-color .15s ease}
.gzopt:hover{border-color:var(--citrus)}
.gzopt input{grid-row:1 / span 2;width:22px;height:22px;margin:2px 0 0;
  accent-color:var(--citrus);cursor:pointer}
.gzopt__k{font-family:var(--display);font-weight:800;text-transform:uppercase;
  letter-spacing:.02em;line-height:1}
.gzopt__d{color:var(--muted)}
.gzopt:has(input:checked){border-color:var(--citrus);background:var(--white)}
.gzopt:has(input:focus-visible){outline:3px solid var(--citrus);outline-offset:2px}

/* --- the pain slider -----------------------------------------------------
   A native <input type=range>: keyboard-operable, announced correctly and
   draggable, none of which a div-based slider gets for free. */
.gzslider{width:100%;display:grid;gap:var(--s3);margin-top:var(--s3)}
.gzslider__n{font-family:var(--display);font-weight:800;font-stretch:112%;
  font-size:clamp(3rem,9vw,5rem);line-height:.9;color:var(--citrus);
  font-variant-numeric:tabular-nums}
.gzslider input[type=range]{width:100%;accent-color:var(--citrus);height:32px;cursor:pointer}
.gzslider__ends{display:flex;justify-content:space-between;gap:var(--s4);
  margin:0;font-family:var(--util);font-size:var(--fs-xs);color:var(--muted);
  text-transform:uppercase;letter-spacing:.06em}
.gzslider__ends span:last-child{text-align:right}

/* --- the 2x2 matrix ------------------------------------------------------
   §5.5's "clean 2x2 matrix" with the visitor's zone visibly highlighted.
   The highlight is NOT colour alone: the live cell also prints "You are here"
   and carries aria-current, so it survives grayscale, colour blindness and a
   screen reader. Colour that is the only carrier of meaning is a defect. */
.gzm{display:grid;width:100%;
  grid-template-columns:auto 1fr 1fr;grid-template-rows:auto 1fr 1fr;
  gap:2px;background:var(--line);border:1px solid var(--line);margin:var(--s3) 0}
.gzm__ax{background:var(--bone);display:grid;place-items:center;padding:10px 12px;
  font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted);text-align:center}
.gzm__ax--r1,.gzm__ax--r2{writing-mode:vertical-rl;transform:rotate(180deg)}
.gzm__c{background:var(--bone-2);padding:var(--s5) var(--s4);min-height:96px;
  display:grid;align-content:center;justify-items:center;gap:6px;text-align:center}
.gzm__k{font-family:var(--display);font-weight:800;font-stretch:112%;
  text-transform:uppercase;letter-spacing:-.01em;line-height:1;
  font-size:clamp(1rem,2.2vw,1.5rem);color:var(--muted)}
/* ink on citrus = 7.20:1. White on citrus is 2.61:1 and is banned sitewide. */
.gzm__c.is-on{background:var(--citrus)}
.gzm__c.is-on .gzm__k{color:var(--ink)}
.gzm__you{font-family:var(--util);font-size:var(--fs-xs);font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink)}
@media(max-width:560px){
  .gzm__ax--r1,.gzm__ax--r2{writing-mode:horizontal-tb;transform:none;font-size:10px}
  .gzm__c{min-height:78px;padding:var(--s4) 10px}
}

.gzbody{display:grid;gap:var(--s3);max-width:56ch}
.gzbody p{margin:0}
.gzthought{margin:0;padding:var(--s5);background:var(--ink);color:var(--on-ink);
  max-width:46ch;justify-self:stretch}
.gzthought p{margin:0;font-family:var(--display);font-weight:700;
  font-size:clamp(1.1rem,2.2vw,1.5rem);line-height:1.25}

/* --- frustration chips --------------------------------------------------- */
.gzchips{display:flex;flex-wrap:wrap;gap:10px;width:100%}
.gzchip{position:relative;display:inline-flex;align-items:center;
  padding:11px var(--s4);border:1px solid var(--line);background:var(--bone-2);
  font-family:var(--util);font-size:var(--fs-sm);font-weight:600;
  cursor:pointer;transition:border-color .15s ease,background .15s ease;
  min-height:44px}                      /* 44px minimum touch target */
.gzchip input{position:absolute;opacity:0;pointer-events:none}
.gzchip:hover{border-color:var(--citrus)}
.gzchip:has(input:checked){background:var(--citrus);border-color:var(--citrus);color:var(--ink)}
.gzchip:has(input:focus-visible){outline:3px solid var(--ink);outline-offset:2px}

/* --- forms --------------------------------------------------------------- */
.gzform{display:grid;gap:var(--s5);width:100%}
.gzfield{display:grid;gap:6px;width:100%;max-width:34rem}
.gzlabel{font-family:var(--util);font-size:var(--fs-sm);font-weight:700;
  letter-spacing:.04em;text-transform:uppercase}
.gzlabel b{color:var(--citrus-text)}
.gzlabel em{font-style:normal;font-weight:600;text-transform:none;
  letter-spacing:0;color:var(--muted)}
.gzhelp{margin:0;font-size:var(--fs-sm);color:var(--muted)}
.gzfield input,.gzfield select,.gzfield textarea{
  width:100%;padding:13px var(--s4);font:inherit;font-family:var(--util);
  background:var(--white);color:var(--ink);border:1px solid var(--line);
  min-height:48px}
.gzfield textarea{min-height:96px;resize:vertical;line-height:1.5}
.gzfield :is(input,select,textarea):focus-visible{outline:3px solid var(--citrus);
  outline-offset:0;border-color:var(--citrus)}
/* The error sits with its own field, not in a summary at the top: an error
   the visitor has to scroll away from to act on is a second task. */
.gzerr{margin:0;font-family:var(--util);font-size:var(--fs-sm);font-weight:700;
  color:var(--ember)}
.gzsend{margin:0;color:var(--muted)}
.gzsend p{margin:0 0 8px}
.gzsend ul{margin:0;padding-left:1.1em}

.gznav{display:flex;flex-wrap:wrap;gap:var(--s3);margin-top:var(--s3)}

/* --- diagnosis + prescription -------------------------------------------- */
.gzdiag{display:grid;gap:var(--s3);max-width:52ch}
.gzdiag p{margin:0;font-size:var(--fs-md);line-height:1.5}
.gzrx{display:grid;gap:10px;justify-items:start;width:100%;
  padding:var(--s6);background:var(--sand);border-left:4px solid var(--citrus)}
.gzrx__t{margin:0;font-family:var(--display);font-weight:800;font-stretch:112%;
  letter-spacing:-.02em;line-height:1.05;font-size:clamp(1.3rem,2.8vw,1.9rem)}
.gzrx__sig{margin:0;font-family:var(--display);font-weight:700;line-height:1.25;
  font-size:clamp(1.05rem,2.1vw,1.35rem);max-width:30ch}
.gzrx__d{margin:0;max-width:52ch;color:var(--muted);font-family:var(--util);
  font-size:var(--fs-sm);letter-spacing:.03em;text-transform:uppercase}
/* Shown wherever a vignette has no verified URL, which in V1 is every route.
   It states the position plainly instead of rendering a dead control. */
/* The prescribed vignette, playing inside the diagnosis card (2026-08-27).
   Capped at 420px rather than filling the card: the card's job is the
   diagnosis, and a full-width plate would make the video the subject of a
   section that is really about what the visitor just told us. */
.gzrx__v{max-width:420px;width:100%;margin-top:6px;border-radius:var(--r-sm)}
.gzrx__v .vfacade__play{width:56px;height:56px}
.gzrx__v .vfacade__play svg{width:22px;height:22px;margin-left:2px}
.gzrx__run{margin:0;font-family:var(--util);font-size:var(--fs-xs);
  letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.gzrx__soon{margin:6px 0 0;font-family:var(--util);font-size:var(--fs-sm);
  font-weight:600;color:var(--muted)}

/* --- the single next step ------------------------------------------------
   §5.10: "Do not show a wall of equal CTAs." One card, one control. */
.gznext{display:grid;gap:12px;justify-items:start;width:100%;
  padding:var(--s6);background:var(--ink);color:var(--on-ink)}
.gznext__t{margin:0;font-family:var(--display);font-weight:800;font-stretch:112%;
  text-transform:uppercase;letter-spacing:-.02em;line-height:1.05;
  font-size:clamp(1.35rem,3vw,2.1rem)}
.gznext__d{margin:0;max-width:52ch;color:var(--on-ink-dim)}

/* --- the homepage module's own heading uses .h2, which only exists once --- */
@media(max-width:560px){
  .gzs{gap:var(--s4)}
  .gzopt{padding:var(--s4)}
}

/* ==========================================================================
   §10 ABOUT: the short version, and the career bridge
   ========================================================================== */

/* The one typographic beat inside §10.2's third paragraph. Not extra copy:
   the brief's own closing sentence, lifted into the display face so the
   paragraph lands on it. ⛔ Never repeat it as a pull-quote: that would put a
   sentence on the page twice and the gate counts sentences, not elements. */
.bio__beat{font-family:var(--display);font-weight:700;font-stretch:105%;
  letter-spacing:-.01em;color:var(--type)}

/* --- §10.3 career bridge -------------------------------------------------
   Seven cells with a hairline between them, then ONE citrus rule under all
   seven. The hairlines are "several careers"; the rule is "It isn't."
   ⚠ The separator is border-inline-START on every item except the first, not
   border-inline-END on every item except the last. The run WRAPS, and with
   -end the last item of a wrapped row keeps a rule pointing at nothing. With
   -start, the only item without a leading rule is the first of each row,
   which is what the eye expects of a wrapped run. */
.bridge__h{margin:10px 0 var(--s6);max-width:16ch;text-transform:uppercase;
  letter-spacing:-.015em;line-height:1.02;text-wrap:balance}
.roles{display:flex;flex-wrap:wrap;list-style:none;margin:0;padding:0 0 var(--s5);
  border-bottom:2px solid var(--citrus)}
.roles li{font-family:var(--display);font-weight:600;font-stretch:105%;
  font-size:var(--fs-lg);line-height:1.25;padding:6px var(--s5);
  border-inline-start:1px solid var(--line)}
.roles li:first-child{border-inline-start:0;padding-inline-start:0}
.bridge__turn{margin-top:var(--s6);max-width:var(--measure)}
.bridge__pre{color:var(--muted);margin:0}
.bridge__no{font-family:var(--display);font-weight:800;font-stretch:112%;
  font-size:var(--fs-2xl);line-height:1.05;letter-spacing:-.02em;
  color:var(--citrus-text);margin:var(--s3) 0 var(--s4)}
.bridge__yes{font-size:var(--fs-md);line-height:1.62;margin:0}
@media(max-width:640px){
  /* Stacked, the hairline turns horizontal and the run reads as a list:
     still seven separate things, still one rule closing them. */
  .roles{display:block;padding-bottom:var(--s4)}
  .roles li{border-inline-start:0;border-block-start:1px solid var(--line);
    padding:10px 0;font-size:var(--fs-md)}
  .roles li:first-child{border-block-start:0;padding-block-start:0}
}

/* ================= §11.3: THE ARCHIVE'S FOUR BEATS ======================
   "Watch. Read. Listen. Disagree." is prescribed copy. Three of its four verbs
   ARE the archive's own type filter: episodes and shorts, articles,
   conversations. The fourth is not a way of consuming anything; it is the
   invitation, and it is the reason the sentence is memorable. So it is set in
   citrus while the other three stay white: the emphasis marks the break in the
   pattern the sentence itself makes.
   ⛔ NOT "accent the last item of a list". If the copy ever changes so that the
   fourth beat is another consumption mode, this rule comes out with it.
   Hairlines rather than the copy's own full stops doing double duty: the stops
   are kept because they are in the brief, the rules are what make four beats
   read as four, at any width, before the words are read. */
.beats{list-style:none;margin:0 0 var(--s5);padding:0;
  display:flex;flex-wrap:wrap;align-items:baseline;
  font-family:var(--util);font-weight:700;text-transform:uppercase;
  letter-spacing:.10em;font-size:var(--fs-sm);color:#fff}
.beats li{display:flex;align-items:baseline}
.beats li + li::before{content:"";flex:none;width:1px;height:.95em;
  margin-inline:14px;background:rgba(255,255,255,.34)}
.beats li:last-child{color:var(--citrus)}
@media(max-width:520px){.beats li + li::before{margin-inline:10px}}

/* =============== §11.5: THE TRACTION FULFILMENT PAGE ====================
   One device, and it is derived from what the fields ARE. Five of them are not
   CRM data (they become the address printed on a parcel), so that fieldset is
   set as the label on the parcel and its <legend> sits in the border gap as the
   "TO". Everything else on the page reuses .field / .fgrid / .opt unchanged.
   ⛔ Do not put .mail on a fieldset that is not a postal address; the box is
   saying something, and on anything else it would only be a box. */
.mail{border:1.5px solid var(--ink);border-radius:var(--r);
  padding:var(--s5) var(--s5) calc(var(--s5) - var(--s4));margin:0 0 var(--s5);background:#fff}
.mail > legend{font-family:var(--util);font-size:var(--fs-xs);font-weight:800;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink);
  padding:0 9px;margin-inline-start:6px}
/* City / State / Zip read as one line on a label, so they are one row, and the
   widths are the real proportions of an address line, not three equal columns. */
.fgrid--addr{grid-template-columns:1.6fr 1fr 1fr}
@media(max-width:640px){
  .fgrid--addr{grid-template-columns:1fr 1fr}
  .fgrid--addr .field:first-child{grid-column:1/-1}
}

/* The EOS trust line. A quoted third-party figure, so it is set as a marked
   quantity rather than as body copy that could be mistaken for our own voice. */
.tstat{font-family:var(--util);font-size:var(--fs-sm);font-weight:700;
  letter-spacing:.02em;color:var(--type);margin:0 0 18px;padding-inline-start:14px;
  border-inline-start:3px solid var(--citrus)}

/* A preference question is a full sentence, not a field label: it wraps, so it
   cannot inherit .field>label's single-line treatment. */
.qlegend{font-family:var(--util);font-size:var(--fs-sm);font-weight:700;
  line-height:1.5;margin-bottom:10px;max-width:62ch;color:var(--type)}
.qlegend em{font-style:italic}
/* Yes / No is a two-up, not a stack: two one-word options in a vertical list
   read as the start of a longer list that never arrives. */
.opts--yn{grid-template-columns:repeat(2,minmax(0,140px));gap:var(--s3)}
.opts--yn .opt{align-items:center}

/* ================= TWO FIXES FOUND BY LOOKING, 2026-08-26 ===============
   (1) 🔴 `hidden` HAS NEVER HIDDEN A BUTTON ON THIS SITE.
   `.btn{display:inline-flex}` is a class rule; `[hidden]{display:none}` lives in
   the USER-AGENT sheet, and an author class beats a UA rule outright. So every
   `hidden` button on every form has been rendering since the forms were built:
   measured 2026-08-26, `#fBack` and `#fSend` on book-call.html and
   book-speaking.html both report `hidden=true` and `display:flex`.
   That is not cosmetic. `#fSend` visible on step 1 means a visitor can submit a
   three-step form from the first step, which runs validate() over every step and
   lights up errors on fields they have not reached, the exact behaviour
   pages.js's own Enter-key comment says the form was built to avoid.
   🔑 The tell is general: whenever a component sets `display`, it has silently
   opted out of `hidden`. `_gate-s11.mjs` leg 7 now asserts the computed display
   of every hidden button, because `hasAttribute('hidden')` is TRUE in both the
   working and the broken case and cannot tell them apart. */
.btn[hidden]{display:none}

/* (2) A HEADING BREAKS MID-WORD ONLY WHEN THERE IS NO ALTERNATIVE.
   `.split>*{overflow-wrap:anywhere}` is a sound guard for body copy in a narrow
   grid column and it is wrong for display type: `anywhere` breaks a word at the
   end of the current line even when moving the whole word down would have fit
   it. That is how the prescribed §11.5 headline rendered as
   "Get Your Complime / ntary Copy of Traction".
   `break-word` keeps the protection and drops the eagerness: a word moves to its
   own line first, and only breaks if it cannot fit on a line by itself.
   ⚠ I tried `normal` first, on the argument that an overflow is a loud failure
   and a mid-word break is a silent one. It IS louder: it immediately turned up
   a real one: "The Entrepreneur's Journey" at --fs-3xl needs 343px and has 312
   at a 360px viewport, so shows.html grew a 7px scroll strip. But `normal` fixes
   nothing on its own; it converts a cosmetic defect into a layout defect on any
   page with a long word and a narrow column, and this site has several. The
   §11.5 headline was fixed by giving it the full column width, which is the real
   answer there; `break-word` is the right global default alongside it. */
:is(h1,h2,h3,.h1,.h2,.h3){overflow-wrap:break-word;word-break:normal;hyphens:none}

/* 🔴 A GRID ITEM WITH `justify-self:center` IS SHRINK-TO-FIT, AND .thumb--book
   HAS NO SIZE OF ITS OWN TO SHRINK TO.
   `.thumb--book img` is `width:auto;height:100%`, so the image's width comes
   from the box's height; the box's height comes from its width via
   `aspect-ratio:2/3`; and with `justify-self:center` its width comes from the
   image. That is a cycle, and the only thing that has ever broken it is an
   image that was ALREADY decoded, which is why the pattern works on every page
   where the jacket is eager, and collapses to 0×0 wherever it is lazy.
   Found 2026-08-26 on coaching.html's §11.5 strip: the box computed 0 wide, so
   it was never in the viewport, so the lazy image never loaded: a deadlock,
   not a slow load, and it presents as "image loaded but not visible".
   ⚠ `width` and `height` ATTRIBUTES DO NOT FIX IT (tried first, measured, no
   change): `width:auto` in the stylesheet overrides the attribute, so there is
   still nothing definite in the chain. A definite width on the BOX is what
   breaks it; the inline `max-width` on each instance still caps the size. */
.thumb--book{width:100%}

/* "The Entrepreneur's Journey" is a real show title and cannot be shortened, and
   at --fs-3xl the single word "Entrepreneur's" measures 343px against a 312px
   column at 360 and a 342px column at 390, so it was breaking mid-word inside a
   proper noun. When a name does not fit, the TYPE gives way, not the name.
   ⚠ Scoped to the show list, and to the widths where it is actually true: the
   same heading has room from 420 up, and dropping the whole .h2 step site-wide
   to fix one string would be paying for it on every other heading. Measured, not
   guessed: `_build/_probe-h1.mjs` reports the need and the have for every
   heading on every page at 1920/1440/768/390/360. */
@media(max-width:420px){ #showList .h2{font-size:var(--fs-2xl)} }



/* 🔴 THESE ARE `.eostool`, NOT `.tool`, AND THIS BLOCK NO LONGER REDEFINES
   `.sec--sand`. The first draft used both names. Both were ALREADY TAKEN:
   `.tool` / `.tool__d` are the icon cards at line ~497 (coaching, speaking,
   the homepage trio), and `.sec--sand` is defined at line ~466 WITH a
   `--line` override this block silently dropped. Appending a rule at the end
   of a stylesheet repaints every existing element that matches it: six
   contrast FAILs at 2.46:1 appeared on three pages this work never touched.
   ⛔ Grep the stylesheet for a class name before defining it. */
/* ======================= 15 - PAIN-POINT PAGES =========================
   Fifteen SEO/content pages that all share one template, so the template is
   the design decision. Three rules it is built on:

   1. A TYPE-LED HERO, because there are fifteen of these and no photography
      for any of them. `.phero` already handles a dark band; what it assumes is
      an image behind the veil, so this variant drops both and keeps the ground.
      Do NOT "fix" these pages by putting a stock photo behind them - the image
      policy on this build is real photography or none.
   2. THE HERO OPENS WITH THE VISITOR'S OWN SENTENCE. `.said` is a vthSignal
      out of the vignette library - the client's own record of what a founder
      in this situation actually says. It is quoted because it IS a quote, and
      it is attributed to nobody because it belongs to the reader.
   3. THE PULL LINE IS NOT `.quote`. `.quote` is the testimonial component:
      white card, attribution, logo. Joseph's own hardest line set in that
      component would read as a client endorsing him, which is the fabricated-
      proof failure this build keeps catching. `.statement` is deliberately
      plain - a rule, type, no card, no marks, no name. */
.phero--type{min-height:auto;align-items:stretch;background:var(--ink)}
.phero--type .phero__in{padding-block:var(--s9) var(--s8)}
.phero--type .phero__copy{max-width:min(46rem,74%)}
@media(max-width:900px){.phero--type .phero__copy{max-width:none}}

.said{margin:0 0 var(--s5);padding-left:var(--s4);border-left:2px solid var(--citrus);
  font-family:var(--body);font-size:var(--fs-md);line-height:1.5;color:var(--on-ink-dim);
  max-width:32rem}

.statement{margin:var(--s8) 0 0;padding-left:var(--s5);border-left:3px solid var(--citrus);
  font-family:var(--display);font-weight:600;font-size:var(--fs-xl);line-height:1.28;
  letter-spacing:-.01em;color:var(--type);max-width:40rem;font-stretch:102%}

/* The EOS explainers. Not numbered: three tools that address one problem are a
   set, not a sequence, and 01/02/03 would assert an order that is not there. */
.eostools{margin:0;display:grid;gap:0}
.eostool{display:grid;gap:var(--s2) var(--s6);padding-block:var(--s5);
  border-top:1px solid var(--line-sand)}
.eostool:last-child{border-bottom:1px solid var(--line-sand)}
.eostool__n{margin:0;font-family:var(--display);font-weight:700;font-size:var(--fs-md);
  line-height:1.25;color:var(--type)}
.eostool__d{margin:0;color:var(--muted);max-width:var(--measure)}
@media(min-width:820px){
  .eostool{grid-template-columns:19rem minmax(0,1fr);align-items:baseline}
  .eostool__d{max-width:52ch}
}

/* The prescribed vignette, same shape and same honesty as the diagnostic's
   card: the title, the client's own signal, and NO watch control until a
   verified URL exists. */
.rx{display:grid;gap:var(--s3);padding:var(--s6);background:var(--white);
  border:1px solid var(--line);border-radius:var(--r-lg);max-width:38rem}
.rx__t{margin:0;font-family:var(--display);font-weight:700;font-size:var(--fs-lg);
  line-height:1.2}
.rx__sig{margin:0;font-family:var(--body);font-size:var(--fs-md);color:var(--type)}
.rx__d{margin:0;font-size:var(--fs-sm);color:var(--muted)}
.rx__soon{margin:0;font-family:var(--util);font-size:var(--fs-sm);color:var(--muted)}

/* Two CTAs, and only ever these two: the acceptance criteria forbid a third
   commercial door. */
.painnext{display:flex;flex-wrap:wrap;gap:var(--s4);margin-top:var(--s6)}

/* Sibling topics. A plain rule-separated index rather than fifteen cards - a
   card grid would give a link farm the visual weight of the two real doors. */
.sibs{list-style:none;margin:0;padding:0;display:grid;gap:0}
.sibs li{border-top:1px solid var(--line)}
.sibs li:last-child{border-bottom:1px solid var(--line)}
.sibs a{display:block;padding:var(--s3) 0;font-family:var(--util);
  font-size:var(--fs-sm);font-weight:600;color:var(--type);text-decoration:none}
.sibs a:hover,.sibs a:focus-visible{color:var(--citrus-text)}
.sibs a[aria-current="page"]{color:var(--muted);font-weight:500}
@media(min-width:760px){.sibs{grid-template-columns:1fr 1fr;column-gap:var(--s7)}}
