/* ==========================================================================
   Sina Jamalzadegan, Ph.D. - personal academic site
   Single stylesheet, no external dependencies (fast + reliable on Pages).
   Accent color matches the CV LaTeX theme: RGB(0,70,140).
   ========================================================================== */

:root {
  --accent:      #00468c;
  --accent-soft: #e8f0f9;
  --bg:          #ffffff;
  --bg-alt:      #f7f8fa;
  --fg:          #17191d;
  --fg-muted:    #5b6169;
  --fg-faint:    #878d95;
  --rule:        #e3e6ea;
  --pill-bg:     #eef3f9;

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, ui-serif, serif;
  --sans:  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --wrap: 820px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --accent:      #7cb2f0;
    --accent-soft: #16283d;
    --bg:          #101215;
    --bg-alt:      #16191d;
    --fg:          #e6e8eb;
    --fg-muted:    #9aa1aa;
    --fg-faint:    #737a83;
    --rule:        #262a30;
    --pill-bg:     #1c2229;
  }
}

:root[data-theme="dark"] {
  --accent:      #7cb2f0;
  --accent-soft: #16283d;
  --bg:          #101215;
  --bg-alt:      #16191d;
  --fg:          #e6e8eb;
  --fg-muted:    #9aa1aa;
  --fg-faint:    #737a83;
  --rule:        #262a30;
  --pill-bg:     #1c2229;
}

/* --------------------------------------------------------- base */

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; scroll-padding-top: 72px; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { font-family: var(--serif); font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; }

.skip {
  position: absolute; left: -9999px;
  background: var(--accent); color: #fff; padding: 10px 16px; z-index: 100;
}
.skip:focus { left: 12px; top: 12px; }

/* --------------------------------------------------------- nav */

.nav {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--rule);
}

.nav-inner {
  display: flex; align-items: center; gap: 20px;
  height: 58px;
}

.nav-brand {
  font-family: var(--serif);
  font-size: 1.02rem; font-weight: 600;
  color: var(--fg); white-space: nowrap;
}
.nav-brand:hover { text-decoration: none; color: var(--accent); }

.nav nav {
  display: flex; gap: 20px; margin-left: auto;
  overflow-x: auto; scrollbar-width: none;
}
.nav nav::-webkit-scrollbar { display: none; }

.nav nav a {
  font-size: 0.85rem; color: var(--fg-muted); white-space: nowrap;
  padding: 4px 0; border-bottom: 2px solid transparent;
}
.nav nav a:hover { color: var(--fg); text-decoration: none; }
.nav nav a.active { color: var(--accent); border-bottom-color: var(--accent); }

.theme-btn {
  flex: none;
  background: none; border: 1px solid var(--rule); border-radius: 7px;
  color: var(--fg-muted); font-size: 0.9rem;
  width: 32px; height: 32px; cursor: pointer; line-height: 1;
}
.theme-btn:hover { color: var(--accent); border-color: var(--accent); }

/* --------------------------------------------------------- hero */

.hero { padding: 76px 0 60px; }

/* --------------------------------------------------------- margin panels

   Two drawings in the outer margins: chemistry on the left (skeletal rings, a
   reaction coordinate with one barrier, a sparse weight matrix), the measurement
   side on the right (a line that splits and never rejoins, a classified
   population, three peaks).

   position: fixed, not absolute. The alternative is a tall image repeated down
   the page, which shows a seam at every repeat unless the artwork is authored to
   tile. Fixed panels sit in the viewport margins and stay put while the content
   scrolls, so they read as a frame around the page rather than wallpaper behind
   it, and the artwork never has to tile at all.

   z-index 0 against the content's implicit stacking keeps them behind
   everything, and pointer-events: none means they cannot swallow a click.
   ------------------------------------------------------------------------ */

.rb {
  position: fixed; top: 0; z-index: 0;
  width: 230px; height: 100vh; height: 100dvh;
  pointer-events: none; user-select: none;
  color: var(--accent); opacity: 0.16;
}
.rb-left  { left: 0;  -webkit-mask-image: linear-gradient(to left,  transparent 0, #000 55%);
                              mask-image: linear-gradient(to left,  transparent 0, #000 55%); }
.rb-right { right: 0; -webkit-mask-image: linear-gradient(to right, transparent 0, #000 55%);
                              mask-image: linear-gradient(to right, transparent 0, #000 55%); }

.rb-k { fill: none; stroke: currentColor; stroke-width: 1; vector-effect: non-scaling-stroke; }

/* The accent is lighter on the dark ground, so it needs a touch more to read. */
:root[data-theme="dark"] .rb { opacity: 0.24; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .rb { opacity: 0.24; } }

/* The sticky nav and every section paint over the panels rather than under them. */
.nav, .section, .hero, .footer { position: relative; z-index: 1; }
.section.alt, .footer, .nav { background-color: var(--bg); }
.section.alt { background-color: var(--bg-alt); }

/* Below this there are no margins left and the panels would sit under the text. */
@media (max-width: 1240px) { .rb { display: none; } }
@media print { .rb { display: none; } }

.eyebrow {
  margin: 0 0 14px;
  font-size: 0.78rem; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--accent); font-weight: 600;
}

.hero h1 {
  margin: 0 0 22px;
  /* Sized to keep "Sina Jamalzadegan, Ph.D." on a single line. The old 3.35rem
     ceiling overflowed the text column once the portrait took its share, so the
     Ph.D. dropped to a second line. nowrap makes the break impossible and the
     clamp shrinks the type instead, down to a floor that still fits at 375px. */
  font-size: clamp(1.5rem, 4.4vw, 2.85rem);
  white-space: nowrap;
}
.hero h1 .phd { color: var(--fg-faint); font-weight: 400; }

.lede { margin: 0 0 16px; font-size: 1.12rem; color: var(--fg); }
.lede strong { color: var(--accent); font-weight: 600; }
.lede-sub { margin: 0 0 18px; font-size: 1rem; color: var(--fg-muted); }

/* The one finding on the page, as opposed to a count. Set apart from the prose so it
   is read rather than skimmed, but quieter than the lede so it does not compete. */
.lede-proof {
  /* Carries the gap the CTA row used to hold: the hero ends on this line and the
     stat rule follows directly, so it needs the breathing room itself. */
  margin: 0 0 42px; padding-left: 14px;
  border-left: 2px solid var(--accent);
  font-size: 0.98rem; line-height: 1.55; color: var(--fg-muted); max-width: 62ch;
}

/* --------------------------------------------------------- buttons */

.cta { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 42px; }

.btn {
  display: inline-block;
  padding: 9px 17px;
  border: 1px solid var(--rule); border-radius: 8px;
  font-size: 0.9rem; font-weight: 500;
  color: var(--fg); background: var(--bg);
  transition: border-color .15s, color .15s, background .15s, transform .15s;
}
.btn:hover {
  text-decoration: none;
  border-color: var(--accent); color: var(--accent);
  transform: translateY(-1px);
}
.btn-primary {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.btn-primary:hover { color: #fff; opacity: .9; }

/* On dark backgrounds the accent is light, so filled buttons need dark text. */
:root[data-theme="dark"] .btn-primary,
:root[data-theme="dark"] .btn-primary:hover { color: #0d1117; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .btn-primary,
  :root:not([data-theme="light"]) .btn-primary:hover { color: #0d1117; }
}

/* --------------------------------------------------------- stats */

/* Two rows carrying different kinds of evidence: research output and the standing
   that comes with it, then recognition, teaching, and training. The split is what
   keeps eight numbers from reading as one undifferentiated brag. The second row is
   set a step quieter so the eye lands on the first. */

.stats { margin: 0; padding: 22px 0 0; border-top: 1px solid var(--rule); }

.stats-row {
  list-style: none; margin: 0; padding: 0;
  /* One column per tile, whatever the count. A fixed four left the fifth tile
     orphaned on a row of its own, which reads as an afterthought. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 18px;
}
.stats-row + .stats-row {
  margin-top: 16px; padding-top: 16px;
  border-top: 1px solid var(--rule);
}
.stats-row li { display: flex; flex-direction: column; gap: 2px; }
/* Every tile links to the page that proves it, without looking like a link at rest. */
.stats-row li > a { display: flex; flex-direction: column; gap: 2px; color: inherit; text-decoration: none; }
.stats-row li > a:hover { text-decoration: none; }
.stats-row li > a:hover .stat-n { color: var(--accent); }
.stats-row li > a:hover .stat-l { color: var(--fg); }
.stats-row li > a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 3px; }

.stat-n {
  font-family: var(--serif); font-size: 1.75rem; font-weight: 600;
  color: var(--accent); line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* The one linked number. No underline at rest: a rule under a single numeral in a
   row of eight reads as a stray artefact rather than as a link. The affordance
   arrives on hover, where it is unambiguous. */
.stat-n a { color: inherit; text-decoration: none; border-bottom: 0; }
.stat-n a:hover { text-decoration: underline; text-underline-offset: 3px; }

.stat-l { font-size: 0.78rem; color: var(--fg-muted); line-height: 1.35; }

/* Row two is a different kind of evidence, so it steps down in size and drops out of
   the accent. A colour token rather than an opacity: compositing the accent against the
   section background shifts its hue between themes and can fall under the contrast floor
   in dark mode, where the accent is already light. */
.stats-row-2 .stat-n { font-size: 1.35rem; color: var(--fg); }
.stats-row-2 .stat-l { font-size: 0.75rem; }

/* --------------------------------------------------------- sections */

.section { padding: 62px 0; border-top: 1px solid var(--rule); }
.section.alt { background: var(--bg-alt); }

.section h2 {
  margin: 0 0 12px;
  font-size: clamp(1.6rem, 3.6vw, 2rem);
}

.section-intro { margin: 0 0 34px; color: var(--fg-muted); font-size: 1rem; max-width: 62ch; }

h3.sub {
  margin: 34px 0 14px;
  font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase;
  font-family: var(--sans); font-weight: 700; color: var(--fg-faint);
}
h3.sub:first-child { margin-top: 0; }

/* --------------------------------------------------------- research thrusts */

.thrust {
  display: grid; grid-template-columns: 58px 1fr; gap: 6px;
  padding: 26px 0;
  border-top: 1px solid var(--rule);
}
.thrust:first-of-type { border-top: none; padding-top: 0; }

.thrust-num {
  font-family: var(--serif); font-size: 1.3rem; font-weight: 600;
  color: var(--accent); opacity: .45; padding-top: 2px;
}

.thrust-body h3 { margin: 0 0 10px; font-size: 1.3rem; }
.thrust-body p { margin: 0 0 12px; color: var(--fg-muted); }

.thrust-tags { display: flex; flex-wrap: wrap; gap: 7px; margin: 14px 0 0 !important; }
.thrust-tags span {
  font-size: 0.72rem; font-weight: 500;
  padding: 3px 10px; border-radius: 20px;
  background: var(--pill-bg); color: var(--fg-muted);
}

/* --------------------------------------------------------- methods strip */

.methods {
  margin: 0;
  font-size: 0.92rem; line-height: 1.7;
  color: var(--fg-muted);
  padding: 16px 18px;
  background: var(--pill-bg);
  border-radius: 10px;
  border-left: 3px solid var(--accent);
}
.methods b { color: var(--fg); font-weight: 600; }

/* --------------------------------------------------------- education timeline */

ol.timeline { list-style: none; margin: 0; padding: 0; }

ol.timeline li {
  display: grid; grid-template-columns: 104px 1fr; gap: 4px 18px;
  padding: 18px 0;
  border-top: 1px solid var(--rule);
}
ol.timeline li:first-child { border-top: none; padding-top: 0; }

.tl-when {
  font-family: var(--serif);
  font-size: 0.92rem; color: var(--accent);
  padding-top: 2px; white-space: nowrap;
}
.tl-what { font-size: 0.97rem; color: var(--fg); }
.tl-what b { font-weight: 600; }
.tl-detail {
  display: block; margin-top: 5px;
  font-size: 0.88rem; color: var(--fg-muted); line-height: 1.6;
}
.tl-detail b { color: var(--fg); }

/* Advisor callout. Names are the thing a faculty reader scans for, so they get
   their own block rather than being buried at the end of a sentence. */
.advisors {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  margin: 12px 0 0;
  padding: 9px 14px;
  font-size: 0.86rem; color: var(--fg);
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-radius: 9px;
}
.advisors-label {
  font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--accent);
  padding-right: 10px;
  border-right: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
}

/* --------------------------------------------------------- publications */

.legend { display: block; margin-top: 6px; font-size: 0.85rem; }

ol.pubs { margin: 0; padding: 0 0 0 0; list-style: none; counter-reset: pub; }

ol.pubs li {
  position: relative;
  padding: 16px 0 16px 44px;
  border-bottom: 1px solid var(--rule);
  font-size: 0.94rem; line-height: 1.6;
  color: var(--fg-muted);
}
ol.pubs li:last-child { border-bottom: none; }

ol.pubs li::before {
  counter-increment: pub;
  content: counter(pub);
  position: absolute; left: 0; top: 17px;
  width: 28px; text-align: right;
  font-family: var(--serif); font-size: 0.9rem;
  color: var(--fg-faint);
}

ol.pubs b { color: var(--fg); font-weight: 700; }
ol.pubs i { color: var(--fg); font-style: italic; }
.venue { font-weight: 600; color: var(--fg-muted); }

.doi {
  display: inline-block; margin-left: 4px;
  font-size: 0.76rem; font-weight: 600; letter-spacing: .03em;
  padding: 1px 7px; border: 1px solid var(--rule); border-radius: 5px;
}
.doi:hover { text-decoration: none; border-color: var(--accent); background: var(--accent-soft); }

.pill {
  display: inline-block; margin-left: 5px;
  font-size: 0.7rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 20px;
}
.pill-cover  { background: var(--accent); color: #fff; }
:root[data-theme="dark"] .pill-cover { color: #0d1117; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .pill-cover { color: #0d1117; }
}
.pill-choice { background: var(--pill-bg); color: var(--accent); border: 1px solid var(--accent); }

.press {
  display: block; margin-top: 5px;
  font-size: 0.8rem; color: var(--fg-faint); font-style: italic;
}

/* --------------------------------------------------------- lists + columns */

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 46px; }
.two-col p { margin: 0 0 14px; color: var(--fg-muted); font-size: 0.95rem; }
.two-col b { color: var(--fg); }

ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain li {
  padding: 9px 0; border-bottom: 1px solid var(--rule);
  font-size: 0.9rem; color: var(--fg-muted); line-height: 1.5;
}
ul.plain li:last-child { border-bottom: none; }
ul.plain b { color: var(--fg); font-weight: 600; }

.muted { color: var(--fg-faint); }
.small { font-size: 0.85rem; }

/* --------------------------------------------------------- figures + covers

   Image support. Drop files into assets/ and use these classes.

   .covers    a row of journal cover images (portrait, ~3:4)
   .figure    a single wide research figure with a caption
   Both are theme-aware and scroll rather than overflowing on narrow screens.
   ------------------------------------------------------------------------ */

.covers {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 22px;
}
.covers li { display: flex; flex-direction: column; gap: 9px; }

.covers img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  border-radius: 8px; border: 1px solid var(--rule);
  background: var(--bg-alt);
  transition: transform .18s ease, box-shadow .18s ease;
}
.covers a:hover img {
  transform: translateY(-3px);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .13);
}
:root[data-theme="dark"] .covers a:hover img { box-shadow: 0 10px 26px rgba(0, 0, 0, .5); }

.covers .cap {
  font-size: 0.8rem; line-height: 1.45; color: var(--fg-muted);
}
.covers .cap b { display: block; color: var(--fg); font-weight: 600; }

figure.figure { margin: 0; }
.thrust-body figure.figure { margin-top: 16px; }
figure.figure img {
  width: 100%; height: auto; display: block;
  border-radius: 10px; border: 1px solid var(--rule);
}
figure.figure figcaption {
  margin-top: 9px;
  font-size: 0.84rem; line-height: 1.55; color: var(--fg-muted);
}

/* Dark-mode images: journal covers and figures are authored on white, so a
   slight dim keeps them from glaring against a dark ground. */
:root[data-theme="dark"] .covers img,
:root[data-theme="dark"] .reel-slide img,
:root[data-theme="dark"] figure.figure img { filter: brightness(.92); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .covers img,
  :root:not([data-theme="light"]) .reel-slide img,
  :root:not([data-theme="light"]) figure.figure img { filter: brightness(.92); }
}

/* --------------------------------------------------------- gallery */

ul.gallery {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 22px;
}
ul.gallery li { display: flex; flex-direction: column; gap: 8px; }

ul.gallery img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 8px; border: 1px solid var(--rule);
  background: var(--bg-alt);
}
ul.gallery .cap { font-size: 0.79rem; line-height: 1.5; color: var(--fg-muted); }

figure.figure-inline { margin-top: 12px; max-width: 420px; }
figure.figure-inline figcaption { font-size: 0.8rem; }

/* --------------------------------------------------------- press list */

ul.press-list { list-style: none; margin: 0; padding: 0; }

ul.press-list > li {
  display: grid; grid-template-columns: 160px 1fr; gap: 18px;
  padding: 18px 0 18px 16px;
  border-top: 1px solid var(--rule);
  border-left: 2px solid transparent;
  transition: border-left-color .15s, padding-left .15s;
}
ul.press-list > li:first-child { border-top: none; padding-top: 0; }
ul.press-list > li:hover { border-left-color: var(--accent); }

/* An article without a thumbnail should not leave a 160px hole. */
ul.press-list > li:not(:has(.press-thumb)) { grid-template-columns: 1fr; }

.press-thumb { display: block; }
.press-thumb img {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  border-radius: 7px; border: 1px solid var(--rule);
  transition: transform .18s ease;
}
.press-thumb:hover img { transform: scale(1.02); }

@media (max-width: 700px) {
  ul.press-list > li { grid-template-columns: 1fr; gap: 10px; }
  .press-thumb img { aspect-ratio: 16 / 9; }
}

.press-meta {
  display: block; margin-bottom: 4px;
  font-size: 0.74rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--fg-faint); font-weight: 600;
}
ul.press-list > li > a {
  font-family: var(--serif); font-size: 1.08rem; line-height: 1.35;
  font-weight: 600;
}
.press-note {
  display: block; margin-top: 6px;
  font-size: 0.88rem; color: var(--fg-muted); line-height: 1.6;
}
.press-note a { font-size: inherit; font-family: inherit; font-weight: 500; }

/* --------------------------------------------------------- portrait (optional) */

.hero-grid { display: grid; grid-template-columns: 1fr auto; gap: 34px; align-items: start; }

.portrait {
  width: 168px; height: 168px;
  border-radius: 12px; object-fit: cover;
  border: 1px solid var(--rule);
  flex: none;
}

@media (max-width: 700px) {
  .hero-grid { grid-template-columns: 1fr; gap: 20px; }
  .portrait { width: 120px; height: 120px; }
}

/* --------------------------------------------------------- news feed

   The landing-page convention on strong academic sites: dated, newest first.
   It is what a returning visitor checks and the clearest signal of momentum.
   ------------------------------------------------------------------------ */

ol.news { list-style: none; margin: 0; padding: 0; }

ol.news li {
  display: grid; grid-template-columns: 92px 1fr; gap: 4px 20px;
  padding: 13px 0;
  border-top: 1px solid var(--rule);
  font-size: 0.93rem; line-height: 1.6;
}
ol.news li:first-child { border-top: none; padding-top: 0; }

.news-when {
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.03em;
  color: var(--accent); text-transform: uppercase;
  padding-top: 3px; white-space: nowrap;
}
.news-what { color: var(--fg-muted); }
.news-what b { color: var(--fg); font-weight: 600; }

.more { margin: 26px 0 0; font-size: 0.9rem; }
.more a { font-weight: 600; }
.more a::after { content: " \2192"; }


/* --------------------------------------------------------- research reel

   The landing page's research section is a showcase that doubles as the index into
   research.html. The rail names all four threads at once and links into each one, so
   nothing is hidden behind the rotation; only the evidence above the rail changes.
   That is why there are no dots, no chevrons, no overlay text, and no full-bleed band:
   the four thread names ARE the position indicator, the navigation, and the route to
   every figure. The frame is set the way research.html already sets figure.figure, so
   this reads as a figure the site knows how to typeset rather than a banner bolted on.
   ------------------------------------------------------------------------ */

.reel {
  --reel-dwell: 8000ms;   /* read by the script, so the sweep and the timer cannot drift */
  --reel-fade: 320ms;
}

/* All four figures share one grid cell rather than being absolutely positioned, so the
   block is as tall as its tallest caption from the very first paint and never jumps
   between turns. Combined with aspect-ratio below, this component contributes no CLS. */
.reel-stage { display: grid; }

.reel-slide {
  grid-area: 1 / 1;
  margin: 0;
  /* visibility, not opacity or an offscreen transform: it removes the inactive slide
     from the tab order AND from the accessibility tree, and unlike display: none it is
     transitionable, so it solves the focus-trap and the crossfade in one declaration.
     The delay flips it at the end of the fade out and immediately on the way in. */
  visibility: hidden;
  transition: visibility 0s linear var(--reel-fade);
}
.reel-slide.is-current {
  visibility: visible;
  z-index: 1;              /* grid items honour z-index without being positioned */
  transition: visibility 0s;
}

.reel-slide img {
  display: block; width: 100%; height: auto;
  /* Reserves the box before the first JPEG decodes. All five figures sit within 0.03%
     of this ratio (1600/893 = 1.7917, fig-crispr 1500/837 = 1.7921), so one value fits
     them all with no cropping. contain rather than cover: a labelled schematic must
     letterbox rather than lose a label if a future figure comes in at another ratio. */
  aspect-ratio: 1600 / 893;
  object-fit: contain;
  background: var(--bg-alt);
  border: 1px solid var(--rule); border-radius: 10px;
  /* The outgoing figure holds at full opacity and snaps off at the end rather than
     fading out, so the incoming one dissolves over a solid picture and the page
     background never shows through the middle of the transition. */
  opacity: 0; transition: opacity 0s linear var(--reel-fade);
}
.reel-slide.is-current img { opacity: 1; transition: opacity var(--reel-fade) ease; }

/* Captions are not cross-faded. Two blocks of dense type dissolving through each other
   are unreadable for the whole transition, so both flip at its midpoint instead: the
   old one is gone before the new one arrives. */
.reel-slide figcaption {
  margin-top: 11px;
  font-size: 0.85rem; line-height: 1.55; color: var(--fg-muted);
  opacity: 0;
  transition: opacity 0s linear calc(var(--reel-fade) / 2);
}
.reel-slide.is-current figcaption { opacity: 1; }
.reel-slide figcaption b { color: var(--fg); font-weight: 600; }

.reel-n {
  margin-right: 8px;
  font-family: var(--serif); font-weight: 600; color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* Foot. The stop control is first in the DOM, so it is the first tab stop inside the
   carousel as the APG pattern requires, and ordered last so the four thread names stay
   the loudest thing in the row. */
.reel-foot { display: flex; align-items: flex-start; gap: 20px; margin-top: 20px; }

.reel-rail {
  order: 1; flex: 1 1 auto;
  /* One column per thread, whatever the count, all equal width. auto-fit with a
     zero minimum was generating dozens of phantom zero-width tracks; auto-flow
     column creates exactly as many as there are children. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0 14px;
}

.reel-tab {
  position: relative; display: block;
  padding: 11px 0 2px;                 /* well over the 24px minimum target */
  border-top: 1px solid var(--rule);
  color: var(--fg-muted); text-decoration: none;
  transition: color .16s, border-top-color .16s;
}
.reel-tab:hover { text-decoration: none; color: var(--fg); border-top-color: var(--fg-faint); }
/* The current thread is marked by its own tinted rule, not by the timer fill, so a
   stopped reel still says plainly which figure is on screen. */
.reel-tab.is-current {
  color: var(--fg);
  border-top-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.reel-tab.is-current .reel-tab-n { color: var(--accent); }

.reel-tab-n {
  display: block; margin-bottom: 3px;
  font-family: var(--serif); font-size: 0.86rem; font-weight: 600;
  color: var(--fg-muted);              /* a token, not accent at low opacity, so contrast
                                          holds deterministically in both themes */
  font-variant-numeric: tabular-nums;
  transition: color .16s;
}
.reel-tab-t { display: block; font-size: 0.78rem; line-height: 1.35; }

/* The dwell timer, drawn as the current thread's own rule filling in. This is the one
   thing a default carousel never does, and it is what changes the feel from "the page
   is moving on its own" to "the page told me it was about to change". It costs one 2px
   accent rule that was already there as the tab's top border. It freezes visibly on
   hover and on focus, which is how a reader learns the reel stopped without a tooltip
   saying so. */
.reel-tab-fill {
  position: absolute; left: 0; top: -1px;
  width: 100%; height: 2px;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left center;
}
@keyframes reel-sweep { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.reel.is-running .reel-tab.is-current .reel-tab-fill {
  animation: reel-sweep var(--reel-dwell) linear forwards;
}
.reel.is-held .reel-tab.is-current .reel-tab-fill { animation-play-state: paused; }

/* The stop control. WCAG 2.2 SC 2.2.2 requires a real mechanism for anything that
   advances on its own, and the five-second exception never applies because it refers to
   how long the motion lasts in total, not the interval between turns. So this cannot be
   left out, but it can look like a footnote. Ships with the hidden attribute and is
   revealed by script, so a page without JavaScript never shows a dead control. */
.reel-hold {
  order: 2; flex: none; margin-left: auto;
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 24px; padding: 11px 2px 2px;   /* baseline-aligns with the tab labels */
  background: none; border: 0; cursor: pointer;
  font-family: var(--sans); font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  /* --fg-muted, not --fg-faint. At 0.7rem this is small text, so it needs 4.5:1, and
     faint measures 3.35:1 on the light ground and 4.32:1 on the dark one. Uppercase
     and letter-spaced already keeps it reading as a footnote next to the thread names. */
  color: var(--fg-muted);
  transition: color .16s;
}
/* inline-flex would otherwise beat the user-agent [hidden] rule. */
.reel-hold[hidden] { display: none; }
.reel-hold:hover { color: var(--accent); }

/* Two bars when running, one triangle when stopped, drawn with borders so the control
   needs no SVG, no icon font, and no second request. */
.reel-glyph {
  width: 9px; height: 10px; flex: none;
  border-left: 3px solid currentColor; border-right: 3px solid currentColor;
}
.reel-hold.is-stopped .reel-glyph {
  width: 0; height: 0;
  border: 5px solid transparent; border-left: 8px solid currentColor; border-right: 0;
}

/* The stage has no overflow: hidden, so nothing here can clip a focus ring. */
.reel-tab:focus-visible,
.reel-hold:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

@media (max-width: 700px) {
  .reel-foot { flex-wrap: wrap; gap: 12px; margin-top: 16px; }
  /* Reset the auto flow as well as the template, or the desktop rule keeps all
     tabs on one line and the rail overflows the viewport. */
  .reel-rail { flex: 1 1 100%; grid-auto-flow: row;
               grid-template-columns: repeat(2, 1fr); gap: 0 16px; }
  .reel-rail > .reel-tab:nth-child(n + 3) { margin-top: 12px; }
  .reel-hold { margin-left: 0; padding-top: 0; }
  .reel-slide figcaption { font-size: 0.82rem; }
}

/* prefers-reduced-motion is not a Level A or AA requirement, but a search committee
   member with a vestibular disorder is a real person. The script separately starts the
   reel held, so it does not advance at all unless the visitor asks it to; this only
   removes the animation. Matching specificity rather than !important. */
@media (prefers-reduced-motion: reduce) {
  .reel-slide, .reel-slide.is-current,
  .reel-slide img, .reel-slide.is-current img,
  .reel-slide figcaption,
  .reel-tab, .reel-tab-n, .reel-hold { transition: none; }
  .reel.is-running .reel-tab.is-current .reel-tab-fill { animation: none; }
}

/* Printing a stack of hidden slides would silently drop three of the four threads. */
@media print {
  .reel-hold, .reel-rail { display: none; }
  .reel-stage { display: block; }
  .reel-slide { grid-area: auto; visibility: visible; margin-bottom: 14pt; break-inside: avoid; }
  .reel-slide img, .reel-slide figcaption { opacity: 1; }
}

/* --------------------------------------------------------- certifications

   Grouped by program rather than one row per certificate. Twenty-five bullets of
   Coursera courses reads as padding; six programs with their component courses named
   underneath reads as a training record, and it lets a reader see that two of them
   are multi-course specializations rather than wondering.
   ------------------------------------------------------------------------ */

ul.certs { list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; }
ul.certs > li { display: flex; flex-direction: column; gap: 4px; }

.cert-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px;
}
.cert-head b { font-size: 1rem; }
.cert-by {
  font-size: 0.74rem; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--fg-faint); font-weight: 600; white-space: nowrap;
}
.cert-detail { font-size: 0.87rem; line-height: 1.55; color: var(--fg-muted); }

/* --------------------------------------------------------- contact invitations

   Two named asks rather than one vague "get in touch". A reader scanning should be
   able to tell in a second which one applies to them, so each opens with a bolded
   label. No boxes or icons: the same restraint as the rest of the page.
   ------------------------------------------------------------------------ */

ul.invites {
  list-style: none; margin: 0 0 30px; padding: 0;
  display: grid; gap: 18px; max-width: 68ch;
}
ul.invites > li {
  padding-left: 15px;
  border-left: 2px solid color-mix(in srgb, var(--accent) 40%, transparent);
  font-size: 0.95rem; line-height: 1.6; color: var(--fg-muted);
}
ul.invites > li b { color: var(--fg); font-weight: 600; }

/* --------------------------------------------------------- contact + footer */

.contact { text-align: left; }
.contact .cta { margin-bottom: 16px; }
.addr { margin: 0; }

.footer {
  padding: 26px 0 34px;
  border-top: 1px solid var(--rule);
  font-size: 0.82rem; color: var(--fg-faint);
}
.footer p { margin: 0; }
.footer-inner {
  display: flex; flex-wrap: wrap; gap: 10px 24px;
  align-items: center; justify-content: space-between;
}
.footer-links { display: flex; flex-wrap: wrap; gap: 18px; }

@media (max-width: 700px) {
  ol.news li { grid-template-columns: 1fr; gap: 2px; }
  .footer-inner { justify-content: flex-start; }
}

/* --------------------------------------------------------- responsive */

@media (max-width: 700px) {
  body { font-size: 16px; }
  .hero { padding: 48px 0 40px; }
  .section { padding: 46px 0; }
  .stats-row { grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); gap: 16px; }
  /* Once the rows are two columns the eight items are four rows of two, and a rule
     after the first four no longer marks a boundary anyone can perceive. */
  .stats-row + .stats-row { border-top: none; padding-top: 0; }
  .two-col { grid-template-columns: 1fr; gap: 8px; }
  .thrust { grid-template-columns: 1fr; gap: 0; }
  ol.timeline li { grid-template-columns: 1fr; gap: 2px; }
  .tl-when { font-size: 0.82rem; }
  .thrust-num { font-size: 0.95rem; margin-bottom: 4px; }
  /* The nav does not fit on one line on a phone. It used to overflow-scroll inside a
     120px strip, which put CV and News off screen with nothing to say they were there.
     Give the links their own full-width row instead. */
  .nav-inner { flex-wrap: wrap; height: auto; padding-top: 10px; padding-bottom: 8px; gap: 8px 14px; }
  .nav-brand { font-size: 0.95rem; margin-right: auto; }
  /* Six items no longer fit on one line at 375px, and space-between on an
     overflowing row pushed Contact off screen. Wrap instead, so the row becomes
     two lines rather than hiding an entry. overflow-x back to visible: the base
     rule scrolls horizontally, which would defeat the wrap. */
  .nav nav {
    order: 3; width: 100%; margin-left: 0;
    flex-wrap: wrap; justify-content: flex-start;
    gap: 2px 15px; overflow-x: visible;
  }
  .nav nav a { font-size: 0.8rem; }
  .nav nav { gap: 14px; }
  .nav nav a { font-size: 0.8rem; }
  ol.pubs li { padding-left: 30px; }
  ol.pubs li::before { width: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn { transition: none; }
}

@media print {
  .nav, .theme-btn, .cta, .footer { display: none; }
  body { font-size: 11pt; color: #000; background: #fff; }
  .section { padding: 12pt 0; border-top: none; }
  .section.alt { background: none; }
  a { color: #000; }
}
