/* The Abide Project (discipleship_project/v1) — bundled CSS
 * Ported from the design source at "Discipleship Project - Revisited/shared.css".
 *
 * Token strategy:
 *   - Canonical Appearance tokens (--brand-primary, --bg-base, --bg-subtle,
 *     --bg-inverse, --text-primary, --text-on-inverse, --section-bg, etc.)
 *     are emitted by base.html from tenant theme. This file declares the
 *     template-local --accent-* and --paper-* variables for legacy rules and
 *     uses color-mix() to derive --accent-deep / --accent-tint so palette
 *     picks (including Custom) automatically deepen on hover and tint on bg.
 *   - body { background: ... } is NOT set here; base.html owns that.
 *   - Per docs/templates/01-source-and-assets.md §6, section-level surfaces
 *     are painted by the [data-tone] wrappers; block partials do NOT set
 *     their own outer background.
 */

:root {
  /* Editorial easing curve (animations.md §"Suggested easing"). */
  --reveal-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Template-local accent derivations from the canonical brand token.
   * Falls back to clay (#B5532B) if --brand-primary isn't bridged yet.
   *
   * Tint ratio was 22% / white originally; bumped to 32% so the
   * resulting salmon matches the DP-Revisited prototype's hand-tuned
   * #E9D4C3 active-row background (the previous 22% blend rendered too
   * pale next to the warm-paper surface). Same calc reasoning for
   * --accent-deep — kept at 80% / black, which already resolves
   * within a couple RGB units of the prototype's #8E3F1F. */
  --accent:      var(--brand-primary, #B5532B);
  --accent-deep: color-mix(in oklab, var(--brand-primary, #B5532B) 80%, black);
  --accent-tint: color-mix(in oklab, var(--brand-primary, #B5532B) 32%, white);

  /* Paper palette — used by inner elements (placeholders, framed photos,
   * card surfaces). Surface tone roles still drive section-level paint via
   * --section-bg from base.html. */
  --paper:        var(--bg-base, #F1EADB);
  --paper-warm:   var(--bg-subtle, #ECE3D0);
  --paper-deep:   var(--bg-elevated, #E4D9C0);
  --ink:          var(--text-primary, #1C1916);
  --ink-soft:     var(--text-secondary, #2D2924);
  --ink-mute:     var(--text-muted, #6B6358);
  --ink-faint:    #94897A;
  --rule:         var(--border-default, #D7CCB5);
  --rule-soft:    #E2D8C2;
  --dark:         var(--bg-inverse, #1F1B16);
  --dark-paper:   #2A2520;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  color: var(--ink);
  font-family: "Geist", -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
.serif { font-family: "Newsreader", Georgia, serif; font-weight: 400; letter-spacing: -0.005em; }
.mono  { font-family: "Geist Mono", ui-monospace, monospace; }
.eyebrow {
  font-family: "Geist Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 500;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
img { display: block; max-width: 100%; }
::selection { background: var(--accent); color: var(--paper); }

/* ─── Photo placeholder (10 tone-mats) ──────────────────────────────────
 * Shown when no image is bound. Hero blocks should always render a real
 * image (with a /static/discipleship_project/v1/images/hero.png fallback)
 * — these tones light up everywhere else in the empty/preview state. */
.photo {
  position: relative;
  overflow: hidden;
  background:
    repeating-linear-gradient(135deg, rgba(28,25,22,0.04) 0 12px, rgba(28,25,22,0.00) 12px 24px),
    linear-gradient(180deg, var(--paper-deep), var(--paper-warm));
  color: var(--ink-mute);
}
.photo .photo-label {
  position: absolute; inset: 0;
  display: flex; align-items: flex-end; justify-content: flex-start;
  padding: 16px;
  font-family: "Geist Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--ink-faint);
}
.photo.tone-warm   { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.05) 0 12px, transparent 12px 24px), linear-gradient(180deg, #D7C2A0, #C7AC83); }
.photo.tone-clay   { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.05) 0 12px, transparent 12px 24px), linear-gradient(180deg, #C9876E, #A55B3F); }
.photo.tone-olive  { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.05) 0 12px, transparent 12px 24px), linear-gradient(180deg, #8B9268, #5F6B3A); }
.photo.tone-plum   { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.06) 0 12px, transparent 12px 24px), linear-gradient(180deg, #7D5675, #4F2F4F); }
.photo.tone-navy   { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.06) 0 12px, transparent 12px 24px), linear-gradient(180deg, #46587C, #2A3B5E); }
.photo.tone-mustard{ background: repeating-linear-gradient(135deg, rgba(28,25,22,0.05) 0 12px, transparent 12px 24px), linear-gradient(180deg, #D9AE5C, #B5852A); }
.photo.tone-cream  { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.04) 0 12px, transparent 12px 24px), linear-gradient(180deg, var(--paper-deep), var(--paper-warm)); }
.photo.tone-ink    { background: repeating-linear-gradient(135deg, rgba(255,255,255,0.04) 0 12px, transparent 12px 24px), linear-gradient(180deg, #2D2924, #1C1916); color: #B0A696; }
.photo.tone-ink .photo-label { color: #8A7F6F; }
.photo.tone-coral  { background: repeating-linear-gradient(135deg, rgba(28,25,22,0.05) 0 12px, transparent 12px 24px), linear-gradient(180deg, #C66A6A, #8E3F3F); }

/* When a real image is bound, the <img> covers the tone-mat entirely;
 * the placeholder text hides itself. Block partials should render the
 * <img> as a child of the .photo container.
 *
 * `object-position: center 30%` is Layer 1 of the thumbnail strategy
 * (see memory item "Focal-point metadata + responsive crop pinning"):
 * most uploaded thumbnails have their subject in the upper half (faces,
 * titles, key visual elements). 30% favors that. Wide screenshots cropped
 * into portrait card slots still get clipped, but the upper portion
 * usually survives. Layer 3 (per-image focal_point) is future work. */
.photo.has-image .photo-label { display: none; }
.photo > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 30%;
  display: block;
}

/* Community block — left photo slot. Tenants drop poster-style content
 * here (Scripture infographics, reading-plan cards, group invites)
 * whose native aspect rarely matches the default 4:5 frame.
 *
 * Strategy: the slot loses its inline `aspect-ratio: 4/5` when an
 * image is bound (see the {% if not _p0_img %} guard in
 * `community/sticky_gallery.html`) and the image renders at natural
 * width × natural-aspect-derived height. The image fills the column
 * width with no inner empty space; container height adapts to
 * whatever the image needs. The 4:5 fallback only fires for the
 * placeholder (no-image) state.
 *
 * The image's `position: absolute; inset: 0` from `.photo > img`
 * gets overridden here so it flows naturally inside the container
 * instead of being stretched to fill a fixed box.
 *
 * `background: transparent` + `object-fit: contain` stay as defence
 * — if the image is constrained for any reason and doesn't reach the
 * container edges, the empty space goes invisible against the page
 * bg rather than showing the tone-gradient. */
.dp-community-left-photo.has-image > img {
  position: static;
  inset: auto;
  width: 100%;
  height: auto;
  object-fit: contain;
  object-position: center;
}
.dp-community-left-photo.has-image {
  background: transparent;
}

/* ─── Buttons + qlink ───────────────────────────────────────────────────*/
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 14px 22px;
  border-radius: var(--btn-radius, 999px);
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.005em;
  transition: transform .2s var(--reveal-ease),
              background .25s var(--reveal-ease),
              color .25s var(--reveal-ease);
}
.btn:hover { background: var(--ink-soft); }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--ink); color: var(--paper); }
.btn.accent { background: var(--accent); border-color: var(--accent); color: var(--paper); }
.btn.accent:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
.btn .arrow { transition: transform .25s var(--reveal-ease); }
.btn:hover .arrow { transform: translateX(3px); }

.qlink {
  display: inline-flex; align-items: baseline; gap: 8px;
  font-size: 14px; color: var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
  transition: gap .2s var(--reveal-ease);
}
.qlink:hover { gap: 12px; }
.qlink .arrow { font-size: 13px; }

/* ─── Layout wrappers ───────────────────────────────────────────────────*/
.wrap { max-width: 1280px; margin: 0 auto; padding: 0 40px; }
.wrap-tight { max-width: 1080px; margin: 0 auto; padding: 0 40px; }
.wrap-text { max-width: 760px; margin: 0 auto; padding: 0 40px; }

/* Prevent grid track blow-out from long descendants. */
.home-hero-grid > *,
.start-grid > *,
.themes-grid > *,
.series-feature-grid > *,
.series-secondary-grid > *,
.latest-grid > *,
.resources-grid > *,
.youth-grid > *,
.youth-card-grid > *,
.next-steps-grid > *,
.community-grid > *,
.community-gallery > *,
.footer-slim-grid > *,
.footer-slim-links > *,
.footer-full-top > *,
.footer-full-directory > * { min-width: 0; }

main [style*="display: grid"] > * { min-width: 0; }

.hairline { height: 1px; background: var(--rule); }

section { padding: 84px 0; }
section.tight { padding: 56px 0; }
section.dark .eyebrow { color: #A89A82; }

/* ─── Editorial primitives ──────────────────────────────────────────────*/
.lift { transition: transform .35s var(--reveal-ease); }
.lift:hover { transform: translateY(-2px); }

.seal {
  width: 64px; height: 64px;
  border-radius: 999px;
  border: 1px solid var(--ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: "Newsreader", serif;
  font-style: italic;
  font-size: 22px;
  color: var(--ink);
  background: transparent;
}

.numeral {
  font-family: "Newsreader", serif;
  font-style: italic;
  font-weight: 300;
  color: var(--ink-faint);
  line-height: 0.85;
}

/* The platform's bundled CSS (`static/dist/css/main.css`) applies
 * `.card { border-radius: 0.5rem; padding: 1.5rem; ... }` globally.
 * Abide's editorial design uses flat slabs (especially inside hairline
 * grids where rounded corners on adjacent cards would cut the 1px rule
 * into corner gaps). Explicitly reset the radius here; padding is
 * already overridden per-instance via the inline `style` on each card. */
.card { border-radius: 0; transition: background .25s var(--reveal-ease), border-color .25s var(--reveal-ease); }
.card:hover { background: rgba(28,25,22,0.02); }

.asterism {
  text-align: center; color: var(--ink-faint);
  font-family: "Newsreader", serif;
  letter-spacing: 0.5em; font-size: 14px; padding: 8px 0;
}
.asterism::before { content: "✻ ✻ ✻"; }

/* ─── Nav dropdowns (CSS-only hover/focus state, no JS in Phase 2b) ─────
 * .dp-nav-item is the wrapper around any nav link with children. The
 * dropdown panel opens on hover or keyboard focus-within for accessible
 * tab navigation. Touch devices fall back to a tap (the trigger <a>'s
 * href still navigates if the user tap-clicks instead of long-pressing
 * — mobile drawer is a Phase 3 concern). */
.dp-nav-item:hover .dp-nav-dropdown,
.dp-nav-item:focus-within .dp-nav-dropdown {
  opacity: 1 !important;
  visibility: visible !important;
}
.dp-nav-dropdown li a:hover {
  background: rgba(28,25,22,0.025);
}

/* ─── Mobile nav drawer ─────────────────────────────────────────────────
 * Hamburger trigger button — animates from 3 horizontal lines to an X
 * when the drawer is open. Hidden on desktop (display: none inline);
 * mobile media query flips display to inline-flex. */
.dp-nav-mobile-trigger {
  width: 42px;
  height: 42px;
  border: 1px solid var(--rule-soft);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  padding: 0;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color .2s var(--reveal-ease);
}
.dp-nav-mobile-trigger:hover {
  border-color: var(--ink);
}
.dp-nav-mobile-trigger-bars {
  width: 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.dp-nav-mobile-trigger-bars > span {
  display: block;
  height: 1.5px;
  background: currentColor;
  transition: transform .2s var(--reveal-ease), opacity .2s var(--reveal-ease);
}
.dp-nav-mobile-trigger[aria-expanded="true"] .dp-nav-mobile-trigger-bars > span:nth-child(1) {
  transform: translateY(5.5px) rotate(45deg);
}
.dp-nav-mobile-trigger[aria-expanded="true"] .dp-nav-mobile-trigger-bars > span:nth-child(2) {
  opacity: 0;
}
.dp-nav-mobile-trigger[aria-expanded="true"] .dp-nav-mobile-trigger-bars > span:nth-child(3) {
  transform: translateY(-5.5px) rotate(-45deg);
}

/* Drawer container — fixed overlay covering the viewport. Hidden by
 * default via the `hidden` HTML attribute; runtime.js removes it. */
.dp-nav-drawer {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  justify-content: flex-end;
}
.dp-nav-drawer[hidden] {
  display: none;
}
.dp-nav-drawer-scrim {
  position: absolute;
  inset: 0;
  background: rgba(28,25,22,0.42);
  backdrop-filter: saturate(140%) blur(4px);
  opacity: 0;
  transition: opacity .25s var(--reveal-ease);
}
.dp-nav-drawer.is-open .dp-nav-drawer-scrim {
  opacity: 1;
}
.dp-nav-drawer-panel {
  position: relative;
  /* Full-width on mobile so the menu reads as a full-screen overlay (matching
   * the search overlay), not a 380px side drawer. The drawer only renders
   * ≤1023px (hamburger breakpoint), so full-width is correct everywhere it shows. */
  width: 100%;
  max-width: 100%;
  height: 100%;
  background: var(--bg-base, var(--paper));
  color: var(--ink);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform .3s var(--reveal-ease);
  overflow-y: auto;
}
.dp-nav-drawer.is-open .dp-nav-drawer-panel {
  transform: translateX(0);
}
.dp-nav-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 24px;
  border-bottom: 1px solid var(--rule-soft);
  position: sticky;
  top: 0;
  background: var(--bg-base, var(--paper));
  z-index: 1;
}
.dp-nav-drawer-close {
  width: 38px;
  height: 38px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  font-size: 16px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.dp-nav-drawer-close:hover {
  background: var(--ink);
  color: var(--bg-base, var(--paper));
  border-color: var(--ink);
}
.dp-nav-drawer-body {
  display: flex;
  flex-direction: column;
  padding: 8px 24px 32px;
}
.dp-nav-drawer-section {
  border-top: 1px solid var(--rule-soft);
  padding: 20px 0;
}
.dp-nav-drawer-section:first-child {
  border-top: none;
}
.dp-nav-drawer-link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  padding: 10px 0;
  color: var(--ink);
}
.dp-nav-drawer-sub {
  list-style: none;
  padding: 0;
  margin: 6px 0 0;
  padding-left: 2px;
}
.dp-nav-drawer-sublink {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 18px;
  align-items: center;
  padding: 12px 0 12px 22px;
  margin-left: 4px;
  border-left: 1px solid var(--rule-soft);
  color: var(--ink);
}
.dp-nav-drawer-sublink:hover {
  border-left-color: var(--accent);
}

/* When drawer is open, lock body scroll. JS adds .dp-nav-locked. */
html.dp-nav-locked,
html.dp-nav-locked body {
  overflow: hidden !important;
}

/* ─── Header chrome ─────────────────────────────────────────────────────*/
header.site {
  position: sticky; top: 0; z-index: 50;
  background: rgba(241,234,219,0.92);
  backdrop-filter: saturate(140%) blur(12px);
  -webkit-backdrop-filter: saturate(140%) blur(12px);
  border-bottom: 1px solid var(--rule-soft);
}

:root {
  --header-h: 115px;
  --space-section:    100px;
  --space-section-sm: 72px;
  --space-grid-gap:   32px;
  --space-eyebrow-h1: 28px;
  --space-h1-deck:    32px;
}
@media (max-width: 720px) {
  :root { --header-h: 132px; }
}

/* ─── Tablet composition (768–1023) ─────────────────────────────────────*/
@media (min-width: 768px) and (max-width: 1023px) {
  :root {
    --space-section:    64px;
    --space-section-sm: 48px;
    --space-grid-gap:   24px;
    --space-eyebrow-h1: 18px;
    --space-h1-deck:    22px;
  }
  /* Generic section rhythm. Sections that declare their OWN inline padding
   * (detail partials especially) opt out via `:not([style*="padding"])`
   * — otherwise the !important here would clobber their inline values.
   * Pattern documented in [reference/gotchas.md] inline-style cascade. */
  main > section:not([style*="padding"]) {
    padding-top: var(--space-section) !important;
    padding-bottom: var(--space-section) !important;
  }
  main > section.tight:not([style*="padding"]) {
    padding-top: var(--space-section-sm) !important;
    padding-bottom: var(--space-section-sm) !important;
  }

  .start-grid,
  .themes-grid,
  .resources-grid,
  .next-steps-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  .series-feature-grid {
    grid-template-columns: 1fr 1fr !important;
    gap: 36px !important;
    margin-bottom: 48px !important;
  }
  .series-secondary-grid {
    gap: 36px !important;
    padding-top: 40px !important;
  }
  .series-compact-card {
    grid-template-columns: 1fr 1.2fr !important;
    gap: 18px !important;
  }

  .latest-grid,
  .youth-grid,
  .community-grid {
    gap: 40px !important;
  }
  .community-grid {
    grid-template-columns: 1fr 1.2fr !important;
  }
  .community-sticky {
    position: static !important;
  }

  .home-hero { padding-top: 40px !important; padding-bottom: 56px !important; }
  .home-hero-grid { gap: 36px !important; }
  .hero-formation .hero-media .photo {
    aspect-ratio: 4 / 5 !important;
    height: auto !important;
  }
  .hero-teaching-photo {
    height: auto !important;
    min-height: 0 !important;
    aspect-ratio: 4 / 5 !important;
  }
  .hero-next-grid {
    grid-template-columns: 1fr 1fr !important;
  }

  .home-hero h1 { font-size: clamp(44px, 6vw, 64px) !important; }
  .section-head h2,
  .section-head-split h2 { font-size: clamp(32px, 4.6vw, 44px) !important; }

  .home-hero p,
  .latest-grid p,
  .youth-grid p,
  .community-grid p { max-width: 480px !important; }

  .footer-full-directory {
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  }
}

/* ─── Touch-target floor ────────────────────────────────────────────────*/
@media (hover: none) and (pointer: coarse) {
  .qlink {
    padding-top: 12px; padding-bottom: 12px;
    min-height: 44px; align-items: center;
  }
  .site-actions button { min-width: 44px; min-height: 44px; }
  .footer-col-link {
    display: inline-flex; align-items: center; min-height: 36px;
  }
  .footer-slim-links a,
  .footer-legal a {
    display: inline-flex; align-items: center; min-height: 36px; padding: 4px 0;
  }
}

/* ─── Desktop heroes fit within first viewport ──────────────────────────*/
@media (min-width: 1024px) {
  .home-hero {
    min-height: calc(100svh - var(--header-h));
    display: flex;
    align-items: center;
    padding-top: 24px !important;
    padding-bottom: 32px !important;
  }
  .home-hero > .wrap { width: 100%; }
  .home-hero .home-hero-grid { align-items: center; }

  .hero-formation h1 { font-size: clamp(48px, 5.4vw, 88px) !important; }
  .hero-formation .hero-media .photo {
    aspect-ratio: auto !important;
    height: calc(100svh - var(--header-h) - 96px) !important;
    max-height: 720px;
    min-height: 440px;
  }

  .hero-teaching h1 { font-size: clamp(44px, 5vw, 76px) !important; }
  .hero-teaching .home-hero-grid { min-height: 0 !important; align-items: stretch; }
  .hero-teaching .hero-teaching-photo {
    min-height: 0 !important;
    height: calc(100svh - var(--header-h) - 96px) !important;
    max-height: 720px;
  }

  .hero-next h1 { font-size: clamp(46px, 5.4vw, 80px) !important; }
  .hero-next .hero-next-grid {
    height: calc(100svh - var(--header-h) - 120px);
    max-height: 720px;
    min-height: 440px;
  }
  .hero-next .hero-next-grid .card { min-height: 0 !important; }

  /* Youth dark hero — same first-viewport treatment as the homepage
   * heroes: the section fills 100svh below the header, content centers,
   * and the portrait media frame is capped to height (overriding its
   * `aspect-ratio: 4/5`) so the whole hero fits without scrolling. */
  .youth-hero {
    min-height: calc(100svh - var(--header-h));
    display: flex;
    align-items: center;
    padding-top: 24px !important;
    padding-bottom: 32px !important;
  }
  .youth-hero > .wrap { width: 100%; }
  .youth-hero .youth-hero-grid { align-items: center; }
  .youth-hero-frame {
    aspect-ratio: auto !important;
    height: calc(100svh - var(--header-h) - 96px) !important;
    max-height: 720px;
    min-height: 440px;
  }
}

/* ─── 1440 / mid-laptop design state ────────────────────────────────────*/
@media (min-width: 1280px) and (max-width: 1599px) {
  :root {
    --space-section:    72px;
    --space-section-sm: 56px;
    --space-grid-gap:   28px;
    --space-eyebrow-h1: 22px;
    --space-h1-deck:    26px;
  }

  main > section:not([style*="padding"]) {
    padding-top: var(--space-section) !important;
    padding-bottom: var(--space-section) !important;
  }
  main > section.tight:not([style*="padding"]) {
    padding-top: var(--space-section-sm) !important;
    padding-bottom: var(--space-section-sm) !important;
  }

  .home-hero p,
  .latest-grid p,
  .youth-grid p,
  .community-grid p { max-width: 560px !important; }

  .home-hero h1      { font-size: clamp(48px, 4.8vw, 72px) !important; }
  .hero-formation h1 { font-size: clamp(48px, 4.8vw, 72px) !important; }
  .hero-teaching h1  { font-size: clamp(44px, 4.6vw, 68px) !important; }
  .hero-next h1      { font-size: clamp(46px, 4.8vw, 70px) !important; }
  .home-hero .eyebrow,
  section .eyebrow { font-size: 11.5px; }

  .section-head h2,
  .section-head-split h2 { font-size: clamp(36px, 4.2vw, 52px) !important; }
  .section-head { margin-bottom: 36px !important; }
  .section-head p { font-size: 16px !important; }

  .start-grid             { gap: 28px !important; }
  .series-feature-grid    { gap: 44px !important; margin-bottom: 56px !important; }
  .series-secondary-grid  { gap: 44px !important; padding-top: 44px !important; }
  .latest-grid,
  .community-grid         { gap: 56px !important; }
  .youth-grid             { gap: 56px !important; }

  .home-hero-grid { gap: 48px !important; }
  .closing-cta-actions { margin-top: 36px !important; }
}

/* ─── Hero variant: full-bleed image with dark overlay ──────────────────
 * This is the default homepage hero (per onboarding decision). The image
 * escapes the .wrap gutter and a 2-stop gradient keeps text legible. */
.hero-image-overlay {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  padding: 0 !important;
  color: var(--paper);
  background: #1C1916;
  min-height: 560px;
}
.hero-image-overlay-media {
  position: absolute; inset: 0; z-index: 0;
}
.hero-image-overlay-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* `image_object_position_vars` (the partial macro) emits only the
   * `--focal-*` CSS variables on the inline `style`. `object-position`
   * lives HERE so the per-viewport `@media` overrides further down
   * win via normal cascade ordering — an inline `object-position`
   * would beat every external rule and the breakpoints would never
   * fire. When no pinning exists the variable is unset and the
   * `center 35%` fallback applies. */
  object-position: var(--focal-default, center 35%);
  display: block;
}
/* Viewport aspect-ratio bands → closest canonical ratio pin. The
 * hero is full-bleed, so its container ratio matches the viewport.
 *
 * The cascade below walks band → adjacent bands (by aspect distance)
 * → `--focal-default` → `center 35%`. The reason: if the editor
 * pinned 1:1 and 4:5 but never touched 16:9, a desktop visitor would
 * otherwise see only the auto-saliency default (often `50% 50%`),
 * making per-ratio edits feel like no-ops on viewports they didn't
 * specifically pin. Falling through other PINNED ratios in distance
 * order respects the editor's intent ("the subject is here") even on
 * an un-pinned band, and only drops to default when nothing is set. */
@media (max-aspect-ratio: 9/10) {
  /* Tall portrait (phones) — primary 4:5 */
  .hero-image-overlay-media img {
    object-position: var(--focal-4x5,
                     var(--focal-1x1,
                     var(--focal-5x4,
                     var(--focal-4x3,
                     var(--focal-16x10,
                     var(--focal-16x9,
                     var(--focal-default, center 35%)))))));
  }
}
@media (min-aspect-ratio: 9/10) and (max-aspect-ratio: 11/10) {
  /* Near-square (tablets in portrait) — primary 1:1 */
  .hero-image-overlay-media img {
    object-position: var(--focal-1x1,
                     var(--focal-4x5,
                     var(--focal-5x4,
                     var(--focal-4x3,
                     var(--focal-16x10,
                     var(--focal-16x9,
                     var(--focal-default, center 35%)))))));
  }
}
@media (min-aspect-ratio: 11/10) and (max-aspect-ratio: 27/20) {
  /* Tablet landscape / narrow laptop — primary 5:4 / 4:3 */
  .hero-image-overlay-media img {
    object-position: var(--focal-5x4,
                     var(--focal-4x3,
                     var(--focal-1x1,
                     var(--focal-16x10,
                     var(--focal-4x5,
                     var(--focal-16x9,
                     var(--focal-default, center 35%)))))));
  }
}
@media (min-aspect-ratio: 27/20) and (max-aspect-ratio: 16/10) {
  /* Standard laptop / monitor — primary 16:10 */
  .hero-image-overlay-media img {
    object-position: var(--focal-16x10,
                     var(--focal-4x3,
                     var(--focal-5x4,
                     var(--focal-16x9,
                     var(--focal-1x1,
                     var(--focal-4x5,
                     var(--focal-default, center 35%)))))));
  }
}
@media (min-aspect-ratio: 16/10) {
  /* Widescreen — primary 16:9 */
  .hero-image-overlay-media img {
    object-position: var(--focal-16x9,
                     var(--focal-16x10,
                     var(--focal-4x3,
                     var(--focal-5x4,
                     var(--focal-1x1,
                     var(--focal-4x5,
                     var(--focal-default, center 35%)))))));
  }
}
.hero-image-overlay-scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg,
      rgba(28,25,22,0.30) 0%,
      rgba(28,25,22,0.55) 45%,
      rgba(28,25,22,0.78) 100%
    ),
    linear-gradient(90deg,
      rgba(28,25,22,0.55) 0%,
      rgba(28,25,22,0.20) 60%,
      rgba(28,25,22,0.10) 100%
    );
}
.hero-image-overlay-content {
  position: relative; z-index: 1;
  padding-top: clamp(64px, 12vh, 140px);
  padding-bottom: clamp(56px, 10vh, 120px);
  max-width: 1280px;
}
.hero-image-overlay-content .eyebrow {
  color: rgba(241,234,219,0.78) !important;
}
.hero-image-overlay-content .btn.ghost:hover {
  background: rgba(241,234,219,0.12);
}

@media (min-width: 1024px) {
  .hero-image-overlay {
    min-height: calc(100svh - var(--header-h));
    display: flex; align-items: center;
  }
  .hero-image-overlay-content {
    width: 100%; padding-top: 32px; padding-bottom: 48px;
  }
}
@media (min-width: 1280px) and (max-width: 1599px) {
  .hero-image-overlay-title { font-size: clamp(48px, 5.4vw, 88px) !important; }
  .hero-image-overlay-deck  { max-width: 560px !important; font-size: 17px !important; }
}
@media (min-width: 768px) and (max-width: 1023px) {
  .hero-image-overlay { min-height: 520px; }
  .hero-image-overlay-title { font-size: clamp(48px, 7vw, 72px) !important; }
  .hero-image-overlay-content { padding-top: 64px; padding-bottom: 72px; }
}
@media (max-width: 720px) {
  .hero-image-overlay { min-height: 480px; }
  .hero-image-overlay-title { font-size: clamp(40px, 11vw, 60px) !important; }
  .hero-image-overlay-deck  { font-size: 16px !important; max-width: none !important; }
  .hero-image-overlay-content { padding-top: 48px; padding-bottom: 56px; }
  /* `object-position` is owned by the viewport-aspect-ratio bands above so
   * the editor's per-ratio focal pin wins. Don't hardcode it here. */
}

/* Below ~760px viewport-height, fall back to natural flow so content
   doesn't get crushed on a small laptop. */
@media (min-width: 1024px) and (max-height: 760px) {
  .home-hero {
    min-height: 0; align-items: stretch;
    padding-top: 40px !important; padding-bottom: 56px !important;
  }
  .hero-formation .hero-media .photo,
  .hero-teaching .hero-teaching-photo,
  .hero-next .hero-next-grid {
    height: auto !important; max-height: none;
  }
  .hero-formation .hero-media .photo { aspect-ratio: 4 / 5 !important; }
  .hero-image-overlay { min-height: 560px; }
  .hero-image-overlay-content { padding-top: 56px; padding-bottom: 64px; }
}

/* ─── Detail-page primitives (Phase 3a) ─────────────────────────────────
 * Styles for /contents/{article,video,audio}/<id> and /series/<slug>.
 * Templates compose detail pages from block partials at
 * public/blocks/discipleship_project/detail/*.html. Most of the layout
 * is inline-styled per-partial; this block is the prose + media-player
 * + transcript-sync chrome that benefits from stylesheet-level rules. */

/* Editorial prose for article body + series intro. Uses the body
 * `--sans` family with serif drop-cap-like first letter feel via type
 * scale, not actual drop caps. Narrow reading width. */
.dp-prose {
  font-family: var(--sans);
  font-size: 18px;
  line-height: 1.7;
  color: var(--ink);
}
/* `shared/accessibility.css` loads AFTER this file and sets
 * `[data-a11y-prose] { font-size: calc(1em * var(--a11y-font-scale,1)) }`
 * at equal specificity — which silently re-resolved the column to the
 * inherited 16px and discarded the 18px above. Re-assert the editorial
 * base at higher specificity while preserving the a11y font-scale slider. */
.dp-prose[data-a11y-prose] {
  font-size: calc(18px * var(--a11y-font-scale, 1));
}
.dp-prose > *:first-child { margin-top: 0; }
.dp-prose > *:last-child { margin-bottom: 0; }
.dp-prose h2,
.dp-prose h3,
.dp-prose h4 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin: 1.6em 0 0.45em;
}
.dp-prose h2 { font-size: 32px; }
.dp-prose h3 { font-size: 26px; }
.dp-prose h4 { font-size: 21px; }
/* Inter-block rhythm. Symmetric vertical margins (they collapse between
 * siblings) mirror the admin editor's reading spec so the published page
 * matches what authors see in `edit_content.html`. NOTE: keep these on the
 * element selectors (specificity 0,1,1) — a `> * + *` owl rule (0,1,0) loses
 * the tie to these resets and the column collapses to a wall of text. */
.dp-prose p { margin: 0.85em 0; }
.dp-prose a {
  color: inherit;
  border-bottom: 1px solid var(--ink-soft);
  padding-bottom: 1px;
}
.dp-prose a:hover { border-bottom-color: var(--accent); color: var(--accent); }
.dp-prose blockquote {
  margin: 1.1em 0;
  padding: 8px 0 8px 28px;
  border-left: 2px solid var(--accent);
  font-family: var(--serif);
  font-style: italic;
  font-size: 22px;
  line-height: 1.45;
  color: var(--ink);
}
.dp-prose ul,
.dp-prose ol { margin: 0.85em 0; padding-left: 1.5em; }
.dp-prose li + li { margin-top: 0.4em; }
.dp-prose img { max-width: 100%; height: auto; display: block; margin: 1.2em 0; }
.dp-prose figcaption {
  font-family: var(--sans);
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: var(--ink-mute);
  margin-top: 8px;
}

/* Audio player + transcript chrome. Layout is inline-styled in the
 * partial; the bits below are for state — playing-state glyph swap,
 * hover, active transcript line highlight.
 *
 * The play/pause button is a single fixed-size circle that swaps
 * between two inline SVG glyphs depending on `aria-pressed`. The
 * shared transcript_audio_player.js toggles aria-pressed on play. */
.dp-audio-play {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 999px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper, var(--bg-base));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background .15s var(--reveal-ease), color .15s var(--reveal-ease);
}
.dp-audio-play:hover { background: var(--accent); border-color: var(--accent); }
.dp-audio-play .dp-audio-glyph { display: none; }
.dp-audio-play[aria-pressed="false"] .dp-audio-glyph-play,
.dp-audio-play:not([aria-pressed]) .dp-audio-glyph-play { display: block; }
.dp-audio-play[aria-pressed="true"] .dp-audio-glyph-pause { display: block; }
/* Optical centering: the play triangle's visual centroid sits ~1px
 * left of its geometric center; nudge it right inside the button. */
.dp-audio-play .dp-audio-glyph-play { transform: translateX(1px); }
/* Transcript rows (KC-derived pattern, refined per the DP-Revisited
 * prototype). The resting state ships inline from the template; these
 * rules paint hover tint and the dropped border on the last row.
 *
 * The active-row salmon highlight + left-accent border + accent-deep
 * timestamp + accent-toned bare-triangle jump button live further
 * down in this file under the dedicated "DP-Revisited audio detail
 * port" section so a single source of truth controls the look. */
.dp-audio-transcript-line:last-child { border-bottom: 0 !important; }

/* Path-detail page (Phase 3d) — magazine vertical-rhythm layout.
 * Hero with 2-col grid (title block + TOC), then one section per
 * playable item with text-left / player-right split. Article items
 * appear in the TOC only and link out to /contents/article/<id>.
 * No click-to-swap stage — each player wires via the shared
 * `data-kms-*` modules auto-discovered at DOMContentLoaded. */

/* Hero + TOC */
.dp-path-toc { background: var(--bg-base, var(--paper)); }
.dp-path-toc-item {
  text-decoration: none;
  transition: color .15s var(--reveal-ease), padding-left .2s var(--reveal-ease);
}
.dp-path-toc-item:hover { color: var(--accent); padding-left: 4px; }
.dp-path-toc-item:hover .dp-path-toc-item-arrow,
.dp-path-toc-item:hover .serif {
  color: var(--accent);
}
.dp-path-toc-item-arrow {
  transition: transform .2s var(--reveal-ease), color .15s var(--reveal-ease);
}
.dp-path-toc-item:hover .dp-path-toc-item-arrow {
  transform: translateX(3px);
}

/* Per-item module rhythm. Soft top-rule between items keeps the
 * vertical stack readable without heavy separators. Generous padding
 * gives each item its own breathing room. */
.dp-path-item { /* padding + border-top set inline so the section
                    is one editorial unit even if a future variant
                    skips a border. */ }
.dp-path-item:first-of-type {
  /* The first item module follows the hero (which already has
     bottom padding) — drop the redundant top rule. */
  border-top: 0;
}
.dp-path-item-text h2 {
  /* Keep the H2 readable when it lands above an audio poster strip
     on narrower viewports — clamp already in inline style. */
}

/* Scroll-anchor offset so anchor jumps from the TOC don't bury the
 * item title under the platform header / breadcrumb chrome. */
.dp-path-item {
  scroll-margin-top: 24px;
}

/* ─── Listing pages (series/sermons/notes/teachings/paths/themes) ─────
 * Shared scaffolding lives in /blocks/discipleship_project/listing/*.
 * These rules style the bits that can't comfortably live as inline
 * style strings: segmented-pill active state, search input focus,
 * card lift, and the empty-state visibility toggle. */

/* Segmented filter — active pill flips background to ink, label to
 * paper. JS toggles `.is-active`; we don't fight inline `style="…"`
 * because the inline declares the resting state and `!important` here
 * wins the cascade for the active state. */
.dp-listing-filter-pill.is-active {
  background: var(--ink, #1F1B16) !important;
  color: var(--paper, #F1EADB) !important;
}

/* Search input under-line lift on focus. Inline style ships a
 * transparent border-bottom; we paint it on :focus-visible. */
.dp-listing-filter-search input:focus-visible {
  border-bottom-color: var(--ink, #1F1B16) !important;
}

/* Card hover lift — quiet, editorial. Same easing as reveal. */
.dp-listing-card {
  transition: transform 0.25s var(--reveal-ease, ease), box-shadow 0.25s var(--reveal-ease, ease);
}
.dp-listing-card:hover {
  transform: translateY(-3px);
}
.dp-listing-card:hover .dp-series-card-img img,
.dp-listing-card:hover .dp-sermon-card-img img,
.dp-listing-card:hover .dp-note-card-img img,
.dp-listing-card:hover .dp-path-card-img img,
.dp-listing-card:hover .dp-theme-card-img img {
  transform: scale(1.02);
}
.dp-listing-card .dp-series-card-img img,
.dp-listing-card .dp-sermon-card-img img,
.dp-listing-card .dp-note-card-img img,
.dp-listing-card .dp-path-card-img img,
.dp-listing-card .dp-theme-card-img img {
  transition: transform 0.6s var(--reveal-ease, ease);
}

/* Hidden cards (set by initListingFilter) collapse out of the grid. */
.dp-listing-card[hidden] { display: none !important; }

/* ─── Resources library (/resources) ──────────────────────────────────
 * The curated shelf + the filterable resource grid. Both grids are
 * hairline-ruled (1px gap over a --rule background). The responsive
 * rules change column COUNT only — the gap stays 1px. (This is why the
 * resource grid does NOT carry the shared `.dp-listing-grid` class:
 * that class's collapse rule widens the gap to 40px, which would break
 * the hairline. Cards still carry `.dp-listing-card` for the filter's
 * hover + `[hidden]` behaviour.) */
.dp-resource-card-img img,
.dp-resources-curated-img img {
  transition: transform 0.6s var(--reveal-ease, ease);
}
.dp-listing-card:hover .dp-resource-card-img img,
.dp-resources-curated-card:hover .dp-resources-curated-img img {
  transform: scale(1.03);
}
.dp-resources-curated-card {
  transition: background 0.25s var(--reveal-ease, ease), transform 0.25s var(--reveal-ease, ease);
}
.dp-resources-curated-card:hover {
  transform: translateY(-3px);
}
@media (max-width: 1023px) {
  .dp-page .dp-resource-grid,
  .dp-page .dp-resources-curated-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}
@media (max-width: 720px) {
  .dp-page .dp-resource-grid,
  .dp-page .dp-resources-curated-grid {
    grid-template-columns: 1fr !important;
  }
}

/* ─── Youth landing page (/youth) ──────────────────────────────────────
 * Reuses the homepage editorial grammar. The dark hero and closing band
 * declare tone_role:inverse (painted by the section wrapper) and carry
 * the youth lane's gold accent inline. The hairline grids (paths-grid,
 * resources, next-steps) join the hairline-restore rules above so their
 * 1px dividers survive the mobile collapse. */
.youth-path-card,
.youth-question-card,
.youth-resource-card,
.youth-step-card {
  transition: transform 0.25s var(--reveal-ease, ease), border-color 0.2s var(--reveal-ease, ease);
}
.youth-path-card:hover,
.youth-resource-card:hover,
.youth-step-card:hover {
  transform: translateY(-3px);
}
.youth-question-card:hover {
  border-color: var(--ink) !important;
}
.youth-paths-showcase > a {
  transition: border-color 0.2s var(--reveal-ease, ease);
}
.youth-paths-showcase > a:hover {
  border-color: var(--ink) !important;
}
/* Tablet — 3-col youth grids fold to 2-col. */
@media (max-width: 1100px) {
  .dp-page .youth-paths-grid,
  .dp-page .youth-resources-grid,
  .dp-page .youth-next-steps-grid,
  .dp-page .youth-questions-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}
/* Tablet/landscape phone — the 2-col editorial splits stack. */
@media (max-width: 860px) {
  .dp-page .youth-hero-grid,
  .dp-page .youth-featured-grid,
  .dp-page .youth-community-grid {
    grid-template-columns: 1fr !important;
    gap: 40px !important;
  }
  .dp-page .youth-community-sticky {
    position: static !important;
  }
}
/* Phone — everything is one column. */
@media (max-width: 720px) {
  .dp-page .youth-paths-grid,
  .dp-page .youth-resources-grid,
  .dp-page .youth-next-steps-grid,
  .dp-page .youth-questions-grid,
  .dp-page .youth-paths-showcase,
  .dp-page .youth-community-gallery {
    grid-template-columns: 1fr !important;
  }
}

/* ─── Search overlay (global chrome) ──────────────────────────────────
 * Injected once by base.html, hidden via the `hidden` attribute. The
 * `.dp-search-overlay[hidden]` rule below is load-bearing: a bare
 * `.dp-search-overlay { display: flex }` would otherwise override the
 * UA `[hidden]` style. `.is-open` (added a paint after `hidden` is
 * removed) drives the fade-in. */
.dp-search-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: min(4vh, 40px) 20px 20px;
  background: rgba(28, 25, 22, 0.42);
  -webkit-backdrop-filter: saturate(140%) blur(6px);
  backdrop-filter: saturate(140%) blur(6px);
  overflow-y: auto;
  opacity: 0;
  transition: opacity 0.25s var(--reveal-ease, ease);
}
.dp-search-overlay.is-open { opacity: 1; }
.dp-search-overlay[hidden] { display: none; }
.dp-search-panel {
  width: min(1100px, 100%);
  max-height: calc(100svh - min(8vh, 60px));
  overflow-y: auto;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule);
  box-shadow: 0 32px 80px rgba(28, 25, 22, 0.28), 0 4px 14px rgba(28, 25, 22, 0.08);
  padding: clamp(20px, 2.6vw, 36px);
  transform: translateY(10px);
  transition: transform 0.3s var(--reveal-ease, ease);
}
.dp-search-overlay.is-open .dp-search-panel { transform: translateY(0); }
.dp-search-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 18px;
}
.dp-search-eyebrow {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
}
.dp-search-close {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s var(--reveal-ease, ease), color 0.2s var(--reveal-ease, ease), border-color 0.2s var(--reveal-ease, ease);
}
.dp-search-close:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.dp-search-title {
  margin: 0;
  font-size: clamp(28px, 3.2vw, 44px);
  line-height: 1.02;
  font-weight: 400;
  letter-spacing: -0.018em;
}
.dp-search-form {
  margin-top: 18px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 14px;
  align-items: center;
  padding-bottom: 10px;
  border-bottom: 1.5px solid var(--ink);
}
.dp-search-form-icon { color: var(--ink-soft); flex-shrink: 0; }
.dp-search-input {
  border: none;
  outline: none;
  background: transparent;
  font-family: inherit;
  font-size: clamp(16px, 1.6vw, 19px);
  color: var(--ink);
  padding: 6px 0;
  min-width: 0;
}
.dp-search-submit {
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px 0 6px 8px;
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dp-search-submit:hover { color: var(--accent); }
.dp-search-teach-cta {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 20px;
  align-items: center;
  margin-top: 22px;
  padding: 18px 22px;
  background: var(--paper-warm, #ECE3D0);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  text-decoration: none;
  color: inherit;
  transition: background 0.2s var(--reveal-ease, ease);
}
.dp-search-teach-cta:hover { background: rgba(28, 25, 22, 0.04); }
.dp-search-teach-eyebrow {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}
.dp-search-teach-title {
  margin: 6px 0 4px;
  font-size: clamp(20px, 2.2vw, 26px);
  line-height: 1.1;
  font-weight: 400;
}
.dp-search-teach-deck {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.dp-search-teach-arrow { font-size: 20px; color: var(--ink-soft); }
.dp-search-map {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  margin-top: 26px;
}
.dp-search-map-col { display: flex; flex-direction: column; gap: 10px; }
.dp-search-map-label {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: 2px;
}
.dp-search-map-col a {
  font-family: var(--serif, Georgia, serif);
  font-size: 17px;
  color: var(--ink);
  text-decoration: none;
  transition: color 0.15s var(--reveal-ease, ease);
}
.dp-search-map-col a:hover { color: var(--accent); }
.dp-search-hint {
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid var(--rule-soft);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.dp-search-kbd {
  display: inline-block;
  padding: 2px 7px;
  border: 1px solid var(--rule);
  border-radius: 4px;
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  background: var(--paper-warm, #ECE3D0);
}
@media (max-width: 720px) {
  .dp-search-map { grid-template-columns: 1fr !important; gap: 24px; }
  .dp-search-teach-cta { grid-template-columns: 1fr !important; }
  .dp-search-teach-arrow { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .dp-search-overlay,
  .dp-search-panel { transition: none; }
}

/* ─── Next-step router (floating pill + drawer) ────────────────────────
 * Editor block that renders a fixed pill (bottom-left, clear of the
 * bottom-right chat widget) opening a left-anchored drawer of intent-led
 * route cards. `[hidden]` on the overlay is load-bearing — see the
 * search-overlay note above.
 *
 * The block's own `<section>` paints nothing (pill + overlay are both
 * fixed) — `padding: 0` cancels the generic `section { padding: 84px 0 }`
 * rule, which would otherwise leave an empty strip in the page flow.
 * This holds in the site-editor preview too: the block stays a floating
 * pill there, and selecting it opens the drawer modal. */
.dp-router { padding: 0; }
.dp-router-pill {
  position: fixed;
  left: 24px;
  bottom: 24px;
  z-index: 90;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 22px;
  background: var(--ink, #1F1B16);
  color: var(--paper, #F1EADB);
  border: none;
  border-radius: 999px;
  font-family: 'Geist Mono', ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: 0.08em;
  cursor: pointer;
  box-shadow: 0 8px 28px rgba(28, 25, 22, 0.26);
  transition: transform 0.2s var(--reveal-ease, ease);
}
.dp-router-pill:hover { transform: translateY(-2px); }
.dp-router-pill-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent, #B5532B);
}
.dp-router-overlay {
  position: fixed;
  inset: 0;
  z-index: 190;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  padding: min(5vh, 40px) 20px;
  background: rgba(28, 25, 22, 0.42);
  -webkit-backdrop-filter: saturate(140%) blur(6px);
  backdrop-filter: saturate(140%) blur(6px);
  overflow-y: auto;
  opacity: 0;
  transition: opacity 0.25s var(--reveal-ease, ease);
}
.dp-router-overlay.is-open { opacity: 1; }
.dp-router-overlay[hidden] { display: none; }
.dp-router-panel {
  width: min(460px, 100%);
  max-height: calc(100svh - min(10vh, 80px));
  overflow-y: auto;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule);
  box-shadow: 0 32px 80px rgba(28, 25, 22, 0.28);
  padding: clamp(24px, 3vw, 36px);
  position: relative;
  transform: translateX(-18px);
  transition: transform 0.3s var(--reveal-ease, ease);
}
.dp-router-overlay.is-open .dp-router-panel { transform: translateX(0); }

.dp-router-close {
  position: absolute;
  top: 18px;
  right: 18px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s var(--reveal-ease, ease), color 0.2s var(--reveal-ease, ease), border-color 0.2s var(--reveal-ease, ease);
}
.dp-router-close:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.dp-router-title {
  margin: 0;
  padding-right: 44px;
  font-size: clamp(24px, 2.8vw, 34px);
  line-height: 1.08;
  font-weight: 400;
  letter-spacing: -0.015em;
}
.dp-router-subtitle {
  margin: 10px 0 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.dp-router-notice {
  margin-top: 20px;
  padding: 12px 16px;
  background: var(--paper-warm, #ECE3D0);
  border: 1px solid var(--rule-soft);
  display: flex;
  gap: 12px;
  align-items: flex-start;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.dp-router-cards {
  margin-top: 24px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}
.dp-router-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 18px;
  background: var(--paper);
  text-decoration: none;
  color: inherit;
  transition: background 0.18s var(--reveal-ease, ease);
}
.dp-router-card:hover { background: var(--paper-warm, #ECE3D0); }
.dp-router-card-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.dp-router-card-title {
  font-size: 17px;
  line-height: 1.2;
  font-weight: 400;
}
.dp-router-card-sub {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.4;
}
.dp-router-card-arrow {
  font-size: 15px;
  color: var(--ink-mute);
  flex-shrink: 0;
  transition: transform 0.18s var(--reveal-ease, ease), color 0.18s var(--reveal-ease, ease);
}
.dp-router-card:hover .dp-router-card-arrow {
  transform: translateX(3px);
  color: var(--accent);
}
.dp-router-footer {
  margin-top: 18px;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
@media (max-width: 720px) {
  /* Collapse to an icon-only circle so the pill no longer overruns the
   * hero CTAs / chat FAB on small screens. Mirrors the chat widget's
   * round launcher in the opposite corner. Accessible name preserved via
   * the button's aria-label. */
  .dp-router-pill {
    left: 16px;
    bottom: 16px;
    width: 52px;
    height: 52px;
    padding: 0;
    gap: 0;
    justify-content: center;
  }
  .dp-router-pill-label { display: none; }
  .dp-router-pill-dot { width: 9px; height: 9px; }
}
@media (prefers-reduced-motion: reduce) {
  .dp-router-pill,
  .dp-router-overlay,
  .dp-router-panel,
  .dp-router-card,
  .dp-router-card-arrow { transition: none; }
}

/* Grow-on-play for video items. Default split is 0.85fr / 1.15fr
 * (meta left, player right). After the user clicks play the section
 * picks up `.is-playing` from runtime.js → the grid shifts so the video
 * column grows ~25% (0.575 → ~0.72 of the row) and the meta column
 * gives up the width. The meta block itself scales down a notch so its
 * type + spacing recede without greying out — it stays fully legible,
 * just quieter, and the eye settles on the larger video. */
.dp-path-item-grid {
  /* Default split: meta left, player right. Declared here (not inline on
   * the partial) so `.is-playing` and the mobile 1-col rule can override
   * it — an inline value would beat both. */
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  transition: grid-template-columns 0.45s var(--reveal-ease, ease);
}
.dp-path-item.is-playing .dp-path-item-grid {
  grid-template-columns: minmax(0, 0.56fr) minmax(0, 1.44fr);
}
.dp-path-item-text {
  transition: transform 0.4s var(--reveal-ease, ease);
  transform-origin: left top;
}
.dp-path-item.is-playing .dp-path-item-text {
  /* Scale (not fade) — font + spacing recede together, no grey-out. */
  transform: scale(0.9);
}

/* Error-page hover state. The link list mirrors the editorial sidebar
 * pattern used elsewhere; the arrow eases right on hover. */
.dp-error-link { transition: padding-left .2s var(--reveal-ease); }
.dp-error-link:hover { color: var(--accent); padding-left: 6px; }
.dp-error-link:hover .dp-error-link-arrow {
  color: var(--accent);
  transform: translateX(3px);
}
.dp-error-link-arrow { transition: transform .2s var(--reveal-ease), color .15s var(--reveal-ease); }

/* Video detail viewport-fit. At desktop+ with a sane viewport height,
 * cap the player width so the full 16:9 frame fits below the hero +
 * header on a 1440×900 viewport without scroll. The math:
 *
 *   max-width = (viewport-height − reserved-chrome) × 16 / 9
 *
 * where `reserved-chrome` covers the platform header + breadcrumbs +
 * media-hero + the 16px gap above the player + a small safety margin.
 * On taller viewports the calc'd width exceeds the container's
 * 1200px wrap, so the player just fills the wrap as before. On
 * shorter viewports the player scales down proportionally, kept
 * centered with `margin: 0 auto`. Aspect-ratio stays 16:9 via the
 * inline `aspect-ratio` on `.video-player`. */
@media (min-width: 1024px) and (min-height: 700px) {
  .dp-video-player .video-player {
    /* Reserve ~340px for: platform header (116) + breadcrumbs (22 top
     * pad + 60 bottom pad + ~18 content = ~100) + media-hero (~200)
     * + a small safety margin. The 16:9 aspect-ratio on the player
     * keeps the height proportional. */
    max-width: calc((100vh - 340px) * 16 / 9);
    /* Left-aligned, not centered. Editorial pages anchor against the
     * left grid line; centering a sub-container-width player floats it
     * awkwardly inside the wrap. */
    margin-left: 0;
    margin-right: auto;
  }
  /* The meta strip that immediately follows the video player inherits
   * the same width clamp, anchored to the same left edge. Adjacent-
   * sibling selector keeps audio_detail's meta (which follows the
   * audio_hero, not the video_player) at the full wrap width. */
  .dp-video-player + .dp-media-meta .video-detail-meta-grid {
    max-width: calc((100vh - 340px) * 16 / 9);
    margin-left: 0;
    margin-right: auto;
  }
}

/* Plyr custom skin: paint the platform-supplied controls with Abide's
 * accent / paper so the editorial palette stays coherent. Plyr wraps
 * the `<video>` in `<div class="plyr">` at init — the class is on the
 * WRAPPER, not the original `<video>`. Scope to `.dp-page .plyr` so
 * every Plyr instance inside an Abide page is skinned, regardless of
 * which custom class we put on the `<video>` element. */
.dp-page .plyr {
  --plyr-color-main: var(--accent);
  --plyr-video-control-color: var(--paper, var(--bg-base));
  --plyr-video-control-color-hover: var(--paper, var(--bg-base));
  --plyr-video-control-background-hover: var(--accent);
  --plyr-range-track-height: 4px;
}

/* ─── Density modifier (Tweaks-Panel idea, kept for editor parity) ──────*/
body[data-density="minimal"] .density-meta { display: none; }
body[data-density="editorial"] .density-extra { display: block; }
body:not([data-density="editorial"]) .density-extra { display: none; }

/* ─── Animation primitives ──────────────────────────────────────────────
 * reveal.js attribute-driven scroll-reveal lives in reveal.js; the CSS
 * keyframes here cover the always-on micro-animations from the source
 * (dropdown/search fade-in, featured-teaching status dot pulse).
 *
 * Per animations.md, the bespoke entrance effects (hero image-settle
 * 1.03→1.00, mask wipes, hover image zoom) are wired in runtime.js so
 * they can read template-local class names. */
.fade-in { animation: dp-fade .5s var(--reveal-ease) both; }
@keyframes dp-fade {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes dp-pulse {
  0%, 100% { opacity: 0.4; transform: scale(0.9); }
  50%      { opacity: 1;   transform: scale(1.15); }
}

/* Image-settle entrance for hero <img> elements. runtime.js stamps the
 * .is-settling class on mount; the keyframe runs once. */
.dp-img-settle {
  animation: dp-img-settle 1.2s var(--reveal-ease) both;
}
@keyframes dp-img-settle {
  from { transform: scale(1.03); opacity: 0.001; }
  to   { transform: scale(1.00); opacity: 1; }
}

/* Hover micro-interactions per animations.md (1.02–1.03 image zoom on
 * cards). Restrained — never beyond 1.03. */
.lift .photo > img,
.card .photo > img {
  transition: transform .35s var(--reveal-ease);
}
.lift:hover .photo > img,
.card:hover .photo > img {
  transform: scale(1.025);
}

/* prefers-reduced-motion respect: strip translations + scale, keep opacity.
 * reveal.js handles its own attribute-driven elements; this catches our
 * always-on CSS animations. */
@media (prefers-reduced-motion: reduce) {
  .fade-in,
  .dp-img-settle {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  .btn, .qlink, .lift, .card,
  .lift .photo > img, .card .photo > img {
    transition: none !important;
  }
  .lift:hover, .card:hover,
  .lift:hover .photo > img, .card:hover .photo > img {
    transform: none !important;
  }
}

/* ─── Responsive homepage frame (≤1100, ≤860, ≤720) ─────────────────────*/
@media (max-width: 1100px) {
  .wrap,
  .wrap-tight,
  .wrap-text {
    padding-left: 32px;
    padding-right: 32px;
  }
  .site-mainbar { gap: 20px !important; }
  .site-nav { justify-content: flex-end !important; }

  .latest-grid,
  .youth-grid,
  .community-grid,
  .footer-slim-grid,
  .footer-full-top {
    gap: 48px !important;
  }

  .start-grid,
  .themes-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  .resources-grid,
  .next-steps-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  .footer-full-directory {
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  }
}

@media (max-width: 860px) {
  section { padding-top: 68px; padding-bottom: 68px; }

  .home-hero,
  .hero-teaching,
  .hero-next,
  .hero-formation {
    padding-top: 42px !important; padding-bottom: 72px !important;
  }

  .latest-grid,
  .youth-grid,
  .community-grid,
  .footer-slim-grid,
  .footer-full-top {
    grid-template-columns: 1fr !important;
  }

  .hero-next-grid,
  .youth-card-grid,
  .community-gallery,
  .footer-slim-links {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  .series-feature-grid {
    grid-template-columns: 1fr !important;
    gap: 36px !important;
    margin-bottom: 52px !important;
  }
  .series-secondary-grid {
    grid-template-columns: 1fr !important;
    gap: 36px !important;
    padding-top: 40px !important;
  }

  .community-sticky { position: static !important; }

  .footer-newsletter-wrap { justify-content: flex-start !important; }

  .footer-colophon {
    align-items: flex-start !important;
    flex-direction: column !important;
    gap: 14px !important;
  }
}

/* ─── Mobile/tablet header transition (≤1023px) ─────────────────────────
 * Verbatim port of source shared.css:1670–1700. Switches the header from
 * desktop layout (3-col: wordmark | nav | actions) to mobile layout
 * (2-col: wordmark | actions, with actions carrying search + hamburger).
 * Source put this at the 1023px breakpoint so tablets get the hamburger
 * too — phones AND iPad-portrait. Keep it there. */
@media (max-width: 1023px) {
  .site-mainbar {
    grid-template-columns: 1fr auto !important;
    gap: 18px !important;
  }
  /* Hide the desktop horizontal nav links + the primary CTA (which
   * carries class `btn`). Search icon, language toggle, and hamburger
   * remain visible. */
  .site-nav,
  .site-actions .btn {
    display: none !important;
  }
  .site-actions {
    gap: 8px !important;
  }
  /* Source styles BOTH <button> and <a> action items at 42×42 on mobile,
   * so the search anchor + hamburger button line up at the same hit
   * size. */
  .site-actions a[aria-label],
  .site-actions button {
    width: 42px !important;
    height: 42px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  .dp-nav-mobile-trigger {
    display: inline-flex !important;
  }
  /* Listing grids drop from 3-col to 2-col at tablet width. The
   * filter-bar hero grid keeps its desktop split until phone. */
  .dp-page .dp-listing-grid {
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 40px !important;
  }
}

/* ─── Mobile (≤720px): the home-page composition collapses to 1-col ─────
   This block does the heavy lifting of mobile responsiveness. The trick:
   block partials use INLINE `grid-template-columns: …` styles for
   composition; inline styles normally beat stylesheet rules, but
   attribute-selector matches (`[style*="…"]`) + `!important` win the
   cascade. The attribute selectors below catch every common grid pattern
   our inline partials emit and force them to 1fr on mobile. Same trick
   the source design used; without it, inline-styled grids stay multi-col
   on phones. */
@media (max-width: 720px) {
  /* Wrap padding shrinks for thumb reach. */
  .wrap,
  .wrap-tight,
  .wrap-text {
    padding-left: 24px;
    padding-right: 24px;
  }

  /* Utility strip wraps to two lines instead of clipping. */
  .site-utility-inner {
    height: auto !important;
    min-height: 32px !important;
    padding-top: 8px !important;
    padding-bottom: 8px !important;
    justify-content: center !important;
    text-align: center !important;
    gap: 8px 14px !important;
    flex-wrap: wrap !important;
    line-height: 1.35 !important;
  }
  /* Header mobile refinement: shorter mainbar height + tighter gap.
   * The 2-col grid switch + hide-desktop-nav happens at the ≤1023px
   * breakpoint above (verbatim port of source line 1670). */
  .site-mainbar {
    height: auto !important;
    min-height: 76px !important;
    gap: 16px !important;
  }

  /* Catch-all: every inline-styled multi-col grid in the page body
     collapses to 1fr on mobile. Scoped to `.dp-page` (the inner
     `<main class="dp-page">` from variants/discipleship_project/v1/base.html),
     NOT `main` — because the platform's parent base.html already wraps the
     whole rendered region in `<main class="flex-1">`, which would otherwise
     pull header chrome (`.site-mainbar`'s `grid-template-columns: auto 1fr auto`
     inline style) into this collapse and break the mobile header layout. */
  .dp-page [style*="grid-template-columns: 1.05fr"],
  .dp-page [style*="grid-template-columns: 0.9fr"],
  .dp-page [style*="grid-template-columns: 1.15fr"],
  .dp-page [style*="grid-template-columns: 1.85fr"],
  .dp-page [style*="grid-template-columns: 1.4fr"],
  .dp-page [style*="grid-template-columns: 1fr 1fr"],
  .dp-page [style*="grid-template-columns: 1fr 1.1fr"],
  .dp-page [style*="grid-template-columns: 1fr 1.2fr"],
  .dp-page [style*="grid-template-columns: 0.85fr"],
  .dp-page [style*="grid-template-columns: 1.05fr 0.95fr"],
  .dp-page [style*="grid-template-columns: 1.4fr 1fr"],
  .dp-page [style*="grid-template-columns: 1fr auto"],
  .dp-page [style*="grid-template-columns: auto 1fr"],
  .dp-page [style*="grid-template-columns: repeat(2"],
  .dp-page [style*="grid-template-columns: repeat(3"],
  .dp-page [style*="grid-template-columns: repeat(4"],
  .dp-page [style*="grid-template-columns: minmax"] {
    grid-template-columns: 1fr !important;
  }
  /* Generous gap on collapsed grids — unless they're hairline grids,
     handled below. */
  .dp-page [style*="display: grid"][style*="grid-template-columns"] {
    gap: min(32px, 8vw) !important;
  }

  /* Hairline-grid sections (cards on a rule-colored background, joined
     by 1px gaps as dividers). Restore the 1px gap so the visual rhythm
     doesn't break into wide tan bands between stacked cards.
     Scoped to `.dp-page` to match the collapse rule above — otherwise the
     broad `[style*="display: grid"][style*="grid-template-columns"]` rule
     wins on specificity and stamps `gap: min(32px, 8vw)` over the 1px. */
  .dp-page .themes-grid[style],
  .dp-page .resources-grid[style],
  .dp-page .next-steps-grid[style],
  .dp-page .hero-next-grid[style],
  .dp-page .dp-resource-grid[style],
  .dp-page .dp-resources-curated-grid[style],
  .dp-page .youth-paths-grid[style],
  .dp-page .youth-resources-grid[style],
  .dp-page .youth-next-steps-grid[style] {
    gap: 1px !important;
  }
  /* Hairline-grid card sizing — desktop min-height leaves a tall hollow
     on mobile; collapse it so each card hugs its content. */
  .dp-page .themes-grid[style] > [class*="card"],
  .dp-page .resources-grid[style] > [class*="card"],
  .dp-page .next-steps-grid[style] > [class*="card"],
  .dp-page .hero-next-grid[style] > [class*="card"],
  .dp-page .dp-resource-grid[style] > [class*="card"],
  .dp-page .dp-resources-curated-grid[style] > [class*="card"],
  .dp-page .youth-paths-grid[style] > [class*="card"],
  .dp-page .youth-resources-grid[style] > [class*="card"],
  .dp-page .youth-next-steps-grid[style] > [class*="card"] {
    min-height: 0 !important;
  }
  .dp-page .themes-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .resources-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .next-steps-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .hero-next-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .dp-resource-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .dp-resources-curated-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .youth-paths-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .youth-resources-grid[style] > [class*="card"] > .mono:last-child,
  .dp-page .youth-next-steps-grid[style] > [class*="card"] > .mono:last-child {
    margin-top: 12px !important;
    padding-top: 0 !important;
  }

  /* Sticky elements stack inline on mobile. */
  .dp-page [style*="position: sticky"] {
    position: static !important;
  }

  /* Section-head splits collapse to stacked. */
  .section-head,
  .section-head-split {
    grid-template-columns: 1fr !important;
    align-items: start !important;
    gap: 18px !important;
    margin-bottom: 34px !important;
  }
  .section-head-action {
    justify-self: start !important;
  }

  /* Hero typography + media tightens. */
  .home-hero-grid {
    grid-template-columns: 1fr !important;
  }
  .home-hero h1 {
    font-size: clamp(42px, 9vw, 60px) !important;
    line-height: 1.02 !important;
  }
  .section-head h2,
  .section-head-split h2 {
    font-size: clamp(32px, 7vw, 44px) !important;
  }
  /* Hero-teaching photo moves below the copy on mobile so the eyebrow
     leads the visual hierarchy. */
  .hero-teaching .hero-media {
    order: 2;
  }
  .hero-teaching-photo {
    height: auto !important;
    min-height: 360px !important;
    aspect-ratio: 4 / 3 !important;
  }

  /* CTA rows wrap; buttons keep 48px touch target. */
  .cta-row {
    flex-wrap: wrap !important;
    gap: 12px !important;
  }
  .cta-row .btn {
    min-height: 48px;
  }

  /* Resource-grid featured (new variant) — image stacks above text. */
  .resources-featured {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
  .resources-type-chips {
    gap: 8px !important;
  }

  /* Footer bottom strip wraps. */
  .footer-bottom,
  .footer-legal,
  .footer-colophon-meta {
    align-items: flex-start !important;
    flex-direction: column !important;
    gap: 12px !important;
  }
  .footer-bottom {
    justify-content: flex-start !important;
  }

  /* Mobile-aspect override for art-directed Photo / FramedPhoto. Photo
     partials that set `data-mobile-aspect="true"` + a `--mobile-aspect`
     CSS var get a different aspect on phones. */
  .photo[data-mobile-aspect="true"] {
    aspect-ratio: var(--mobile-aspect) !important;
  }

  /* Detail-page responsive collapses. Inline-styled multi-col grids in
   * detail partials follow the same `.dp-page` collapse rule above, but
   * a few need bespoke layout overrides on mobile. */
  .dp-page .video-detail-title-strip,
  .dp-page .audio-detail-title-strip {
    grid-template-columns: 1fr !important;
    gap: 14px !important;
    margin-bottom: 24px !important;
  }
  .dp-page .video-detail-context,
  .dp-page .density-meta {
    text-align: left !important;
    padding-bottom: 0 !important;
  }
  .dp-page .video-detail-meta-grid {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
  .dp-page .dp-audio-grid {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
  .dp-page .dp-series-hero-grid {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
  .dp-page .dp-prev-next,
  .dp-page .dp-related-grid,
  .dp-page .dp-series-companions-grid {
    grid-template-columns: 1fr !important;
  }
  .dp-page .dp-series-messages-list > li > a.card {
    grid-template-columns: 48px 1fr !important;
    gap: 14px !important;
  }
  .dp-page .dp-series-messages-list > li > a.card > .photo,
  .dp-page .dp-series-messages-list > li > a.card > [aria-hidden="true"]:last-child {
    display: none !important;
  }
  .dp-page .dp-error-grid {
    grid-template-columns: 1fr !important;
    gap: 40px !important;
  }
  .dp-page .dp-error-sidebar {
    position: static !important;
  }
  .dp-page .dp-listing-hero-grid {
    grid-template-columns: 1fr !important;
    gap: 24px !important;
    align-items: start !important;
  }
  .dp-page .dp-listing-hero-title {
    /* The wordmark gets enormous at desktop; cap it on phones so it
     * doesn't overflow the viewport. */
    font-size: clamp(56px, 14vw, 96px) !important;
  }
  /* Breadcrumbs: keep to a single line on phones. The earlier crumbs
   * (Home / Teachings / …) and the `/` separators hold their size; the
   * current-page label — always the last child — shrinks and ellipsizes
   * instead of wrapping to a second line. */
  .dp-page .dp-crumbs {
    flex-wrap: nowrap !important;
    overflow: hidden;
  }
  .dp-page .dp-crumbs > * {
    flex: 0 0 auto;
    white-space: nowrap;
  }
  .dp-page .dp-crumbs > span:last-child {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .dp-page .dp-listing-filter-grid {
    flex-wrap: wrap !important;
    gap: 12px !important;
    /* The 40px desktop inset is too much on top of the wrap's own
       mobile padding — pull it back so controls keep thumb room. */
    padding-left: 0 !important;
    padding-right: 0 !important;
    padding-top: 10px !important;
    padding-bottom: 10px !important;
  }
  /* Mobile filter: the facet row collapses behind a `Filters` toggle.
     Row 1 is just [Filters] + the result count; the facet controls
     drop into a full-width stacked panel only when the bar carries
     `.is-filters-open` (toggled by runtime.js's initListingFilter). */
  .dp-page .dp-listing-filter-toggle {
    display: inline-flex !important;
  }
  .dp-page .dp-listing-filter-facets {
    display: none !important;
  }
  .dp-page .dp-listing-filter-bar.is-filters-open .dp-listing-filter-facets {
    display: flex !important;
    flex-direction: column;
    align-items: stretch;
    gap: 18px;
    width: 100%;
    padding-top: 6px;
    padding-bottom: 6px;
  }
  /* Toggle picks up the filled treatment while the panel is open. */
  .dp-page .dp-listing-filter-bar.is-filters-open .dp-listing-filter-toggle {
    background: var(--ink, #1F1B16) !important;
    color: var(--paper, #F1EADB) !important;
    border-color: var(--ink, #1F1B16) !important;
  }
  /* Stacked facets stretch to the panel width. */
  .dp-page .dp-listing-filter-bar.is-filters-open .dp-listing-filter-search,
  .dp-page .dp-listing-filter-bar.is-filters-open .dp-listing-filter-select-wrap,
  .dp-page .dp-listing-filter-bar.is-filters-open .dp-listing-filter-select {
    max-width: none !important;
    width: 100%;
  }
  .dp-page .dp-listing-filter-meta {
    margin-left: auto !important;
    padding-left: 0 !important;
    border-left: 0 !important;
  }
  .dp-page .dp-listing-grid {
    grid-template-columns: 1fr !important;
    gap: 40px !important;
  }
  .dp-page .dp-listing-closing-grid {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
  .dp-page .dp-path-hero-grid,
  .dp-page .dp-path-item-grid {
    grid-template-columns: 1fr !important;
    gap: 32px !important;
  }
  .dp-page .dp-path-item {
    padding: 64px 0 !important;
  }
}

/* ─── Small mobile (≤560px) ─────────────────────────────────────────────*/
@media (max-width: 560px) {
  body {
    overflow-x: hidden;
  }
  .wrap,
  .wrap-tight,
  .wrap-text {
    padding-left: 20px;
    padding-right: 20px;
  }
  section {
    padding-top: 56px;
    padding-bottom: 56px;
  }
  .home-hero,
  .hero-teaching,
  .hero-next,
  .hero-formation {
    padding-top: 36px !important;
    padding-bottom: 58px !important;
  }
  /* Wider gaps for stacked editorial blocks. */
  .home-hero-grid,
  .latest-grid,
  .youth-grid,
  .community-grid {
    gap: 34px !important;
  }
  /* Even on small mobile, force the typical grids to 1 column. */
  .hero-next-grid,
  .start-grid,
  .themes-grid,
  .resources-grid,
  .next-steps-grid,
  .youth-card-grid,
  .community-gallery,
  .footer-slim-links,
  .footer-full-directory {
    grid-template-columns: 1fr !important;
  }
  /* Hairline-grid gap stays 1px on stacked cards. */
  .hero-next-grid,
  .themes-grid,
  .resources-grid,
  .next-steps-grid {
    gap: 1px !important;
  }
  /* Community testimony quote: KEEP the quote mark beside the first line of
     the quote (a hanging opening quote — reads "Text…), not stacked above it.
     The broad inline-grid collapse `.dp-page [style*="display: grid"]…` (≈L1870)
     forces grid-template-columns:1fr + gap:min(32px,8vw); match its specificity
     (.dp-page + class + [style]) and come later to hold the 2-column layout
     with a smaller mark. */
  .dp-page .community-quote[style] {
    grid-template-columns: auto 1fr !important;
    column-gap: 14px !important;
    row-gap: 0 !important;
    align-items: start !important;
  }
  .community-quote > .serif {
    font-size: 48px !important;
    line-height: 0.9 !important;
  }
  .footer-slim,
  .footer-full {
    padding-top: 56px !important;
  }
  .footer-newsletter-form {
    grid-template-columns: 1fr !important;
    gap: 10px !important;
  }
}

/* ─── Tiny mobile (≤420px) — narrow phones ─────────────────────────────*/
@media (max-width: 420px) {
  .wrap,
  .wrap-tight,
  .wrap-text {
    padding-left: 18px;
    padding-right: 18px;
  }
  .site-wordmark {
    gap: 10px !important;
  }
  .site-wordmark .mono {
    white-space: nowrap;
    letter-spacing: 0.16em;
  }
  .home-hero h1 {
    font-size: clamp(38px, 11vw, 52px) !important;
    line-height: 1.0 !important;
    letter-spacing: -0.015em !important;
  }
  .section-head h2,
  .section-head-split h2 {
    font-size: clamp(30px, 8.6vw, 40px) !important;
    line-height: 1.05 !important;
  }
  .hero-teaching-photo {
    min-height: 300px !important;
  }
  .closing-cta-actions {
    justify-content: flex-start !important;
  }
}

/* ─── Media-type chip ───────────────────────────────────────────────────
   Shared affordance overlay used by every card surface that shows a
   video/audio thumbnail (sermon_card, featured_card, related_grid). The
   chip's `position: absolute` requires the host element to be
   `position: relative` — every card that includes it already is. Markup
   lives in templates/public/blocks/discipleship_project/_partials/
   media_type_chip.html. */
.dp-media-chip {
  position: absolute; top: 14px; left: 14px;
  font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--paper, #F1EADB);
  background: rgba(28, 25, 22, 0.55);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  padding: 6px 10px; border-radius: 999px;
  display: inline-flex; align-items: center; gap: 8px;
  z-index: 1;
}

/* Icon-only variant — BBC-style circular badge for compact card grids
 * where the meta row below the title already carries readable type
 * context (speaker · duration · date). The icon alone communicates
 * the format; no text needed. Paper-on-ink so the badge reads on any
 * thumbnail content. */
.dp-media-icon {
  position: absolute; top: 12px; left: 12px;
  width: 30px; height: 30px;
  border-radius: 999px;
  background: rgba(28, 25, 22, 0.72);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  color: var(--paper, #F1EADB);
  display: inline-flex; align-items: center; justify-content: center;
  z-index: 1;
}

/* ─── Path Explore (path-detail redesign) ───────────────────────────────
   The new /path/<slug> composition: existing hero + a numbered editorial
   list of rows + a closing band + a full-bleed modal reader. Rules below
   live in the `dp-pe-*` namespace so they don't collide with the older
   `dp-path-item-*` rules. Resting state ships inline from the partials;
   these rules handle hover lifts, the numeral colour shift, the modal
   chip active state, and the mobile collapse. */
.dp-pe-row { transition: background-color .15s var(--reveal-ease, ease); }
.dp-pe-row-link { transition: background-color .15s var(--reveal-ease, ease); }
.dp-pe-row:hover { background: rgba(28, 25, 22, 0.022); }
/* Row hover affordance: lift the thumbnail slightly, swap the arrow
 * circle to ink so the row feels actionable. Numeral picks up the
 * accent on hover even when the item is not started. */
.dp-pe-row-link:hover .dp-pe-row-thumb {
  transform: translateY(-2px);
}
.dp-pe-row-thumb {
  transition: transform .35s cubic-bezier(.2,.7,.2,1);
}
.dp-pe-row-link:hover .dp-pe-row-arrow {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.dp-pe-row-arrow {
  transition: background .2s var(--reveal-ease), color .2s var(--reveal-ease), border-color .2s var(--reveal-ease);
}
.dp-pe-row-link:hover .dp-pe-row-numeral { color: var(--accent); }
.dp-pe-row-numeral { transition: color .2s var(--reveal-ease, ease); }

/* Video player inside the modal: when we include the canonical
 * `video_player.html` partial, it ships its own `<section
 * class="dp-video-player"> > .wrap` chrome whose `.wrap` adds
 * `padding: 0 40px` (so the standalone /contents/video page sits in
 * the editorial column). Inside the modal slot — which already
 * provides horizontal padding — that doubles the inset and shrinks
 * the video. Zero the partial's wrap padding here so the video
 * fills the slot's content area; the Plyr chrome + 16:9 + object-fit
 * stay exactly as the partial defines them. */
.dp-pe-modal-media .dp-video-player { padding: 0; }
.dp-pe-modal-media .dp-video-player > .wrap { padding: 0; max-width: none; }

/* Modal nav buttons — hover swap to ink. JS already paints the
 * disabled state via aria-disabled; this rule covers the resting hover. */
.dp-pe-modal-navbtn:hover,
.dp-pe-modal-closebtn:hover {
  background: var(--ink);
  color: var(--paper);
}
.dp-pe-modal-navbtn,
.dp-pe-modal-closebtn {
  transition: background .2s var(--reveal-ease), color .2s var(--reveal-ease);
}
.dp-pe-modal-chip:hover { background: rgba(28, 25, 22, 0.035); }
.dp-pe-modal-stripend:hover { background: rgba(28, 25, 22, 0.035); }

/* ─── Mobile collapse for the row grid + modal layout ───────────────── */
@media (max-width: 900px) {
  .dp-pe-row-link {
    grid-template-columns: 36px 96px 1fr !important;
    gap: 18px !important;
    padding: 22px 0 !important;
  }
  .dp-pe-row-numeral { font-size: 32px !important; }
  .dp-pe-row-title { font-size: 22px !important; }
  /* Hide the right rail on narrow viewports — the row is already a
   * tappable link, so the state badge + circle arrow read as redundant
   * chrome. Keep duration via the kicker line instead. */
  .dp-pe-row-right { display: none !important; }
  .dp-pe-closing-grid { grid-template-columns: 1fr !important; gap: 24px !important; }
  .dp-pe-closing-actions { justify-content: flex-start !important; }
  /* Modal: stack the top-strip clusters on small screens so the prev/
   * next buttons don't crowd the title meta. */
  .dp-pe-modal-top { flex-wrap: wrap !important; gap: 12px !important; padding: 12px 16px !important; }
  .dp-pe-modal-top-left { gap: 10px !important; font-size: 11px; }
  .dp-pe-modal-slot { padding: 24px 18px 48px !important; }
}

/* ─── DP-Revisited audio detail port ─────────────────────────────────
 * The audio detail page now renders as vertical bands:
 * title hero → player band (cover-inside) → transcript section
 * (left rail aside + shared card) → series orientation → meta/related.
 * Most styling ships inline from the partials; these rules cover the
 * states JS or hover triggers (active transcript row, hover lifts),
 * the language-chip active state, and the mobile collapse for each
 * band. Tokens reused from the existing palette (--accent / --rule /
 * --paper-warm). The shared transcript card is reused by the
 * path-explore modal so any change here ripples to both surfaces. */

/* Player band — hover swap on skip buttons. The play button + scrub
 * track already have their resting state inline; we just need hover. */
.dp-audio-player-skip {
  transition: background .15s var(--reveal-ease, ease), color .15s var(--reveal-ease, ease), border-color .15s var(--reveal-ease, ease);
}
.dp-audio-player-skip:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Transcript card — active row gets the salmon tint + 3px accent
 * left-border + ink text + accent-deep timestamp + accent jump button.
 *
 * Resting state lives HERE (not inline) so the `.is-active` overrides
 * win the cascade. Earlier attempts shipped the colour + border + weight
 * inline, and the inline shorthand beat the external rules by
 * specificity — the row stayed light grey and the "active strip" never
 * showed. Sizing / layout stays inline since it doesn't swap. */
.dp-audio-transcript-line {
  border-left: 3px solid transparent;
  background: transparent;
  color: var(--ink-soft);
  transition: background-color .15s var(--reveal-ease, ease), border-left-color .15s var(--reveal-ease, ease);
}
.dp-audio-transcript-line:hover {
  background: rgba(28, 25, 22, 0.025);
}
.dp-audio-transcript-line.is-active {
  background: var(--accent-tint);
  border-left-color: var(--accent);
  color: var(--ink);
}
.dp-audio-transcript-time {
  color: var(--ink-faint);
}
.dp-audio-transcript-line.is-active .dp-audio-transcript-time {
  color: var(--accent-deep);
}
.dp-audio-transcript-text {
  color: var(--ink-soft);
  font-weight: 300;
  transition: color .15s var(--reveal-ease, ease), font-weight .15s var(--reveal-ease, ease);
}
.dp-audio-transcript-line.is-active .dp-audio-transcript-text {
  color: var(--ink);
  font-weight: 400;
}
.dp-audio-transcript-jump {
  border: 1px solid var(--rule);
  color: var(--ink-faint);
  transition: border-color .12s var(--reveal-ease, ease), color .12s var(--reveal-ease, ease), transform .12s var(--reveal-ease, ease);
}
.dp-audio-transcript-line.is-active .dp-audio-transcript-jump {
  border-color: var(--accent);
  color: var(--accent);
}
.dp-audio-transcript-jump:hover {
  border-color: var(--ink);
  color: var(--ink);
  transform: scale(1.08);
}

/* DOWNLOAD TRANSCRIPT link — quiet hover swaps the underline to the
 * ink tone so the affordance reads as actionable. */
.dp-audio-transcript-download:hover {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

/* Language chips. Resting colour / background / border live HERE
 * (not inline) so the `.is-active` toggle wins the cascade when JS
 * swaps the active chip on click — inline shorthand beats external
 * rules and the chip would stay un-selected after a swap. Sizing
 * + typography stay inline. */
.dp-audio-transcript-lang-chip {
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  transition: background .15s var(--reveal-ease, ease), color .15s var(--reveal-ease, ease), border-color .15s var(--reveal-ease, ease);
}
.dp-audio-transcript-lang-chip:hover:not(.is-active) {
  background: rgba(28, 25, 22, 0.04);
}
.dp-audio-transcript-lang-chip.is-active {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Series orientation strip — quiet card hover, mobile collapse to
 * a single stacked column. */
.dp-series-orientation-card {
  transition: background-color .15s var(--reveal-ease, ease), transform .25s cubic-bezier(.2,.7,.2,1);
}
.dp-series-orientation-card.lift:hover {
  transform: translateY(-2px);
  background: var(--paper-warm, var(--paper));
}
.dp-series-orientation-link:hover {
  color: var(--accent);
}

/* ─── Mobile collapse for the new audio-detail bands ───────────────── */
@media (max-width: 900px) {
  .dp-audio-player { grid-template-columns: 1fr !important; }
  .dp-audio-player-cover { min-height: 200px !important; }
  .dp-audio-player-controls { flex-wrap: wrap !important; gap: 12px !important; }
  .dp-audio-now-playing { flex-basis: 100% !important; order: 5; margin-left: 0 !important; }
  .dp-audio-transcript-grid { grid-template-columns: 1fr !important; gap: 32px !important; }
  .dp-audio-transcript-aside { position: static !important; }
  .dp-series-orientation-grid { grid-template-columns: 1fr !important; gap: 14px !important; }
  .dp-series-orientation-card { align-items: flex-start !important; text-align: left !important; }
}
