/* ===========================================================
   Tech Policy Hub — design system
   Fonts/colors: Source Sans Pro body/headings, Roboto Condensed
     bold-uppercase nav/labels, Crimson Text accent, #E21833 red,
     #272727 dark band, #F8F9FA nav band, #F8F8F8 sidebar fill.
   Structure: implements the redesign brief's content architecture
     -- shallow Research/People/Events/News/About nav, affiliation
     strip, Bloomberg-style signal ticker, featured research hero,
     Questions We Answer, Research/People/Events pathways, an
     external reading carousel (Phronesis + Tech Policy Press),
     card-based research/news/people grids, dark About band, and a
     minimal footer with a bottom wordmark + copyright bar.
   =========================================================== */

@import url('https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&family=Roboto+Condensed:wght@400;700&family=Crimson+Text:ital@0;1&display=swap');

:root{
  --umd-red: #E21833;
  --umd-red-dark: #A6081F;
  --umd-gold: #FFD200;
  --accent-teal: #0B7285;
  --ink: #212529;
  --header-band: #272727;
  --nav-band: #F8F9FA;
  --sidebar-band: #F8F8F8;
  --ink-soft: #52565A;
  --paper: #FFFFFF;
  --paper-soft: #F6F5F2;
  --slate-gray: #6B7176; /* darker frame gray -- see .spotlight-track */
  --line: #DDE0E3;
  --radius: 2px;
  --radius-lg: 20px; /* Follow-up (direct user request): the homepage's
     "Guiding Questions" and "Stay in the Loop" bands used to be full-
     bleed color fields running edge-to-edge of the browser window. The
     user wanted them as contained content instead -- rounded-rectangle
     cards with page whitespace on either side, not walls of color
     touching the window edges. This token is the corner radius those
     two cards (and any future ones) use, distinct from --radius's much
     tighter 2px used on buttons/pills. */
  --font-body: 'Source Sans 3', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-nav: 'Roboto Condensed', 'Source Sans 3', sans-serif;
  --font-accent: 'Crimson Text', Georgia, serif;
  /* Follow-up (direct user request): the user compared a screenshot of
     the site zoomed to 110% against the default 100% render and asked
     for the default to fill the window the way the zoomed-in version
     did. Measuring both screenshots pixel-for-pixel, the zoomed-in
     content column was consistently ~10% wider relative to the window
     than the default -- exactly what 110% browser zoom does (it scales
     the whole rendered page, container included, without changing the
     physical screenshot size). Rather than anything zoom-related, the
     fix is to widen the site's own content container by that same
     ~10% so every visitor gets the fuller layout by default: 1320px
     was the sole full-width value in the initial reference design; the
     nearest clean number to +10% is 1440px, a standard content-width
     figure in its own right. */
  --maxw: 1440px;
}

*{ box-sizing: border-box; }
html{ scroll-behavior: smooth; }
body{
  margin:0;
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--paper);
  line-height: 1.6;
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
}
h1,h2,h3,h4{
  font-family: var(--font-body);
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 .5em;
  color: var(--ink);
}
h1{ font-size: clamp(2rem, 3.6vw, 2.75rem); }
h2{ font-size: clamp(1.5rem, 2.2vw, 1.9rem); }
h3{ font-size: 1.2rem; font-weight: 700; }
p{ margin: 0 0 1em; color: var(--ink-soft); }
a{ color: var(--umd-red); text-decoration: none; }
a:hover{ text-decoration: underline; }
img{ max-width:100%; display:block; }
ul{ padding-left: 1.2em; }
.container{ max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
/* Follow-up (direct user request): Research and People were called out
   as looking "stretched" next to Home and Events. Home and Events don't
   actually use a smaller .container -- they just happen to keep their
   main content in a narrower internal column (Home's middle lead-grid
   column, Events' with-sidebar main column both land around 620-630px)
   because those pages are built as rail/sidebar layouts. Research's
   People/Projects/Publications lists and People's person rows have no
   such sidebar, so they were filling the full 1272px inner width, which
   reads as noticeably wider/emptier-feeling than the other pages even
   though the outer container is identical. .container-narrow gives just
   those two pages' main content sections a matching narrower ceiling
   without touching Home, Events, or the page-hero bands (breadcrumb/
   title) which stay full width for visual consistency across all pages. */
/* Scaled up with the --maxw bump above (1320px -> 1440px, a +~9%
   change) so Research/People keep the same ~0.82 ratio to the main
   container that fixed their "stretched next to Home/Events" look --
   otherwise widening --maxw alone would have reopened that gap from
   the other direction. */
.container-narrow{ max-width: 1180px; }
section{ padding: 56px 0; }
.section-tight{ padding: 34px 0; }
.eyebrow{
  font-family: var(--font-nav);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--umd-red);
  display: inline-block;
  margin-bottom: 14px;
}
.rule{ border: none; border-top: 1px solid var(--line); margin: 0; }
.soft-bg{ background: var(--paper-soft); }
.ink-bg{ background: var(--header-band); color: #fff; }
.ink-bg h1, .ink-bg h2, .ink-bg h3{ color: #fff; }
.ink-bg p{ color: #C7CACD; }
.label-note{ font-size: .92rem; color: var(--ink-soft); margin-top: 6px; max-width: 46ch; }

/* ---------- Buttons ---------- */
.btn{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-nav);
  font-weight: 700;
  font-size: .88rem;
  letter-spacing: .02em;
  text-transform: uppercase;
  padding: 12px 22px;
  border-radius: var(--radius);
  border: 1.5px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  text-decoration: none !important;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
  white-space: nowrap;
}
.btn:hover{ background: var(--ink); color: #fff; }
.btn-primary{ background: var(--umd-red); color: #fff; border-color: var(--umd-red); }
.btn-primary:hover{ background: var(--umd-red-dark); border-color: var(--umd-red-dark); }
.btn-ghost{ background: transparent; color: var(--ink); border-color: var(--ink); }
.btn-white{ border-color: #fff; color: #fff; }
.btn-white:hover{ background: #fff; color: var(--ink); }
.btn-gold{ border-color: var(--umd-gold); color: var(--umd-gold); }
.btn-gold:hover{ background: var(--umd-gold); color: var(--ink); border-color: var(--umd-gold); }
.btn-arrow::after{ content: '→'; margin-left: 2px; transition: transform .15s ease; }
.btn-arrow:hover::after{ transform: translateX(3px); }
.text-link{
  font-family: var(--font-nav); font-weight: 700; font-size: .9rem; text-transform: uppercase; letter-spacing: .02em;
  color: var(--ink); border-bottom: 1px solid var(--ink); padding-bottom: 2px; display: inline-block;
}
.text-link:hover{ color: var(--umd-red); border-color: var(--umd-red); text-decoration: none; }
.center-cta{ text-align: center; padding-top: 12px; }

/* ---------- Header: single row, brand | nav | actions ---------- */
header.site-header{
  position: sticky; top: 0; z-index: 200;
  background: #fff;
  border-bottom: 1px solid var(--line);
}
.header-inner{
  display:flex; align-items:center; justify-content:space-between;
  /* padding-top/bottom only -- .header-inner shares this element with
     .container (see header() in generate.py), so a `padding` shorthand
     here would zero out .container's left/right 24px and pin the logo
     and nav flush to the viewport edges. That bug was invisible on
     desktop (max-width centers the whole header with slack to spare)
     but pinned everything to the screen edges on mobile. */
  padding-top: 18px; padding-bottom: 18px; gap: 20px;
  --brand-mark-h: 64px; /* height for .brand-mark, see below */
}
.brand-lockup{ display:flex; align-items:center; gap:18px; min-width:0; flex:1 1 auto; overflow:hidden; }
.brand{ display:flex; align-items:center; text-decoration:none !important; min-width:0; }

/* Single combined logo mark: UMD seal + "School of Public Policy" +
   "Tech Policy Hub" lockup, mirroring the School of Public Policy's own
   institute logos. Replaces the old separate GoTech seal image +
   divider + "TECH POLICY HUB" wordmark box. Sized off --brand-mark-h
   (set on .header-inner, overridden per breakpoint below). */
.brand-mark{ height: var(--brand-mark-h); width:auto; flex:none; transition: opacity .15s ease; }
.brand:hover .brand-mark{ opacity:.72; }

nav.primary-nav{ display:flex; align-items:center; gap: 4px; }
nav.primary-nav > ul{ display:flex; align-items:center; gap: 2px; list-style:none; margin:0; padding:0; }
nav.primary-nav > ul > li{ position: relative; }
nav.primary-nav a.nav-link{
  display:flex; align-items:center; gap:5px; padding: 10px 15px; font-family: var(--font-nav); font-weight:700; font-size:.92rem;
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--umd-red);
}
nav.primary-nav a.nav-link:hover{ color: var(--ink); text-decoration:none; }
nav.primary-nav li.current > a.nav-link{ color: var(--ink); }
.caret{ font-size:.62em; transition: transform .15s ease; }
li.has-dropdown.open .caret{ transform: rotate(180deg); }

.dropdown{
  position:absolute; top: 100%; left: 0; transform:none;
  background:#fff; border:1px solid var(--line); box-shadow: 0 8px 16px rgba(0,0,0,.08);
  width: max-content; min-width: 220px; padding: 6px 0; margin: 6px 0 0; display:none;
  list-style: none; z-index: 50;
}
.dropdown li{ margin:0; padding:0; list-style:none; }
li.has-dropdown:hover .dropdown,
li.has-dropdown:focus-within .dropdown,
li.has-dropdown.open .dropdown{ display:block; }
.dropdown a{ display:block; padding:10px 16px; color: var(--ink); font-family: var(--font-body); font-size:.92rem; font-weight:600; text-transform:none; }
.dropdown a:hover{ background: var(--paper-soft); color: var(--umd-red); text-decoration:none; }

.header-actions{ display:flex; align-items:center; gap:12px; flex:none; }
.nav-toggle{ display:none; background:none; border:none; cursor:pointer; padding:8px; }
.nav-toggle span{ display:block; width:22px; height:2px; background:var(--ink); margin:5px 0; transition: transform .2s ease, opacity .2s ease; }
/* Hamburger -> X, driven purely by .primary-nav's own "open" class via
   a sibling selector (nav.primary-nav comes before .header-actions in
   the markup -- see header() in generate.py) -- no JS needed to toggle
   an icon class separately. Gives the button itself a visible "this
   closes the menu" cue once open, on top of the explicit .nav-close
   button and backdrop click below. */
nav.primary-nav.open ~ .header-actions .nav-toggle span:nth-child(1){ transform: translateY(7px) rotate(45deg); }
nav.primary-nav.open ~ .header-actions .nav-toggle span:nth-child(2){ opacity:0; }
nav.primary-nav.open ~ .header-actions .nav-toggle span:nth-child(3){ transform: translateY(-7px) rotate(-45deg); }
/* Both hidden by default (desktop) -- shown only in the collapsed-nav
   breakpoint below, same pattern as .nav-toggle itself. */
.nav-panel-head{ display:none; }
.nav-backdrop{ display:none; }

/* ---------- Affiliation strip ---------- */
.affiliation-strip{ background: var(--nav-band); border-bottom: 1px solid var(--line); }
.affiliation-strip .container{ padding-top:8px; padding-bottom:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.affiliation-strip span, .affiliation-strip a{
  font-family: var(--font-nav); font-size:.74rem; letter-spacing:.03em; text-transform:uppercase; color: var(--ink-soft);
}
.affiliation-strip a{ text-decoration: none !important; transition: color .15s ease; }
.affiliation-strip a:hover{ color: var(--umd-red); }
.affiliation-strip .dot{ color: var(--line); }

/* ---------- Signal ticker (NYSE-tape style: one short line, label and
   scrolling lane share a single flex row, dark bg, gold top/bottom
   rule). Scope is DMV + federal tech policy tracking only; each card is
   badged with its jurisdiction via .signal-card .jurisdiction. Streams
   continuously (main.js drives .ticker-viewport's scrollLeft via
   requestAnimationFrame -- see ticker_track_html() in generate.py for
   why the item list is duplicated), pauses on hover, and is
   click-and-drag scrubbable in either direction while paused. ---------- */
.signal-ticker{
  display:flex; align-items:stretch; background:#1a1a1a; height:36px;
  border-top: 3px solid var(--umd-gold); border-bottom: 3px solid var(--umd-gold);
}
.ticker-live{
  display:flex; align-items:center; gap:6px; padding: 0 14px; font-family: var(--font-nav); font-size:.68rem;
  font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:#9a9ea3;
  flex:none; border-right: 1px solid rgba(255,255,255,.14); white-space:nowrap;
}
.ticker-live .dot{ width:6px; height:6px; border-radius:50%; background:#E0292F; flex:none; animation: ticker-live-pulse 1.6s ease-in-out infinite; }
@keyframes ticker-live-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:.35; } }
.ticker-viewport{
  flex: 1 1 auto; min-width:0; display:flex; align-items:center;
  overflow: hidden; cursor: grab;
  /* No native scroll here at all (not even overflow-x:auto) -- the tape's
     position is driven entirely by main.js setting a transform on
     .ticker-track-inner, both for the auto-scroll and for drag/touch
     scrubbing. An earlier version used .scrollLeft with overflow-x:auto,
     which real mobile Safari would silently ignore programmatic writes to
     (via its native momentum-scroll compositor) until physically touched
     -- reading as "the ticker never moves on mobile." transform sidesteps
     that whole bug class since there's no native scroll for the browser
     to intercept. */
}
.ticker-viewport.dragging{ cursor: grabbing; user-select: none; }
.ticker-track-inner{ display:flex; align-items:center; width:max-content; will-change: transform; }
.signal-card{
  display:flex; align-items:center; gap:8px; padding: 0 18px; border-right: 1px solid rgba(255,255,255,.12);
  flex: none; text-decoration: none !important;
}
.signal-card .jurisdiction{
  font-family: var(--font-nav); font-size:.6rem; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  color:#1a1a1a; background: var(--umd-gold); border-radius:3px; padding:1px 6px; flex:none;
}
.signal-card .datum{ font-size:.78rem; color:#fff; font-weight:600; white-space:nowrap; }
.signal-card:hover .datum{ text-decoration: underline; }

/* ---------- Lead section: SCOTUSblog-style compact 3-column grid --
   secondary headlines / lead story / Hub-updates rail, no oversized
   placeholder art. Replaces the old single-story "featured-hero" block. ---------- */
/* Follow-up (direct user request): the homepage's top section (What
   We Do / Research Spotlight / Hub News, down through Research Areas)
   was taller than a typical browser window, so part of it was hidden
   below the fold on load -- the user wants everything down to "What
   we're reading" visible without scrolling. Tightened this section's
   own padding plus a series of internal spacing/line-height trims
   below (.rail-head, .spotlight-slide, .spotlight-footer,
   .rail-head--stacked, .research-matrix--rail .matrix-cell,
   .lead-secondary p) to reclaim that height. */
/* Follow-up (direct user request): the 12px top padding above was too
   tight against the signal ticker right above this section -- restored
   some breathing room here (28px->12px->24px) even though it eats back
   into the above-the-fold slack the tighter value bought; the user
   flagged the crowding as the more pressing problem on the live site. */
.lead-section{ padding: 24px 0 14px; border-bottom: 1px solid var(--line); }
/* Follow-up (direct user request): dropped the explicit align-items:
   start so the three columns stretch to match the tallest one (the
   Research Spotlight, .lead-story) again -- with `start`, the two side
   columns' own boxes (and their border-right/border-left divider lines)
   only ran as tall as their OWN content, stopping well short of the
   spotlight's bottom instead of reaching it, which read as the vertical
   dividers and the Hub News rail's bottom line not lining up with the
   rest of the row. Reverting to the grid default (stretch) makes both
   side columns' boxes -- and their divider borders -- extend the full
   row height. The Hub News scroll box's own capped height (set by the
   IIFE in main.js, just below) is unaffected: that JS now measures
   .research-matrix--rail's own content bottom directly rather than
   .lead-secondary's box, so it isn't thrown off by .lead-secondary's
   box now being taller than its content. */
.lead-grid{ display:grid; grid-template-columns: 280px 1fr 280px; gap: 40px; }
/* Direct user request: "when you click About & Contact you should be
   taken to the What We Do section" -- id="about" sits on this element
   (see home_body in build_all.py). Without scroll-margin-top, a native
   #about jump lands this element's top flush with the viewport's very
   top edge, which is exactly where .site-header sits (position:sticky,
   top:0) -- so the "What We Do" heading would land hidden BEHIND the
   header instead of visible below it. 120px clears the header's own
   ~101px height with a little breathing room to spare, at every width
   (header height barely changes between the desktop and collapsed-nav
   layouts). */
#about{ scroll-margin-top: 120px; }
.lead-secondary{ grid-column: 1; display:flex; flex-direction:column; }
.lead-secondary > .rail-head{ padding-right: 28px; }
.lead-secondary-body{ flex: 1 1 auto; display:flex; flex-direction:column; border-right: 1px solid var(--line); padding-right: 28px; }
/* Follow-up: briefly matched to .rail-item h4's darkness/weight (Hub
   News headlines), then walked back per direct user request -- back to
   the plain sitewide p{ color: var(--ink-soft) } weight/color. */
.lead-secondary p{ font-size:.88rem; line-height:1.5; }
/* Follow-up: opening phrase is bold + var(--ink) black, matching the
   Research Areas card titles (.matrix-cell h3) per direct user request
   -- overrides the sitewide p{ color: var(--ink-soft) } rule, which was
   otherwise making the bold strong text still read as gray. The founder
   link keeps its own bold/red treatment below (link color already comes
   from the sitewide a{color:var(--umd-red)} rule; this just adds bold). */
.lead-join strong{ color: var(--ink); }
.lead-join a{ font-weight:700; }
.lead-story{ grid-column: 2; }
/* Follow-up (direct user request): "Hub News" was sitting 28px right
   of the column's true left edge (the old padding-left, kept from
   before the divider moved off .lead-rail onto .lead-rail-body below)
   while "What We Do" opposite it sits flush left -- the border-bottom
   underline spans the full column width regardless of this padding
   (block-level, not sized to the text), so that padding was only ever
   shifting the text, not preserving the underline's width. Dropped so
   both headers start at the same left offset. */
.lead-rail{ grid-column: 3; display:flex; flex-direction:column; }
.lead-rail-body{ flex: 1 1 auto; display:flex; flex-direction:column; border-left: 1px solid var(--line); padding-left: 28px; }
.lead-media{
  aspect-ratio: 16/7; margin-bottom: 18px; position: relative; overflow: hidden;
  background: linear-gradient(135deg, var(--ink) 0%, var(--ink) 42%, var(--umd-red) 42%, var(--umd-red) 100%);
}
/* Follow-up (direct user request): lead_media_html() in generate.py can
   now take a real photo path instead of the abstract diagonal-graphic
   placeholder. object-fit:cover crops the photo to fill this box at
   whatever aspect ratio it's shot at -- it never stretches/distorts the
   photo the way a plain width:100%;height:100% would, regardless of the
   source image's own proportions. */
.lead-media img{ position:absolute; inset:0; width:100%; height:100%; object-fit: cover; }
.lead-media::before{
  content:''; position:absolute; inset:0;
  background-image: repeating-linear-gradient(135deg, rgba(255,255,255,.07) 0 2px, transparent 2px 16px);
}
.lead-media .topic-mark{
  position:absolute; left:18px; bottom:14px; font-family: var(--font-nav); font-size:.76rem; font-weight:700;
  letter-spacing:.06em; text-transform:uppercase; color:#fff;
}
.lead-story .meta{ font-family: var(--font-nav); font-size:.8rem; letter-spacing:.04em; color: var(--ink-soft); text-transform: uppercase; margin-bottom: 6px; }
/* Scoped override of the shared .hero-actions{margin-top:24px} (also used
   by .page-hero elsewhere) -- tighter here since this whole card is
   fighting for vertical space in the above-the-fold homepage layout. */
.spotlight-slide .hero-actions{ margin-top: 10px; }
.lead-story h1{ margin-bottom: .4em; font-size: clamp(1.35rem, 2.1vw, 1.85rem); }
.lead-story h1 a{ color: var(--ink); }
.lead-story h1 a:hover{ color: var(--umd-red); text-decoration: none; }
.lead-story .lede{ font-size: .92rem; max-width: 52ch; }
/* Research Spotlight -- a bordered card (.spotlight-card) rebuilt per a
   Bloomberg "Today's Videos" card the user provided as a reference: the
   image, the title/summary content, and a bottom bar pairing the slide
   dots with the prev/next arrows. The card's own header strip (label +
   "View All Research" link) from an earlier pass was dropped per user
   feedback -- the plain .rail-head above the card (matching "What We
   Do"/"Research Areas"/"Hub News") handles the label now, so the image
   is the card's first/top element. Rounded corners + overflow:hidden
   apply to the whole card rather than per-section, which is what
   actually rounds the image's top corners and .spotlight-footer's
   bottom corners specifically, per direct user request. Slide content
   still reuses the .meta/.lead-media/h1/.lede/.hero-actions rules above
   (descendant selectors under .lead-story, which still wraps this).
   .spotlight-media-track (the image) and .spotlight-track (title/meta/
   credit/summary/actions) are each their own CSS Grid with every slide
   sharing one grid-area, so each track's height stays fixed to its
   tallest slide no matter which one is active -- main.js's show()
   toggles the matching .spotlight-media-slide/.spotlight-slide pair
   (same data-slide index) together. */
.spotlight-card{
  position: relative; background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px; overflow: hidden;
}
/* Gold accent moved to the bottom of the image (was the card's top
   border) per direct user request. */
.spotlight-media-track{
  position: relative; display: grid; grid-template-areas: "slide";
  border-bottom: 6px solid var(--umd-gold);
}
.spotlight-media-slide{ grid-area: slide; opacity:0; pointer-events:none; transition: opacity .6s ease-in-out; }
.spotlight-media-slide.is-active.is-visible{ opacity:1; pointer-events:auto; }
.spotlight-media-slide .lead-media{ margin-bottom: 0; }
.spotlight-track{ position: relative; display: grid; grid-template-areas: "slide"; }
/* Fade (not a hard cut) -- main.js's show() removes .is-visible first
   (opacity 1->0 over FADE_MS), waits for that to finish, THEN swaps
   .is-active to the next slide and adds .is-visible back (opacity
   0->1). FADE_MS here must match the JS constant of the same name. */
.spotlight-slide{
  grid-area: slide; opacity:0; pointer-events:none;
  transition: opacity .6s ease-in-out;
  padding: 10px 18px;
}
.spotlight-slide.is-active.is-visible{ opacity:1; pointer-events:auto; }
/* Fixed-height title/summary via a hard 2-line cap so there's never a
   taller sibling slide inflating the reservation -- the tradeoff is
   that a title/summary long enough to need a 3rd line is truncated
   with an ellipsis rather than reserving room for it. */
.spotlight-slide h1{
  line-height: 1.2; min-height: 2.2em; margin-bottom: .3em;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.spotlight-slide .lede{
  max-width: none; line-height: 1.35; min-height: 2.6em;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
/* Small filled red chip for the tag word ("Publication"/"Award"/etc). */
.meta-tag{
  display:inline-block; background: var(--umd-red); color:#fff; font-weight:700; text-transform:uppercase;
  font-size:.68rem; letter-spacing:.03em; padding: 3px 8px; border-radius: var(--radius); margin-right: 6px;
}
/* Credit line -- who the research is from. The Hub spans multiple
   campus departments/labs and the Spotlight isn't only for Hub-
   authored work, so every slide names its source (Hub team or an
   outside lab/department) rather than assuming one shared affiliation. */
.spotlight-credit{ font-size:.82rem; font-style: italic; color: var(--ink-soft); margin: 4px 0 10px; }
/* Bottom bar -- dots (playback position) on the left, prev/next arrows
   on the right, both static flex items now (not absolutely positioned
   over/beside the image) since they live in their own row below the
   content, matching the reference's paired bottom controls. Same
   --paper-soft gray the card's old header strip used, per direct user
   request, even with that strip itself gone now. */
.spotlight-footer{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding: 6px 18px; background: var(--paper-soft); border-top: 1px solid var(--line);
}
.spotlight-dots{ display:flex; gap:8px; }
.spotlight-dot{
  width:8px; height:8px; padding:0; border-radius:50%; border:1.5px solid var(--ink-soft);
  background: transparent; cursor:pointer; transition: background .15s ease, border-color .15s ease;
}
.spotlight-dot:hover{ border-color: var(--umd-red); }
.spotlight-dot.is-active{ background: var(--ink); border-color: var(--ink); }
.spotlight-arrows{ display:flex; gap:8px; }
/* CSS-drawn chevrons (a rotated two-sided border corner), matching the
   project's no-icon-font convention already used by .spotlight-dot/
   .spotlight-pause. */
.spotlight-arrow{
  position: static; width: 32px; height: 32px; padding: 0; border-radius: 50%;
  border: 1.5px solid var(--ink-soft); background: transparent;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: border-color .15s ease, background .15s ease;
}
.spotlight-arrow:hover{ border-color: var(--umd-red); background: var(--paper); }
.spotlight-arrow::before{
  content: ''; width: 8px; height: 8px;
  border-right: 2px solid var(--ink-soft); border-top: 2px solid var(--ink-soft);
  transition: border-color .15s ease;
}
.spotlight-arrow:hover::before{ border-color: var(--umd-red); }
.spotlight-prev::before{ transform: rotate(-135deg); margin-left: 4px; }
.spotlight-next::before{ transform: rotate(45deg); margin-right: 4px; }
/* Pause/play toggle -- CSS-drawn icon (two bars = "playing, click to
   pause"; a triangle = "paused, click to play"), swapped via .is-paused.
   Lives INSIDE .spotlight-media-track (position:relative) as an overlay
   in the image's bottom-right corner -- a translucent white circle so
   the icon stays legible against whichever color the current slide's
   image graphic happens to be. */
.spotlight-pause{
  position: absolute; right: 10px; bottom: 10px; z-index: 5;
  width: 30px; height: 30px; padding: 0; border-radius: 50%;
  border: 1.5px solid var(--ink-soft); background: rgba(255,255,255,.85); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: border-color .15s ease, background .15s ease;
}
.spotlight-pause:hover{ border-color: var(--umd-red); background: #fff; }
.spotlight-pause .bar{ display:block; width:2.5px; height:9px; background: var(--ink-soft); border-radius:1px; }
.spotlight-pause .bar + .bar{ margin-left: 3px; }
.spotlight-pause .tri{
  display: none; width:0; height:0; margin-left: 2px;
  border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-left: 8px solid var(--ink-soft);
}
.spotlight-pause:hover .bar{ background: var(--umd-red); }
.spotlight-pause:hover .tri{ border-left-color: var(--umd-red); }
.spotlight-pause.is-paused .bar{ display:none; }
.spotlight-pause.is-paused .tri{ display:block; }
@media (max-width: 1150px){
  .lead-grid{ grid-template-columns: 1fr; }
  .lead-secondary, .lead-story, .lead-rail{ grid-column: 1; }
  .lead-secondary > .rail-head{ padding-right:0; }
  .lead-secondary-body{ border-right:none; padding-right:0; }
  .lead-rail-body{ border-left:none; padding-left:0; }
  .lead-secondary, .lead-rail{ border-top: 2px solid var(--ink); padding-top: 24px; }
  .lead-story{ order: -1; }
}

/* ---------- Questions We Answer ---------- */
.questions-grid{ display:grid; grid-template-columns: 1fr 1fr; gap: 2px; background: var(--line); }
.question-card{
  background:#fff; padding: 38px 34px; display:flex; flex-direction:column; justify-content:space-between; gap: 22px;
  min-height: 210px; text-decoration: none !important; transition: background .15s ease;
}
.question-card:hover{ background: var(--paper-soft); }
.question-card h3{
  font-family: var(--font-accent); font-style: italic; font-weight: 400; font-size: clamp(1.2rem, 2vw, 1.5rem);
  color: var(--ink); line-height: 1.3; margin: 0;
}
.question-card .qtag{
  font-family: var(--font-nav); font-size:.76rem; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  color: var(--umd-red); display:inline-flex; align-items:center; gap:6px;
}
.question-card .qtag::after{ content:'→'; transition: transform .15s ease; }
.question-card:hover .qtag::after{ transform: translateX(3px); }
/* Homepage's curated 4-of-6 preview: tighter than the base card so this
   section doesn't compete for the same visual weight as the lead grid
   above it -- it's elaboration, not the opening pitch. */
.questions-grid--compact .question-card{ padding: 24px 22px; min-height: 150px; gap: 14px; }
.questions-grid--compact .question-card h3{ font-size: clamp(1.05rem, 1.6vw, 1.25rem); }

/* Compact text-list treatment for About's full 6-question set --
   deliberately distinct from the homepage's card treatment above so the
   same content doesn't read as "the same section, twice." */
.question-list{ border-top: 1px solid var(--ink); }
.question-row{
  display:grid; grid-template-columns: 180px 1fr; gap: 22px; align-items:baseline;
  padding: 18px 0; border-bottom: 1px solid var(--line); text-decoration: none !important;
}
.question-row .qtag{
  font-family: var(--font-nav); font-size:.76rem; font-weight:700; letter-spacing:.03em; text-transform:uppercase; color: var(--umd-red);
}
.question-row .qtext{ color: var(--ink); font-size:.98rem; }
.question-row:hover .qtext{ color: var(--umd-red); }
@media (max-width: 640px){ .question-row{ grid-template-columns: 1fr; gap: 6px; } }

/* ---------- Field Pulse ("What we're reading") ---------- */
/* Follow-up 51 (superseded within the same round -- see below): first
   tried a plain hairline border-bottom here (matching .lead-section's
   own rule), confirmed via AskUserQuestion. The user then said to drop
   the rule entirely and just tighten the gap before Calendar instead,
   closer to how SCOTUSblog's own cream box has a moderate (not huge) gap
   before the next module with no divider line. `.reading-section` no
   longer carries a border; it just trims its own bottom padding, paired
   with `.calendar-section`'s trimmed top padding below -- together they
   cut the reading-panel-to-"Events" gap roughly in half (was ~144px:
   panel's own 32px + two default 56px section paddings; now ~80px). */
.reading-section{ padding-bottom: 24px; }
.carousel-wrap{ position: relative; }
/* Head is just the eyebrow/h2 (follow-up 46 dropped the scroll arrows
   entirely -- see below) -- the description used to live inside this row
   too (follow-up 45 moved it below to fix a dead-gap issue when arrows
   were still here; that row now kept simply for layout consistency with
   other section heads even with nothing on its right). */
.carousel-head{ display:flex; align-items:flex-end; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom: 10px; }
/* follow-up 46: rebuilt from a bordered-card horizontal-scroll carousel to
   a static 3-column list, per a direct user request with a SCOTUSblog
   "More News & Commentary" screenshot as the explicit target -- plain
   text rows with a light boxed panel and vertical hairline dividers
   between columns, NOT individually-boxed cards, and no scroll arrows
   since every item is visible at once. Reuses the site's existing
   .grid/.card hairline-divided component (the same one news_cards_html()
   uses elsewhere) rather than a bespoke component, which is what makes
   this "in line with the design of the site at large" -- .card already
   handles the responsive 3-col -> 2-col -> 1-col collapse (see the
   .grid-3 media queries below), so no new breakpoint CSS was needed here.
   .reading-grid only adds the row spacing .news-grid-section normally
   supplies for its own multi-row .grid-3 use.

   Follow-up 48: direct user correction -- three changes. (1) The section
   itself went to .soft-bg (build_all.py) so the WHOLE band read as
   off-white, matching the site's alternating section-background rhythm
   ("the site is supposed to alternate colors as we go down the page") --
   .reading-panel dropped its own fill at the time (would have double-
   stacked two near-identical off-whites) and became just a border +
   padding wrapper that also holds the header/button row so header+grid
   read as one bounded unit. **Reverted in follow-up 50, see below --
   this paragraph is kept for the header-inside-the-panel structural
   change, which IS still current.** (2) The top border is UMD gold, not
   red -- a direct request ("we are missing any yellow, which we need"),
   overriding the site's usual red-default-for-accent-color convention
   since the user named the color explicitly -- still current. (3) row-gap
   increased 36px -> 56px so stacked items in the same column read as
   clearly separate readings instead of running together -- and this
   turned out to be a real pre-existing bug, not just a preference: the
   old `.reading-grid{ row-gap: 36px }` was silently zeroed out by
   `.grid{ gap: 0 }` below, since that rule shares .reading-grid's
   specificity (one class each) and comes later in the source, so its
   `gap` shorthand (which also sets row-gap) won -- rows had ZERO spacing
   all along. Fixed by writing this as `.grid.reading-grid` so it
   outranks `.grid` on specificity and can't be silently overridden by
   source order again -- still current.

   Follow-up 50: user clarified the intent behind "off-white" was
   SCOTUSblog's actual page structure -- a WHITE section with the
   content sitting in a bounded OFF-WHITE box, not the whole band
   painted off-white. `<section>` (build_all.py) dropped `.soft-bg`
   again; `.reading-panel` gets its fill back below so it once again
   reads as a distinct cream box against the white page, the way the
   reference genuinely looks (its outer page is white; only the "More
   News & Commentary" module itself is cream). The header/button-inside-
   the-panel structure from follow-up 48 is unaffected -- only WHERE the
   off-white color lives moved back to the panel. */
.grid.reading-grid{ row-gap: 56px; }
.reading-panel{ background: var(--sidebar-band); border-top: 4px solid var(--umd-gold); padding: 32px; }
.reading-panel .card{ padding-bottom: 0; }
.reading-panel .carousel-head{ margin-bottom: 6px; }
/* Paired with .reading-section's trimmed bottom padding above -- see the
   follow-up 51 comment there. Scoped to .calendar-section specifically
   (only used once, on the homepage) so this doesn't touch events.html's
   own calendar layout, which reuses other classes, not this one. */
.calendar-section{ padding-top: 24px; }

/* ---------- Cards / feed — flat, hairline-divided, no shadows ---------- */
.grid{ display:grid; gap: 0; }
.grid-2{ grid-template-columns: repeat(2, 1fr); }
.grid-3{ grid-template-columns: repeat(3, 1fr); }
.grid-4{ grid-template-columns: repeat(4, 1fr); }

.card{
  padding: 0 28px 28px 0;
  border-left: 1px solid var(--line);
  padding-left: 28px;
  display:flex; flex-direction:column; gap: 8px;
}
.grid > .card:first-child,
.grid-2 > .card:nth-child(2n+1),
.grid-3 > .card:nth-child(3n+1),
.grid-4 > .card:nth-child(4n+1){ border-left: none; padding-left: 0; }
.card .media{
  aspect-ratio: 4/3; background: var(--paper-soft); border: 1px solid var(--line); margin-bottom: 4px;
}
.soft-bg .card .media{ background: #ECEAE4; }
.card .kicker{ font-family: var(--font-nav); font-size:.78rem; font-weight:700; letter-spacing:.03em; text-transform:uppercase; color: var(--umd-red); border: 1px solid var(--umd-red); border-radius: var(--radius); padding: 3px 10px; display:inline-block; width: fit-content; }
/* follow-up 50: Field Pulse's content-type label dropped the boxed
   .kicker treatment (used by follow-up 49) for a plain italic caption,
   leaning on SCOTUSblog's own style more directly -- its category labels
   (COURT NEWS, EMPIRICAL SCOTUS, ...) are unboxed italic text, not
   bordered pills. --font-accent (Crimson Text) is used rather than
   --font-nav (Roboto Condensed) because Crimson Text is the only family
   in this stack with a REAL italic loaded (see the @import at the top of
   this file) -- Roboto Condensed/Source Sans 3 only have upright weights
   loaded, so `font-style: italic` on them would fall back to a browser-
   synthesized oblique rather than true italic glyphs. Still a real link
   out to the matching phronesisresearch.org content-type page (unlike
   news_cards_html()'s plain-span .kicker), just without the box. */
.card .reading-type{
  font-family: var(--font-accent); font-style: italic; font-weight: 400;
  font-size: .95rem; text-transform: uppercase; letter-spacing: .02em;
  color: var(--umd-red); text-decoration: none; display: inline-block; width: fit-content;
}
.card .reading-type:hover{ text-decoration: underline; }
.card .meta{ font-size:.8rem; color: var(--ink-soft); }
.card h3{ margin-bottom: 4px; }
.card h3 a{ color: var(--ink); }
.card h3 a:hover{ color: var(--umd-red); text-decoration:none; }
.card p{ margin-bottom: 6px; font-size: .95rem; }

.news-grid-section{ padding-top: 0; }
.news-grid-section .grid-3{ row-gap: 40px; }
.news-grid-section .grid-3 > .card:nth-child(n+4){ border-top: 1px solid var(--line); padding-top: 32px; margin-top: 8px; }
.news-grid-section .grid-3 > .card:nth-child(3n+1):nth-child(n+4){ border-left: none; padding-left: 0; }
/* The homepage's Recent News grid pairs just 2 wide cards next to the
   Upcoming Events rail, so each card's column is much wider than in the
   3/4-up grids above -- a 4:3 media placeholder there stretched into a
   huge, mostly-empty grey block. Cap it to a shallower ratio so the
   placeholder reads as a thumbnail, not dead space. */
.news-grid-section .grid-2 > .card .media{ aspect-ratio: 16/7; }

.topic-card{
  padding: 28px 28px 28px 0;
  border-left: 1px solid var(--line);
  padding-left: 28px;
}
.grid-4 > .topic-card:nth-child(4n+1){ border-left:none; padding-left:0; }
.topic-card .index{ font-family: var(--font-nav); font-weight: 700; font-size: 1rem; color: var(--umd-red); margin-bottom: 16px; display:block; }
.topic-card h3 a{ color: var(--ink); }
.topic-card h3 a:hover{ color: var(--umd-red); text-decoration: none; }

.pill-row{ display:flex; flex-wrap:wrap; gap:10px; }
.teaching-cta{ text-align: right; }

/* "Research Areas" matrix -- a real 2x2 grid (hard-coded for exactly 4
   TOPICS, see research_matrix_html()) with ruled dividers between
   cells, like a small table, rather than a card grid or pill row. */
.research-matrix{
  display:grid; grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line); border-left: 1px solid var(--line);
}
.matrix-cell{
  padding: 24px 28px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
  text-decoration: none !important; transition: background .15s ease;
  /* Follow-up 56: `1fr` tracks default to a `minmax(auto,1fr)` sizing
     algorithm -- a grid item's IMPLIED minimum width is its own
     min-content size (the width of its widest unbreakable word) unless
     overridden, so a column holding a longer single word ("Cybersecurity")
     was quietly growing wider than its "equal" 1fr share while the other
     column shrank to compensate -- an unequal-column-width bug that was
     always there but only got noticed once follow-up 55's top-alignment
     fix made the grid easy to scrutinize closely. `min-width:0` removes
     that implied minimum so `1fr 1fr` actually splits evenly regardless
     of content; paired with `overflow-wrap`/`hyphens` on h3 below so a
     word that no longer fits on one line wraps (with a proper hyphen,
     not a raw mid-word break) instead of overflowing the cell. */
  min-width: 0;
}
.matrix-cell:hover{ background: var(--nav-band); }
.matrix-cell .index{ font-family: var(--font-nav); font-weight: 700; font-size: .9rem; color: var(--umd-red); display:block; margin-bottom: 8px; }
.matrix-cell h3{ color: var(--ink); margin-bottom: 6px; overflow-wrap: break-word; hyphens: auto; }
.matrix-cell:hover h3{ color: var(--umd-red); }
/* Direct user request: each area's short code (area_meta.yml, same
   codes as the project-tile badges on research.html) now sits in
   parentheses after the name -- de-emphasized relative to the name
   itself (regular weight, ink-soft) so it reads as a secondary label,
   not a second heading. */
.matrix-cell .matrix-code{ color: var(--ink-soft); font-weight: 400; }
.matrix-cell:hover .matrix-code{ color: inherit; }
.matrix-cell p{ color: var(--ink-soft); font-size: .92rem; margin:0; }

/* Compact variant of the ruled 2x2 research-matrix grid, used at
   ~280px wide (now .lead-secondary, beneath What We Do/Join Us --
   originally .lead-rail, beneath Hub News, before follow-up 60 moved
   Research Areas across the hero to sit under What We Do instead). Each
   cell is too narrow at this width for a full blurb, so it's index +
   name only. A ruled divider + extra top space sets it off as its own
   block rather than reading as a continuation of whatever's above it.
   Follow-up 54: `margin-top` tightened 32px -> 16px, direct user request
   to shrink that gap (still enough space to read as its own block, just
   less of it).
   Follow-up 60: the old fixed-pixel column-height-parity tuning that
   used to live in this comment (a specific `.rail-scroll` max-height
   measured via Playwright to match .lead-secondary's height at a given
   moment) is gone now that Research Areas has swapped columns -- see
   the note on `.rail-scroll-wrap`/`.rail-scroll` below, and
   syncHubNewsHeight() in main.js, for the runtime-measured approach
   that replaced it. */
.rail-head--stacked{ margin-top: 10px; padding-top: 14px; border-top: 1px solid var(--line); }
.research-matrix--rail{ border-color: var(--line); }
/* Follow-up 55: `justify-content:center` here vertically centered each
   cell's index+name block WITHIN the row's full height, which is set by
   the tallest cell in that row (grid rows share height). Direct user
   request/screenshot: row 1 ("01 Cybersecurity", 1-line h3, vs "02
   Consumer Privacy", 2-line h3) visibly misaligned, since the 1-line
   cell's content got centered in a box tall enough for 2 lines and
   dropped lower than the 2-line cell's content, which already fills the
   row top-to-bottom. Row 2 ("03 Information Integrity" / "04
   Trustworthy ML") happened to look fine only because both of THAT
   row's h3s wrap to 2 lines, so centering vs. top-aligning made no
   visible difference there. Switched to `flex-start` so every cell's
   index+name starts at the same top offset regardless of how many lines
   its own heading wraps to -- fixes row 1 without depending on both
   cells in a row having matching line counts. */
.research-matrix--rail .matrix-cell{
  /* Follow-up 56: horizontal padding trimmed 16px -> 10px so "Cybersecurity"
     (the one topic name that's a single unbreakable word, ~102px wide at
     this font) fits on one line within an EQUAL-width column -- see the
     min-width:0 note on the shared .matrix-cell rule above for why the
     columns are now forced equal in the first place. Padding alone can't
     make an unbreakable word wrap, so this is a real width budget fix,
     not a cosmetic tightening -- `overflow-wrap`/`hyphens` on h3 stay in
     place as a fallback for any future topic name that's still too wide
     even at this padding. */
  padding: 30px 10px; min-height: 116px;
  display:flex; flex-direction:column; justify-content:flex-start;
}
.research-matrix--rail .matrix-cell .index{ font-size:.76rem; margin-bottom: 6px; }
.research-matrix--rail .matrix-cell h3{ font-size:.96rem; line-height:1.32; margin-bottom:0; }
.research-matrix--rail .matrix-cell p{ display:none; }

.feed-item{
  display:grid; grid-template-columns: 130px 1fr; gap: 22px;
  padding: 22px 0; border-top: 1px solid var(--line);
}
.feed-item:first-child{ border-top: 1px solid var(--ink); }
.feed-item .tag{
  font-family: var(--font-nav); font-size:.76rem; font-weight:700; text-transform:uppercase; letter-spacing:.02em;
  color: var(--umd-red); border: 1px solid var(--umd-red); padding: 4px 10px; border-radius: var(--radius);
  justify-self:start; height: fit-content;
}
.feed-item h3{ margin-bottom:6px; font-size: 1.15rem; }
.feed-item h3 a{ color: var(--ink); }
.feed-item h3 a:hover{ color: var(--umd-red); text-decoration:none; }
.pub-authors{ font-size:.85rem; color: var(--ink-soft); margin-top:2px; }

/* ---------- Research page (People / Projects / Publications explorer)
   ----------
   Follow-up 7 (direct user request, per a hand-drawn sketch replacing
   the earlier click-to-select single-card explorer): multi-select area
   filter pills (.area-filter-pill -- any combination at once, none
   selected shows everything) plus a shared search box (.area-controls)
   sit above three always-visible sections -- People tiles (.rp-grid of
   .rp-tile, each with an avatar since these are real people),
   Project tiles (.rp-grid of .rp-tile, no avatar), and a Publications
   list (.pub-list) styled after a reference screenshot of Princeton's
   CoCoSci lab site: a small colored area code (.area-tag, one of
   AREA_META's four colors in build_all.py, set inline via --area-color
   since these are per-area, not something a class alone can express)
   to the left of each citation-style row, grouped under a year heading
   (.pub-year). People and Projects are collapsible (.subsection-toggle,
   wired in main.js); Publications has no toggle -- it's a plain <h2>.
   main.js's researchExplorer closure shows/hides every [data-search-row]
   (tiles AND publication rows alike) against the active area-pill
   selection and the search text. */
/* Direct user request: the Role/Category/Research Area dropdown +
   search-box row (shared across People, Events, and Research via this
   one .area-controls class) reads as loose, unbounded controls
   floating in open space -- wrapped it in its own rounded box.
   var(--paper-soft) (a warm light grey already used for the Research
   carousels' panel background) was picked over black (reads too close
   to the policy-updates ticker and forces white text), red (jarring
   for a plain utility bar, also needs white text), or gold (odd fit)
   -- grey needs no text-color changes at all, and the dropdown/search
   controls keep their own white pill backgrounds nested inside it. */
/* Direct user request: "Filter By" now sits above the dropdown/search
   row, inside the same grey box, on every page that uses this shared
   component (People/Research/Events). */
/* Follow-up (direct user request, second pass): "Filter By" was still
   reading as visually equal in weight to the actual controls, and the
   row wasn't truly vertically centered -- the previous margin-top/
   bottom:auto attempt on .area-controls-row did nothing, because a
   flex container that isn't given any height beyond its own content
   (.area-controls here has none) has zero free space for auto margins
   to distribute; they always resolved to 0 and the row just sat
   directly under the label.
   Fixed properly this time: the label is pulled out of the flow
   entirely (position:absolute, small/muted, pinned to the box's
   top-left corner) so it no longer takes a "row" of its own height
   that the real content has to share space with. .area-controls-row is
   now the box's only flow child, so equal top/bottom padding centers
   it exactly -- true vertical centering, not something that has to
   share space with a label sitting above it. padding-top just needs to
   clear the label's own (small) height, which it does with room to
   spare.

   Follow-up (direct user request): a later round ("reduce the white
   space below the filter button") dropped padding-bottom to 12px/10px
   while leaving padding-top at 33px, and a round after that made the
   padding symmetric (26px/26px) plus nudged the label up to true-
   center the search bar's own line against the box's center line.
   Direct user follow-up request reverted that centering pass: "The
   filter by text and button do not need to move up for the latter to
   be aligned with the search bar. Those two elements can go back to
   their previous locations" -- so the label and padding below are
   back to the pre-centering 18px/33px-24px-12px values; the row is no
   longer perfectly vertically centered, by request. */
.area-controls{
  position: relative;
  margin-bottom: 40px;
  background: var(--paper-soft); border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg);
  padding: 20px 24px 12px;
}
/* Follow-up (direct user request): "the filter by text should be
   aligned with the text in the filter button" -- left:29px was a manual
   nudge from an earlier round (see the corner-radius optical-alignment
   note elsewhere in this file) that predates the button gaining its own
   16px text padding + 1.5px border, so the label's text start and the
   button's text start didn't actually line up. left:42px = the
   dropdown row's own 24px inset + the button's 1.5px border + the
   button's 16px text padding, i.e. the label started at the exact same
   x as "ROLE" itself.
   Follow-up (direct user request): "align the vertical line of the 'F'
   in 'Filter by' with the leftmost edge of the filter button" -- a
   different alignment target than the request above (the button's own
   outer edge, not its inner text start), so left:42px reverts to
   left:24px, matching .area-controls' own 24px left padding, which is
   exactly where .area-controls-row (and the button that starts it)
   begins. */
.area-controls-label{
  position: absolute; top:18px; left:24px; line-height:1;
  font-family: var(--font-nav); font-size:.64rem; font-weight:700;
  letter-spacing:.05em; text-transform: uppercase; color: var(--ink-soft);
}
/* Follow-up (direct user request): "the search bar is too low -- just
   match up the center line of the filter button and the center line of
   the search bar." Reverted the previous round's decoupled/absolute
   positioning back to plain flex siblings, centered together -- the
   search box and the Role dropdown are plain flex siblings again.
   .area-controls' padding-bottom also reverts (31px -> 12px) now that
   there's no independently-positioned search box needing extra room
   below it.
   Follow-up (direct user request): "align the bottom of the search bar
   with the bottom of the filter button." align-items:center -> flex-end
   -- the search box (taller, ~52px) already fills the row's full
   cross-axis height either way, so this only moves the (shorter, ~37px)
   dropdown: from centered within that space down to flush with the
   search box's own bottom edge. */
.area-controls-row{
  display:flex; align-items:flex-end; justify-content:space-between; gap:20px; flex-wrap:wrap;
}
.area-controls-row .search-box{ margin:0; max-width:380px; flex:1 1 300px; }
/* Follow-up (direct user request): "when the people page loads we want to
   see the full entry for the first 2 people in the core members list" --
   a chain of small, people.html-scoped reductions (filter-bar spacing,
   subsection-heading margin, panel padding, row padding/gap, and the
   text block's own margins/line-height) that together claw back enough
   vertical space for both of Core Members' first two rows to clear a
   normal laptop viewport on load. Font sizes themselves (set two rounds
   above, per direct request) are left alone -- only the space around and
   between lines is trimmed. */
#people-main .area-controls{ margin-bottom: 8px; padding: 20px 24px 10px; }
/* Below ~505px the filter-dropdowns-row and search-box wrap onto separate
   lines (.area-controls-row's flex-wrap kicks in once they no longer fit
   side by side). Once wrapped, the dropdown button sits directly at the
   row's own top edge instead of being pushed down by align-items:flex-end
   (that offset only exists when it shares a flex line with the taller
   search box) -- so the reduced 20px padding-top above, which relies on
   that offset for clearance under the absolutely-positioned "FILTER BY"
   label, is no longer enough and the button overlaps the label. Restoring
   the original 33px padding-top just for this range keeps the tightened
   spacing everywhere the row stays unwrapped. */
@media (max-width: 520px){
  .area-controls{ padding-top: 33px; }
  #people-main .area-controls{ padding-top: 33px; }
}
#people-list .subsection-toggle{ margin-bottom: 4px; }

.area-filter-bar{ display:flex; flex-wrap:wrap; gap:10px; }
.area-filter-pill.active{
  background: var(--area-color, var(--umd-red)); border-color: var(--area-color, var(--umd-red)); color:#fff;
}

/* Bug fix (direct user request -- "too much white space between the
   bottom of the People section and the top of Projects"): the generic
   `section{ padding: 56px 0; }` rule up top applies to EVERY <section>,
   including these -- only padding-top was ever overridden here, so
   each of People/Projects/Publications was still carrying that 56px of
   padding-BOTTOM, stacking on top of the next subsection's own 12px
   padding-top. Zeroed it out; spacing between subsections is meant to
   come from padding-top alone. */
.research-subsection{ padding-top: 12px; padding-bottom: 0; }
.research-subsection:first-of-type{ padding-top: 0; }
/* Follow-up (direct user request): Publications reads as a denser,
   text-heavy list rather than another tile carousel like People/
   Projects above it -- the shared 12px padding-top (tight on purpose
   for People/Projects, which sit right next to each other) read as
   cluttered here, with the Projects carousel's arrows landing right on
   top of the year-filter row. Publications alone gets more breathing
   room above it. */
#publications{ padding-top: 56px; }
.research-subsection > h2{
  font-size:1.25rem; padding-bottom:12px; margin-bottom:20px; border-bottom: 1px solid var(--ink);
}
/* Publications' header row: the plain h2 above is now paired with a
   year-filter pill row on the same line (data-year on each .pub-row,
   matched in main.js alongside the existing area filter/search --
   direct user request). .pub-section-head carries the border/spacing
   instead of the h2 itself so the pills sit flush with the divider. */
/* Follow-up (direct user request): dropped the border-bottom that used
   to underline the "Publications" heading/year-pill row -- padding-
   bottom (which existed only to keep text off that line) drops with
   it; margin-bottom alone now carries the gap before .pub-panel. */
.pub-section-head{
  display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap;
  margin-bottom:20px;
}
.pub-section-head h2{ font-size:1.25rem; margin:0; padding:0; border:0; }
/* Follow-up (direct user request): the year filter used to be its own
   pill row sitting inline next to "Publications" (.year-filter-bar,
   now unused) -- it's now a "Year" .filter-dropdown alongside "Research
   Area" up in .area-controls (see research_body in build_all.py), so
   there's exactly one "Filter By" control on the page instead of two
   different filter UI patterns. .year-filter-pill (further down, right
   after the base .filter-pill rule) no longer needs its own look now
   that it renders inside .filter-dropdown-menu -- that selector's
   higher specificity already gives it the same flat list-item styling
   as every other dropdown's pills, for free. */
/* Follow-up (direct user request, applies to every .subsection-toggle
   site-wide -- "Filters" on People/Research/Events, "People"/"Projects"
   on Research): the button used to be width:100% with
   justify-content:space-between, which stretched its border-bottom
   underline across the whole container and pushed the caret to the far
   right edge, away from the word it belongs to. inline-flex makes the
   button (and its underline) only as wide as "Filters" + the caret,
   with the caret sitting directly beside the text via `gap` instead of
   being shoved to the opposite end of the row. */
/* Direct user request, modeled on Bloomberg's section headers ("Today's
   Videos", "Bloomberg Originals" etc, which run plain with no rule
   under them): dropped the border-bottom that used to underline
   "People"/"Projects" here (the only two pages still using the plain,
   non---filters variant of this toggle -- see the --filters modifier
   below, unaffected and still underlined, for "Filters" elsewhere). */
.subsection-toggle{
  display:inline-flex; align-items:center; gap:10px;
  background:none; border:none; padding:0; margin-bottom:12px;
  cursor:pointer; text-align:left; font: inherit; color: inherit;
}
.subsection-toggle h2{ font-size:1.25rem; margin:0; }
.subsection-caret{
  width:9px; height:9px; flex:0 0 auto;
  border-right:2px solid var(--ink); border-bottom:2px solid var(--ink);
  /* Follow-up (direct user request): expanded points UP, collapsed
     points DOWN -- i.e. the caret shows the direction the section will
     move/reveal from, not (as it did before) the direction clicking it
     will point next. rotate(-135deg) on this border-right+border-bottom
     corner is the up-pointing chevron; rotate(45deg) is the down-
     pointing one -- swapped from their original assignment below. */
  transform: rotate(-135deg); transition: transform .15s ease;
}
.subsection-toggle[aria-expanded="false"] .subsection-caret{ transform: rotate(45deg); }

/* Follow-up (direct user request): "Filters" was sharing the exact same
   bold, full-size .subsection-toggle look as real content-section
   headers -- on Research it sat directly above "People"/"Projects"/
   "Publications" in identical styling, so all four read as
   interchangeable section headers; on the People page, which has no
   such content header at all, that same bold treatment made "Filters"
   look like an orphaned page heading with nothing to pair with. Only
   Events read fine, because there "Filters" sits above a much larger
   plain h2 ("Upcoming Events") with real visual contrast.
   .subsection-toggle--filters restyles just the "Filters" buttons
   (People/Research/Events) as a small, muted, uppercase utility control
   -- in the same visual family as .eyebrow/.rail-head elsewhere on the
   site -- so it clearly reads as secondary to whatever content headers
   follow it, without touching the base .subsection-toggle look that
   People/Projects/Publications still use. */
/* Follow-up: the base .subsection-toggle rule above no longer sets its
   own border-bottom (People/Projects' underline was dropped, direct
   user request) -- "Filters" still wants its own underline, so this
   modifier now sets the full border-bottom itself instead of just
   overriding its color. */
.subsection-toggle--filters{ padding-bottom:6px; margin-bottom:24px; border-bottom: 1px solid var(--line); }
.subsection-toggle--filters h2{
  font-family: var(--font-nav); font-size:.78rem; font-weight:700; letter-spacing:.06em;
  text-transform: uppercase; color: var(--ink-soft);
}
.subsection-toggle--filters .subsection-caret{ width:7px; height:7px; border-color: var(--ink-soft); }

.area-tag{
  display:inline-block; font-family: var(--font-nav); font-size:.66rem; font-weight:700; letter-spacing:.03em;
  color:#fff; background: var(--area-color, var(--umd-red)); padding:2px 7px; border-radius: var(--radius);
}
/* Wraps one .area-tag per area a tile/row belongs to -- usually just
   one, but a person listed under more than one research area (direct
   user request: one tile per person, not one per area) gets a tag for
   each, in flex-wrap so 3-4 tags on a narrow tile still reads cleanly. */
.rp-tags{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:10px; }

/* Follow-up (direct user request, modeled on Bloomberg.com's homepage
   "Bloomberg Originals"/"Watch" rows): was a free `overflow-x:auto`
   scroll strip (before that, a wrapping 3-up grid), which could leave a
   tile half-cut-off at the right edge -- no partial tiles is the whole
   point of the Bloomberg reference. `.rp-carousel` is the shaded/
   rounded panel (the --paper-soft + --radius-lg "contour instead of a
   wall of white" treatment, same reasoning as .guiding-section
   .container); `.rp-carousel-viewport` clips to a JS-computed width
   so only whole tiles are ever visible (see initTileCarousel() in
   main.js, which also stretches tiles to fill that width edge-to-edge
   whenever there are enough of them -- direct user request to remove
   the leftover gap a fixed tile width could leave at the row's end);
   `.rp-grid` is the flex track that gets paged inside it via
   scrollLeft, controlled by the prev/next arrows in
   `.rp-carousel-footer` below (no dots -- direct user request) or,
   follow-up direct user request, a touch swipe on mobile (see the
   touchstart/touchmove/touchend handlers in initTileCarousel(),
   main.js) -- both page by a whole screenful only, never a free
   drag/scroll, so there's never a mid-scroll partial frame at rest
   either. `.rp-carousel--people` keeps tiles small, like
   Bloomberg's "Originals" row (no bio -- see _person_tile_html, each
   tile links straight to that person's full listing on people.html);
   `.rp-carousel--projects` is the taller variant, like Bloomberg's
   "Watch" row/"Today's Videos" card -- a photo on top with the title/
   description below in a shaded caption strip, see .rp-tile-photo/
   .rp-tile-caption further down. */
/* Direct user request: the panel itself now carries a visible border
   too (was background-only) -- "a distinct outline both for the tiles
   and the section/rectangle holding them." */
.rp-carousel[hidden]{ display:none; }
.rp-carousel-panel{
  background: var(--paper-soft); border: 1.5px solid var(--ink-soft);
  border-radius: var(--radius-lg); padding: 20px;
}
.rp-carousel-viewport{ overflow: hidden; cursor: grab; }
.rp-carousel-viewport.dragging{ cursor: grabbing; user-select: none; }
.rp-grid{ display:flex; flex-wrap:nowrap; align-items:stretch; gap: 20px; }
/* Follow-up (direct user request): "the cards/tiles on the research
   page should have rounded corners like the container they sit in" --
   var(--radius) (2px, near-square) swapped for var(--radius-lg), the
   same corner radius .rp-carousel-panel (the grey container) uses.
   People/Project tiles' own overflow:hidden (see .rp-carousel--people/
   --projects below) already clips their photo's square corners to
   whatever radius is set here, so this "just works" for those too. */
.rp-grid > .rp-tile{
  flex: 0 0 250px;
  background: var(--paper); border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg); padding: 16px;
}
.rp-tile[hidden]{ display:none; }
/* People tiles are links (straight to people.html#person-<slug>) --
   reset the usual <a> look and add a clear hover affordance in place of
   the underline, matching how other card-shaped links on the site
   (.question-card, .signal-card) signal "clickable" without one. */
.rp-tile--link{
  display:flex; flex-direction:column; text-decoration:none; color: inherit;
  transition: border-color .15s ease;
}
.rp-tile--link:hover{ border-color: var(--umd-red); text-decoration: none; }
.rp-tile--link:hover h3{ color: var(--umd-red); }
.rp-tile h3{ font-size:1.02rem; margin-bottom:2px; }
.rp-desc{ font-size:.9rem; color: var(--ink-soft); line-height:1.5; margin:0; }

/* Small/photo-card variant (direct user request, modeled on Bloomberg's
   "Bloomberg Originals" cards): "[Picture] _________ Name [badges]" --
   a photo on top, name + research-area badges in a plain white section
   below, no bio/role text. The tile itself goes edge-to-edge (no
   padding of its own; overflow:hidden clips the photo's square corners
   to the tile's rounded ones) same structural pattern as the taller
   Project tiles below, just capped at 4 per page regardless of row
   width (data-max-per-page="4" in research_body, read by
   initTileCarousel() in main.js) to match Bloomberg's own 4-across
   Originals row. */
.rp-carousel--people .rp-grid > .rp-tile{
  flex-basis: 220px; padding:0; overflow:hidden; display:flex; flex-direction:column;
}
/* Bug fix: this rule's `display:flex` has higher specificity (3 classes)
   than the shared `.rp-tile[hidden]{ display:none; }` rule above (1
   class + 1 attribute), so a filtered-out tile here was staying visible
   despite JS correctly setting its `hidden` property -- the carousel's
   own width/paging math (initTileCarousel() in main.js) does read the
   real `hidden` property, so it was sizing its clipped viewport for the
   true filtered count while the DOM still rendered the old, larger set,
   which is what produced both wrong-content tiles and a tile clipped
   mid-card ("half card"). Same author-display-beats-[hidden] gotcha
   documented near .area-controls[hidden] further down this file. */
.rp-carousel--people .rp-grid > .rp-tile[hidden]{ display:none; }
/* No real headshots yet -- solid --ink placeholder (not area-tinted
   like Projects' photo, since one person can carry several areas).
   Follow-up (direct user request): replaced the centered initials with
   a generic person-silhouette icon, "like a locked character in a
   video game" -- every tile deliberately shows the exact same shape,
   the way a game's locked slots all render identically until a real
   photo/headshot replaces it. */
.rp-tile-photo.rp-tile-photo--person{ background: var(--ink); aspect-ratio: 6/5; }
/* Follow-up (direct user request): people.html used to scope this down
   to a shorter 16/8 ratio; removed so the photo goes back to the base
   16/10 ratio shared with research.html's tiles, which raises the
   photo's share of the card toward the requested "60-70% photo" (was
   running closer to 54% of the card at 16/8). */
.rp-tile-photo-silhouette{
  position:absolute; left:50%; top:50%; transform: translate(-50%, -50%);
  width:auto; height:58%; fill: rgba(255,255,255,.3);
}
/* Real headshot (people.yml's optional `photo` field, see
   _person_photo_html) -- fills the same fixed-aspect-ratio box the
   silhouette placeholder used, cropped to cover rather than letterboxed,
   so every card in a row lines up the same regardless of the source
   photo's own dimensions. */
.rp-tile-photo-img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position: center 20%;
}
/* Badges sit last (after the name), matching the mockup's stacking
   order; the shared .rp-tags rule's trailing margin-bottom is wasted
   space once they're the last thing in the card, so it's zeroed here.
   Follow-up (direct user request): "make the bottom tile/card
   information that is white and holds the name and research area
   badges black... like we see on the people page tiles/cards" -- swaps
   this from the original white "bottom section" to var(--ink), matching
   people.html's own person-card caption (.person-media-caption); the
   name needs an explicit white color since h3's global default
   (var(--ink)) is illegible on black.
   Follow-up (direct user request): "the tile/cards for people should
   have a red border that mirrors what we see on the tiles/cards of the
   people page" -- the same 4px red border-top people.html's own card
   caption uses at the photo/caption boundary (.person-media-caption),
   now added here too so both pages' person cards match. */
.rp-tile--person .rp-tile-caption{ background: var(--ink); padding: 12px 14px; border-top: 4px solid var(--umd-gold); }
.rp-tile--person .rp-tile-caption h3{ color:#fff; }
.rp-tile--person .rp-tags{ margin-bottom: 0; }

@media (max-width: 560px){
  .rp-carousel--people .rp-grid > .rp-tile{ flex-basis: 170px; }
  /* Direct user request ("redundancy in the outlines" on mobile): at
     this width main.js stretches a person tile to fill nearly the
     whole carousel panel edge-to-edge (see the initTileCarousel note
     above), leaving only .rp-carousel-panel's own 20px padding between
     its grey border and this tile's white one -- two 1.5px ink-soft
     rings sitting that close together read as one doubled outline
     rather than two distinct boxes. Dropping the tile's own border
     color (radius/sizing untouched, so nothing shifts) leaves exactly
     one visible ring -- the panel's -- with the white-on-grey color
     change alone still clearly marking the tile's edge underneath it. */
  .rp-carousel--people .rp-grid > .rp-tile{ border-color: transparent; }
}

/* Tall variant (direct user request: "projects can be taller like the
   Watch section", with "a photo and a description... the title... in
   the grey section of the tile", modeled on Bloomberg's "Today's
   Videos" card) -- the tile itself goes edge-to-edge (no padding/
   min-height of its own; overflow:hidden clips the photo's square
   corners to the tile's rounded ones), a fixed-aspect-ratio photo area
   on top, and a shaded caption strip below holding the title +
   description. No real project photos exist yet, so .rp-tile-photo
   reuses the homepage Research Spotlight's colored/textured
   placeholder treatment (.lead-media), recolored per the project's own
   research area via --area-color rather than the Spotlight's fixed
   ink/red split. */
.rp-carousel--projects .rp-grid > .rp-tile{
  flex-basis: 280px; padding:0; overflow:hidden; display:flex; flex-direction:column;
}
/* Bug fix: same author-display-beats-[hidden] gotcha as
   .rp-carousel--people .rp-grid > .rp-tile[hidden] above -- see that
   comment for the full explanation. */
.rp-carousel--projects .rp-grid > .rp-tile[hidden]{ display:none; }
.rp-tile-photo{
  aspect-ratio: 16/10; position:relative; overflow:hidden; flex:none;
  background: var(--area-color, var(--umd-red));
}
.rp-tile-photo::before{
  content:''; position:absolute; inset:0;
  background-image: repeating-linear-gradient(135deg, rgba(255,255,255,.14) 0 2px, transparent 2px 16px);
}
.rp-tile-photo-badge{ position:absolute; left:12px; top:12px; }
/* Follow-up (direct user request): Projects' caption strip now matches
   People's own caption background (.rp-tile--person .rp-tile-caption,
   further up) -- plain white instead of the paper-soft grey, so both
   tile variants read consistently. */
.rp-tile-caption{ background: var(--paper); padding: 16px; flex: 1 1 auto; }
.rp-carousel--projects .rp-tile h3{ font-size:1.05rem; margin-bottom:8px; }
.rp-carousel--projects .rp-desc{ font-size:.92rem; }

/* Footer bar below each carousel -- just the prev/next arrows (direct
   user request: no page dots -- the arrows greying out via :disabled at
   either end already shows position well enough), right-aligned. Same
   CSS-drawn chevron styling as the homepage Research Spotlight's
   .spotlight-arrow (kept as a separate class since that one is scoped
   to the spotlight widget in main.js and reusing it directly would risk
   cross-wiring the two). Always shown, even with only one page (both
   arrows simply sit disabled) -- direct user request ("if there is
   only one page then that is fine"). */
.rp-carousel-footer{ display:flex; align-items:center; justify-content:flex-end; margin-top:16px; }
.rp-carousel-arrows{ display:flex; gap:8px; }
.rp-carousel-arrow{
  width:32px; height:32px; padding:0; border-radius:50%;
  border: 1.5px solid var(--ink-soft); background: transparent;
  cursor:pointer; display:flex; align-items:center; justify-content:center;
  transition: border-color .15s ease, background .15s ease;
}
.rp-carousel-arrow:hover{ border-color: var(--umd-red); background: var(--paper); }
.rp-carousel-arrow::before{
  content:''; width:8px; height:8px;
  border-right:2px solid var(--ink-soft); border-top:2px solid var(--ink-soft);
  transition: border-color .15s ease;
}
.rp-carousel-arrow:hover::before{ border-color: var(--umd-red); }
.rp-carousel-prev::before{ transform: rotate(-135deg); margin-left:4px; }
.rp-carousel-next::before{ transform: rotate(45deg); margin-right:4px; }
.rp-carousel-arrow:disabled{ opacity:.35; cursor:default; }
.rp-carousel-arrow:disabled:hover{ border-color: var(--ink-soft); background:transparent; }
.rp-carousel-arrow:disabled:hover::before{ border-color: var(--ink-soft); }

/* Direct user request ("we also need a background for our
   publications"): Publications used to sit directly on the page's white
   background with no container at all, unlike People/Projects above it
   (.rp-carousel-panel) and Core Members/Affiliates on people.html
   (.people-group). .pub-panel gives it the same paper-soft/ink-soft-
   border/radius-lg treatment -- wraps just the list (+ its empty-state
   message), not the "Publications" heading/year-filter row above, same
   split as People/Projects (.subsection-toggle sits outside
   .rp-carousel-panel too).
   Follow-up (direct user request): "the background should be black so
   that the entries when they alternate do not get muted or drowned out
   by the background." .pub-row--alt's grey (var(--paper-soft)) was
   exactly the same color as this panel's own background, so an alt row
   visually vanished into the panel -- only its border gave it away.
   Switching the panel to var(--ink) (the same near-black used for
   person-media-caption/.rp-tile--person's caption elsewhere) fixes that
   directly: both the white default row and the off-white alt row now
   read clearly against it, no row-color changes needed. What DOES need
   to change is anything that used to sit straight on the light panel
   background assuming dark-on-light text -- .pub-year (below) and the
   empty-state message (.list-empty, scoped to just this panel further
   down) both get explicit light-on-dark colors for the same reason. */
.pub-panel{
  background: var(--ink); border: 1.5px solid var(--ink-soft);
  border-radius: var(--radius-lg); padding: 20px 24px;
}
.pub-year{
  font-family: var(--font-nav); font-size:1rem; font-weight:700;
  margin: 20px 0 10px; color: var(--paper);
}
.pub-year:first-child{ margin-top:0; }
/* Follow-up (direct user request): each publication entry is now its
   own bordered, rounded-corner card -- same 1.5px ink-soft border +
   radius-lg treatment as .person-row/.rp-tile -- instead of a flat
   list row divided by a thin top border. .pub-row + .pub-row (not a
   bare margin-top on the base rule) keeps the first row of each year
   group flush under its heading, same pattern as .person-row. */
.pub-row{
  display:flex; gap:14px; align-items:flex-start; padding:14px 18px;
  background: var(--paper); border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg);
}
.pub-row + .pub-row{ margin-top:10px; }
/* Direct user request: "each publication entry should be alternate,
   between grey and white... no matter how the content is filtered."
   The background above is white by default; .pub-row--alt (grey) is
   toggled by main.js's researchExplorer render() every time it
   recomputes which rows are hidden, walking only the currently-
   visible ones in order -- so the stripe always alternates cleanly
   against what's actually on screen, never raw DOM position. */
.pub-row--alt{ background: var(--paper-soft); }
.pub-row[hidden]{ display:none; }
.pub-row .rp-tags{ flex:0 0 auto; flex-direction:column; margin-bottom:0; margin-top:3px; }
.pub-cite{ margin:0; font-size:.92rem; line-height:1.55; color: var(--ink); }
.pub-cite-authors{ font-weight:700; }
.pub-cite-venue{ font-style:italic; color: var(--ink-soft); }

@media (max-width: 560px){
  .rp-carousel--projects .rp-grid > .rp-tile{ flex-basis: 220px; }
  .pub-row{ gap:10px; }
}


/* ---------- Section header row ---------- */
.section-head{ display:flex; align-items:flex-end; justify-content:space-between; margin-bottom: 28px; gap: 16px; flex-wrap:wrap; padding-bottom: 16px; border-bottom: 2px solid var(--ink); }
.section-head h2{ margin-bottom:0; }

/* ---------- Guiding Questions (homepage) ---------- */
/* Follow-up: rebuilt off a Bloomberg reference (the "How To" module) at
   direct user request -- a plain bold title positioned like Bloomberg's
   "How To" label, and each question as its own bordered, rounded-corner
   card (.guide-q) arranged in a grid like Bloomberg's "Odd Lots" row.
   Follow-up 2: user asked for the section's previous dark full-bleed
   band + gold/white color scheme back on top of this new card layout --
   .guiding-section is the dark band again (was .about-hub), with the
   title and card outlines/text picking up the old gold/white treatment
   below instead of .about-hub's old dark-mode overrides.
   Follow-up 3 (direct user request): briefly became an inset
   rounded-rectangle card (background/radius/padding moved onto
   .container, with the section itself left transparent) -- reverted
   back to the wall-to-wall full-bleed band, "the tiny rectangle feels
   odd" on the homepage. Background goes back on the section itself, at
   its full edge-to-edge width; .container inside it goes back to its
   plain default (just the centered max-width content column, no
   card styling of its own), same convention as .affiliation-strip
   elsewhere on the site. No padding override needed either -- the
   generic `section{ padding: 56px 0; }` default reads fine now that
   there's no adjacent inset card to close a gap against. */
.guiding-section{ background: var(--header-band); }
.guiding-head{ color: var(--umd-gold); margin-bottom: 24px; }
.guide-q-list{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }

@media (max-width: 900px){
  .guide-q-list{ grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px){
  .guide-q-list{ grid-template-columns: 1fr; }
}

/* ---------- Newsletter / subscribe band ---------- */
/* Follow-up (direct user request): was a full-bleed light-gray band,
   briefly became an inset rounded-rectangle card (same change as
   .guiding-section above) -- reverted back to wall-to-wall, "the tiny
   rectangle feels odd" on the homepage. Background/border go back on
   the section itself, full edge-to-edge width; .inner keeps just its
   max-width (560px, widened from the original 460px so the email form
   has room to breathe) to keep the text column from stretching the
   full page width. */
.newsletter-band{ text-align: center; background: var(--paper-soft); border-top: 1px solid var(--line); }
.newsletter-band .inner{ max-width: 560px; margin: 0 auto; }
.newsletter-band h2{ margin-bottom: .3em; }
.newsletter-form{ display:flex; gap:10px; max-width: 440px; margin: 8px auto 0; flex-wrap: wrap; justify-content: center; }
.newsletter-form input[type=email]{
  flex:1; min-width:220px; padding:12px 18px; border-radius: var(--radius);
  border:1.5px solid var(--line); background:#fff; color:var(--ink); font-family:var(--font-body); font-size:.95rem;
}
.newsletter-form input::placeholder{ color:#9a9d9f; }
.newsletter-message{ margin: 12px 0 0; font-size: .85rem; font-weight: 600; }
.newsletter-message--success{ color: #1B5E20; }
.newsletter-message--error{ color: #B71C1C; }
.newsletter-partner{ margin: 18px 0 0; font-size: .82rem; color: var(--ink-soft); }
.newsletter-partner a{ font-weight: 600; }

/* ---------- Shared search box + "no results" message ----------
   Originally built for events.html (SCOTUSblog-style calendar search),
   now reused by research.html's publications search too -- generic
   names since it's no longer events-specific; see applyFilters() in
   main.js, which looks for .filter-search-input by class (not by page-
   specific id) so this drops into any page with a [data-filter-group]
   bar. */
.search-box{ position: relative; margin: 22px 0 4px; max-width: 420px; }
.search-box-icon{ position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--ink-soft); pointer-events: none; }
.search-box-input{
  width:100%; padding: 17px 16px 17px 40px; border-radius: var(--radius-lg);
  border: 1.5px solid var(--line); background:#fff; color: var(--ink);
  font-family: var(--font-body); font-size:.92rem;
}
.search-box-input::placeholder{ color:#9a9d9f; }
.search-box-input:focus{ outline: none; border-color: var(--umd-red); }
.list-empty{ padding: 20px 0; color: var(--ink-soft); font-size:.92rem; border-top: 1px solid var(--line); }
/* Scoped override: .list-empty is shared with the People/Projects empty
   states, which still sit on a light panel -- only the Publications one
   needs light-on-dark text now that .pub-panel's background is ink. */
.pub-panel .list-empty{ color: var(--paper-soft); }

/* ---------- Events ---------- */
/* Follow-up: scroll-capped Upcoming/Past lists, added at direct user
   request modeled on SCOTUSblog's "Supreme Court Calendar" page (and,
   per later direct feedback, the same fixed-height/fade-out cap as the
   homepage's Hub News rail -- see events_body in build_all.py and
   .rail-scroll below) so a long Past Events history never turns into
   an endless scroll down the page. .events-scroll's max-height here is
   only the pre-JS/no-JS fallback -- main.js's capToRows() (see the
   events-page section near syncHubNewsHeight) measures the real
   rendered height of the first 5 rows and sets a precise inline
   max-height once the page loads (direct user request:
   preview exactly 5 rows, not "however many happen to fit in 480px"),
   the same division of labor as the homepage rail's syncHubNewsHeight.
   Written as `.rail-scroll.events-scroll` (not a bare `.events-scroll`)
   so this fallback max-height reliably wins over the plain
   `.rail-scroll` rule below regardless of the two rules' order in this
   file -- a bare single-class override here previously lost that tie
   silently (equal specificity, and `.rail-scroll` happens to be
   declared later), capping events at 420px instead of the intended
   480px without anyone noticing. */
.rail-scroll.events-scroll{ max-height: 480px; }
/* Follow-up (direct user request): the list now reads as one framed
   panel -- a gold top border and an ink bottom border around the whole
   box -- instead of a gold bar repeating on every other row (too
   jarring/busy down a long scrolling list). This is the "find another
   way to work the gold in" resolution: one accent line per list,
   echoing .reading-panel's gold border-top on the homepage, rather
   than the eyesore of an every-other-row treatment. Lives on the OUTER
   .rail-scroll-wrap, not the scrolling .rail-scroll element itself, so
   the borders stay put at the panel's edges instead of scrolling away
   with the content. events.html-specific (.events-list-wrap), so the
   homepage's plain .rail-scroll-wrap (Hub News) is untouched. */
.events-list-wrap{ border-top: 4px solid var(--umd-gold); border-bottom: 2px solid var(--ink); }

.event-row{
  display:grid; grid-template-columns: 90px 1fr auto; gap: 22px; align-items:center;
  padding: 20px 24px; border-top: 1px solid var(--line);
}
/* Follow-up (direct user request): every other row keeps a quiet shaded
   background for scan-ability down a long list -- just no border-top
   accent of its own any more, now that .events-list-wrap carries the
   page's one gold accent instead (see above). nth-child(even) counts
   DOM position, not visible position, so a filtered-out row can leave
   two same-shade rows adjacent -- a minor, accepted tradeoff of a
   pure-CSS striping rule. */
.event-row:nth-child(even){ background: var(--sidebar-band); }
.event-date{ text-align:center; border-right: 1px solid var(--line); padding-right: 18px; }
.event-date .d{ font-family: var(--font-nav); font-size:1.8rem; font-weight:700; line-height:1; color: var(--umd-red); }
.event-date .m{ font-size:.72rem; text-transform:uppercase; letter-spacing:.04em; color: var(--ink-soft); font-weight:700; font-family: var(--font-nav); }
.event-row h3{ font-size:1.05rem; margin-bottom:4px; }
.event-row .meta{ font-size:.82rem; color: var(--ink-soft); }
/* Past events (events.html/speaker-series.html) show YEAR where upcoming
   rows show DAY-of-month -- 4 digits needs a touch less size than the
   2-digit day number to sit comfortably in the same 90px date column. */
.event-row--past .event-date .d{ font-size:1.3rem; }

/* ---------- People ----------
   Follow-up (direct user request): the old 3-up initials/role/one-liner
   grid (dead code removed above) is now a vertical list -- a headshot
   slot with links/contact underneath it, the person's title doing
   double duty as a section-header-style divider (.person-title, same
   border-bottom idiom as .research-subsection > h2), and a bio to the
   right of the headshot alongside their research-area badges (reusing
   .area-tag/.rp-tags from the research page rather than a new badge
   style). Two filter facets (Role, Research Area) sit above the list --
   see .people-controls below and _person_row_html()/ROLE_TYPES in
   build/build_all.py + build/generate.py for the data side, and
   researchExplorer's `data-roles` facet in main.js for the filtering
   itself (same closure that already filtered research.html's tiles). */
/* .area-controls sets `display:flex` as an author rule, which beats the
   browser's default (user-agent-origin) `[hidden]{display:none}` even
   though it isn't more specific -- author styles always win over UA
   styles regardless of specificity. Same gotcha as .rp-tile[hidden] /
   .person-row[hidden] elsewhere in this file: anything toggled via the
   `hidden` attribute needs its OWN explicit override once something
   else sets its `display`. General on `.area-controls` itself (not a
   people.html-only class) since any collapsible controls bar hits the
   same issue -- this is what makes the People page's collapsed-by-
   default Filters panel actually collapse. */
.area-controls[hidden]{ display: none; }
/* Same author-display-beats-[hidden] gotcha as .area-controls[hidden]
   just above: .section-head sets `display:flex`, so main.js hiding a
   Core Members/Affiliates section-head via the `hidden` attribute (see
   the people-group-panel handling in main.js) needs this explicit
   override too, or the UA default alone won't actually hide it. */
.section-head[hidden]{ display: none; }

/* Follow-up (direct user request, replacing two full-width pill rows
   that read as "too big" once there were two filter dimensions): Role
   and Research Area each collapse into a small .filter-dropdown button
   that reveals its pills as a compact checklist menu on click -- see
   people_body in build/build_all.py for why the pills themselves are
   untouched (still plain .role-filter-pill/.area-filter-pill buttons
   with the same data-role/data-area attributes; only their container
   changed). Opening/closing the menu and the selected-count badge are
   handled by a small dedicated script in main.js, kept separate from
   researchExplorer's own filtering logic so that logic didn't need to
   change at all. Scales to a future third filter by adding one more
   .filter-dropdown rather than another full-width pill row. */
.filter-dropdowns-row{ display:flex; flex-wrap:wrap; gap:12px; }
.filter-dropdown{ position:relative; }
.filter-dropdown-toggle{
  display:flex; align-items:center; gap:8px; padding:10px 16px;
  border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg); background:#fff;
  font-family: var(--font-nav); font-weight:700; font-size:.8rem; letter-spacing:.03em; text-transform:uppercase;
  color: var(--ink-soft); cursor:pointer; transition: color .15s ease, border-color .15s ease;
}
.filter-dropdown-toggle:hover,
.filter-dropdown-toggle[aria-expanded="true"]{ border-color: var(--ink); color: var(--ink); }
.filter-dropdown-count{
  display:inline-flex; align-items:center; justify-content:center; min-width:17px; height:17px; padding:0 4px;
  background: var(--umd-red); color:#fff; border-radius:999px; font-size:.66rem; font-weight:700;
}
/* Same author-display-beats-[hidden] gotcha as .area-controls[hidden]
   above -- this badge sets its own `display` via the class rule right
   above, so it needs the same explicit [hidden] override or a 0-count
   badge (hidden=true, no selection yet) shows an empty/zero pill. */
.filter-dropdown-count[hidden]{ display:none; }
.filter-dropdown-caret{
  width:7px; height:7px; flex:0 0 auto; margin-top:-3px;
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform: rotate(45deg); transition: transform .15s ease;
}
.filter-dropdown-toggle[aria-expanded="true"] .filter-dropdown-caret{ transform: rotate(-135deg); margin-top:2px; }
.filter-dropdown-menu{
  position:absolute; top:calc(100% + 8px); left:0; z-index:20; min-width:230px;
  display:flex; flex-direction:column; gap:2px;
  background:#fff; border:1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: 0 12px 28px rgba(20,20,20,.14); padding:8px;
}
.filter-dropdown-menu[hidden]{ display:none; }
.filter-dropdown-menu .filter-pill{
  border:none; border-radius: var(--radius); padding:9px 10px; width:100%; text-align:left;
  font-family: var(--font-body); font-weight:600; font-size:.85rem; letter-spacing:normal; text-transform:none;
  color: var(--ink); background:none;
}
.filter-dropdown-menu .filter-pill:hover{ background: var(--sidebar-band); color: var(--ink); }
.filter-dropdown-menu .filter-pill.active{ background: var(--umd-red); color:#fff; }
/* Follow-up (direct user request): "Core member and affiliate sections
   should have grey backgrounds that have rounded corners... separate
   containers" -- same treatment as .area-controls/.rp-carousel-panel
   (var(--paper-soft), var(--radius-lg) corners) so this reads as the
   same family of boxed panel already used elsewhere on the site,
   rather than a one-off.
   Follow-up (direct user request): "we need greater distinction and
   contour between entries [and section]" -- var(--line) (a very light
   #DDE0E3) barely showed up against var(--paper-soft)'s own light grey;
   switched to var(--ink-soft) at 1.5px so the box actually reads as
   outlined rather than almost borderless. Core Members/Affiliates now
   sit in their own .research-subsection (see people_body in build_all.
   py, "look at the research section for inspiration") with a plain
   .subsection-toggle heading above this box instead of inside it, so
   the old .people-group + .people-group spacing rule is gone --
   spacing between the two now comes from .research-subsection's own
   padding-top (bumped further below, scoped to #people-list, since the
   shared site-wide 12px is tuned for research.html's tighter People/
   Projects pairing, not two full bordered boxes like these). */
.people-group{
  background: var(--paper-soft); border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg);
  padding: 16px;
}
#people-list .research-subsection + .research-subsection{ padding-top: 32px; }
.people-list{ margin-top: 8px; }
/* The Affiliates rail's fade-out (.rail-scroll-wrap::after, shared with
   the homepage Hub News rail and events.html's lists) ramps to
   var(--paper) -- right against the homepage/events pages' plain white
   background, but wrong here now that the rail lives inside this card's
   grey (var(--paper-soft)) background. Scoped to .people-group so the
   shared rule (and every other page using it) is untouched. */
.people-group .rail-scroll-wrap::after{
  background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0) 45%, var(--paper-soft) 100%);
}
/* Follow-up (direct user request, referencing Princeton CoCoSci's and
   the Brady Lab's people pages): row padding cut from 32px to 16px and
   the photo/text gap from 32px to 20px -- see _person_row_html() in
   build/build_all.py for the bigger structural change (name/role/tags/
   bio/links all now flow in one column beside the photo, instead of
   name/role/links being stranded in their own column under it) that's
   most of why the old rows read as having "too much white space". */
/* Follow-up (direct user request): "we need greater distinction and
   contour between entries" -- a thin 1px top border read as too subtle
   once each row grew into a full photo-card layout on the .people-group
   grey background (see below). Each row is now its own white/paper
   card (same border/radius as the group panel and the photo card
   inside it), with a gap between rows standing in for the old divider
   line, so entries read as clearly separate cards sitting on the grey
   background rather than one continuous list. */
/* Follow-up (direct user request): "the white bubble does not need to
   extend to [both] the image and badge card... [and] the bubble that
   has the bio, name, and role" -- .person-row used to be its own
   bordered white card wrapping both .person-media and .person-content,
   so the two inner cards sat inside a third outer card, redundant with
   the .people-group panel already behind all of them. .person-row is
   now just a plain layout wrapper (no background/border/radius of its
   own) -- .person-media and .person-content (see both further below)
   are the only two visible cards, standing on their own side by side
   directly against .people-group's grey background. */
.person-row{ padding: 0; }
.person-row + .person-row{ margin-top: 10px; }
.person-row[hidden]{ display:none; }
/* Follow-up (direct user request, modeled on research.html's own
   People-tile cards -- "we should look like the tiles/cards we have in
   the people section of the research page. The card should be as tall
   as the information... next to it"): .person-media is now a proper
   photo card (border/radius/overflow-hidden, same visual language as
   .rp-tile) instead of a bare 96px avatar square -- see .person-media
   below. align-items:stretch (was start) is what makes the card match
   the text column's height: with the row now a 2-col grid, each row's
   height is set by its tallest cell (the bio column), and stretch
   makes the shorter card cell grow to fill that same height instead of
   sizing to its own (shorter) content. The first column widens from
   96px to 240px to hold a real photo + a caption stack (email/website/
   area badges), matching the width research.html's own People tiles
   use (.rp-carousel--people .rp-grid > .rp-tile's flex-basis: 220px). */
.person-body{ display:grid; grid-template-columns: 240px 1fr; gap: 20px; align-items:stretch; }
/* Follow-up (direct user request): "the badge [photo card] should
   stand alone on mobile and desktop... the bio should have its own
   white bubble" -- .person-content used to be undecorated text with
   just a thin gold rule down its left edge, so the row read as one
   real card (.person-media) next to plain type. It's now a proper card
   in its own right -- same white background/border/radius as .person-
   media, so the two sit side by side as a visually matched pair.
   align-items:stretch above already makes this column's box as tall as
   the row itself (both cards match height), same as before.
   This bar's exact placement has gone through several rounds: next to
   just the name/role; a border spanning the whole card; a version
   flush with the card's own edge (which then read as part of the
   card's own outline rather than its own distinct accent); and back to
   a border-left on just the name/role wrapper, inset inside the card
   with a small gap of its own, clearly separate from the card's
   outline -- see .person-heading below. */
/* Follow-up (direct user request): "the bio text should be big enough
   to fill space (top to bottom)" -- a short bio next to a taller photo
   card (see .person-body's align-items:stretch) used to just sit at the
   top of this column with dead air below it. Making .person-content a
   flex column and .person-bio flex:1 + vertically centered lets the bio
   paragraph occupy and center within whatever vertical space is left
   under the name/role, for any row height, rather than only a bigger
   font (which can't itself guarantee filling a height that varies row
   to row with card content). */
.person-content{
  background: var(--paper); border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg);
  padding: 14px 18px 16px;
  display:flex; flex-direction:column;
}
/* Direct follow-up, after briefly trying the bar flush with the card's
   own outer edge: "we do not want the gold bar to be a part of the
   bubble outline... we just do not want the bar to touch the bio. We
   should have whitespace between the role and the bio." Flush-left
   made the bar read as an extension of the card's own border (blending
   into the rounded corner); back to a border-left on .person-heading,
   inset inside the card the same way .person-media's and .person-
   content's own borders are.
   Follow-up (direct user request): "the gold bar and the text next to
   it need to shift over to the right, such that the yellow bar is
   aligned with the leftmost starting point of the first letter of the
   bio." -- .person-heading no longer pulls itself left to compensate
   for its own border (that's what kept the name/role text flush with
   the bio); it now sits at .person-content's normal text column like
   everything else, so the border-left itself lines up with where the
   bio's own text starts, and the name/role text sits further right
   again, past the bar and its padding. */
/* Follow-up (direct user request): "let's shorten the golden bar a
   little bit more" -- a plain border-left necessarily runs the full
   height of .person-heading's box, so there was no more room to trim
   without also trimming the name/role text's own line height. Switched
   to an absolutely positioned strip (top/bottom inset from the box's
   edges by a few px each) so the bar can be shorter than the text block
   it sits beside, independent of the text itself. padding-left stays
   the same 16px total (4px bar width + 12px gap) so the text doesn't
   move. */
.person-heading{
  position: relative;
  padding-left: 16px;
}
.person-heading::before{
  content: "";
  position: absolute;
  top: 3px; bottom: 3px; left: 0;
  width: 4px;
  background: var(--umd-gold);
}
/* The photo itself reuses research.html's People-tile photo/silhouette
   markup and classes verbatim (.rp-tile-photo.rp-tile-photo--person +
   .rp-tile-photo-silhouette, defined earlier alongside research.html's
   carousels) -- same placeholder, same "locked character" look, on
   both pages. .person-media is the bordered/rounded card wrapping it;
   flex-direction:column lets .person-media-caption below grow to fill
   however tall align-items:stretch has made the card, per "card should
   be as tall as the information... next to it".
   Follow-up (direct user request): a fixed-height photo (aspect-ratio
   16/10) + a flex:1 caption meant the photo:caption SPLIT wasn't
   constant -- a longer bio stretched the row taller, which the photo
   (fixed height) couldn't absorb, so the extra height all went into
   the caption and the ratio drifted from card to card (e.g. Ido's
   longer bio vs. Sipho's shorter one). Direct request: "we want the
   ratio to be the same across all people's cards." Fixed by giving the
   photo and caption proportional flex-grow shares (64:36, both
   flex-basis 0) of .person-media's stretched height instead of a fixed
   photo height + a caption that absorbs the remainder -- now both
   scale together, so the split is the same 64%/36% on every row
   regardless of bio length. See the scoped override on
   .rp-tile-photo--person below (aspect-ratio doesn't apply once the
   photo's main-axis size comes from flex-grow instead). */
/* Follow-up (direct user request): "outline the tiles/cards on the
   people page in black like we see in research page people section" --
   1px var(--line) (a near-invisible light grey, chosen back when this
   was a plain photo placeholder) swapped for the same 1.5px
   var(--ink-soft) border research.html's own People tiles use
   (.rp-grid > .rp-tile), so the two pages' person cards read as the
   same component. */
.person-media{
  display:flex; flex-direction:column;
  background: var(--paper); border: 1.5px solid var(--ink-soft); border-radius: var(--radius-lg);
  overflow:hidden;
}
/* Scoped override (see the .person-media comment above): people.html's
   photo card gives up the base rule's fixed 16/10 aspect-ratio in favor
   of flex-grow, filling whatever height the caption below doesn't use.
   Follow-up (direct user request): "the padding below the content on
   the black section should be reduced... there is no additional
   information -- the desktop version should look more like mobile."
   The earlier fixed 64:36 photo:caption split (see below) kept the
   ratio constant across cards, but forced the caption to always claim
   36% of the row's height even when its own content (Email/Website,
   0-4 badges) needed far less -- people with no badges (e.g. Sipho)
   ended up with a lot of empty black space below their content. Caption
   is now flex:0 0 auto (sized to its own content, like the @media
   (max-width:700px) override below already does) and the photo takes
   flex:1 1 0 to absorb 100% of whatever height that leaves -- bio
   length now varies the photo's crop instead of the caption's empty
   space, which reads better since the photo is already cropped via
   object-fit regardless. */
.person-media .rp-tile-photo--person{ aspect-ratio: auto; flex: 1 1 0; min-height: 0; }
/* "___________ / Email / Website / [badges]" (direct user request,
   matching the user's own mockup order) -- same idea as research.
   html's .rp-tile--person .rp-tile-caption. flex:64 1 0 on the photo
   above + flex:36 1 0 here (both flex-basis 0) is what keeps the
   photo:caption split at a constant 64%/36% of .person-media's
   stretched height on every row, regardless of bio length (see the
   .person-media comment above) -- replacing the earlier flex:1 "fill
   whatever's left over" rule, which let a longer bio's extra height
   all land in the caption instead of being shared proportionally.
   Follow-up (direct user request): caption background switched from
   white/paper to black (var(--ink), matching the photo placeholder's
   own background so the two halves of the card read as one dark unit)
   -- text/link colors below were already legible-on-dark (white
   links... actually red links, see .person-contact/.person-links) so
   only the background needed to change. Gap between the stacked Email/
   Website lines tightened 5px -> 3px per direct request to reduce the
   white space between them.
   Follow-up (direct user request): "add a red bar to the bottom of the
   photos... at the top of the black bottom section" -- a 4px red
   border-top, same accent-bar thickness used elsewhere on the site
   (.reading-panel, .events-list-wrap), just in the brand red instead
   of gold since this divides a photo from a name-card-style caption
   rather than framing a whole panel. */
.person-media-caption{
  flex: 0 0 auto; display:flex; flex-direction:row; align-items:flex-start;
  justify-content:space-between; gap:8px;
  padding: 6px 10px 8px; background: var(--ink); border-top: 4px solid var(--umd-gold);
}
/* Follow-up (direct user request): "move the badges to be on the right
   side of the email and website" -- the caption is now a row, not a
   column: .person-caption-links (Email + Website, still stacked) on
   the left, the area-tag badges as their own wrapping group on the
   right, instead of Email/Website/badges all stacked as three separate
   lines. Badges wrap onto a second line as needed (2+2 for someone
   with 4, a single line for 1-2) rather than being paired one-per-
   label -- the number of badges per person varies (0 to 4+) and
   doesn't correspond to Email vs. Website, so a wrapping group is the
   layout that holds up for everyone. This also shortens the caption
   for the 13 people with only 0-2 badges, who no longer need a
   dedicated third row just for them. */
.person-caption-links{ display:flex; flex-direction:column; gap:3px; }
/* Follow-up (direct user request, black caption background): the
   red link color read too dark against var(--ink) to stay comfortably
   legible, so Email/Website links go white with a gold hover instead
   -- both already-used UMD accent colors, just swapped for a dark
   background instead of light. */
.person-contact{ display:flex; flex-direction:column; gap:4px; }
.person-contact a{ font-size:.78rem; font-weight:700; color:#fff; }
.person-contact a:hover{ color: var(--umd-gold); text-decoration:underline; }
/* Follow-up (direct user request): personal/lab website links moved
   into the photo card's caption (stacked, matching Email above them)
   instead of sitting inline in the bio column -- see .person-contact
   above, which they're now styled to match. */
.person-links{ display:flex; flex-direction:column; gap:4px; }
.person-links a{ font-size:.78rem; font-weight:700; color:#fff; }
.person-links a:hover{ color: var(--umd-gold); text-decoration:underline; }
/* Static fallback (direct user request: "if the person does not yet
   have an email or website url we can just make the button static")
   -- same label, same position, no href, muted grey instead of a live
   link's white so it visibly reads as "not yet available" rather than
   a broken link. Reuses the search placeholder's existing muted grey. */
.person-link-static{ font-size:.78rem; font-weight:700; color:#9a9d9f; cursor:default; }
/* Follow-up (direct user request): "the badges should be a 2x2 grid --
   since there are only 4 research areas at this time that should not
   be a problem." A CSS grid (rather than the previous flex-wrap +
   max-width, which wrapped by available width, not a fixed column
   count) guarantees exactly 2 columns regardless of a given badge's
   own text width, so badges line up in even columns/rows for anyone
   with 2-4 of them, rather than wrapping at a slightly different point
   depending on which codes happen to be present. justify-content:end
   right-aligns the grid within the caption's row (matching the
   previous flex-end), and grid-auto-flow's default (row, left to
   right, wrapping every 2) is what actually produces the 2x2 shape for
   someone with all 4 areas. */
.person-media-caption .rp-tags{
  display:grid; grid-template-columns: repeat(2, auto); gap:6px;
  margin:0; justify-content:end;
}
/* Follow-up (direct user request): "increase the font size that we
   don't have this much white space... for the entries." Name/role/bio
   all sized up (h3 1.05rem->1.3rem, role gets an explicit size instead
   of inheriting the body default, bio 1.05rem->1.15rem below) -- a
   short bio next to a taller photo card still bottom-aligns (see
   .person-bio below), but bigger type wraps to more lines and closes
   most of the gap that used to sit above it, rather than just widening
   the empty space below the name/role even further. */
/* Follow-up (direct user request, second pass -- the first font bump
   wasn't enough): "this text... should fill the space... better...
   there is too much whitespace in the entries' rectangles." Sized up
   again (h3 1.3rem->1.4rem, role 1.1rem->1.2rem) with more breathing
   room (role's margin-bottom 12px->18px) so name+role+bio reads as one
   generously-set block rather than a tight cluster up top with a stark
   gap before the (still bottom-aligned, per the gold-line request)
   bio. */
.person-content h3{ font-size:1.4rem; margin-bottom:3px; font-weight:700; }
/* Follow-up (direct user request): "shorten the gold bar so that it
   does not border the top of the bio's text" -- .person-role's own
   margin-bottom used to be what created the gap before the bio, but
   that margin sits INSIDE .person-heading's box (the gold bar's own
   border-left runs the full height of that box), so the bar was
   extending down through this gap to the bio's very edge. Moved to
   .person-bio's margin-top instead (below) -- same visible gap, but
   now outside .person-heading entirely, so the bar stops right at the
   bottom of the role text itself. */
.person-role{ font-style:italic; font-weight:400; font-size:1.2rem; color: var(--ink-soft); }
/* Follow-up (direct user request, referencing Princeton CoCoSci's people
   page): the bio column used to stretch to fill the full 1fr grid track,
   which at our container's max-width made lines of bio text quite long
   and hard to read. Capping it keeps line length reasonable while the
   photo-card column stays fixed-width beside it.
   Follow-up (direct user request): "It feels like there is a lot of
   whitespace here [in the bio bubble]... making the bio text larger or
   extending it out further... a combination of both might work." The
   640px cap above left a consistently empty ~160px strip on the right
   of every card (.person-content's own inner content width is ~800px
   at every viewport this page supports -- see the page's own max-width
   container, confirmed not to grow past that even on very wide
   screens, so there's no risk of overly long lines from widening this).
   Raised to 760px (not the full ~800px, so text doesn't run flush to
   the card's own edge) and paired with a larger font size below to
   close most of that gap while keeping line length reasonable. */
/* Follow-up (direct user request): "the last line of the bio should
   align with [the] end point of the gold vertical line" -- switched
   from align-items:center to flex-end so the bio's bottom edge always
   sits flush with the bottom of .person-content's own padding, for any
   row height, instead of centering with unequal gaps above/below. (The
   gold vertical line this originally aligned to is gone now that
   .person-content is its own bordered card -- see the comment on that
   rule above -- but bottom-aligning a short bio still reads better than
   leaving it stranded at the top with empty space below.) */
.person-bio{ max-width: 760px; flex: 1 1 auto; display:flex; align-items:flex-end; margin-top: 8px; }
.person-bio p{ font-size:1.35rem; line-height:1.5; color: var(--ink-soft); margin:0; }

/* .person-scroll wraps .person-heading + .person-bio (see build_all.py's
   _person_row_html) so the pair can become one shared scrolling region
   on mobile (below) without a plain block wrapper changing desktop's
   layout -- flex column here just delegates to the same column layout
   .person-content already used before this wrapper existed. */
.person-scroll{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
/* .person-page-next only means anything inside the paged mobile layout
   below (max-width:700px) -- hidden by default so it never shows at
   wider widths where .person-body is the normal 2-column grid and
   there's no swipe to page through. Placed BEFORE the @media block on
   purpose: same selector specificity as the media query's `display:
   flex` override, so it must come first in source order for that
   override to win once the media query's condition is true, rather
   than this unconditional rule winning at every width by virtue of
   appearing later. */
.person-page-next{ display:none; }
@media (max-width: 700px){
  /* Follow-up (direct user request): "the name, role, and bio card
     should still be one swipe away. So the user sees the image card,
     then either clicks bio or swipes left and sees the name, role,
     bio card which they can scroll through to read the fully bio."
     Back to a 2-page swipe/snap track (.person-media, .person-content)
     after a round of trying it as two always-stacked cards -- simpler
     than the earlier 3-page version (photo -> clamped preview -> full
     bio) since there's only ONE content page now, holding the complete
     name/role/bio together rather than a separate clamped-preview page
     first. .person-page-next--bio (top-right of the photo) advances to
     it; .person-page-next--back (bottom-right of the content card)
     returns to the photo -- both are the click/tap/keyboard/screen-
     reader equivalent of the swipe gesture that already works via the
     scroll-snap track alone. */
  .person-body{
    grid-template-columns: none; gap: 0;
    display:flex; flex-wrap:nowrap; align-items:stretch;
    height: 262px;
    overflow-x:auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .person-body::-webkit-scrollbar{ display:none; }
  .person-media{
    max-width:none; flex:0 0 100%; width:100%;
    scroll-snap-align:start; scroll-snap-stop:always;
    position:relative;
  }
  /* Follow-up (direct user request, "face cards can remain the size we
     had previously"): the same fixed 16/10 aspect-ratio the photo card
     uses everywhere else (research.html's People tiles, desktop
     people.html) -- .person-body's own height above (262px) is
     calibrated to this photo+caption's natural combined height, so
     stretching .person-media to fill the row doesn't add any visible
     slack under it. */
  .person-media .rp-tile-photo--person{ aspect-ratio: 16/10; flex: 0 0 auto; position:relative; }
  /* Direct user request: "the black section... should cover the entire
     bottom of the card, there should be no white sliver." The photo
     (fixed aspect-ratio above) and caption (sized to its own content)
     don't always add up to exactly .person-body's own fixed 262px
     height -- any small leftover was showing as a strip of .person-
     media's own white background below the caption. Letting the
     caption grow to absorb that leftover (instead of the fixed-size
     photo) keeps the photo at its calibrated size while guaranteeing
     the black caption always reaches the card's bottom edge. */
  .person-media-caption{ flex: 1 1 auto; }
  /* Direct user request: "the links... should be placed in a 2x2 way"
     on mobile only -- desktop keeps them in the single stacked column
     above (plenty of vertical room there), but mobile's fixed 262px-
     tall card (see .person-body above) has much less to spare, so
     Email/LinkedIn/Website go 2-across here instead of one per line.
     Follow-up (direct user request): the first attempt at this
     flattened .person-contact and .person-links into ONE 2-column grid
     via display:contents, which auto-flowed every link across both
     columns in DOM order -- fine when there's a LinkedIn (Email+
     LinkedIn fill row 1, Website starts row 2, matching the request),
     but wrong for the common no-LinkedIn case, where Website slid up
     into row 1's empty second cell instead of staying beneath Email:
     "we do not want to move website to the second column, it should be
     beneath the email option. If the person had a 3rd or 4th listing
     like a LinkedIn then it would start the second column." So each
     wrapper is now its OWN 2-column grid instead of being flattened
     away: .person-contact (Email, +LinkedIn if present) is row 1,
     .person-links (Website, +any extra site) is always row 2 --
     Website only ever shares a row with another website, never with
     Email/LinkedIn, regardless of how many items either row has. */
  .person-caption-links{ display:flex; flex-direction:column; gap:4px; }
  .person-contact, .person-links{ display:grid; grid-template-columns: repeat(2, auto); gap: 4px 14px; }
  .person-content{
    flex:0 0 100%; width:100%;
    scroll-snap-align:start; scroll-snap-stop:always;
    position:relative;
    display:flex; flex-direction:column;
    /* Bottom clearance for .person-page-next--back (bottom:10px, ~34px
       tall) so it has its own reserved strip below .person-scroll
       instead of floating on top of it -- an earlier version pinned a
       page button over text that scrolled underneath it, and it got
       covered at some scroll positions ("the button should also be
       cleanly separated from the bio text"). */
    padding-bottom: 50px;
  }
  /* The whole heading+bio pair scrolls together as one unit if it runs
     past the shared 262px card height, rather than the card itself
     growing taller (reopening the "huge card" problem the fixed height
     above already solves) -- direct user request: "The entire card
     with the bio should be scrollable. So as you scroll through it the
     person's name and role should scroll as well," i.e. not just the
     bio paragraph on its own. min-height:0 lets this flex child actually
     shrink to fit the space .person-content's padding-bottom leaves it,
     instead of pushing the card taller. */
  .person-scroll{ overflow-y: auto; min-height: 0; }
  .person-content .person-bio{ align-items:flex-start; }
  .person-page-next{
    display:flex; align-items:center; justify-content:center;
    position:absolute; bottom:10px; right:10px;
    width:34px; height:34px; border-radius:50%;
    border:none; font-size:1.15rem; line-height:1; cursor:pointer;
  }
  .person-media .person-page-next{ background: rgba(0,0,0,.55); color:#fff; }
  .person-content .person-page-next{ background: var(--umd-gold); color: var(--ink); }
  /* Direct user request: "on the person's image card we should have the
     bio button in the top right corner, not the carrot button." A
     labeled pill in the photo's top-right corner instead of the
     bottom-right circle, auto-sized to fit "Bio ›" rather than the
     fixed 34px circle, with the same translucent-dark/white styling the
     circle already uses on top of a photo. */
  .person-page-next--bio{
    top: 10px; bottom: auto;
    width: auto; height: auto;
    border-radius: 999px;
    padding: 6px 14px 6px 16px;
    gap: 4px;
    font-size: .85rem; font-weight: 700;
  }
  /* Direct user request: "the yellow button with a carrot should point
     in the opposite direction when you get to the end" -- this button
     goes back to the photo (the only one of the two that goes backward
     instead of advancing), so its glyph is a left caret instead of the
     photo page's right caret (see build_all.py's &lsaquo; on this one
     button). It sits in .person-content's own reserved bottom strip
     (see the padding-bottom comment above), so it stays put regardless
     of .person-scroll's scroll position instead of floating over it. */
}

/* Follow-up (direct user request, modeled on the homepage's Hub News
   rail): "The list of them [Affiliates] should be scrollable like the
   Hub News section on the homepage" -- reusing .rail-scroll/.rail-
   scroll-wrap's fade-bottom + capped-height + thin-scrollbar component
   (see people_body in build/build_all.py for the markup, and the fuller
   explanation of .rail-scroll-wrap/.rail-scroll further down this
   file). Written as `.rail-scroll.affiliates-scroll` (not a bare
   `.affiliates-scroll`) for the same cascade-order reason as events.
   html's `.rail-scroll.events-scroll` above -- a bare single-class
   override here would lose the specificity tie against the plain
   `.rail-scroll` rule silently, depending on file order. Deliberately
   taller than Events' 480px: each Affiliate row is now a much taller
   photo-card row than a one-line event, so a shorter cap would leave
   almost nothing showing before the fade/scroll kicks in. */
.rail-scroll.affiliates-scroll{ max-height: 900px; }

/* ---------- Page hero (interior pages) ---------- */
.page-hero{ background: var(--paper); border-bottom:1px solid var(--line); padding: 40px 0 36px; }
/* Follow-up (direct user request): Research, People, and Events no
   longer use .page-hero above -- they open with just this breadcrumb
   line instead of a breadcrumb/eyebrow/big-title/lede block. Courses,
   Speaker Series, and Annual Event still use .page-hero as before. */
.breadcrumb-bar{ padding: 22px 0 4px; }
.breadcrumb-bar .breadcrumb{ margin-bottom: 0; }
/* Follow-up (direct user request): "reduce some of the white space
   above the filter and search section and below the breadcrumb" -- the
   generic section{padding:56px 0} rule left a big gap between the
   breadcrumb and .area-controls below it on people.html specifically;
   scoped to #people-main (see people_body in build/build_all.py) so
   research.html/events.html's own top spacing is untouched. Only the
   top half is shrunk -- bottom stays 56px for the usual gap before the
   footer. */
#people-main{ padding-top: 8px; padding-bottom: 56px; }
.breadcrumb{ font-size:.82rem; color: var(--ink-soft); margin-bottom: 16px; }
.breadcrumb a{ color: var(--ink-soft); }
.page-hero p.lede{ max-width: 68ch; font-size: 1.05rem; }
.page-hero .hero-actions{ display:flex; gap:14px; margin-top: 24px; flex-wrap:wrap; }

/* ---------- Compact rail (CNBC/Bloomberg-style "Latest"/"Upcoming" side
   list) -- keeps the hero and Recent News from being one massive
   full-width block by giving the page a persistent side column, the way
   a financial-news front page pairs a lead story with a headline rail. ---------- */
.rail-panel{ border-left: 1px solid var(--line); padding-left: 28px; }
.rail-head{
  font-family: var(--font-nav); font-size:.72rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color: var(--ink-soft); padding-bottom: 8px; margin-bottom: 2px; border-bottom: 2px solid var(--ink);
}
/* Follow-up 52: Hub News became the site's only news listing once the
   standalone news.html page was removed, so it now shows every
   NEWS_ITEMS entry instead of a 3-item excerpt + "More News" link --
   scoped to just the rail-item list (NOT the rail-head above it or the
   Research Areas matrix below it in .lead-rail), so the header stays
   pinned and only the list itself scrolls, matching CNBC's own "Latest
   News" sidebar box. A slim custom scrollbar (Firefox via
   scrollbar-width, WebKit/Chromium via ::-webkit-scrollbar) keeps the
   ~280px-wide rail from getting a bulky default scrollbar. */
/* Follow-up 53: `.rail-scroll-wrap` is a plain wrapper around
   `.rail-scroll` that exists solely to host the fade overlay below. Its
   `::after` sits OUTSIDE the scrolling box, so it stays put over
   whichever headline currently lands at the bottom edge -- an overlay
   painted inside `.rail-scroll` (the scrolling element itself) would
   scroll away with the content instead. The gradient runs to
   `var(--paper)`, the page's own background, so it reads as the list
   softening into whatever's below rather than a hard cutoff -- the same
   fade-to-white treatment CNBC uses at the foot of their "Latest News"
   sidebar box.
   Follow-up 54: shortened AND softened, per a direct user complaint that
   the last visible item was getting swallowed almost entirely -- the
   first ~45% of the (now shorter) fade zone stays fully transparent, so
   most of the last row's headline stays legible, and only the final
   ~22px actually ramps to solid, matching CNBC's own subtler cue (their
   last row is still readable, just softened right at the edge) rather
   than the heavier full-height fade this started as.
   Follow-up 60: `max-height` here is just a fallback for before JS runs
   (and for no-JS). A pure-CSS attempt at self-matching this box's height
   to .lead-secondary's (align-items:stretch + flex:1/height:100%) turned
   out to be circular and doesn't work: grid track sizing needs each
   item's OWN natural content height to size the row in the first place,
   so a flex:1 child with no explicit cap just reports its full,
   unclipped list height as "natural" -- the row balloons to fit every
   item instead of clipping to match its sibling. The actual match now
   happens in main.js (syncHubNewsHeight), which measures
   .lead-secondary's real rendered height and sets this max-height to
   fit -- see the comment there for the full explanation. */
.rail-scroll-wrap{ position: relative; }
.rail-scroll-wrap::after{
  content: ""; position: absolute; left: 0; right: 6px; bottom: 0; height: 40px;
  background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0) 45%, var(--paper) 100%);
  pointer-events: none;
}
.rail-scroll{ max-height: 420px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.rail-scroll::-webkit-scrollbar{ width: 6px; }
.rail-scroll::-webkit-scrollbar-track{ background: transparent; }
.rail-scroll::-webkit-scrollbar-thumb{ background: var(--line); border-radius: var(--radius); }
.rail-item{ display:block; padding: 14px 0; border-top: 1px solid var(--line); text-decoration: none !important; }
.rail-item:first-of-type{ border-top:none; padding-top:0; }
.rail-item .tag{
  font-family: var(--font-nav); font-size:.68rem; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  color: var(--umd-red); display:inline-block; margin-bottom: 4px;
}
.rail-item .date{
  font-family: var(--font-nav); font-size:.68rem; letter-spacing:.02em; color: var(--ink-soft);
  display:inline-block; margin-bottom: 4px; margin-left: 6px;
}
.rail-item .date::before{ content:"\00b7 "; }
.rail-item h4{ font-size:.92rem; margin:0; line-height:1.35; color: var(--ink); font-weight:700; }
.rail-item:hover h4{ color: var(--umd-red); }
.rail-more{
  display:block; width:fit-content; margin-left:auto; margin-top: 14px;
  color: var(--umd-red); border-color: var(--umd-red);
}
.rail-more:hover{ color: var(--umd-red-dark); border-color: var(--umd-red-dark); }

/* Guiding Questions -- rounded-rectangle card per question (Bloomberg
   "Odd Lots" reference), laid out by .guide-q-list's grid above. */
.guide-q{
  display:flex; flex-direction: column; gap: 8px;
  padding: 20px 18px;
  border: 1px solid rgba(255,255,255,.3); border-radius: 10px;
  background: transparent;
}
.guide-q .num{ font-family: var(--font-nav); font-weight:700; font-size:.74rem; color: var(--umd-gold); }
.guide-q .qtext{ font-size:.86rem; line-height:1.4; color: #fff; }
/* Follow-up 54: no hover highlight -- direct user request, since these
   items don't link anywhere (see follow-up 34), a hover highlight was
   implying interactivity that isn't actually there. */

/* ---------- Two column with sidebar ---------- */
.with-sidebar{ display:grid; grid-template-columns: 1fr 280px; gap: 48px; }
.rail-wrap{ align-items: start; }
/* Follow-up: events.html's own column ratio -- direct user request to
   widen the calendar and narrow the events list, which was leaving a
   lot of empty space between each row's meta text and its Details
   button at the old 1fr/280px split. Capping the events column at
   620px (rather than letting it be the flexible 1fr side) and giving
   the calendar the flexible remainder does both at once. Declared after
   .with-sidebar so it wins the tie on shared selector specificity;
   still overridden by the 900px .with-sidebar mobile rule below since
   that one comes later in the file. */
.events-layout{ grid-template-columns: minmax(0, 620px) 1fr; }
/* Follow-up (direct user request): the calendar travels with the
   viewport on desktop instead of scrolling away. .events-sidebar is
   the plain grid column (left at .with-sidebar's default
   align-items:stretch, so it's stretched to the full height of the
   taller events-list column beside it); .events-sidebar-sticky, the
   actual calendar content, is nested one level inside it and is what
   carries position:sticky. That nesting is required, not cosmetic --
   putting position:sticky directly on the grid item itself (tested and
   confirmed) gives it zero effective range to move in, since a
   stretched grid item's own box is by definition exactly as tall as
   its containing block; there's no "slack" left for it to stick
   *within*, so nothing visibly happens. Nesting the sticky element one
   level down gives it a real, shorter box inside a real, taller (grid-
   stretched) containing block, which is what lets it float and stay
   pinned for the whole scroll instead of just moving with the page.
   The site header is itself position:sticky (top:0, see
   header.site-header above), so the 104px top offset here clears it
   rather than sticking the calendar directly under the viewport edge,
   behind the header. Desktop-only (matches the 900px breakpoint the
   .with-sidebar mobile rule below switches at) -- below that width the
   layout stacks into a single column, where a sticky sidebar would
   just sit awkwardly between two stacked lists. */
@media (min-width: 901px){
  .events-sidebar-sticky{ position: sticky; top: 104px; }
}

/* ---------- Homepage calendar (replaces the old Recent News feed) --
   real month-grid, Sun-Sat, correct weekday math from the stdlib
   calendar module (see calendar_widget_html() in generate.py). Legend
   + dark nav band + borderless day grid with color-coded event dots,
   paired with the existing Upcoming Events rail. ---------- */
.cal-legend{ display:flex; flex-wrap:wrap; gap: 8px 22px; margin-bottom: 24px; }
.cal-legend-item{
  display:inline-flex; align-items:center; gap:7px; font-family: var(--font-nav); font-size:.78rem;
  color: var(--ink-soft);
}
.cal-legend-item .sw{ width:11px; height:11px; flex:none; }
.cal-widget{ min-width:0; }
.cal-head{
  display:flex; align-items:center; justify-content:space-between; background: var(--header-band);
  padding: 12px 18px; border-top: 3px solid var(--umd-gold);
}
.cal-title{
  font-family: var(--font-nav); font-weight:700; font-size:.86rem; letter-spacing:.06em; text-transform:uppercase;
  color:#fff;
}
.cal-nav{
  background:none; border:1px solid rgba(255,255,255,.3); color:#fff; width:30px; height:30px; line-height:1;
  cursor:pointer; font-size:1.05rem; flex:none; transition: border-color .15s ease, color .15s ease;
}
.cal-nav:hover{ border-color: var(--umd-gold); color: var(--umd-gold); }
.cal-month{ display:none; border: 1px solid var(--line); border-top:none; }
.cal-month.active{ display:block; }
.cal-weekdays{ display:grid; grid-template-columns: repeat(7,1fr); border-bottom:1px solid var(--line); }
.cal-weekdays span{
  text-align:center; padding: 8px 2px; font-family: var(--font-nav); font-size:.66rem; font-weight:700;
  letter-spacing:.04em; color: var(--ink-soft); text-transform:uppercase;
}
.cal-days{ display:grid; grid-template-columns: repeat(7,1fr); }
.cal-day{
  min-height: 68px; padding: 6px 4px; text-align:center; border-bottom:1px solid var(--line);
  display:flex; flex-direction:column; align-items:center; gap:4px;
}
.cal-day.empty{ background: var(--paper-soft); }
.cal-day .daynum{ font-size:.8rem; color: var(--ink-soft); }
.cal-day.has-event .daynum{ color: var(--ink); font-weight:700; }
.cal-day .dots{ display:flex; gap:3px; justify-content:center; flex-wrap:wrap; }
.cal-day .dot{ width:9px; height:9px; flex:none; }
.sidebar-box{
  border-top: 10px solid var(--umd-red);
  background: var(--sidebar-band);
  padding: 22px; margin-bottom: 20px;
}
.sidebar-box h4{ font-family: var(--font-body); font-size:.95rem; font-weight:700; margin-bottom:12px; }
.sidebar-box ul{ list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:10px; }
.sidebar-box li a{ font-size:.9rem; font-weight:600; }

/* ---------- Tabs (topic pages: Projects / Publications / People) ---------- */
.tabs{ display:flex; gap: 4px; border-bottom: 2px solid var(--ink); margin-bottom: 28px; flex-wrap:wrap; }
.tab-btn{
  background:none; border:none; padding: 10px 16px 10px 0; margin-right: 22px; font-family: var(--font-nav);
  font-weight:700; font-size:.92rem; letter-spacing: .02em; text-transform: uppercase; color: var(--ink-soft); cursor:pointer;
  border-bottom: 3px solid transparent; margin-bottom:-2px;
}
.tab-btn.active{ color: var(--umd-red); border-bottom-color: var(--umd-red); }
.tab-panel{ display:none; }
.tab-panel.active{ display:block; }

/* ---------- Filter pills (Research: by focus area / Events: by category)
   -- see filter_pills_html() in generate.py. Distinct look from the
   underline .tab-btn style above since these toggle visibility of content
   further down the same page rather than switching between panels. ---------- */
.filter-bar{ display:flex; flex-wrap:wrap; gap:10px; }
.filter-pill{
  font-family: var(--font-nav); font-weight:700; font-size:.8rem; letter-spacing:.03em; text-transform:uppercase;
  padding: 9px 18px; border: 1.5px solid var(--ink-soft); border-radius: var(--radius);
  color: var(--ink-soft); background: transparent; cursor:pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.filter-pill:hover{ border-color: var(--ink); color: var(--ink); }
.filter-pill.active{ background: var(--umd-red); border-color: var(--umd-red); color:#fff; }
/* Follow-up (direct user request): the .filter-pill look above (bold
   uppercase, thick border, sharp corners) reads as a heavy button --
   fine for the Research Area/Category dropdown menus, too loud for a
   plain year toggle sitting next to the Publications heading. Restyled
   as a small, quiet, fully-rounded chip instead -- plain-weight text,
   thin border, soft grey fill -- so it reads as a minor filter rather
   than competing for attention. Placed here (after .filter-pill,
   .active included) so it wins the cascade tie instead of the base
   rule silently overriding it. */
.year-filter-pill{
  font-family: var(--font-body); font-weight: 600; letter-spacing: normal; text-transform: none;
  padding: 4px 12px; font-size:.76rem; border-radius: 999px;
  border-color: var(--line); color: var(--ink-soft); background: var(--paper-soft);
}
.year-filter-pill:hover{ border-color: var(--ink-soft); background: var(--nav-band); color: var(--ink); }
/* Hovering an already-selected year pill would otherwise fall back to
   the grey hover state above (same specificity, later in source order
   than .filter-pill.active) -- keep it red/white while active. */
.year-filter-pill.active:hover{ background: var(--umd-red); border-color: var(--umd-red); color:#fff; }

/* ---------- Footer ---------- */
footer.site-footer{ background: #fff; border-top: 1px solid var(--line); padding-top: 48px; }
/* Follow-up (direct user request): 4 columns -> 3 now that the social
   icons' old first column is gone (see footer() in generate.py) --
   Research/Events/Connect are the only columns left.
   Follow-up (direct user request): "center these three columns... they
   skew left." 1fr 1fr 1fr stretched all 3 tracks to fill the full
   ~1440px container edge-to-edge, so each column (Research especially)
   was 400px+ wide -- structurally centered as a GRID, but every
   column's actual text is short and left-aligned within its own wide
   track, so the visible content still hugged the left edge of each
   third, reading as left-skewed overall rather than as one centered
   block (unlike the newsletter band right above it, which IS a
   centered block). max-content columns shrink each track to fit its
   own content instead of stretching.
   Follow-up (direct user request): "spaced out across the bottom... do
   not need them centered and hugging each other" -- justify-content
   flipped center -> space-between, so Research sits at the container's
   left edge, Connect at its right edge, and Events falls in the even
   gap between them, spread across the full width like the original
   1fr 1fr 1fr did spatially, but without that version's problem (each
   column's own content is still sized to itself, left-aligned under
   its own heading, not stretched into a wide track). gap is now just a
   floor under that spacing, not the main mechanism, so it's back down
   to a plain 32px. Mobile (max-width:900px/720px below) is untouched --
   1fr 1fr there is the right call at that size.
   Follow-up (direct user request): "Research and Connect can come in a
   bit... do not need to be hugging the ends" -- space-between put
   Research and Connect flush against .container's own left/right edges
   (the same edges the header logo/nav and every other section's content
   line up with), which read as too tight. 64px of horizontal padding
   here insets both of them from those edges without touching how
   Events falls evenly between them -- space-between still distributes
   whatever width is left after that padding is subtracted. */
.footer-grid{ display:grid; grid-template-columns: repeat(3, max-content); justify-content:space-between; gap: 32px; padding: 0 64px 44px; }
.footer-grid h4{ font-family: var(--font-nav); font-size:.82rem; text-transform:uppercase; letter-spacing:.04em; margin-bottom:14px; color: var(--ink); }
.footer-grid ul{ list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:9px; }
.footer-grid a{ color: var(--ink-soft); font-size:.92rem; }
.footer-grid a:hover{ color: var(--umd-red); }
/* Follow-up (direct user request): margin-bottom -> margin-top -- this
   used to open its column with .footer-legal's link list right below it
   (hence a BOTTOM margin separating the two); now it's the last thing
   in the Connect column, below the About & Contact list, so the same
   18px gap needs to go on top instead, or the icons would sit flush
   against "About & Contact" above them. */
.footer-social{ display:flex; flex-wrap:wrap; align-items:center; row-gap:10px; column-gap:16px; margin-top: 18px; }
.footer-social a{
  display:inline-flex; align-items:center; justify-content:center;
}
.footer-social img{
  height:20px; width:auto; display:block; opacity:.8;
  transition: opacity .15s ease, transform .15s ease;
}
.footer-social a:hover img{ opacity:1; transform: translateY(-1px); }
.footer-legal{ list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:8px; }
.footer-legal a{ color: var(--ink-soft); font-size:.85rem; }
.footer-bottom{
  border-top: 1px solid var(--line); padding: 22px 0 30px;
  display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:14px;
}
.footer-bottom-brand{ display:flex; align-items:center; gap:12px; }
.footer-bottom-brand img{ height:26px; width:auto; }
.footer-bottom-brand span{ font-family: var(--font-accent); font-style: italic; font-size: .95rem; color: var(--ink-soft); }
.footer-bottom-partner{ font-size: .82rem; color: var(--ink-soft); }
.footer-bottom-partner a{ font-weight: 600; }
.footer-bottom-copyright{ font-size:.8rem; color: var(--ink-soft); }

/* ---------- Responsive ---------- */
@media (max-width: 1150px){
  .rail-wrap{ grid-template-columns: 1fr; gap: 32px; }
  .rail-wrap .rail-panel{ border-left:none; padding-left:0; border-top: 2px solid var(--ink); padding-top: 24px; }
}
/* Nav collapses to an off-canvas hamburger menu starting here -- five nav
   items plus dropdowns don't leave enough room next to the GoTech logo +
   Hub lockup + Subscribe button above this width. (This used to kick in
   only at 720px, which left a squeezed 720-1080px zone where the brand
   lockup -- and before it, the plain text wordmark -- could get clipped
   by .brand-lockup's overflow:hidden.)
   Follow-up (direct user request): this used to be a FULL-SCREEN
   overlay with no visible way to close it (the hamburger never
   changed appearance, so re-finding "the same 3 bars" was the only,
   undiscoverable exit). Now a bounded-width drawer (min(320px, 85vw) --
   never the full screen even on a narrow phone) that slides in from
   the right over a dimmed, click-to-close .nav-backdrop, with an
   explicit "X" .nav-close button at the top of the panel and the
   hamburger itself morphing into an X (see base rules above) -- three
   redundant, discoverable ways to exit instead of one hidden one. */
@media (max-width: 1080px){
  .nav-backdrop{
    display:block; position:fixed; inset:0; background:rgba(15,15,20,.45);
    z-index:140; opacity:0; pointer-events:none; transition: opacity .2s ease;
  }
  .nav-backdrop.open{ opacity:1; pointer-events:auto; }
  nav.primary-nav{
    position: fixed; top: 0; right:0; bottom:0; left:auto;
    width: min(320px, 85vw);
    background:#fff; flex-direction:column; align-items:stretch;
    padding: 16px 20px 20px; transform: translateX(100%); transition: transform .2s ease;
    overflow-y:auto; z-index: 150; box-shadow: -12px 0 32px rgba(15,15,20,.18);
  }
  nav.primary-nav.open{ transform: translateX(0); }
  .nav-panel-head{ display:flex; justify-content:flex-end; margin-bottom:8px; }
  .nav-close{
    display:flex; align-items:center; justify-content:center; width:36px; height:36px;
    background:none; border:1.5px solid var(--line); border-radius: var(--radius);
    color: var(--ink); font-size:1.3rem; line-height:1; cursor:pointer;
  }
  .nav-close:hover{ border-color: var(--ink); }
  nav.primary-nav > ul{ flex-direction:column; align-items:stretch; gap:0; }
  nav.primary-nav a.nav-link{ color: var(--ink); padding:14px 4px; border-bottom:1px solid var(--line); justify-content:space-between; }
  .dropdown{ position:static; transform:none; width:auto; display:block; box-shadow:none; border:none; padding-left:12px; margin:0; }
  .nav-toggle{ display:block; }
}
/* Prevents the page behind the dimmed backdrop from scrolling while the
   drawer is open (main.js toggles this class on <body> alongside the
   drawer/backdrop) -- otherwise a touch-scroll on what looks like a
   dimmed, inert background still scrolls the real page underneath it. */
body.nav-open{ overflow:hidden; }
@media (max-width: 900px){
  .grid-4{ grid-template-columns: repeat(2,1fr); }
  .grid-3{ grid-template-columns: repeat(2,1fr); }
  .news-grid-section .grid-3 > .card:nth-child(n+3){ border-top: 1px solid var(--line); padding-top: 32px; margin-top: 8px; }
  .questions-grid{ grid-template-columns: 1fr; }
  .grid-4 > .topic-card:nth-child(2n+1){ border-left:none; padding-left:0; }
  .with-sidebar{ grid-template-columns: 1fr; }
  .cal-day{ min-height: 46px; }
  /* padding reset to 0 -- the 64px horizontal inset above (direct user
     request, desktop-only in spirit) is sized for space-between's wide
     desktop spread; at this width the grid is back to a plain 1fr 1fr
     fill, where .container's own 24px padding is the only inset this
     already had before that request, and doubling up with 64px more
     here would crowd out most of a phone-width column. */
  .footer-grid{ grid-template-columns: 1fr 1fr; gap: 28px; padding-left: 0; padding-right: 0; }
  .teaching-cta{ text-align:left; margin-top:16px; }
  .header-inner{ --brand-mark-h: 56px; }
}
@media (max-width: 720px){
  .brand-lockup{ gap:10px; }
  .header-inner{ --brand-mark-h: 48px; }
  .grid-2, .grid-3, .grid-4{ grid-template-columns: 1fr; }
  .grid > .card, .grid-3 > .person, .grid-4 > .topic-card{ border-left:none !important; padding-left:0 !important; border-top: 1px solid var(--line); padding-top: 24px; }
  .grid > .card:first-child, .grid-3 > .person:first-child, .grid-4 > .topic-card:first-child{ border-top:none; padding-top:0; }
  /* Collapsing to 1 column moves the grid's "last column" cells' own
     border-right (which used to close off the right edge) out of the
     layout entirely, leaving the whole block with no right edge at
     all -- put it back on the container itself instead. */
  .research-matrix{ grid-template-columns: 1fr; border-right: 1px solid var(--line); }
  .matrix-cell{ border-right:none; }
  .news-grid-section .grid-3 > .card{ border-top: 1px solid var(--line); padding-top: 24px; margin-top: 0; }
  .news-grid-section .grid-3 > .card:first-child{ border-top:none; padding-top:0; }
  .feed-item{ grid-template-columns: 1fr; }
  .event-row{ grid-template-columns: 60px 1fr; }
  .event-row .btn{ grid-column: 1 / -1; justify-self:start; }
  .footer-grid{ grid-template-columns: 1fr 1fr; padding-left: 0; padding-right: 0; }
}
@media (max-width: 480px){
  .header-inner{ gap:10px; --brand-mark-h: 38px; }
  .brand-lockup{ gap:8px; }
  .header-actions{ gap:6px; }
  .header-actions .btn{ padding:9px 14px; font-size:.72rem; }
  .affiliation-strip span, .affiliation-strip a{ font-size:.68rem; }
  .signal-card{ padding:0 14px; }
  .ticker-live{ padding:0 10px; }
  .ticker-live .label{ display:none; }
}
