/* ---------------------------------------------------------------
   Yujia Yuan — portfolio (v2)
   Fixed-size, vertically-compact layout. Blank gutters on wide
   screens; full responsive rearrangement at iPhone widths.
   --------------------------------------------------------------- */

:root {
  --bg: #f7f3e2;
  --bg-soft: #fbf8ed;
  --paper: #ffffff;
  --ink: #0d0e11;
  /* ----- Harmonized ink palette --------------------------------------
     Titles, body, and emphasis live on the same blue hue (~222°) at
     different saturation and lightness so they read as one coordinated
     two-tone ink, not as several unrelated darks. Both are pulled
     toward neutral so they sit quietly against the cream paper and
     never fight the warm orange accent.
     ------------------------------------------------------------------ */
  /* Headings + <strong> emphasis — clearly blue, mid-dark. Lifted from
     near-black so it reads as inked navy, not solemn black. */
  --ink-title: #2d3d65;
  --ink-2: #2a2c30;
  /* Body paragraphs — mid slate-blue, softer than title.
     Comfortable for sustained reading; visibly lighter so titles still
     anchor the page hierarchy. */
  --ink-body: #4f5570;
  --muted: #6f7279;
  --muted-2: #a3a59f;
  --rule: #ded7c6;
  --rule-soft: #ebe6d9;
  --rule-strong: #c2bba8;
  --accent: #a85327;
  --accent-soft: #c47148;
  --detail-cta: #5e6f93;
  --card-title: #414b63;

  /* The page renders inside a fixed-width column. Anything wider
     than --col-max is blank gutter on either side. */
  --col-max: 1080px;
  --col-pad: 28px;

  --serif: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* Display font for headings — Fraunces is an editorial serif with
     optical sizing. Gives titles a "research notebook" character that
     pairs cleanly with Inter's neutral body. */
  --display: "Fraunces", ui-serif, Georgia, "Times New Roman", serif;
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 72px;
  -webkit-text-size-adjust: 100%;
  /* iOS-style momentum scrolling. */
  -webkit-overflow-scrolling: touch;
  -webkit-font-smoothing: antialiased;
  /* Stop iOS rubber-band from competing with the JS-triggered
     smooth scroll on the cover. Without this, a flick at the cover
     can momentarily over-scroll past the page top and the smooth
     animation has to fight back. */
  overscroll-behavior-y: none;
  /* The cover ↔ About transition is handled in JS (animateScrollTo),
     not via CSS scroll-snap — scroll-snap kept feeling "stuck" mid-
     transition because mandatory mode tries to keep the scroll
     position at a snap point even mid-animation. JS gives a clean,
     decisive jump in both directions while leaving the rest of the
     page completely free to scroll. */
}
body {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: none;
}
/* Prefers-reduced-motion users: skip smooth scroll. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  color: var(--ink);
  background: var(--bg);
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "ss01", "cv11", "cv02";
  /* Prevent any inner element wider than viewport from creating a
     horizontal scrollbar — without it, sections appear off-center
     and a sliver of body background shows on the right on mobile.
     Use `clip` (not `hidden`) so `position: sticky` on the header
     keeps working — `overflow-x: hidden` would break sticky. */
  overflow-x: clip;
}
html { overflow-x: clip; }

img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
p { margin: 0; color: var(--ink-2); }

.skip-link {
  position: absolute;
  top: -100px;
  left: 16px;
  z-index: 30;
  padding: 10px 14px;
  color: var(--bg);
  background: var(--ink);
  border-radius: 4px;
  font-weight: 600;
}
.skip-link:focus { top: 14px; }

/* ----------------------- Header ----------------------- */

.site-header {
  /* Fixed, not sticky — when hidden the header should not occupy any
     space in the document flow, otherwise it leaves a cream "ghost bar"
     at the top of the cover splash. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  /* Hidden while the cover splash is showing — slides down into
     place once the user scrolls past the splash. */
  transform: translateY(-110%);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.32s ease, opacity 0.28s ease;
  /* Warm-paper frosted-glass tint that sits between the various
     section backgrounds (cream, paper-white, soft cream) so the
     header reads as one coherent surface no matter what's scrolling
     under it. Stronger blur + slight saturate gives a refined feel. */
  background: rgba(251, 247, 235, 0.85);
  border-bottom: 1px solid rgba(194, 187, 168, 0.55);
  backdrop-filter: blur(18px) saturate(1.08);
  -webkit-backdrop-filter: blur(18px) saturate(1.08);
  /* Faint inner highlight at the top edge — adds the glassy quality
     without being a heavy box-shadow. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  /* The padding trick keeps content inside the fixed column on wide screens
     while still letting the header background span the full width.
     Use 100% instead of 100vw to avoid scrollbar-width overflow on mobile. */
  padding: 12px max(var(--col-pad), calc((100% - var(--col-max)) / 2));
}

.site-header > .header-inner {
  display: contents;
}
/* Header appears once the user scrolls past the cover splash. */
body.past-cover .site-header {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}

.brand {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  /* Softened so the header reads as a quiet anchor, not a focal point. */
  color: rgba(13, 14, 17, 0.5);
  font-weight: 500;
  letter-spacing: -0.01em;
  font-size: 15.5px;
  transition: color 0.2s ease;
}
.brand:hover { color: rgba(13, 14, 17, 0.8); }

.brand-dot {
  width: 7px;
  height: 7px;
  background: rgba(13, 14, 17, 0.45);
  border-radius: 50%;
  display: inline-block;
  margin-right: 4px;
  position: relative;
  top: -1px;
}

.brand small {
  display: inline-block;
  margin-left: 12px;
  padding-left: 12px;
  border-left: 1px solid rgba(168, 83, 39, 0.18);
  color: rgba(13, 14, 17, 0.4);
  font-family: var(--mono);
  font-size: 15.5px;
  letter-spacing: -0.005em;
  text-transform: none;
  font-weight: 400;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
}

.site-nav a {
  position: relative;
  padding: 6px 13px;
  color: var(--muted);
  font-size: 15.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
  transition: color 0.18s ease;
}
.site-nav a:hover { color: var(--ink-title); }
.site-nav a.active { color: var(--ink-title); font-weight: 600; }
/* Underline lives on every nav link as a hidden ::after, then
   scales in with a smooth transform when .active is added. That
   lets the indicator animate cleanly between links instead of
   "popping" instantly between two stationary underlines. */
.site-nav a::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: -1px;
  height: 2px;
  background: var(--accent);
  border-radius: 1px;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.28s cubic-bezier(0.4, 0.7, 0.3, 1);
}
.site-nav a.active::after { transform: scaleX(1); }

/* ----------------------- Containers ----------------------- */

/* Sections are now full-width color blocks. Vertical padding lives on
   the section so the tint extends edge-to-edge; the inner wrapper
   carries the fixed column width so content lines up across sections. */
.section {
  width: 100%;
  padding: 32px 0;
}

.section-inner {
  width: 100%;
  max-width: var(--col-max);
  margin: 0 auto;
  padding: 0 var(--col-pad);
}

/* Tonal palette — matches the warm cream design language */
.section--paper { background: var(--paper); }
.section--soft  { background: var(--bg-soft); }
.section--bg    { background: var(--bg); }
.section--cream { background: #f1e9d4; }
.section--ink-soft { background: #ece5d2; }

/* ----------------------- Type ----------------------- */

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 500;
  letter-spacing: -0.018em;
  line-height: 1.18;
  /* Slightly lifted from pure ink — same dark presence, less solemn. */
  color: var(--ink-title);
}
/* Display serif on the real titles — h1 page heading, h2 section
   titles, h3 card titles. h4 stays in Inter because it's a small-caps
   meta label ("TOOLS & STACK", "CONTEXT") where a serif looks wrong. */
h1, h2, h3 {
  font-family: var(--display);
  font-feature-settings: "ss01", "ss02";
  letter-spacing: -0.012em;
}

h2.section-title {
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.022em;
  line-height: 1.18;
  max-width: 56ch;
}

@media (max-width: 640px) {
  h2.section-title { font-size: 24px; }
}

h3 {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.008em;
}

h4 {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.text-link {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  color: var(--ink);
  font-weight: 500;
  font-size: 13px;
  border-bottom: 1px solid var(--ink);
  padding-bottom: 1px;
  transition: color 0.18s ease, border-color 0.18s ease;
}
.text-link:hover { color: var(--accent); border-color: var(--accent); }
.text-link.subtle { color: var(--muted); border-color: var(--rule-strong); }
.text-link.subtle:hover { color: var(--ink); border-color: var(--ink); }
.text-link::after { content: "→"; font-family: var(--mono); font-size: 0.86em; position: relative; top: -1px; }
.text-link.external::after { content: "↗"; }

.section-heading {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  margin-bottom: 22px;
  padding-bottom: 0;
  border-bottom: none;
}

.section-heading .section-title {
  margin: 0;
  max-width: 64ch;
}

.section-heading .heading-side {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  color: var(--muted);
}

/* =====================================================================
   COVER — full-viewport paper splash with a technical scale sketch.
   ===================================================================== */

.cover {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  min-height: 480px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  box-sizing: border-box;
  background-color: #f6f1e8;
}

.cover-frame {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(42px, 5vh, 62px) clamp(28px, 4vw, 76px) 42px;
  box-sizing: border-box;
  transform: translateY(clamp(28px, 3.6vh, 36px));
}
.cover img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Editorial hero: warm sans welcome title, italic subtitle,
   hand-drawn illustration beneath. Stately and clear. */
.cover-frame {
  isolation: isolate;
}
.cover-head {
  position: relative;
  z-index: 2;
  text-align: center;
  width: 100%;
  max-width: 1260px;
  margin: 0 auto;
  padding: 0;
  flex: 0 0 auto;
}
.cover-title {
  font-family: var(--serif);
  font-size: clamp(38px, 4.1vw, 66px);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.08;
  color: rgba(45, 61, 101, 0.82);
  margin: 0 0 clamp(22px, 2.6vh, 30px);
}
.cover-title-line {
  display: inline;
}
.cover-tagline {
  font-family: "Brill", "Bodoni 72", "Bodoni 72 Display", Didot, "Bodoni MT", Georgia, serif;
  font-size: clamp(23px, 2.1vw, 36px);
  font-weight: 400;
  font-style: italic;
  line-height: 1.08;
  color: rgba(58, 72, 108, 0.74);
  margin: 0;
  text-align: center;
  flex: 0 0 auto;
}
.cover-tagline span {
  display: inline;
}
.cover-illustration {
  position: relative;
  z-index: 1;
  flex: 0 1 auto;
  min-height: 0;
  width: min(100%, 1840px);
  height: clamp(430px, 56vh, 560px);
  margin: 0;
  margin-top: clamp(18px, 2.6vh, 26px);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 1;
  pointer-events: none;
}
.cover-illustration-desktop {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.cover-illustration-desktop img {
  display: block;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  object-fit: contain;
  object-position: center;
  filter: none;
  mix-blend-mode: multiply;
}
.cover-mobile-image {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .cover-title,
  .cover-tagline,
  .cover-illustration {
    opacity: 1;
    animation: none;
    transform: none;
  }
}
/* Scroll hint: visible enough to invite movement, still soft enough
   to sit with the pencil-sketch hero. */
.scroll-hint {
  position: absolute;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 38px;
  padding: 0 14px 0 16px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: rgba(45, 61, 101, 0.82);
  background: rgba(255, 253, 244, 0.72);
  border: 1px solid rgba(45, 61, 101, 0.22);
  border-radius: 999px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 28px -18px rgba(45, 61, 101, 0.58), inset 0 1px 0 rgba(255,255,255,0.62);
  text-decoration: none;
  cursor: pointer;
  animation: scroll-hint-bounce 1.8s ease-in-out infinite;
  opacity: 0.92;
  transition: color 0.18s ease, background 0.18s ease, border-color 0.18s ease, opacity 0.18s ease, box-shadow 0.18s ease;
}
.scroll-hint span {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip: auto;
  white-space: nowrap;
}
.scroll-hint:hover {
  color: rgba(45, 61, 101, 0.96);
  background: rgba(255, 253, 244, 0.9);
  border-color: rgba(45, 61, 101, 0.34);
  box-shadow: 0 14px 34px -18px rgba(45, 61, 101, 0.72), inset 0 1px 0 rgba(255,255,255,0.7);
  opacity: 1;
}
.scroll-hint svg {
  display: block;
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}

@keyframes scroll-hint-bounce {
  0%, 100% { transform: translateX(-50%) translateY(0); opacity: 0.92; }
  50%      { transform: translateX(-50%) translateY(7px); opacity: 1; }
}

@media (max-width: 640px) {
  .cover {
    min-height: 100svh;
    height: auto;
  }
  .cover-frame {
    min-height: 100svh;
    padding: 32px 18px 58px;
    justify-content: center;
    transform: none;
  }
  .cover-head {
    max-width: 100%;
    order: 1;
  }
  .cover-title {
    display: inline-block;
    font-size: clamp(36px, 10vw, 42px);
    line-height: 0.98;
    margin-bottom: 0;
    text-align: center;
    white-space: normal;
  }
  .cover-title-line {
    display: block;
  }
  .cover-title-line::before {
    content: attr(data-mobile-text);
  }
  .cover-title-line {
    font-size: 0;
  }
  .cover-title-line::before {
    font-size: inherit;
  }
  .cover-title-greeting::before {
    display: block;
    font-size: clamp(22px, 6vw, 25px);
    font-weight: 500;
    line-height: 1.1;
    margin-bottom: 4px;
  }
  .cover-title-name::before {
    display: block;
    font-size: clamp(42px, 11.4vw, 48px);
    font-weight: 650;
    line-height: 0.96;
  }
  .cover-tagline {
    order: 3;
    max-width: 340px;
    margin: clamp(14px, 2.8svh, 22px) auto 0;
    font-size: clamp(18px, 5vw, 20px);
    line-height: 1.12;
    white-space: normal;
  }
  .cover-tagline span {
    display: block;
  }
  .cover-illustration {
    order: 2;
    flex: 0 0 auto;
    width: 100%;
    height: auto;
    margin-top: clamp(12px, 2.2svh, 20px);
    align-items: flex-start;
  }
  .cover-illustration-desktop {
    display: none;
  }
  .cover-mobile-image {
    display: flex;
    align-items: center;
    justify-content: center;
    width: min(100%, 340px);
    margin: 0 auto;
    mix-blend-mode: multiply;
  }
  .cover-mobile-image img {
    width: 100%;
    max-width: none;
    height: auto;
    max-height: none;
    object-fit: contain;
  }
  .scroll-hint {
    bottom: 14px;
    min-height: 34px;
    padding: 0 12px 0 14px;
    gap: 7px;
    font-size: 10.8px;
    letter-spacing: 0.12em;
  }
  .scroll-hint svg { width: 14px; height: 14px; }
}

/* =====================================================================
   ABOUT
   ===================================================================== */

.about { padding-top: 36px; padding-bottom: 36px; }

/* Two-column grid with explicit row tracks: heading lives in row 1
   on the left only; photo (left) and bio copy (right) share row 2,
   so the bio paragraphs start at the top of the photo, not above it. */
.about-grid {
  display: grid;
  grid-template-columns: 230px 1fr;
  grid-template-rows: auto 1fr;
  column-gap: 36px;
  row-gap: 14px;
  align-items: start;
}

.about-heading {
  grid-column: 1 / -1;
  grid-row: 1 / 2;
  justify-self: center;
  text-align: center;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.018em;
  text-transform: none;
  /* Matches the rest of the heading family — deep cool ink, low sat. */
  color: var(--ink-title);
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  white-space: nowrap;
}
.about-heading::after {
  content: none;
}

.about-photo {
  grid-column: 1 / 2;
  grid-row: 2 / 3;
  width: 100%;
  position: relative;
  /* Photo paper backing — gives a thin white margin around the print,
     like a real darkroom photo mounted on a research notebook. */
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 6px;
  box-shadow: 0 16px 32px -22px rgba(13, 14, 17, 0.28);
  /* isolation lets z-index of ::before sit behind without escaping
     the section. */
  isolation: isolate;
}
.about-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 60% 35%;
  filter: saturate(0.96);
  position: relative;
  z-index: 1;
}
/* A second cream-paper card peeks out from behind the photo,
   slightly rotated and offset — reads as "two prints clipped together
   on a research notebook page." */
.about-photo::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  border-radius: 6px;
  transform: translate(10px, 14px) rotate(-3.2deg);
  transform-origin: top left;
  z-index: -1;
  box-shadow: 0 10px 22px -16px rgba(13, 14, 17, 0.24);
}
/* A tiny third card behind the second one — adds depth, like a small
   stack of prints. Very subtle. */
.about-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 6px;
  transform: translate(-6px, 8px) rotate(2deg);
  transform-origin: top right;
  z-index: -2;
  box-shadow: 0 6px 14px -10px rgba(13, 14, 17, 0.18);
  opacity: 0.85;
}

.about-copy {
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  padding-top: 0;
}

.about-copy p {
  margin: 0 0 14px;
  color: var(--ink-body);
  font-size: 15px;
  line-height: 1.6;
}
.about-copy p strong { color: var(--ink-title); font-weight: 500; }

.about-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Desktop: pushed apart (was 36px) but still centered, so the row
     stays narrower than the bio text above. */
  gap: 64px;
  margin-top: 20px;
  flex-wrap: wrap;
}
/* Slider and icon row, both centered as a unit under the bio paragraphs. */
.about-actions .icon-row { margin-left: 0; }

.button-primary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  background: var(--ink);
  color: var(--bg);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  transition: background 0.18s ease;
}
.button-primary:hover { background: var(--accent); }

/* =====================================================================
   CV SLIDER — slide-to-confirm control that opens CV.pdf only after the
   user drags the handle across the track. Stops accidental clicks from
   navigating away from the page. Behaves on both touch and mouse.
   ===================================================================== */
.cv-slider {
  position: relative;
  width: 228px;
  height: 44px;
  background: var(--bg-soft);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
  flex-shrink: 0;
  /* iOS — disable default touch behaviors (scroll/pinch) on the slider
     so the drag tracks cleanly. */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  --cv-progress: 0;
}
.cv-slider:focus { outline: 2px solid var(--accent-soft); outline-offset: 2px; }
/* Filled track: a coloured layer behind the label whose width grows
   with --cv-progress (0 → 1). Visual feedback as the user slides. */
.cv-slider::before {
  content: "";
  position: absolute;
  inset: 0;
  width: calc(var(--cv-progress) * 100%);
  background: linear-gradient(90deg,
    rgba(168, 83, 39, 0.18),
    rgba(168, 83, 39, 0.32));
  pointer-events: none;
  z-index: 0;
  transition: width 0.05s linear;
}
.cv-slider-label {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding-left: 44px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-style: italic;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--muted);
  pointer-events: none;
  z-index: 1;
  /* Fade out as the slider progresses (driven by --cv-progress). */
  opacity: calc(1 - var(--cv-progress) * 1.8);
  transition: opacity 0.05s linear;
}
.cv-slider-handle {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 36px;
  height: 36px;
  background: var(--ink);
  color: var(--bg);
  border-radius: 999px;
  display: grid;
  place-items: center;
  touch-action: none;
  z-index: 2;
  transition: background 0.18s ease;
}
.cv-slider:hover .cv-slider-handle { background: var(--accent); }
.cv-slider.is-confirmed .cv-slider-handle { background: var(--accent); }
.cv-slider.is-confirmed .cv-slider-label { opacity: 0; }
/* Smooth snap-back / animate-to-end when drag ends; suspended during drag. */
.cv-slider:not(.is-dragging) .cv-slider-handle { transition: left 0.22s cubic-bezier(.2,.7,.2,1), background 0.18s ease; }
.cv-slider:not(.is-dragging)::before { transition: width 0.22s cubic-bezier(.2,.7,.2,1); }

@media (max-width: 640px) {
  .cv-slider { width: 180px; height: 36px; }
  .cv-slider-handle { width: 28px; height: 28px; }
  .cv-slider-label { font-size: 10px; padding-left: 32px; }
  .icon-button { width: 34px; height: 34px; }
  .icon-button svg { width: 14px; height: 14px; }
}

.icon-row { display: inline-flex; align-items: center; gap: 8px; }

.icon-button {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--ink);
  color: var(--bg);
  cursor: pointer;
  font: inherit;
  transition: background 0.18s ease, transform 0.18s ease;
}
.icon-button:hover { background: var(--accent); transform: translateY(-1px); }
.icon-button svg { width: 16px; height: 16px; fill: currentColor; }
.icon-button[data-popover].is-open { background: var(--accent); }

/* Inline button styled as a text link (used in the footer for
   email and phone, so clicking shows a copy bubble instead of
   triggering mailto: / tel: redirects). */
.link-text {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  letter-spacing: inherit;
  text-transform: inherit;
  border-bottom: 1px solid transparent;
  transition: color 0.18s ease, border-color 0.18s ease;
}
.link-text:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* =====================================================================
   NOTE GRIDS
   ===================================================================== */

.notes-grid { display: grid; gap: 10px; }

.notes-grid.papers,
.notes-grid.projects {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.note-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 220px;
  background: var(--paper);
  color: inherit;
  /* Slightly thicker border — gives the cards more presence on the
     cream background without feeling heavy. */
  border: 1.5px solid var(--rule-strong);
  /* Project cards: small radius. Paper cards override below to a
     slightly larger radius (they're wider, so it reads in proportion). */
  border-radius: 8px;
  overflow: hidden;
  transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}
.note-card.paper { border-radius: 8px; }
.note-card.is-hidden { display: none; }

a.note-card { cursor: pointer; }
@media (min-width: 641px) and (hover: hover) and (pointer: fine) {
  a.note-card:hover {
    transform: translateY(-2px);
    border-color: var(--ink);
    box-shadow: 0 14px 32px -22px rgba(13, 14, 17, 0.25);
  }
}

.note-media {
  display: block;
  position: relative;
  flex: 0 0 156px;
  min-width: 0;
  min-height: 0;
  background: var(--bg-soft);
  border-right: none;
  border-bottom: 1px solid var(--rule);
  overflow: hidden;
}
.note-card .note-media > :not(:first-child) {
  display: none;
}
.note-card .note-media > :first-child {
  position: absolute;
  inset: 0;
}
.note-media img,
.note-media video {
  width: 100%;
  height: 100%;
  min-height: 0;
  aspect-ratio: auto;
  object-fit: fill;
  background: var(--bg-soft);
  filter: saturate(0.96);
  transition: transform 0.45s ease;
  display: block;
}
@media (min-width: 641px) and (hover: hover) and (pointer: fine) {
  a.note-card:hover .note-media img { transform: scale(1.03); }
}
.note-media img + img,
.note-media img + video,
.note-media video + img,
.note-media video + video,
.note-media img + .crop-top,
.note-media .crop-top + img,
.note-media .crop-top + video,
.note-media video + .crop-top,
.note-media .crop-top + .crop-top { border-left: 1px solid var(--rule); }

/* Crop a video to its top half: a wrapper sized to the normal media slot,
   with the video stretched to 2× height anchored at the top, then clipped. */
.note-media .crop-top,
.detail-figures .crop-top {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--bg-soft);
}
.note-card .note-media .crop-top {
  height: 100%;
  min-height: 0;
  aspect-ratio: auto;
}
.note-media .crop-top video,
.detail-figures .crop-top video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
  transform: scaleY(2);
  transform-origin: top center;
  border: none;
  background: transparent;
  filter: saturate(0.96);
}
.detail-figures .crop-top { border: 1px solid var(--rule); }
.note-media.single { grid-template-columns: 1fr; }

.note-body {
  position: static;
  min-width: 0;
  flex: 1 1 auto;
  padding: 5px 8px 6px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 2px;
}

.note-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 10.4px;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1.08;
}
.note-card .note-meta > span:first-child {
  display: none;
}
.note-card .note-meta > span:nth-child(2) {
  margin-left: auto;
  text-align: right;
}
.note-meta .venue { color: var(--ink); font-weight: 500; }
.note-meta .paper-link {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  cursor: pointer;
  color: inherit;
  transition: color 0.18s ease;
}
.note-meta .paper-link:hover { color: var(--accent); }
.note-meta .pill {
  display: inline-block;
  padding: 1px 5px;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  font-size: 8.2px;
  letter-spacing: 0.08em;
}
.note-meta .pill.live,
.note-meta .pill.published {
  color: var(--accent);
  border-color: var(--accent-soft);
}

.note-card h3 {
  font-family: Arial, Helvetica, sans-serif;
  font-size: 15.2px;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.08;
  font-feature-settings: normal;
  font-variant: normal;
  font-variant-caps: normal;
  text-transform: none;
  /* Same harmonized blue ink as the rest of the heading family —
     not pure black, sits comfortably on every card background. */
  color: var(--card-title);
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

.note-card.paper h3 {
  display: block;
  white-space: normal;
  font-size: 15.2px;
  line-height: 1.08;
  overflow: visible;
  text-overflow: clip;
}

.note-card .hook {
  position: absolute;
  inset: 0;
  z-index: 2;
  font-family: Arial, Helvetica, sans-serif;
  font-style: normal;
  font-weight: 400;
  font-feature-settings: normal;
  font-variant: normal;
  text-transform: none;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.94);
  border-top: none;
  font-size: 13.8px;
  line-height: 1.32;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  margin: 0;
  padding: 14px;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.18s ease, visibility 0s linear 0.18s;
}

.note-card .hook::after {
  content: "Click for more details";
  display: block;
  margin-top: 10px;
  font-size: 0.82em;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--detail-cta);
}

@media (min-width: 641px) and (hover: hover) and (pointer: fine) {
  .note-card:hover .hook {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
  }
}

.paper-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 2px;
}
.paper-actions .text-link { font-size: 12px; }

/* ----------------------- Project filter strip ----------------------- */

.project-filters {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.filter-button {
  padding: 8px 15px;
  font: inherit;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-title);
  background: rgba(255, 253, 244, 0.94);
  border: 1.5px solid var(--rule-strong);
  box-shadow: 0 6px 16px -14px rgba(45, 61, 101, 0.62), inset 0 0 0 1px rgba(255, 255, 255, 0.72);
  border-radius: 999px;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.18s ease, background 0.18s ease,
              border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
.filter-button:hover {
  color: var(--accent);
  border-color: var(--accent-soft);
  box-shadow: 0 10px 22px -16px rgba(168, 83, 39, 0.62), inset 0 0 0 1px rgba(196, 113, 72, 0.35);
  transform: translateY(-1px);
}
.filter-button.active {
  color: var(--bg);
  background: var(--ink-title);
  border-color: var(--ink-title);
  box-shadow: 0 12px 24px -16px rgba(45, 61, 101, 0.75), inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}

/* =====================================================================
   PHYSICAL INTELLIGENCE ACROSS SCALES
   ===================================================================== */

.scale-portfolio {
  padding-top: 58px;
  padding-bottom: 58px;
}

.scale-heading {
  max-width: 100%;
  margin-inline: auto;
  text-align: center;
}

.section-kicker {
  margin: 0 0 8px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

.section-subtitle {
  max-width: none;
  margin: 12px auto 0;
  color: var(--ink-body);
  font-size: 16.5px;
  line-height: 1.55;
  white-space: nowrap;
}

.scale-jump-nav {
  width: fit-content;
  max-width: 100%;
  margin: 18px auto 28px;
  padding: 8px 10px;
  background: rgba(255, 253, 244, 0.72);
  border: 1.5px solid rgba(194, 187, 168, 0.78);
  border-radius: 999px;
  box-shadow: 0 16px 38px -30px rgba(45, 61, 101, 0.58), inset 0 1px 0 rgba(255, 255, 255, 0.68);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.scale-filter-chain {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.scale-filter-chain::before {
  display: none;
  content: "";
  position: absolute;
  left: 18px;
  right: 18px;
  top: 50%;
  border-top: 1.5px solid rgba(194, 187, 168, 0.78);
  transform: translateY(-50%);
}

.scale-filter-chain .filter-button {
  position: relative;
  z-index: 1;
}

.scale-filter-chain .filter-button:nth-of-type(-n+2)::after {
  content: "";
  position: absolute;
  left: calc(100% + 1px);
  top: 50%;
  width: 8px;
  border-top: 1.5px solid rgba(194, 187, 168, 0.78);
  transform: translateY(-50%);
  pointer-events: none;
}

.scale-stack {
  display: grid;
  gap: 34px;
}

.scale-subsection {
  scroll-margin-top: 92px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
}
.scale-subsection.is-filter-hidden {
  display: none;
}

.scale-subsection:first-child {
  border-top-color: var(--rule-strong);
}

.scale-subhead {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  margin-bottom: 14px;
}

.scale-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: 1.5px solid var(--rule-strong);
  border-radius: 50%;
  color: var(--accent);
  background: rgba(255, 253, 244, 0.92);
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  box-shadow: 0 10px 22px -18px rgba(45, 61, 101, 0.52);
}

.scale-subhead h3 {
  margin: 0;
  color: var(--ink-title);
  font-family: Arial, Helvetica, sans-serif;
  font-style: normal;
  font-size: clamp(18.5px, 1.85vw, 21px);
  font-weight: 600;
  line-height: 1.15;
}

.scale-subhead p {
  max-width: 720px;
  margin: 6px 0 0;
  color: var(--ink-body);
  font-family: Arial, Helvetica, sans-serif;
  font-style: normal;
  font-size: 15.2px;
  line-height: 1.55;
}

.scale-grid + .scale-grid {
  margin-top: 16px;
}

.scale-toggle-hint {
  margin: 14px 0 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--detail-cta);
  text-align: center;
}

.publication-clones:empty {
  display: none;
}

@media (max-width: 640px) {
  .scale-portfolio {
    padding-top: 38px;
    padding-bottom: 40px;
  }

  .section-subtitle {
    font-size: 14.5px;
    line-height: 1.55;
    white-space: normal;
  }

  .scale-jump-nav {
    margin-bottom: 22px;
    gap: 5px;
    padding: 6px;
    overflow-x: auto;
  }

  .scale-jump-nav .filter-button {
    padding: 6px 9px;
    font-size: 10px;
  }

  .scale-stack {
    gap: 28px;
  }

  .scale-subsection {
    scroll-margin-top: 76px;
    padding-top: 18px;
  }

  .scale-subhead {
    gap: 10px;
    margin-bottom: 12px;
  }

  .scale-index {
    width: 36px;
    height: 36px;
    font-size: 11.5px;
  }

  .scale-subhead h3 {
    font-size: 19px;
  }

  .scale-subhead p {
    font-size: 13.8px;
    line-height: 1.5;
  }

  .scale-toggle-hint {
    font-size: 9.4px;
    line-height: 1.35;
  }
}

/* =====================================================================
   STORY CONTINUES — closing section after the map
   ===================================================================== */

.ahead { padding-top: 36px; padding-bottom: 44px; }

.ahead .ahead-content {
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
}

.ahead .ahead-decoration {
  display: block;
  width: 240px;
  max-width: 70%;
  height: 24px;
  margin: 0 auto 22px;
  opacity: 0.95;
}

.ahead .ahead-content p {
  font-size: 15.5px;
  line-height: 1.7;
  color: var(--ink-body);
  text-align: left;
  margin: 0 auto;
}

.ahead .ahead-content p strong { color: var(--ink-title); font-weight: 500; }

/* Grid keeps the row strictly 4-up (or 2-up on mobile) — no 3+1
   wrap glitches. Gap controls the breathing room. */
.ahead-tags {
  display: grid;
  grid-template-columns: repeat(4, max-content);
  justify-content: center;
  gap: 22px;
  margin-top: 26px;
}

button.ahead-tag {
  font: inherit;
  cursor: pointer;
}
.ahead-tag {
  display: inline-block;
  padding: 6px 16px;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  /* Editorial-italic Fraunces — different from the mono used elsewhere
     for meta rows; gives the forward-look pills a "tagline" voice. */
  font-family: var(--display);
  font-style: italic;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-title);
  background: var(--bg-soft);
  transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.ahead-tag:hover {
  color: var(--accent);
  border-color: var(--accent-soft);
  background: var(--paper);
}

@media (max-width: 640px) {
  .ahead { padding-top: 22px; padding-bottom: 22px; }
  .ahead .ahead-content p { font-size: 13px; line-height: 1.55; }
  .ahead .ahead-decoration { margin-bottom: 12px; height: 14px; }
  /* Forward-look pills — comfortable tap target without dominating. */
  .ahead-tag {
    font-size: 12.5px !important;
    padding: 4px 12px !important;
    letter-spacing: 0 !important;
    border-radius: 999px;
    line-height: 1.4;
  }
  .ahead-tags {
    grid-template-columns: repeat(2, max-content);
    gap: 8px 10px;
    margin-top: 14px;
  }
}

@media (max-width: 420px) {
  .ahead-tag {
    font-size: 12px !important;
    padding: 4px 11px !important;
  }
  .ahead-tags { gap: 7px 8px; }
}

/* Future-direction popup — image at top, blurb + link below */
.future-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  background: rgba(13, 14, 17, 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.future-modal.is-open { display: flex; opacity: 1; }

.future-modal-backdrop {
  position: absolute;
  inset: 0;
  cursor: zoom-out;
}

.future-modal-content {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 460px;
  background: var(--paper);
  border: 1px solid var(--rule);
  box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.4);
  outline: none;
  overflow: hidden;
  transform: translateY(8px);
  transition: transform 0.22s ease;
}
.future-modal.is-open .future-modal-content { transform: translateY(0); }

.future-modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 5;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  color: var(--ink);
  border-radius: 50%;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.future-modal-close:hover {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
  transform: rotate(90deg);
}

.future-modal-image {
  width: 100%;
  height: 200px;
  background: var(--bg-soft);
  border-bottom: 1px solid var(--rule);
  overflow: hidden;
}
.future-modal-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.96);
}

.future-modal-body { padding: 20px 22px 22px; }
.future-modal-body h3 {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin: 0 0 10px;
}
.future-modal-body p {
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-2);
  margin: 0 0 14px;
}
.future-modal-body strong { color: var(--ink-title); font-weight: 500; }
.future-modal-body a.text-link { font-size: 12px; }

@media (max-width: 480px) {
  .future-modal-image { height: 170px; }
  .future-modal-body { padding: 16px 18px 18px; }
  .future-modal-body h3 { font-size: 16px; }
}

/* =====================================================================
   PLACES MAP (Leaflet/OSM)
   ===================================================================== */

.places,
.ahead {
  background: var(--paper);
}

.places { padding-bottom: 36px; }

.map-shell {
  margin: 0 auto;
  width: 100%;
  max-width: 940px;
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  padding: 10px;
}

#trajectory-map {
  width: 100%;
  /* Height tuned to the latitude band we frame in JS (-50° → 70°).
     At a 1024px-wide column the band naturally renders ~460px tall,
     so the map fills horizontally with almost no vertical gap. */
  height: 400px;
  background: #dde6ee;
  border: 1px solid var(--rule);
  filter: saturate(0.94);
}

/* Custom Leaflet pin / popup styling */
.leaflet-pin {
  width: 14px;
  height: 14px;
  background: var(--accent);
  border: 2px solid var(--bg);
  border-radius: 50%;
  box-shadow: 0 0 0 3px rgba(168, 83, 39, 0.22);
}
/* Travel-pin variant — hollow muted-teal dot, no trajectory association. */
.leaflet-pin.trip {
  width: 12px;
  height: 12px;
  background: #fbf8ed;
  border: 2px solid #2f6f7e;
  box-shadow: 0 0 0 3px rgba(47, 111, 126, 0.22);
}
.leaflet-tooltip.pin-label.trip {
  color: #2f6f7e;
  border-color: rgba(47, 111, 126, 0.35);
  font-weight: 500;
}
.leaflet-popup-content-wrapper {
  border-radius: 4px;
  box-shadow: 0 8px 24px -10px rgba(13, 14, 17, 0.35);
  background: var(--paper);
}
.leaflet-popup-content {
  margin: 8px 12px;
  font-family: var(--serif);
  font-size: 12.5px;
  color: var(--ink);
  line-height: 1.4;
}
.leaflet-popup-content b { font-weight: 600; }
.leaflet-popup-content small {
  display: block;
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--muted);
  text-transform: uppercase;
}
.leaflet-popup-tip { background: var(--paper); }
.leaflet-control-attribution {
  font-family: var(--mono);
  font-size: 9px !important;
  background: rgba(255, 255, 255, 0.78) !important;
}

.places figcaption {
  margin-top: 10px;
  color: var(--muted);
  font-size: 12.5px;
  max-width: 78ch;
}

/* =====================================================================
   PROJECT DETAIL PAGES
   ===================================================================== */

.detail-hero {
  width: 100%;
  max-width: var(--col-max);
  margin: 0 auto;
  padding: 40px var(--col-pad) 26px;
  border-bottom: 1px solid var(--rule);
}

.detail-hero .crumbs {
  /* Hidden — the breadcrumb (Home / Projects / E-04) doesn't carry
     useful information inside the modal or on the standalone page. */
  display: none;
}
.detail-hero .crumbs a {
  color: var(--muted);
  border-bottom: 1px solid transparent;
  transition: color 0.18s ease, border-color 0.18s ease;
}
.detail-hero .crumbs a:hover { color: var(--ink); border-bottom-color: var(--ink); }
.detail-hero .crumbs .sep { color: var(--rule-strong); }

.detail-hero h1 {
  max-width: 28ch;
  font-size: 28px;
  font-weight: 500;
  letter-spacing: -0.022em;
  line-height: 1.18;
}

.detail-hero .meta-row {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  margin-top: 18px;
  font-size: 13px;
  color: var(--muted);
}
.detail-hero .meta-row strong {
  display: block;
  margin-top: 2px;
  color: var(--ink);
  font-weight: 500;
}
.detail-hero .meta-row .label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.detail-hero .meta-row a.venue-paper {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  transition: color 0.18s ease;
}
.detail-hero .meta-row a.venue-paper:hover { color: var(--accent); }

.detail-figures {
  width: 100%;
  max-width: var(--col-max);
  margin: 22px auto 32px;
  padding: 0 var(--col-pad);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.detail-figures figure { margin: 0; }
.detail-figures img,
.detail-figures video,
.detail-figures .placeholder {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: fill;
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  display: block;
}
.detail-figures.single { grid-template-columns: 1fr; }
.detail-figures .placeholder {
  display: grid;
  place-items: center;
  border-style: dashed;
  border-color: var(--rule-strong);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.detail-figures figcaption {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.detail-body {
  width: 100%;
  max-width: var(--col-max);
  margin: 0 auto;
  padding: 18px var(--col-pad) 24px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 240px;
  gap: 40px;
  align-items: start;
}

.detail-prose p {
  margin: 0 0 14px;
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--ink-2);
}
.detail-prose strong { color: var(--ink-title); font-weight: 650; }
.detail-prose em { color: var(--ink); font-style: italic; }

.detail-side {
  position: sticky;
  top: 90px;
  display: grid;
  gap: 18px;
  font-size: 12.5px;
}
.detail-side .side-block { padding-bottom: 18px; border-bottom: 1px solid var(--rule); }
.detail-side .side-block:last-child { border-bottom: none; }
.detail-side h4 { margin-bottom: 8px; }
.detail-side p, .detail-side li { color: var(--ink-2); }
.detail-side ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.detail-side li {
  padding: 3px 8px;
  background: var(--bg-soft);
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

.detail-nav {
  width: 100%;
  max-width: var(--col-max);
  margin: 30px auto 0;
  padding: 22px var(--col-pad) 50px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  border-top: 1px solid var(--rule);
}
.detail-nav a { display: block; color: inherit; padding: 10px 0; }
.detail-nav a.next { text-align: right; }
.detail-nav .label {
  display: block;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.detail-nav strong {
  display: block;
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  transition: color 0.18s ease;
}
.detail-nav a:hover strong { color: var(--accent); }

html.modal-embed .skip-link,
html.modal-embed .site-header {
  display: none !important;
}
html.modal-embed body {
  padding-top: 0 !important;
}
html.modal-embed main {
  margin-top: 0 !important;
}
html.modal-embed .detail-figures img {
  cursor: zoom-in;
}

@media (min-width: 881px) {
  .detail-figures {
    max-width: 900px;
    gap: 10px;
  }
}

@media (max-width: 640px) {
  html.modal-embed .detail-hero,
  html.modal-embed .detail-figures,
  html.modal-embed .detail-body,
  html.modal-embed .detail-nav {
    padding-left: 12px !important;
    padding-right: 12px !important;
  }
  html.modal-embed .detail-hero {
    padding-top: 12px !important;
    padding-bottom: 4px !important;
  }
  html.modal-embed .detail-figures {
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
    gap: 5px !important;
    padding-top: 6px !important;
  }
  html.modal-embed .detail-figures figure {
    min-width: 0 !important;
  }
  html.modal-embed .detail-figures figcaption {
    font-size: 9.5px !important;
    margin-top: 3px !important;
  }
  html.modal-embed .detail-body {
    grid-template-columns: 1fr !important;
    gap: 12px !important;
    padding-top: 8px !important;
  }
}

/* =====================================================================
   FOOTER
   ===================================================================== */

.site-footer-min {
  width: 100%;
  max-width: var(--col-max);
  margin: 0 auto;
  padding: 18px var(--col-pad) 32px;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  color: var(--muted);
  text-transform: uppercase;
}
.site-footer-min a { color: var(--muted); border-bottom: 1px solid transparent; }
.site-footer-min a:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* =====================================================================
   RESPONSIVE — rearrange below 880, tighten further at iPhone widths
   ===================================================================== */

@media (max-width: 880px) {
  :root { --col-pad: 20px; }

  /* Narrow desktop/tablet: fixed cards drop from 4-up to 3-up. */
  .notes-grid.papers { grid-template-columns: repeat(3, 1fr); }
  .notes-grid.projects { grid-template-columns: repeat(3, 1fr); }

  .detail-body { grid-template-columns: 1fr; gap: 20px; }
  .detail-side { position: static; }

  .about-grid { grid-template-columns: 200px 1fr; column-gap: 24px; row-gap: 12px; }

}

@media (max-width: 640px) {
  :root { --col-pad: 12px; }
  body { font-size: 14px; }

  .site-header {
    /* On phones, hide the "Yujia Yuan" brand so the sticky bar is just
       the nav row — saves vertical space and looks cleaner. */
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding-top: 8px;
    padding-bottom: 8px;
  }
  .brand { display: none; }
  .site-nav {
    width: 100%;
    overflow-x: auto;
    flex-wrap: nowrap;
    justify-content: center;
    margin-left: 0;
    padding-bottom: 2px;
  }
  .site-nav a { white-space: nowrap; padding: 5px 10px; font-size: 13.5px; }

  .section { padding: 24px 0; }
  h2.section-title { font-size: 23px; }

  .about-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    row-gap: 14px;
  }
  /* On mobile, let items flow in source order: heading → photo → copy.
     Smaller photo so it doesn't dominate the screen. */
  .about-heading { grid-column: 1 / 2; grid-row: auto; font-size: 23px; }
  .about-photo   { grid-column: 1 / 2; grid-row: auto; max-width: 180px; margin: 0 auto; padding: 5px; }
  /* Smaller offsets for the layered cards so they don't overshoot
     the narrower mobile photo. */
  .about-photo::before { transform: translate(7px, 9px) rotate(-3deg); }
  .about-photo::after  { transform: translate(-4px, 6px) rotate(2deg); }
  .about-copy    { grid-column: 1 / 2; grid-row: auto; }
  .about-copy p { font-size: 13.8px; line-height: 1.55; }

  /* iPhone: spread the slider and icon row to opposite ends of the
     bio column so the actions row matches the text width above. */
  .about-actions {
    width: 100%;
    justify-content: space-between;
    gap: 12px;
  }

  /* Force section headings to read flush-left on phones — the base
     style is `text-align: center` (good for desktop), but on iPhone
     widths the title can wrap to 2 lines and the centering looks
     inconsistent next to other left-aligned UI. */
  .section-heading {
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
    gap: 6px;
  }
  .section-heading .section-title { max-width: 100%; }
  /* Scale jump strip: keep the five anchors compact on a single row.
     Paper bg + strong border still apply. */
  .project-filters { gap: 5px; flex-wrap: nowrap; justify-content: center; }
  .filter-button {
    padding: 6px 9px;
    font-size: 10.2px;
    letter-spacing: 0.06em;
    font-weight: 500;
  }

  /* ---- Xiaohongshu / Red-Note style dense grid on mobile ----
     Both papers and projects keep TWO columns on phones, so several
     cards are visible per screen instead of one giant card. Cards
     get tighter padding, smaller type, and titles are allowed to wrap
     to 2 lines instead of being ellipsised on a single line. */
  .notes-grid { gap: 8px; }
  .notes-grid.papers   { grid-template-columns: repeat(2, 1fr); }
  .notes-grid.projects { grid-template-columns: repeat(2, 1fr); }

  .note-card {
    height: 188px;
  }
  .note-media {
    flex-basis: 126px;
    border-right: none;
    border-bottom: 1px solid var(--rule);
  }
  .note-media img,
  .note-media video,
  .note-media .crop-top {
    aspect-ratio: auto;
    min-height: 0;
    height: 100%;
  }
  .note-card .note-media .crop-top {
    aspect-ratio: auto;
    min-height: 0;
    height: 100%;
  }

  .note-body { padding: 5px 7px 6px; gap: 2px; }

  .note-meta {
    font-size: 8.8px;
    letter-spacing: 0.045em;
    gap: 4px;
    flex-wrap: wrap;
    min-width: 0;
  }
  .note-meta .venue {
    min-width: 0;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
  }
  .note-meta .pill {
    font-size: 8.4px;
    padding: 1px 5px;
    letter-spacing: 0.06em;
    flex-shrink: 0;
  }

  .note-card h3,
  .note-card.paper h3 {
    white-space: normal;
    display: block;
    overflow: visible;
    font-size: 12.4px;
    line-height: 1.08;
  }

  .note-card .hook::after {
    content: "Tap again for more details";
  }

  /* Phones: latitude band fills the container width at fractional zoom,
     so set height to match the natural rendered height (~160-180px on
     a ~360px wide phone). */
  #trajectory-map { height: 175px; }
  .map-shell { padding: 6px; }

  .detail-figures { grid-template-columns: 1fr; }
  .detail-nav { grid-template-columns: 1fr; }
  .detail-nav a.next { text-align: left; }

  .site-footer-min { flex-direction: column; gap: 6px; font-size: 10px; }
}

@media (max-width: 420px) {
  .about-actions { gap: 10px; }
  .icon-button { width: 32px; height: 32px; }
  .button-primary { padding: 8px 14px; font-size: 10.5px; }

  /* Stay 2-up even at very narrow widths — keep the dense feed feel.
     Force the one-image card rule (override any earlier stacking). */
  .notes-grid { gap: 8px; }
  .note-card { height: 180px; }
  .note-media { flex-basis: 120px; }
  .note-media { display: block; }
  .note-media img + img,
  .note-media img + video,
  .note-media video + img,
  .note-media video + video,
  .note-media img + .crop-top,
  .note-media .crop-top + img,
  .note-media .crop-top + video,
  .note-media video + .crop-top,
  .note-media .crop-top + .crop-top {
    border-left: none;
    border-top: none;
  }
  .note-body { padding: 5px 6px 6px; gap: 2px; }
  .note-card h3,
  .note-card.paper h3 {
    font-size: 12px;
    line-height: 1.08;
  }
  .note-card .hook { font-size: 12px; }
  #trajectory-map { height: 150px; }
}

/* =====================================================================
   COPY BUBBLE — cartoon speech-bubble style popover for email/phone
   ===================================================================== */

.copy-bubble {
  position: absolute;
  z-index: 80;
  top: 0;
  left: 0;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 14px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-radius: 14px;
  box-shadow: 0 14px 32px -14px rgba(13, 14, 17, 0.3);
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  transform: translate(-50%, -4px);
  transform-origin: top center;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s;
}
.copy-bubble.is-open {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
  visibility: visible;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0s;
}
/* Cloud-like tail at the top, pointing toward the icon */
.copy-bubble::before {
  content: "";
  position: absolute;
  top: -6px;
  left: 50%;
  width: 11px;
  height: 11px;
  background: var(--paper);
  border-top: 1px solid var(--rule-strong);
  border-left: 1px solid var(--rule-strong);
  transform: translateX(-50%) rotate(45deg);
}
.copy-bubble.below::before {
  /* Tail flips when bubble is rendered above the trigger */
  top: auto;
  bottom: -6px;
  border-top: none;
  border-left: none;
  border-bottom: 1px solid var(--rule-strong);
  border-right: 1px solid var(--rule-strong);
}

.copy-bubble .copy-text {
  color: var(--ink);
  user-select: text;
}

.copy-bubble .copy-btn {
  background: var(--ink);
  color: var(--bg);
  border: none;
  padding: 5px 10px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}
.copy-bubble .copy-btn:hover { background: var(--accent); }
.copy-bubble .copy-btn.copied { background: #2f7a3a; }

@media (max-width: 420px) {
  .copy-bubble { font-size: 11.5px; padding: 7px 8px 7px 12px; }
  .copy-bubble .copy-btn { font-size: 9px; padding: 4px 8px; }
}

/* =====================================================================
   CARD MODAL — opens project / paper notes inside an overlay so the
   user never leaves the page and returns to the same scroll position.
   ===================================================================== */

html { scrollbar-gutter: stable; }

.card-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding: 32px 16px;
  background: rgba(13, 14, 17, 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.card-modal.is-open { display: flex; opacity: 1; }

.card-modal-backdrop {
  position: absolute;
  inset: 0;
  cursor: zoom-out;
}

.card-modal-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 920px;
  max-height: calc(100vh - 64px);
  margin: auto;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.5);
  overflow-y: auto;
  outline: none;
  transform: translateY(8px);
  transition: transform 0.22s ease;
}
.card-modal.is-open .card-modal-content { transform: translateY(0); }

.card-modal-close {
  position: sticky;
  top: 10px;
  align-self: flex-end;
  margin: 10px 10px 0;
  z-index: 5;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  color: var(--ink);
  border-radius: 50%;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.card-modal-close:hover {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
  transform: rotate(90deg);
}

.card-modal-body { flex: 1; padding-top: 0; }
.card-modal-body:empty {
  min-height: 200px;
  background-image: linear-gradient(90deg, transparent, rgba(0,0,0,0.04), transparent);
  background-size: 200% 100%;
  animation: modal-skel 1.2s linear infinite;
}
.card-modal-body.uses-frame {
  display: flex;
  min-height: min(760px, calc(100vh - 136px));
}
.card-modal-frame {
  flex: 1;
  width: 100%;
  min-height: min(760px, calc(100vh - 136px));
  border: 0;
  background: var(--bg);
}
@keyframes modal-skel {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Inside the modal, the injected .detail-* sections shouldn't carry
   their own outer max-width gutters — the modal itself provides the
   column width. */
.card-modal-body .detail-hero,
.card-modal-body .detail-figures,
.card-modal-body .detail-body,
.card-modal-body .detail-nav {
  width: 100%;
  max-width: none;
  margin: 0;
  padding-left: 28px;
  padding-right: 28px;
}
.card-modal-body .detail-hero { padding-top: 4px; padding-bottom: 22px; }
.card-modal-body .detail-figures { padding-top: 18px; }
.card-modal-body .detail-body { padding-top: 18px; padding-bottom: 18px; }
.card-modal-body .detail-nav { padding-bottom: 28px; margin-top: 14px; }
.card-modal-body .detail-figures img { cursor: zoom-in; }

@media (min-width: 641px) {
  .card-modal-body .detail-figures {
    max-width: 840px;
    margin-left: auto;
    margin-right: auto;
  }
}

/* Body scroll lock while modal is open */
body.modal-open { overflow: hidden; }
body.modal-open .card-modal-content { touch-action: pan-y; }
/* Stop the modal's scroll from chaining out to the body — without
   this, pulling down at the top of the modal triggers Safari's
   pull-to-refresh and bubbles into the underlying page. */
.card-modal-content,
.future-modal-content {
  overscroll-behavior: contain;
  -webkit-overscroll-behavior: contain;
}

@media (max-width: 640px) {
  /* Backdrop padding tightened — popup uses more of the screen so the
     content can start higher. */
  .card-modal { padding: 16px 14px; }
  .card-modal-content {
    max-height: calc(100vh - 32px);
    border-radius: 8px;
  }
  .card-modal-body.uses-frame,
  .card-modal-frame {
    min-height: calc(100vh - 32px);
  }
  /* Float the X button absolutely so it doesn't push the title down.
     Close still works exactly the same. */
  .card-modal-close {
    position: absolute;
    top: 8px;
    right: 8px;
    margin: 0;
    width: 28px;
    height: 28px;
    z-index: 10;
  }
  .card-modal-body .detail-hero,
  .card-modal-body .detail-figures,
  .card-modal-body .detail-body,
  .card-modal-body .detail-nav {
    padding-left: 12px;
    padding-right: 12px;
  }
  /* Title rides up to the very top, with just enough space for the X. */
  .card-modal-body .detail-hero { padding-top: 12px; padding-bottom: 4px; }
  .card-modal-body .detail-figures { padding-top: 6px; }
  .card-modal-body .detail-body { padding-top: 8px; padding-bottom: 10px; grid-template-columns: 1fr; gap: 12px; }
  /* Keep the two figures side-by-side inside the modal on mobile too,
     with a tighter gap so the popup stays compact. */
  .card-modal-body .detail-figures { grid-template-columns: 1fr 1fr; gap: 5px; }
  .card-modal-body .detail-figures figcaption { font-size: 9.5px; margin-top: 3px; }

  /* Hero meta row: 2+2 grid for projects (4 items),
     2+3 grid for papers (5 items). Very tight gaps so the meta row
     doesn't push the figures down. */
  .card-modal-body .detail-hero .meta-row,
  .detail-hero .meta-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3px 8px;
    margin-top: 4px;
    font-size: 11.5px;
  }
  /* Papers carry 5 meta cells (Domain, Place, Field, Year, Paper) — use
     a 6-track grid so the first 2 span 3 each (row 1) and the last 3
     span 2 each (row 2). :has() is supported on iOS 15.4+ / Chrome 105+. */
  .card-modal-body .detail-hero .meta-row:has(> :nth-child(5)),
  .detail-hero .meta-row:has(> :nth-child(5)) {
    grid-template-columns: repeat(6, 1fr);
  }
  .card-modal-body .detail-hero .meta-row:has(> :nth-child(5)) > :nth-child(-n+2),
  .detail-hero .meta-row:has(> :nth-child(5)) > :nth-child(-n+2) {
    grid-column: span 3;
  }
  .card-modal-body .detail-hero .meta-row:has(> :nth-child(5)) > :nth-child(n+3),
  .detail-hero .meta-row:has(> :nth-child(5)) > :nth-child(n+3) {
    grid-column: span 2;
  }
  .detail-hero .meta-row > div { line-height: 1.2; }
  .detail-hero .meta-row .label { font-size: 8.5px; letter-spacing: 0.14em; }
  .detail-hero .meta-row strong { font-size: 11px; margin-top: 0; line-height: 1.2; }
  .detail-hero h1 { font-size: 20px; line-height: 1.15; margin: 0; }

  /* Image-magnify cursor cue on tap targets in the modal. */
  .card-modal-body .detail-figures img,
  .card-modal-body .detail-figures video {
    cursor: zoom-in;
  }
}

/* =====================================================================
   LIGHTBOX — fullscreen image magnifier (used on mobile when you tap
   a figure image inside an open project / paper modal).
   ===================================================================== */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(8, 8, 10, 0.94);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  cursor: zoom-out;
}
.lightbox.is-open { display: flex; }
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  filter: none;
  background: #000;
  border-radius: 4px;
  box-shadow: 0 30px 80px -20px rgba(0,0,0,0.7);
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

/* =====================================================================
   MAP — permanent labels next to each city pin
   ===================================================================== */

.leaflet-tooltip.pin-label {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  padding: 3px 8px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  box-shadow: 0 4px 10px -6px rgba(13, 14, 17, 0.25);
  white-space: nowrap;
}
.leaflet-tooltip.pin-label::before { display: none; } /* hide leaflet's default arrow */
