/* ===========================================================================
   cv.css — the CV.

   One idea: the page writes itself the way a board gets written on. Nothing
   arrives already there — every rule draws, every entry resolves out of chalk
   dust, every number is counted onto the board. All of it driven by the
   scroll position, so the reader is the one doing the writing, and all of it
   off under prefers-reduced-motion.

   The visual system is the LaTeX CV's: TeX Gyre Pagella, Lato, bronze rules,
   pill badges, a date gutter, the stat strip.
   =========================================================================== */

:root { --measure: 72ch; }

/* ------------------------------------------------------------------ shell */
.page {
  position: relative;
  min-block-size: 100svh;
  background:
    radial-gradient(58% 40% at 30% 10%, rgb(214 226 218 / .075), transparent 66%),
    radial-gradient(46% 32% at 80% 46%, rgb(214 226 218 / .05), transparent 68%),
    radial-gradient(68% 44% at 40% 90%, rgb(214 226 218 / .04), transparent 70%),
    radial-gradient(130% 104% at 48% 38%, transparent 56%, rgb(0 0 0 / .34) 100%),
    linear-gradient(170deg, var(--board-lift), var(--board) 44%, var(--board-deep));
}
.page::after {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 40;
  opacity: .24; mix-blend-mode: overlay;
  background-image: var(--grain); background-size: 220px 220px;
}

/* him, standing at the board — an absence of chalk, not a photograph */
.standing {
  position: fixed;
  inset-block-end: 0; inset-inline-end: clamp(-3rem, 2vw, 4rem);
  block-size: 88vh; aspect-ratio: 1000 / 4476;
  z-index: 0; pointer-events: none;
  color: #050807; opacity: .6;
  filter: blur(13px);
  mask-image: linear-gradient(to bottom, transparent, #000 20%, #000 82%, transparent),
              linear-gradient(to right, transparent, #000 24%, #000 100%);
  mask-composite: intersect;
  translate: 0 var(--drift, 0px);
}
.standing svg { display: block; inline-size: 100%; block-size: 100%; }

.wrap {
  position: relative; z-index: 2;
  margin-inline: auto;
  max-inline-size: calc(var(--measure) + 12rem);
  padding-inline: clamp(1.3rem, 5vw, 5rem);
  padding-block: clamp(3rem, 9vh, 7rem) 7rem;
}

/* --------------------------------------------------------------- masthead */
.back { margin: 0 0 2.2rem; display: flex; gap: 2rem; flex-wrap: wrap; }
.back .alt { color: var(--chalk-soft); }
.back .alt:hover { color: var(--chalk-ochre); }
.back a {
  font-family: var(--sans); font-size: .7rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--chalk-faint); text-decoration: none;
}
.back a:hover { color: var(--chalk-soft); }

.masthead { max-inline-size: var(--measure); }
.masthead h1 {
  margin: 0; font-weight: 400; line-height: .95;
  font-size: clamp(2rem, 5.6vw, 3.9rem);
  color: var(--chalk);
  filter: url(#chalk-text) drop-shadow(0 0 12px rgb(230 227 216 / .16));
}
.masthead h1 span { font-family: var(--sans); display: inline-block; }
.masthead h1 .given  { font-weight: 300; letter-spacing: .14em; }
.masthead h1 .family { font-weight: 700; letter-spacing: .14em; }

.rule { block-size: 1.4px; background: var(--chalk-ochre); opacity: .85; margin-block: 1rem 1.3rem;
        transform-origin: left center; }

.ident { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 1.4rem 3rem; align-items: start; }
.ident .role { font-style: italic; font-size: 1.05rem; line-height: 1.45; color: var(--chalk-soft);
               margin: 0 0 .9rem; max-inline-size: 54ch; }
.ident .now  { margin: 0; font-size: .92rem; line-height: 1.6; color: var(--chalk-soft); }
.ident .now strong { color: var(--chalk); font-weight: 700; }
.ident .contact { display: flex; flex-direction: column; gap: .34rem;
                  font-family: var(--sans); font-size: .76rem; }
.ident .contact a { color: var(--chalk-soft); text-decoration: none;
                    border-block-end: 1px solid transparent; width: fit-content; }
.ident .contact a:hover { color: var(--chalk); border-block-end-color: var(--chalk-faint); }

.keywords { margin-block-start: 1.2rem; font-family: var(--sans); font-size: .78rem;
            line-height: 1.7; color: var(--chalk-faint); }
.keywords b { color: var(--chalk-ochre); font-weight: 400; letter-spacing: var(--tracking-label);
              text-transform: uppercase; font-size: .66rem; display: block; margin-block-end: .4rem; }

.stats {
  margin-block: 2.2rem; max-inline-size: var(--measure);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  border-block: 1.2px solid var(--chalk-ochre);
  background: rgb(232 236 230 / .035);
}
.stat { text-align: center; padding: .95rem .4rem; border-inline-start: 1px solid rgb(216 178 94 / .3); }
.stat:first-child { border-inline-start: 0; }
.stat b { display: block; font-family: var(--sans); font-weight: 700; font-size: 1.6rem;
          color: var(--chalk); line-height: 1; font-variant-numeric: tabular-nums; }
.stat span { display: block; font-family: var(--sans); font-size: .58rem; letter-spacing: .16em;
             text-transform: uppercase; color: var(--chalk-soft); margin-block-start: .45rem; }

.summary {
  border-inline-start: 2.5px solid var(--chalk-ochre);
  padding-inline-start: 1.1rem; margin-block: 2rem;
  font-size: 1rem; line-height: 1.68; color: var(--chalk); max-inline-size: var(--measure);
}
.summary b { font-weight: 700; }

.download {
  display: inline-block; margin-block: .5rem 1rem;
  font-family: var(--sans); font-size: .78rem; letter-spacing: .04em;
  color: var(--chalk); text-decoration: none;
  border-block-end: 1px solid rgb(230 227 216 / .35); padding-block-end: 2px;
  transition: color .3s var(--ease-out), border-color .3s var(--ease-out);
}
.download:hover { color: var(--chalk-ochre); border-block-end-color: var(--chalk-ochre); }

/* -------------------------------------------------------------- sections */
.sec { margin-block-start: 3.6rem; max-inline-size: var(--measure); }
.sec > h2 {
  position: sticky; inset-block-start: 0; z-index: 6;
  margin: 0; padding-block: 1.1rem .55rem;
  font-family: var(--sans); font-weight: 700;
  font-size: .82rem; letter-spacing: .13em; text-transform: uppercase; color: var(--chalk);
  /* opaque behind the type and the rule, fading only below them — at 62% the
     entries scrolled visibly through the heading */
  background: linear-gradient(180deg, var(--board) 0 88%, rgb(18 22 20 / .85) 96%, transparent);
}
.sec > h2 .bar {
  display: block; block-size: 1.1px; margin-block-start: .5rem;
  background: var(--chalk-ochre); opacity: .8; transform-origin: left center;
}

/* ---------------------------------------------------------------- entries */
.entry {
  display: grid; grid-template-columns: 6.4rem minmax(0, 1fr); gap: 0 1.1rem;
  padding-block: .68rem;
  border-block-end: 1px solid rgb(255 255 255 / .05);
}
.entry .when {
  font-family: var(--sans); font-size: .72rem; color: var(--chalk-ochre);
  padding-block-start: .2rem; letter-spacing: .02em;
  transition: color .3s var(--ease-out), translate .5s var(--ease-out);
}
.entry h3 { margin: 0; font-size: 1rem; font-weight: 700; line-height: 1.36; color: var(--chalk); }
.entry h3 a { color: inherit; text-decoration: none;
              border-block-end: 1px solid rgb(216 178 94 / .3);
              transition: border-color .3s var(--ease-out); }
.entry .meta { margin: .2rem 0 0; font-size: .82rem; line-height: 1.55; color: var(--chalk-soft); }
.entry .desc { margin: .3rem 0 0; font-size: .82rem; font-style: italic; color: var(--chalk-faint); }

/* hover: press a little harder with the chalk */
.entry:hover .when { color: #f0c877; translate: -3px 0; }
.entry:hover h3 { color: #f4f1e6; text-shadow: 0 0 14px rgb(230 227 216 / .3); }
.entry:hover h3 a { border-block-end-color: var(--chalk-ochre); }
.entry:hover .meta { color: #b9beb5; }

/* badges, matching the pills in the PDF */
.badges { display: inline-flex; flex-wrap: wrap; gap: .3rem; margin-inline-start: .45rem; vertical-align: 2px; }
.badge {
  font-family: var(--sans); font-weight: 700; font-size: .55rem; letter-spacing: .09em;
  text-transform: uppercase; padding: .17rem .4rem; border-radius: 2.5px; white-space: nowrap;
}
.badge.letter, .badge.thesis, .badge.preprint,
.badge.in-prep, .badge.review-nature, .badge.review-science {
  border: .5px solid var(--chalk-faint); color: var(--chalk-faint);
}
.badge.in-prep, .badge.review-nature, .badge.review-science { font-style: italic; }
.badge.editors, .badge.media { background: var(--chalk-ochre); color: #171c19; }

/* meetings: the dense two-column list from the PDF */
.meetings { columns: 2; column-gap: 2.6rem; font-size: .8rem; margin-block-start: .4rem; }
.meetings div { break-inside: avoid; margin-block-end: .46rem; color: var(--chalk-soft);
                transition: color .3s var(--ease-out); }
.meetings div:hover { color: var(--chalk); }
.meetings b { color: var(--chalk-ochre); font-weight: 400; font-family: var(--sans); font-size: .72rem;
              margin-inline-end: .5rem; }

.skills .entry { grid-template-columns: 8.6rem minmax(0,1fr); }
.skills .when { color: var(--chalk-soft); }

.foot { margin-block-start: 4rem; font-size: .74rem; color: var(--chalk-faint); max-inline-size: 60ch; }
.foot a { color: var(--chalk-soft); }

/* ===========================================================================
   MOTION — the page writing itself
   =========================================================================== */
@media (prefers-reduced-motion: no-preference) {

  /* --- on load: the masthead resolves out of dust ----------------------- */
  @keyframes settle {
    from { opacity: 0; filter: blur(7px); translate: 0 10px; letter-spacing: .34em; }
    60%  { opacity: 1; }
    to   { opacity: 1; filter: blur(0);   translate: 0 0;    letter-spacing: .14em; }
  }
  @keyframes rise { from { opacity: 0; filter: blur(4px); translate: 0 12px; } }
  @keyframes draw { from { scale: 0 1; } }

  .masthead h1 .given  { animation: settle 1.5s var(--ease-out) .1s backwards; }
  .masthead h1 .family { animation: settle 1.5s var(--ease-out) .32s backwards; }
  .masthead .rule      { animation: draw 1.1s var(--ease-out) .7s backwards; }
  .ident, .stats, .summary, .download, .back {
    animation: rise 1s var(--ease-out) backwards;
  }
  .back    { animation-delay: .05s; }
  .ident   { animation-delay: .95s; }
  .stats   { animation-delay: 1.12s; }
  .summary { animation-delay: 1.3s; }
  .download{ animation-delay: 1.45s; }

  /* --- on scroll: everything else is written as you reach it ------------ */
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes chalk-in {
      from { opacity: 0; filter: blur(3.5px); translate: 0 16px; }
      to   { opacity: 1; filter: blur(0);     translate: 0 0; }
    }
    @keyframes bar-in  { from { scale: 0 1; opacity: 0; } to { scale: 1 1; opacity: .8; } }
    @keyframes head-in { from { opacity: 0; letter-spacing: .34em; } to { opacity: 1; letter-spacing: .13em; } }

    /* `cover`, not `entry`. The `entry` range for a 60px row spans only 60px
       of scroll, so the reveal is over before you have seen it. `cover` spans
       the whole time the element is anywhere in the viewport, so a slice of it
       is a real distance: these run while the row travels the lower quarter
       of the screen, and are finished well before it reaches reading height. */
    .entry, .meetings div {
      animation: chalk-in auto var(--ease-out) both;
      animation-timeline: view();
      animation-range: cover 0% cover 26%;
    }

    /* The headings are sticky, so they take their timeline from the section
       rather than from themselves. */
    .sec { view-timeline: --sec block; }
    .sec > h2 {
      animation: head-in auto var(--ease-out) both;
      animation-timeline: --sec;
      animation-range: cover 0% cover 10%;
    }
    .sec > h2 .bar {
      animation: bar-in auto var(--ease-out) both;
      animation-timeline: --sec;
      animation-range: cover 1% cover 14%;
    }
  }

  /* Fallback for browsers without scroll-driven animations: the same
     motion, triggered by an IntersectionObserver that adds .seen. */
  @supports not ((animation-timeline: view()) and (animation-range: entry)) {
    .js .entry, .js .meetings div, .js .sec > h2 {
      opacity: 0; filter: blur(3.5px); translate: 0 14px;
      transition: opacity .7s var(--ease-out), filter .7s var(--ease-out), translate .7s var(--ease-out);
    }
    .js .sec > h2 .bar { scale: 0 1; transform-origin: left center; transition: scale .8s var(--ease-out); }
    .js .seen { opacity: 1; filter: blur(0); translate: 0 0; }
    .js .seen .bar { scale: 1 1; }
  }
}

/* --------------------------------------------------------------- responsive */
@media (max-width: 900px) {
  .standing { block-size: 54vh; opacity: .34; inset-inline-end: -14%; }
  .ident { grid-template-columns: 1fr; }
  .stats { grid-auto-flow: row; grid-template-columns: repeat(3, 1fr); }
  .stat { padding: .65rem .3rem; border-inline-start: 1px solid rgb(216 178 94 / .22);
          border-block-start: 1px solid rgb(216 178 94 / .18); }
  .stat:nth-child(-n+3) { border-block-start: 0; }
  .stat:nth-child(3n+1) { border-inline-start: 0; }
  .stat b { font-size: 1.3rem; }
  .entry { grid-template-columns: 1fr; gap: .18rem; }
  .entry .when { padding-block-start: 0; }
  .skills .entry { grid-template-columns: 1fr; }
  .meetings { columns: 1; }
}

/* ------------------------------------------------------------------ print */
@media print {
  .page { background: #fff; }
  .page::after, .standing, .back, .download { display: none !important; }
  .wrap { padding: 0; max-inline-size: none; }
  body { color: #16263d; font-size: 10pt; }
  .masthead h1, .entry h3, .stat b { color: #16263d; filter: none; }
  .entry .meta, .entry .desc, .ident .now, .summary { color: #3e4a5e; }
  .entry { break-inside: avoid; border-block-end: 0; }
  .sec > h2 { position: static; background: none; color: #16263d; }
  * { animation: none !important; opacity: 1 !important; filter: none !important; translate: none !important; }
}
