/* ============================================================
   ViPM — site styles. Vanilla CSS on tokens.css custom props.
   Every value resolves from a token. Field-manual restraint:
   squared corners, 1px borders over shadows, one accent.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
html, body { margin: 0; }
body {
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg, video { max-width: 100%; height: auto; display: block; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); text-decoration: underline; }
::selection { background: var(--bg-tint); }
:focus-visible {
  outline: var(--border-w-strong) solid var(--accent);
  outline-offset: 2px;
}

/* ---- Reusable atoms ---- */
.label {
  font-family: var(--font-mono); font-weight: 600;
  font-size: var(--t-label-size); line-height: var(--t-label-lh);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
}
.mono { font-family: var(--font-mono); }
.muted { color: var(--text-muted); }
/* Visible to screen readers only. Not `display:none`, which removes it from the
   accessibility tree along with everything else. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---- Layout containers ---- */
.container { max-width: 1100px; margin: 0 auto; padding: 0 var(--space-lg); }
.content-col { max-width: var(--content-w); margin: 0 auto; padding: 0 var(--space-lg); }
.section { padding-block: var(--space-3xl); }
.stack > * + * { margin-top: var(--space-lg); }

/* skip link */
.skip {
  position: absolute; left: -999px; top: 0; z-index: 100;
  background: var(--bg-ink); color: var(--text-inverse);
  padding: var(--space-sm) var(--space-md); font-family: var(--font-mono);
}
.skip:focus { left: var(--space-md); top: var(--space-md); }

/* ---- Buttons ---- */
.btn {
  display: inline-block; font-family: var(--font-mono); font-weight: 600;
  font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  padding: 12px 18px; cursor: pointer; border: none; text-decoration: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { text-decoration: none; }
.btn--primary { background: var(--accent); color: var(--text-inverse); }
.btn--primary:hover { background: var(--accent-hover); color: var(--text-inverse); }
.btn--ghost {
  background: transparent; color: var(--text-primary);
  border: var(--border-w) solid var(--border-strong);
}
.btn--ghost:hover { background: var(--bg-sunken); color: var(--text-primary); }

/* ============================================================
   Site header
   ============================================================ */
.site-header {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--bg-base) 92%, transparent);
  backdrop-filter: saturate(1.1) blur(6px);
  border-bottom: var(--border-w) solid var(--border-subtle);
}
.site-header__inner {
  /* `relative` so the Resources menu panel can anchor to the header bar rather
     than to its own summary below 640px — see .nav__menu-panel. */
  position: relative;
  max-width: 1100px; margin: 0 auto; padding: var(--space-sm) var(--space-lg);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-lg);
}
.brand { display: flex; align-items: center; gap: var(--space-sm); text-decoration: none; }
.brand:hover { text-decoration: none; }
.brand__mark { width: 30px; height: 30px; flex: 0 0 auto; }
/* Header lockup. Both variants ship in the markup; CSS decides which shows, so
   the right one is painted on first render rather than swapped in after JS. */
.brand__wordmark { height: 42px; width: auto; flex: 0 0 auto; }
.brand__compact { display: none; align-items: center; gap: var(--space-sm); }
@media (max-width: 640px) {
  .brand__wordmark { display: none; }
  .brand__compact { display: flex; }
}
.brand__text {
  font-family: var(--font-mono); font-weight: 600; font-size: 18px;
  color: var(--text-primary); letter-spacing: .01em;
}
.brand__text b { color: var(--text-primary); }
.nav { display: flex; align-items: center; gap: var(--space-lg); }
.nav a {
  font-family: var(--font-mono); font-weight: 500; font-size: 14px;
  color: var(--text-secondary); text-decoration: none;
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--accent); }
.nav__cta { font-size: 12px !important; }

/* Resources menu.
   A <details>, not a hover menu and not a JS widget. Hover strands touch users,
   and the whole nav has to keep working with no JS — so the disclosure is the
   browser's, and assets/js/nav.js only adds outside-click and Escape on top.
   Without JS it still opens and closes on the summary. */
.nav__menu { position: relative; }
.nav__menu > summary {
  font-family: var(--font-mono); font-weight: 500; font-size: 14px;
  color: var(--text-secondary); cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: var(--space-2xs);
}
.nav__menu > summary::-webkit-details-marker { display: none; }
.nav__menu > summary:hover,
.nav__menu[open] > summary,
.nav__menu--current > summary { color: var(--accent); }
.nav__menu > summary::after {
  content: "▾"; font-size: 10px; line-height: 1;
  transition: transform var(--dur) var(--ease);
}
.nav__menu[open] > summary::after { transform: rotate(180deg); }
.nav__menu-panel {
  position: absolute; top: calc(100% + var(--space-sm)); right: 0; z-index: 40;
  min-width: 220px; display: flex; flex-direction: column;
  background: var(--bg-surface); border: var(--border-w) solid var(--border-default);
  border-top: var(--rule-accent) solid var(--border-accent);
  box-shadow: var(--shadow-1); padding: var(--space-2xs) 0;
}
.nav__menu-panel a {
  padding: var(--space-xs) var(--space-lg); font-size: 13px;
}
.nav__menu-panel a:hover { background: var(--bg-sunken); color: var(--accent); }

@media (max-width: 640px) {
  .nav { gap: var(--space-md); }
  .nav a.nav--hide-sm { display: none; }
  /* Anchored to the header bar, not to the summary. "Resources" sits mid-nav at
     this width, so a 220px panel hung off its right edge runs off the left of a
     narrow screen. `position: static` on the details drops the containing block
     back to .site-header__inner, and the panel then spans the full bar. */
  .nav__menu { position: static; }
  .nav__menu-panel {
    left: var(--space-lg); right: var(--space-lg); min-width: 0;
    top: calc(100% - var(--border-w));
  }
}

/* ============================================================
   Site footer — olive-drab ink, structural
   ============================================================ */
.site-footer {
  background: var(--bg-ink); color: var(--text-inverse);
  margin-top: var(--space-4xl); padding-block: var(--space-3xl);
}
.site-footer a { color: var(--text-inverse-muted); }
.site-footer a:hover { color: var(--text-inverse); }
.site-footer__grid {
  display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: var(--space-2xl);
}
.site-footer__brand .brand__text,
.site-footer__brand .brand__text b { color: var(--text-inverse); }
.site-footer__tag { color: var(--text-inverse-muted); font-size: var(--t-body-sm-size); margin-top: var(--space-sm); max-width: 34ch; }
.footcol h3 {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-label-size);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-inverse-muted); margin: 0 0 var(--space-md);
}
.footcol ul { list-style: none; margin: 0; padding: 0; }
.footcol li { margin-bottom: var(--space-xs); font-size: var(--t-body-sm-size); }
.site-footer__legal {
  margin-top: var(--space-2xl); padding-top: var(--space-lg);
  border-top: var(--border-w) solid var(--bg-ink-alt);
  font-size: var(--t-caption-size); color: var(--text-inverse-muted);
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--space-md);
}
/* The credit link sits inside a sentence rather than in a list, so it needs an
   underline to read as a link — the column links get that from context. */
.site-footer__credit { text-decoration: underline; text-underline-offset: 2px; }
.site-footer__sep { margin: 0 .35em; opacity: .55; }
/* Site-wide small print. Quiet on purpose — see the comment in base.njk. It sits
   below the legal row rather than in it, because that row is a two-column flex
   and a third item wraps into the gap between them. */
.site-footer__smallprint {
  margin: var(--space-md) 0 0; max-width: 90ch;
  font-size: var(--t-caption-size); line-height: 1.5;
  color: var(--text-inverse-muted); opacity: .7;
}
@media (max-width: 720px) { .site-footer__grid { grid-template-columns: 1fr 1fr; } .site-footer__brand { grid-column: 1 / -1; } }

/* ============================================================
   Home — hero
   ============================================================ */
.hero { background: var(--bg-ink); color: var(--text-inverse); overflow: hidden; }
.hero__inner {
  max-width: 1100px; margin: 0 auto; padding: var(--space-4xl) var(--space-lg);
  display: grid; grid-template-columns: 1.5fr 1fr; gap: var(--space-3xl); align-items: center;
}
.hero__kicker { color: var(--accent-bright); margin-bottom: var(--space-md); }
.hero h1 {
  font-family: var(--font-mono); font-weight: 600;
  font-size: clamp(2rem, 5vw, var(--t-display-size)); line-height: var(--t-display-lh);
  margin: 0 0 var(--space-lg); color: var(--text-inverse);
}
.hero p {
  font-size: var(--t-body-lg-size); line-height: 1.6; color: var(--text-inverse);
  max-width: 46ch; margin: 0 0 var(--space-xl);
}
.hero__actions { display: flex; gap: var(--space-md); flex-wrap: wrap; }
.hero__art { justify-self: center; }
.hero__art img {
  width: 100%; max-width: 320px; border: var(--border-w) solid var(--bg-ink-alt);
}
@media (max-width: 820px) {
  .hero__inner { grid-template-columns: 1fr; gap: var(--space-2xl); padding-block: var(--space-3xl); }
  .hero__art { display: none; }
}

/* section heading with top rule (field-manual) */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md);
  border-top: var(--border-w-strong) solid var(--text-primary);
  padding-top: 14px; margin-bottom: var(--space-xl);
}
.section-head h2 {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h2-size);
  line-height: var(--t-h2-lh); margin: 0;
}
.section-head a { font-family: var(--font-mono); font-weight: 500; font-size: 13px; }

/* positioning strip */
.positioning { background: var(--bg-sunken); }
.positioning p {
  font-family: var(--font-mono); font-weight: 500;
  font-size: var(--t-h3-size); line-height: 1.5; color: var(--text-secondary);
  max-width: 60ch; margin: 0 auto; text-align: center;
}
.positioning strong { color: var(--text-primary); }

/* signpost cards */
.signposts { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-lg); }
.signpost {
  display: block; background: var(--bg-surface); border: var(--border-w) solid var(--border-default);
  padding: var(--space-xl); text-decoration: none; color: var(--text-primary);
  transition: box-shadow var(--dur) var(--ease);
}
.signpost:hover { box-shadow: var(--shadow-1); text-decoration: none; }
.signpost h3 { font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h3-size); margin: var(--space-xs) 0 var(--space-xs); }
.signpost p { font-size: var(--t-body-sm-size); color: var(--text-secondary); margin: 0; }
.signpost .label { color: var(--accent); }
@media (max-width: 640px) { .signposts { grid-template-columns: 1fr; } }

/* ============================================================
   Episode card (components.html §1)
   ============================================================ */
.ep-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); }
@media (max-width: 900px) { .ep-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .ep-grid { grid-template-columns: 1fr; } }

.ep-card {
  display: flex; flex-direction: column; background: var(--bg-surface);
  border: var(--border-w) solid var(--border-default);
  transition: box-shadow var(--dur) var(--ease); text-decoration: none; color: inherit;
}
.ep-card:hover { box-shadow: var(--shadow-1); text-decoration: none; }
.ep-card--soon { opacity: .82; }
.ep-card--soon:hover { box-shadow: none; }
.ep-card__thumb {
  height: 190px; display: flex; align-items: flex-end; justify-content: space-between; padding: 12px;
  background: repeating-linear-gradient(135deg,#3F4522,#3F4522 9px,#454b26 9px,#454b26 18px);
  position: relative; overflow: hidden;
}
/* Badge and branch tag sit over the artwork, so the image is positioned out
   of flow and the flex layout still places them at the bottom corners. */
.ep-card__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.ep-card__thumb .tag, .ep-card__thumb .ep-badge { position: relative; z-index: 1; }
/* The artwork carries its own ViPM wordmark bottom-left, which is where the
   EP badge sits by default. Move the badges to the top when there's an image
   so the two don't stack on each other. */
.ep-card__thumb:has(.ep-card__img) { align-items: flex-start; }
.ep-card__thumb .tag {
  font-family: var(--font-mono); font-weight: 600; font-size: 10px; letter-spacing: .08em;
  color: var(--text-inverse); background: var(--accent); padding: 5px 8px;
}
.ep-card__thumb .ep-badge { font-family: var(--font-mono); font-weight: 600; font-size: 10px; letter-spacing:.08em; color:#DDD9C6; background:var(--bg-ink); padding:5px 8px; }
.ep-card__body { padding: 18px 18px 20px; display: flex; flex-direction: column; flex: 1; }
.ep-card__num { color: var(--accent); margin-bottom: 8px; }
.ep-card__title {
  font-family: var(--font-mono); font-weight: 500; font-size: var(--t-h3-size);
  line-height: var(--t-h3-lh); margin: 0 0 12px;
}
.ep-card__guest { font-size: var(--t-body-sm-size); color: var(--text-secondary); margin-bottom: 14px; }
.ep-card__meta {
  display: flex; gap: 16px; font-family: var(--font-mono); font-weight: 500; font-size: 12px;
  color: var(--text-muted); border-top: var(--border-w) solid var(--border-subtle);
  padding-top: 12px; margin-top: auto;
}

/* ============================================================
   Breadcrumbs + episode header
   ============================================================ */
.breadcrumb { font-family: var(--font-mono); font-size: 13px; color: var(--text-muted); margin-bottom: var(--space-xl); }
.breadcrumb a { color: var(--text-muted); }
.breadcrumb a:hover { color: var(--accent); }
.breadcrumb__sep { margin: 0 8px; color: var(--border-strong); }
.breadcrumb__current { color: var(--text-secondary); }

.ep-header { margin-bottom: var(--space-xl); }
.ep-header .tag-pill {
  display: inline-block; font-family: var(--font-mono); font-weight: 600; font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
}
.ep-title {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-title-size);
  line-height: var(--t-title-lh); margin: var(--space-xs) 0 var(--space-md);
}
.ep-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-sm);
  font-family: var(--font-mono); font-weight: 500; font-size: 13px; color: var(--text-muted);
}
.ep-meta .dot { color: var(--border-strong); }
/* Service branch in the meta line. A link into the branch archive, styled as a
   neutral sibling of .cert-chip so the two chips read as one row of filters
   rather than two unrelated widgets. */
.meta-chip {
  display: inline-block;
  font-family: var(--font-mono); font-weight: 500; font-size: 12px; line-height: 1.4;
  color: var(--text-secondary); background: var(--bg-surface);
  border: var(--border-w) solid var(--border-default); border-radius: var(--radius-sm);
  padding: 3px 9px; text-decoration: none; white-space: nowrap;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
/* Every chip on the site fills with the accent tint on hover. It used to be a
   border shade and a text-colour shift, which is invisible on a 12px pill and
   left people unsure these were links at all (Stuart, 2026-08-22). The fill is
   what reads as "clickable" at this size. Keep the four chip classes in step:
   they appear together on an episode page, and one that doesn't light up looks
   broken rather than different. */
.meta-chip:hover {
  border-color: var(--border-accent); background: var(--bg-tint);
  color: var(--accent); text-decoration: none;
}

/* ============================================================
   Player block (components.html §2) — self-hosted video + audio
   ============================================================ */
/* Episode disclaimer — between the video and Listen on every episode page.
   Body-small like the guest bio, one step quieter in colour (muted rather than
   secondary) and italic, so it reads as a standing note rather than as part of
   the episode. --text-muted is 5.0:1 on --bg-base, so quiet still clears AA. */
.episode-disclaimer {
  margin: var(--space-lg) 0 0;
  font-size: var(--t-body-sm-size); line-height: var(--t-body-sm-lh);
  font-style: italic; color: var(--text-muted);
}

.player { background: var(--bg-surface); border: var(--border-w) solid var(--border-default); }
/* Video and audio are separate blocks so watching and listening read as two
   choices rather than one stacked control. */
.player--audio { margin-top: var(--space-lg); }
.player__video {
  aspect-ratio: 16 / 9; background: var(--bg-ink); position: relative;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  border: none; padding: 0; width: 100%;
}
.player__video video { display: block; width: 100%; height: 100%; object-fit: contain; }
.player__play {
  position: absolute; width: 64px; height: 64px; background: var(--accent);
  border: none; cursor: pointer; display: flex; align-items: center; justify-content: center;
  z-index: 2; transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.player__play:hover { background: var(--accent-hover); transform: scale(1.04); }
.player__play::after {
  content: ""; display: block; width: 0; height: 0; margin-left: 4px;
  border-style: solid; border-width: 11px 0 11px 18px;
  border-color: transparent transparent transparent var(--text-inverse);
}
/* Audio transport play button sits inline in the row, not overlaid on video. */
.player__play--inline { position: static; width: 40px; height: 40px; flex: 0 0 40px; }
.player__play--inline::after { border-width: 9px 0 9px 14px; }
/* Pause glyph: swap the CSS triangle for two bars via box-shadow, so no extra
   markup or SVG is needed and it inherits the same colour. */
.player--audio.is-playing .player__play::after {
  width: 4px; height: 16px; margin-left: 0; border: none;
  background: var(--text-inverse); box-shadow: 7px 0 0 var(--text-inverse);
  transform: translateX(-3px);
}
.player__play[disabled] { opacity: 0.4; cursor: not-allowed; }
.player__play[disabled]:hover { background: var(--accent); transform: none; }

/* Native <audio> is hidden while JS drives the custom transport. The noscript
   block in episode.njk reverses this, so JS-off gets a working player. */
.player__native { display: none; }

.player__transport {
  padding: 16px 20px; border-bottom: var(--border-w) solid var(--border-subtle);
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
/* The scrubber owns the click target and the focus ring; the two wave layers
   inside it are purely visual. */
.player__scrub {
  flex: 1 1 220px; min-width: 160px; height: 36px; position: relative;
  cursor: pointer; touch-action: none;
}
.player__scrub:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.player__wave {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: 2px; overflow: hidden;
}
/* Fixed-width bars, not flex:1 — letting them stretch turned a waveform into a
   row of blocks. Extra bars overflow and are clipped, so the trace fills any
   width without reflowing. */
.player__wave i { flex: 0 0 2px; width: 2px; background: var(--border-strong); }
/* Played portion: identical bars in accent, revealed by clip-path from the left. */
.player__wave--played { clip-path: inset(0 100% 0 0); }
.player__wave--played i { background: var(--accent); }

.player__actions { display: flex; align-items: center; gap: 6px; }
.player__btn {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  color: var(--text-secondary); background: var(--bg-base);
  border: var(--border-w) solid var(--border-default);
  padding: 6px 9px; cursor: pointer; font-variant-numeric: tabular-nums;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.player__btn:hover { border-color: var(--border-strong); color: var(--text-primary); }
.player__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.player__time {
  font-family: var(--font-mono); font-weight: 500; font-size: var(--t-timestamp-size);
  color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.player__subs { padding: 14px 20px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }

/* ============================================================
   Subscribe links (.subs / .sub-link)
   ------------------------------------------------------------
   ONE component, three placements: the episode player above, the homepage
   panel, and the ink footer. Markup lives in components/subscribe.njk.

   The icon is an inline SVG on currentColor, so the whole button — glyph
   included — recolours from the ONE `color` declaration in each variant. That
   is what lets the same markup sit on warm paper and on the ink footer with no
   second icon set. Don't give .sub-link__icon a colour of its own.
   ============================================================ */
.subs { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; }
.sub-link {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-weight: 500; font-size: 12px;
  letter-spacing: .03em; white-space: nowrap;
  color: var(--text-secondary); background: var(--bg-base);
  border: var(--border-w) solid var(--border-default);
  padding: 7px 13px; text-decoration: none;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
/* Fixed basis so a wrapping row keeps its glyphs aligned, and so the mark never
   squashes when the label is the long "Apple Podcasts". */
.sub-link__icon { flex: 0 0 16px; }
/* Same hover as the chips this replaced — see the .meta-chip hover. */
.sub-link:hover {
  border-color: var(--border-accent); background: var(--bg-tint);
  color: var(--accent); text-decoration: none;
}
.sub-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Homepage panel: bigger tap target, and the row is allowed to be the loudest
   thing in its own block because nothing else in it is a control. */
.subs--lg .sub-link { font-size: 13px; padding: 11px 17px; gap: 10px; }

/* Ink footer. Transparent rather than --bg-base, which is paper and would
   punch three light rectangles into the dark band. The border does the work. */
.subs--inverse .sub-link {
  background: transparent; color: var(--text-inverse-muted);
  border-color: var(--bg-ink-alt);
}
.subs--inverse .sub-link:hover {
  background: var(--bg-ink-alt); color: var(--text-inverse);
  border-color: var(--text-inverse-muted);
}
.subs--inverse .sub-link:focus-visible { outline-color: var(--text-inverse-muted); }

/* Homepage subscribe panel. Cover art, copy, buttons — three columns that
   collapse to one stack. Matches .signpost's surface + 1px border, so it reads
   as the same family as the two cards directly below it. */
.subscribe-panel {
  display: grid; grid-template-columns: auto 1fr auto;
  align-items: center; gap: var(--space-xl);
  background: var(--bg-surface); border: var(--border-w) solid var(--border-default);
  padding: var(--space-xl);
}
.subscribe-panel__art {
  width: 96px; height: 96px; display: block;
  border: var(--border-w) solid var(--border-default);
}
.subscribe-panel__copy .label { color: var(--accent); }
.subscribe-panel__copy h2 {
  font-family: var(--font-mono); font-weight: 600;
  font-size: var(--t-h3-size); line-height: var(--t-h3-lh);
  margin: var(--space-xs) 0 var(--space-2xs);
}
.subscribe-panel__copy p {
  font-size: var(--t-body-sm-size); color: var(--text-secondary); margin: 0;
}
/* Two breaks, not one. At 900px the buttons drop under the copy but the art
   stays beside it; only below 560px does the art go on its own line, because a
   96px square above a heading is a fine way to open a stacked card. */
@media (max-width: 900px) {
  .subscribe-panel { grid-template-columns: auto 1fr; row-gap: var(--space-lg); }
  .subscribe-panel .subs { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .subscribe-panel { grid-template-columns: 1fr; gap: var(--space-lg); }
  .subscribe-panel .subs { grid-column: auto; }
}

/* On the archive page the panel is a grid item inside #episodeGrid, so it has
   to span every column at each of .ep-grid's three breakpoints. `1 / -1` does
   that without repeating the column counts, which means changing .ep-grid above
   can't leave this stranded in column one. --bg-sunken rather than --bg-surface
   because the .ep-card tiles either side of it are already the surface colour,
   and a promo the same shade as the cards reads as a broken card. */
.subscribe-panel--in-grid { grid-column: 1 / -1; background: var(--bg-sunken); }
/* KNOWN AND ACCEPTED: at the 2-column breakpoint (601–900px) the panel follows
   the third tile, so the third tile sits alone and leaves one empty cell beside
   it. It's correct at 3 columns and at 1, and the position is a CONTENT
   decision — "after the first three episodes" — which can't track a column
   count fixed at build time. `grid-auto-flow: dense` would pull tile four into
   the hole and was rejected: it makes the visual order diverge from the DOM
   order a keyboard user tabs through, to close a gap that reads as a section
   break anyway. */

/* Footer "Listen on" block. Uses .footcol h3's type so the label matches the
   two column headings across the row from it. */
.site-footer__listen { margin-top: var(--space-lg); }
.site-footer__listen h3 {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-label-size);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-inverse-muted); margin: 0 0 var(--space-sm);
}

/* ============================================================
   Chapters
   ============================================================ */
.chapters { margin-top: var(--space-2xl); }
.chapters__list { list-style: none; margin: 0; padding: 0; border-top: var(--border-w) solid var(--border-subtle); }
.chapters__item { border-bottom: var(--border-w) solid var(--border-subtle); }
.chapters__btn {
  width: 100%; display: flex; align-items: baseline; gap: var(--space-md);
  background: none; border: none; cursor: pointer; text-align: left;
  padding: 11px 4px; font-family: inherit; color: var(--text-primary);
  transition: color var(--dur) var(--ease);
}
.chapters__btn:hover { color: var(--accent); }
.chapters__time {
  font-family: var(--font-mono); font-weight: 500; font-size: var(--t-timestamp-size);
  color: var(--text-muted); font-variant-numeric: tabular-nums; min-width: 48px;
}
.chapters__title { font-size: var(--t-body-size); }

/* ============================================================
   Highlights
   ============================================================ */
.highlights ol { margin: 0; padding-left: 1.4em; }
.highlights li { margin-bottom: var(--space-sm); font-size: var(--t-body-size); color: var(--text-secondary); }
.highlights li::marker { color: var(--accent); font-family: var(--font-mono); font-weight: 600; }

/* ============================================================
   Guest card
   ============================================================ */
.guest-card { background: var(--bg-surface); border: var(--border-w) solid var(--border-default); padding: var(--space-lg); }
.guest-card__row { display: flex; align-items: flex-start; gap: var(--space-lg); }
.guest-card__photo {
  flex: 0 0 88px; width: 88px; height: 88px; object-fit: cover;
  background: var(--bg-ink-alt); border: var(--border-w) solid var(--border-default);
}
.guest-card__avatar {
  flex: 0 0 88px; width: 88px; height: 88px; display: flex; align-items: center; justify-content: center;
  background: var(--bg-ink-alt); color: var(--text-inverse);
  font-family: var(--font-mono); font-weight: 600; font-size: 26px;
}
.guest-card__name { font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h3-size); margin: 0 0 4px; }
.guest-card__bio { font-size: var(--t-body-sm-size); color: var(--text-secondary); margin: 0 0 var(--space-sm); }

/* ============================================================
   CTA block (components.html §5)
   ============================================================ */
.cta {
  background: var(--bg-tint); border-left: var(--rule-accent) solid var(--border-accent);
  padding: 24px 26px;
}
.cta__kicker { color: var(--accent); margin-bottom: 10px; letter-spacing: .1em; }
.cta__title { font-family: var(--font-mono); font-weight: 600; font-size: 20px; line-height: 1.3; margin: 0 0 8px; max-width: 26ch; }
.cta__text { margin: 0 0 18px; font-size: var(--t-body-size); color: var(--text-secondary); max-width: 58ch; }
.cta__actions { display: flex; gap: 12px; flex-wrap: wrap; }

/* ============================================================
   Email capture (components.html §4)
   ============================================================ */
/* THE FORM IS THE FLEX CONTAINER, not this panel, and the copy is inside it.
   See components/email.njk for why. Short version: Turnstile is
   interaction-only, so the element holding it changes height at runtime, and
   while the copy and the form were siblings that height change re-centered the
   two columns and visibly moved the opt-in row (Mike, 2026-08-19).

   Now the copy and the input row are items on the same flex line and the widget
   wraps to a line of its own, so the first line's height is the copy's height
   whether or not a challenge appears. The input row does not move.

   Two fixes were tried and rejected before this one, both worth not repeating.
   align-items: flex-start pins the row but changes the no-widget layout too,
   top-aligning a form that should sit centered against the copy. Absolutely
   positioning the widget keeps the panel at its no-widget height and pushes the
   challenge out through the bottom edge onto whatever follows. */
.email {
  background: var(--bg-ink); padding: 26px 28px;
}
.email__form { display: flex; gap: 24px; flex-wrap: wrap; align-items: center; }
.email__copy { flex: 1; min-width: 240px; }
.email__title { font-family: var(--font-mono); font-weight: 600; font-size: 20px; color: var(--text-inverse); margin-bottom: 6px; }
.email__sub { font-size: 13px; color: var(--text-inverse-muted); }
.email__row { display: flex; }
.email__form input {
  border: var(--border-w) solid #5A6138; background: var(--bg-ink-alt); color: var(--text-inverse);
  padding: 12px 14px; font-family: var(--font-sans); font-size: 14px; outline: none; width: 200px;
}
.email__form input::placeholder { color: var(--text-inverse-muted); }
.email__form button {
  border: none; background: var(--accent); color: var(--text-inverse);
  font-family: var(--font-mono); font-weight: 600; font-size: 12px; letter-spacing: .06em;
  text-transform: uppercase; padding: 12px 18px; cursor: pointer;
}
.email__form button:hover { background: var(--accent-hover); }
/* Result of a submission, read from the query string by the script in
   components/email.njk. Full width so it takes the row under the form rather
   than squeezing it, on a dark panel so it borrows the inverse text colors. */
.email__msg {
  margin: 10px 0 0;
  font-family: var(--font-mono); font-size: 13px; line-height: 1.5;
}
.email__msg--ok { color: var(--text-inverse); }
.email__msg--err { color: #E8A87C; }
/* flex-basis: 100% puts the widget on its own line under both columns — the
   same trick .email__msg used to use, and the reason the input row above it
   never moves. justify-content keeps it under the input it belongs to rather
   than under the copy on the other side of the panel.

   interaction-only renders nothing until a challenge is needed, so :not(:empty)
   is what stops an empty container adding a phantom gap on every other page. */
.email__turnstile { flex-basis: 100%; display: flex; justify-content: flex-end; }
.email__turnstile:not(:empty) { margin-top: 10px; }
/* Both the widget and the result line hang off the right edge, under the input
   they belong to, rather than under the copy on the other side of the panel.
   Only once the panel is actually two columns — below that everything is
   stacked and left-aligned, and right-aligning it would look like a mistake. */
@media (min-width: 641px) {
  .email__msg { text-align: right; }
}
@media (max-width: 640px) {
  /* Stacked and left-aligned at this width, so the widget follows the copy
     instead of hanging off an edge that no longer means anything. */
  .email__form { align-items: flex-start; }
  .email__turnstile { justify-content: flex-start; }
}

/* ============================================================
   Guide card (Brutal Guides — foreign register)
   ============================================================ */
.guide { display: flex; gap: 18px; background: var(--bg-surface); border: var(--border-w) solid var(--border-default); padding: 18px; }
.guide__cover {
  flex: 0 0 108px; height: 150px; background: #1a1a17; border: 1px solid #000;
  display: flex; flex-direction: column; justify-content: space-between; padding: 12px 11px;
}
.guide__body { flex: 1; display: flex; flex-direction: column; }
.guide__title { font-family: var(--font-mono); font-weight: 500; font-size: 17px; line-height: 1.25; margin: 0 0 8px; }
.guide__desc { margin: 0 0 auto; font-size: 13px; color: var(--text-secondary); }
.guide__dl {
  margin-top: 14px; align-self: flex-start; font-family: var(--font-mono); font-weight: 600; font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-inverse); background: var(--bg-ink-alt);
  padding: 11px 16px; text-decoration: none;
}
.guide__dl:hover { color: var(--text-inverse); background: var(--accent); }

/* resources empty state */
.empty-note {
  border: var(--border-w) dashed var(--border-default); background: var(--bg-surface);
  padding: var(--space-2xl); text-align: center; color: var(--text-muted);
  font-family: var(--font-mono); font-size: 14px;
}

/* prose (show notes / about body) */
.prose { max-width: var(--measure); }
.prose p { margin: 0 0 var(--space-md); }
.prose h2 { font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h2-size); margin: var(--space-xl) 0 var(--space-sm); }
.prose h3 { font-family: var(--font-mono); font-weight: 500; font-size: var(--t-h3-size); margin: var(--space-lg) 0 var(--space-xs); }

/* ============================================================
   Page header (About / Resources / Guest)
   ============================================================ */
.page-head { border-top: var(--border-w-strong) solid var(--text-primary); padding-top: 16px; margin-bottom: var(--space-2xl); }
.page-head__kicker { color: var(--accent); margin-bottom: var(--space-sm); }
.page-head h1 {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-title-size);
  line-height: var(--t-title-lh); margin: 0 0 var(--space-md); max-width: 20ch;
}
/* THE DECK, and it has to be unmistakably not-body-copy. Widened 2026-08-23
   after a reviewer read it as the first paragraph of the article and asked
   whether the gap below it was a stray blank line — the same misread has come up
   on other sites in this family, so it's the treatment that was wrong rather
   than one reader.

   It used to differ from body copy on two axes and only just: 18px against 16px,
   and a slightly lighter ink. That reads as a paragraph someone nudged. It now
   differs on FOUR at once — 20px, medium weight, secondary ink, and noticeably
   tighter leading than the 1.65 the body runs at — which is what makes it
   scan as a standfirst before anybody reads a word of it.

   Size, weight and color only, by instruction. No box, no rule, no italic. The
   other lever worth knowing about and NOT taken here is the measure: a deck set
   to a shorter line than the body is the strongest signal of the lot, but it's a
   layout change rather than a type change. Raise it before reaching for a box. */
.page-head__lede {
  font-size: var(--t-lede-size); line-height: var(--t-lede-lh);
  font-weight: var(--weight-medium); color: var(--text-secondary);
  max-width: var(--measure); margin: 0;
}
/* `margin: 0` above is what keeps a single lede tight under the h1. A second
   one needs the gap back, or the two paragraphs run together. */
.page-head__lede + .page-head__lede { margin-top: var(--space-md); }

/* ---- Hosts (About) ---- */
.hosts { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-lg); margin-top: var(--space-lg); }
.host {
  background: var(--bg-surface); border: var(--border-w) solid var(--border-default);
  padding: var(--space-lg); display: flex; gap: var(--space-lg); align-items: flex-start;
}
.host__avatar {
  flex: 0 0 72px; width: 72px; height: 72px; display: flex; align-items: center; justify-content: center;
  background: var(--bg-ink-alt); color: var(--text-inverse);
  font-family: var(--font-mono); font-weight: 600; font-size: 22px;
}
/* Matches .host__avatar's footprint exactly, so a host with a photo and one
   without sit on the same grid without the row jumping. */
.host__photo {
  flex: 0 0 72px; width: 72px; height: 72px; object-fit: cover; display: block;
  border: var(--border-w) solid var(--border-default);
}
.host__name { font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h3-size); margin: 0 0 2px; }
.host__role { font-family: var(--font-mono); font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); margin-bottom: var(--space-sm); }
.host__bio { font-size: var(--t-body-sm-size); color: var(--text-secondary); margin: 0; }
/* The profile links reuse the guest card's treatment; they only need separating
   from the bio, which sits flush at margin 0. A column, because a host can carry
   two (LinkedIn and their own company) and they belong one per line —
   `align-items` keeps each one's hit area the width of its own label. */
.host__links { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-xs); margin-top: var(--space-sm); }
@media (max-width: 640px) { .hosts { grid-template-columns: 1fr; } }

/* ---- Value list (About "what you get") ---- */
.value-list { list-style: none; margin: 0; padding: 0; }
.value-list li { padding: var(--space-md) 0; border-bottom: var(--border-w) solid var(--border-subtle); display: flex; gap: var(--space-md); }
.value-list li::before { content: "→"; color: var(--accent); font-family: var(--font-mono); font-weight: 600; flex: 0 0 auto; }
.value-list strong { font-family: var(--font-mono); font-weight: 600; }

/* ---- Form (Be a guest) ---- */
.form { max-width: var(--content-w); }
.form__row { margin-bottom: var(--space-lg); }
.form__row--split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-lg); }
@media (max-width: 560px) { .form__row--split { grid-template-columns: 1fr; } }
.form label { display: block; font-family: var(--font-mono); font-weight: 600; font-size: var(--t-label-size); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-secondary); margin-bottom: var(--space-xs); }
.form input, .form select, .form textarea {
  width: 100%; font-family: var(--font-sans); font-size: var(--t-body-size);
  color: var(--text-primary); background: var(--bg-surface);
  border: var(--border-w) solid var(--border-default); border-radius: var(--radius-sm);
  padding: 11px 12px; outline: none; transition: border-color var(--dur) var(--ease);
}
.form input:focus, .form select:focus, .form textarea:focus { border-color: var(--border-strong); }
.form textarea { resize: vertical; min-height: 120px; }
.form__note { font-size: var(--t-caption-size); color: var(--text-muted); margin-top: var(--space-md); }

/* Honeypot. Off-screen rather than display:none — some bots skip hidden fields,
   and a field that is genuinely rendered but unreachable catches more of them.
   aria-hidden and tabindex="-1" in the markup keep it away from screen readers
   and the tab order, so nobody honest ever meets it. */
.form__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Submission error, shown by the script on /guest/ when the Worker sends one
   back as ?error=. Accent left rule, matching the card pattern used elsewhere. */
.form-error {
  max-width: var(--content-w); margin-bottom: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  background: var(--bg-tint); border: var(--border-w) solid var(--border-default);
  border-left: var(--rule-accent) solid var(--accent);
  color: var(--text-primary); font-size: var(--t-body-sm-size);
}

/* ---- Resources ---- */
.guide-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-lg); }
@media (max-width: 720px) { .guide-grid { grid-template-columns: 1fr; } }

/* generic block spacing on episode page */
.block { margin-top: var(--space-2xl); }
.block__head {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h2-size);
  line-height: var(--t-h2-lh); margin: 0 0 var(--space-md);
}

/* ---- Utilities assumed by the shared search core ----
   search-core.js is a vendored copy of MSB's, where these two are Tailwind
   utilities. ViPM has no Tailwind, so they're defined here. The core toggles
   `.hidden` on the status, clear button, results wrapper and card grid — if it
   isn't defined, every one of those stays permanently visible. */
.hidden { display: none !important; }
.no-underline { text-decoration: none; }

/* ---- Site search (episode index) ----
   Deliberately unlike .transcript__search on the episode page: that one is a
   compact inline control inside a toolbar, this is a full-width bar that owns
   the top of the listing. Site search finds which episode; transcript search
   finds where in an episode. They should not read as the same control. */
.site-search { margin-bottom: var(--space-2xl); }

.site-search__box {
  display: flex; align-items: center; gap: 4px;
  background: var(--bg-surface);
  border: var(--border-w) solid var(--border-default);
  border-radius: var(--radius-sm);
  padding: 0 12px;
  transition: border-color var(--dur) var(--ease);
}
.site-search__box:focus-within { border-color: var(--border-strong); }
.site-search__icon { color: var(--text-muted); font-size: 18px; line-height: 1; }
.site-search__input {
  flex: 1; border: none; background: transparent; outline: none;
  font-family: var(--font-sans); font-size: var(--t-body-size);
  color: var(--text-primary); padding: 13px 8px;
}
.site-search__input::placeholder { color: var(--text-muted); }
/* `type="search"` is kept for the mobile keyboard's search key, but WebKit's
   built-in clear decoration is suppressed: it renders as an off-palette blue
   cross sitting right next to our own Clear button, which reads as two
   different controls doing the same job. The transcript search box on episode
   pages has the same input type and the same reason. */
.site-search__input::-webkit-search-cancel-button,
.site-search__input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.site-search__clear {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-label-size);
  text-transform: uppercase; letter-spacing: .06em;
  background: none; border: none; cursor: pointer; padding: 6px 4px;
  color: var(--accent);
}
.site-search__clear:hover { color: var(--accent-hover); }

.site-search__status {
  font-size: var(--t-caption-size); color: var(--text-muted);
  margin: var(--space-md) 0 0;
}

.site-search__results { list-style: none; margin: var(--space-md) 0 0; padding: 0; }
.site-search__result { border-top: var(--border-w) solid var(--border-subtle); }
.site-search__result:first-child { border-top: none; }
.site-search__result-link {
  display: block; padding: var(--space-md) 0; text-decoration: none; color: inherit;
}
.site-search__result-link:hover .site-search__result-title { color: var(--accent-hover); }
.site-search__result-head {
  display: flex; align-items: baseline; gap: var(--space-sm); flex-wrap: wrap;
}
.site-search__result-title {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h3-size);
  line-height: var(--t-h3-lh); color: var(--accent);
}
/* One show today, so every badge says Episode. It earns its place when a
   second content type lands; until then it's a cheap, honest label. */
.site-search__badge {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-label-size);
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-muted); border: var(--border-w) solid var(--border-subtle);
  padding: 2px 6px; border-radius: var(--radius-sm);
}
.site-search__excerpt {
  font-size: var(--t-body-sm-size); line-height: var(--t-body-sm-lh);
  color: var(--text-secondary); margin: 6px 0 0;
}
/* Pagefind wraps hits in <mark>. Tinted rather than the browser default yellow,
   which fights the warm-paper palette. */
.site-search__excerpt mark {
  background: var(--bg-tint); color: var(--text-primary); font-weight: 600;
}

.site-search__empty {
  padding: var(--space-md) 0; font-size: var(--t-body-sm-size); color: var(--text-secondary);
}
.site-search__empty-action {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; text-decoration: underline;
}

.site-search__more-wrap { margin-top: var(--space-md); }
.site-search__more {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--t-label-size);
  text-transform: uppercase; letter-spacing: .06em;
  background: var(--bg-surface); color: var(--text-secondary);
  border: var(--border-w) solid var(--border-default);
  border-radius: var(--radius-sm); padding: 10px 14px; cursor: pointer;
}
.site-search__more:hover { border-color: var(--border-strong); color: var(--text-primary); }

/* ============================================================
   Certifications — display badge, full list, and index
   ============================================================ */

/* The one credential worth showing next to a name. Accent-tinted so it reads as
   a credential rather than more metadata, and sized to sit on the same baseline
   as whatever it follows without pushing the line height around. */
.cert-badge {
  display: inline-block; margin-left: var(--space-xs);
  font-family: var(--font-mono); font-weight: 600; font-size: 11px;
  line-height: 1.4; letter-spacing: .04em;
  color: var(--accent); background: var(--bg-tint);
  border: var(--border-w) solid var(--border-accent); border-radius: var(--radius-sm);
  padding: 2px 6px; text-decoration: none; white-space: nowrap;
  vertical-align: 1px;
  transition: background-color var(--dur) var(--ease);
}
a.cert-badge:hover { background: var(--bg-base); text-decoration: none; }

/* Inside an episode card, which is itself one big <a>. No nested anchor, so no
   hover affordance either — it must not look clickable when it isn't. */
.cert-badge--static { cursor: inherit; }

/* Branch + off-site profile link, on one row above the credential list. */
.guest-card__meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-xs) var(--space-md); margin-bottom: var(--space-sm);
}
.guest-card__social {
  font-family: var(--font-mono); font-weight: 600;
  font-size: var(--t-label-size); line-height: var(--t-label-lh);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--accent); text-decoration: none;
}
.guest-card__social:hover { color: var(--accent-hover); text-decoration: underline; }

/* Full credential list on the guest card. */
.cert-list { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-xs) var(--space-sm); }
.cert-list__label { color: var(--text-muted); }
.cert-list__items { display: flex; flex-wrap: wrap; gap: var(--space-xs); list-style: none; margin: 0; padding: 0; }
.cert-chip {
  display: inline-block;
  font-family: var(--font-mono); font-weight: 500; font-size: 12px;
  color: var(--text-secondary); background: var(--bg-base);
  border: var(--border-w) solid var(--border-default); border-radius: var(--radius-sm);
  padding: 4px 10px; text-decoration: none; white-space: nowrap;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
/* See the .meta-chip hover for why this is a fill rather than a border shade. */
.cert-chip:hover {
  border-color: var(--border-accent); background: var(--bg-tint);
  color: var(--accent); text-decoration: none;
}
/* There was a .cert-chip--degree here that accented the degree chip. Removed:
   the degree now also appears as a .cert-badge on the name line, so the card
   reads as an accented top half and a neutral chip-row bottom half. Its own
   "Degree" label is what distinguishes it from a certification, not its color. */

/* Index page. auto-fill with a MAX track width, not 1fr: several categories hold
   a single certification today, and a lone 1fr card stretches the full 1100px
   container and reads as a banner rather than one item in a set. Capping the
   track keeps a single card the same size as one in a full row. */
.cert-group { margin-bottom: var(--space-2xl); }
.cert-index {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 340px));
  gap: var(--space-md); list-style: none; margin: 0; padding: 0;
}
.cert-index__link {
  display: flex; flex-direction: column; gap: var(--space-2xs); height: 100%;
  background: var(--bg-surface); border: var(--border-w) solid var(--border-default);
  border-left: var(--rule-accent) solid var(--border-accent);
  padding: var(--space-md) var(--space-lg); text-decoration: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
/* Same complaint as the chips, same fix: the old hover moved the border one
   shade and added a 1px shadow, which on a card this size was easy to miss.
   The surface takes the accent tint, the title goes accent, and the shadow
   deepens. NO transform — nothing else on this site moves on hover, and a
   translate would need a prefers-reduced-motion block the stylesheet doesn't
   have. The left rule is recoloured, never thickened: it's a real border, so
   growing it would reflow the card's text by a pixel. */
.cert-index__link:hover {
  border-color: var(--border-strong); border-left-color: var(--border-accent);
  background: var(--bg-tint); box-shadow: var(--shadow-2); text-decoration: none;
}
.cert-index__link:hover .cert-index__name { color: var(--accent); }
.cert-index__name { font-family: var(--font-mono); font-weight: 600; font-size: var(--t-h3-size); color: var(--text-primary); transition: color var(--dur) var(--ease); }
.cert-index__full { font-size: var(--t-body-sm-size); color: var(--text-secondary); }
.cert-index__meta { color: var(--text-muted); margin-top: var(--space-2xs); }

/* /training-guides/ — the same tile and the same track as the grouped index
   above, deliberately, so the two pages read as one system. The only addition
   is the registry `description`, which runs 175-380 characters, so the meta
   line is pushed to the bottom rather than left floating under a short one.
   Don't widen the track to make the descriptions shorter: auto-fill counts
   repetitions off the MAX of minmax when both ends are definite, so 380px
   drops the row from three columns to two. */
.cert-index__desc {
  font-size: var(--t-body-sm-size); color: var(--text-secondary);
  margin-top: var(--space-2xs); line-height: 1.5;
}
.cert-index--guides .cert-index__meta { margin-top: auto; padding-top: var(--space-sm); }

/* Footnote under an index grid. Not `.empty-note`, which is a dashed empty
   state, and not `.tg-note`, which lives in guides.css and isn't loaded here. */
.index-note {
  margin-top: var(--space-xl); color: var(--text-muted);
  font-size: var(--t-body-sm-size); max-width: var(--measure);
}

@media (max-width: 640px) {
  .cert-index { grid-template-columns: 1fr; }
}

/* ============================================================
   Episode grid filters (/podcast/) — certification + branch
   ============================================================ */
.ep-filters { display: flex; flex-direction: column; gap: var(--space-sm); margin-bottom: var(--space-lg); }
.ep-filters__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
/* Fixed width so the two rows' chips line up with each other rather than
   starting at different offsets because "Branch" is shorter than
   "Certification". */
.ep-filters__label { color: var(--text-muted); flex: 0 0 auto; min-width: 160px; }
.filter-chip {
  display: inline-block;
  font-family: var(--font-mono); font-weight: 500; font-size: 12px; line-height: 1.4;
  color: var(--text-secondary); background: var(--bg-surface);
  border: var(--border-w) solid var(--border-default); border-radius: var(--radius-sm);
  padding: 5px 11px; text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
/* See the .meta-chip hover for why this is a fill rather than a border shade. */
.filter-chip:hover {
  border-color: var(--border-accent); background: var(--bg-tint);
  color: var(--accent); text-decoration: none;
}
.filter-chip.is-active {
  background: var(--bg-ink); border-color: var(--bg-ink); color: var(--text-inverse);
}
/* An ACTIVE chip stays dark on hover. `background` is restated rather than
   inherited from .is-active: that rule and .filter-chip:hover have equal
   specificity, so the tint would win on source order alone if either block ever
   moves. */
.filter-chip.is-active:hover {
  background: var(--bg-ink); border-color: var(--bg-ink); color: var(--text-inverse);
}

.ep-filters__empty { margin-top: var(--space-lg); color: var(--text-secondary); font-size: var(--t-body-sm-size); }
.ep-filters__reset {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; text-decoration: underline;
}

/* ---- Archive pagination ----
   Three columns rather than space-between, so the page count stays optically
   centred on page 1 and on the last page, where only one of the two links
   exists. The template emits an empty <span> for the missing side to hold the
   column — a disabled-looking arrow would invite a click that does nothing. */
.pagenav {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--space-md); margin-top: var(--space-2xl);
  border-top: var(--border-w) solid var(--border-subtle); padding-top: var(--space-lg);
}
.pagenav__link {
  font-family: var(--font-mono); font-weight: 500; font-size: 13px;
  text-decoration: none;
}
.pagenav__link:hover { text-decoration: underline; }
.pagenav__link[rel="next"] { justify-self: end; }
.pagenav__count {
  font-family: var(--font-mono); font-size: var(--t-caption-size);
  color: var(--text-muted); font-variant-numeric: tabular-nums;
}
@media (max-width: 520px) {
  .pagenav { grid-template-columns: 1fr; justify-items: center; gap: var(--space-sm); }
  .pagenav__link[rel="next"] { justify-self: center; }
  /* Count first on a phone: it's the orientation, and the two links are the
     actions. Order only moves in the single-column layout. */
  .pagenav__count { order: -1; }
}

@media (max-width: 640px) {
  .ep-filters__label { min-width: 0; flex-basis: 100%; }
}
