/* ======================================================================
   REBL — Robotics-Enabled Biology Lab · UCL CS
   Warm cream + ink editorial style, Helvetica Neue.
   ====================================================================== */

/* Helvetica Neue is licensed and cannot ship with a public repo, so the body
   face is left to the system stack in --sans below. */
/* Anton — the heavy condensed display face used for the hero headline.
   Open source (SIL OFL 1.1), from github.com/google/fonts */
@font-face{
  font-family: "Anton";
  src: url("assets/fonts/Anton-Regular.ttf") format("truetype");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root{
  /* Warm cream + ink palette */
  --bg: #F2EEE3;
  --bg2: #EAE6D9;
  --panel: rgba(23,21,15,0.045);
  --panel2: rgba(23,21,15,0.085);
  --stroke: rgba(23,21,15,0.13);
  --stroke2: rgba(23,21,15,0.24);
  --text: #17150F;
  --muted: #6F6A5C;
  --muted2:#97927F;

  /* Accent: warm near-black ink (dark pills, dots, active states).
     Kept under the old --yellow names so existing rules pick it up. */
  --yellow:   #211C10;
  --yellow-2: #3A3426;
  --yellow-3: #211C10;
  --ink:      #F6F2E6; /* text on the dark accent */

  /* REBL brand marks — the three accents the diagrams are drawn in. Kept as
     tokens so the page and the artwork stay on the same three colours. */
  --accent-yellow: #F2D64B;
  --accent-blue:   #21b1ff;
  --accent-pink:   #ff218c;

  /* The two grounds, always available under both themes. Dark bands on an
     otherwise light page (Projects, Join, Contact) read them directly. */
  --ground-dark:  #0B0B0A;
  --ground-dark2: #121210;
  --ground-warm:  #F2EEE3;
  --on-dark:      #F5F3EC;
  --on-dark-muted: rgba(245,243,236,0.66);

  --good: #34d399;
  --warn: #fbbf24;

  --shadow: 0 18px 44px rgba(23,21,15,0.10);
  --shadow2: 0 8px 22px rgba(23,21,15,0.07);

  --radius-xl: 26px;
  --radius-lg: 18px;
  --radius-md: 12px;
  --radius-sm: 8px;

  --max: 1180px;
  /* The side gutter: the least room left between the content and the window
     edge. It scales with the window, from 24px on a phone through ~40px on a
     tablet and 50–64px on a laptop window that isn't full size, and tops out
     at the 68px a full-size window had already. */
  --pad: clamp(24px, 4.5vw + 6px, 68px);

  /* How far the content column spreads past --max: the logo/headline sit further
     left, the nav/menu the same distance right. Self-limiting — it collapses to 0
     as soon as the viewport stops having the spare gutter for it, so nothing is
     pushed off-screen on small displays. --col is the resulting column width, and
     every band on the page (header, hero, sections, footer) shares it. Columns
     are border-box with --pad inside them, so --col grows with the gutter past
     its original 20px: content on a full-size window is exactly as wide as it
     was, and below that the gutter, not the column, sets the margin. */
  --hero-outset: clamp(0px, (100vw - var(--max)) / 2 - 48px, 96px);
  --col: calc(var(--max) + var(--hero-outset) * 2 + (var(--pad) - 20px) * 2);

  --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  --sans: "Helvetica Neue", Helvetica, "Segoe UI", Arial, sans-serif;
  --display-condensed: "Anton", "Helvetica Neue Condensed", Impact, sans-serif;
  --serif: Georgia, "Times New Roman", serif;
  --display: var(--sans);
}

/* ===== Night mode — warm near-black, white pills (reference dark variant) ===== */
html[data-theme="night"]{
  --bg: #0B0B0A;
  --bg2: #121210;
  --panel: rgba(245,243,236,0.05);
  --panel2: rgba(245,243,236,0.09);
  --stroke: rgba(245,243,236,0.14);
  --stroke2: rgba(245,243,236,0.26);
  --text: #F5F3EC;
  --muted: #A6A296;
  --muted2:#7B776C;

  /* Accent pills invert: white pill, near-black label */
  --yellow:   #F5F3EC;
  --yellow-2: #FFFFFF;
  --yellow-3: #F5F3EC;
  --ink:      #16150F;

  --shadow: 0 18px 44px rgba(0,0,0,0.50);
  --shadow2: 0 8px 22px rgba(0,0,0,0.40);
}
html[data-theme="night"] .header{ background: rgba(11,11,10,0.88) }
/* Outranks the night bar colour so the over-hero state stays transparent */
html[data-theme="night"] .header.is-over-hero{ background: transparent; backdrop-filter: none }
/* The kicker dot is the one place the brand yellow shows on a dark ground —
   matched to the dot inside .page-intro so every dark opening reads the same. */
html[data-theme="night"] .kicker .dot{
  background: var(--accent-yellow);
  box-shadow: 0 0 0 5px rgba(242,214,75,0.14);
}
html[data-theme="night"] .card::after{
  background: radial-gradient(420px 240px at 20% 10%, rgba(245,243,236,0.07), transparent 62%);
}
html[data-theme="night"] .tag,
html[data-theme="night"] .code{ background: rgba(245,243,236,0.06) }
html[data-theme="night"] .small-link,
html[data-theme="night"] .person .links a,
html[data-theme="night"] .pi-info .links a,
html[data-theme="night"] .thesis-pdf{ background: rgba(245,243,236,0.055) }
html[data-theme="night"] details.bib pre{ background: rgba(245,243,236,0.06) }
html[data-theme="night"] .pub .byline strong{
  background: linear-gradient(transparent 70%, rgba(245,243,236,0.22) 70%);
}
html[data-theme="night"] .pub-card .thumb,
html[data-theme="night"] .project-card .thumb{ background: #26251F }
html[data-theme="night"] .pub-card .thumb.is-empty,
html[data-theme="night"] .project-card .thumb.is-empty{
  background:
    repeating-linear-gradient(45deg, rgba(245,243,236,0.05) 0 6px, transparent 6px 12px),
    #26251F;
}
html[data-theme="night"] .contact-form input,
html[data-theme="night"] .contact-form select,
html[data-theme="night"] .contact-form textarea{ background: rgba(245,243,236,0.055) }
html[data-theme="night"] .person .photo,
html[data-theme="night"] .person.lead .photo,
html[data-theme="night"] .pi-photo{ background: #26251F }

*{box-sizing:border-box}
body{
  margin:0;
  font-family:var(--sans);
  color:var(--text);
  background: var(--bg);
  overflow-x:hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-size: 16px;
  line-height: 1.55;
  letter-spacing: 0;
}

::selection{background: var(--yellow); color: var(--ink)}

/* Accessibility */
.skip-link{
  position:absolute; top:8px; left:8px;
  padding:10px 12px; border-radius:12px;
  background: var(--panel2); border:1px solid var(--stroke);
  color:var(--text);
  transform: translateY(-140%); transition: transform 180ms ease;
  z-index: 9999;
}
.skip-link:focus{transform: translateY(0)}

/* Layout helpers */
/* --pad is the side gutter every column keeps, so nothing runs flush to the
   window edge once the viewport is narrower than --col. Rules that share an
   element with .wrap (.section, .header-inner, .footer) set block padding only,
   or the shorthand would zero this gutter out again. */
.wrap{max-width:var(--col); margin:0 auto; padding:0 var(--pad)}
.section{padding-block: 78px}

/* ===== Pages =====
   The site is a set of discrete pages inside one document: the nav mounts one
   section at a time rather than scrolling a single long column. --header-h is
   measured in script.js so inner pages always clear the fixed bar. */
.page{ display: none }
.page.is-active{ display: block }
.section.page{ padding-top: calc(var(--header-h, 86px) + clamp(48px, 8vh, 92px)) }
.section.page.is-active{ animation: page-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) both }
@keyframes page-in{
  from{ opacity: 0; transform: translateY(10px) }
  to{ opacity: 1; transform: none }
}

/* ===== Dark opening band =====
   Projects, Join and Contact open on the near-black ground and then hand over
   to the warm page for the cards, the form and the contact details. The band
   is full-bleed, so the section drops its own top padding and the band carries
   the clearance for the fixed bar instead. */
.section.page.has-intro{ padding-top: 0 }

.page-intro{
  /* Out of the column and across the viewport. body has overflow-x:hidden, so
     the 100vw overshoot beside a scrollbar never becomes a horizontal scroll. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  position: relative;
  isolation: isolate;
  background: var(--ground-dark);
  color: var(--on-dark);
  padding: calc(var(--header-h, 86px) + clamp(48px, 8vh, 92px)) 0 clamp(48px, 8vh, 92px);
  margin-bottom: clamp(40px, 6vh, 72px);
}
/* The column, restored inside the full-bleed band. */
.page-intro-inner{
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 var(--pad);
  position: relative;
  z-index: 1;
}
/* Type on the band reads off the dark tokens rather than the page's ink ones. */
.page-intro .kicker{ color: var(--on-dark-muted) }
.page-intro .kicker .dot{
  background: var(--accent-yellow);
  box-shadow: 0 0 0 5px rgba(242,214,75,0.14);
}
.page-intro h2,
.page-intro .display-title{ color: var(--on-dark) }
.page-intro p{ color: var(--on-dark-muted) }
.page-intro a{ color: var(--on-dark) }
.page-intro strong{ color: var(--on-dark) }

/* --- Photo variant ---
   Projects, Join and Contact open over a photograph. Swap the image by
   changing --intro-photo on the band; the scrim keeps the type legible
   whatever the picture underneath is doing. --intro-photo-x/-y pick which part
   of the photo stays in frame, in the photo's own coordinates. The type and
   the heavy end of the scrim sit on the left, so a photo whose subject is on
   the left takes --intro-photo-flip: -1 to mirror it across to the right, the
   way the cheetah sits on the home hero. */
.page-intro.has-photo::before{
  content: "";
  position: absolute; inset: 0;
  background-image: var(--intro-photo);
  background-size: cover;
  background-position: var(--intro-photo-x, center) var(--intro-photo-y, 38%);
  transform: scaleX(var(--intro-photo-flip, 1));
  /* Greyscale ties the photo to the cheetah hero and stops it competing with
     the yellow/blue/pink the diagrams are drawn in. */
  filter: grayscale(1) contrast(1.05);
  opacity: 0.8;
  z-index: -2;
}
.page-intro.has-photo::after{
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(11,11,10,0.55), rgba(11,11,10,0) 40%, rgba(11,11,10,0.35)),
    linear-gradient(to right, rgba(11,11,10,0.97), rgba(11,11,10,0.8) 35%, rgba(11,11,10,0) 72%);
  z-index: -1;
}
/* --- Photo beside the type ---
   For a photo that has to be seen rather than sit behind the copy (Join): the
   type keeps the plain dark ground on the left, and the photo fills the right
   of the band at full strength. It starts under the end of the copy and is
   masked in from nothing over its first 45%, along a smoothstep curve: a
   straight ramp has a hard start and end the eye reads as an edge, this one
   eases in and out of both. The floor, 520px past the gutter, keeps the copy
   over the clear end of the ramp on narrow laptops. Below 900px the copy runs the full width, so
   the photo moves under it instead and fades in from the top. */
.page-intro.photo-right{
  --photo-fade:
    rgba(0,0,0,0) 0%, rgba(0,0,0,.03) 4.5%, rgba(0,0,0,.1) 9%,
    rgba(0,0,0,.22) 13.5%, rgba(0,0,0,.35) 18%, rgba(0,0,0,.5) 22.5%,
    rgba(0,0,0,.65) 27%, rgba(0,0,0,.78) 31.5%, rgba(0,0,0,.9) 36%,
    rgba(0,0,0,.97) 40.5%, #000 45%;
}
.page-intro.photo-right::before,
.page-intro.photo-right::after{ left: max(40%, var(--pad) + 520px) }
.page-intro.photo-right::before{
  opacity: 1;
  -webkit-mask-image: linear-gradient(to right, var(--photo-fade));
          mask-image: linear-gradient(to right, var(--photo-fade));
}
/* Only the top shade is left, for the nav links that sit over the photo. */
.page-intro.photo-right::after{
  background: linear-gradient(to bottom, rgba(11,11,10,0.55), rgba(11,11,10,0) 30%);
}
@media (max-width: 900px){
  .page-intro.photo-right{
    --intro-photo-h: min(62vw, 420px);
    padding-bottom: calc(var(--intro-photo-h) + 28px);
  }
  .page-intro.photo-right::before,
  .page-intro.photo-right::after{ inset: auto 0 0 0; height: var(--intro-photo-h) }
  .page-intro.photo-right::before{
    -webkit-mask-image: linear-gradient(to bottom, var(--photo-fade));
            mask-image: linear-gradient(to bottom, var(--photo-fade));
  }
  .page-intro.photo-right::after{ background: none }
}

/* --- Header over a dark band ---
   Same idea as the over-hero bar: no fill while the band is under it, white
   type, and the white-on-dark lockup. Once the band scrolls away the bar
   returns to the solid warm one. */
.header.is-over-dark{
  background: transparent;
  backdrop-filter: none;
}
.header.is-over-dark .logo-lock .lock-day{ display: none }
.header.is-over-dark .logo-lock .lock-night{ display: block }
.header.is-over-dark .nav a{ color: var(--on-dark); opacity: 0.78 }
.header.is-over-dark .nav a:hover,
.header.is-over-dark .nav a[aria-current="page"]{ color: var(--on-dark); opacity: 1 }
.header.is-over-dark .icon-btn{
  color: var(--on-dark);
  border-color: rgba(245,243,236,0.34);
}
.header.is-over-dark .icon-btn:hover{ background: rgba(245,243,236,0.14) }

/* The footer runs on every page; on home it sits under the full-height hero. */
.section h2{
  margin:0 0 16px;
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(30px, 3.6vw, 54px);
  letter-spacing: -0.03em;
  line-height: 1.05;
}
/* Section heading set in the hero's condensed display face. */
.section h2.display-title{
  font-family: var(--display-condensed);
  font-weight: 400;
  font-size: clamp(24px, 2.6vw, 38px);
  text-transform: uppercase;
  letter-spacing: 0.005em;
  line-height: 0.94;
}
.hl-robotics{ color: var(--accent-blue) }
.hl-biology{ color: var(--accent-pink) }

.section h2 ~ h2{ margin-top: 64px }

.kicker{
  display:inline-flex; align-items:center; gap:10px;
  margin-bottom: 10px;
  color: var(--muted);
  font-family: var(--sans);
  font-weight:500;
  letter-spacing: 1.5px;
  text-transform:uppercase;
  font-size: 12px;
}
.kicker .dot{
  width:8px; height:8px; border-radius:99px;
  background: var(--yellow);
  box-shadow: 0 0 0 5px rgba(23,21,15,0.08);
}

/* ===== Header ===== */
/* Fixed so the hero image runs full-bleed underneath it. Over the hero the bar is
   transparent with white type; past the hero it becomes the solid cream bar. */
.header{
  position:fixed; top:0; left:0; right:0; z-index: 1000;
  backdrop-filter: blur(12px);
  background: rgba(242,238,227,0.88);
  transition: background 300ms ease, backdrop-filter 300ms ease;
}
.header-inner{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: 16px;
  padding-block: 14px;
}
.header-right{ display:flex; align-items:center; gap: 8px }

.nav{ display:flex; align-items:center; gap: 4px; white-space: nowrap }
.nav a{
  color:var(--text); text-decoration:none;
  opacity: 0.72;
  padding: 8px 10px;
  border-radius: 8px;
  font-weight:400;
  font-size: 14.5px;
  transition: opacity 160ms ease, color 300ms ease;
  font-family: var(--sans);
  letter-spacing: 0;
}
.nav a:hover{ opacity: 1 }
.nav a[aria-current="page"]{ opacity: 1; font-weight: 500 }

.brand{
  display:flex; align-items:center;
  text-decoration:none; color:var(--text);
  flex: 0 0 auto;
  /* Fixed height so swapping the two lockups never resizes the header */
  height: 58px;
}
/* Night page, cream band underneath (Research, past "How we do research"):
   the bar takes back the day tokens, the cream fill and the black lockup. */
html[data-theme="night"] .header.is-over-light{
  --panel: rgba(23,21,15,0.045);
  --stroke: rgba(23,21,15,0.13);
  --stroke2: rgba(23,21,15,0.24);
  --text: #17150F;
  --muted: #6F6A5C;
  --shadow: 0 18px 44px rgba(23,21,15,0.10);
  background: rgba(242,238,227,0.88);
}
html[data-theme="night"] .header.is-over-light .logo-lock .lock-day{ display: block }
html[data-theme="night"] .header.is-over-light .logo-lock .lock-night{ display: none }

/* The lockup off the hero: same artwork, on the cream bar and in the footer.
   Both files carry the ink in the same spot on a mostly-empty canvas, so the box
   is a window onto that spot and the rest is pulled off the layout box. Offsets
   are fractions of --lock (the image's own height), so one variable resizes it.
   The fractions are .brand-glow's own margins over its height (-56/-38 of 180,
   window 184x58): sharing them means that at a matching --lock the ink lands on
   exactly the same pixel here as it does over the hero, so the mark doesn't jump
   when the bar swaps lockups mid-scroll. */
.logo-lock{
  --lock: 110px;
  display: block;
  height: calc(var(--lock) * 0.3222);
  width: calc(var(--lock) * 1.0222);
  overflow: hidden;
  flex: 0 0 auto;
}
.logo-lock img{
  display: block;
  height: var(--lock);
  width: auto;
  max-width: none;
  margin: calc(var(--lock) * -0.3111) 0 0 calc(var(--lock) * -0.2111);
}
/* Day: black type on a transparent canvas, dropped straight onto the cream.
   Night: the white-type file, screened so its black canvas falls away. */
.logo-lock .lock-night{ display: none; mix-blend-mode: screen }
html[data-theme="night"] .logo-lock .lock-day{ display: none }
html[data-theme="night"] .logo-lock .lock-night{ display: block }

/* Matches --lock to the hero lockup's own height (.brand-glow), so the mark is
   exactly the same size on the cream bar as it is over the photo. */
.brand-ink{ --lock: 180px }
/* The lockup fills only ~29% of its PNG's height (the rest is transparent glow),
   so it is scaled up and the empty margin is pulled back off the layout box.
   `screen` keeps the soft halo blending into the photo instead of greying it. */
.brand-glow{
  height: 180px;
  width: auto;
  display: none;
  margin: -56px -48px -66px -38px;
  mix-blend-mode: screen;
  pointer-events: none;
}

/* --- Over-hero variant --- */
.header.is-over-hero{
  background: transparent;
  backdrop-filter: none;
}
.header.is-over-hero .brand-glow{ display: block }
.header.is-over-hero .brand-ink{ display: none }
.header.is-over-hero .nav a{ color: #fff; opacity: 0.86 }
.header.is-over-hero .nav a:hover,
.header.is-over-hero .nav a[aria-current="page"]{ color: #fff; opacity: 1 }
.header.is-over-hero .icon-btn{
  color: #fff;
  border-color: rgba(255,255,255,0.34);
}
.header.is-over-hero .icon-btn:hover{ background: rgba(255,255,255,0.14) }

.icon-btn{
  width: 40px; height: 40px;
  display:grid; place-items:center;
  border-radius: 999px;
  background: transparent;
  border:1px solid var(--stroke);
  color: var(--text);
  cursor:pointer;
  transition: transform 120ms ease, background 180ms ease, border-color 180ms ease;
}
.icon-btn:hover{ background: var(--panel); border-color: var(--stroke2) }
.icon{ width: 18px; height: 18px }

.menu-btn{ display:none }

/* ======================================================================
   HERO — full-bleed cheetah, left-aligned display type, yellow accent
   ====================================================================== */
.hero{
  height: 100vh;
  min-height: 560px;
  background: #0B0B0A;
  position: relative;
  z-index: 1;
  padding: 0;
}
.hero-frame{
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: hidden;
  background: #0B0B0A;
}
.hero-bg{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  user-select: none;
  /* Nudges the cheetah to the right of the frame. Raise --hero-shift to push it
     further; --hero-zoom is what creates the spare image to slide into, so it must
     stay at or above 1 / (1 - 2 × shift) — at 6% that is 1.14 — or the left edge
     goes bare. (object-position can't do this job: at a 16:9 viewport the photo
     matches the frame and there is no horizontal crop for it to act on.) */
  --hero-zoom: 1.24;
  --hero-shift: 9.5%;
  transform: scale(var(--hero-zoom)) translateX(var(--hero-shift));
  /* Black and white, as in the reference — drop grayscale() for full colour */
  filter: grayscale(1) contrast(1.04) brightness(0.96);
}
/* Darkens the left side so the white display type holds up over the grass */
.hero-scrim{
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(90deg, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.55) 38%, rgba(0,0,0,0.15) 72%, rgba(0,0,0,0.05) 100%),
    linear-gradient(180deg, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0) 28%, rgba(0,0,0,0) 70%, rgba(0,0,0,0.40) 100%);
}

/* Deliberately not .wrap: that class's padding shorthand is re-declared in a later
   media query, which would wipe the bottom padding out from under this block. */
.hero-copy{
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--col);
  margin: 0 auto;
  text-align: left;
  /* Sits low in the frame like the reference, clear of the funding strip */
  padding: 0 var(--pad) clamp(88px, 13vh, 130px);
}
.hero-title{
  margin: 0 0 22px;
  font-family: var(--display-condensed);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(42px, 6.6vw, 104px);
  letter-spacing: 0.005em;
  line-height: 0.94;
  color: #fff;
  animation: hero-up-20 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.25s both;
}
.hero-title .hl{ color: var(--accent-yellow) }
.hero-desc{
  margin: 0 0 32px;
  max-width: 44ch;
  font-weight: 300;
  font-size: clamp(15px, 1.2vw, 18px);
  line-height: 1.5;
  color: rgba(255,255,255,0.90);
  animation: hero-up-16 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.45s both;
}
.hero-btns{
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  animation: hero-up-16 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.65s both;
}
.hero-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 17px 32px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 200ms ease, color 200ms ease, border-color 200ms ease;
}
.hero-btn.solid{
  background: var(--accent-yellow);
  border-color: var(--accent-yellow);
  color: #17150F;
}
.hero-btn.solid:hover{ background: #FFE783; border-color: #FFE783 }
.hero-btn.outline{
  background: transparent;
  border-color: var(--accent-yellow);
  color: var(--accent-yellow);
}
.hero-btn.outline:hover{ background: rgba(242,214,75,0.14) }

/* Funding credit — quiet strip in the lower-left corner */
/* Funding credit — lower-left, aligned to the same column as the copy */
.hero-funding{
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 26px;
  z-index: 2;
  width: 100%;
  max-width: var(--col);
  padding: 0 var(--pad);
  display: flex;
  align-items: center;
  gap: 16px;
  opacity: 0.6;
  transition: opacity 200ms ease;
  /* fill-mode backwards so the resting opacity (and hover) stays ours */
  animation: hero-funding-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.9s backwards;
}
.hero-funding:hover{ opacity: 0.95 }
.hero-funding-label{
  font-family: var(--sans);
  font-size: 11px;
  color: rgba(255,255,255,0.85);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Always over the dark photo: invert to white marks, screen out the white boxes */
.hero-funding img{
  height: 20px;
  width: auto;
  display: block;
  filter: grayscale(1) invert(1) brightness(1.6);
  mix-blend-mode: screen;
}
@keyframes hero-funding-in{
  from{ opacity: 0; transform: translateX(-50%) translateY(10px) }
  to{ opacity: 0.6; transform: translateX(-50%) translateY(0) }
}
@media (max-width: 768px){
  .hero-funding{ display: none }
}

/* Hero entrance animations */
@keyframes hero-up-16{
  from{ opacity: 0; transform: translateY(16px) }
  to{ opacity: 1; transform: translateY(0) }
}
@keyframes hero-up-20{
  from{ opacity: 0; transform: translateY(20px) }
  to{ opacity: 1; transform: translateY(0) }
}
.btn{
  display:inline-flex; align-items:center; gap: 10px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--stroke);
  background: var(--panel);
  color: var(--text);
  text-decoration:none;
  font-weight:500;
  font-size: 14px;
  cursor:pointer;
  transition: transform 120ms ease, background 180ms ease, border-color 180ms ease;
  font-family: var(--sans);
  letter-spacing: 0;
  line-height: 1;
  white-space: nowrap;
}
.btn:hover{transform: translateY(-1px); background: var(--panel2); border-color: var(--stroke2)}
.btn.primary{
  border-color: var(--yellow-3);
  background: var(--yellow);
  color: var(--ink);
}
.btn.primary:hover{ background: var(--yellow-2); border-color: var(--yellow) }
.btn .kbd{
  font-family: var(--mono); font-size: 11px; color: var(--ink);
  border: 1px solid rgba(13,13,11,0.30);
  padding: 2px 7px; border-radius: 999px;
  background: rgba(255,255,255,0.45);
}

/* ===== Cards / grids ===== */
.grid{ display:grid; gap: 16px }
.grid.cols-3{ grid-template-columns: repeat(3, minmax(0,1fr)) }
.grid.cols-2{ grid-template-columns: repeat(2, minmax(0,1fr)) }
.card{
  border-radius: var(--radius-lg);
  background: var(--panel);
  border: 1px solid var(--stroke);
  box-shadow: var(--shadow2);
  padding: 18px;
  position:relative;
  overflow:hidden;
}
.card::after{
  content:""; position:absolute; inset:-30%;
  background: radial-gradient(420px 240px at 20% 10%, rgba(23,21,15,0.06), transparent 62%);
  opacity: 0; transition: opacity 240ms ease; pointer-events:none;
}
.card:hover::after{ opacity:1 }
.card h3{
  margin:0 0 8px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.card p{ margin:0; color:var(--muted); line-height:1.6; font-size: 14px }
.card p + p{ margin-top: 10px }
.card .meta{ margin-top: 12px; display:flex; flex-wrap:wrap; gap: 8px }
.project-card{
  display: flex;
  gap: 24px;
  /* Stretch so the text column is at least as tall as the icon and the tags
     can sit on the card's bottom edge. */
  align-items: stretch;
  min-width: 0;
  padding: 24px;
}
.project-card .info{ min-width: 0; flex: 1; display: flex; flex-direction: column }
.project-card h3{ margin: 4px 0 10px; padding-right: 18px; font-size: 18px }
.project-card p{ font-size: 14px; line-height: 1.6 }
.project-card .meta{ margin-top: auto; padding-top: 16px }
/* Same click-to-open behaviour as the people cards. */
.project-card{
  cursor: pointer;
  transition: transform 160ms ease, border-color 160ms ease;
}
.project-card:hover{ transform: translateY(-2px); border-color: var(--yellow-3) }
.project-card.expanded{ transform: translateY(-2px); border-color: var(--yellow-3) }
.project-card.expanded .extra{ max-height: 460px; opacity: 1; margin-top: 16px }
.project-card .extra p{ font-size: 13px; line-height: 1.6 }
/* A grid of four question-sized cards reads better with even rows. */
.project-grid{ align-items: start; gap: 20px }
.tag{
  display:inline-flex; align-items:center; gap: 8px;
  border-radius: 999px;
  border: 1px solid var(--stroke);
  background: rgba(23,21,15,0.05);
  padding: 6px 10px;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text);
}
.tag .pin{
  width:7px; height:7px; border-radius:99px;
  background: var(--yellow);
}

/* ===== Publications ===== */

/* Featured papers: image-left cards in the same family as the people cards. */
.pub-featured{
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin: 20px 0 34px;
  align-items: start;
}
.pub-card{
  display: flex;
  gap: 14px;
  padding: 16px;
  border-radius: var(--radius-lg);
  background: var(--panel);
  border: 1px solid var(--stroke);
  box-shadow: var(--shadow2);
  transition: transform 160ms ease, border-color 160ms ease;
  min-width: 0;
  /* Height of a card whose text is shorter than the thumbnail — the collapsed size. */
  min-height: max(118px, var(--collapsed-card-height, 0px));
  position: relative;
  cursor: pointer;
}
.pub-card:hover{ transform: translateY(-2px); border-color: var(--yellow-3) }
.pub-card.expanded{ border-color: var(--yellow-3); transform: translateY(-2px) }
.pub-card .thumb,
.project-card .thumb{
  width: 84px; height: 84px;
  border-radius: 14px;
  border: 1px solid var(--stroke);
  background: #E3DFD0;
  flex: 0 0 auto;
  overflow: hidden;
  transition: width 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              height 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              border-radius 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
.pub-card.expanded .thumb{ width: 120px; height: 120px; border-radius: 18px }
.pub-card .thumb img,
.project-card .thumb img{ width: 100%; height: 100%; object-fit: cover; display: block }
/* Project icons sit at twice the publication thumbnail. Expanding keeps the
   same size rather than shrinking below it. */
.project-card .thumb{ width: 168px; height: 168px; border-radius: 22px }
.project-card.expanded .thumb{ width: 168px; height: 168px; border-radius: 22px }
@media (max-width: 560px){
  /* Too narrow for icon and text side by side: the icon sits above instead. */
  .project-card{ flex-direction: column; gap: 16px; padding: 20px }
  .project-card .thumb,
  .project-card.expanded .thumb{ width: 112px; height: 112px; border-radius: 16px }
}
/* Until the artwork lands, show a neutral tile rather than a broken image. */
.pub-card .thumb.is-empty,
.project-card .thumb.is-empty{
  background:
    repeating-linear-gradient(45deg, rgba(23,21,15,0.05) 0 6px, transparent 6px 12px),
    #E3DFD0;
}
.pub-card .thumb.is-empty img,
.project-card .thumb.is-empty img{ display: none }
.pub-card .info{ min-width: 0; flex: 1; padding-right: 16px }
.pub-card h3{
  margin: 0 0 5px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 13.5px;
  letter-spacing: -0.005em;
  line-height: 1.35;
}
.pub-card .authors{
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              margin 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
.pub-card.expanded .authors{ max-height: 160px; opacity: 1; margin: 4px 0 0 }
.pub-card .authors.todo{ color: var(--muted2); font-style: italic }
.pub-card .venue{
  margin: 7px 0 0;
  color: var(--muted2);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Datasets and software: two per row, detail revealed on click. */
.ds-grid{
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  /* Each card keeps its own height when a neighbour is toggled. */
  align-items: start;
}
/* Keep enough room for the software cards' single row of resource links. */
.software-grid{ grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr)) }
.ds-card{
  position: relative;
  min-height: var(--collapsed-card-height, 0px);
  /* The link row sits at the bottom of each card. */
  display: flex;
  flex-direction: column;
  padding: 18px;
  border-radius: var(--radius-lg);
  background: var(--panel);
  border: 1px solid var(--stroke);
  box-shadow: var(--shadow2);
  cursor: pointer;
  transition: transform 160ms ease, border-color 160ms ease;
}
.ds-card:hover{ transform: translateY(-2px); border-color: var(--yellow-3) }
.ds-card.expanded{ transform: translateY(-2px); border-color: var(--yellow-3) }
.ds-head{
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding-right: 22px; /* clear the expand-icon */
}
.ds-card h4{
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.ds-card .tag{ padding: 3px 9px; font-size: 11px; gap: 6px }
.ds-card .tag .pin{ width: 6px; height: 6px }
.ds-card .ds-sub{ margin: 7px 0 0; color: var(--muted); font-size: 13px; line-height: 1.55 }
.ds-card .ds-list{
  margin: 0; padding: 0; list-style: none;
  display: grid; gap: 5px;
}

.ds-card .ds-list li{
  position: relative;
  padding-left: 15px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.ds-card .ds-list li::before{
  content: "";
  position: absolute; left: 2px; top: 7px;
  width: 5px; height: 5px; border-radius: 1px;
  background: var(--muted2);
  transform: rotate(45deg);
}
/* A card carrying clips takes the whole row when opened. The copy stays in
   a narrow column at its collapsed measure and the clips fill the width
   that frees up, so the card gains width rather than height. */
.ds-card--media .ds-body{ display: flex; flex-direction: column; flex: 1; min-width: 0 }
.ds-media{ display: none }
.ds-card--media.expanded{
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: clamp(200px, 26%, 320px) minmax(0, 1fr);
  gap: 18px;
  align-items: stretch;
}
/* Roomier than the shared cap: the copy is tall once its column narrows. */
.ds-card--media.expanded .extra{ max-height: 600px }
/* Clips run side by side and fill the card's height, cropping at the sides.
   The row is taken out of flow so the videos are sized by the card rather
   than driving its height; the copy column alone decides how tall it is. */
.ds-card--media.expanded .ds-media{
  display: block;
  position: relative;
  min-height: 190px;
}
.ds-card--media.expanded .ds-media-row{
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.ds-card--media.expanded .ds-media figure{
  aspect-ratio: auto;
  height: 100%;
  min-height: 0;
}
.ds-media figure{
  position: relative;
  margin: 0;
  aspect-ratio: 16 / 9; /* the sources are 16:9; overridden when the frame fills the card */
  overflow: hidden;
  border-radius: 8px;
  border: 1px solid var(--stroke);
  background: rgba(23,21,15,0.06);
}
.ds-media video{
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* Composite clips (plots beside footage) are shown whole rather than
   cropped; their own grounds are white, so the letterbox matches them. */
.ds-media--whole figure{ background: #fff }
.ds-media--whole video{ object-fit: contain }
.ds-media--whole figcaption{ color: #17150f; text-shadow: none }
.ds-media figcaption{
  position: absolute;
  left: 5px; bottom: 4px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,0.75);
  pointer-events: none;
}
.ds-card .extra p{ margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.6 }
.ds-card .extra .ds-list + p{ margin-top: 11px }
.ds-card.expanded .extra{ margin-top: 12px }
.ds-card .links{ margin-top: auto; padding-top: 14px; display: flex; gap: 6px; flex-wrap: wrap }
.software-grid .links{ gap: 4px; flex-wrap: nowrap }

/* Shown when the filterable list has nothing to display. */
.pub-empty{
  margin: 0 0 6px;
  padding: 22px 18px;
  border-radius: var(--radius-lg);
  border: 1px dashed var(--stroke2);
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}
.pub-empty[hidden]{ display: none }

.pub-tools{
  display:flex; flex-wrap:wrap; gap: 10px; align-items:center;
  margin: 18px 0 14px;
}
.input{
  flex: 1 1 240px; min-width: 220px;
  display:flex; align-items:center; gap: 10px;
  padding: 12px 12px;
  border-radius: 12px;
  background: var(--panel);
  border: 1px solid var(--stroke);
}
.input input{
  width:100%; border:0; outline:0;
  background: transparent;
  color: var(--text);
  font-size: 14px;
  font-family: var(--sans);
}
.input input::placeholder{ color: var(--muted2) }
.chips{ display:flex; flex-wrap:wrap; gap: 6px }
.chipbtn{
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--stroke);
  background: var(--panel);
  color: var(--muted);
  cursor:pointer;
  font-weight:500;
  font-size: 12px;
  letter-spacing: 0;
  transition: 180ms ease;
  font-family: var(--sans);
}
.chipbtn:hover{ color: var(--text); border-color: var(--stroke2) }
.chipbtn[aria-pressed="true"]{
  color: var(--ink);
  background: var(--yellow);
  border-color: var(--yellow-3);
}

.pub-list{ display:grid; gap: 12px }
.pub{
  border-radius: var(--radius-lg);
  background: var(--panel);
  border: 1px solid var(--stroke);
  box-shadow: var(--shadow2);
  padding: 16px 18px;
}
.pub .top{ display:flex; align-items:flex-start; justify-content:space-between; gap: 14px }
.pub h3{
  margin:0 0 6px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  line-height: 1.3;
  text-wrap: pretty;
}
.pub .byline{
  margin:0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}
.pub .byline strong{
  color: var(--text);
  background: linear-gradient(transparent 70%, rgba(23,21,15,0.14) 70%);
  padding: 0 2px;
}
.pub .venue{
  margin: 8px 0 0;
  color: var(--muted2);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.pub .links{ display:flex; flex-wrap:wrap; gap: 6px; margin-top: 12px }
.small-link{
  display:inline-flex; align-items:center; gap: 8px;
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid var(--stroke);
  background: rgba(23,21,15,0.045);
  color: var(--text);
  text-decoration:none;
  font-weight:600;
  font-size: 11px;
  letter-spacing: 0.06em;
  font-family: var(--mono);
  text-transform: uppercase;
}
.small-link:hover{ background: var(--yellow); color: var(--ink); border-color: var(--yellow-3) }
details.bib{
  margin-top: 10px;
  border-top: 1px dashed var(--stroke);
  padding-top: 10px;
}
details.bib summary{
  cursor:pointer;
  color: var(--muted);
  font-weight:600;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  list-style:none;
  font-family: var(--mono);
}
details.bib summary::-webkit-details-marker{ display:none }
details.bib summary::before{ content: "▸ "; color: var(--yellow) }
details.bib[open] summary::before{ content: "▾ " }
details.bib pre{
  margin: 10px 0 0;
  padding: 14px;
  border-radius: 10px;
  background: rgba(23,21,15,0.05);
  border: 1px solid var(--stroke);
  overflow:auto;
  color: var(--text);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
}

/* ---- Research: the statement that opens the page ----
   Set at hero scale and centred, so it is the first and only thing read before
   the rule hands over to the section proper. */
.research-hero{
  text-align: center;
  padding: clamp(16px, 5vh, 56px) 0 clamp(26px, 7vh, 68px);
}
.section h2.research-statement{
  margin: 0 auto;
  font-family: var(--display-condensed);
  font-weight: 400;
  text-transform: uppercase;
  /* The second line is the long one; the vw term keeps it inside the measure on
     a phone rather than running to both edges. */
  font-size: clamp(21px, 6.4vw, 76px);
  letter-spacing: 0.005em;
  line-height: 1.0;
}
/* Two deliberate lines rather than whatever the measure happens to give. */
.research-statement .line{ display: block }
.research-lede{
  margin: clamp(16px, 2.6vh, 28px) auto 0;
  /* Deliberately uncapped: the sentence is meant to sit on one line under the
     statement, so the section's own width is the only limit before it wraps. */
  max-width: none;
  color: var(--muted);
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.6;
}
/* "What we do" image, sitting under the lede and above the hairline. Same
   transparent-PNG treatment as the themes diagram, so it works day and night. */
.research-whatwedo{
  margin: clamp(20px, 3.4vh, 38px) auto 0;
  max-width: 900px;
  padding: 0;
}
.research-whatwedo img{ width: 100%; height: auto; display: block }

/* Hairline: closes the statement, opens the section. */
.rule{
  border: 0;
  border-top: 1px solid var(--stroke);
  margin: 0 0 clamp(26px, 5vh, 52px);
}

/* "How we do research" band: the rest of the Research page leaves the night
   ground for the cream one. Full-bleed like .page-intro, it takes back the day
   tokens and runs down into the footer, swallowing the section's bottom padding. */
.research-how{
  --bg: #F2EEE3;
  --stroke: rgba(23,21,15,0.13);
  --text: #17150F;
  --muted: #6F6A5C;
  --yellow-3: #211C10;
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  margin-top: clamp(10px, 3vh, 30px);
  margin-bottom: -78px;
  padding: clamp(48px, 8vh, 88px) 0 clamp(36px, 6vh, 64px);
  background: var(--bg);
  color: var(--text);
}
.research-how-inner{
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 var(--pad);
}
@media (max-width: 720px){
  .research-how{ margin-bottom: -56px }
}

/* The six capabilities, sitting above the themes diagram: label plus one line,
   no card chrome, so the diagram below stays the loudest thing on the page. */
.research-loop{
  gap: 18px 28px;
  margin: 0 0 clamp(20px, 4vh, 40px);
}
.loop-item{
  border-top: 1px solid var(--stroke);
  padding-top: 12px;
}
.loop-item h3{
  margin: 0 0 6px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
.loop-item p{
  margin: 0;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.6;
}

/* Research themes diagram. The PNG is transparent, so it sits on the page
   background and works in both day and night. */
.research-themes{
  margin: 14px auto 4px;
  max-width: 750px;
  padding: 0;
}
.research-themes img{ width: 100%; height: auto; display: block }

/* ===== People ===== */

/* Small group headings inside the People section: dot + label + hairline rule. */
.people-subhead{
  display: flex; align-items: center; gap: 10px;
  margin: 34px 0 14px;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--muted);
}
.people-subhead.first{ margin-top: 4px }
/* A subhead introducing its own heading: the pair travels together, so the
   heading drops the 64px gap the section rule gives a standalone second h2. */
.people-subhead + h2.display-title{ margin-top: 0 }
.people-subhead .dot{
  width: 6px; height: 6px; border-radius: 1px;
  background: var(--muted2);
  transform: rotate(45deg);
  flex: 0 0 auto;
}
.people-subhead .label{ flex: 0 0 auto }
.people-subhead .chev{ flex: 0 0 auto; margin-left: -3px }

/* Alumni heading doubles as the disclosure control. */
button.people-subhead{
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
  transition: color 160ms ease;
}
.alumni-toggle .chev{
  flex: 0 0 auto;
  font-size: 13px;
  line-height: 1;
  transition: transform 0.28s ease, color 0.18s ease;
}
.alumni-toggle:hover{ color: var(--text) }
.alumni-toggle:hover .dot{ background: var(--text) }
.alumni-toggle[aria-expanded="true"] .chev{ transform: rotate(180deg); color: var(--yellow) }
.alumni-toggle:focus-visible{ outline: 2px solid var(--yellow-3); outline-offset: 4px; border-radius: 4px }

.alumni-panel{
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.45s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.32s ease;
}
.alumni-panel.open{ max-height: 1600px; opacity: 1 }
/* Breathing room so hover lift / focus rings are not clipped by the panel. */
.alumni-panel .people-grid{ padding: 4px 2px 8px }

/* ---- Principal investigator: portrait + links left, bio right ---- */
.pi-block{
  display: grid;
  position: relative;
  cursor: pointer;
  /* 60% of two people-grid columns (gap included). */
  max-width: calc(((100% - 28px) / 3 * 2 + 14px) * 0.6);
  grid-template-columns: 144px minmax(0, 1fr);
  gap: 14px;
  padding: 16px;
  border-radius: var(--radius-lg);
  background: var(--panel);
  border: 1px solid var(--stroke);
  box-shadow: var(--shadow2);
  transition: transform 160ms ease, border-color 160ms ease,
              max-width 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              grid-template-columns 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              gap 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              padding 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
.pi-block:hover{ transform: translateY(-2px); border-color: var(--yellow-3) }
.pi-block.expanded{
  transform: translateY(-2px);
  border-color: var(--yellow-3);
  max-width: 960px;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 24px;
  padding: 20px;
}
.pi-media{ display: flex; flex-direction: column; gap: 8px; min-width: 0 }
/* The portrait takes whatever height the bio leaves, so the column never ends
   in dead space below the links — it simply grows as the card opens. */
.pi-photo{
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  max-height: 144px;
  border-radius: 20px;
  border: 1px solid var(--stroke);
  background: #E3DFD0;
  overflow: hidden;
  transition: max-height 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              border-radius 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
.pi-block.expanded .pi-photo{ max-height: 520px; border-radius: 18px }
.pi-photo img,
.pi-photo svg{ width: 100%; height: 100%; object-fit: cover; display: block }
.pi-info .links{ display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px }
.pi-info{ min-width: 0; display: flex; flex-direction: column }
.pi-block > .pi-info > .links{ margin-top: auto; padding-top: 8px }
.pi-info h3{
  margin: 0 0 4px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.005em;
  line-height: 1.3;
  transition: font-size 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
.pi-block.expanded .pi-info h3{ font-size: 20px; letter-spacing: -0.01em }
.pi-info .pi-role{ color: var(--text); font-size: 12.5px }
.pi-block.expanded .pi-info .pi-role{ font-size: 13px }
.pi-info .pi-lede{
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
  max-height: 200px;
  opacity: 1;
  overflow: hidden;
  transition: max-height 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.30s cubic-bezier(0.4, 0, 0.2, 1),
              margin-top 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
.pi-block.expanded .pi-info .pi-lede{ max-height: 0; opacity: 0; margin-top: 0 }
.pi-info p{
  margin: 0 0 10px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.65;
}
.pi-info .extra p:last-child{ margin-bottom: 0 }

.people-grid{
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 14px;
}

.person{
  display:flex; gap: 14px;
  padding: 16px;
  border-radius: var(--radius-lg);
  background: var(--panel);
  border: 1px solid var(--stroke);
  box-shadow: var(--shadow2);
  transition: transform 160ms ease, border-color 160ms ease;
  min-width: 0;
  overflow: hidden;
  cursor: pointer;
  position: relative;
}
.person:hover{ transform: translateY(-2px); border-color: var(--yellow-3) }
/* Small envelope beside a name — opens the visitor's mail client. Sized in em
   so it tracks whatever heading it sits in. */
.email-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: -0.15em;
  margin-left: 0.45em;
  width: 1.35em;
  height: 1.35em;
  border-radius: 6px;
  color: var(--muted);
  text-decoration: none;
  flex: 0 0 auto;
  transition: color 160ms ease, background 160ms ease;
}
.email-icon svg{ width: 0.9em; height: 0.9em; display: block }
.email-icon:hover{ background: var(--yellow); color: var(--ink) }
.email-icon:focus-visible{ outline: 2px solid var(--yellow-3); outline-offset: 2px }
.person .photo{
  width: 144px; height: 144px;
  border-radius: 20px;
  border: 1px solid var(--stroke);
  flex: 0 0 auto;
  overflow: hidden;
  background: #E3DFD0;
  transition: width 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              height 0.42s cubic-bezier(0.4, 0, 0.2, 1),
              border-radius 0.42s ease;
}
.person .photo svg,
.person .photo img{ width: 100%; height: 100%; object-fit: cover; display: block }
.person.lead .photo{ background: #E3DFD0 }
.person .info{ min-width: 0; flex: 1; display: flex; flex-direction: column }
.person > .info > .links{ margin-top: auto; padding-top: 8px }
/* Keep a long name and its symbol clear of the expand chevron. */
.person .info h3{ padding-right: 16px }
.person h3{
  margin:0 0 4px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.005em;
  line-height: 1.3;
}
.person p{
  margin:0;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.person .links{ margin-top: 8px; display:flex; gap: 6px; flex-wrap:wrap }
/* The single research line the collapsed card keeps; everything else — supervision,
   degrees, links — lives in .extra and appears on expansion. */
.person .person-q{ margin-top: 6px; color: var(--text) }
.person .extra p{ margin-top: 0 }
.person .extra p + p{ margin-top: 6px }
.person .links a,
.pi-info .links a,
.ds-card .links a{
  color: var(--text); text-decoration:none;
  font-family: var(--mono);
  font-size: 10.5px;
  /* Explicit, so a pill is the same height wherever it is nested. */
  line-height: 1.35;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 5px 9px;
  border-radius: 999px;
  border: 1px solid var(--stroke);
  background: rgba(23,21,15,0.045);
}
.person .links a:hover,
.pi-info .links a:hover,
.ds-card .links a:hover{ background: var(--yellow); color: var(--ink); border-color: var(--yellow-3) }
.software-grid .links a{ padding-inline: 4px; letter-spacing: 0.02em; white-space: nowrap }
/* Thesis card layout */
.thesis-grid.grid{ gap: 10px; align-items: start; }
.thesis-grid .person{ padding: 13px; }
.thesis-grid .person-header{
  display: flex; align-items: flex-start; gap: 8px;
  padding-right: 18px; /* clear the expand-icon */
}
.thesis-grid .person-header h3{
  flex: 1; margin: 0;
  font-size: 12.5px; line-height: 1.4;
}
.thesis-grid .meta{
  display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px;
}
.thesis-grid .tag{
  padding: 2px 6px; font-size: 10px; gap: 4px;
}
.thesis-grid .tag .pin{ width: 5px; height: 5px; }
.thesis-grid .person.expanded .extra{ margin-top: 10px; }
.thesis-grid .extra p{ font-size: 12.5px; }

.thesis-pdf{
  display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0;
  color: var(--muted); text-decoration: none;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em;
  padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--stroke);
  background: rgba(23,21,15,0.045);
  transition: background 0.18s, color 0.18s, border-color 0.18s;
  white-space: nowrap;
}
.thesis-pdf:hover{ background: var(--yellow); color: var(--ink); border-color: var(--yellow-3) }
.person .expand-icon,
.pub-card .expand-icon,
.ds-card .expand-icon,
.project-card .expand-icon,
.pi-block .expand-icon{
  position: absolute;
  top: 14px; right: 14px;
  font-size: 13px;
  color: var(--muted);
  line-height: 1;
  pointer-events: none;
  transition: transform 0.28s ease, color 0.18s ease;
  font-family: var(--sans);
}
.person.expanded .expand-icon,
.pub-card.expanded .expand-icon,
.ds-card.expanded .expand-icon,
.project-card.expanded .expand-icon,
.pi-block.expanded .expand-icon{ transform: rotate(180deg); color: var(--yellow); }
.person .extra,
.ds-card .extra,
.project-card .extra,
.pi-block .extra{
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  margin-top: 0;
  transition: max-height 0.42s ease, opacity 0.30s ease, margin-top 0.30s ease;
}
.person.expanded .extra,
.ds-card.expanded .extra{
  max-height: 400px;
  opacity: 1;
  margin-top: 8px;
}
/* The full bio is long, and longer again once the column narrows. */
.pi-block.expanded .extra{ max-height: 1000px; opacity: 1; margin-top: 12px }
.person.expanded{
  border-color: var(--yellow-3);
  transform: translateY(-2px);
  align-items: flex-start;
}
/* Photos start large; opening a card shrinks the photo back to make room for
   the details. */
.person.expanded .photo{
  width: 72px;
  height: 72px;
  border-radius: 16px;
}

/* Alumni cards: four per row, one notch smaller than the team cards */
.alumni-grid{ grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px }
.alumni-grid .person{ padding: 13px; gap: 11px }
/* Starts at the size a team card's photo shrinks to when opened, and grows ~5%
   when the alumni card opens */
.alumni-grid .person .photo{ width: 72px; height: 72px; border-radius: 16px }
.alumni-grid .person.expanded .photo{ width: 76px; height: 76px; border-radius: 17px }
.alumni-grid .person h3{ font-size: 13px }
.alumni-grid .person p{ font-size: 11.5px }
.alumni-grid .person .links{ margin-top: 7px }
.alumni-grid .person .links a{ font-size: 10px; padding: 4px 8px }

/* ---- Robots & experimental systems: photo-led cards ---- */
.robot-grid{ grid-template-columns: repeat(4, minmax(0, 1fr)) }
/* The photo runs to the card edge, so the padding moves onto the copy below it. */
.robot-card{ padding: 0; overflow: hidden }
.robot-card > *:not(.robot-photo):not(.expand-icon){ margin-left: 18px; margin-right: 18px }
.robot-card .ds-head{ margin-top: 14px }
.robot-card .links{ margin-bottom: 18px }
.robot-card .extra{ margin-bottom: 0 }
/* The copy reaches ~340px in the narrowest four-column layout; the extra room
   over the shared 400px keeps the closing UCT credit from being clipped where
   the fallback fonts set wider. */
.robot-card.expanded .extra{ max-height: 520px; margin-bottom: 18px }
/* No links row on these cards, so the copy carries the bottom padding itself. */
.robot-card:not(.expanded) .ds-sub{ margin-bottom: 18px }
.robot-photo{
  /* Square, so every card in the row is the same height. A portrait source is
     shown whole inside it (see .is-uncropped) rather than making the frame taller. */
  aspect-ratio: 1 / 1;
  background: #E3DFD0;
  border-bottom: 1px solid var(--stroke);
  overflow: hidden;
}
.robot-photo img{ width: 100%; height: 100%; object-fit: cover; display: block }
/* Some subjects run the full height of their frame — Dima's tail is the whole
   point of the robot — so those are shown whole, on the mat, rather than cropped. */
.robot-photo.is-uncropped img{ object-fit: contain }
/* Until the photograph lands, a neutral tile rather than a broken image. */
.robot-photo.is-empty{
  background:
    repeating-linear-gradient(45deg, rgba(23,21,15,0.05) 0 6px, transparent 6px 12px),
    #E3DFD0;
}
.robot-photo.is-empty img{ display: none }
html[data-theme="night"] .robot-photo{ background: #26251F }
/* The chevron sits over the photo, which may be a dark image or the pale
   placeholder tile, so it carries its own backdrop rather than relying on either. */
.robot-card .expand-icon{
  top: 10px; right: 10px;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: 999px;
  color: #F6F2E6;
  background: rgba(23,21,15,0.5);
  backdrop-filter: blur(4px);
}
.robot-card.expanded .expand-icon{ color: var(--accent-yellow) }

/* ===== Join ===== */
/* Stands in for the vacancy cards while nothing is open. */
.vacancy-note{
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 18px 20px;
  border-radius: var(--radius-lg);
  border: 1px dashed var(--stroke2);
  background: var(--panel);
}
.vacancy-dot{
  width: 9px; height: 9px; border-radius: 999px;
  background: var(--muted2);
  flex: 0 0 auto;
  margin-top: 7px;
  box-shadow: 0 0 0 5px rgba(23,21,15,0.06);
}
html[data-theme="night"] .vacancy-dot{ box-shadow: 0 0 0 5px rgba(245,243,236,0.07) }
.vacancy-note h3{
  margin: 0 0 6px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
}
.vacancy-note p{ margin: 0; color: var(--muted); font-size: 14px; line-height: 1.6; max-width: 88ch }
.vacancy-note a{
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--stroke2);
  text-underline-offset: 3px;
}
.vacancy-note a:hover{ text-decoration-color: var(--yellow) }

/* ===== News =====
   A compact feed at the foot of the Home page: date and line in one row, the
   whole list in a fixed-height scroller so it cannot dominate the page. */
.news-scroll{
  /* Sized to about four entries; anything past that scrolls, so the feed can
     grow without the section growing with it. */
  max-height: 220px;
  overflow-y: auto;
  margin-top: 18px;
  border: 1px solid var(--stroke);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow2);
  /* Firefox */
  scrollbar-width: thin;
  scrollbar-color: var(--stroke2) transparent;
}
.news-scroll:focus-visible{ outline: 2px solid var(--yellow-3); outline-offset: 3px }
.news-scroll::-webkit-scrollbar{ width: 11px }
.news-scroll::-webkit-scrollbar-track{ background: transparent }
.news-scroll::-webkit-scrollbar-thumb{
  background: var(--stroke2);
  border-radius: 999px;
  /* Transparent border inset via background-clip keeps the thumb slim. */
  border: 3px solid transparent;
  background-clip: content-box;
}
.news{ margin: 0; padding: 0; list-style: none }
.news-item{
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: 16px;
  align-items: baseline;
  padding: 11px 18px;
  border-bottom: 1px solid var(--stroke);
}
.news-item:last-child{ border-bottom: 0 }
.news-item time{
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted2);
  white-space: nowrap;
}
.news-item p{
  margin: 0;
  /* Uncapped: each entry runs the full width of the panel. */
  max-width: none;
  color: var(--muted);
  line-height: 1.5;
  font-size: 13.5px;
}
.news-item p strong{ color: var(--text); font-weight: 700 }

@media (max-width: 560px){
  /* The date moves above the line rather than squeezing the text column. */
  .news-item{ grid-template-columns: minmax(0, 1fr); gap: 4px; padding: 12px 16px }
}

/* ===== Contact ===== */
.contact-grid{
  display:grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 16px;
}
/* The photo fills whatever height the details card sets, rather than driving it. */
/* Embedded map fills its card the way the photo does. */
.contact-map{ padding: 0; min-height: 320px }
.contact-map::after{ display: none }
.contact-map iframe{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ===== Contact form ===== */
.contact-form{ display: grid; gap: 12px; margin-top: 14px }
.contact-form .field{ display: grid; gap: 5px; min-width: 0 }
.contact-form label{
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.contact-form input,
.contact-form select,
.contact-form textarea{
  width: 100%;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--stroke);
  background: var(--panel);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.45;
  outline: 0;
  transition: border-color 180ms ease, background 180ms ease;
}
.contact-form textarea{ resize: vertical; min-height: 88px }
.contact-form select{
  /* Keep the native arrow but match the surrounding type. */
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 16px center, right 11px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 32px;
}
.contact-form input:focus,
.contact-form select:focus,
.contact-form textarea:focus{
  border-color: var(--stroke2);
  background: var(--panel2);
}
.contact-form input::placeholder,
.contact-form textarea::placeholder{ color: var(--muted2) }
.form-actions{ display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px }
.form-actions .btn{ font-size: 13px; padding: 11px 14px }
.form-note{
  margin: 0;
  min-height: 18px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.form-note.is-error{ color: var(--text) }

/* Photo gallery: drag the image sideways, or use the arrows. */
.contact-gallery{ padding: 0; min-height: 280px; overflow: hidden }
.contact-gallery::after{ display: none }
.contact-gallery:focus-visible{ outline: 2px solid var(--yellow-3); outline-offset: 2px }
.gallery-track{
  position: absolute;
  inset: 0;
  display: flex;
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
  cursor: grab;
  /* Horizontal drags belong to the gallery, vertical ones scroll the page. */
  touch-action: pan-y;
}
.gallery-track.is-dragging{ cursor: grabbing; transition: none }
.gallery-track img{
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}
.gallery-nav{
  position: absolute;
  top: 50%;
  z-index: 2;
  transform: translateY(-50%);
  width: 34px; height: 34px;
  display: grid; place-items: center;
  padding: 0;
  border-radius: 999px;
  border: 1px solid rgba(246,242,230,0.45);
  background: rgba(23,21,15,0.42);
  backdrop-filter: blur(6px);
  color: #F6F2E6;
  cursor: pointer;
  opacity: 0.72;
  transition: opacity 200ms ease, background 200ms ease;
}
.contact-gallery:hover .gallery-nav{ opacity: 1 }
.gallery-nav:hover{ background: rgba(23,21,15,0.72) }
.gallery-nav:focus-visible{ opacity: 1; outline: 2px solid var(--accent-yellow); outline-offset: 2px }
.gallery-nav svg{ width: 15px; height: 15px }
.gallery-nav.prev{ left: 10px }
.gallery-nav.next{ right: 10px }
.list{
  margin: 10px 0 0; padding: 0; list-style: none;
  display:grid; gap: 14px;
}
.list li{
  display:flex; gap: 12px; align-items:flex-start;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
}
.list li svg{ flex: 0 0 auto; margin-top: 2px; color: var(--yellow) }
.list b{ color: var(--text); font-weight: 700 }
/* There is no global anchor style, so inline links in the address block set
   their own: they keep the list's colour and read as links by the rule. */
.list a{
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--stroke2);
  text-underline-offset: 3px;
  transition: color 0.18s ease, text-decoration-color 0.18s ease;
}
.list a:hover,
.list a:focus-visible{ color: var(--text); text-decoration-color: var(--yellow) }
.code{
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid var(--stroke);
  background: rgba(23,21,15,0.05);
  padding: 5px 10px;
  border-radius: 999px;
  color: var(--text);
  display:inline-flex; align-items:center; gap: 8px;
}

.toast{
  position:fixed; left:50%; bottom: 22px;
  transform: translateX(-50%);
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--ink);
  border:1px solid var(--yellow-3);
  color: var(--yellow);
  font-family: var(--mono);
  font-weight:600;
  font-size: 12px;
  letter-spacing: 0.06em;
  opacity:0; pointer-events:none;
  transition: opacity 220ms ease, transform 220ms ease;
}
.toast.show{ opacity:1; transform: translateX(-50%) translateY(-6px) }

/* ===== Footer =====
   Column panel — navigation, social, address, email — over a rule, with the
   copyright line beneath it. No lockups here: the mark lives in the header. */
.footer{
  padding-block: 0 40px;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 13.5px;
  letter-spacing: 0;
  line-height: 1.6;
}
/* The footer stays on the cream ground on every page, including the night-mode
   Research page: it takes back the day tokens and paints the ground edge to
   edge past the .wrap column. */
html[data-theme="night"] .footer{
  --bg: #F2EEE3;
  --stroke: rgba(23,21,15,0.13);
  --text: #17150F;
  --muted: #6F6A5C;
  --yellow-3: #211C10;
  background: var(--bg);
  box-shadow: 0 0 0 100vmax var(--bg);
  clip-path: inset(0 -100vmax -100vmax);
}
html[data-theme="night"] .footer-cols{ border-top-color: transparent }
/* The top rule lives here rather than on .footer so it spans the text column,
   not the gutter around it, and lines up with the .footer-base rule below. */
.footer-cols{
  display: grid;
  grid-template-columns: 1.5fr 1fr 1.5fr 1.2fr;
  gap: 30px 32px;
  align-items: start;
  padding-top: 44px;
  border-top: 1px solid var(--stroke);
}
.footer-col h2{
  margin: 0 0 14px;
  color: var(--text);
  font-family: var(--sans);
  font-weight: 500;
  font-size: 14.5px;
  letter-spacing: 0;
  line-height: 1.3;
}
.footer-list{ margin: 0; padding: 0; list-style: none }
.footer-list li{ margin: 0 0 9px }
.footer-col p{ margin: 0; max-width: 34ch }
.footer-col a{
  color: var(--muted);
  text-decoration: none;
  transition: color 160ms ease;
}
.footer-col a:hover,
.footer-col a[aria-current="page"]{ color: var(--text) }
.footer-col a:focus-visible{ outline: 2px solid var(--yellow-3); outline-offset: 3px; border-radius: 4px }
/* Eight links read better as two short columns than one tall one. */
.footer-nav{ columns: 2; column-gap: 26px }
.footer-nav li{ break-inside: avoid }

.footer-base{
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  align-items: center;
  justify-content: space-between;
  margin-top: 38px;
  padding-top: 20px;
  border-top: 1px solid var(--stroke);
  font-size: 12.5px;
}
.footer-base-links{ display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: center }
.footer-base a{
  color: var(--muted);
  text-decoration: none;
  transition: color 160ms ease;
}
.footer-base a:hover{ color: var(--text) }
.footer-base a:focus-visible{ outline: 2px solid var(--yellow-3); outline-offset: 3px; border-radius: 4px }

@media (max-width: 900px){
  .footer-cols{ grid-template-columns: 1fr 1fr }
}
@media (max-width: 560px){
  .footer-cols{ grid-template-columns: 1fr; gap: 26px }
  .footer-base{ justify-content: flex-start }
}

/* ===== Small screens ===== */
@media (max-width: 768px){
  /* The desktop line breaks re-wrap badly at this width — let the text flow */
  .hero-title br{ display: none }
  .hero-title{ font-size: clamp(32px, 9vw, 46px) }
  .hero-desc{ font-size: 15px; max-width: 34ch }
  .hero-btn{ padding: 15px 24px; font-size: 12px }
  .brand{ height: 44px }
  .brand-glow{ height: 136px; margin: -42px -36px -50px -29px }
  .brand-ink{ --lock: 136px }
}

/* Nav collapses to the menu button once the six links stop fitting */
@media (max-width: 900px){
  .nav{
    position:fixed; left: var(--pad); right: var(--pad); top: calc(var(--header-h, 86px) + 6px);
    padding: 12px;
    border-radius: 16px;
    background: rgba(244,240,229,0.98);
    border: 1px solid var(--stroke);
    box-shadow: var(--shadow);
    display:none; flex-direction:column; align-items:stretch;
    gap: 4px;
    white-space: normal;
  }
  /* The dropdown is a cream panel even over the hero or a dark opening band,
     so keep its links dark in both. */
  .nav a,
  .header.is-over-hero .nav a,
  .header.is-over-dark .nav a{ padding: 12px 14px; color: var(--text); opacity: 0.8 }
  .nav a:hover,
  .header.is-over-hero .nav a:hover,
  .header.is-over-dark .nav a:hover,
  .header.is-over-hero .nav a[aria-current="page"],
  .header.is-over-dark .nav a[aria-current="page"]{ color: var(--text); opacity: 1 }
  .nav.open{ display:flex }
  .menu-btn{ display:grid }
  html[data-theme="night"] .nav{ background: rgba(24,24,21,0.98) }
  html[data-theme="night"] .header.is-over-light .nav{ background: rgba(244,240,229,0.98) }
}

@media (max-width: 1100px){
  .alumni-grid{ grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ds-grid{ grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .pub-featured{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 980px){
  .contact-grid{ grid-template-columns: 1fr }
  .contact-map{ min-height: 260px }
  .grid.cols-3{ grid-template-columns: repeat(2, minmax(0,1fr)) }
  .people-grid{ grid-template-columns: 1fr 1fr; }
  .alumni-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pi-block{ max-width: 100% }
  .pi-block.expanded{ grid-template-columns: 180px minmax(0, 1fr) }
}
@media (max-width: 720px){
  .ds-card--media.expanded{ grid-column: auto; grid-template-columns: minmax(0, 1fr) }
  .ds-card--media.expanded .ds-media{ position: static; min-height: 0 }
  .ds-card--media.expanded .ds-media-row{ position: static }
  .ds-card--media.expanded .ds-media figure{ aspect-ratio: 16 / 9; height: auto }
  .header-inner{ padding-block: 10px }
  .section{ padding-block: 56px }
  .grid.cols-3, .grid.cols-2{ grid-template-columns: 1fr }
  .people-grid{ grid-template-columns: 1fr; }
  .alumni-grid{ grid-template-columns: 1fr; }
  .pub-featured{ grid-template-columns: 1fr; }
  .ds-grid{ grid-template-columns: 1fr; }
  .contact-gallery{ min-height: 220px }
  .pi-block{ max-width: 100% }
  .pi-block.expanded{ grid-template-columns: 1fr; gap: 16px; padding: 16px }
  .pi-block.expanded .pi-media{ max-width: 200px }
  .pi-block.expanded .pi-photo{ max-height: 200px }
  .pi-info h3{ font-size: 18px }
  .pi-block{ grid-template-columns: 112px minmax(0, 1fr) }
  .pi-photo{ max-height: 112px }
  .person .photo{ width: 112px; height: 112px; border-radius: 18px }
}

/* Motion preferences */
@media (prefers-reduced-motion: reduce){
  *{scroll-behavior:auto !important; transition:none !important; animation:none !important}
}
