/* ============================================================
   Decision Tree Research — decisiontreeresearch.com
   Shared stylesheet
   ============================================================ */

/* Custom properties — Decision Tree Research palette
   Authoritative colors from the official DTR logo files.
   ============================================================ */
:root {
  --bg: #F4F3EF;             /* light cream — page background */
  --bg-light: #FFFFFF;       /* surface cards, lifted panels */
  --bg-tint: #E8E0CC;        /* tan-tinted bands (mid-warm) */
  --bg-dark: #072420;        /* very dark teal — official DTR brand dark */
  --bg-dark-2: #051917;      /* darkest teal — footer, deepest dark */

  --border: rgba(7, 36, 32, 0.14);
  --border-strong: rgba(7, 36, 32, 0.26);
  --border-dark: rgba(244, 243, 239, 0.14);

  --text: #072420;           /* near-black teal — body text */
  --text-muted: #4D5854;     /* desaturated green-gray; darkened 2026-05-12
                                from #5C6E68 to clear WCAG AA 4.5:1 on both
                                cream (#F4F3EF) and tan tint (#E8E0CC). At
                                the old value, .featured-tile-blurb on tan
                                measured 4.1:1 (below AA threshold for
                                normal text). New value measures ~6.8:1 on
                                cream and ~5.7:1 on tan. */
  --text-eyebrow: #4D5854;   /* darkened 2026-05-12 from #8E9B95 (was 2.6:1
                                on cream — failed WCAG AA). Now aliased to
                                --text-muted's new value so the "Research
                                featured in" / "Event and Media photos"
                                gray captions on the founder bio clear AA.
                                The visual distinction from --text-muted
                                was always subtle and the design loses
                                little by collapsing them. */
  --text-on-dark: #F4F3EF;
  --text-on-dark-muted: #DDCAAB;

  --accent: #67C070;         /* bright DTR lime green — official accent */
  --accent-dark: #4FA85B;    /* slightly darker — decorative-only as of 2026-05-12 */
  /* Forest green for any TEXT or BUTTON BACKGROUND on the cream
     #F4F3EF page surface. Added 2026-05-12 because both --accent
     (#67C070) and --accent-dark (#4FA85B) fail WCAG AA 4.5:1 contrast
     on cream — measured ratios 2.7:1 and 2.7:1 respectively. The new
     --accent-text clears 8.77:1 on cream and 9.59:1 with white text
     on it, so it works as both text color AND solid button background.
     Per the 2026-05-12 brand decision: keep --accent and --accent-dark
     for non-text decorative uses (the mark's chosen-path leaf, hover
     and focus rings, accent dividers, ::selection background, border-
     accents); switch any green TEXT and any green BUTTON BACKGROUND
     on light surfaces to --accent-text. On the dark teal #072420
     surface, the bright --accent still clears AA at 7.4:1, so the
     header nav, on-dark CTAs, and other dark-surface accents keep
     their bright green. */
  --accent-text: #134d1c;    /* forest green for text and button bg on cream */
  --accent-soft: #C9E8CC;    /* tint background for badges */
  --accent-2: #BFA26A;       /* gold — secondary highlight */
  --accent-3: #DDCAAB;       /* DTR cream — tertiary highlight */
  --accent-4: #072420;       /* very dark teal — fourth */

  --font-serif: 'IBM Plex Serif', 'IBMPlex-Fallback', Georgia, 'Times New Roman', serif;
  --font-sans: 'Jost', 'Jost-Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;

  --container: 1180px;
  --container-narrow: 760px;
  --container-prose: 660px;
  /* Wider feature-band tier. Currently used by the homepage Areas-of-focus
     section to render its tile photos ~15% larger than they would at the
     standard 1180px container. Promoted from a literal 1357px override
     2026-05-12 (C4 alignment-audit hygiene pass) so the value lives in one
     place and the intent is explicit. */
  --container-wide: 1357px;

  --radius: 2px;
  --radius-md: 4px;
  --radius-pill: 999px;

  --shadow-soft: 0 1px 0 rgba(61, 46, 37, 0.04);

  /* Spacing scale — 8-point grid (per docs/Brand_and_Design.md section 5).
     Use these for padding, margin, and gap. Avoid arbitrary rem/px values. */
  --space-0-5: 0.25rem;  /*  4px — micro */
  --space-1:   0.5rem;   /*  8px */
  --space-2:   1rem;     /* 16px */
  --space-3:   1.5rem;   /* 24px */
  --space-4:   2rem;     /* 32px */
  --space-5:   2.5rem;   /* 40px */
  --space-6:   3rem;     /* 48px */
  --space-7:   3.5rem;   /* 56px — added 2026-05-12 for the post-tightening pass */
  --space-8:   4rem;     /* 64px */
  --space-9:   4.5rem;   /* 72px — added 2026-05-12 for the post-tightening pass */
  --space-10:  5rem;     /* 80px */
  --space-12:  6rem;     /* 96px */
  --space-16:  8rem;     /* 128px */

  /* Macro section spacing — vertical padding between major page bands.
     Tightened 2026-05-12 from 96 / 64 to 72 / 56 (Option D differentiated
     vertical-rhythm pass). Pages feel more punchy without losing the
     section-divider rhythm. Heroes and dark CTA bands keep their original
     breathing room — see .hero and .subscribe-band rules below. */
  --section-padding:        var(--space-9);   /* 72px between sections (was 96px) */
  --section-padding-mobile: var(--space-7);   /* 56px on mobile (was 64px) */

  /* Type scale — 1.25 ratio on a 16px base, snapped to 8px multiples for
     vertical-rhythm compatibility. h1=60, h2=48, h3=40, h4=32, h5=24, h6=20. */
  --fs-body:    1rem;       /* 16px */
  --fs-h6:      1.25rem;    /* 20px */
  --fs-h5:      1.5rem;     /* 24px */
  --fs-h4:      2rem;       /* 32px */
  --fs-h3:      2.5rem;     /* 40px */
  --fs-h2:      3rem;       /* 48px */
  --fs-h1:      3.75rem;    /* 60px */
  --fs-display: 4.5rem;     /* 72px — reserved for hero h1 on widest viewports */
}

/* Self-hosted fonts — Jost and IBM Plex Serif served from same origin.
   Eliminates the font-swap jitter that Google Fonts CDN delivery caused.
   ============================================================ */
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('../fonts/jost-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('../fonts/jost-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url('../fonts/jost-600.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('../fonts/ibm-plex-serif-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: italic;
  font-weight: 400;
  font-display: block;
  src: url('../fonts/ibm-plex-serif-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('../fonts/ibm-plex-serif-500.woff2') format('woff2');
}

/* Metric-matched fallback fonts — last-line defense if WOFF2 fails to load.
   ============================================================ */
@font-face {
  font-family: 'Jost-Fallback';
  src: local('Arial'), local('Helvetica');
  size-adjust: 102%;
  ascent-override: 92%;
  descent-override: 22%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'IBMPlex-Fallback';
  src: local('Georgia'), local('Times New Roman');
  size-adjust: 100%;
  ascent-override: 90%;
  descent-override: 24%;
  line-gap-override: 0%;
}

/* Reset
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Reserve space for the vertical scrollbar at all times so the layout doesn't
     shift when navigating from a long page (Publications, Press) to a short one
     (404, Contact). Modern browsers honor scrollbar-gutter; older fall back to
     overflow-y: scroll which forces the scrollbar always-visible. */
  scrollbar-gutter: stable;
  overflow-y: scroll;
}
/* Touch devices use overlay scrollbars that don't take layout space — so the
   stable-gutter reservation above leaves an always-empty 15 px column on the
   right and shifts every page's content 15 px left of viewport center. Caught
   2026-05-20 on iPhone Safari. Drop the reservation on touch-primary devices;
   desktop with a layout scrollbar keeps the rule and the navigation-no-jump
   behavior it's there for. `(hover: none) and (pointer: coarse)` selects
   touchscreens whose primary input is a finger; hybrid laptops with a real
   mouse register `(hover: hover) and (pointer: fine)` and stay on the desktop
   branch. */
@media (hover: none) and (pointer: coarse) {
  html {
    scrollbar-gutter: auto;
    overflow-y: auto;
  }
}
body {
  margin: 0;
  font-family: var(--font-serif);
  font-size: 17px;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
img, video, svg { max-width: 100%; height: auto; display: block; }
a { color: var(--accent-text); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; transition: color 120ms ease; }
a:hover, a:focus { color: var(--accent-text); }
button { font: inherit; cursor: pointer; background: none; border: 0; color: inherit; }
input, textarea, select { font: inherit; color: inherit; }
ul, ol { padding-left: 1.25rem; }
ul, ol, p { margin-top: 0; margin-bottom: 1rem; }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-sans);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.012em;
  margin-top: 0;
  margin-bottom: 0.75rem;
  color: var(--text);
  /* Balance multi-line headings so wraps are even (no last line that's
     much shorter than the others). Supported in Chrome 114+, Safari 17.4+,
     Firefox 121+; older browsers fall back to default wrapping. */
  text-wrap: balance;
}
::selection { background: var(--accent); color: var(--bg-light); }

/* Skip link
   ============================================================ */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--text);
  color: var(--bg-light);
  padding: 0.75rem 1rem;
  z-index: 100;
  font-family: var(--font-sans);
  font-size: 14px;
  text-decoration: none;
}
.skip-link:focus { left: 0; outline: 2px solid var(--accent); outline-offset: 2px; }

/* Layout
   ============================================================ */
.container,
.container-narrow,
.container-prose {
  margin: 0 auto;
  padding: 0 clamp(2rem, 7vw, 6rem);
}
.container { max-width: var(--container); }
.container-narrow { max-width: var(--container-narrow); }
.container-prose { max-width: var(--container-prose); }

/* Prose-width content block, LEFT-aligned within a wider .container.
   Use inside `.container` (not as a replacement for it) when you want
   reading-width prose that visually aligns with the container's left
   edge — so the prose shares the left edge with a `.container` hero
   above it. Different from `.container-prose`, which centers itself
   inside its parent and produces a column floating in the middle. */
.container > .prose-block {
  max-width: var(--container-prose);
}
/* Wider reading-block variant — same idea as .prose-block (a direct-child
   wrapper inside .container that constrains body content while inheriting
   the container's left edge, so the block aligns with wider section heads
   above and below). Sits between .prose-block (660px, dense reading width)
   and the full .container (1180px). Used on the homepage "Built on a body
   of work" section, where the lede + button row would feel too narrow at
   660px against the wider hero and tile grid that frame them, but a
   full-container 1180px paragraph would lose readability. 960px keeps
   the lede roughly 2 lines at desktop widths without becoming a single
   hard-to-scan line. */
.container > .prose-block-wide {
  max-width: 960px;
}
/* No-side-padding variant — for sections whose inner layout owns its own gutters
   (e.g., archive lists that handle their own indentation) */
.container.container-flush { padding-left: 0; padding-right: 0; }
/* Centered lede — text-align center plus narrower max-width, used for one-off
   centered intros above wide content blocks */
.lede-centered { text-align: center; max-width: 640px; margin: 0 auto var(--space-5); }
section { padding: var(--section-padding) 0; }
.section-tight { padding: var(--space-5) 0; }    /* 40px (was 48px) — Option D pass 2026-05-12 */
/* Body-of-work button row needs extra scroll runway below it.
   main.js initRevealOnScroll() finishes the scroll-scrub animation
   when the button row's top reaches 65% from the viewport top —
   meaning the page must have ~35% of viewport-height of scrollable
   content below the row to complete the animation. With only
   .section-tight's 40px bottom padding plus the footer, taller-but-
   not-tall-enough viewports were running out of scroll before the
   buttons reached their final position. 14vh floor gives 100-160px
   of extra runway across common desktop viewport heights without
   leaving the page looking visually empty. Mobile gets a smaller
   value because the wrapped multi-row button stack and shorter vh
   already provide enough runway. */
.body-of-work-section { padding-bottom: max(2.5rem, 14vh); }
@media (max-width: 720px) {
  .body-of-work-section { padding-bottom: var(--space-5); }
}
/* Anchor-offset for sticky header. When a hash fragment (/about#firm,
   /about#gregory-c-allen, etc.) is the scroll target, the browser
   natively scrolls so the element's top edge sits at viewport-top --
   which on this site puts the heading underneath the fixed
   .site-header. scroll-margin-top adds a top buffer when the element
   is being scrolled-to, so the section starts below the header.
   Applied to any <section> with an id (which is the merged-About
   page's anchored sections, and any future anchored section pattern).

   Tuned 2026-05-12 (five rounds). The final value tucks the section
   box partially under the sticky header so the section's own
   padding-top is the visible breathing room. With header ~104 px and
   section padding-top 72 px (desktop default), scroll-margin-top
   88 px gives a content position of 88 + 72 = 160 px from viewport
   top, which is 56 px below the 104-px header bottom — matching the
   gap the first-section hero already had before its inline padding-
   top: 3 rem override was removed in the same commit. Earlier rounds
   at 120, 144, 128, and 112 left progressively-too-much-but-still-
   too-much empty space below the header. Letting the section box
   slide partially under the sticky header is the correct geometry
   when sticky-header height and section-padding compound. */
section[id] {
  scroll-margin-top: 88px;
}
.section-bg-light { background: var(--bg-light); }
.section-bg-tint { background: var(--bg-tint); }
.section-bg-dark { background: var(--bg-dark); color: var(--text-on-dark); }
.section-bg-dark h1, .section-bg-dark h2, .section-bg-dark h3 { color: var(--text-on-dark); }
.section-divider { border-top: 0.5px solid var(--border); }

/* Typography helpers
   ============================================================ */
.eyebrow {
  font-family: var(--font-sans);
  font-size: 12.5px;
  letter-spacing: 0.14em;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--accent-text);
  display: inline-block;
  margin-bottom: 0.75rem;
}
.eyebrow-muted { color: var(--text-eyebrow); }

h1, .h1 { font-size: clamp(var(--fs-h2), 5vw, var(--fs-h1)); }
h2, .h2 { font-size: clamp(var(--fs-h4), 4vw, var(--fs-h2)); }
h3, .h3 { font-size: clamp(var(--fs-h5), 3vw, var(--fs-h3)); }
h4, .h4 { font-size: var(--fs-h4); }
h5, .h5 { font-size: var(--fs-h5); }
h6, .h6 { font-size: var(--fs-h6); }

.lede {
  font-size: 1.125rem;
  line-height: 1.55;
  color: var(--text-muted);
  margin-bottom: 1.5rem;
}

/* Avoid widows on body paragraphs and list items — single-word last lines
   like "...within each / filter." `text-wrap: pretty` is supported in
   Chrome 117+, Safari 17.4+, and Firefox 121+ (all > 1 year old as of
   May 2026). Browsers without support fall back to default wrapping.
   Applied globally to p / li / .lede so every reading-width block gets
   the treatment, not just the homepage and topic-page ledes. */
p, li, .lede { text-wrap: pretty; }
.muted { color: var(--text-muted); }
.small { font-size: 0.875rem; }
.smaller { font-size: 0.8125rem; }
.label-sans {
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.05em;
  color: var(--accent-text);
}

/* Buttons
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.01em;
  border: 0.5px solid transparent;
  border-radius: var(--radius);
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  transition: all 140ms ease;
  white-space: nowrap;
}
.btn-primary {
  background: var(--accent-text);
  color: var(--bg-light);
  border-color: var(--accent-text);
}
/* Hover/focus: pronounced darken + 2px lift + drop shadow. The .btn
   base supplies the transition timing (140ms ease). filter:
   brightness() darkens the whole element (background + border) without
   introducing a new color token; the white text inside picks up the
   same brightness shift and stays well above WCAG AA against the
   darker green. translateY matches the existing 2px lift convention
   from .highlight-photo and .cover-card. box-shadow adds visible
   depth so the lift reads as deliberate rather than as a 1px jitter.
   Tuned 2026-05-12 from a subtler initial pass (brightness 0.92 / 1px
   lift / no shadow) -- the earlier values were imperceptible. */
.btn-primary:hover, .btn-primary:focus {
  /* color: explicit override of the generic a:hover rule at line 207
     (color: var(--accent-text)) which would otherwise paint the text
     the same forest green as the button background, making it invisible.
     Caught 2026-05-16 when Greg reported the Newsletter button losing
     its text on hover in the body-of-work row. */
  color: var(--bg-light);
  filter: brightness(0.75);
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(7, 36, 32, 0.22);
}
.btn-outline {
  background: transparent;
  color: var(--accent-text);
  border-color: var(--accent-text);
}
.btn-outline:hover, .btn-outline:focus {
  background: var(--accent-text);
  color: var(--bg-light);
}
.btn-text {
  background: transparent;
  color: var(--accent-text);
  border: none;
  padding: 0.75rem 0.5rem;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.btn-text:hover { color: var(--accent-text); }
/* On-dark variant of the primary CTA — used on the Stay Current
   subscribe band's "Subscribe ↗" button (and any other primary CTA
   placed on a dark teal surface). Background stays the bright --accent
   green so the button pops against the dark band, but the inherited
   white text color from .btn-primary fails WCAG AA at 2.5:1. Override
   the text color to --text (dark teal #072420) which clears AA at
   7.55:1 against the bright green and matches the pattern used on the
   header's .nav-cta-primary pill (also dark text on bright green).
   Hover state keeps dark text and uses --accent-dark for a subtle
   button-darken effect (5.7:1 dark-on-darker-green). */
.btn-on-dark.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text);
}
.btn-on-dark.btn-primary:hover,
.btn-on-dark.btn-primary:focus {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
  color: var(--text);
}
.btn-on-dark.btn-outline { color: var(--text-on-dark); border-color: var(--text-on-dark-muted); }
.btn-on-dark.btn-outline:hover { background: var(--text-on-dark); color: var(--bg-dark); }
.btn-block { display: flex; width: 100%; }
.btn-large { padding: 0.95rem 1.5rem; font-size: 15px; }
.btn-sm { padding: 0.45rem 0.9rem; font-size: 13px; }

/* Site header / nav
   ============================================================ */
.site-header {
  position: sticky;
  top: 0;
  /* z-index: 60 — must sit above the mobile .site-nav overlay (z-index
     50) so the hamburger toggle (auto-transforms to an X via the
     .mobile-toggle[aria-expanded="true"] rules) stays visible and
     tappable when the mobile menu is open. Caught 2026-05-16: prior
     z-index 50 was the same as the overlay, and DOM order put the
     overlay on top, hiding the close affordance entirely. */
  z-index: 60;
  background: var(--bg-dark);
  border-bottom: 0.5px solid var(--border-dark);
}
.site-header.scrolled { background: var(--bg-dark-2); }
.site-header-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 1rem;
  padding-bottom: 1rem;
  /* Override the body container's max-width and padding so the logo can hug
     the left edge (SemiAnalysis-style) rather than sitting inside the 1180px
     content column. */
  max-width: 1640px;
  padding-left: clamp(1rem, 2.5vw, 2.5rem);
  padding-right: clamp(1rem, 2.5vw, 2.5rem);
}
.site-logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: var(--text-on-dark);
  /* Belt-and-suspenders: do not let the flex parent .site-header-inner
     shrink the logo to make room for nav items. The logo holds its full
     natural width; the hamburger collapse (see the @media (max-width:
     1100px) rules below) is what gives way for narrower viewports.
     Without this, on viewports between ~1000-1100px the nav items were
     pushing the logo to be flex-shrunk down to a sliver or off-screen
     (caught 2026-05-13 in Greg's resize series). */
  flex-shrink: 0;
  /* The horizontal logo SVG carries its own dark teal background, which matches the header. */
  line-height: 0;
}
.site-logo:hover { opacity: 0.9; }
.site-logo-mark {
  /* Override the global img { height: auto } — SVGs in <img> can fall back to
     a 2:1 default intrinsic ratio without explicit width/height attrs.
     Sized 2026-05-13: bumped from 72px to 88px at the default tier — the
     prior 72px sat smaller than the surrounding nav links + pill CTAs at
     desktop widths and read as proportionally diminished against the brand
     wordmark we just spent time making render correctly via flattened SVG
     paths. The hamburger collapse at ≤980px (see .site-nav rules below)
     is what gives way for narrower widths, not the logo. */
  height: 88px !important;
  width: calc(88px * (1080 / 280));  /* preserve 1080:280 aspect ≈ 339px */
  display: block;
  object-fit: contain;
}
/* Header logo minimum-size floor. The logo stays at the default 88px down
   through tablet/medium viewports — the hamburger collapse at ≤980px is
   what makes room as widths get tighter, not the logo. Only at the very
   narrowest phone viewports (≤480px) does the logo drop to its minimum
   of 64px, which is still meaningfully larger than the previous 48px
   floor. At a 375px viewport this leaves 247px logo + 48px hamburger
   toggle = ~295px in a ~311px content area (375px viewport - 32px
   horizontal padding), which fits without overflow. Per Greg's
   2026-05-13 review: the logo should have a minimum size beneath which
   it does not shrink; other responsive effects (hamburger, reduced
   header options) absorb the width budget instead. */
@media (max-width: 480px) {
  .site-logo-mark {
    height: 64px !important;
    width: calc(64px * (1080 / 280));
  }
}

/* Footer brand mark */
.footer-copyright-line {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
/* The "Founded by Gregory C. Allen" byline below the wordmark sits in
   the dark-teal footer band. The base .muted color (--text-muted)
   is tuned for cream surfaces and measures only ~3.1:1 on the darkest
   teal #051917 — below WCAG AA 4.5:1 for normal text. Override to use
   --text-on-dark-muted (the brand cream), which clears AA at 13:1
   against the footer surface. */
.footer-copyright-line .muted { color: var(--text-on-dark-muted); }
.footer-mark {
  /* Aspect ratio 230:160 ≈ 1.4375 — matches the official horizontal logo. */
  height: 26px !important;
  width: calc(26px * (230 / 160)) !important;  /* ≈ 37px */
  flex-shrink: 0;
  display: block;
  object-fit: contain;
}

.site-nav {
  display: flex;
  gap: 1.75rem;        /* bumped 2026-05-13 from 1.5rem for slightly more horizontal breathing room between nav items */
  align-items: center;
}
.site-nav a {
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 400;
  color: var(--text-on-dark);
  text-decoration: none;
  padding: 0.5rem 0.75rem;          /* horizontal padding added 2026-05-13 to give the hover-pill background room */
  border-radius: 999px;             /* matches the .nav-cta pill silhouette so the hover background reads as a button */
  position: relative;
  transition: background 140ms ease, color 140ms ease;
}
.site-nav a.nav-cta,
.site-nav a.nav-cta-primary {
  padding: 0.75rem 1.5rem;
  border-radius: 999px;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;  /* never let "Newsletter ↗" or "Podcast" wrap onto two lines */
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease, transform 140ms ease, box-shadow 200ms ease;
}
.site-nav a {
  white-space: nowrap;  /* same — keep all nav links to one line */
}
/* Outlined CTA (Podcast) */
.site-nav a.nav-cta {
  border: 1.5px solid var(--accent);
  color: var(--accent);
}
.site-nav a.nav-cta:hover,
.site-nav a.nav-cta.active {
  background: var(--accent);
  color: var(--bg-dark);
  transform: translateY(-1px);
}
/* Filled CTA (Newsletter, Podcast) */
.site-nav a.nav-cta-primary {
  background: var(--accent);
  color: var(--bg-dark);
  border: 1.5px solid var(--accent);
  box-shadow: 0 0 0 0 rgba(103, 192, 112, 0);
}
.site-nav a.nav-cta-primary:hover {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
  /* Keep dark text (was --text-on-dark) so the hovered pill still clears
     WCAG AA 4.5:1 — white on #4FA85B is only 2.95:1; dark teal on #4FA85B
     is 5.74:1. Matches the rest-state dark-on-bright pattern; pill just
     darkens slightly on hover. */
  color: var(--bg-dark);
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(103, 192, 112, 0.35);
}
/* Active state on filled CTA — looks like a "pressed" version, never invisible.
   Text color is --bg-dark (dark teal) rather than --text-on-dark (white) so
   the active pill clears WCAG AA 4.5:1 on the --accent-dark background
   (5.74:1 dark-on-darker-green vs the failing 2.95:1 white-on-darker-green).
   Same fix as the :hover state above. */
.site-nav a.nav-cta-primary.active {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
  color: var(--bg-dark);
}
/* Suppress the underline on .active when it's also a CTA. The
   .nav-cta-primary.active selector is specificity (0,3,2) so it beats
   the (0,2,2) underline rule below — without it, the green pill button
   gets a redundant underline on the page it links to. */
.site-nav a.nav-cta.active::after,
.site-nav a.nav-cta-primary::after,
.site-nav a.nav-cta-primary.active::after { content: none; }
.site-nav a:hover {
  color: var(--accent);
  background: rgba(255, 255, 255, 0.08);  /* subtle hover pill revealing the clickable area, SemiAnalysis-style */
}
.site-nav a.active {
  color: var(--accent);
}
.site-nav a.active::after {
  content: "";
  position: absolute;
  /* Underline tracks the text, not the full padded pill width — left/right
     offsets match the .site-nav a horizontal padding so the underline sits
     under the link text rather than under the hover-pill silhouette. */
  left: 0.75rem; right: 0.75rem;
  bottom: -2px;
  height: 1px;
  background: var(--accent);
}

/* About dropdown — disclosure-style menu in the primary nav.
   Trigger is a <button> with aria-expanded; panel is a <ul> with
   role="menu" and role="menuitem" links inside. Added 2026-05-12 as
   Stage 2 of the About + Founder bio merge. The trigger matches the
   visual treatment of regular .site-nav a items so the dropdown reads
   as just another nav item until hovered/focused. */
.nav-dropdown {
  position: relative;
}
.nav-dropdown-trigger {
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 400;
  color: var(--text-on-dark);
  background: none;
  border: 0;
  padding: 0.5rem 0.75rem;       /* matches .site-nav a so the hover pill is consistent across the row */
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  position: relative;
  transition: background 140ms ease, color 140ms ease;
}
.nav-dropdown-trigger:hover,
.nav-dropdown-trigger:focus-visible,
.nav-dropdown-trigger[aria-expanded="true"] {
  color: var(--accent);
  background: rgba(255, 255, 255, 0.08);   /* same hover pill as .site-nav a */
}
.nav-dropdown-trigger.active { color: var(--accent); }
.nav-dropdown-trigger.active::after {
  content: "";
  position: absolute;
  /* Track the word "About" — left padding matches the trigger's horizontal
     padding, right offset excludes the caret width plus the same horizontal
     padding so the underline sits under the text only. */
  left: 0.75rem; right: calc(0.75rem + 16px);
  bottom: -2px;
  height: 1px;
  background: var(--accent);
}
.nav-dropdown-caret {
  transition: transform 140ms ease;
  flex: 0 0 auto;
}
.nav-dropdown-trigger[aria-expanded="true"] .nav-dropdown-caret {
  transform: rotate(180deg);
}
/* Desktop panel — absolute-positioned dropdown below the trigger.
   Hidden by default via [hidden]; CSS-revealed when the trigger has
   aria-expanded="true" (state managed in JS so hover and keyboard
   stay in sync). Hover on .nav-dropdown also opens the panel for the
   mouse-driven user; the JS observes the same state. */
.nav-dropdown-panel {
  position: absolute;
  /* Flush with the trigger's bottom so mouse movement from trigger
     into panel stays inside .nav-dropdown's hit area (no mid-gap
     mouseleave that snaps the panel shut). */
  top: 100%;
  left: 0;
  min-width: 224px;
  list-style: none;
  margin: 0;
  padding: 0.5rem 0;
  background: var(--bg-dark);
  border: 0.5px solid rgba(221, 202, 171, 0.18);  /* subtle cream-tinted hairline */
  box-shadow: 0 12px 32px rgba(7, 36, 32, 0.35);
  z-index: 60;
}
.nav-dropdown-panel[hidden] { display: none; }
.nav-dropdown-panel li { margin: 0; }
.nav-dropdown-panel a {
  display: block;
  padding: 0.5rem 1.25rem;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 400;
  color: var(--text-on-dark);
  text-decoration: none;
  white-space: nowrap;
  transition: background 100ms ease, color 100ms ease;
}
.nav-dropdown-panel a:hover,
.nav-dropdown-panel a:focus-visible {
  color: var(--accent);
  background: rgba(103, 192, 112, 0.08);
  outline: 0;
}

.mobile-toggle {
  display: none;
  width: 28px; height: 28px;
  position: relative;
  z-index: 60;
}
.mobile-toggle span,
.mobile-toggle span::before,
.mobile-toggle span::after {
  display: block;
  position: absolute;
  height: 1px;
  width: 22px;
  background: var(--text-on-dark);
  transition: transform 200ms ease, top 200ms ease, opacity 100ms ease;
}
.mobile-toggle span { top: 50%; left: 3px; }
.mobile-toggle span::before { content: ""; top: -7px; left: 0; }
.mobile-toggle span::after { content: ""; top: 7px; left: 0; }
.mobile-toggle[aria-expanded="true"] span { background: transparent; }
.mobile-toggle[aria-expanded="true"] span::before { top: 0; transform: rotate(45deg); }
.mobile-toggle[aria-expanded="true"] span::after { top: 0; transform: rotate(-45deg); }

/* Hero
   ============================================================ */
.hero {
  /* Padding 2026-05-12: bottom reduced from --space-12 (96px) to --space-10
     (80px) as part of the Option D vertical-rhythm pass. Top stays at
     --space-8 (64px) — already moderate, sits cleanly below the sticky nav. */
  padding: var(--space-8) 0 var(--space-10);
}
.hero-grid {
  display: grid;
  grid-template-columns: 1fr 1.1fr;  /* portrait gets slight visual edge over text */
  gap: var(--space-6);
  align-items: center;
}
/* Founder bio uses a magazine-style float layout (not .hero-grid):
   - .founder-hero is the full-width eyebrow + H1 stack at the top
   - .founder-body holds the bio prose with the portrait floated to the right
   - On the wrap side, prose flows around the portrait at the top and reclaims
     full width once the prose extends past the portrait's bottom edge
   The earlier 2-column grid layout pushed the portrait halfway down the page
   on the long bio because grid columns are rigid; this float-based layout
   lets the prose use the right-side space below the portrait. */
.founder-hero {
  margin-bottom: var(--space-5);
}
.founder-body {
  /* clearfix so the surrounding section grows to contain the floated portrait */
}
.founder-body::after {
  content: '';
  display: block;
  clear: both;
}
.founder-portrait {
  float: right;
  width: 400px;
  max-width: 45%;
  margin: 0 0 var(--space-4) var(--space-5);
}
.founder-portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 22%;
}
/* Portrait is wrapped in an anchor to /photos.html as a discovery
   path into the photo gallery. Hover/focus uses a subtle opacity
   shift rather than scale or overlay — portrait stays in documentary
   register. focus-visible outline uses the brand text-green so
   keyboard navigation is obvious without competing with portrait. */
.founder-portrait-link {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: opacity 0.2s ease;
}
.founder-portrait-link:hover,
.founder-portrait-link:focus-visible {
  opacity: 0.92;
}
.founder-portrait-link:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
}
/* Semafor pull-quote rendered as the portrait's <figcaption>.
   Replaces the standalone .quote-band section (retired 2026-05-12).
   Magazine-caption styling: snug under the photo with no hairline
   divider, type slightly smaller than the section-level .pull,
   left-aligned with the photo edge rather than centered. FT / NYT
   photo-caption treatment — caption reads as part of the photo's
   composition, not a separate labeled box. */
.founder-portrait-quote {
  margin: 0.5rem 0 0 0;
  padding-left: 1em;     /* Slight hang from the photo's left edge — caption sits inset by ~1 character */
  text-align: left;
}
.founder-portrait-quote blockquote.pull {
  font-size: 1rem;        /* ~16px, was 1.18rem in band */
  line-height: 1.4;
  margin: 0 0 0.25rem 0;
}
.founder-portrait-quote .quote-cite {
  display: block;
  font-size: 12px;
}
/* Mobile: drop the float so the portrait stacks above the bio prose instead of
   squeezing alongside it on narrow viewports. */
@media (max-width: 768px) {
  .founder-portrait {
    float: none;
    width: 100%;
    max-width: 280px;
    margin: 0 auto var(--space-4);
  }
}
.hero h1 {
  /* Capped at h3 (40px) — was h2 (48px) until 2026-05-16. The homepage
     hero now nests the press-endorsement band between H1 and the
     subscribe lede/form, which makes the whole hero composition tall;
     dropping the max-clamp endpoint from 48px to 40px shrinks the H1
     line-count from ~4 to ~3 on desktop and brings the subscribe form
     closer to above-the-fold. Display size (72px) remains off the
     table — these hero headlines are long (10+ words) and forcing
     display size would over-wrap. Lower bound stays at h4 (32px) so
     the headline doesn't ride the viewport edge on a 393pt iPhone
     column where 40px would give ~3 words per line. */
  font-size: clamp(var(--fs-h4), 4vw, var(--fs-h3));  /* 32–40px */
  line-height: 1.15;
  margin-bottom: var(--space-4);  /* 32px — bumped 2026-05-13 from --space-3 (24px) for more visible breathing room before the hero lede */
}
.hero-lede {
  font-size: var(--fs-h6);  /* 20px — one step above body */
  line-height: 1.55;
  color: var(--text-muted);
  margin-bottom: var(--space-4);
}
.hero-ctas {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  align-items: center;
}
/* Hero portrait is a real <img> (not a background-image div) — iOS Safari
   has bugs computing aspect-ratio on a background-only div in single-column
   grid, which collapsed the portrait to 0×0 on mobile. <img> has natural
   dimensions from the file, which renders predictably everywhere. */
.hero-portrait {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 22%;
  background-color: var(--border);
}
.hero-portrait-frame {
  position: relative;
}

/* Single-column hero — used on the homepage where the cascade animation
   lives in a separate full-bleed banner above this section. Drops the
   two-column .hero-grid layout in favor of a stack of text elements
   (eyebrow / H1 / lede / subscribe form). The TEXT inside is left-aligned
   (Rhodium-style) so each line begins at the same left edge and the H1
   reads as a paragraph rather than a stanza. The text stack fills its
   parent .container (1180px max) rather than capping at a narrower
   reading-width — without that, the headline column reads as visibly
   narrower than the Areas-of-focus tile row below (which uses a
   1357px-wide container, see .featured-topics .container) and the page
   feels misaligned. The class name "hero-centered" is retained for now
   to avoid churn across build.py and any external references; it refers
   to the block's historical horizontal centering, not to text alignment. */
.hero-centered .hero-text-stack {
  text-align: left;
}
.hero-centered .hero-text-stack .hero-ctas {
  display: flex;
  justify-content: flex-start;
}
.hero-centered .hero-text-stack .newsletter-quick {
  max-width: 480px;
  margin: 0;
}

/* Founder byline below the homepage hero H1. Plex Serif italic at
   ~18px with muted color — magazine-byline register, subordinate
   to the H1, distinct from the hero-lede that lives below the press
   band. The :has() override on the H1 tightens the H1's
   margin-bottom to 8px when the byline immediately follows, so the
   H1+byline reads as a tightly-paired unit (title + attribution)
   rather than two independent lines with the H1's normal 32px gap. */
.hero h1:has(+ .hero-byline) {
  margin-bottom: var(--space-2);  /* 16px when paired with byline */
}
.hero-byline {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.125rem;  /* 18px */
  line-height: 1.4;
  color: var(--text-muted);
  margin: 0;  /* press-band below provides its own top margin */
}

/* Press endorsements nested inside the homepage hero, between the
   firm-description H1 + founder byline and the subscribe ask
   (lede + form). Visually distinct from a standalone .quote-band:
   no contrasting background (sits on the hero's cream like the H1),
   top + bottom hairline rules to demarcate, smaller italic body
   text (these are inline supporting evidence, not section-level
   pull quotes). Three quotes in a 3-column grid on desktop; stacks
   to 1-column at <760px. The 2-column intermediate was rejected
   2026-05-16 because three items in a 2-col grid orphan the third
   on its own row. Stood up 2026-05-16 to replace the earlier
   standalone .quote-band section in this position. */
.hero-press-band {
  margin: 2rem 0;
  padding: 1.5rem 0;
  border-top: 0.5px solid var(--border-strong);
  border-bottom: 0.5px solid var(--border-strong);
}
.hero-press-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 2rem;
}
.hero-press-grid > figure {
  margin: 0;
  padding-right: 2rem;
  border-right: 0.5px solid var(--border-strong);
}
.hero-press-grid > figure:last-child {
  padding-right: 0;
  border-right: 0;
}
/* Typography matches the standalone blockquote.pull + .quote-cite
   treatment (1.18rem italic Plex / 13px sans Jost / 0.05em letter-
   spacing on the attribution) so press endorsements read at a
   consistent register whether they appear inline in the hero or
   in a standalone band. Earlier 1rem / 12px sizing read too small
   against the H1 above. */
.hero-press-grid blockquote {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.18rem;
  line-height: 1.4;
  margin: 0;
  color: var(--text);
  text-wrap: balance;
}
.hero-press-grid figcaption {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-muted);
  letter-spacing: 0.05em;
  margin-top: 0.5rem;
}
@media (max-width: 759px) {
  .hero-press-grid {
    grid-template-columns: 1fr;
    gap: 1.25rem;
  }
  .hero-press-grid > figure {
    padding-right: 0;
    padding-bottom: 1.25rem;
    border-right: 0;
    border-bottom: 0.5px solid var(--border-strong);
  }
  .hero-press-grid > figure:last-child {
    padding-bottom: 0;
    border-bottom: 0;
  }
}

/* Featured topic tiles — three prominent practice areas displayed as a
   row of cards immediately under the homepage hero. Replaces the previous
   8-topic "Explore by topic" tile grid. Icons render at 56px in the brand
   accent color via currentColor. The other 5 topics are still reachable
   via the writing-archive and press-archive filter chips. */
/* Areas of focus tiles, Rhodium-style: photographic cover image at top
   with a colored badge label overlaid in the bottom-left of the image,
   and a description paragraph below the image. The whole tile is a
   single anchor element so the image, badge, and blurb all click into
   the topic page. */
.featured-topics-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.75rem;
  margin-top: 1.5rem;
}
/* Areas of focus tiles. As of 2026-05-11 these are display-only blocks
   (image + blurb) rather than navigation — the per-topic landing pages
   were removed and the chip-filtered archive views became the canonical
   per-topic surface. The text-decoration / color resets were originally
   for the <a> form and are kept as defensive defaults in case the tiles
   become clickable again (they may; see CLAUDE.md "Per-topic landing
   pages were removed"). The hover zoom on the cover image is kept as a
   light visual flourish on mouseover — it doesn't signal clickability
   strongly enough to be misleading on the current non-interactive tiles,
   and is the right interaction the day the tiles become anchors again. */
.featured-tile {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
.featured-tile-cover {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.featured-tile-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s ease;
}
.featured-tile:hover .featured-tile-cover img,
.featured-tile:focus-within .featured-tile-cover img {
  transform: scale(1.04);
}
/* Respect users who've asked for reduced motion — no zoom for them. */
@media (prefers-reduced-motion: reduce) {
  .featured-tile-cover img { transition: none; }
  .featured-tile:hover .featured-tile-cover img,
  .featured-tile:focus-within .featured-tile-cover img { transform: none; }
}
.featured-tile-blurb {
  font-size: 15px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 1rem 0 0 0;
}

/* Areas of focus eyebrow — Rhodium-style small caption-as-heading,
   replacing the old large H2. Kept as an <h2> for document outline /
   accessibility while visually rendering as the existing .eyebrow
   token (uppercase, small, green). */
.featured-topics .areas-eyebrow {
  display: block;
  margin: 0 0 0.75rem 0;
  /* let the existing .eyebrow rule handle font / case / color */
}

/* Tighten the vertical gap between the homepage subscribe band and the
   Areas of focus section. The cumulative bottom-padding of the hero +
   top-padding of the tan band was leaving a ~250px cream void; trim
   each side ~30%. */
.featured-topics {
  padding-top: 3.5rem;
  padding-bottom: 3.5rem;
}
/* Bump the Areas-of-focus section's inner container 15% wider than the
   default --container (1180px → ~1357px, via --container-wide). Each tile
   sits in a 1fr grid column, so a 15%-wider container makes each tile 15%
   wider — and because the cover image uses aspect-ratio: 16/9, the image
   height auto-scales by the same 15%. Net effect: the three Areas-of-focus
   pictures render visibly larger without changing the source files, the
   description copy below, or the rest of the page. Scoped to this section
   so the wider container doesn't leak to the hero / quote band / Built-on-
   a-body-of-work band, which stay at --container. */
.featured-topics .container {
  max-width: var(--container-wide);
}

/* Tablet / narrow-laptop: 3 columns get cramped below ~960px. Drop to a
   2-column layout (Rhodium-style) so each tile keeps a comfortable line
   length for the blurb. */
@media (max-width: 960px) {
  .featured-topics-grid { grid-template-columns: repeat(2, 1fr); gap: 1.25rem; }
}
/* Phone: collapse to a single column once even 2 wide tiles get too narrow
   to fit the title + blurb without ugly word-wrap. */
@media (max-width: 560px) {
  .featured-topics-grid { grid-template-columns: 1fr; gap: 1rem; }
  .featured-tile { padding: 1.5rem 1.25rem; }
}

/* Page hero (smaller, single column) */
.page-hero {
  padding: 3.5rem 0 2.5rem;
  border-bottom: 0.5px solid var(--border);
}
.page-hero h1 { margin-bottom: 0.75rem; }
.page-hero .lede { max-width: 660px; }
/* Wider page-hero variant — the lede sits at 960px instead of the default
   660px reading width. Used on pages where the hero lede should visually
   align with body sections that use .prose-block-wide (960px) rather than
   the narrower .prose-block (660px) — currently the About page. Higher
   specificity than .page-hero .lede so it wins regardless of source order. */
.page-hero.page-hero-wide .lede { max-width: 960px; }

/* Logo wall band
   ============================================================ */
.logo-wall {
  background: var(--bg-light);
  border-top: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  padding: 2rem 0 2.25rem;
}
.logo-wall .eyebrow {
  display: block;
  text-align: center;
  color: var(--text-eyebrow);
  margin-bottom: 1rem;
}
.logo-wall-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 1.5rem 2.25rem;
}
/* Outlet-mark variants are tuned for visual parity, not for a single
   nominal size. Italic Plex Serif 18px is the baseline; bold serif and
   sans are matched by visual presence rather than by em-size.

   Retuned twice in 2026-05-12. First pass (sans 13.5px / bold 16px)
   undersized the sans outlets dramatically -- the original "step down
   to compensate for sans visual heaviness" assumption was wrong for
   this pairing. Second pass (sans 15.5px / bold 17px) was based on
   x-height equality, which still undersold the sans outlets because
   most of them are all-caps (NPR, BBC, CNBC, CNN, TIME, WIRED) and
   the relevant comparison is cap-height, not x-height. Final pass
   matches sans at the italic's nominal 18px so caps land at roughly
   equal height to the italic serif's lowercase, accepting a small
   "lowercase sans looks slightly taller than lowercase italic" trade
   for the mixed-case outlets (Politico, Fox News, Fox Business). */
.outlet-mark {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 18px;
  color: var(--text-muted);
  white-space: nowrap;
  letter-spacing: -0.005em;
}
.outlet-mark-sans {
  font-family: var(--font-sans);
  font-style: normal;
  font-weight: 500;
  letter-spacing: 0.05em;
  font-size: 18px;
}
.outlet-mark-bold {
  font-family: var(--font-serif);
  font-style: normal;
  font-weight: 500;
  font-size: 17px;
}

/* Pull quote band
   ============================================================ */
.quote-band {
  background: var(--bg-tint);
  padding: 2.75rem 0;
}
/* Cream variant — used immediately after a section-bg-tint band (e.g.,
   the homepage Areas of focus → quote pair) so the section seam is
   actually visible. Without this, two consecutive tan bands read as one
   giant empty void. See CLAUDE.md: "Adjacent sections sharing background
   color = invisible boundary." */
.quote-band.quote-band-cream { background: var(--bg); }
/* Section-label eyebrow inside the quote band centers over the 2-column
   grid below. The default .eyebrow is inline-block + left-aligned,
   which works as a "kicker" above an H2 but reads as left-weighted
   when it's the band's only header. Block + center + extra margin-
   bottom give it presiding-header treatment. Scoped to .quote-band so
   the rest of the site's eyebrow conventions are untouched.

   The padding-right: 3rem shifts the centered text 1.5rem to the left
   so it aligns over the column-divider rule (which sits at the right
   edge of the left column, 1.5rem left of the container's true center
   because of the 3rem grid gap). With text-align: center inside a
   box-sizing: border-box block, padding-right asymmetrically narrows
   the content area, pulling the centered text leftward by half the
   padding value. Reset in the 980px breakpoint where the grid collapses
   to a single column and the divider goes away. */
.quote-band .eyebrow {
  display: block;
  text-align: center;
  padding-right: 3rem;
  margin-bottom: 1.5rem;
}
.quote-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
}
.quote-grid > div { padding-right: 1.5rem; border-right: 0.5px solid var(--border-strong); }
.quote-grid > div:last-child { border-right: 0; }
/* Facing-pages magazine layout: the left column right-aligns toward
   the divider so the two quotes mirror each other across the vertical
   rule. Both columns get the same gap from the divider (3rem) -- left
   column via padding-right, right column via the grid gap. Reset in
   the 980px breakpoint where the grid collapses to a single column --
   right-alignment on a lone stacked quote reads as an accident rather
   than as a deliberate mirror.
   Note (2026-05-16): the .quote-band/.quote-grid markup is currently
   unused on the homepage — the press endorsements moved into the
   hero composition as .hero-press-band. The CSS is retained for any
   future surface that wants a standalone 2-quote band. */
.quote-grid > div:first-child {
  text-align: right;
  padding-right: 3rem;
}
blockquote.pull {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.18rem;
  line-height: 1.4;
  color: var(--text);
  margin: 0 0 0.75rem;
  text-wrap: balance;
}
.quote-cite {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-muted);
  letter-spacing: 0.05em;
}

/* Stat callout band
   ============================================================ */

/* Card grid (writing, podcast, recent work)
   ============================================================ */
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 1.5rem;
}
.section-head h2 { margin-bottom: 0; }
.section-head a:not(.btn) {
  font-family: var(--font-sans);
  font-size: 14px;
  text-decoration: none;
  letter-spacing: 0.02em;
  color: var(--accent-text);
  font-weight: 500;
}
.section-head a:not(.btn):hover { color: var(--accent-text); }

.cards-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
}

.card {
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  transition: border-color 160ms ease, transform 160ms ease;
}
.card:hover { border-color: var(--text-muted); }
.card-thumb {
  aspect-ratio: 4 / 3;
  background: var(--border);
  margin-bottom: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
}
.card-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
  letter-spacing: 0.03em;
  margin-top: auto;
  padding-top: 0.5rem;
}
.card-type {
  font-weight: 500;
  letter-spacing: 0.05em;
}
.card-type-report,
.card-type-article,
.card-type-commentary { color: var(--accent-text); }         /* forest green (text-safe) */
.card-type-oped,
.card-type-criticalquestions,
.card-type-translation { color: var(--accent-2); }            /* gold */
.card-type-testimony,
.card-type-podcast,
.card-type-event,
.card-type-interview,
.card-type-talk { color: var(--accent-4); }                   /* deep forest */

.card-title {
  font-family: var(--font-sans);
  font-size: 1.05rem;
  line-height: 1.3;
  font-weight: 500;
  margin-top: 0.25rem;
  margin-bottom: 0.5rem;
  color: var(--text);
}
.card-summary {
  font-family: var(--font-serif);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text-muted);
  margin-bottom: 0.5rem;
  flex-grow: 1;
}

/* Publications archive — single-column rows
   ============================================================ */
.writing-rows {
  display: flex;
  flex-direction: column;
}
.writing-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 140px;
  gap: 2.25rem;
  align-items: start;
  /* Horizontal padding + negative margin so the row's content aligns with
     the surrounding column on non-hovered cards, but the :hover background
     extends 1rem past the content on each side. */
  padding: 1.75rem 1rem;
  margin: 0 -1rem;
  border-bottom: 0.5px solid var(--border);
  border-radius: 4px;
  text-decoration: none;
  color: inherit;
  transition: background 140ms ease;
}
.writing-row:first-child { padding-top: 0.75rem; }
.writing-row:hover {
  background: rgba(255, 255, 255, 0.55);
}
.writing-row-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.writing-row-eyebrow {
  font-family: var(--font-sans);
  font-size: 12.5px;
  letter-spacing: 0.14em;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: 0.5rem;
}
.writing-row-title {
  font-family: var(--font-sans);
  font-size: 1.28rem;
  line-height: 1.25;
  font-weight: 500;
  color: var(--text);
  margin: 0 0 0.75rem;
  text-wrap: balance;
}
.writing-row:hover .writing-row-title {
  color: var(--accent-text);
}
.writing-row-summary {
  font-family: var(--font-serif);
  font-size: 0.98rem;
  line-height: 1.55;
  color: var(--text-muted);
  margin: 0 0 0.75rem;
  max-width: 56rem;
}
.writing-row-meta {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 13.5px;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  margin-top: auto;
  flex-wrap: wrap;
}
.writing-row-publisher {
  color: var(--text);
  font-weight: 500;
  letter-spacing: 0.01em;
}
.writing-row-dot { color: var(--text-eyebrow); }
.writing-row-date { letter-spacing: 0.01em; }

/* Right-side image / icon tile */
.writing-row-thumb {
  width: 140px;
  aspect-ratio: 8.5 / 11;
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  background-size: cover;
  background-position: center top;
  flex-shrink: 0;
  align-self: start;
  transition: box-shadow 200ms ease, transform 200ms ease;
}
.writing-row:hover .writing-row-thumb {
  box-shadow: 0 8px 22px rgba(7, 36, 32, 0.12);
  transform: translateY(-2px);
}
.writing-row-thumb-icon {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  background: var(--bg-dark);
  border: 0;
  padding: 1rem;
}
.writing-row-thumb-svg {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  flex: 1;
}
.writing-row-thumb-svg svg {
  width: 56px;
  height: 56px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
}
.writing-row-thumb-label {
  font-family: var(--font-sans);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-on-dark-muted);
  font-weight: 500;
  text-align: center;
  line-height: 1.3;
}

/* Type-specific icon accent (shows through the SVG via currentColor) */
.thumb-type-report               { color: var(--accent-text); }
.thumb-type-oped                 { color: var(--accent-2); }
.thumb-type-commentary           { color: var(--accent-text); }
.thumb-type-criticalquestions    { color: var(--accent-3); }
.thumb-type-testimony            { color: var(--accent-2); }
.thumb-type-translation          { color: var(--accent-3); }
.thumb-type-article              { color: var(--accent-text); }

@media (max-width: 720px) {
  .writing-row {
    grid-template-columns: 80px 1fr;
    grid-template-areas: "thumb body";
    gap: 1rem;
    padding: 1.5rem 0;
  }
  .writing-row-body { grid-area: body; }
  .writing-row-thumb {
    grid-area: thumb;
    width: 80px;
  }
  .writing-row-thumb-svg svg { width: 36px; height: 36px; }
  .writing-row-thumb-icon { gap: 0.5rem; padding: 0.55rem; }
  .writing-row-thumb-label { font-size: 10px; letter-spacing: 0.12em; }
  .writing-row-title { font-size: 1.1rem; }
  .writing-row-summary { font-size: 0.94rem; }
}

/* Home page — "In the room" highlight strip
   ============================================================ */
.highlight-strip-section .section-head {
  margin-bottom: 1.25rem;
  align-items: center;
}
.highlight-strip-section .section-head .eyebrow {
  margin-bottom: 0;
  font-size: 13px;
}
.highlight-strip-section .section-head a { font-size: 14.5px; }
.highlight-strip {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0.75rem;
}
.highlight-photo {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  transition: transform 160ms ease;
}
.highlight-photo:hover { transform: translateY(-2px); }
/* Renders the <picture> element wrapping the highlight-strip thumb.
   Was a background-image div until 2026-05-19; now uses real <img>
   inside a <picture> with WebP + JPG sources, matching the documented
   "use real <img> tags" pattern (see CLAUDE.md). The display: block
   override is needed because <picture> is inline by default. The
   inner <img> is sized via object-fit: cover so the same -highlight
   thumb fills both the 4:3 default frame and the 16:9 fifth-tile
   frame at the 2-column desktop breakpoint without distortion. */
.highlight-photo-frame {
  display: block;
  aspect-ratio: 4 / 3;
  width: 100%;
  overflow: hidden;
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  margin-bottom: 0.5rem;
  transition: box-shadow 200ms ease;
}
.highlight-photo-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.highlight-photo:hover .highlight-photo-frame {
  box-shadow: 0 8px 22px rgba(7, 36, 32, 0.14);
}
.highlight-photo-caption {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.highlight-photo-headline {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  line-height: 1.3;
}
.highlight-photo-when {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  margin-top: 0.25rem;
}
@media (max-width: 860px) {
  .highlight-strip { grid-template-columns: repeat(2, 1fr); }
  .highlight-strip .highlight-photo:nth-child(5) { grid-column: span 2; }
  .highlight-strip .highlight-photo:nth-child(5) .highlight-photo-frame { aspect-ratio: 16 / 9; }
}
@media (max-width: 480px) {
  .highlight-strip { grid-template-columns: 1fr; }
  .highlight-strip .highlight-photo:nth-child(5) { grid-column: auto; }
  .highlight-strip .highlight-photo:nth-child(5) .highlight-photo-frame { aspect-ratio: 4 / 3; }
}

/* Home page — subscribe closing band
   ============================================================ */
.subscribe-band {
  background: var(--bg-dark);
  color: var(--text-on-dark);
  padding: 4rem 0;
}
.subscribe-band-inner {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 3rem;
  align-items: center;
}
.subscribe-band .eyebrow {
  color: var(--accent);
  margin-bottom: 0.75rem;
}
.subscribe-band h2 {
  color: var(--text-on-dark);
  /* 24–32px, on the type-scale tokens. Replaces the prior literal
     clamp(1.5rem, 2.6vw, 2rem); identical resolved sizes. */
  font-size: clamp(var(--fs-h5), 2.6vw, var(--fs-h4));
  line-height: 1.2;
  margin-bottom: 0.75rem;
  text-wrap: balance;
}
.subscribe-band-lede {
  font-family: var(--font-serif);
  font-size: 1.02rem;
  line-height: 1.5;
  color: var(--text-on-dark-muted);
  margin: 0;
  max-width: 38rem;
}
.subscribe-band-actions {
  display: flex;
  justify-content: flex-end;
}
@media (max-width: 860px) {
  .subscribe-band { padding: 3rem 0; }
  .subscribe-band-inner { grid-template-columns: 1fr; gap: 1.5rem; }
  .subscribe-band-actions { justify-content: flex-start; }
}

/* Photo lightbox
   ============================================================ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(7, 36, 32, 0.96);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4.5rem 5rem 4rem;
}
.lightbox[hidden] { display: none; }
.lightbox-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 100%;
  max-height: 100%;
}
.lightbox-figure img {
  max-width: 100%;
  max-height: calc(100vh - 10rem);
  object-fit: contain;
  display: block;
}
.lightbox-figure figcaption {
  font-family: var(--font-serif);
  font-size: 1.02rem;
  color: var(--text-on-dark);
  margin-top: 1.25rem;
  text-align: center;
  max-width: 56rem;
  line-height: 1.4;
}
.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  background: transparent;
  border: 0;
  color: var(--text-on-dark-muted);
  cursor: pointer;
  line-height: 1;
  transition: color 140ms;
  padding: 0;
}
.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover { color: var(--accent); }
.lightbox-close {
  top: 1.5rem;
  right: 1.75rem;
  font-size: 2.25rem;
  width: 2.5rem;
  height: 2.5rem;
}
.lightbox-prev,
.lightbox-next {
  top: 50%;
  transform: translateY(-50%);
  font-size: 3.25rem;
  width: 4rem;
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox-prev { left: 1rem; }
.lightbox-next { right: 1rem; }
.lightbox-counter {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-on-dark-muted);
  letter-spacing: 0.12em;
}
@media (max-width: 540px) {
  .lightbox { padding: 3rem 1rem 4rem; }
  .lightbox-prev, .lightbox-next { font-size: 2.4rem; width: 3rem; height: 3rem; }
  .lightbox-prev { left: 0.25rem; }
  .lightbox-next { right: 0.25rem; }
}

.photo-card { cursor: zoom-in; }

/* Photos page
   ============================================================ */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
}
@media (max-width: 860px) { .photo-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .photo-grid { grid-template-columns: 1fr; } }
.photo-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
.photo-frame {
  aspect-ratio: 4 / 3;
  width: 100%;
  background: var(--bg-light);
  background-size: cover;
  background-position: center;
  border: 0.5px solid var(--border);
  margin-bottom: 0.75rem;
}
.photo-frame-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  background: var(--bg-tint);
  border-style: dashed;
}
.photo-caption {
  font-family: var(--font-serif);
  font-size: 1.02rem;
  line-height: 1.45;
  color: var(--text);
}
.photo-caption code {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  background: var(--bg-light);
  padding: 0 0.25rem;
}

/* Cover-style cards (Selected Work)
   ============================================================ */
.cover-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 1.5rem 1rem;
  margin-top: 1.5rem;
}

.cover-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  transition: transform 160ms ease;
}
.cover-card:hover { transform: translateY(-2px); }
.cover-card:hover .cover-art { box-shadow: 0 8px 22px rgba(7, 36, 32, 0.12); }
.cover-art {
  aspect-ratio: 8.5 / 11;
  width: 100%;
  background: var(--bg-light);
  background-size: cover;
  background-position: center top;
  border: 0.5px solid var(--border);
  margin-bottom: 1rem;
  transition: box-shadow 200ms ease;
}
.cover-art-placeholder {
  background: var(--bg-dark);
  color: var(--text-on-dark);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 1.5rem 1.25rem;
  border: 0;
}
.cover-art-pub {
  font-family: var(--font-sans);
  font-size: 12px;
  letter-spacing: 0.14em;
  color: var(--accent);
  font-weight: 500;
  text-transform: uppercase;
}
.cover-art-title {
  font-family: var(--font-sans);
  font-size: 1.1rem;
  line-height: 1.25;
  font-weight: 500;
  color: var(--text-on-dark);
}
.cover-art-date {
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text-on-dark-muted);
  letter-spacing: 0.04em;
}
.cover-card-meta {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  margin-bottom: 0.25rem;
}
.cover-card-type { color: var(--accent-text); font-weight: 500; }
.cover-card-title {
  font-family: var(--font-sans);
  font-size: 0.95rem;
  line-height: 1.3;
  font-weight: 500;
  color: var(--text);
  margin: 0 0 0.25rem;
}
.cover-card-publisher {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
}
.cover-art { margin-bottom: 0.7rem; }

/* Topic tiles
   ============================================================ */
.topic-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.75rem;
}
.topic-tile {
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  border-left: 3px solid var(--accent);
  padding: 1rem 1.25rem 1.25rem;
  text-decoration: none;
  color: inherit;
  display: block;
  transition: border-left-width 160ms ease, transform 160ms ease;
}
.topic-tile:hover { border-left-width: 6px; }
.topic-tile-title {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 500;
  margin-bottom: 0.25rem;
  color: var(--text);
}
.topic-tile-count {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
}
/* Tile accent colors are arranged so consecutive tiles in the homepage grid
   never share a border color, regardless of how the grid wraps (4-wide on
   desktop, 2-wide on tablet, 1-wide on mobile). Display order in build.py:
   us-china, semiconductors, defense-tech, policy, export, diplomacy,
   energy. The standalone "space" tile was merged into the topic on
   2026-05-10; .t-space and .t-defense-and-space are kept as legacy
   aliases (the latter was the canonical slug between 2026-05-10 and
   2026-05-16 before the rename back to 'defense-tech') so any rendered
   HTML still in a stale browser cache continues to look right until
   it's evicted. */
.topic-tile.t-us-china          { border-left-color: var(--accent);   } /* bright green */
.topic-tile.t-semiconductors    { border-left-color: var(--accent-4); } /* deep forest */
.topic-tile.t-defense-tech      { border-left-color: var(--accent);   } /* bright green */
.topic-tile.t-defense-and-space { border-left-color: var(--accent);   } /* legacy alias */
.topic-tile.t-policy            { border-left-color: var(--accent-4); } /* deep forest */
.topic-tile.t-export            { border-left-color: var(--accent-2); } /* gold */
.topic-tile.t-diplomacy         { border-left-color: var(--accent-4); } /* deep forest */
.topic-tile.t-space             { border-left-color: var(--accent);   } /* legacy alias */
.topic-tile.t-energy            { border-left-color: var(--accent);   } /* bright green */
.topic-tile.t-safety            { border-left-color: var(--accent-4); } /* deep forest (legacy class) */

/* Filter chips
   ============================================================ */
.filter-bar {
  position: sticky;
  top: 108px;
  z-index: 40;
  background: var(--bg);
  /* Horizontal padding + negative margin matches the .writing-row /
     .archive-row pattern below: row boxes extend 1rem past the column
     on each side so their :hover background can sit 1rem proud of the
     text. The filter bar must extend its own background the same 1rem
     on each side, or the row's hover background pokes out from behind
     the bar's left/right edges when a hovered row scrolls beneath it.
     Padding pulls the chips back to the column edge so their visible
     position is unchanged. */
  padding: 1rem 1rem;
  margin: 0 -1rem 2rem;
  border-bottom: 0.5px solid var(--border);
}
/* On mobile the filter chips wrap to many rows (each topic chip
   on its own line at narrow widths), so the sticky bar grows to
   ~400px tall and covers more than half the viewport, burying the
   actual archive content. Drop sticky on mobile so the filter bar
   scrolls away with the page; user scrolls back up to re-filter.
   Common mobile-archive pattern (Reuters, FT, NYT all do this).
   Caught 2026-05-17. */
@media (max-width: 720px) {
  .filter-bar {
    position: static;
    /* Mobile scrolls with the page — no hovered-row-bleed-through risk
       because the bar moves out of the way. Drop the negative-margin
       background extension so the bar lines up with the column on
       narrow screens. */
    padding: 1rem 0;
    margin: 0 0 2rem;
  }
}
.filter-row {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  margin-bottom: 0.75rem;
}
.filter-row:last-child { margin-bottom: 0; }
.filter-label {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-muted);
  letter-spacing: 0.05em;
  font-weight: 500;
  min-width: 4.5rem;
  flex-shrink: 0;
  /* Vertically align the label with the first row of chips. The chip
     padding (0.45rem) plus its border (0.5px) puts text baseline ~0.5rem
     down from the chip's top edge — this padding-top brings the label
     baseline to the same position. */
  padding-top: 0.5rem;
}
/* Wraps the chip buttons inside a filter-row so they wrap as a group rather
   than each chip wrapping independently and snapping to the row's left edge.
   With this, line 2 of chips begins at the same horizontal indent as line 1
   (i.e., to the right of the label). */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
}
.chip {
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 1rem;
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--text);
  cursor: pointer;
  user-select: none;
  transition: all 120ms ease;
}
.chip:hover { border-color: var(--text-muted); }
.chip.active {
  background: var(--accent-text);
  color: var(--bg-light);
  border-color: var(--accent-text);
}

.search-input {
  flex: 1;
  min-width: 200px;
  max-width: 360px;
  padding: 0.5rem 1rem 0.5rem 2.5rem;
  background-color: var(--bg-light);
  /* Inline magnifying-glass SVG, positioned at the left padding gutter.
     Stroke color hardcoded to a brand-muted grey-green since CSS variables
     can't be referenced inside data URIs. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237a8a7d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7.5'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>");
  background-repeat: no-repeat;
  background-position: 0.9rem center;
  background-size: 15px 15px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.search-input::placeholder {
  color: var(--text-muted);
  font-style: normal;
  opacity: 0.75;
}
.search-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(103, 192, 112, 0.18);
}

/* Archive list (rows)
   ============================================================ */
.archive-list { list-style: none; padding: 0; margin: 0; }
/* <li> wrapper added 2026-05-12 so the parent <ul> only contains valid
   list children (the older flat-<a>-inside-<ul> structure tripped axe-core's
   "list" rule). The <li> itself is purely structural — no margin, no
   padding, no list marker — so the visual row continues to be the inner
   .archive-row. */
.archive-list > li { margin: 0; padding: 0; display: block; }
.archive-row {
  display: grid;
  grid-template-columns: 110px 1fr 140px;
  gap: 1.5rem;
  align-items: baseline;
  /* Horizontal padding + negative margin so the row's text aligns with the
     surrounding column on a non-hovered list, but the :hover background
     extends 1rem past the text on each side instead of stopping flush. */
  padding: 1.25rem 1rem;
  margin: 0 -1rem;
  border-bottom: 0.5px solid var(--border);
  border-radius: 4px;
  text-decoration: none;
  color: inherit;
}
.archive-row:hover { background: var(--bg-light); }
.archive-date {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--text-muted);
}
.archive-title {
  font-family: var(--font-sans);
  font-size: 1.1rem;
  line-height: 1.35;
  font-weight: 500;
  color: var(--text);
}
.archive-meta {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--text);
  font-weight: 500;
  letter-spacing: 0.01em;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  text-align: right;
}

/* Footer
   ============================================================ */
.site-footer {
  background: var(--bg-dark);
  color: var(--text-on-dark-muted);
  padding: 3rem 0 2rem;
}
.footer-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  padding-bottom: 1.25rem;
  border-bottom: 0.5px solid var(--border-dark);
  margin-bottom: 1rem;
}
.footer-nav,
.footer-social {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  font-family: var(--font-sans);
  font-size: 14.5px;
}
.footer-nav a,
.footer-social a {
  color: var(--text-on-dark-muted);
  text-decoration: none;
}
.footer-nav a:hover,
.footer-social a:hover { color: var(--text-on-dark); }
.footer-tagline {
  font-family: var(--font-serif);
  font-size: 1rem;
  color: var(--text-on-dark);
  line-height: 1.4;
  max-width: 18rem;
}

.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-on-dark-muted);
  flex-wrap: wrap;
  gap: 1rem;
}
.footer-legal { display: inline-flex; align-items: center; gap: 0.55rem; }
.footer-legal a { color: var(--text-on-dark-muted); text-decoration: none; }
.footer-legal a:hover { color: var(--text-on-dark); }
.footer-disclaimer { max-width: 38rem; line-height: 1.5; }

/* Forms
   ============================================================ */
.form {
  display: grid;
  gap: 1rem;
  max-width: 640px;
}
.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
.form-field { display: flex; flex-direction: column; }
.form-field label {
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
  margin-bottom: 0.5rem;
  letter-spacing: 0.02em;
}
.form-field .req { color: var(--accent-text); margin-left: 2px; }
.form-field input,
.form-field select,
.form-field textarea {
  padding: 0.75rem 1rem;
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  font-family: var(--font-sans);
  font-size: 15px;
  color: var(--text);
  border-radius: var(--radius);
  width: 100%;
}
.form-field textarea { min-height: 110px; resize: vertical; font-family: var(--font-serif); font-size: 16px; line-height: 1.5; }
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: 1px solid var(--accent);
  border-color: var(--accent);
}
.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error textarea {
  border-color: var(--accent);
  background: #FBEFE9;
}
/* Per-field validation message — hidden by default; the form JS adds
   .has-error to the parent .form-field on a failed submit attempt. */
.form-error {
  display: none;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--accent-text);
  margin-top: 0.25rem;
}
.form-field.has-error .form-error {
  display: block;
}
.form-help {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
  margin-top: 0.25rem;
}
.form-success {
  background: var(--bg-light);
  border: 0.5px solid var(--accent-2);
  border-left: 3px solid var(--accent-2);
  padding: 1.25rem 1.5rem;
  font-family: var(--font-serif);
}
/* Form-level error banner — surfaced when the contact-form POST fails
   (network error, server error, Turnstile rejection). Sits above the submit
   button so it's visible without scrolling. */
.form-error-banner {
  display: none;
  background: #FBEFE9;
  border: 0.5px solid var(--accent);
  border-left: 3px solid var(--accent);
  color: var(--text);
  padding: var(--space-2) var(--space-3);
  margin: var(--space-3) 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  border-radius: var(--radius-md);
}
.form-actions {
  margin-top: var(--space-1);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* Cloudflare Turnstile widget — give it breathing room above the submit row */
.cf-turnstile {
  margin: var(--space-3) 0 var(--space-2);
}

/* Inquiry router (contact page)
   ============================================================ */
.inquiry-router {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.75rem;
  margin: 2rem 0 2.5rem;
}
.inquiry-button {
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  padding: 1.5rem 1.25rem;
  text-align: left;
  border-radius: var(--radius);
  cursor: pointer;
  transition: all 160ms ease;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.inquiry-button:hover { border-color: var(--accent); }
.inquiry-button.active {
  border-color: var(--accent);
  border-width: 1.5px;
  background: var(--accent-soft);
}
/* Inquiry-button inner spans (was <h3> and <p> before 2026-05-12;
   moved to <span> so the parent <button> stays in HTML-spec phrasing-
   content rules, then styled as block elements here to keep the
   visual layout identical). */
.inquiry-button-title {
  display: block;
  font-family: var(--font-sans);
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--text);
}
.inquiry-button-desc {
  display: block;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-muted);
  margin: 0;
  line-height: 1.4;
}

.inquiry-pane {
  display: none;
  border-top: 0.5px solid var(--border);
  padding-top: 2rem;
}
.inquiry-pane.active { display: block; }
.inquiry-sla {
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-muted);
  margin-bottom: 1.5rem;
  padding: 1rem 1rem;
  background: var(--bg-tint);
  border-left: 3px solid var(--accent);
}

/* Bio page
   ============================================================ */

.bio-prose p {
  font-size: 1.06rem;
  line-height: 1.65;
  margin-bottom: 1rem;
}

/* Topic page
   ============================================================ */
.topic-hero {
  background: var(--bg-light);
  border-bottom: 0.5px solid var(--border);
  padding: 4rem 0 3.5rem;
}
.topic-hero .eyebrow { color: var(--accent-text); }
/* Topic-hero h1 + topic-intro paragraph block both widened from the
   original 720px reading width to 960px on 2026-05-10 to match the new
   .prose-block-wide pattern used on the homepage Built-on-a-body-of-work
   section and the About page body. The wider treatment keeps the hero
   visually aligned with the .topic-essential cards-3 grid and the
   .topic-news list below it, which fill the full 1180px container. The
   topic-intro paragraphs are still 720-px-readable in practice because
   the intro copy is typically only 2 paragraphs of dense prose; 960px
   trades a small amount of per-line readability for a much-improved
   visual relationship with the wider sections that frame the hero. */
.topic-hero h1 { max-width: 960px; }
.topic-intro {
  max-width: 960px;
  font-size: 1.15rem;
  line-height: 1.55;
  color: var(--text-muted);
}
.topic-essential h2 { margin-bottom: 0.5rem; }
.topic-essential .lede { margin-bottom: 1.5rem; }

.topic-coverage-band {
  background: var(--bg-tint);
  padding: 3rem 0;
  margin-top: 1rem;
}
.topic-coverage-band h3 {
  text-align: center;
  font-family: var(--font-sans);
  font-size: 13.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  font-weight: 500;
  margin-bottom: 1.5rem;
}

/* Single writing item page
   ============================================================ */
.article-header {
  border-bottom: 0.5px solid var(--border);
  padding: 4rem 0 2.5rem;
}
.article-header h1 {
  font-size: clamp(1.8rem, 3.5vw, 2.4rem);
  line-height: 1.15;
  margin-bottom: 1rem;
  max-width: 760px;
}
.article-meta {
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
}
.article-meta strong { color: var(--text); font-weight: 500; }

.article-body {
  font-size: 1.08rem;
  line-height: 1.7;
}
.article-body p { margin-bottom: 1.25rem; }
.article-body h2 { margin-top: 2.5rem; margin-bottom: 0.75rem; }
.article-body blockquote {
  margin: 2rem 0;
  padding: 1.25rem 1.5rem;
  border-left: 3px solid var(--accent);
  background: var(--bg-light);
  font-style: italic;
}

.article-aside {
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  border-left: 3px solid var(--accent-2);
  padding: 1.25rem 1.5rem;
  margin: 2rem 0;
}
.article-aside h3 { font-size: 1rem; margin-bottom: 0.5rem; }
.article-aside p { margin-bottom: 0; font-size: 0.95rem; line-height: 1.55; }

/* Pagination
   ============================================================ */

/* Podcast page — three-act layout
   ============================================================ */

/* Act 1: dark hero with new show */
.podcast-hero {
  background: var(--bg-dark);
  color: var(--text-on-dark);
  padding: 5rem 0 5.5rem;
}
.podcast-hero-grid {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 4rem;
  align-items: center;
}
.podcast-hero h1 {
  color: var(--text-on-dark);
  /* 40–60px, on the type-scale tokens. Replaces the prior literal
     clamp(2.4rem, 5vw, 3.6rem) (38.4–57.6px); ~2px larger at both ends,
     visual change picked up in the screenshot-grid re-baseline. */
  font-size: clamp(var(--fs-h3), 5vw, var(--fs-h1));
  line-height: 1.05;
  margin-bottom: 1.5rem;
  letter-spacing: -0.02em;
}
.podcast-hero .eyebrow { color: var(--accent); margin-bottom: 1rem; }
.podcast-positioning {
  font-family: var(--font-serif);
  font-size: 1.18rem;
  line-height: 1.5;
  color: var(--text-on-dark);
  margin-bottom: 1rem;
  max-width: 36rem;
}
.podcast-positioning-detail {
  font-family: var(--font-sans);
  font-size: 14.5px;
  letter-spacing: 0.01em;
  color: var(--text-on-dark-muted);
  margin-bottom: 2rem;
}
.podcast-hero-art img {
  width: 100%;
  max-width: 380px;
  margin: 0 auto;
  display: block;
  /* Brand green hairline border lifts the cover off the dark teal background;
     the navy of the cover and the page teal are close enough in value that
     without a border the image floats indistinctly. 1px keeps it whisper-quiet. */
  border: 1px solid var(--accent);
  box-shadow: 0 16px 60px rgba(0, 0, 0, 0.4);
}

/* Subscribe button row (in dark hero) */
.podcast-subscribe-buttons {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  margin-bottom: 1.75rem;
  max-width: 460px;
}
.podcast-subscribe-btn {
  display: flex;
  flex-direction: column;
  padding: 0.75rem 1rem;
  background: rgba(244, 243, 239, 0.06);
  border: 0.5px solid rgba(244, 243, 239, 0.2);
  border-radius: var(--radius);
  text-decoration: none;
  transition: background 140ms, border-color 140ms;
}
.podcast-subscribe-btn:hover {
  background: var(--accent);
  border-color: var(--accent);
}
.podcast-subscribe-btn:hover .subscribe-platform,
.podcast-subscribe-btn:hover .subscribe-status { color: var(--bg-light); }
.subscribe-platform {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-on-dark);
  letter-spacing: 0.01em;
}
.subscribe-status {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-on-dark-muted);
  margin-top: 4px;
  letter-spacing: 0.02em;
}

/* Newsletter quick-subscribe (inline email + Subscribe button).
   Used on the homepage hero (light bg) and in subscribe bands (dark bg).
   On submit, JS redirects to Substack with the email pre-filled. */
.newsletter-quick {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  flex-wrap: wrap;
  max-width: 520px;
}
.newsletter-quick .newsletter-input {
  flex: 1 1 220px;
  min-width: 200px;
  padding: 0.75em 1em;
  font: inherit;
  font-size: 1rem;
  color: var(--text);
  background: var(--bg-light);
  border: 1px solid var(--bg-tint);
  border-radius: 0.4rem;
  transition: outline-color 0.15s ease;
}
.newsletter-quick .newsletter-input::placeholder {
  color: var(--text-muted);
  opacity: 0.7;
}
.newsletter-quick .newsletter-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: transparent;
}
.newsletter-quick .btn { flex: 0 0 auto; }

/* On-dark variant: input gets cream-on-dark styling for legibility against
   the dark teal subscribe band. */
.newsletter-quick-on-dark .newsletter-input {
  color: var(--text-on-dark);
  background: var(--bg-dark-2);
  border-color: var(--text-on-dark-muted);
}
.newsletter-quick-on-dark .newsletter-input::placeholder {
  color: var(--text-on-dark-muted);
  opacity: 0.65;
}

/* On narrow viewports, stack input above button so neither feels cramped. */
@media (max-width: 540px) {
  .newsletter-quick { flex-direction: column; align-items: stretch; }
  /* When the form flips to flex-direction: column, the input's
     `flex: 1 1 220px` (set above for the desktop row layout) reinterprets
     as "grow vertically, basis 220px tall" — and with no height cap on the
     form, the input balloons to fill the screen. Reset to auto so the
     input stays text-input-tall on mobile. */
  .newsletter-quick .newsletter-input { flex: 0 0 auto; }
  .newsletter-quick .btn { width: 100%; }
}

/* Notify form inline (dark hero) */
.podcast-notify-label {
  display: block;
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--text-on-dark-muted);
  margin-bottom: 0.5rem;
  letter-spacing: 0.01em;
}
.podcast-notify-inline {
  display: flex;
  gap: 0.5rem;
  max-width: 460px;
}
.podcast-notify-inline .newsletter-input {
  flex: 1;
  background: var(--bg-light);
  border: 0.5px solid transparent;
}

/* Meet the host — sits between the podcast hero and the lineage section */
.podcast-host {
  padding: 4rem 0 1rem;
}
.podcast-host-grid {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 3rem;
  align-items: center;
  max-width: 920px;
  margin: 0 auto;
}
.podcast-host-portrait-frame {
  display: block;
  width: 100%;
}
.podcast-host-portrait {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 4px;
}
.podcast-host-text .eyebrow { color: var(--accent-text); }
.podcast-host-text h2 {
  margin: 0.5rem 0 1rem;
}
.podcast-host-text p {
  margin: 0 0 1rem;
}
.podcast-host-link {
  color: var(--accent-text);
  text-decoration: none;
  font-weight: 500;
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s ease;
}
.podcast-host-link:hover { border-bottom-color: var(--accent-text); }

/* Act 2: lineage + guests on cream */
.podcast-lineage {
  text-align: center;
  max-width: 720px;
  margin: 0 auto 3rem;
}
.podcast-lineage .eyebrow { color: var(--accent-text); }

.podcast-succession-compact {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 2.5rem;
  max-width: 600px;
  margin: 0 auto 4rem;
}
.podcast-card-compact {
  text-align: center;
}
.podcast-card-compact img {
  width: 100%;
  max-width: 180px;
  margin: 0 auto 0.75rem;
  display: block;
  /* Matches the 1px brand green hairline on .podcast-hero-art img. */
  border: 1px solid var(--accent);
  box-shadow: 0 6px 18px rgba(7, 36, 32, 0.12);
}
.podcast-card-compact .podcast-card-name {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 15px;
  color: var(--text);
  margin-bottom: 0.25rem;
}
.podcast-card-compact .podcast-card-dates {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
  letter-spacing: 0.02em;
}
.podcast-arrow-compact {
  font-family: var(--font-sans);
  /* 32px — replaces prior literal 2rem; same resolved size. */
  font-size: var(--fs-h4);
  color: var(--accent-text);
  line-height: 1;
  text-align: center;
  padding-bottom: 3rem;
}

.podcast-interviews {
  max-width: 1080px;
  margin: 0 auto;
}
.podcast-interviews h3 {
  text-align: center;
  margin-bottom: 0.5rem;
}
.interview-group-heading {
  font-family: var(--font-sans);
  font-size: 13px;
  letter-spacing: 0.14em;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 2.75rem 0 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 0.5px solid var(--border);
}
.interview-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 0.75rem;
}
.interview-list li {
  display: flex;
  flex-direction: column;
  padding: 1rem 1.25rem 1rem;
  background: var(--bg-light);
  border: 0.5px solid var(--border);
  border-left: 3px solid var(--accent);
  transition: transform 140ms ease, box-shadow 140ms ease, border-left-width 140ms ease;
}
/* Hover: lift + soft shadow + thicker accent stripe. Three reinforcing
   cues so the card visibly responds to the cursor (the inner
   .interview-link also gets an underline on hover, via its own rule
   below). Lift matches the .cover-card / .highlight-photo / .btn-primary
   patterns elsewhere on the site; the border-left-width thicken matches
   .topic-tile:hover so the chosen-path accent stripe also reacts. */
.interview-list li:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 14px rgba(7, 36, 32, 0.10);
  border-left-width: 6px;
}
.interview-name {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 15px;
  color: var(--text);
  line-height: 1.3;
}
.interview-role {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  margin-top: 0.25rem;
  line-height: 1.4;
}
.interview-desc {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 14.5px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 0.75rem 0 1rem;
  flex: 1;
}
.interview-link {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--accent-text);
  text-decoration: none;
  margin-top: auto;
  align-self: flex-start;
}
.interview-link:hover {
  /* Color stays --accent-text (same as default); the visible change is
     the underline + the card-level lift + shadow + thicker stripe from
     .interview-list li:hover above. */
  text-decoration: underline;
}

/* Act 3: archive on dark */
.podcast-archive {
  text-align: center;
}
.podcast-archive .eyebrow { color: var(--accent); }
.podcast-archive h2 { color: var(--text-on-dark); }
.podcast-archive-lede {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--text-on-dark-muted);
  max-width: 38rem;
  margin: 0 auto 1.75rem;
}
.podcast-archive-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

@media (max-width: 860px) {
  .podcast-hero { padding: 3rem 0 3.5rem; }
  .podcast-hero-grid { grid-template-columns: 1fr; gap: 2.25rem; }
  .podcast-hero-art { order: -1; max-width: 280px; margin: 0 auto; }
  .podcast-subscribe-buttons { grid-template-columns: 1fr 1fr; }
  .podcast-host { padding: 2.5rem 0 0.5rem; }
  .podcast-host-grid {
    grid-template-columns: 1fr;
    gap: 1.5rem;
    max-width: 380px;
  }
  .podcast-host-portrait-frame { max-width: 280px; margin: 0 auto; }
  .podcast-succession-compact {
    grid-template-columns: 1fr;
    gap: 1.5rem;
    max-width: 240px;
  }
  .podcast-arrow-compact { transform: rotate(90deg); padding: 0; }
}
@media (max-width: 480px) {
  .podcast-subscribe-buttons { grid-template-columns: 1fr; }
  .podcast-notify-inline { flex-direction: column; }
}

/* About page — "Why Decision Tree?" stacked layout
   ============================================================
   Restructured 2026-05-10 from a 2-column grid (prose + Venn side-by-side)
   to a stacked layout: prose runs at .prose-block-wide (960px) matching
   the rest of the About page body, with the Venn figure sitting below
   at its natural 520px width, centered horizontally. The legacy
   .why-dt-grid / .why-dt-prose / @media (max-width: 860px) rules were
   removed because the grid no longer exists; only the figure-positioning
   and SVG-sizing rules remain. */
.why-dt-figure {
  margin: 2.5rem auto 0;
  max-width: 520px;
}
.venn-svg {
  width: 100%;
  max-width: 520px;
  height: auto;
}

/* Homepage cascade animation hero
   ============================================================
   Full-bleed dark teal band carrying the firm's animated logo. Sizing
   tuned 2026-05-12 to guarantee a visible peek of the next section
   above the fold on a 13" laptop — without that peek, first-time
   visitors on shorter viewports could mistake the hero for the entire
   page and miss the firm intro / Areas of focus below.

   Three reinforcing scroll cues:
     1. max-height capped at 62vh (was 70vh) so the iframe doesn't fill
        the full viewport even on short screens.
     2. Tighter vertical padding (1rem, was 2rem) so the dark band is
        a tight wrapper rather than generous breathing room.
     3. A small SCROLL ↓ chevron pinned to the bottom of the band as
        an explicit affordance for anyone who still doesn't catch the
        peek. See .cascade-hero-scroll below. */
.cascade-hero {
  position: relative;       /* anchor for .cascade-hero-scroll */
  background: var(--bg-dark);
  padding: 1rem 0;
  display: block;
  width: 100%;
  overflow: hidden;
}
.cascade-hero-frame {
  /* Container preserves the cascade's 16:9 aspect ratio. max-height
     caps the iframe so it leaves room for the next section to peek
     above the fold on shorter viewports. */
  width: 100%;
  max-width: 1600px;
  max-height: 62vh;
  margin: 0 auto;
  aspect-ratio: 16 / 9;
}
.cascade-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: var(--bg-dark);
}
/* Scroll-down affordance pinned to the bottom-center of the cascade
   band. Small Jost eyebrow + chevron icon; subtle 2.4s vertical bob
   animation that respects prefers-reduced-motion. Clicking smooth-
   scrolls to #firm-intro (the firm-intro hero immediately below).
   Hidden on mobile because the smaller viewport + tighter cascade
   already shows the next section without needing the cue. */
.cascade-hero-scroll {
  position: absolute;
  left: 50%;
  bottom: 0.6rem;
  transform: translateX(-50%);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  color: var(--text-on-dark-muted);
  text-decoration: none;
  /* Hidden by default; the fade-in animation below brings it in after the
     cascade animation completes (cascade total = 7360ms with logo fully
     formed and held at 6610ms; we reveal the chevron at 6.6s so it appears
     as the cascade settles). pointer-events: none prevents accidental
     hover during the hidden phase. */
  opacity: 0;
  pointer-events: none;
  animation: cascade-scroll-reveal 0.6s ease-out 6.6s forwards;
  transition: opacity 0.2s ease, color 0.2s ease;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm, 4px);
}
@keyframes cascade-scroll-reveal {
  to {
    opacity: 0.78;
    pointer-events: auto;
  }
}
.cascade-hero-scroll:hover,
.cascade-hero-scroll:focus-visible {
  opacity: 1;
  color: var(--text-on-dark);
}
.cascade-hero-scroll:focus-visible {
  outline: 2px solid var(--text-on-dark);
  outline-offset: 2px;
}
.cascade-hero-scroll-label {
  font-family: 'Jost', system-ui, sans-serif;
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  line-height: 1;
}
.cascade-hero-scroll-chevron {
  display: block;
  animation: cascade-scroll-bob 2.4s ease-in-out infinite;
}
@keyframes cascade-scroll-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .cascade-hero-scroll-chevron { animation: none; }
}
@media (max-width: 720px) {
  /* On mobile the 16:9 cascade gets short — the tree gets cramped vertically.
     Switch to 4:3 so the tree has more vertical room to grow. The peek + scroll
     cue is less necessary at this width because the hero is already shorter
     relative to the next section. */
  .cascade-hero-frame { aspect-ratio: 4 / 3; }
  .cascade-hero-scroll { display: none; }
  /* Tightened from 1rem to 0.25rem 2026-05-17. The 1rem was reserving
     visual breathing room around the scroll chevron (hidden at this
     breakpoint via the rule above), so on mobile it was just dead
     padding eating viewport real estate. On a 375px phone with the
     1:1 cascade aspect ratio (≤480px breakpoint below), removing 24px
     of padding gets the firm-intro section above the fold sooner. */
  .cascade-hero { padding: 0.25rem 0; }
}
@media (max-width: 480px) {
  /* On phones, prioritize tree visibility — go to 1:1 */
  .cascade-hero-frame { aspect-ratio: 1 / 1; }
}

/* 404
   ============================================================ */
.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6rem 1rem 4rem;
  min-height: 60vh;
}
.error-logo {
  width: 220px;
  height: auto;
  margin-bottom: 2.5rem;
}
.error-code {
  font-family: var(--font-sans);
  font-size: 6rem;
  font-weight: 500;
  color: var(--accent-text);
  line-height: 1;
  margin-bottom: 0.5rem;
}

/* Utility
   ============================================================ */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0;
}
.divider { border: 0; height: 0.5px; background: var(--border); margin: 2rem 0; }

/* Mobile
   ============================================================ */
@media (max-width: 980px) {
  section { padding: var(--section-padding-mobile) 0; }
  /* Mobile header is 48 px logo + 32 px padding ≈ 80 px. 80 px scroll-
     margin-top tucks the section box exactly at the header bottom; the
     section's own padding-top (56 px mobile) is the visible breathing
     room (80 + 56 = 136 px content, 56 px below header). Matches the
     desktop geometry of 56 px gap below the header. */
  section[id] { scroll-margin-top: 80px; }
  .hero { padding: var(--space-5) 0 var(--space-7); }   /* mobile hero bottom 56px (was 64px) — Option D 2026-05-12 */
  .hero-grid { grid-template-columns: 1fr; gap: var(--space-4); }
  /* On narrow viewports, default behavior is portrait BELOW the text —
     readers get the firm's positioning copy + subscribe CTA before the
     face. Width 280px (not 360px) so the photo has visible whitespace on
     either side and reads as centered rather than edge-to-edge on a 393pt
     iPhone column. max-width: 100% caps it on viewports narrower than
     280px so the photo never overflows. Founder page is the exception:
     portrait-first there matches the brand-book section 5 guidance for
     personal-brand firms — see the scoped `.hero-grid--founder` rule
     below. */
  .hero-portrait-frame { width: 280px; max-width: 100%; margin: 0 auto; }
  .hero-grid--founder .hero-portrait-frame { order: -1; }
  .quote-grid { grid-template-columns: 1fr; gap: 1.5rem; }
  /* Reset the desktop facing-pages right-align once the grid stacks. */
  .quote-grid > div:first-child { text-align: left; }
  /* Reset the desktop divider-aligned eyebrow shift -- no divider to
     align with when the grid is a single column. */
  .quote-band .eyebrow { padding-right: 0; }
  .quote-grid > div { padding-right: 0; border-right: 0; padding-bottom: 1.5rem; border-bottom: 0.5px solid var(--border-strong); }
  .quote-grid > div:last-child { border-bottom: 0; padding-bottom: 0; }
  
  .cards-3, 
  .topic-grid { grid-template-columns: 1fr; }
  
  .archive-row { grid-template-columns: 1fr; gap: 0.4rem; padding: 1rem 0; }
  .archive-meta { text-align: left; flex-direction: row; gap: 0.6rem; }
  .form-row { grid-template-columns: 1fr; }
  .inquiry-router { grid-template-columns: 1fr 1fr; }
  .newsletter-form { flex-direction: column; }

  /* Mobile nav overlay. Items are top-aligned with breathing room for
     the fixed-position close button (.mobile-toggle, see below) and
     vertically scrollable when the About dropdown's expansion makes
     the total content exceed viewport height. justify-content: center
     was the earlier treatment but broke when items overflowed —
     centering pushed top items above and bottom items below the
     viewport, with no way to scroll to them. Caught 2026-05-16. */
  .site-nav {
    position: fixed;
    inset: 0;
    background: var(--bg-dark);
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    gap: 1.5rem;
    padding-top: 5rem;
    padding-bottom: 2rem;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateX(100%);
    transition: transform 240ms ease;
    z-index: 50;
  }
  .site-nav.open { transform: translateX(0); }
  .site-nav a { font-size: 1.5rem; font-family: var(--font-sans); }
  /* .mobile-toggle becomes a fixed-position close button on mobile —
     escapes the .site-header's stacking and iOS sticky-detach issues
     by living directly at the viewport level. See the .mobile-toggle
     overrides at the end of this @media block. */
  .mobile-toggle {
    display: block;
    position: fixed;
    /* top: 30px puts the 28x28 toggle's vertical center at the
       header's vertical center (header is 88px tall: 56px logo +
       16px padding top + 16px padding bottom; logo center at 44px;
       toggle center at top + 14 = 30 + 14 = 44px). Tuned 2026-05-16. */
    top: 30px;
    right: clamp(1rem, 2.5vw, 2.5rem);
    z-index: 70;
  }

  /* Mobile dropdown — in-flow accordion rather than absolute-positioned
     panel. The trigger sits inline with the other nav items at 1.5rem
     and the submenu expands below it, pushing subsequent items down.
     Submenu items use smaller font + tighter line-height to read as
     subordinate to the trigger. */
  .nav-dropdown {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .nav-dropdown-trigger {
    font-size: 1.5rem;
    color: var(--text-on-dark);
  }
  .nav-dropdown-panel {
    position: static;
    background: transparent;
    border: 0;
    box-shadow: none;
    margin-top: 1rem;
    padding: 0;
    text-align: center;
  }
  .nav-dropdown-panel a {
    font-size: 1.125rem;
    padding: 0.5rem 0;
    line-height: 1.4;
  }

  .filter-bar { top: 84px; }

  .topic-coverage-band .logo-wall-grid { gap: 0.8rem 1.4rem; }
}

/* Nav collapse: fires EARLIER than the 980px content-layout breakpoint.
   The 88px logo (~339px wide) plus the full nav row (About / Publications /
   In the News / Speaking / Contact / Podcast pill / Newsletter pill) needs
   roughly 1080-1100px of horizontal space to fit comfortably. Below that,
   flexbox was squeezing the logo to make room — fixed defensively with
   flex-shrink: 0 on .site-logo, but the right user-facing answer is to
   collapse the nav to a hamburger earlier so the logo stays full size and
   the nav stays usable.
   Rules duplicate the .site-nav / .mobile-toggle / .nav-dropdown rules
   from the 980px block. The 980px block's copies are redundant but
   harmless under CSS cascade rules — kept there as documentation of which
   rules belong to the nav-collapse vs. the content-layout half of the
   shared 980px breakpoint historically. */
@media (max-width: 1100px) {
  /* Duplicates the 980px block above. See that block for the
     rationale behind top-aligned + scrollable overlay and
     fixed-position .mobile-toggle. */
  .site-nav {
    position: fixed;
    inset: 0;
    background: var(--bg-dark);
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    gap: 1.5rem;
    padding-top: 5rem;
    padding-bottom: 2rem;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateX(100%);
    transition: transform 240ms ease;
    z-index: 50;
  }
  .site-nav.open { transform: translateX(0); }
  .site-nav a { font-size: 1.5rem; font-family: var(--font-sans); }
  .mobile-toggle {
    display: block;
    position: fixed;
    /* top: 30px puts the 28x28 toggle's vertical center at the
       header's vertical center (header is 88px tall: 56px logo +
       16px padding top + 16px padding bottom; logo center at 44px;
       toggle center at top + 14 = 30 + 14 = 44px). Tuned 2026-05-16. */
    top: 30px;
    right: clamp(1rem, 2.5vw, 2.5rem);
    z-index: 70;
  }

  .nav-dropdown {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .nav-dropdown-trigger {
    font-size: 1.5rem;
    color: var(--text-on-dark);
  }
  .nav-dropdown-panel {
    position: static;
    background: transparent;
    border: 0;
    box-shadow: none;
    margin-top: 1rem;
    padding: 0;
    text-align: center;
  }
  .nav-dropdown-panel a {
    font-size: 1.125rem;
    padding: 0.5rem 0;
    line-height: 1.4;
  }
}

@media (max-width: 480px) {
  /* Headings now use clamp() with --fs-* variables and scale gracefully without
     hard overrides; only the inquiry-router needs a single-column collapse here. */
  .inquiry-router { grid-template-columns: 1fr; }
}

/* Per-topic landing pages
   ============================================================
   Three first-class topic surfaces (AI, Semiconductors, Defense Tech)
   stood up 2026-05-16 as Stage 3 of the topic-pages workstream.
   Hero reuses the existing .page-hero / .eyebrow / .lede pattern
   from /publications/index.html for visual continuity — the topic
   page is a sub-view of the publications archive and should read
   as one. (Earlier image-led variant tried the same day was wrong
   register — the topic illustrations are marketing visuals on the
   homepage tiles but fought the research-archive register on the
   topic pages themselves.) */

.topic-section {
  padding-top: var(--space-9);
  padding-bottom: var(--space-9);
}
.topic-section-heading {
  margin: 0 0 1.5rem 0;
}
.topic-section-body {
  margin-bottom: 1.5rem;
}
.topic-press-list {
  margin: 0 0 1.5rem 0;
}
/* "See all N publications on this topic" / "See all N news clips on
   this topic" affordances render as .btn .btn-outline to match the
   homepage's "Built on a body of work" button row. No topic-page-
   specific CSS needed — the existing .btn rules handle it. */
.empty-state {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--text-muted);
  list-style: none;
  padding: 1rem 0;
}

/* Scroll-scrub fly-in for the body-of-work button row
   ============================================================
   Continuous scroll-driven animation: the buttons fly in from the
   left as the user scrolls the row into view, and fly back off to
   the left if the user scrolls up. Progress is fully reversible.

   How it works:
     - JS (initRevealOnScroll in main.js) writes a --scroll-progress
       CSS variable (0..1) on the container, updated on every scroll
       frame via requestAnimationFrame. 0 = container is at the
       bottom of the viewport or below; 1 = container has scrolled
       up far enough to be fully revealed; values between interpolate
       linearly.
     - Each button reads --scroll-progress and derives its own
       per-button progress (--p) via :nth-child rules with a stagger
       offset. The clamp() math means button 1 finishes flying in
       before button 2 starts, etc., spread across the scroll range.
     - opacity = --p; transform = translateX((1 - --p) * -140px).
       At --p = 0, button is fully off-screen left and invisible;
       at --p = 1, button is in its final position and fully opaque.
     - A short transition (90ms linear) smooths between scroll
       events so the animation reads continuous even on slower
       devices where rAF can drop frames.

   Why two classes (.has-scroll-scrub on the container as a JS-set
   marker, plus :not selectors for the no-JS state): users with JS
   disabled never get .has-scroll-scrub, so the buttons render at
   their natural state immediately. No hidden-forever failure.

   prefers-reduced-motion: the scrub is suppressed; buttons appear
   in place at --p = 1 regardless of scroll position. */
.body-of-work-buttons.has-scroll-scrub > a {
  --p: var(--scroll-progress, 0);
  /* Opacity ramps to 1 at --p=0.2 (multiplier 5) rather than at
     --p=1.0. The earlier 1:1 opacity:--p ramp left buttons
     mid-flight partially transparent, which made overlapping
     buttons see-through during the staggered sweep. The faster
     ramp means each button is briefly translucent at the start of
     its flight (--p=0 to 0.2) and then solid for the rest. */
  opacity: clamp(0, calc(var(--p) * 5), 1);
  /* Fixed -140px translate-left for every button. The "dealt from
     a deck of cards" per-button-offset variant was tried briefly
     2026-05-17 (each button starting from the same x position and
     fanning out to its destination) but Greg preferred the simpler
     fade-in-and-stagger feel where each button slides a short fixed
     distance from its own natural position. Reverted. */
  transform: translateX(calc((1 - var(--p)) * -140px));
  transition: opacity 90ms linear, transform 90ms linear;
  will-change: transform, opacity;
}
/* Solid fill on .btn-outline buttons IN THIS CONTAINER ONLY so the
   scrub-mid-flight overlap doesn't show through. The natural
   transparent fill on .btn-outline reads fine when buttons are at
   rest (section background is white via .section-bg-light, so the
   buttons appear white-bordered-green-stroked), but mid-flight the
   translateX offsets put each button ~18px on top of the next, and
   transparent-on-transparent lets the lower button's content bleed
   through. Solid white fill prevents that without changing the
   resting-state appearance. .btn-primary (Podcast, Newsletter)
   already has a solid green fill so it doesn't need this. */
.body-of-work-buttons.has-scroll-scrub > a.btn-outline {
  background: var(--bg-light);
}
/* Hover/focus override at matching specificity so the .btn-outline
   default green hover background still wins inside this container.
   Without this, the rule above (background: var(--bg-light)) wins
   over .btn-outline:hover (background: var(--accent-text)) due to
   higher selector specificity, and .btn-outline:hover's color
   change (text → white) lands on a still-white background = text
   invisible. Caught 2026-05-17 when Greg reported the outline
   buttons losing their text on hover. */
.body-of-work-buttons.has-scroll-scrub > a.btn-outline:hover,
.body-of-work-buttons.has-scroll-scrub > a.btn-outline:focus {
  background: var(--accent-text);
}
/* Per-button stagger. Each button has its own slice of the global
   scroll range; the multiplier of 2.5 means each button's fly-in
   completes over 40% of the scroll progress (0.4 * 2.5 = 1), and
   the offset shifts the starting point so successive buttons begin
   roughly 8% of scroll progress after the previous one. End result:
   first button finishes around progress 0.40, last button finishes
   around progress 0.80, with overlapping mid-flight motion that
   reads as a sweep rather than six discrete arrivals. */
.body-of-work-buttons.has-scroll-scrub > a:nth-child(1) { --p: clamp(0, calc((var(--scroll-progress, 0) - 0.00) * 2.5), 1); }
.body-of-work-buttons.has-scroll-scrub > a:nth-child(2) { --p: clamp(0, calc((var(--scroll-progress, 0) - 0.08) * 2.5), 1); }
.body-of-work-buttons.has-scroll-scrub > a:nth-child(3) { --p: clamp(0, calc((var(--scroll-progress, 0) - 0.16) * 2.5), 1); }
.body-of-work-buttons.has-scroll-scrub > a:nth-child(4) { --p: clamp(0, calc((var(--scroll-progress, 0) - 0.24) * 2.5), 1); }
.body-of-work-buttons.has-scroll-scrub > a:nth-child(5) { --p: clamp(0, calc((var(--scroll-progress, 0) - 0.32) * 2.5), 1); }
.body-of-work-buttons.has-scroll-scrub > a:nth-child(6) { --p: clamp(0, calc((var(--scroll-progress, 0) - 0.40) * 2.5), 1); }
@media (prefers-reduced-motion: reduce) {
  .body-of-work-buttons.has-scroll-scrub > a {
    --p: 1;
    transform: none;
    transition: none;
  }
}

/* Print
   ============================================================ */
@media print {
  .site-header, .site-footer, .newsletter-band, .filter-bar, .hero-ctas, .form-actions { display: none; }
  body { background: white; color: black; }
}
