:root {
  --bg: #FFFFFF;
  --surface: #FAF9F6;
  --text-primary: #1F1E1B;
  --text-secondary: #423F3A;
  --text-muted: #8C897F;
  --border: #E4E1D8;
  --accent: #1D5DA6;
  --accent-bg: #E9F1FA;
  --um-blue: #00274C;
  --um-beige: #9B9A6D;
  --on-accent: #FFFFFF;
  --heading-color: #1F1E1B;
  /* Flags a "Current ..." role (e.g. a trainee still active in the lab,
     not a past alumnus) in the alumni list -- see .alumni-current and
     render_alumnus_entry. Plain literal values (not templated via
     VISUAL-DESIGN-PARAMETERS.md like the vars above) since it's a one-off
     status accent, not part of the site's core palette; still themed
     per light/dark below so it stays legible in both. */
  --current-affiliation: #b3382c;
  --radius: 6px;
  --max-width: 52rem;
  --nav-logo-height: 84px;
  /* Reserved height of the sticky .site-nav bar, so any sticky element lower
     on the page (e.g. .contributions-nav) can offset its own `top` by this
     amount and never end up hidden behind the nav bar while scrolling. The
     nav bar's actual rendered height is driven by the logo image (the
     tallest thing in the flex row), not the text links -- see
     .nav-logo-link's 15px top margin and .site-nav's 0.75rem (12px)
     top+bottom padding below -- so this is computed from --nav-logo-height
     rather than hardcoded, and stays correct if that parameter is ever
     changed. 39px = 15px logo margin-top + 12px padding-top + 12px
     padding-bottom. */
  --nav-height: calc(var(--nav-logo-height) + 39px);
  /* Where the nav *text* links' own bottom edge sits, as opposed to
     --nav-height (which is driven by the taller logo image and so extends
     well below the text) -- used only by .hero-banner, so it can dock
     right under the text instead of wasting the extra vertical space the
     logo needs. .nav-links no longer vertically centers with the logo --
     Rick asked for the text's top edge to align with the logo's own top
     edge instead, to reclaim the vertical space that used to sit above
     the centered text (see .nav-links' align-self/margin-top below).  The
     logo's own top edge sits at the nav's 0.75rem top padding plus its
     own 15px top margin; the text's line-box is 1.6rem tall (body's 1rem
     font-size * 1.6 line-height -- keep in sync if either changes). So:
     text bottom = nav padding-top + logo's own top margin + text height. */
  --nav-text-bottom: calc(0.75rem + 15px + 1.6rem);
  --font-heading: 'Barlow Condensed', 'Arial Narrow', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-body: 'Nunito Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-condensed: var(--font-heading);
  --font-display: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1B1A17;
    --surface: #242320;
    --text-primary: #F3F1EA;
    --text-secondary: #B8B5AB;
    --text-muted: #8C897F;
    --border: #3B3A35;
    --accent: #7BB0E8;
    --accent-bg: #1E3A57;
    --um-blue: #7BB0E8;
    --on-accent: #1B1A17;
    --heading-color: #D0CDC4;
    --current-affiliation: #e2726a;
  }
}

[data-theme="dark"] {
  --bg: #1B1A17;
  --surface: #242320;
  --text-primary: #F3F1EA;
  --text-secondary: #B8B5AB;
  --text-muted: #8C897F;
  --border: #3B3A35;
  --accent: #7BB0E8;
  --accent-bg: #1E3A57;
  --um-blue: #7BB0E8;
  --on-accent: #1B1A17;
  --heading-color: #D0CDC4;
  --current-affiliation: #e2726a;
}

* { box-sizing: border-box; }
html { color-scheme: light dark; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
}

input, select, button { font-family: inherit; font-size: 1rem; }

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.5rem 1rem;
  z-index: 100;
}
.skip-link:focus { left: 0.5rem; top: 0.5rem; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.pub-title-link { color: var(--um-blue); font-weight: 600; }

.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.75rem 1.5rem;
  min-height: var(--nav-height);
  box-sizing: border-box;
  border-bottom: 0.5px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
}
.nav-links { display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; }
/* .nav-logo-link lives INSIDE .nav-links, not as its sibling (see
   render_nav()) -- .nav-links itself is .site-nav's only real flex item
   here (besides the theme toggle button), and it already exactly fills
   .site-nav's cross-axis space (its own height is set by the logo, the
   tallest thing in it), so an align-self on .nav-links itself has nothing
   to do. The actual logo-vs-text centering happens one level deeper, via
   .nav-links' *own* align-items: center, between .nav-logo-link and these
   plain <a>s / .nav-item-dropdown wrappers. So it's THESE that need
   align-self: flex-start, plus a margin-top matching the logo's own 15px
   top margin, to bring the text's top edge flush with the logo image's
   own top edge instead of vertically centered against it. See
   --nav-text-bottom above, which depends on this exact positioning. */
.nav-links > a:not(.nav-logo-link),
.nav-links > .nav-item-dropdown { align-self: flex-start; margin-top: 15px; }
.nav-links a { color: var(--text-secondary); }
.nav-links a:hover { color: var(--accent); text-decoration: none; }
.nav-links a.active { color: var(--text-primary); font-weight: 600; }

.nav-item-dropdown { position: relative; display: inline-flex; align-items: center; }
.nav-dropdown {
  display: none;
  flex-direction: column;
  gap: 0.4rem;
  position: absolute;
  top: 100%;
  left: 0;
  padding: 1.1rem 0.8rem 0.6rem;
  min-width: 15rem;
  background: var(--bg);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  z-index: 20;
}
.nav-item-dropdown:hover .nav-dropdown,
.nav-item-dropdown:focus-within .nav-dropdown { display: flex; }
.nav-dropdown a { color: var(--text-secondary); font-size: 0.85rem; white-space: normal; line-height: 1.35; }
.nav-dropdown a:hover { color: var(--accent); text-decoration: none; }

/* Back down to 84px tall (132px read as too large), but still nudged so its
   CENTER lands on the same point as umich.edu's own logo center -- their
   132x138px box sits flush at the page's literal top-left corner (0,0), so
   its center is (66, 69) from the page edge. Ours, at 84px tall (~79px wide
   at this asset's aspect ratio) sitting in .site-nav's normal padded flow,
   would default to a center around (63.5, 54) -- close, but not the same
   point -- so a small explicit margin nudges it the remaining (+2.5px right,
   +15px down) to match exactly. */
.nav-logo-link { display: inline-flex; align-items: center; line-height: 0; margin: 15px 0 0 2.5px; }
.um-logo { height: var(--nav-logo-height); width: auto; display: block; }
.um-logo-dark { display: none; }
[data-theme="dark"] .um-logo-light { display: none; }
[data-theme="dark"] .um-logo-dark { display: block; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .um-logo-light { display: none; }
  html:not([data-theme="light"]) .um-logo-dark { display: block; }
}

#theme-toggle {
  border: 0.5px solid var(--border);
  background: var(--surface);
  color: var(--text-primary);
  border-radius: var(--radius);
  padding: 0.35rem 0.75rem;
  font-size: 0.85rem;
  cursor: pointer;
  min-height: 2.25rem;
}
#theme-toggle:hover { background: var(--accent-bg); }

/* ---- Home page hero banner (render_hero_banner(), index.html only) ----
   Provisional/easy-to-remove: see that function's docstring for the exact
   rollback steps. Sits between the nav bar and <main> (outside main's
   max-width column, on purpose -- centered at 80vw rather than edge-to-
   edge, but still independent of main's own max-width/padding).

   Stacking order, bottom to top: ordinary page content (z-index auto) ->
   .hero-nav-backdrop (z-index 5) -> .hero-banner (z-index 10) ->
   .site-nav itself (z-index 50). .hero-nav-backdrop is a plain, empty,
   opaque strip -- exactly the nav bar's own width/height, also `position:
   fixed; top: 0` -- whose only job is to keep hiding ordinary scrolled
   page content, which .site-nav's own background used to do until Rick
   pointed out that making THAT transparent (an earlier version of this)
   let page content show through the nav for the entire time you're
   scrolled anywhere on the page, not just while the banner is nearby.
   With the backdrop doing that job instead, .site-nav's own background
   can safely go transparent (see .site-nav:has(...) below) without ever
   exposing anything other than the hero banner sitting just below it in
   the stack -- the one thing that's always supposed to show through.
   .hero-banner's z-index (10) is below the nav bar's (50), so the UM
   logo and nav links keep rendering on top of it; since the banner is
   horizontally centered at 80vw, it doesn't reach the logo's own column
   on the far left anyway. This backdrop is unaffected by the sticky/
   release behavior below -- it's needed just as much once the banner has
   released and ordinary page content is what's scrolling past under the
   (still transparent) nav.

   .hero-banner-track is a STICKY CONTAINER, not just a DOM wrapper: it
   holds .hero-banner (position: sticky) and .hero-header-inner (the
   name/photo/affiliations header) as normal-flow siblings, so the
   track's own height is simply "banner height + header height" with no
   manual calc needed. .hero-banner sticks at `top: var(--nav-text-
   bottom)` for as long as .hero-banner-track hasn't finished scrolling
   past -- i.e. for as long as the visitor is scrolling through the name/
   title/affiliations header -- then releases and scrolls away normally
   once .landing-columns (Background/Research) is about to appear, since
   at that point continuing to stick would require the banner to spill
   outside its own container. The small negative `margin-top` below pulls
   the whole track up into the nav bar's own "wasted" height (the extra
   room the logo needs beyond the text), so the banner appears to sit
   snugly under the nav text from the very first scroll pixel rather than
   under the nav's full, taller box. */
.hero-nav-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--nav-height);
  background: var(--bg);
  border-bottom: 0.5px solid var(--border);
  z-index: 5;
}
.hero-banner-track {
  position: relative;
  margin-top: calc(-1 * (var(--nav-height) - var(--nav-text-bottom)));
  /* NOTE: a padding-bottom used to live here, meant as "extra scroll
     distance to hold the banner stuck for" -- removed. It didn't do that:
     padding-bottom on the STICKY ELEMENT's OWN container doesn't create
     an invisible scroll buffer that gets silently consumed and then
     disappears -- it's real, rendered space, so it just permanently
     pushed Background/Research further down the page, visible at ANY
     scroll position, not just something you'd scroll through while the
     banner is stuck. Worse, since it was padding (not margin), it also
     blocked .hero's own trailing margin-bottom from collapsing through
     this element the way it used to, so the two stacked into an even
     bigger gap than intended. The real fix lives in the JS instead --
     see ANIMATION_DISTANCE's own comment in the scroll-effect IIFE. */
}
/* :has() with a general-sibling argument -- true only for a .site-nav
   that's followed somewhere later by a .hero-banner-track, i.e. true on
   index.html only, since that markup is opt-in per page (see
   render_hero_banner()'s docstring). Keeps this whole feature CSS-only
   and self-contained -- no Python-side "is this the home page" plumbing
   needed -- and leaves every other page's nav untouched. Safe to make
   fully transparent (rather than needing its own opacity/blur tuning)
   because .hero-nav-backdrop, not this background, is what actually
   keeps ordinary page content hidden -- see the comment above. */
.site-nav:has(~ .hero-banner-track) {
  background: transparent;
  border-bottom-color: transparent;
}
/* <main>'s own padding-top used to be the gap between the banner and the
   header, back when header.hero was <main>'s first child. Now that the
   header lives inside .hero-banner-track instead (see above), that
   padding would just stack an unwanted extra gap above .landing-columns
   -- .hero-header-inner's own margin-top (hero-gap, below) is what
   provides that spacing now. Selector direction is deliberate: .hero-
   banner-track comes BEFORE <main> in the DOM, so this is a plain
   (non-:has()) general-sibling selector, not the reverse-lookup :has()
   trick used for .site-nav above. */
.hero-banner-track ~ main { padding-top: 0; }
/* Replicates <main>'s own HORIZONTAL box model exactly (max-width,
   centering, side padding) so the name/photo/affiliations header lines up
   with the rest of the page's content column even though it no longer
   lives inside <main> itself -- deliberately NOT copying main's own
   vertical padding (2rem top / 4rem bottom), since that's not this
   element's job. margin-top is hero-gap (VISUAL-DESIGN-PARAMETERS.md) --
   the literal blank space between the banner's bottom edge and the
   header's top edge, now a single direct value instead of one term in a
   larger formula (see render_hero_banner()'s docstring). */
.hero-header-inner {
  max-width: var(--max-width);
  margin: 2.5rem auto 0;
  padding: 0 1.5rem;
}
.hero-banner {
  position: sticky;
  top: var(--nav-text-bottom);
  width: 80vw;
  margin: 0 auto;
  z-index: 10;
  /* margin: 0 auto centers this -- NOT left: 50% + transform:
     translateX(-50%), which was the previous attempt here and turned out
     wrong in practice: that combination left most of the banner's left
     side off-screen (only auto-margin centering was reliably applying;
     the left/transform trick apparently didn't resolve the way plain
     position:relative/absolute math would predict for a sticky element).
     auto margins are the standard, well-supported way to center a fixed-
     width block within its container regardless of position scheme
     (static/relative/sticky all support it identically), and
     .hero-banner-track (this element's containing block) is full
     viewport width -- no padding of its own, not nested inside <main>'s
     constrained column -- so 80vw centered via auto margins lands
     exactly at 10vw on each side, matching the old position:fixed
     version's appearance. */
}
.hero-banner-clip {
  aspect-ratio: 2166 / 460;
  overflow: hidden;
  background: var(--bg);
  /* Rick tried real translucency here (distinct from the baked-in white
     background the merged image itself already fades to) -- opacity 75%
     then 85%, with a backdrop-filter blur so scrolling content showed
     through, softened -- then asked to remove it entirely. Fully opaque
     now (no opacity/backdrop-filter properties at all, rather than
     opacity: 1, so there's nothing here to accidentally interact with
     .hero-nav-backdrop's own stacking below it). The overflow clipping
     lives here, one level below .hero-banner itself, specifically so the
     "What's this image?" triggers -- .hero-banner's OTHER children, see
     below -- can sit outside it and let their hover tooltips expand
     upward past the banner's edge without getting clipped too. */
}
.hero-banner-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0.28;
}
/* Dark-mode variant: babbage-neuron-dark.png is a per-pixel
   luminance-weighted blend of the theme's own --dark-bg color with white
   (not a plain channel inversion -- see render_hero_banner()'s docstring),
   same exact geometry as the light PNG, so it drops straight into the
   same box. Same show-one-hide-the-other pattern as .um-logo-light/
   .um-logo-dark right above -- since the hidden one is `display: none`
   (not just invisible), it takes no layout space, so the visible one
   alone still fills .hero-banner via its own width:100%/height:100% with
   no overlap to worry about. */
.hero-banner-img-dark { display: none; }
[data-theme="dark"] .hero-banner-img-light { display: none; }
[data-theme="dark"] .hero-banner-img-dark { display: block; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .hero-banner-img-light { display: none; }
  html:not([data-theme="light"]) .hero-banner-img-dark { display: block; }
}
.hero-word {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1.71rem, 2.88vw, 2.61rem);
  color: #2F65A7; /* dark gray -- was Law Quad Stone (#655A52), too light */
  white-space: nowrap;
  pointer-events: none;
}
/* Starting positions -- 30%/26% for "artificial intelligence", the mirror
   position (70%/74%) for "cognitive science". These are already 20% of
   the way along each word's path from its original corner (25%/20% and
   75%/80%) toward center (50%/50%) -- Rick asked for the resting/
   progress=0 position to start a little further along, rather than right
   in the corner -- so site.js's own start-position constants (see the
   scroll-effect IIFE in SITE_JS) must match these exactly, or there'd be
   a visible jump the instant the scroll listener first runs. Both still
   interpolate the rest of the way toward 50%/50% (and fade out) as the
   visitor scrolls. */
.hero-word-ai { left: 30%; top: 74%; }
.hero-word-cs { left: 70%; top: 26%; }
/* In dark mode, #2F65A7's dark-gray fixed value reads too
   close to the dark background -- #B8B5AB is its own
   separate design parameter (defaults to matching an unselected
   .nav-links link's dark-mode color, but independently tunable -- see
   "Hero banner" in VISUAL-DESIGN-PARAMETERS.md, which documents all three
   hero word colors' light/dark behavior together). Same
   [data-theme]/prefers-color-scheme pairing used for .hero-banner-img-dark
   above. */
[data-theme="dark"] .hero-word-ai,
[data-theme="dark"] .hero-word-cs { color: #B8B5AB; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .hero-word-ai,
  html:not([data-theme="light"]) .hero-word-cs { color: #B8B5AB; }
}
.hero-word-center {
  font-weight: 800;
  font-size: clamp(2.07rem, 3.6vw, 3.24rem);
  color: #00B2A9; /* Arboretum Blue, Michigan's palette -- was Taubman Teal (#00B2A9) */
  opacity: 0;
  /* Deliberately the SAME color in light and dark mode (unlike
     .hero-word-ai/.hero-word-cs just above, which do get a dark-mode
     override) -- Rick's explicit call. See "Hero banner" in
     VISUAL-DESIGN-PARAMETERS.md, which documents all three hero word
     colors' light/dark behavior together in one place. */
}
@media (prefers-reduced-motion: reduce) {
  /* site.js skips the scroll listener entirely under reduced motion and
     jumps straight to the "settled" end state -- these are that end state's
     styles, so the page isn't stuck showing the *initial* corner-phrases
     state with no way to ever reach the payoff. */
  .hero-word-ai, .hero-word-cs { opacity: 0; }
  .hero-word-center { opacity: 1; }
}

/* Two small "What's this image?" hover notes, one per bottom corner of the
   hero banner -- content comes from content/site.org's "Hero image notes"
   section. These ARE .hero-banner's children now (not its siblings, as
   an earlier position:fixed-based version had them) -- .hero-banner's own
   `overflow: hidden` moved down a level to .hero-banner-clip specifically
   so these could sit here, outside the clip, with their hover tooltips
   free to expand upward past the banner's edge uncropped. position:
   absolute, anchored directly to .hero-banner's own bottom corners --
   much simpler than the old version's viewport-relative vw/nav-text-
   bottom arithmetic, and it means these now stick/release in exact sync
   with the banner automatically, with no separate positioning logic of
   their own needed for that. Same visual language as .bg-note/.bg-note-
   trigger/.bg-note-tooltip (the "What's in this background?" note on the
   landing page) -- deliberately reusing that pattern rather than
   inventing a new one. The trigger gets its own small opaque chip
   (.bg-note-trigger doesn't need one, since it sits on the plain page
   background, not on top of a busy image) so it stays legible over any
   part of the illustration. */
.hero-img-note {
  position: absolute;
  bottom: 0.6rem;
  z-index: 11;
  font-size: 0.7rem;
}
.hero-img-note-left { left: 0.6rem; }
.hero-img-note-right { right: 0.6rem; }
.hero-img-note-trigger {
  position: relative;
  display: inline-block;
  cursor: default;
  color: var(--text-muted);
  /* 70% opaque / 30% transparent (started at 50/50, Rick asked to make it
     "slightly more opaque") -- color-mix rather than the `opacity`
     property, since `opacity` would also fade the trigger's own text/
     hover color, not just this background chip. color-mix's first
     percentage is how much of --bg survives, so 70% here = 70% opaque. */
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius);
}
.hero-img-note-trigger:hover,
.hero-img-note-trigger:focus-visible {
  color: var(--accent);
}
.hero-img-note-tooltip {
  display: none;
  position: absolute;
  bottom: 100%;
  padding: 0.6rem 0.8rem;
  width: 18rem;
  max-width: 70vw;
  background: var(--bg);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-secondary);
  font-size: 0.78rem;
  line-height: 1.45;
  text-align: left;
  z-index: 20;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
  /* flex, not block: the left tooltip's portraits column (see
     .hero-img-note-portraits) sits beside the text, not stacked under
     it. Harmless for the right tooltip too, which never gets a second
     child, so it just behaves like a single-column block as before. */
  gap: 0.6rem;
  align-items: flex-start;
}
.hero-img-note-tooltip-text { flex: 1; min-width: 0; }
/* Wider than the base 18rem specifically where the portrait column adds
   to it, so the text doesn't get cramped. */
.hero-img-note-left .hero-img-note-tooltip { left: 0; width: 21rem; }
.hero-img-note-right .hero-img-note-tooltip { right: 0; }
.hero-img-note-trigger:hover .hero-img-note-tooltip,
.hero-img-note-trigger:focus-visible .hero-img-note-tooltip {
  display: flex;
}
.hero-img-note-tooltip a { color: var(--accent); }
.hero-img-note-portraits {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex-shrink: 0;
}
.hero-img-note-portrait {
  width: 2.75rem;
  height: 2.75rem;
  object-fit: cover;
  border-radius: 50%;
  border: 1px solid var(--border);
  display: block;
}

main { max-width: var(--max-width); margin: 0 auto; padding: 2rem 1.5rem 4rem; }
/* Contributions page only: same left/right padding as main (and the nav bar
   above it), but no max-width/centering, so its sidebar can sit flush at the
   page's true left edge -- aligned with the nav logo -- instead of floating
   in whatever gutter happens to be left over from a centered column. */
main.main-full { max-width: none; margin: 0; }

h1, h2 { font-family: var(--font-display); color: var(--heading-color); line-height: 1.2; }
h1 { font-size: 1.85rem; font-weight: 600; margin: 0 0 1rem; }
h2 { font-size: 1.38rem; font-weight: 600; margin: 2rem 0 0.75rem; }
.hero h1 { font-size: 2.05rem; }

.section-bar {
  display: inline-block;
  vertical-align: middle;
  height: 0.9rem;
  width: 2.75rem;
  border-radius: 2px;
  margin-right: 0.6rem;
  margin-bottom: 0.15rem;
}
.landing-columns h2, .contribution h2 { margin-top: 0; }

.info-box { background: var(--surface); border-radius: var(--radius); padding: 0.9rem 1.1rem; }
.info-box p { margin: 0; }

.hero { margin-bottom: 2.5rem; }
/* The TEXT block (name/titles/affiliations) is what aligns with the
   Research column's left edge below -- not the container, and not the
   photo, which sits to its left "as usual". So the flex row's own
   margin-left is set further left than that alignment point, by exactly
   the photo's width plus the row's own 1.5rem gap, so that once the
   photo (first flex child) and the gap are laid out, the text div (second
   child) lands exactly on the target line.
   Target line = Research column's left edge = Background column's share
   of the available width (32/100 of (100% - the 2.5rem landing-columns
   gap)) plus that same 2.5rem gap -- matching .landing-columns' own
   32fr/68fr split below. Hardcoded arithmetic, not wired to
   32fr 68fr -- if that design parameter's ratio ever changes,
   this needs a matching manual update (same "structural, lives in code"
   category as the rest of the hero banner's layout -- see
   VISUAL-DESIGN-PARAMETERS.md). */
.hero-content {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
  margin-left: calc((100% - 2.5rem) * 32 / 100 + 2.5rem - 108px - 1.5rem);
}
.photo {
  width: 108px; height: 108px; flex-shrink: 0;
  border-radius: 50%;
  border: 0.5px solid var(--border);
  object-fit: cover;
  filter: grayscale(100%);
}
.title-line { margin: 0; color: var(--text-secondary); font-size: 1.1rem; font-family: var(--font-heading); font-weight: 400; line-height: 1.3; }

.affiliations { margin-top: 0.6rem; font-size: 1.08rem; line-height: 1.3; font-family: var(--font-heading); font-weight: 400; }
.affiliations-uni, .affiliations-units { display: flex; flex-wrap: wrap; gap: 0 1.5rem; }
.um-name { font-family: var(--font-heading); font-weight: 400; color: var(--text-secondary); }

.landing-columns { display: grid; grid-template-columns: 32fr 68fr; gap: 2.5rem; margin: 1.5rem 0 2rem; }
.col-background, .col-research { font-size: 0.92rem; }
/* Explicit full margin shorthand (not just margin-top) so this <p> doesn't
   also carry the browser's default paragraph bottom margin -- that stray
   default was stacking with .contact-heading's own top margin below it and
   making the gap before "Contact" look too large. */
.small-links { font-size: 0.85rem; margin: 1rem 0 0; }
.small-links a { margin-right: 1rem; }

/* Contact: sits at the bottom of the Background column, under the CV/
   Scholar links and "What's in this background?" note -- deliberately
   subordinate to the "Background" h2 above it (no section-bar), not a full
   sibling section of its own. Margins kept tight on purpose (Rick flagged
   an earlier pass as having too much vertical space around the heading).
   The "Contact" label (.contact-label) is an inline span, not a block
   heading -- it and the first line (typically just the email) share one
   row, .contact-line. Any further paragraphs (.contact-text) render below
   as ordinary text -- optional, e.g. office hours or a second contact
   method. */
.contact-line { margin: 0.6rem 0 0.15rem; font-size: 0.92rem; line-height: 1.4; }
.contact-label {
  /* --font-display (Source Sans Pro SemiBold), same typeface + weight as
     h1/h2 -- Rick specifically wants "Contact" in the main-heading font,
     not this column's condensed --font-heading (Barlow Condensed, used for
     .title-line/.affiliations/.bio-list). A v25 pass tried switching it to
     --font-heading on the theory that "header font" meant this column's
     surrounding typography rather than the page's h1/h2 -- wrong guess,
     reverted per Rick's explicit correction. */
  font-family: var(--font-display);
  color: var(--heading-color);
  font-size: 1.15rem;
  font-weight: 600;
  margin-right: 0.4rem;
}
.contact-text { margin: 0.3rem 0 0; font-size: 0.92rem; line-height: 1.4; }

.col-background { position: relative; }
.bg-watermark {
  position: absolute;
  top: -1rem;
  right: -1.5rem;
  width: 17rem;
  height: auto;
  opacity: 0.2;
  z-index: 0;
  pointer-events: none;
}
[data-theme="dark"] .bg-watermark { opacity: 0.24; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .bg-watermark { opacity: 0.24; }
}
.col-content { position: relative; z-index: 1; }

.bio-list { list-style: none; padding: 0; margin: 0; font-family: var(--font-heading); font-size: 1.05rem; }
.bio-list li { padding: 0.5rem 0; border-top: 0.5px solid var(--border); font-size: inherit; line-height: 1.35; }
.bio-list li:last-child { border-bottom: 0.5px solid var(--border); }
.bio-list strong { font-weight: 400; }

.pub-list { display: flex; flex-direction: column; }
.pub-entry { padding: 1rem 0; border-top: 0.5px solid var(--border); }
.pub-list .pub-entry:last-child { border-bottom: 0.5px solid var(--border); }
.pub-title { font-weight: 600; margin: 0 0 0.2rem; }
.pub-authors { color: var(--text-secondary); font-size: 0.85rem; margin: 0 0 0.15rem; }
.pub-meta { color: var(--text-secondary); font-size: 0.92rem; font-weight: 600; margin: 0 0 0.35rem; }
.blurb { color: var(--text-secondary); font-size: 0.92rem; margin: 0; }
.blurb-indent {
  margin-left: 0.5rem;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
  font-size: 0.85rem;
}
/* Per-publication abstract fold: a native <details> whose <summary> IS the
   .blurb-indent box (see render_foldable_text()) -- when closed,
   .blurb-fold-text is CSS-clamped to --fold-lines lines; opening it (click
   anywhere on the box) removes the clamp. Same no-JS disclosure pattern as
   .narrative-more on the Contributions page, just clamping by line count
   instead of by paragraph. Class names (.blurb/.blurb-indent/.blurb-fold)
   predate a retired `Blurb` bib field (Rick's own hand-written plain-
   language summary, preferred over the paper's formal Abstract when both
   existed) -- kept as-is since renaming every selector is pure churn now
   that both pages always show the same thing, the paper's own Abstract. */
.blurb-fold summary.blurb-fold-summary { cursor: pointer; list-style: none; }
.blurb-fold summary.blurb-fold-summary::-webkit-details-marker { display: none; }
.blurb-fold summary.blurb-fold-summary::marker { content: ""; }
.blurb-fold:not([open]) .blurb-fold-text {
  display: -webkit-box;
  -webkit-line-clamp: var(--fold-lines, 5);
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.blurb-fold-toggle {
  display: block;
  margin-top: 0.3rem;
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 500;
}
.blurb-fold summary.blurb-fold-summary:hover .blurb-fold-toggle { text-decoration: underline; }
.blurb-fold[open] .blurb-fold-toggle .label-open { display: none; }
.blurb-fold:not([open]) .blurb-fold-toggle .label-close { display: none; }

.recent-pub { display: block; }
.pub-citation-row { display: flex; align-items: center; gap: 0.65rem; }
.pub-thumb-placeholder {
  width: 84px; height: 84px; flex-shrink: 0;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: 0.7rem;
}
.pub-venue-badge {
  width: 84px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  font-family: var(--font-heading); font-weight: 400;
  font-size: 1.5rem; line-height: 1.15;
  color: var(--um-beige);
}
.pub-citation { flex: 1; min-width: 0; }

/* Shared by both pages: the link row + abstract sit stacked below the
   citation row, indented to clear the thumb/badge column that sits above
   them (outside the normal text flow) so they line up under the citation
   text instead of the thumb. See render_recent_pub / render_publications --
   render_pub_citation_block() and render_pub_links_row() are the components
   both pages share to produce this identical layout. */
.recent-pub-body {
  margin-left: calc(84px + 0.65rem + 0.5rem);
  /* No margin-top here -- .pub-meta (the venue/year line right above this)
     already carries its own small bottom margin; stacking a second margin
     on top of that read as an unwanted blank line between the venue and
     the BibTeX/RIS link row. */
}
.recent-pub-body .pub-links { margin-top: 0; }
.recent-pub-body .blurb-indent { margin-top: 0.4rem; }

.page-links { display: flex; gap: 1.5rem; margin-top: 2rem; }

.pubs-header-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem 2rem;
}
.pubs-header-row h1 { margin: 0; }
.pubs-header-row .search-controls { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.pubs-header-row .search-control {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 15rem;
  max-width: 100%;
}
.pubs-header-row .search-control label {
  display: inline;
  margin-bottom: 0;
  white-space: nowrap;
}
.pubs-header-row .search-control input { flex: 1; min-width: 0; }

.pub-controls {
  display: grid;
  grid-template-columns: 9rem 1fr;
  gap: 1.75rem;
  align-items: start;
  margin: 1rem 0 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 0.5px solid var(--border);
}
.controls-left { display: flex; flex-direction: column; gap: 1.1rem; }
.control-col label, .control-col .control-label { display: block; font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.35rem; padding: 0; }
.sort-row { display: flex; gap: 0.5rem; }
.sort-control select {
  flex: 1;
  min-height: 2.25rem;
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-primary);
  padding: 0.25rem 0.5rem;
  font-size: 0.85rem;
}
#sort-dir {
  min-height: 2.25rem;
  min-width: 2.25rem;
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-primary);
  cursor: pointer;
}
#sort-dir:hover { background: var(--surface); }

.tag-pills { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.tag-pill {
  border: 0.5px solid var(--border);
  background: var(--surface);
  color: var(--text-secondary);
  border-radius: 999px;
  padding: 0.3rem 0.75rem;
  font-size: 0.78rem;
  cursor: pointer;
  min-height: 2rem;
}
.tag-pill:hover { background: var(--accent-bg); }
.tag-pill[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

.search-control input {
  width: 100%;
  min-height: 2.25rem;
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-primary);
  padding: 0.25rem 0.6rem;
  font-size: 0.92rem;
}

/* Publications-page-only: a left year-margin column, sitting to the left of
   the (otherwise identical-to-the-home-page) citation block -- Rick asked to
   keep this after the v47 reformat replaced it along with the rest of the
   old layout. .pub-row (the <article class="pub-entry pub-row" ...>) is
   flex here so .pub-margin-col and .pub-main sit side by side; .pub-entry
   still supplies the shared padding/border. Not used on the home page's
   recent-pubs cards (render_recent_pub never adds .pub-margin-col), so this
   is the one remaining place the two pages' layouts differ. */
.pub-row { display: flex; gap: 1rem; }
.pub-margin-col { width: 3.25rem; flex-shrink: 0; }
.pub-year-margin {
  color: var(--um-blue);
  font-family: var(--font-condensed);
  font-size: 1.55rem;
  font-weight: 400;
  padding-top: 0.1rem;
}
.pub-main { flex: 1; min-width: 0; }

/* Per-publication link row: plain text links, not buttons -- the BibTeX/RIS
   copy/download actions and "Copy APA citation" are <button>s (JS actions),
   PDF/Publisher/Scholar are real <a>s, all sharing this same styling so the
   row reads as one set of citation-related actions rather than competing
   for attention with the citation above it. Order: PDF/Publisher/Scholar
   first, then Copy APA citation, then BibTeX/RIS last -- see
   render_pub_links_row(). Rendered by the shared render_pub_links_row() on
   both the Publications
   page and the home page's recent-pubs list, directly under the citation
   block and above the abstract on both (see render_publications /
   render_recent_pub and .recent-pub-body above). Rick asked for these links
   in a blueish color and a slightly larger size than a typical
   secondary-text treatment, so they read as inviting actions rather than
   fine print. */
.pub-links {
  margin-top: 0.4rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 0.45rem;
  row-gap: 0.3rem;
}
.pub-link {
  font: inherit;
  font-size: 0.85rem;
  font-family: var(--font-body);
  color: var(--accent);
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-decoration: none;
}
.pub-link:hover { color: var(--um-blue); text-decoration: underline; }
/* A subtle separator between items, skipped before the first link in the
   row -- gap alone (the sitewide convention for e.g. the affiliations line)
   reads ambiguously here since these are several *distinct actions*, not
   one continuous phrase. Scoped to DIRECT children of .pub-links (the ">"
   combinator) -- .pub-link-group's own nested copy/download buttons are
   also .pub-link but several levels of "not first child" logic deep for a
   different reason (see .pub-link-group's own separator rule below); without
   the ">" scope this rule would incorrectly also dot the group's "copy"
   button, which is the group span's second child (after its label) even
   though it should read as the first action. */
.pub-links > .pub-link:not(:first-child)::before {
  content: "\00b7";
  display: inline-block;
  margin-right: 0.3rem;
  font-size: 1.3em;
  line-height: 1;
  color: var(--text-muted);
}
/* Compact "Label: copy download" group (_copy_download_group) -- BibTeX and
   RIS each need a Copy and a Download action, but spelling out "Download
   BibTeX"/"Copy BibTeX"/"Download RIS"/"Copy RIS" as four full-width
   .pub-link items ran wide; this packs the format name plus both short
   action words into one flex item instead, to save horizontal space in the
   row. Plain, non-clickable label text, same secondary-text color as the
   rest of the page's small labels (not --accent -- only the actions
   themselves should read as links). */
.pub-link-group { display: inline-flex; align-items: baseline; gap: 0.35rem; }
.pub-link-label { font-size: 0.85rem; font-family: var(--font-body); color: var(--text-secondary); }
/* No "·" between the group's own copy/download actions (unlike every other
   pair of items in the row) -- Rick asked for plain "copy download" spacing
   here, just the flex `gap` above, no dot. (An earlier round did add a dot
   via a scoped `.pub-link-group .pub-link + .pub-link::before` rule; removed
   per this feedback rather than left in place unused.) */
/* Separator before a whole group, same look as between any two .pub-link
   items -- groups aren't .pub-link themselves, so they need their own copy
   of the same rule. No margin-right here (unlike the .pub-link version
   above): because .pub-link-group is itself `display: inline-flex`, this
   ::before becomes the group's first flex item, so its own `gap` (above)
   already spaces it from the "BibTeX:"/"RIS:" label that follows -- adding
   a margin-right on top of that gap was stacking two spacers and is what
   made the label sit noticeably further from the dot than everywhere else
   in the row. */
.pub-link-group:not(:first-child)::before {
  content: "\00b7";
  display: inline-block;
  font-size: 1.3em;
  line-height: 1;
  color: var(--text-muted);
}

/* Standard clip-based visually-hidden pattern -- used for the Publications
   page's aria-live status region (see #pub-copy-status), which announces
   "Copied!" to screen readers without a visible on-page element. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Sidebar sits flush against main.main-full's left padding (which matches
   the nav bar's own padding, so it lines up with the logo above it). Three
   grid rows: row 1 is the `<h1>` alone (sidebar column left empty), row 2 is
   the intro paragraph, row 3 is the contribution grid -- the sidebar spans
   rows 2-3, so its top aligns with the *intro paragraph*, not the heading
   and not the grid. */
.contributions-layout {
  display: grid;
  grid-template-columns: 11rem 1fr;
  column-gap: 2rem;
  align-items: start;
}
.contributions-layout > h1 { grid-column: 2; grid-row: 1; max-width: var(--max-width); margin: 0 0 1.5rem; }
.contributions-intro {
  grid-column: 2;
  grid-row: 2;
  max-width: var(--max-width);
  color: var(--text-secondary);
  font-size: 0.98rem;
  line-height: 1.5;
  margin: 0 0 1.5rem;
}
/* top offset clears the sticky .site-nav bar above it (--nav-height) plus a
   small breathing gap, so the sidebar's top edge never scrolls in behind the
   nav bar -- previously just `top: 1rem`, which was fine before the nav bar
   itself became sticky. */
.contributions-nav { grid-column: 1; grid-row: 2 / span 2; position: sticky; top: calc(var(--nav-height) + 0.75rem); }
.contributions-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.contributions-nav a { color: var(--text-secondary); font-size: 0.85rem; line-height: 1.35; display: block; }
.contributions-nav a:hover { color: var(--accent); text-decoration: none; }

.contributions { grid-column: 2; grid-row: 3; max-width: var(--max-width); display: grid; grid-template-columns: 1fr 1fr; column-gap: 2.5rem; min-width: 0; }
.contribution { padding-top: 1.5rem; padding-bottom: 0.5rem; border-top: 0.5px solid var(--border); scroll-margin-top: calc(var(--nav-height) + 1rem); }
.contributions .contribution:nth-child(-n+2) { border-top: none; padding-top: 0; }
/* "Wide: yes" in contributions.org spans a single entry across both grid
   columns (e.g. for one that reads awkwardly narrow, or that you want to set
   apart visually). Note the :nth-child(-n+2) border-removal rule above
   assumes the first *two* DOM entries occupy row 1 -- if a wide entry is
   placed first or second, that assumption breaks and the border removal may
   need a manual once-off adjustment; it's untouched here since the current
   wide entry is last. */
.contribution-wide { grid-column: 1 / -1; }
/* Wide entries only: figure and key-papers box sit side by side in their own
   two-column row (using the extra width a wide entry has), narrative goes
   full-width below. figure_html always renders something (real image or the
   dashed placeholder), so this row never collapses to one item in practice. */
.contribution-wide-top { display: grid; grid-template-columns: 1fr 1fr; column-gap: 2.5rem; align-items: start; }
.contribution-wide-top .contribution-figure,
.contribution-wide-top .figure-placeholder { margin: 0; }
.contribution-wide-top .key-papers-box { margin: 0; }
/* Wide entries only: narrative text is narrower than the full two-column
   width for readability (Rick's spec: ~1.6 column-widths out of 2, i.e. 80%
   of the wide entry's full width), left-aligned with the left column's
   starting edge (not centered) so it lines up with the key-papers box above
   it. Normal (non-wide) entries are unaffected -- their narrative already
   fills their single column, which is narrow enough on its own. */
.contribution-wide > .narrative { max-width: 80%; margin-left: 0; margin-right: auto; }
.narrative p { margin: 0 0 0.75rem; font-size: 0.98rem; line-height: 1.45; }
/* Optional per-contribution "Click for more" fold (see the MORE marker in
   contributions.org / parse_contributions_org). Native <details>, so the
   folded paragraphs inherit the same .narrative p spacing/size once open --
   only the summary toggle itself needs its own styling. */
.narrative-more summary.narrative-toggle {
  cursor: pointer;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 500;
  list-style: none;
  margin-top: -0.25rem;
}
.narrative-more summary.narrative-toggle::-webkit-details-marker { display: none; }
.narrative-more summary.narrative-toggle::marker { content: ""; }
.narrative-more summary.narrative-toggle:hover { text-decoration: underline; }
.narrative-more[open] summary.narrative-toggle .label-open { display: none; }
.narrative-more:not([open]) summary.narrative-toggle .label-close { display: none; }
.narrative-more[open] p:first-of-type { margin-top: 0.75rem; }
.key-papers-box {
  margin: 0.75rem 0;
  padding: 0.75rem 1rem;
  background: var(--surface);
  border-radius: var(--radius);
}
.key-papers-label {
  font-family: var(--font-heading);
  font-size: 0.95rem;
  font-weight: 400;
  color: var(--um-blue);
  margin: 0 0 0.4rem;
}
.key-papers {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
.key-papers li { padding: 0.25rem 0; }
.figure-placeholder {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-bottom: 1rem;
}
/* Real contribution figures (rasterized once from a source PDF/image in
   the project root's graphics/ folder -- see figure_image_filename()) are
   plots/diagrams assumed to be drawn on a white background; a fixed white
   backing + padding keeps them legible in dark mode too, rather than a
   plot's white canvas floating directly against the dark page background. */
.contribution-figure {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 0 1rem;
  border-radius: var(--radius);
  background: #fff;
  padding: 0.5rem;
}
/* Hover-to-zoom for contribution figures: .contribution-figure-wrap is the
   positioning context (position: relative); the zoom copy is display:none
   until hovered, then shown as an absolutely-positioned overlay anchored to
   the wrap itself -- horizontally centered directly over wherever the small
   source figure sits on the page (not the viewport center, which put the
   zoom far from the figure whenever it wasn't already screen-centered) --
   sized to 85% of the contributions column's width (--max-width). CSS-only,
   no JS. */
.contribution-figure-wrap { position: relative; display: block; }
.contribution-figure-zoom {
  display: none;
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: calc(var(--max-width) * 0.85);
  max-width: 90vw;
  max-height: 85vh;
  object-fit: contain;
  z-index: 100;
  background: #fff;
  padding: 0.75rem;
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}
.contribution-figure-wrap:hover .contribution-figure-zoom { display: block; }

.bg-note {
  text-align: right;
  font-style: italic;
  font-size: 0.8rem;
  margin: 0.6rem 0 0;
}
.bg-note-trigger {
  position: relative;
  display: inline-block;
  color: var(--text-muted);
}
.bg-note-trigger:hover,
.bg-note-trigger:focus-visible {
  color: var(--accent);
}
.bg-note-tooltip {
  display: none;
  position: absolute;
  bottom: 100%;
  right: 0;
  padding: 0.6rem 0.8rem 1rem;
  width: 16rem;
  max-width: 70vw;
  background: var(--bg);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-secondary);
  font-style: normal;
  font-size: 0.8rem;
  line-height: 1.4;
  text-align: left;
  z-index: 20;
}
.bg-note-trigger:hover .bg-note-tooltip,
.bg-note-trigger:focus-visible .bg-note-tooltip {
  display: block;
}

/* Same sidebar-plus-content grid as .contributions-layout (see that rule's
   own comment for the row-alignment rationale) -- row 1 is the <h1> alone,
   row 2 is the whole .acknowledgments column of sections, sidebar spans
   row 2 only (no separate intro-paragraph row here, unlike Contributions). */
.acknowledgments-layout {
  display: grid;
  grid-template-columns: 11rem 1fr;
  column-gap: 2rem;
  align-items: start;
}
.acknowledgments-layout > h1 { grid-column: 2; grid-row: 1; max-width: var(--max-width); margin: 0 0 1.5rem; }
.ack-nav { grid-column: 1; grid-row: 2; position: sticky; top: calc(var(--nav-height) + 0.75rem); }
.ack-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.ack-nav a { color: var(--text-secondary); font-size: 0.85rem; line-height: 1.35; display: block; }
.ack-nav a:hover { color: var(--accent); text-decoration: none; }

.acknowledgments { grid-column: 2; grid-row: 2; max-width: var(--max-width); display: flex; flex-direction: column; gap: 2.5rem; min-width: 0; }
.ack-section { scroll-margin-top: calc(var(--nav-height) + 1rem); }
.ack-section h2 { margin-top: 0; }
/* Alumni/mentor person lists: 2-column CSS grid (NOT a multi-column flow --
   default grid auto-flow is row-by-row, so reading order is left-to-right
   then down, per Rick's request, rather than top-to-bottom-then-next-column). */
.alumni-list, .mentor-list {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem 2rem;
  font-size: 0.92rem;
}
.alumni-entry, .mentor-entry { padding-top: 1rem; border-top: 0.5px solid var(--border); }
/* First row (first 2 entries, since it's a 2-column grid) gets no top
   border/padding -- same :nth-child(-n+2) technique as .contributions'
   first row (see that rule). */
.alumni-list .alumni-entry:nth-child(-n+2),
.mentor-list .mentor-entry:nth-child(-n+2) {
  border-top: none;
  padding-top: 0;
}
.alumni-name-line, .mentor-name-line { margin: 0; line-height: 1.4; }
/* Alumni bios only (not mentors): hanging indent for the whole entry, plus
   a slightly larger name -- classic hanging-indent technique, the same
   idea as a bibliography entry. padding-left indents everything in the
   <li> (name, meta, thesis lines alike); the name line's matching negative
   text-indent then pulls just ITS first line back out to the margin, so
   the name reads flush-left while the meta/thesis lines underneath (and
   any wrapped second line of a long name) sit indented beneath it. */
.alumni-entry { padding-left: 1.3em; }
.alumni-name-line { font-size: 1.05rem; text-indent: -1.3em; }
/* Flags a "Current ..." role/status (still active in the lab, not a past
   alumnus) in the meta line -- see render_alumnus_entry. */
.alumni-current { color: var(--current-affiliation); }
.alumni-affiliation-line, .alumni-meta-line, .mentor-meta-line, .alumni-thesis-line {
  margin: 0.15rem 0 0;
  color: var(--text-secondary);
  line-height: 1.4;
}
.alumni-list a, .mentor-list a { color: var(--text-primary); }
.alumni-list a:hover, .mentor-list a:hover { color: var(--accent); }
.ack-list {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: 0.98rem;
}
.ack-list a { color: var(--text-primary); }
.ack-list a:hover { color: var(--accent); }
.ack-empty { color: var(--text-muted); font-style: italic; font-size: 0.9rem; margin: 0.5rem 0 0; }
.ack-entry + .ack-entry { margin-top: 2rem; padding-top: 2rem; border-top: 0.5px solid var(--border); }
.ack-image {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 1rem 0 0.5rem;
  border-radius: var(--radius);
  border: 0.5px solid var(--border);
}
.ack-attribution { font-size: 0.85rem; color: var(--text-muted); margin: 0 0 1rem; }
.ack-story p { margin: 0 0 1rem; font-size: 0.98rem; line-height: 1.5; }
.ack-links {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
  font-size: 0.85rem;
}

/* Patterns of Thought: the <h1> stands alone, full width, above a
   two-column row -- image in a 35fr 65fr-ratio left column, story
   text (and the video embed) in the right column. Plain CSS grid, image as
   an ordinary grid item (not floated), so it never wraps text around
   itself. */
.pot-layout { display: grid; grid-template-columns: 35fr 65fr; gap: 2rem; align-items: start; margin-top: 1.5rem; }
.pot-image { width: 100%; border-radius: var(--radius); display: block; }
.pot-story { margin-top: 0; }
.pot-story p { font-size: 0.98rem; line-height: 1.55; margin: 0; }
.pot-video-wrapper {
  position: relative; width: 100%; max-width: 640px; aspect-ratio: 16 / 9;
  margin-top: 1.5rem; border-radius: var(--radius); overflow: hidden; background: #000;
}
.pot-video-wrapper iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

@media (max-width: 700px) {
  /* Below the breakpoint, drop the indent entirely -- .hero-content spans
     full-width again (matching .landing-columns' own single-column
     collapse right below) rather than staying pushed in from an indent
     computed for a much wider viewport. */
  .hero-content { margin-left: 0; flex-direction: column; align-items: flex-start; }
  .landing-columns { grid-template-columns: 1fr; gap: 1.5rem; }
  .acknowledgments-layout { grid-template-columns: 1fr; row-gap: 1rem; }
  .acknowledgments-layout > h1 { grid-column: 1; grid-row: 1; max-width: none; }
  .ack-nav { grid-column: 1; grid-row: 2; position: static; }
  .ack-nav ul { flex-direction: row; flex-wrap: wrap; gap: 0.35rem 1rem; }
  .acknowledgments { grid-column: 1; grid-row: 3; max-width: none; }
  .alumni-list, .mentor-list { grid-template-columns: 1fr; }
  main { padding: 1.5rem 1rem 3rem; }
  .site-nav { padding: 0.75rem 1rem; }
  .pub-controls { grid-template-columns: 1fr; }
  .contributions-layout { grid-template-columns: 1fr; row-gap: 1rem; }
  .contributions-layout > h1 { grid-column: 1; grid-row: 1; max-width: none; }
  .contributions-intro { grid-column: 1; grid-row: 2; max-width: none; }
  .contributions-nav { grid-column: 1; grid-row: 3; position: static; }
  .contributions-nav ul { flex-direction: row; flex-wrap: wrap; gap: 0.35rem 1rem; }
  .contributions { grid-column: 1; grid-row: 4; max-width: none; grid-template-columns: 1fr; }
  /* Desktop's :nth-child(-n+2) removes the top border from both items in row
     1 of the 2-column grid; collapsed to a single column here, that would
     wrongly strip the border from item 2 as well, so restore it. */
  .contributions .contribution:nth-child(2) { border-top: 0.5px solid var(--border); padding-top: 1.5rem; }
  .contribution-wide-top { grid-template-columns: 1fr; row-gap: 1rem; }
  .pot-layout { grid-template-columns: 1fr; gap: 1rem; }
  /* Hero banner: turned off below the site's one mobile breakpoint
     (--breakpoint, 700px by default) rather than trying to make its
     sticky/80vw-width/nowrap-word layout responsive -- Rick found it
     didn't hold up well on a narrow window, and didn't want it at all in
     "iphone mode" (an iPhone's own viewport is always well under 700px,
     even the largest models in landscape are close but still under; an
     iPad in portrait is comfortably over 700px, so this doesn't touch it
     -- matches Rick's "ipad ok" call). Only .hero-banner and .hero-nav-
     backdrop are hidden here, NOT the whole .hero-banner-track -- since
     the name/photo/affiliations header now lives inside that track too
     (see .hero-banner-track's own comment above), hiding the track
     entirely would wrongly take the header down with it. The track's own
     negative margin-top (pulling it up into the nav's wasted logo space,
     a banner-specific trick) is zeroed out along with it, and
     .hero-header-inner gets a plain, modest top margin instead of
     hero-gap (which was sized for the gap AFTER the banner, not for
     sitting directly under the nav with no banner at all). .site-nav's
     own background has to be forced back to opaque here too, since the
     `:has(~ .hero-banner-track)` rule that makes it transparent (see
     that rule's own comment) matches on DOM structure regardless of
     `display: none` on its own descendants, and without this override
     the nav would otherwise go transparent over ordinary scrolled
     content with nothing behind it to justify that. */
  .hero-banner, .hero-nav-backdrop { display: none; }
  .hero-banner-track { margin-top: 0; }
  .hero-header-inner { margin-top: 1.5rem; }
  .site-nav:has(~ .hero-banner-track) { background: var(--bg); border-bottom-color: var(--border); }
}
