@charset "UTF-8";
/* ============================================================== HEADER */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--navy-900);
  border-bottom: 2px solid var(--gold-600);
}
/* While a full-screen overlay (mobile nav, lightbox) has scroll locked via
   body { position: fixed }, .site-header's own `sticky` has no scrolling
   ancestor left to stick within and falls back to its plain in-flow
   position — which can land far off-screen if the page was scrolled down
   when the lock kicked in, taking the logo and the mobile nav's only close
   button with it (see lockScroll() in main.js for how this was found).
   `position: fixed` doesn't depend on a scrolling ancestor at all, so
   forcing it for the duration keeps the header correctly pinned. */
body.scroll-locked .site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: var(--sp-sm);
  height: var(--header-h);
}

.brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.brand-logo { height: 4rem; width: auto; display: block; }

/* ---- desktop nav ---- */
.site-nav { flex: 1; display: flex; justify-content: flex-end; position: relative; }
.nav-list { display: flex; gap: var(--sp-4xs); }
.nav-item { position: static; }
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  font-family: var(--font-nav);
  font-size: var(--fs-lg);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  padding: var(--sp-2xs) var(--sp-sm);
  border-radius: var(--radius);
  color: var(--white);
  transition: var(--transition);
}
.nav-item:hover .nav-link, .nav-link:focus-visible { color: var(--gold-400); }
/* current-page state — same gold the hover/focus states already use, so the
   active item simply looks permanently "hovered" rather than introducing a
   second visual language for "current" vs "hovered". */
.nav-link.is-active { color: var(--gold-400); }

/* `top: 100%` (not `calc(100% + .5rem)`) is deliberate: the panel's own
   box needs to sit flush against the trigger with zero real gap, or the
   pointer has to cross a strip that belongs to neither box on the way
   from one to the other, dropping :hover before ever reaching the panel
   — worse on some pointer/OS combinations than others, since it comes
   down to how many intermediate mousemove samples happen to land in that
   exact strip (confirmed by owner report + reproduced here by stepping a
   simulated pointer down 1px at a time and watching :hover drop out).
   This is purely a hit-testing fix, independent of `padding` below —
   moving the .5rem gap into padding-top was tried first to keep the exact
   old visual spacing, but that grows the empty space actually inside the
   panel's own border (16px -> 24px before the first item), which reads as
   too much once you're looking at the panel itself rather than measuring
   from the trigger — so padding stays plain and even on every side, and
   only `top` carries the fix.
   (Two other approaches were tried and rejected: an invisible ::before
   bridge *inside* .dropdown doesn't work because .dropdown's own
   visibility is itself conditional on .nav-item:hover already being true,
   so anything nested inside it inherits that same conditional and can't
   independently sustain the hover state that's supposed to keep it shown;
   extending .nav-item's own box via padding-bottom instead grows
   .nav-item's layout height, which grows .site-nav's height too since
   .site-nav sizes to its content — and .dropdown's `top: 100%` is
   relative to that same .site-nav, so the panel gets pushed down by a
   correlated, hard-to-predict amount instead of staying still.) */
.dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  min-width: 240px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: var(--transition);
  z-index: var(--z-sticky);
  /* Without a cap, the panel's height is whatever its content needs —
     harmless on a tall viewport, but on a short one (a laptop window that
     isn't maximised, a landscape phone/tablet in dev tools, a browser with
     a lot of vertical chrome) the mega menu's four columns can run past the
     bottom of the screen with no way to reach what's below the fold: no
     scroll, no visible cue anything is missing (owner-reported, 2026-08-01,
     with a screenshot of exactly this on /person/academia/edinburgh/).
     top: 100% is relative to .nav-item, which sits at --header-h from the
     viewport top, so the panel's available room is the viewport height
     minus that offset minus a bottom margin so it never touches the
     screen edge. Once content would exceed that, it scrolls internally
     instead of running off-screen (see .dropdown-scroll-area below — the
     scrolling itself lives one level in, not on .dropdown directly, so the
     scroll-indicator bar can stay fixed in place while only the content
     above it moves). display:flex + column stacks that scroll area above
     the bar and lets the bar keep a fixed height regardless of content. */
  max-height: calc(100vh - var(--header-h) - var(--sp-lg));
  max-height: calc(100dvh - var(--header-h) - var(--sp-lg));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-lg);
}
.nav-item:hover .dropdown, .nav-item:focus-within .dropdown {
  opacity: 1; visibility: visible; transform: translateY(0);
}
.nav-item.menu-force-closed .dropdown {
  opacity: 0 !important; visibility: hidden !important; pointer-events: none !important;
}
.dropdown-scroll-area {
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0; /* required for a flex child to actually scroll instead of stretching its parent */
  padding: var(--sp-sm);
}
.dropdown-flat--wide { display: grid; grid-template-columns: repeat(4, minmax(190px, 1fr)); gap: var(--sp-4xs) var(--sp-sm); min-width: 760px; }
.dropdown-flat--single { display: grid; grid-template-columns: 1fr; gap: var(--sp-4xs); min-width: 200px; }
.dropdown-flat li a, .dropdown-col li a {
  display: block;
  padding: var(--sp-2xs) var(--sp-2xs);
  border-radius: var(--radius);
  font-size: var(--fs-base);
  color: var(--text-muted);
}
.dropdown-flat li a:hover, .dropdown-col li a:hover,
.dropdown-flat li a.is-active, .dropdown-col li a.is-active { background: var(--bg-sunken); color: var(--accent-strong); }

.dropdown--mega { min-width: 760px; }
.dropdown--mega .dropdown-scroll-area { display: grid; grid-template-columns: repeat(4, minmax(190px, 1fr)); gap: var(--sp-sm); }

/* ---------------------------------------------------------- scroll indicator
   The bar that tells a visitor a dropdown scrolls at all (owner-requested,
   2026-08-01, after the max-height/overflow above shipped with no visible
   cue). Hidden by default; main.js's initDropdownScroll() adds .has-scroll
   to .dropdown only for a panel that measures as actually taller than its
   available room, so a normal, short dropdown never shows it. --navy-900 —
   the same colour as .site-header — deliberately, so the bar reads as part
   of the site's own chrome rather than a random accent. Sits below
   .dropdown-scroll-area in flex order, so it never scrolls away with the
   content above it. */
.dropdown-scroll-bar {
  display: none;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: var(--sp-lg);
  padding-block: var(--sp-3xs);
  background: var(--navy-900);
}
.dropdown.has-scroll .dropdown-scroll-bar { display: flex; }
.dropdown-scroll-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  color: var(--white);
  opacity: .75;
  transition: var(--transition);
}
.dropdown-scroll-btn:hover { color: var(--gold-400); opacity: 1; }
.dropdown-scroll-btn:disabled { opacity: .25; pointer-events: none; }
.dropdown-heading {
  display: block;
  font-family: var(--font-sans);
  font-weight: var(--fw-bold);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-slight);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--sp-2xs);
  padding: var(--sp-3xs) var(--sp-2xs);
}

/* ---- header tools ---- */
.header-tools { display: flex; align-items: center; gap: var(--sp-3xs); flex-shrink: 0; color: var(--white); }
.tool-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: var(--radius);
  color: inherit;
  transition: var(--transition);
}
.tool-btn:hover { background: color-mix(in srgb, var(--white) 14%, transparent); color: var(--gold-400); }
.font-controls { display: flex; gap: var(--sp-4xs); }
.font-btn { width: auto; padding: 0 var(--sp-2xs); font-size: var(--fs-2xs); font-weight: var(--fw-bold); font-family: var(--font-sans); }
.font-btn-big { font-size: var(--fs-xs); }

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: var(--sp-3xs);
  width: 2.3rem;
  height: 2.3rem;
  padding: var(--sp-2xs);
}
.nav-toggle span { display: block; height: 2px; width: 100%; background: currentColor; transition: var(--transition); }
.nav-toggle.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle.open span:nth-child(2) { opacity: 0; }
.nav-toggle.open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ================================================================ MOBILE NAV ≤1200px */
@media (max-width: 1200px) {
  .site-nav { display: none; }
  .nav-toggle { display: flex; }
  .header-inner { gap: var(--sp-2xs); }
}
@media (max-width: 480px) {
  .brand-logo { height: 2.5rem; }
  .header-tools { gap: var(--sp-4xs); }
  .tool-btn { width: 1.9rem; height: 1.9rem; }
  .font-btn { padding: 0 var(--sp-3xs); }
}

.mobile-nav-panel {
  display: none;
  position: fixed;
  top: var(--header-h);
  left: 0; right: 0;
  /* `bottom: 0` is relative to whatever the viewport was when it was last
     computed — on iOS Safari, scrolling can collapse/expand the address
     bar and resize the *real* visible viewport without this being
     recomputed, exposing a strip of whatever's behind the panel at the
     bottom. 100dvh always tracks the actual currently-visible height, so
     the panel stays flush with the true bottom edge regardless of browser
     chrome state; the plain vh line is a harmless fallback for browsers
     old enough not to understand dvh. */
  height: calc(100vh - var(--header-h));
  height: calc(100dvh - var(--header-h));
  z-index: var(--z-header);
  background: var(--bg);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: none;    /* iOS: prevents rubber-band showing background, same as .lightbox */
  padding-bottom: env(safe-area-inset-bottom, var(--sp-md));
  border-top: 1px solid var(--border-color);
}
.mobile-nav-panel.open { display: block; }

/* Sizes raised one rung across all three levels below (owner-requested,
   2026-08-01) after comparing against the desktop mega menu, where dropdown
   links sit at --fs-base (17-18px) — mobile's item links were --fs-xs
   (13-14px), nearly half that, for the same content on a screen a visitor
   is more likely to be touching with a finger, not aiming a mouse pointer
   at. The chevron below needs no separate change: its border-drawn triangle
   is sized in em against this element's own font-size, so raising this one
   token scales the arrow with it automatically. */
.mobile-nav-section-toggle {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--sp-sm) var(--pad);
  font-family: var(--font-sans);
  font-weight: var(--fw-bold);
  font-size: var(--fs-base);
  border-bottom: 1px solid var(--border-color);
  color: var(--text-soft);
}
/* While a section is open, pin its own toggle (the item's name) to the top
   of the panel's scroll container — which sits flush against the header's
   bottom edge — so scrolling a long expanded list never carries the name
   out of view above the panel; it can only ever settle right under the
   top-bar, never underneath it. */
.mobile-nav-section-toggle[aria-expanded='true'] {
  position: sticky;
  top: 0;
  z-index: var(--z-base);
  background: var(--bg);
}
/* A CSS-drawn triangle, not a Unicode glyph (same reasoning as the lightbox
   prev/next arrows) — a bare character here would depend on the active
   font actually covering that codepoint, which isn't guaranteed across
   platforms; a shape drawn from plain borders always renders the same. */
.mobile-nav-section-toggle .chevron {
  flex-shrink: 0;
  width: 0; height: 0;
  margin-left: .5em;
  border-left: .3em solid transparent;
  border-right: .3em solid transparent;
  border-top: .35em solid var(--text-faint);
  transition: transform var(--transition);
}
.mobile-nav-section-toggle[aria-expanded='true'] .chevron { transform: rotate(180deg); }
.mobile-nav-section-body { padding: var(--sp-3xs) 0 var(--sp-2xs); background: var(--bg-sunken); }
.mobile-nav-section-body[hidden] { display: none; }
/* --fs-base, matching .mobile-nav-item below — owner's explicit call,
   2026-08-01: the group label (Biography, Academia, Bishop, Ecumenical
   Activities) should read at the same size as the links it labels, not
   smaller. This replaces an earlier decision (kept here for the record,
   since it was reversed rather than just forgotten): the group label was
   deliberately held at --fs-xs as an eyebrow-style tag while --fs-base
   carried the item links, with hierarchy meant to come from weight and
   colour alone. The owner's later instruction overrides that — size
   equality was the point, not a gap. */
.mobile-nav-group-title {
  display: block;
  padding: var(--sp-2xs) var(--pad);
  font-family: var(--font-sans);
  font-weight: var(--fw-bold);
  font-size: var(--fs-base);
  letter-spacing: var(--ls-slight);
  text-transform: uppercase;
  color: var(--accent-strong);
}
/* .mobile-nav-section-toggle above is also --fs-base — confirmed
   2026-08-01, owner asked to check that the bold top-level items (Home,
   Person, Theology, Library, Reception, Foundation) are never smaller than
   the group label or the item links below them. All three levels
   (section toggle, group title, item) now share one size; hierarchy comes
   entirely from weight and colour (bold + --text-soft for the section
   name, bold + --accent-strong for the group label, regular + --text-muted
   for the item link). --sp-sm padding on the item (up from --sp-xs) keeps
   the touch target generous at this size. */
.mobile-nav-item {
  display: block;
  padding: var(--sp-sm) var(--pad) var(--sp-sm) calc(var(--pad) + var(--sp-xs));
  font-size: var(--fs-base);
  color: var(--text-muted);
  border-bottom: 1px solid var(--border-color);
}
.mobile-nav-item:hover, .mobile-nav-item.is-active { color: var(--accent-strong); }
.mobile-nav-section-toggle.is-active { color: var(--accent-strong); }

@media (max-width: 480px) {
  .mobile-nav-section-toggle, .mobile-nav-item, .mobile-nav-group-title {
    padding-left: var(--sp-sm); padding-right: var(--sp-sm);
  }
}

/* ================================================================= SEARCH */
.search-overlay {
  position: fixed; inset: 0;
  z-index: var(--z-overlay);
  background: rgba(7, 14, 31, .55);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 8vh var(--sp-sm) var(--sp-sm);
}
.search-overlay[hidden] { display: none; }
.search-box {
  width: 100%;
  max-width: 640px;
  background: var(--bg-elevated);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.search-input-row { display: flex; align-items: center; gap: var(--sp-2xs); padding: var(--sp-sm) var(--sp-sm); border-bottom: 1px solid var(--border-color); }
.search-icon { color: var(--text-faint); flex-shrink: 0; }
.search-input { flex: 1; border: 0; background: none; font-size: var(--fs-base); color: var(--text); }
.search-close { display: flex; align-items: center; justify-content: center; color: var(--text-faint); }
.search-close:hover { color: var(--text); }
.search-results { max-height: 56vh; overflow-y: auto; }
.search-result { display: flex; gap: var(--sp-xs); padding: var(--sp-xs) var(--sp-sm); cursor: pointer; }
.search-result:hover, .search-result.focused { background: var(--bg-sunken); }
.search-result-thumb { width: 48px; height: 48px; object-fit: contain; background: var(--bg-sunken); border-radius: var(--radius); flex-shrink: 0; }
.search-result-thumb-placeholder {
  width: 48px; height: 48px; border-radius: var(--radius); flex-shrink: 0;
  background: var(--bg-sunken); display: flex; align-items: center; justify-content: center; color: var(--accent);
}
.search-result-title { font-weight: var(--fw-semibold); font-size: var(--fs-sm); color: var(--text-soft); }
.search-result-title mark { background: var(--accent-soft); color: var(--ink); border-radius: var(--radius-sm); }
.search-result-meta { font-size: var(--fs-2xs); color: var(--text-faint); margin-top: var(--sp-4xs); line-height: var(--lh-normal); }
.search-result-meta mark { background: var(--accent-soft); color: var(--ink); border-radius: var(--radius-sm); }
.search-empty { padding: var(--sp-md); text-align: center; color: var(--text-faint); }
.search-footer {
  display: flex; gap: var(--sp-sm);
  padding: var(--sp-2xs) var(--sp-sm);
  border-top: 1px solid var(--border-color);
  font-size: var(--fs-3xs);
  color: var(--text-faint);
  font-family: var(--font-sans);
}

/* =============================================================== LIGHTBOX */
.lightbox {
  position: fixed; inset: 0;
  min-height: 100dvh;
  z-index: var(--z-modal);
  background: #111113;
  display: flex;
  flex-direction: column;
  overscroll-behavior: none;
}
.lightbox[hidden] { display: none; }
/* Two rows: row 1 is the counter (left) with prev/close/next grouped on the
   right; row 2 is the caption, centred, full width, free to wrap across as
   many lines as it needs (nothing beside it to dodge, unlike the old
   3-column grid this replaced). Layout and control style matched to the
   holyicon project's own lightbox, 2026-08-01 — see renderLightbox()'s own
   comment in render.mjs for what was and wasn't carried over. */
.lightbox-topbar {
  display: flex;
  flex-direction: column;
  /* Row gap (counter/controls row -> caption row). Went --sp-3xs (4px, too
     cramped) -> --sp-md (24px, then reported as too much the other way) ->
     a bare 10px, owner's exact number, 2026-08-01. 10px sits between
     --sp-2xs (8px) and --sp-xs (12px) — no scale step lands on it — so this
     is a deliberate, named exception in tools/lint-tokens.mjs's ALLOWED
     list (keyed to this selector), not a value that slipped past the guard. */
  gap: 10px;
  padding: var(--sp-sm) var(--sp-lg);
  flex-shrink: 0;
  /* calc(), not max(): see the ALLOWED-list comment above for why a bare
     px value is used at all. The formula itself is the important part —
     ADDS a fixed amount on top of whatever the safe area already is, on
     every device, rather than letting the inset alone stand in for all the
     breathing room the way max(--sp-sm, env(...)) originally did. Amount
     went --sp-lg (32px) -> a bare 20px, owner's exact number, 2026-08-01,
     after 32px read as more than needed once the row gap above was also
     fixed. 20px sits between --sp-xs (12px) and --sp-sm (16px) -- again no
     scale step matches, same ALLOWED-list treatment. */
  padding-top: calc(env(safe-area-inset-top) + 20px);
}
.lightbox-topbar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
}
/* --fs-sm, up from --fs-xs, and the controls below sized to match (owner-
   reported, 2026-08-01: "jos vece nego sto jesu") — both raised together
   so the counter and the button row read as one deliberate scale, not the
   counter trailing behind larger buttons. */
.lightbox-counter {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  letter-spacing: var(--ls-slight);
  color: #fff;
  flex-shrink: 0;
}
.lightbox-topbar-actions { display: flex; align-items: center; gap: var(--sp-sm); flex-shrink: 0; }
/* prev / close / next: one consistent triangle family — close points up,
   between the two arrows, not an X — drawn as real SVG polygons for crisp
   edges at any zoom level, matching holyicon's own approach exactly (that
   project's rationale, carried over verbatim: a shape drawn from vector
   points renders identically everywhere, unlike a glyph that depends on
   the active font covering it). Full white, not a dimmed tone — the
   strongest contrast available against the near-black backdrop. */
.lightbox-prev, .lightbox-close, .lightbox-next {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem; height: 2.25rem;
  border: none;
  background: transparent;
  padding: 0;
  flex-shrink: 0;
  color: #fff;
}
.lightbox-prev svg, .lightbox-close svg, .lightbox-next svg { fill: currentColor; }
.lightbox-prev svg, .lightbox-next svg { width: .85rem; height: 1.15rem; }
.lightbox-close svg { width: 1.15rem; height: .85rem; }
.lightbox-prev:hover, .lightbox-close:hover, .lightbox-next:hover { color: var(--accent); }

.lightbox-info { text-align: center; color: #fff; }
.lightbox-info[hidden] { display: none; }
/* White, not --accent, and a step up the scale. The caption is read against a
   photograph filling the rest of the screen, so what matters is not the ratio
   against the backdrop — accent already gave 8.11:1 — but the ratio against
   the bright image beside it, the same relation-to-neighbour effect recorded
   for the epigraph in STYLE.md. White reaches 18.86:1 and holds against any
   photo. The description is prose, so --fs-base is its floor; the dimming it
   used to carry is dropped for the same reason, with size alone carrying the
   hierarchy between the two. */
/* font-family is set explicitly because this is an h2, and every heading
   otherwise inherits the display face. It is a caption, not a heading in the
   reading sense — it happens to be marked up as h2 so the dialog carries one
   valid heading level below the page's h1. Sans, like every other caption. */
.lightbox-info h2 { font-family: var(--font-sans); color: var(--text-on-dark); font-size: var(--fs-lg); margin-bottom: var(--sp-4xs); }
.lightbox-info p { color: var(--text-on-dark); font-size: var(--fs-base); }

.lightbox-body { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; display: flex; justify-content: center; padding: 0; }
.lightbox-inner {
  width: 100%; height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox-img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
}

/* ============================================================== ARTICLE PAGE */
.breadcrumbs {
  padding-top: var(--sp-sm);
  padding-bottom: var(--sp-sm);
  margin-bottom: var(--sp-3xs);
  border-bottom: 1px solid var(--border-color);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-slight);
  color: var(--text-muted);
}
.breadcrumbs a { color: var(--accent-strong); }
.breadcrumbs a:hover { color: var(--accent); text-decoration: underline; }
.breadcrumbs span:last-child { color: var(--text-soft); }
.crumb-sep { margin: 0 var(--sp-2xs); opacity: .6; }

.article-hero { padding-top: var(--sp-md); padding-bottom: var(--sp-md); }
.article-hero h1 { margin-bottom: var(--sp-2xs); }
.article-hero h1::after {
  content: '';
  display: block;
  width: 3.5rem;
  height: 3px;
  margin-top: var(--sp-xs);
  background: var(--gold-500);
}
.content-kind {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--navy-900);
  background: var(--gold-400);
  padding: var(--sp-3xs) var(--sp-2xs);
  border-radius: var(--radius);
  margin-bottom: var(--sp-2xs);
}
.article-excerpt { font-family: var(--font-display); font-style: italic; font-weight: var(--fw-light); font-size: var(--fs-lg); color: var(--text-muted); }

/* Real article images sit above the text at their native size (no crop,
   no letterbox fill, no lightbox) — they're illustrative, not a gallery. */
.article-lead-image {
  max-width: 808px; /* 750px image + 2×29px inline padding (border-box) */
  border: 1px solid var(--border-color);
  /* Article images are landscape 750×521 (ratio 1.44:1). Equal absolute padding
     looks larger at top/bottom because 24px is a bigger fraction of 521px than
     of 750px. Setting inline slightly larger compensates: 20/521 ≈ 29/750 ≈ 3.85% */
  padding-block: var(--sp-fluid-3xs);
  padding-inline: var(--sp-fluid-2xs);
  margin: 0 auto var(--sp-fluid-xs);
}
.article-lead-image img { width: 100%; height: auto; display: block; cursor: default; }

/* Same thin frame as .article-lead-image, for an article whose lead is an
   embedded YouTube player instead of a photo (e.g. an online-video-resources
   entry with a single video) — the frame box is sized to the video's own
   16:9 aspect ratio rather than a fixed photo width. */
.article-lead-video {
  width: 100%;
  max-width: 750px;
  border: 1px solid var(--border-color);
  padding: var(--sp-fluid-3xs);
  margin: 0 auto var(--sp-fluid-xs);
}
.article-lead-video-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--ink);
}
.article-lead-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.article-body { max-width: var(--container-narrow); }

/* Measure — prose is bounded by the column, the same edge photographs,
   galleries and tables use, so text and image line up rather than the text
   stopping short of them. --measure resolves to --container-narrow (see
   tokens.css for why a character-count cap was tried and dropped).

   The rule is kept rather than deleted so the intent stays visible: prose
   width is a decision, and it is this one. Headings are excluded for the
   same reason as before — a heading wrapping earlier than its own paragraph
   looks like a mistake. */
.article-body :is(p, ul, ol, blockquote, .article-kicker, .letter-meta, .resource-source) {
  max-width: var(--measure);
}

.article-body :is(h2, h3, h4, h5, h6) { margin-top: 1.8em; }

/* Opt-in drop cap for an article's opening paragraph — pass dropCap={true}
   to ArticleLayout for a given section/route to enable it. A .section-break
   (see theology/art) starts a fresh thematic cluster within the same article,
   so the paragraph right after one gets the same treatment as a new opening.
   The :not() guards matter: when an article opens with a classed helper
   paragraph (a .verse--epigraph like theology/holy-spirit, or an
   .article-kicker), that helper IS the first <p> — without the guards the
   initial lands on the epigraph's own first letter (a "broken initial")
   while the real opening paragraph below it gets nothing. Instead the
   helper is skipped and the sibling selectors below hand the initial to
   the first real prose paragraph that follows it. */
.article-body.has-drop-cap > p:first-of-type:not(.verse):not(.article-kicker):not(.letter-meta):not(.section-break)::first-letter,
.article-body.has-drop-cap > .verse--epigraph + p::first-letter,
.article-body.has-drop-cap > .article-kicker + p:not(.verse):not(.letter-meta)::first-letter,
.article-body.has-drop-cap .section-break + p::first-letter {
  float: left;
  font-family: var(--font-display);
  font-size: 3.2em;
  line-height: .82;
  font-weight: var(--fw-semibold);
  margin: .03em .1em 0 0;
  color: var(--accent-strong);
}
.article-body img {
  border-radius: var(--radius);
  margin-block: var(--sp-2xs);
  cursor: zoom-in;
}
.article-body .article-lead-image img { cursor: default; }
.article-body ol {
  list-style: decimal;
  margin: 0 0 1.2em;
  padding-left: 1.4em;
}
.article-body ol li { padding-left: .3em; margin-bottom: .5em; }
.article-body ol li::marker { color: var(--accent); font-weight: var(--fw-semibold); }

.article-body ul { margin: 0 0 1.2em; padding-left: 0; }
.article-body ul li {
  list-style: none;
  padding-left: 1.4em;
  position: relative;
  margin-bottom: .5em;
}
.article-body ul li::before {
  content: '';
  position: absolute;
  left: .2em;
  top: .65em;
  width: 6px; height: 6px;
  border-radius: var(--radius-full);
  background: var(--accent);
}
/* =========================================================== STICKY SUBHEADER
   Rendered by ArticleLayout/ListingLayout/GalleryLayout. Sits under the
   sticky site-header; shown for as long as the page's own .breadcrumbs
   nav is at all covered by the header (see initStickySubheader() in
   main.js), so a reader deep in a long article still sees what section/
   page they're on, with no gap between the real breadcrumb disappearing
   and this one taking over. */
.sticky-subheader {
  position: sticky;
  top: var(--header-h);
  z-index: var(--z-sticky);
  max-height: 0;
  overflow: hidden;
  background: var(--navy-800);
  border-bottom: 0 solid var(--navy-600);
  opacity: 0;
  transition: max-height .25s var(--ease), opacity .2s var(--ease), border-bottom-width .25s var(--ease);
}
.sticky-subheader.is-visible { max-height: var(--subheader-h); opacity: 1; border-bottom-width: 1px; }
.sticky-subheader p {
  display: flex;
  align-items: center;
  gap: .5em;
  padding-block: var(--sp-xs);
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sticky-subheader-eyebrow { color: var(--gold-400); text-decoration: none; }
a.sticky-subheader-eyebrow:hover, a.sticky-subheader-eyebrow:focus-visible { color: var(--gold-300); text-decoration: none; }
.sticky-subheader-eyebrow::after { content: '·'; margin-left: .5em; color: color-mix(in srgb, var(--white) 45%, transparent); }
.sticky-subheader-title { color: var(--white); font-weight: var(--fw-medium); overflow: hidden; text-overflow: ellipsis; }

/* =============================================================== PAGE BANNER */
.page-banner {
  width: 100%;
  height: clamp(140px, 22vw, 280px);
  background-size: cover;
  background-position: center;
  position: relative;
  display: flex;
  align-items: center;
}
.page-banner .container { width: 100%; text-align: left; }
.page-banner::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, color-mix(in srgb, var(--navy-950) 78%, transparent), color-mix(in srgb, var(--navy-950) 25%, transparent));
}
.page-banner-eyebrow {
  position: relative;
  z-index: var(--z-base);
  margin: 0 0 .3em;
  color: var(--gold-400);
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-widest);
  text-transform: uppercase;
}
.page-banner-heading {
  position: relative;
  z-index: var(--z-base);
  margin: 0;
  color: var(--white);
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  letter-spacing: var(--ls-slight);
  text-transform: uppercase;
}

/* ================================================================ SHARE BAR */
.share-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
  flex-wrap: wrap;
  margin-block: var(--sp-md);
  padding-block: var(--sp-sm);
  border-top: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
}
.share-bar-label {
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--accent-strong);
}
.share-bar-icons { display: flex; align-items: center; gap: var(--sp-3xs); flex-wrap: wrap; }
.share-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: var(--radius);
  color: var(--text-muted);
  transition: var(--transition);
  flex-shrink: 0;
}
.share-btn:hover { color: var(--accent-strong); background: var(--bg-sunken); }
.share-btn svg { width: .9rem; height: .9rem; fill: currentColor; }
.share-btn-copy.copied { color: var(--accent-strong); }

/* ============================================================== ARTICLE PAGER */
.article-pager {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-md);
  margin-top: var(--sp-fluid-sm);
}
.article-pager-link {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
  padding: var(--sp-sm) var(--sp-sm);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  transition: var(--transition);
}
.article-pager-link:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.article-pager-link.is-empty { visibility: hidden; pointer-events: none; }
.article-pager-next { text-align: right; align-items: flex-end; }
.article-pager-label {
  font-family: var(--font-sans);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--accent-strong);
}
/* Sans, not the display face. Two reasons, and they agree: at --fs-sm this
   rendered Playfair at 16px, three pixels under the documented floor of
   --fs-lg, where a didone's hairlines start to look worn rather than fine.
   And the pager is navigation chrome — it names where a link goes, it is
   not a heading or another voice — so it falls under the same rule as photo
   captions: Playfair is for prominent places. Raising it to --fs-lg instead
   would have obeyed the floor but turned two quiet navigation boxes into
   the heaviest thing on the page. */
.article-pager-title { font-family: var(--font-sans); font-size: var(--fs-sm); color: var(--text-soft); }
@media (max-width: 560px) { .article-pager { grid-template-columns: 1fr; } .article-pager-next { text-align: left; align-items: flex-start; } }

/* ------------------------------------------------------------- masonry gallery
   Columns are balanced by estimated height and step down to a single column
   at phone widths; the column-building JS lives in assets/js/main.js (see
   its masonry section). Default column count is set per gallery type by the JS
   (5 for a full gallery page — `.inline-gallery--full`, e.g.
   /person/academia/academia-photo-gallery — 4 for an inline article gallery,
   e.g. /person/bishop/election-and-ordination), computed from the gallery's
   own measured width rather than the viewport, so the same breakpoint logic
   is correct both for a full-bleed gallery page and for a gallery embedded
   in the much narrower article prose column.

   The rules below double as the no-JS/pre-hydration fallback: a plain
   responsive grid that already looks reasonable before masonryGallery.ts
   replaces the figures' flat DOM order with real `.inline-gallery-col`
   columns and flips the container to `.is-built` (flex row of columns). */
.inline-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--sp-sm);
  margin: var(--sp-md) 0;
  align-items: start;
}
.inline-gallery--full { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--sp-sm); }
.inline-gallery.is-built {
  display: flex;
  align-items: flex-start;
}
.inline-gallery-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: inherit;
  min-width: 0;
}
@media (max-width: 480px) {
  .inline-gallery { grid-template-columns: 1fr; }
}

.inline-gallery figure { margin: 0; cursor: zoom-in; }
/* No fixed height/object-fit — each image keeps its own natural aspect
   ratio (the defining trait of a masonry layout, vs. a uniform thumbnail
   grid), and fades in once loaded rather than popping in abruptly.
   margin: 0 overrides .article-body img's own margin-block: .5em (global.css)
   — that rule is meant for a plain inline-prose image, but a gallery image
   sits inside .article-body too and would otherwise inherit it, stacking on
   top of the figure's own equal 4-side padding and making the frame read
   further from the photo on top/bottom than left/right. Found 2026-08-14,
   measured: 17px top/bottom vs 9px left/right around the same photo before
   this reset; 9/9/9/9 after. */
.inline-gallery img {
  display: block;
  width: 100%;
  height: auto;
  margin: 0;
  background: var(--bg);
  border-radius: var(--radius);
  opacity: 0;
  transition: opacity .35s ease;
}
.inline-gallery img.is-loaded { opacity: 1; }
/* Reserved space below every image for its own caption — never an
   overlay-on-hover treatment, since these captions carry real archival
   information (who/where/when), not decorative labels. */
.inline-gallery figcaption {
  font-size: var(--fs-xs);
  color: var(--text-faint);
  margin-top: var(--sp-3xs);
  line-height: var(--lh-snug);
}

/* ---------------------------------------------------------------- showcase
   The site-wide gallery card treatment (see the markup in
   election-and-ordination.md and academia-photo-gallery.md) — a calm,
   gold-on-hover bordered frame around each masonry item, plus a section
   eyebrow announcing the gallery instead of having it simply appear after
   the last paragraph. */
.gallery-eyebrow {
  margin: var(--sp-xl) 0 var(--sp-sm);
  padding-top: var(--sp-md);
  border-top: 1px solid var(--border-color);
  font-family: var(--font-sans);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--accent-strong);
}
.inline-gallery--showcase figure {
  border: 1px solid var(--border-color);
  background: var(--bg);
  padding: var(--sp-2xs);
  transition: var(--transition);
}
.inline-gallery--showcase figure:hover {
  border-color: var(--gold-500);
  box-shadow: var(--shadow-sm);
  transform: translateY(-2px);
}
/* Sans, roman — the same face as every other caption (owner's call,
   2026-08-01). Two decisions in one, both narrowing where Playfair appears:
   the italic marks another voice (quotations, epigraphs, .article-excerpt)
   and a caption is not one — it is the site speaking plainly about the
   photograph above it; and the display face is reserved for prominent
   places, which a caption is not. Only the centring distinguishes a showcase
   caption now, and that is enough, since it sits under a framed gallery card
   rather than beside body prose. */
.inline-gallery--showcase figcaption {
  text-align: center;
}

/* ------------------------------------------------------------- quote block
   The single, site-wide treatment for any quoted/highlighted passage —
   .archival-quote (a cited primary-source passage) and .pull-quote (an
   excerpt pulled from the surrounding prose with a social-share link
   instead of a <cite>) share identical visual rules: a large decorative
   gold quotation mark, a tinted panel, and the same display-italic font
   size. A bare `blockquote` with neither class (see the global.css rule)
   renders the same way too, so a missed class attribute can't produce a
   visually different quote. Do not introduce a third quote treatment —
   every "isticanje/navod" on the site must look like this one. */
.archival-quote,
.pull-quote {
  position: relative;
  margin: var(--sp-lg) 0;
  padding: var(--sp-lg) var(--sp-lg) var(--sp-md) var(--sp-2xl);
  background: var(--bg-sunken);
  border-top: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
  border-left: 3px solid var(--gold-500);
}
.archival-quote::before,
.pull-quote::before {
  content: '\201C';
  position: absolute;
  top: .4rem;
  left: .85rem;
  font-family: var(--font-display);
  font-size: 3.2rem;
  line-height: var(--lh-none);
  color: var(--gold-500);
  opacity: .6;
}
.archival-quote p,
.pull-quote p {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--fw-regular);
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
  color: var(--text-soft);
  margin: 0 0 var(--sp-sm);
}
.archival-quote p:last-of-type,
.pull-quote p:last-of-type { margin-bottom: 0; }
.archival-quote cite {
  display: block;
  margin-top: var(--sp-sm);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--accent-strong);
}
.archival-quote cite::before { content: '— '; color: var(--accent); }

/* source pages sometimes mark a scene/topic break within an article with a bare
   "***" — that's a typographic dinkus in the original, not a rule, so render it as
   a small centered glyph (theology/art) rather than letting markdown turn it into
   an <hr> line. */
.section-break {
  margin: var(--sp-lg) 0;
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-ultra);
  color: var(--accent);
}

/* Every subheading inside an article, at any level, gets one size.

   This content mixes heading levels for a single role, because each piece
   kept whatever level its original publication happened to use. The markup
   is being normalised to ## so the document outline is honest (screen
   readers and search engines read heading levels, and h1 -> h6 claims four
   levels of nesting that do not exist). This rule is the safety net beneath
   that: whatever level survives anywhere, a section heading inside an
   article looks the same as every other one on the site.

   --fs-xl is 22 -> 26px: clearly above the 17px body text it introduces,
   clearly below the 30 -> 36px page title above it. It previously bottomed
   out at 16.8px, under body text, so a subheading rendered smaller than its
   own paragraph on a narrow window. */
.article-body :is(h2, h3, h4, h5, h6) {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-snug);
}

/* ----------------------------------------------------------- lettered list
   Source pages often enumerate items as bare "a) ... b) ..." text — restyle
   as a real <ol> (see e.g. glasgow.md, metropolitan-of-pergamon.md) with a
   hanging gold letter, instead of the generic round-bullet <ul> treatment. */
.article-body .lettered-list {
  list-style: none;
  counter-reset: lettered;
  margin: var(--sp-md) 0 var(--sp-md);
  padding-left: 0;
}
.article-body .lettered-list li {
  counter-increment: lettered;
  position: relative;
  padding-left: 2.1em;
  margin-bottom: .7em;
}
.article-body .lettered-list li::before {
  content: counter(lettered, lower-alpha) ')';
  position: absolute;
  left: 0;
  font-family: var(--font-sans);
  font-weight: var(--fw-bold);
  color: var(--accent-strong);
}
/* multi-paragraph items (e.g. theology/church, theology/philosophy-and-ontology) —
   zero the default <p> margin so paragraphs inside one <li> sit tight, not doubled
   up with the li's own margin-bottom */
.article-body .lettered-list li p {
  margin: 0 0 .8em;
}
.article-body .lettered-list li p:last-child {
  margin-bottom: 0;
}
/* nested sub-enumeration (source "(i) ... (ii) ..." points within a lettered item,
   e.g. theology/church, theology/philosophy-and-ontology) — same list, roman numerals
   in parens instead of a hanging "a)" letter */
.article-body .lettered-list--roman {
  margin: var(--sp-sm) 0 0;
}
.article-body .lettered-list--roman li::before {
  content: '(' counter(lettered, lower-roman) ')';
}

/* a Greek-language parallel text enumerating with α) β) γ) instead of a) b) c)
   (e.g. library/lectures/the-church-and-the-world) — same list, native CSS
   lower-greek counter style instead of lower-alpha */
.article-body .lettered-list--greek li::before {
  content: counter(lettered, lower-greek) ')';
}

/* a source enumeration that was already Arabic-numbered ("1. 2. 3.") rather
   than lettered, but still spans multiple paragraphs per item (e.g.
   library/articles/the-mystery-of-the-church-in-orthodox-tradition) — same
   list, decimal counter with a period instead of a hanging "a)" letter */
.article-body .lettered-list--decimal li::before {
  content: counter(lettered, decimal) '.';
}

/* ------------------------------------------------------------- letter meta
   For any short block of distinct, single-line facts that belongs together
   but isn't prose — a typed letterhead/recipient address (library/letters/*),
   a credits/cast list (library/videotheque/when-god-dies), a venue/date/
   participant block. The source has each line on its own line, but plain
   Markdown joins consecutive lines of one paragraph into a run-on sentence,
   so the block needs an explicit <br>-separated <p class="letter-meta">
   instead, set apart from the prose paragraphs around it. */
.article-body .letter-meta {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  line-height: var(--lh-relaxed);
  color: var(--text-soft);
  margin: 0 0 var(--sp-md);
}

/* ---------------------------------------------------------- article kicker
   Reception/Library articles typically open with 1–3 short metadata
   fragments — author byline, original publication (often a link), a date,
   a book citation, or a descriptive subtitle — which the extractor left as
   bare paragraphs that read as broken fragments dangling under the page
   banner. Group them into a single <p class="article-kicker"> with literal
   <br> separators (same one-HTML-block convention as .letter-meta): a quiet
   sans block closed by a neutral hairline that separates the metadata from
   the prose. An optional leading <span class="kicker-subtitle"> line (a
   descriptive subtitle the source set as a small heading) renders larger,
   in Playfair italic, so it reads as a subtitle rather than a byline. */
/* One uniform size for everything in the kicker — deliberately a single
   size for the whole block (author name included; bold/italic carry the
   hierarchy, not size), sitting just under the body text's 1.0625rem so
   the metadata still reads as a preamble rather than a first paragraph.
   (Earlier iterations used .88rem with only the author's name enlarged —
   too small to read comfortably under the page banner.) */
.article-body .article-kicker {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
  margin: 0 0 var(--sp-lg);
  padding-bottom: var(--sp-md);
  border-bottom: 1px solid var(--border-color);
}
.article-body .article-kicker strong {
  color: var(--text-soft);
  font-weight: var(--fw-semibold);
}
.article-body .article-kicker .kicker-subtitle {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-snug);
  color: var(--text-soft);
  margin-bottom: var(--sp-3xs);
}

/* ------------------------------------------------------------------- verse
   Poems and verse quotations (reception/poetry/*, epigraph poems or psalm
   verses opening an article or sermon). CommonMark collapses single
   newlines into spaces, so stanzas extracted as consecutive plain lines
   silently render as run-on prose — each stanza must instead be one
   <p class="verse"> with literal <br> line breaks (same convention as
   .letter-meta). Playfair italic, matching the site's quote language.
   .verse--epigraph is the opening-quotation variant: right-aligned and
   muted, typically closed by a
   <span class="verse-attrib"> attribution line. */
.article-body .verse {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  margin: var(--sp-md) 0;
}
.article-body .verse--epigraph {
  text-align: right;
  color: var(--text-muted);
  margin: 0 0 var(--sp-lg);
}
.article-body .verse .verse-attrib {
  display: block;
  margin-top: var(--sp-xs);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--accent-strong);
}

/* -------------------------------------------------------------- interview Q&A
   Source interview transcripts (library/interview/*) mark the interviewer's
   question as a bare bold paragraph, with the reply opening with the
   speaker's name (e.g. "ZIZIOULAS:"). That's a turn-taking marker, not
   emphasis, so it gets a dedicated treatment — Playfair italic, a gold rule
   on the left — to read as a question, distinct from the surrounding answer
   prose and from ordinary in-body bold. */
.article-body .interview-q {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--ink);
  border-left: 3px solid var(--accent);
  padding-left: var(--sp-sm);
  margin: var(--sp-lg) 0 var(--sp-sm);
}
.article-body .interview-q p {
  margin: 0;
}

/* ================================================================ FOOTNOTES */
/* scroll-margin-top here for the same reason as :focus-visible's own rule
   in global.css (search "focus not obscured") -- but unconditional, not
   gated behind :focus-visible. Clicking a footnote number is a mouse
   click, and a mouse-triggered jump to a fragment target generally does
   NOT satisfy :focus-visible's heuristic, so that rule alone never fires
   for this. Without this, both jump directions (superscript -> definition
   and definition's back-arrow -> superscript) land the browser's native
   fragment-scroll at the very top of the viewport, which the sticky
   header then covers — the target is technically on-screen, scrolled to,
   but hidden, so it looks like scrolling stopped short.
   Uses --scroll-target-offset (tokens.css) rather than a bare header-h
   calc, so this stays in sync with :focus-visible's identical need —
   see that token's own comment for why .sticky-subheader's height has
   to be part of this too, not just the header's. */
.footnote-ref,
.footnote-back {
  scroll-margin-top: var(--scroll-target-offset);
}
.footnote-ref {
  font-size: .72em;
  vertical-align: super;
  font-weight: var(--fw-bold);
  color: var(--accent-strong);
  margin-left: .1em;
}
/* .72em is deliberately relative to whatever text the marker sits in
   (see lint-tokens.mjs's own comment on IS_RELATIVE_FONT) -- correct
   inside body prose, where every paragraph is the same size, but a
   marker placed at the end of a heading (e.g. an article title sourced
   with its own footnote) inherits the heading's much larger size
   instead, rendering far bigger than every other [N] in the piece. Same
   problem for typeface: headings set font-family to --font-display
   (Playfair, a serif display face — see .article-body :is(h2,h3,...)
   above), which a marker sitting inside one also inherits, so it doesn't
   just look bigger than the other [N]s, it's in a visibly different
   typeface too. Pinned back to fixed values here so a heading-placed
   marker reads exactly like one sitting in body text, regardless of
   which heading level it's in -- .article-body's own headings are all
   one size anyway (see "Heading levels in content" in CLAUDE.md), so
   this doesn't need to vary by h2/h3/etc. --fs-2xs was picked by
   measuring what .72em resolves to against --fs-base (12.2–13px) and
   matching the nearest scale step, not chosen freehand; --font-sans is
   simply what body text already uses (body { font-family: var(--font-sans) },
   global.css), undoing the heading's own override. */
:is(h1, h2, h3, h4, h5, h6) .footnote-ref {
  font-size: var(--fs-2xs);
  font-family: var(--font-sans);
}
.footnote-back { color: var(--accent-strong); font-weight: var(--fw-bold); margin-right: .3em; text-decoration: none; }

/* A run of footnote-definition paragraphs (each one starts with a
   .footnote-back marker) used to read as ordinary body prose — same
   size as the article around it, with nothing marking where the notes
   begin or end, so a long note-heavy piece drifted straight from
   argument into apparatus with no seam. Quieted one step down the
   scale, the same size already used for every other "this is metadata,
   not the argument" block (.article-kicker, .attribution-note), and
   bounded on both sides: a hairline above — the identical device
   .article-kicker already uses to close itself off from the prose that
   follows it — marks where the notes begin, and extra space below
   keeps the run from crowding into whatever comes next (a Source:
   line, a translator credit, a Photo: attribution all follow a
   footnote list somewhere on this site).
   :has() finds these paragraphs from the .footnote-back marker they
   already carry, so none of the 11 content files with footnotes needed
   a markup change for this — new footnoted content gets the same
   treatment automatically. */
.article-body p:has(> a.footnote-back) {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.article-body :not(p:has(> a.footnote-back)) + p:has(> a.footnote-back) {
  margin-top: var(--sp-lg);
  padding-top: var(--sp-md);
  border-top: 1px solid var(--border-color);
}
.article-body p:has(> a.footnote-back):not(:has(+ p:has(> a.footnote-back))) {
  margin-bottom: var(--sp-lg);
}

/* ================================================================== BOARD LIST */
.board-tagline {
  margin: -.5em 0 1.3em;
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-lg);
  color: var(--text-soft);
}
.board-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  /* No row gap: each item already ends in a rule, so a gap only shows up as
     extra space ABOVE the next name that has no counterpart below the role —
     one half of a 3.4px asymmetry that was visible in the finished page. */
  gap: 0 var(--sp-lg);
  margin: 0 0 var(--sp-lg);
  padding: 0;
  list-style: none;
}
.board-list li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .4em;
  /* Optically centred, not numerically. Equal padding leaves the text sitting
     low between the rules: the line box reserves ascender room the capitals
     do not reach, while the descenders in a name like "Yangazoglou" do use
     the space below. Measured by glyph ink, equal 8px padding gave 14.4px
     above against 12.8px below with a role, and 14.4 against 9.8 without one.
     The two cases need different corrections because what sits at the bottom
     differs — a role line in one, the name's own descenders in the other.

     The correction only ever ADDS to the bottom; the top keeps its full step
     (owner's rule, and the right one — opening a gap is safer than closing
     one, since squeezing makes a row feel cramped while a little extra air
     never does).

     One value for both cases, arrived at by measuring all 39 items rather
     than one. Ink extent varies with the string — "Protodeacon" has no
     descender, "Presbyter" does — so a single sample is not representative:
     items with a role averaged 1.3px top-heavy and reached 2.6px at worst.
     A bottom generous enough for the worst case leaves every row bottom-
     heavy by 1.8-2.7px, which is the direction to err and needs no
     :has() split. */
  padding-block: var(--sp-2xs) calc(var(--sp-xs) + var(--sp-4xs));
  padding-inline: 0;
  border-bottom: 1px solid var(--border-color);
}
/* The dot is anchored to .board-name (always one line), not the <li> —
   when .board-role wraps to a second line the <li> grows taller, and a
   dot centered on the whole <li> would drift away from the name. */
.article-body .board-list li::before { content: none; }
/* Name and role share one line-height so their half-leading matches.
   line-height adds half its slack ABOVE the glyphs and half below; at
   different sizes with the same ratio the two halves differ, and the
   difference lands entirely on one side of the row. Here it was 6.3px above
   the name against 4.9px below the role — the other half of the asymmetry.
   Equal leading brings the two within 0.7px, below noticing. */
.board-name {
  position: relative;
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--text-soft);
}
.board-name::before {
  content: '';
  position: absolute;
  left: -1.2em;
  top: 50%;
  transform: translateY(-50%);
  width: 6px; height: 6px;
  border-radius: var(--radius-full);
  background: var(--accent);
}
.board-role {
  flex-basis: 100%;
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--text-muted);
}
.board-role::before { content: '— '; color: var(--accent); }

/* =============================================================== RESOURCE LINKS */
.resource-source {
  margin: -.3em 0 1.3em;
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-base);
  color: var(--text-muted);
}

/* An attribution note, not itself prose — a bare photo credit with no
   <figure> of its own to sit inside (see .inline-gallery figcaption for
   the same photo-attribution role when a real image is present), a
   source publication's own "about the contributor" line, or a speaker's
   name/title block placed before the text it introduces (attribution
   is attribution whether it closes a piece or opens it — see
   library/lectures/the-church-and-the-world.md, where it sits above the
   Greek text it names). Quieter than .resource-source, but not as small
   as a photo caption (--fs-xs read as too fine once seen at actual
   size, 2026-08-16) — --fs-sm matches .article-kicker's own
   opening-metadata size, so this reads at the same quiet weight
   wherever it sits. */
.attribution-note {
  font-size: var(--fs-sm);
  color: var(--text-faint);
  margin-top: var(--sp-sm);
}
.resource-source a { color: var(--accent-strong); }
/* Hand-rolled ::before dots (ours and the inherited generic
   .article-body ul li::before) are positioned by guessing a pixel/em
   offset from the <li>'s box — that guess depends on font metrics that
   render slightly differently across browser engines, so it never
   lines up reliably for everyone. A native ::marker is the browser's
   own built-in mechanism for aligning a list bullet with an item's
   first line, with no positioning math at all — re-enable it (the
   generic .article-body ul li rule sets list-style: none and its own
   ::before, both need the extra .article-body prefix here to win the
   specificity tie) instead of faking one. */
/* ================================================================ PULL QUOTE
   Visual rules (panel, gold quote mark, font size) are shared with
   .archival-quote above; this block only adds the share-link element that
   .archival-quote doesn't have. */
.pull-quote-share {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2xs);
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--accent-strong);
}
.pull-quote-share:hover { color: var(--accent); }

/* ============================================================ YOUTUBE EMBED */
.yt-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: var(--sp-sm) 0 var(--sp-md);
  background: var(--ink);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.yt-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.yt-btn { display: inline-flex; margin: var(--sp-sm) 0 var(--sp-md); }
/* In-prose links sit in the same gold (--link / --accent-strong) used by
   plain non-link emphasis elsewhere on the page (eyebrows, <cite> lines,
   list markers) — color alone can't tell a reader which gold text is
   clickable. A permanent underline gives a link its own affordance that
   doesn't depend on hovering (useless on touch anyway), but a full-strength
   underline under every inline link would compete with prose; instead it
   sits at a third of the link color until hover, then fills in to full
   color — present at rest, confirmed on interaction, never shouting.
   Footnote markers (tiny superscript/return-arrow glyphs, not prose words)
   keep their own convention and opt out below. */
.article-body a:not(.btn),
.archival-quote a:not(.btn),
.pull-quote a:not(.btn),
blockquote a:not(.btn) {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  text-underline-offset: .15em;
  text-decoration-thickness: 1px;
  transition: text-decoration-color var(--transition), color var(--transition);
}
.article-body a:hover:not(.btn),
.archival-quote a:hover:not(.btn),
.pull-quote a:hover:not(.btn),
blockquote a:hover:not(.btn) {
  text-decoration-color: currentColor;
}
.article-body a.speaker-link { font-weight: var(--fw-semibold); }
.article-body a.footnote-ref,
.article-body a.footnote-back {
  text-decoration: none;
}
/* youtubeEmbed.ts deletes a <p> left fully empty once its only link becomes
   an embed/button (see the script's own cleanup pass) — belt-and-braces hide
   for the rare case any markup still leaves one behind. */
.article-body p:empty { display: none; }

/* =============================================================== MAP EMBED */
.map-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: var(--sp-sm) 0 var(--sp-md);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.map-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.map-embed--small { aspect-ratio: 4 / 3; margin: var(--sp-xs) 0 0; }

.contact-locations {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-fluid-xs);
  margin: var(--sp-md) 0;
}
@media (max-width: 700px) { .contact-locations { grid-template-columns: 1fr; } }
.contact-location { font-size: var(--fs-sm); }
.contact-address-block { display: flex; align-items: flex-start; gap: var(--sp-sm); margin-bottom: var(--sp-sm); }
.contact-address-icon { flex-shrink: 0; margin-top: var(--sp-3xs); color: var(--accent); }
.contact-address-eyebrow {
  margin: 0 0 .3em;
  font-family: var(--font-sans);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--accent-strong);
}
.contact-address { margin: 0; line-height: var(--lh-relaxed); color: var(--text-soft); }

.contact-channels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-sm);
  margin-top: var(--sp-md);
  padding-top: var(--sp-md);
  border-top: 1px solid var(--border-color);
}
.contact-channel {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  color: var(--text-soft);
  transition: var(--transition);
}
.contact-channel:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.contact-channel-icon { flex-shrink: 0; color: var(--accent); }
.contact-channel-text { display: flex; flex-direction: column; gap: var(--sp-4xs); }
.contact-channel-label {
  font-family: var(--font-sans);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--text-faint);
}
.contact-channel-value { font-family: var(--font-sans); font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
.contact-channel:hover .contact-channel-value { color: var(--accent-strong); }

/* The .listing-grid / .listing-card card-grid styles that used to sit here
   were removed 2026-07-31 together with renderListingPage's unreachable
   grid branch (see that function's comment in tools/lib/render.mjs) — every
   listing on this site renders as .listing-row markup, styled below. */

.listing-pager { display: flex; gap: var(--sp-2xs); flex-wrap: wrap; margin-top: var(--sp-fluid-sm); }
.pager-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.4rem;
  height: 2.4rem;
  padding-inline: var(--sp-2xs);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  transition: var(--transition);
}
.pager-btn:hover { background: var(--accent-soft); color: var(--accent-strong); }
.pager-btn.is-current { background: var(--accent); color: var(--white); }
.pager-btn.is-disabled { opacity: .4; pointer-events: none; }
.listing-empty { color: var(--text-faint); font-style: italic; }

/* ---- row variant: category-root pages (e.g. /library/interview) — articles
   line up as blocks, image on top sized to its own ratio (no letterboxing). ---- */
.listing-rows {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-fluid-xs);
}
@media (max-width: 1100px) { .listing-rows { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .listing-rows { grid-template-columns: 1fr; } }
.listing-row {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: var(--transition);
}
.listing-row:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.listing-row:hover .listing-row-title { color: var(--accent); }
.listing-row-thumb { display: flex; }
.listing-row-thumb img { width: 100%; height: auto; display: block; transition: transform .4s var(--ease); }
.listing-row:hover .listing-row-thumb img { transform: scale(1.04); }
.listing-row-body { display: flex; flex-direction: column; gap: var(--sp-2xs); min-width: 0; padding: var(--sp-sm) var(--sp-sm) var(--sp-md); }
.listing-row-title { font-family: var(--font-display); font-weight: var(--fw-medium); font-size: var(--fs-lg); color: var(--text-soft); transition: var(--transition); }
.listing-row-excerpt { font-size: var(--fs-xs); color: var(--text-muted); }
/* --accent (#ee945d) measures 2.33:1 on white — fine for a large graphic
   element, far below AA for 12.8px text like this. --accent-strong is the
   same gold family at 4.96:1, and is already this site's --link colour. */
.listing-row-link { font-family: var(--font-sans); font-size: var(--fs-2xs); font-weight: var(--fw-bold); letter-spacing: var(--ls-slight); color: var(--accent-strong); text-transform: uppercase; }

/* Uniform preview blocks (ListingLayout's clampExcerpt prop — trial, currently
   only /foundation/blog): the excerpt is clamped *visually* to 3 lines, with
   the browser adding the ellipsis at the cut point — no textual truncation,
   so previews that don't end on a full stop still read cleanly. READ MORE is
   pinned to the card's bottom edge so every card in a grid row shares one
   baseline even when titles run different lengths. */
.listing-rows--clamp .listing-row-body { flex: 1; }
.listing-rows--clamp .listing-row-excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}
.listing-rows--clamp .listing-row-link { margin-top: auto; padding-top: var(--sp-2xs); }

/* ================================================================= FOOTER */
.site-footer { background: var(--footer-bg); color: var(--footer-text); margin-top: var(--sp-fluid-sm); }
.footer-inner { padding-block: var(--sp-xl) var(--sp-lg); }

.footer-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-md);
  margin-bottom: var(--sp-md);
}
.footer-logo { display: block; flex-shrink: 0; }
.footer-logo img { height: 6rem; width: auto; display: block; opacity: .95; }
.footer-menu-links { display: flex; gap: var(--sp-md); flex-wrap: wrap; justify-content: flex-end; }
.footer-menu-links a {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-slight);
  color: var(--footer-text);
  opacity: .85;
}
.footer-menu-links a:hover { color: var(--gold-400); opacity: 1; }

.footer-divider { border-top: 1px solid color-mix(in srgb, var(--white) 14%, transparent); margin-bottom: var(--sp-md); }

.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-md);
}
.footer-copyright { margin: 0; font-size: var(--fs-sm); color: var(--footer-text); opacity: .75; }
.footer-social { display: flex; gap: var(--sp-sm); }
.footer-social a { color: var(--footer-text); }
.footer-social a:hover { color: var(--gold-400); }

@media (max-width: 700px) {
  /* Logo and menu now centre in step with copyright/social below them
     (owner's request, 2026-08-01) — same breakpoint, same alignment, so the
     whole footer reads as one centred column rather than a left-aligned top
     half sitting above a centred bottom half. Menu already follows the logo
     in source order (see renderFooter()), so centring alone puts it below. */
  .footer-top { flex-direction: column; align-items: center; text-align: center; }
  .footer-menu-links { justify-content: center; }
  .footer-bottom { flex-direction: column; text-align: center; justify-content: center; }
}

/* ── Selection-share bubble ───────────────────────────────────────────────── */
/* Desktop and "mobile view" (touch OR ≤1200px — the same population that
   sees the hamburger nav, components.css's own MOBILE NAV breakpoint; see
   ssIsMobileView() in main.js) now share the exact same positioning rule
   (owner's call, 2026-08-15) — top/left/transform come entirely from
   main.js's ssPositionNearSelection(), run exactly ONCE at the moment the
   bubble is shown and never again on scroll: horizontally the screen's own
   central vertical line, always, regardless of device or where the
   selection sits ("prakticno na svim ekranima treba da pravi centralnu
   vertikalu, tu nema razlike" — the owner's own words); vertically
   wherever the selected text dictates, sitting just above it. Owner's
   explicit requirements: "ne sme da skroluje sa stranicom" (must not
   scroll with the page — position: fixed, not absolute, is what
   guarantees this) and "treba da bude tamo gde se pojavio na pocetku"
   (should stay right where it first appeared) — a value set once and
   never touched again does exactly that, and is also what makes
   "unnecessary movement" structurally impossible: there is no code path
   left that moves it after placement.
   .is-mobile (further down) now only differs in SIZE, not position — one
   step up from this feature's very first version for desktop specifically
   (a fixed-position bubble reads small at arm's length on a monitor in a
   way it doesn't in the hand, owner's request, 2026-08-14), with
   .is-mobile keeping its own touch-friendly sizing on top of that. */
.selection-share-bubble {
  position: fixed;
  display: flex;
  align-items: center;
  gap: var(--sp-2xs);
  padding: var(--sp-2xs) var(--sp-2xs) var(--sp-2xs) var(--sp-xs);
  background: var(--ink);
  color: #fff;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-slight);
  white-space: nowrap;
  cursor: pointer;
  /* Complements the touchstart preventDefault() in main.js — tells the
     browser this element has its own tap handling, skipping default touch
     gesture disambiguation (double-tap-zoom, etc.) that can otherwise delay
     or compete with a real tap once a live selection is back on-screen. */
  touch-action: manipulation;
  user-select: none;
  z-index: var(--z-overlay);
  box-shadow: var(--shadow-dark-sm);
  transition: background .15s;
}
.selection-share-bubble svg { width: 15px; height: 15px; }
.selection-share-bubble[hidden] { display: none; }
/* downward-pointing arrow — restored 2026-08-15, purely decorative on this
   fixed-position bubble (not anchored to anything specific to point at). */
.selection-share-bubble::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--ink);
  transition: border-top-color .15s;
}
.selection-share-bubble:hover,
.selection-share-bubble:focus-visible {
  background: color-mix(in srgb, var(--ink) 85%, #000);
}
.selection-share-bubble:hover::after,
.selection-share-bubble:focus-visible::after {
  border-top-color: color-mix(in srgb, var(--ink) 85%, #000);
}
/* Mobile view: top/left/transform come from main.js inline, set once at
   selection time — nothing here positions it, only sizes it. */
.selection-share-bubble.is-mobile {
  position: fixed;
  padding: var(--sp-2xs) var(--sp-xs) var(--sp-2xs) var(--sp-xs);
  font-size: var(--fs-xs);
  gap: var(--sp-2xs);
}
.selection-share-bubble.is-mobile svg { width: 13px; height: 13px; }

/* ── Selection-share thread panel ────────────────────────────────────────── */
.selection-thread-panel {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  width: min(380px, calc(100vw - 1.5rem));
  max-height: min(480px, calc(100vh - 2.5rem));
  background: var(--ink);
  color: #fff;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-dark-md);
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: var(--font-sans);
  /* Background containment is main.js's job, not this property's: on
     phone, opening this panel engages the site's own lockScroll() (the
     same real position: fixed pin used for the lightbox and mobile nav)
     for as long as the panel stays open, and releases it when the panel
     closes — see ssShowPanel()/ssHidePanel(). touch-action: none here just
     stops the header/footer chrome from starting a native gesture of its
     own (.stp-list below opts back into panning, the one part that needs
     to scroll). */
  touch-action: none;
}
.selection-thread-panel[hidden] { display: none; }

.stp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-2xs) var(--sp-sm);
  border-bottom: 1px solid rgba(255,255,255,.1);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-slight);
  flex-shrink: 0;
}
.stp-close {
  background: none;
  border: none;
  color: rgba(255,255,255,.5);
  cursor: pointer;
  font-size: var(--fs-xs);
  line-height: var(--lh-none);
  transition: color .12s;
  /* The visible "✕" glyph alone is far under a usable touch target on
     mobile — padding grows the actual tappable box well past it, and the
     matching negative margin cancels that growth for layout purposes so
     the glyph still sits flush in the header's usual top-right corner. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-xs);
  margin: -12px -10px -12px var(--sp-3xs);
}
.stp-close:hover { color: #fff; }

/* Plain native scrolling, with real momentum/bounce — safe because the
   background is already fully locked while this panel is open (see
   .selection-thread-panel's own comment above), so there's nothing for
   this element's own scroll to leak into. overscroll-behavior-y: contain
   still stops its overscroll bounce from chaining into whatever's behind
   it regardless. */
.stp-list { overflow-y: auto; flex: 1; touch-action: pan-y; overscroll-behavior-y: contain; }

.stp-item {
  display: grid;
  grid-template-columns: 2.2rem 1fr auto;
  gap: var(--sp-2xs) var(--sp-2xs);
  align-items: start;
  padding: var(--sp-2xs) var(--sp-sm);
  border-bottom: 1px solid rgba(255,255,255,.06);
}
.stp-item:last-child { border-bottom: none; }

.stp-num {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  color: rgba(255,255,255,.35);
  padding-top: .15em;
  letter-spacing: var(--ls-slight);
}

.stp-text {
  font-size: var(--fs-2xs);
  line-height: var(--lh-normal);
  color: rgba(255,255,255,.82);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Thread instruction note */
.stp-info {
  font-size: var(--fs-3xs);
  /* .4 measured 3.82:1 on --ink — below the 4.5:1 floor, and this is a full
     instruction the reader has to follow, not a label to glance at. .7 gives
     9.52:1: half the contrast of the white text beside it, so it still reads
     as secondary, but clears the threshold STYLE.md records for reading
     faint next to a brighter neighbour. */
  color: rgba(255,255,255,.7);
  line-height: var(--lh-normal);
  margin: 0;
  padding: var(--sp-3xs) var(--sp-sm) var(--sp-2xs);
  border-bottom: 1px solid rgba(255,255,255,.06);
  flex-shrink: 0;
}

.stp-btn-group { display: flex; flex-direction: column; gap: var(--sp-3xs); margin-top: var(--sp-4xs); }

.stp-open-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-4xs);
  background: var(--accent);
  color: var(--ink);
  border: none;
  border-radius: var(--radius-lg);
  padding: var(--sp-3xs) var(--sp-2xs);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s;
}
.stp-open-btn:hover { background: var(--gold-300, #fad2ae); }

.stp-copy-btn {
  background: none;
  border: 1px solid rgba(255,255,255,.25);
  color: rgba(255,255,255,.65);
  border-radius: var(--radius-lg);
  padding: var(--sp-4xs) var(--sp-2xs);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  white-space: nowrap;
  font-family: var(--font-sans);
  transition: border-color .12s, color .12s;
}
.stp-copy-btn:hover { border-color: rgba(255,255,255,.6); color: #fff; }

/* Premium toggle at the bottom of the panel */
.stp-footer {
  border-top: 1px solid rgba(255,255,255,.08);
  padding: var(--sp-2xs) var(--sp-sm);
  flex-shrink: 0;
}
.stp-premium-label {
  display: flex;
  align-items: center;
  gap: var(--sp-2xs);
  font-size: var(--fs-3xs);
  color: rgba(255,255,255,.55);
  cursor: pointer;
  user-select: none;
}
.stp-premium-label input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
.stp-premium-label:hover { color: rgba(255,255,255,.85); }

/* Mobile: a deliberate 10px gap on the three edges the panel sits near —
   owner's explicit choice, 2026-08-15. Was flush (0px) at first, matching
   what looked like a bottom-sheet; the owner then asked for a small,
   fixed breathing margin instead, specifically so the card's rounded
   corners (all four, kept unconditionally — see the card's base rule
   above) read as an intentional floating card rather than looking
   pointless against edges they were touching. 10px doesn't land on the
   spacing scale (between --sp-2xs 8px and --sp-xs 12px) — a bare value
   here is fine regardless, `bottom`/`left`/`right`/`width` are not among
   the properties tools/lint-tokens.mjs guards. */
@media (max-width: 480px) {
  .selection-thread-panel {
    /* --ss-viewport-bottom-gap (kept in sync by main.js's
       ssSyncPanelViewportGap()) is the live difference between the layout
       viewport (what a plain bottom offset alone anchors to) and the
       actually visible visual viewport, which on iOS Safari can differ by
       the height of the dynamic toolbar's current state — added on top of
       the fixed 10px, not instead of it, so the card keeps its own margin
       even where the toolbar isn't adding anything extra (falls back to
       0px wherever visualViewport isn't available). */
    bottom: calc(10px + var(--ss-viewport-bottom-gap, 0px));
    right: 10px;
    left: 10px;
    width: auto;
    max-height: 65vh;
  }
}

/* ===================================================== PRINT PHOTOS DIALOG
   Confirmation prompt shown by the Share block's Print button, asking
   whether to include the page's photos in the printed output (the site's
   own print stylesheet includes photos by default — this dialog's "Text
   only" choice is the opt-out; see global.css's @media print block) before
   calling window.print(). Same dark-scrim /
   centered-card pattern as the rest of the site's overlays (lightbox,
   search), not a native browser confirm() — kept visually consistent with
   the rest of the design system rather than jarring the visitor out of it
   right before they print. */
.print-photos-dialog {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-md);
  background: rgba(12, 16, 24, 0.6);
}
.print-photos-dialog[hidden] { display: none; }
.print-photos-box {
  position: relative;
  width: 100%;
  max-width: 420px;
  background: var(--bg-elevated);
  color: var(--text);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-lg) var(--sp-md) var(--sp-md);
  text-align: center;
}
.print-photos-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  margin: 0 0 var(--sp-2xs);
}
.print-photos-desc {
  color: var(--text-muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  margin: 0 0 var(--sp-md);
}
.print-photos-actions {
  display: flex;
  gap: var(--sp-xs);
  justify-content: center;
  flex-wrap: wrap;
}
/* Quiet by design — a discoverability nudge (most desktop/Android print
   dialogs already surface "Save as PDF" as an obvious destination option;
   this line mainly helps iOS Safari, where it takes a pinch-zoom gesture on
   the print preview instead), not a second decision competing with the
   Yes/No choice above it. */
.print-photos-tip {
  margin: var(--sp-sm) 0 0;
  padding-top: var(--sp-sm);
  border-top: 1px solid var(--border-color);
  font-size: var(--fs-2xs);
  line-height: var(--lh-normal);
  color: var(--text-faint);
}
.print-photos-close {
  position: absolute;
  top: .6rem;
  right: .6rem;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  color: var(--text-faint);
  transition: background var(--transition), color var(--transition);
}
.print-photos-close:hover { background: var(--bg-sunken); color: var(--text); }

/* ============================================================== CHRONOLOGY
   The /person/biography/chronology timeline — a vertical gold spine with a
   dot per entry, Playfair years right-aligned against the spine, events to
   the right. `.chrono-milestone` (birth, episcopal consecration, repose)
   fills the dot and gilds the year. Pure article-body markup, same
   convention as the other content patterns — see CLAUDE.md
   "Content markup patterns". */
.article-body .chronology {
  --chrono-yearw: 5.8rem;
  --chrono-gap: var(--sp-xl);
  list-style: none;
  margin: var(--sp-xl) 0 var(--sp-2xs);
  padding: 0;
  position: relative;
}
.article-body .chronology::before {
  content: '';
  position: absolute;
  top: 1.1rem;
  bottom: 1.1rem;
  left: calc(var(--chrono-yearw) + var(--chrono-gap) / 2);
  width: 1px;
  background: linear-gradient(
    to bottom,
    transparent,
    var(--gold-500) 2.5rem,
    var(--gold-500) calc(100% - 2.5rem),
    transparent
  );
}
.article-body .chronology li {
  position: relative;
  display: grid;
  grid-template-columns: var(--chrono-yearw) minmax(0, 1fr);
  column-gap: var(--chrono-gap);
  padding-block: var(--sp-xs);
}
.article-body .chronology li::before {
  content: '';
  position: absolute;
  top: 1.32rem;
  left: calc(var(--chrono-yearw) + var(--chrono-gap) / 2);
  transform: translateX(-50%);
  width: 9px;
  height: 9px;
  border-radius: var(--radius-full);
  background: var(--bg);
  border: 2px solid var(--gold-600);
  z-index: var(--z-base);
}
.article-body .chronology .chrono-year {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--text-soft);
  text-align: right;
  letter-spacing: var(--ls-slight);
}
.article-body .chronology .chrono-event {
  align-self: center;
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
  padding-top: var(--sp-4xs);
}
.article-body .chronology .chrono-milestone .chrono-year { color: var(--accent-strong); font-weight: var(--fw-semibold); }
.article-body .chronology .chrono-milestone .chrono-event { color: var(--text-soft); font-weight: var(--fw-medium); }
.article-body .chronology .chrono-milestone::before {
  background: var(--gold-600);
  width: 13px;
  height: 13px;
  top: 1.22rem;
}

@media (max-width: 640px) {
  /* Stacked: spine hugs the left edge, year above its event. */
  .article-body .chronology { --chrono-indent: 1.9rem; }
  .article-body .chronology::before { left: .45rem; }
  .article-body .chronology li {
    grid-template-columns: 1fr;
    row-gap: var(--sp-4xs);
    padding-left: var(--chrono-indent);
    padding-block: var(--sp-xs);
  }
  .article-body .chronology li::before { left: .45rem; top: 1.27rem; }
  .article-body .chronology .chrono-milestone::before { top: 1.14rem; }
  .article-body .chronology .chrono-year { text-align: left; font-size: var(--fs-lg); transform: translateY(-4px); }
  .article-body .chronology .chrono-event { padding-top: 0; }
}
