/* ==========================================================================
   Nilesh Sarkar - site stylesheet

   One file, one theme. The design is deliberately flat: whitespace and
   hairline rules do the separating, not boxes. Almost nothing has a border,
   a fill, or a shadow, so the page reads as a document rather than a
   dashboard.

   Contents
     01  Reset & tokens
     02  Base elements
     03  Layout
     04  Top dock
     05  Page headers
     06  Hero
     07  Sections
     08  Entries
     09  Cards & lists
     10  Tags
     11  Timeline
     12  Papers & resources
     13  Figures, code & math
     14  Callouts
     15  Forms & contact
     16  Footer & 404
     17  Responsive
     18  Print
   ========================================================================== */


/* 01  Reset & tokens
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchor jumps must clear the sticky dock, or the heading lands under it. */
  scroll-padding-top: 72px;
}

body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ul, ol {
  margin: 0;
  padding: 0;
}

:root {
  /* Surfaces - only two, and the second is barely there. */
  --bg:      #0a0a0a;
  --sunk:    #131313;

  /* Hairlines */
  --line:        rgba(255, 255, 255, 0.10);
  --line-strong: rgba(255, 255, 255, 0.20);

  /* Text. Every step is kept at or above WCAG AA on the page background:
     --muted 9.1:1 and --faint 5.6:1, so secondary copy and meta lines stay
     readable instead of fading into the background. */
  --heading: #f7f7f8;
  --text:    #dedee1;
  --muted:   #b2b2b7;
  --faint:   #8b8b91;

  /* A single accent, used sparingly. */
  --accent: #7cc7ff;

  /* Type - one clean sans throughout. */
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Rhythm */
  --measure: 70ch;
  --wrap:    1040px;
  --gap:     clamp(2.6rem, 6vw, 4.5rem);

  /* Legacy aliases.
     The article pages carry ~285 inline styles written against the old
     multi-theme variable names. Rather than editing markup on 19 pages,
     those names are mapped onto the tokens above, so inline colours,
     borders and radii keep resolving instead of silently dropping out. */
  --accent-color:     var(--accent);
  --link-color:       var(--accent);
  --clay-deep:        var(--accent);
  --sage:             var(--accent);
  --text-color:       var(--text);
  --body:             var(--text);
  --heading-color:    var(--heading);
  --ink:              var(--heading);
  --muted-color:      var(--muted);
  --bg-color:         var(--bg);
  --surface-elevated: var(--sunk);
  --glass-bg:         var(--sunk);
  --glass-border:     var(--line);
  --radius-sm:        8px;
  --radius-md:        12px;
  --mono:             var(--font-mono);
  --serif:            var(--font);

  /* Semantic data colours used in result tables on the research pages. */
  --pos:   #7fd8a0;
  --neg:   #ff9e9e;
  --amber: #ffd27a;
}


/* 02  Base elements
   ========================================================================== */

/* The reading measure as one shared absolute length. Registered via @property
   so the `76ch` below computes against the body font once and inherits as a
   fixed length. An unregistered property would substitute the literal `76ch`
   at each use site, where `ch` tracks that element's own font - which is how
   h2s ended up "capped" wider than the column while h4s went narrower than
   the prose. Browsers without @property fall back to element-relative ch. */
@property --prose-measure {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

body {
  --prose-measure: 76ch;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 1.0625rem;
  line-height: 1.7;
  /* `clip`, not `hidden`. overflow-x:hidden turns <body> into a scroll
     container, and once the body scrolls the browser stops treating the
     document as the root scroller - which kills pull-to-refresh and the
     overscroll bounce on mobile. `clip` trims stray horizontal overflow
     without creating a scroll container, so native scrolling stays intact. */
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  color: var(--heading);
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.021em;
}

h1 { font-size: clamp(2rem, 5vw, 3rem); }
h2 { font-size: clamp(1.4rem, 2.9vw, 1.85rem); margin: 3rem 0 1rem; }
h3 { font-size: clamp(1.1rem, 2vw, 1.28rem); margin: 2.2rem 0 0.7rem; }
h4 { font-size: 1.02rem; margin: 1.7rem 0 0.5rem; }

p { margin: 0 0 1.15rem; }

/* Prose keeps a readable measure; grids and headers are free to go wider. */
article p, article li,
.lede, .bio, .subtitle,
.curriculum-intro, .contact-intro p {
  max-width: var(--measure);
}

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

strong, b { color: var(--heading); font-weight: 600; }

hr { border: 0; border-top: 1px solid var(--line); margin: var(--gap) 0; }

img, svg, video { max-width: 100%; height: auto; display: block; }

ul, ol { padding-left: 1.2rem; margin: 0 0 1.15rem; }
article ul, article ol { max-width: var(--measure); }
li { margin-bottom: 0.4rem; }
li::marker { color: var(--faint); }

blockquote {
  margin: 1.8rem 0;
  padding-left: 1.2rem;
  border-left: 2px solid var(--line-strong);
  color: var(--muted);
}

/* Tables scroll instead of widening the page. */
table {
  display: block;
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-collapse: collapse;
  font-size: 0.93rem;
  margin: 1.8rem 0;
}
th, td {
  padding: 0.65rem 1rem 0.65rem 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
}
th {
  color: var(--heading);
  font-weight: 500;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  white-space: nowrap;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
::selection { background: var(--heading); color: var(--bg); }

/* Decorative layer from the previous build. */
.bg-mesh { display: none; }


/* 03  Layout
   ========================================================================== */

main, article, section { display: block; }

/* Every top-level block shares one column, so headers, articles and footers
   line up identically no matter how a given page nests its markup. */
/* `body > main` rather than `main#top`: some pages use a bare <main>, and
   those were falling outside the column and running edge to edge. */
body > main,
body > header,
body > article,
body > footer,
body > .error-page,
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(20px, 5vw, 40px);
}

/* .wrap nests inside those containers, so it must not pad twice. */
body > main .wrap,
body > article .wrap,
body > header .wrap { padding-inline: 0; max-width: 100%; }

body > main { padding-block: 0 var(--gap); }

/* Article pages are a reading column, so the column itself is narrowed and
   centred rather than setting a 70ch measure inside a 1040px box - that left
   the text hugging the left edge with all the leftover space stacked on the
   right, which is what made wide screens look so empty and lopsided. */
body > article {
  max-width: min(78rem, 100%);
  padding-bottom: var(--gap);
}
body > article > header,
body > article > section,
body > article > p,
body > article > ul,
body > article > ol,
body > article > figure,
body > article > div {
  max-width: min(62rem, 100%);
  margin-inline: auto;
}


/* Genuinely wide media only. Code (pre / .codelog / .code-block) is content
   and sits on the reading column with the prose - see section 21. */
body > article .figure-frame,
body > article figure,
body > article table,
body > article .card-grid,
body > article .obs-grid,
body > article .achievement-grid,
body > article .theme-grid,
body > article .skills-grid,
body > article .img-grid,
body > article .two-col,
body > article .featured-image,
body > article .project-img-large {
  max-width: 100%;
}

/* (removed) A legacy `body > article p/li { max-width: none }` override lived
   here. Its higher specificity beat the site-wide 76ch prose cap in section 21
   on every page whose <article> is a direct child of <body>, leaving headings
   centred to the reading measure while paragraphs ran the full column. */


/* 04  Top dock
   ========================================================================== */

/* Glass dock: content stays faintly visible through it as the page moves
   underneath. The heavy blur is what keeps label text legible over whatever
   scrolls past - without it, a low alpha alone would make the labels sit on
   moving content and become hard to read. */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(12, 12, 13, 0.55);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--line);
}

/* Browsers without backdrop-filter get a solid bar instead of unreadable
   text floating over the page content. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-nav { background: rgba(10, 10, 10, 0.96); }
}

/* The name belongs in the hero, not the dock. */
.nav-brand { display: none; }

.nav-links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  flex-wrap: wrap;
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 8px clamp(10px, 4vw, 30px);
}

.nav-links a {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 40px;
  padding: 6px 14px;
  border-radius: 10px;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 450;
  transition: color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

.nav-links a:hover {
  color: var(--heading);
  background: rgba(255, 255, 255, 0.07);
  text-decoration: none;
}
.nav-links a:active { transform: scale(0.97); }

/* The current page gets a rule that grows out from the centre. */
.nav-links a[aria-current="page"] { color: var(--heading); }
/* Wider spacing between dock items on anything above a phone. Kept off small
   screens because the four labels only just fit one row at 320px, and extra
   gap there would wrap them. */
@media (min-width: 620px) {
  .nav-links { gap: clamp(10px, 2.2vw, 30px); }
  .nav-links a { padding-inline: 6px; }
}

.nav-links a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 3px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--heading);
  transform-origin: center;
  animation: nav-underline 0.45s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
@keyframes nav-underline { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }

/* Scroll-driven condense: the bar tightens and deepens as the page moves.
   Pure CSS - browsers without scroll timelines simply keep the static bar. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .site-nav {
      animation: nav-condense linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 140px;
    }
    /* Deepens a little on scroll but stays glass - going to 0.94 here made
       the bar opaque the moment you scrolled, losing the effect entirely. */
    @keyframes nav-condense {
      to {
        background: rgba(12, 12, 13, 0.72);
        box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
      }
    }
    .nav-links {
      animation: nav-tighten linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 140px;
    }
    @keyframes nav-tighten { to { padding-block: 4px; } }
  }
}


/* 05  Page headers
   ========================================================================== */

body > header { padding-block: clamp(1.8rem, 4vw, 3rem) 0; }

body > header h1 { margin-bottom: 0.7rem; }

.subtitle, .lab-tagline, .error-sub {
  color: var(--muted);
  font-size: 1.08rem;
  line-height: 1.6;
}

/* Only the title block is centred - the page title, its one-line standfirst
   and the action buttons under it. Nothing here targets a container, because
   .project-header on several pages wraps the whole article body, and setting
   text-align on it centred every paragraph on the page. Running text is
   always left-aligned: centred prose gives the eye no fixed left edge to
   return to on each new line. */
body > header > h1,
.page-head h1,
.lab-hero h1, .lab-hero .lab-eyebrow, .lab-hero .lab-tagline,
.guide-header h1, .curriculum-header h1,
.project-header > h1,
.error-code, .error-message {
  text-align: center;
}

/* Only the first paragraph of a header block is the standfirst - these
   sections carry ordinary body copy after it on several pages. */
.page-head > .lede, .page-head > .subtitle,
body > header > .subtitle, body > header > .lede,
.lab-hero > p:first-of-type,
.guide-header > p:first-of-type,
.curriculum-header > p:first-of-type,
.error-sub {
  text-align: center;
  margin-inline: auto;
}

/* .page-head holds only a title block (title, standfirst, one action), so it
   is safe to centre wholesale - unlike .project-header, which on several
   pages wraps the entire article body. */
.page-head, .page-head > .wrap { text-align: center; }
.page-head .lede, .page-head .subtitle { margin-inline: auto; }
.page-head .header-links { justify-content: center; }
.page-head .back-link { display: inline-flex; }

/* The first section follows a header that already carries a rule and its own
   bottom margin, so it does not need a second full gap stacked on top. */
.page-head + .block,
.page-head + section,
body > header + main > .block:first-of-type { padding-top: clamp(0.6rem, 1.5vw, 1.2rem); }

/* Action rows under a title sit centred beneath it. */
.page-head > .header-links,
.lab-hero > .header-links,
.guide-header > .header-links,
.curriculum-header > .header-links,
body > header > .header-links { justify-content: center; }

/* Title blocks: eyebrow, large centred title, one narrow standfirst, then a
   lot of air. No dividing rule - the whitespace does that job. */
.page-head, .guide-header,
.curriculum-header, .lab-hero,
body > header {
  padding-block: clamp(2.5rem, 7vw, 6rem) clamp(2rem, 4vw, 3.5rem);
  margin-bottom: 0;
  border-bottom: none;
}

.page-head h1, .lab-hero h1, .guide-header h1,
.curriculum-header h1, body > header > h1 {
  font-size: clamp(2.1rem, 5.2vw, 3.5rem);
  line-height: 1.08;
  letter-spacing: -0.028em;
  margin-bottom: 1.1rem;
}

/* Only the FIRST paragraph is the standfirst. These header blocks contain
   ordinary body prose further down on some pages, and centring that made it
   unreadable - hence :first-of-type rather than every <p>. */
.page-head .lede, .page-head .subtitle,
body > header > .subtitle, body > header > .lede,
.lab-hero > p:first-of-type, .lab-tagline,
.guide-header > p:first-of-type,
.curriculum-header > p:first-of-type {
  max-width: 46ch;
  margin-inline: auto;
  font-size: clamp(1.02rem, 1.5vw, 1.18rem);
  line-height: 1.55;
  color: var(--muted);
}

/* The eyebrow sits above the title as a quiet label. */
.lab-eyebrow, .page-head .cd-kicker, body > header .cd-kicker {
  text-align: center;
  margin-bottom: 0.9rem;
}

/* Actions sit well below the standfirst, not crowded against it. */
.page-head .header-links,
.lab-hero .header-links,
.guide-header .header-links,
.curriculum-header .header-links,
body > header .header-links { margin-top: 2rem; }

/* .project-header is not a title block on every page - on several it wraps
   the whole article - so it only gets spacing, never centring. */
.project-header { padding-bottom: 1.4rem; margin-bottom: 1.8rem; }

.lab-eyebrow, .cd-kicker, .post-meta, .lab-meta, .contact-meta {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 0.7rem;
}
.post-meta, .lab-meta { display: flex; flex-wrap: wrap; gap: 0.3rem 1.4rem; }

.lede, .cd-lede, .curriculum-intro {
  font-size: 1.18rem;
  line-height: 1.65;
  color: var(--muted);
  margin-bottom: 1.6rem;
}

/* Rows of actions */
.header-links, .section-cta, .cd-cta, .resource-links,
.contact-social-row, .form-actions, .language-selector, .share-section {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 1.6rem 0;
}

/* The copy-link control closes the page, so it is centred under the article
   rather than left-hanging off the last paragraph. */
.share-section {
  justify-content: center;
  margin-top: 4rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
}

.share-label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--faint);
}

/* Buttons - outlined, never filled, except the one primary. */
.btn-pill, .pill, .back-link, .resource-link, .lang-btn,
.submit-btn, .error-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 18px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--heading);
  font-family: var(--font);
  font-size: 0.9rem;
  font-weight: 450;
  cursor: pointer;
  transition: background 0.16s ease, border-color 0.16s ease;
}
.btn-pill:hover, .pill:hover, .back-link:hover, .resource-link:hover,
.lang-btn:hover, .submit-btn:hover, .error-link:hover {
  background: rgba(255, 255, 255, 0.07);
  border-color: var(--line-strong);
  text-decoration: none;
}

.btn-pill svg, .pill svg, .back-link svg { width: 15px; height: 15px; flex: 0 0 auto; }

.pill.primary, .submit-btn, .lang-btn.active {
  background: var(--heading);
  border-color: var(--heading);
  color: var(--bg);
}
.pill.primary:hover, .submit-btn:hover, .lang-btn.active:hover {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--bg);
}
.submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }


/* 06  Hero
   ========================================================================== */

/* The hero is an introduction, not a landing screen: it stays short enough
   that the first section of actual work is visible without scrolling. */
.hero { padding-block: clamp(1.6rem, 3.5vw, 2.6rem) 0; }

/* Centred stack at every width: portrait, name, bio, links. One layout that
   behaves the same from a 320px phone to a 4K display - nothing to reflow,
   nothing to break at an in-between size.
   column-reverse because the portrait comes last in the markup. */
.hero-inner {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: clamp(12px, 2vw, 18px);
  text-align: center;
}

.hero-text {
  width: 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.hero-text h1 {
  font-size: clamp(1.7rem, 3.6vw, 2.35rem);
  margin-bottom: 0.5rem;
}

.bio {
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--text);
  margin-bottom: 1.15rem;
  margin-inline: auto;
  max-width: 56ch;
}
.bio strong { color: var(--heading); font-weight: 500; }
.xbio { font-style: italic; color: var(--text); }

.hero-portrait {
  flex: 0 0 auto;
  width: clamp(88px, 9vw, 112px);
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  overflow: hidden;
  background: var(--sunk);
  border: 3px solid #ffffff;
  transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1),
              box-shadow 0.4s ease;
}
.hero-portrait:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
}
.hero-portrait img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 12%; }

.links { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 8px; }

.icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--muted);
  transition: color 0.16s ease, border-color 0.16s ease;
}
.icon-link:hover { color: var(--heading); border-color: var(--line-strong); transform: translateY(-2px); }
.icon-link svg { width: 18px; height: 18px; }

/* On a wide screen the whole action row should sit on one line, so the hero
   controls run slightly tighter than the 44px touch minimum kept on phones. */
@media (min-width: 701px) {
  .links { gap: 7px; }
  .links .icon-link { width: 40px; height: 40px; }
  .links .pill { min-height: 40px; padding: 8px 15px; font-size: 0.87rem; }
}


/* 07  Sections
   ========================================================================== */

.block { padding-block: var(--gap) 0; }

/* The first section already sits under a page header with its own rule and
   bottom margin, so it does not need a second full gap on top of that. */
main > .block:first-child,
main > .wrap:first-child > .block:first-child { padding-top: clamp(1rem, 2vw, 1.6rem); }

.sec-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding-bottom: 14px;
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}

.sec-num {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--faint);
}

.sec-head h2 { margin: 0; font-size: clamp(1.3rem, 2.6vw, 1.7rem); }


/* 08  Entries
   ========================================================================== */

.entries { display: flex; flex-direction: column; }

/* Rows in a document, divided by a rule - not stacked cards. */
.entry {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: clamp(14px, 3vw, 34px);
  align-items: start;
  padding-block: 1.6rem;
  border-bottom: 1px solid var(--line);
}
.entry:last-child { border-bottom: none; }

.entry-date {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--faint);
  padding-top: 0.3rem;
}

.entry-body { display: flex; gap: 16px; min-width: 0; }
.entry-text { min-width: 0; }

/* Three kinds of logo tile, distinguished by what the artwork already is:
     .entry-logo        real company logos, drawn dark on white -> white tile
     .entry-logo.icon   SVGs drawn for this theme               -> dark tile
     .entry-logo.cover  photographs                             -> fill it   */
.entry-logo {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: 10px;
  background: #ffffff;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.entry-logo img { width: 100%; height: 100%; object-fit: contain; padding: 5px; }

.entry-logo.icon { background: var(--sunk); }
.entry-logo.icon img { padding: 9px; }

.entry-logo.cover { background: var(--sunk); }
.entry-logo.cover img { padding: 0; object-fit: cover; }

/* These letterform marks stand in for a missing logo and sit on the white
   tile, so they take dark ink - the muted grey they used to inherit was
   unreadable against it. */
.mono-mark {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  color: #1c1c1e;
}
.serif-mark { font-size: 1.2rem; color: #1c1c1e; }
.entry-logo.icon .mono-mark,
.entry-logo.icon .serif-mark { color: var(--muted); }

/* The Lossfunk file is a white wordmark on transparency, so on the white tile
   the other logos use it would vanish. Inverting it to black keeps this tile
   the same size and colour as the rest, so every row's text stays aligned. */
.entry-logo.lossfunk-mark img { filter: invert(1); padding: 7px; }

.entry-title {
  font-size: 1.08rem;
  font-weight: 600;
  line-height: 1.4;
  margin: 0 0 0.4rem;
  color: var(--heading);
}
.entry-title a { color: var(--heading); }
.entry-title a:hover { color: var(--accent); text-decoration: none; }

.entry-text p { margin: 0 0 0.6rem; font-size: 0.98rem; color: var(--muted); }


/* 09  Cards & lists
   ========================================================================== */

.card-grid, .achievement-grid, .theme-grid,
.skills-grid, .obs-grid, .focus-list, .paper-list {
  display: grid;
  gap: 0;
  margin: 1.4rem 0 0;
  padding: 0;
  list-style: none;
}

.card-grid   { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 0 clamp(24px, 4vw, 48px); }
.achievement-grid, .theme-grid, .obs-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 0 clamp(24px, 4vw, 44px); }
.skills-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: clamp(24px, 4vw, 40px); }

/* Flat rows: a rule on top, nothing else. */
.card, .achievement-card, .theme-item, .obs-card,
.week-card, .timeline-card, .focus-item, .paper-item,
.summit-stat, .contact-item, .research-paper {
  display: block;
  padding-block: 1.4rem;
  border-top: 1px solid var(--line);
  background: none;
}

.card { display: flex; flex-direction: column; gap: 5px; }
.card:hover .card-title, .focus-item:hover .focus-title { color: var(--accent); }
.card:hover { text-decoration: none; }

.card-title, .focus-title, .week-title, .paper-title {
  font-size: 1.04rem;
  font-weight: 600;
  color: var(--heading);
  line-height: 1.4;
  transition: color 0.16s ease;
}

.card-meta, .obs-label, .week-number, .credit,
.caption, .cd-shot-cap, .paper-authors {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--faint);
}

.card-excerpt, .focus-description, .week-description,
.obs-value, .paper-description, .timeline-content, .figure-explainer {
  font-size: 0.97rem;
  line-height: 1.62;
  color: var(--muted);
  margin: 0;
}

.obs-card { display: flex; flex-direction: column; gap: 4px; }
.obs-label { text-transform: uppercase; }
.obs-value strong { color: var(--heading); }

.week-header { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 0.4rem; }
.week-number { text-transform: uppercase; }
.week-topics { margin: 0.6rem 0 0; padding-left: 1.1rem; font-size: 0.93rem; color: var(--muted); }

/* The stats are bare <span>s in an unclassed wrapper, so the wrapper is
   selected by what it contains and laid out as a row of figures. */
div:has(> .summit-stat) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0 clamp(20px, 4vw, 40px);
  margin: 1.2rem 0;
}
.summit-stat .number { display: block; font-size: 1.9rem; font-weight: 600; color: var(--heading); }
.summit-stat .label {
  font-family: var(--font-mono);
  font-size: 0.73rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--faint);
}

.skill-group { border: none; padding: 0; }
.skill-group-title {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.73rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--faint);
  margin-bottom: 0.8rem;
}


/* 10  Tags
   ========================================================================== */

.tags, .skill-pills, .tech-stack, .cd-points {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0.7rem 0 0;
  padding: 0;
  list-style: none;
}

.tag, .skill-pill, .highlight-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
}

.title-tag { font-size: 0.72rem; padding: 1px 9px; vertical-align: middle; }

/* Category classes are kept neutral on purpose - colour is reserved for
   links, so the page stays quiet. */
.tag.research, .tag.systems, .tag.pytorch, .tag.cuda, .tag.vision,
.tag.perception, .tag.medical, .tag.agent, .tag.design, .tag.data,
.tag.community, .tag.python, .tag.javascript, .tag.soft { color: var(--muted); }


/* 11  Timeline
   ========================================================================== */

/* Markup here is: li > .date + .timeline-card > .timeline-content > [logo, text]
   so .timeline-content is the row, not .timeline-card. */
.timeline { list-style: none; padding: 0; margin: 1.6rem 0 0; }

.timeline li {
  margin: 0;
  padding-block: 1.7rem;
  border-top: 1px solid var(--line);
}
.timeline li:first-child { border-top: none; padding-top: 0.8rem; }

.timeline-card { display: block; }

.timeline-content {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}
.timeline-content > div:last-child { min-width: 0; flex: 1 1 auto; }

.timeline-logo-placeholder {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--sunk);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--muted);
}
.timeline-logo-placeholder svg { width: 22px; height: 22px; }

/* The rows are written with <br> runs, so they need the leading set here. */
.timeline-content > div:last-child { line-height: 1.72; }
.timeline-content b { font-size: 1.04rem; font-weight: 600; color: var(--heading); }
.timeline-content b a { color: var(--heading); }
.timeline-content b a:hover { color: var(--accent); }
.timeline-content .tag { margin-top: 0.3rem; }

.timeline .date, .date {
  display: block;
  margin-bottom: 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--faint);
}


/* 12  Papers & resources
   ========================================================================== */

.paper-item { margin: 0; }
.paper-header { margin-bottom: 0.4rem; }
.paper-title { display: inline; margin-right: 5px; }
.paper-authors { font-size: 0.85rem; }
.paper-description { margin-bottom: 0.7rem; }

.paper-links { display: flex; flex-wrap: wrap; gap: 14px; }
.paper-links a { font-size: 0.85rem; color: var(--accent); }


/* 13  Figures, code & math
   ========================================================================== */

/* Images sit in their own band of space, centred, and never wider than the
   text column plus a little - a full-bleed diagram beside a 70ch paragraph
   reads as two unrelated things. */
.figure-frame, figure {
  margin: 2.4rem 0;
  padding: 0;
  border: none;
  background: none;
}
.figure-frame img, figure img {
  width: auto;
  max-width: 100%;
  max-height: 78vh;
  border-radius: 8px;
  margin-inline: auto;
  background: var(--sunk);
}

figcaption, .caption, .cd-shot-cap, .figure-explainer {
  display: block;
  margin-top: 0.85rem;
  font-family: var(--font);
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--faint);
  letter-spacing: 0;
  max-width: var(--measure);
}

/* These class names are used on both <img> elements and on <div> wrappers
   around an image, so they are styled as containers and the inner image is
   centred separately below. */
.featured-image, .project-img-large, .cd-shot {
  display: block;
  max-width: 100%;
  margin: 2rem auto;
  border-radius: 8px;
}
img.featured-image, img.project-img-large, img.cd-shot {
  width: auto;
  max-height: 72vh;
  background: var(--sunk);
}

/* Centre images inside their wrapper by making the wrapper do the centring.
   Auto margins are unreliable here because these images carry intrinsic
   widths and per-page inline sizing; a flex column centres them whatever
   width they end up at. The caption stays a normal block beneath. */
figure,
.figure-frame,
div.featured-image,
div.project-img-large,
div.cd-shot {
  display: flex;
  flex-direction: column;
  align-items: center;
}
figure > figcaption,
.figure-frame > figcaption,
.figure-frame > .caption,
.figure-frame > .figure-explainer { align-self: center; width: 100%; max-width: var(--measure); }

article img, main img { margin-inline: auto; }

/* Loose images inside article prose get the same treatment, so pages that
   never used .figure-frame still line up with the ones that did. */
article > img,
article p > img,
article section > img {
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  border-radius: 8px;
  margin: 2rem auto;
  background: var(--sunk);
}

code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  padding: 0.12em 0.36em;
  border-radius: 4px;
  background: var(--sunk);
  color: var(--text);
}

/* Code is a distinct kind of content, so it gets a real panel: its own
   surface, a hairline edge, and room to breathe. It also breaks out slightly
   wider than the prose measure, because code lines do not wrap. */
pre, .code-block, .primer-code {
  margin: 2rem auto;
  padding: 18px 20px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #0e0e10;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  line-height: 1.65;
  tab-size: 2;
}
pre code, .code-block code {
  padding: 0;
  background: none;
  color: var(--text);
  font-size: inherit;
}

/* Program output and training logs read as results, not as source: dimmer
   ink, a marked left edge, so they are never mistaken for code to run. */
.codelog {
  border-left: 3px solid var(--line-strong);
  color: var(--muted);
}

/* Results tables get a defined header and quiet row rules, so figures are
   scannable down a column. */
table.data thead th,
article table thead th,
main table thead th {
  border-bottom: 1px solid var(--line-strong);
  color: var(--heading);
  padding-bottom: 0.6rem;
}
article table tbody tr:hover,
main table tbody tr:hover { background: rgba(255, 255, 255, 0.03); }
article table td, main table td { font-variant-numeric: tabular-nums; }

.math-block { margin: 1.8rem auto; overflow-x: auto; }


/* 14  Callouts
   ========================================================================== */

/* A rule in the margin, not a tinted box. */
.highlight-box, .theory-box, .note {
  margin: 1.8rem 0;
  padding: 0.2rem 0 0.2rem 1.3rem;
  border-left: 2px solid var(--line-strong);
  background: none;
  max-width: var(--measure);
}
.note { color: var(--muted); font-size: 0.96rem; }

.highlight-box p:last-child, .theory-box p:last-child,
.note p:last-child, .content-box p:last-child { margin-bottom: 0; }

.content-box { margin: 1.6rem 0; }
/* .intro-block is a boxed opening section: keep its own inset padding, but
   centre the box itself on the reading measure like every other block. */
.intro-block {
  margin: 1.6rem auto;
  max-width: min(100%, var(--prose-measure, 76ch));
}
.credits-section, .worldview-section { margin-top: var(--gap); }


/* 15  Forms & contact
   ========================================================================== */

.contact-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(24px, 5vw, 56px);
  margin-top: 1.6rem;
}
.contact-intro p { color: var(--muted); }
.contact-note { color: var(--faint); font-size: 0.95rem; }

.contact-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  margin-top: 1.4rem;
  font-family: var(--font);
  font-size: 0.95rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
}

/* Contact rows are links, not cards - icon beside label, no rule above. */
.contact-item {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  border-top: none;
  padding-block: 0.3rem;
  color: var(--text);
}
.contact-item:hover { color: var(--accent); text-decoration: none; }
.contact-item svg { width: 17px; height: 17px; fill: currentColor; flex: 0 0 auto; }
.contact-social-row { gap: 20px; margin: 0.3rem 0 0; }

.contact-form { display: flex; flex-direction: column; gap: 12px; }
.form-row { display: flex; gap: 12px; flex-wrap: wrap; }
.form-row > * { flex: 1 1 200px; }

.modern-input, .modern-textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: 1rem;
}
.modern-textarea { min-height: 150px; resize: vertical; }
.modern-input::placeholder, .modern-textarea::placeholder { color: var(--faint); }
.modern-input:focus, .modern-textarea:focus { outline: none; border-color: var(--heading); }


/* 16  Footer & 404
   ========================================================================== */

footer {
  margin-top: var(--gap);
  padding-block: 1.8rem 2.4rem;
  border-top: 1px solid var(--line);
  font-size: 0.86rem;
  color: var(--faint);
  text-align: center;
}

.error-page {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 62vh;
}
.error-code { font-size: clamp(3.5rem, 13vw, 5.5rem); font-weight: 600; color: var(--heading); line-height: 1; }
.error-message { font-size: 1.25rem; color: var(--heading); margin: 0.9rem 0 0.4rem; }
.error-sub { margin-inline: auto; margin-bottom: 1.8rem; }


/* 16b Research-page components
   Ported here from per-page <style> blocks so every page draws from this one
   file. These are the pieces the write-ups genuinely need: results tables,
   training-log excerpts, roadmaps and image grids.
   ========================================================================== */

/* Numeric results tables: figures right-aligned, labels left. */
table.data { font-size: 0.88rem; }
table.data th, table.data td { padding: 0.5rem 0.9rem 0.5rem 0; text-align: right; }
table.data th:first-child, table.data td:first-child { text-align: left; }
table.data tbody tr.sum { font-weight: 600; color: var(--heading); }
table.data .mono, td.mono, .mono { font-family: var(--font-mono); }

/* Verdict colours - the one place colour carries meaning besides links. */
.v-pos { color: var(--pos); }
.v-neg, table.data .neg { color: var(--neg); }
.v-amber { color: var(--amber); }

/* Raw training-log excerpts keep their line breaks and scroll sideways. */
.codelog {
  margin: 1.6rem auto;
  padding: 0.95rem 1.1rem;
  border-radius: 10px;
  background: var(--sunk);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  line-height: 1.55;
  white-space: pre;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(16px, 3vw, 32px); }

.img-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
  margin: 1.8rem 0;
}
.img-grid img { width: 100%; border-radius: 8px; }

.content-section { margin-block: var(--gap); }

/* Next-steps roadmap: phase marker, description, status. */
.roadmap { list-style: none; padding: 0; margin: 1.4rem 0; }
.roadmap li {
  display: flex;
  gap: 1.1rem;
  align-items: flex-start;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--line);
}
.roadmap .ph {
  flex: 0 0 auto;
  min-width: 2.2rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--faint);
  padding-top: 0.15rem;
}
.roadmap .b { flex: 1 1 auto; min-width: 0; }
.roadmap .b b { display: block; color: var(--heading); margin-bottom: 0.15rem; }
.roadmap .b span { font-size: 0.92rem; color: var(--muted); }
.roadmap .s {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  white-space: nowrap;
  color: var(--faint);
}

.secnote {
  margin: 0.2rem 0 1.3rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--muted);
}
.src { font-family: var(--font-mono); font-size: 0.74rem; color: var(--faint); }
.cd-stats { font-family: var(--font-mono); font-size: 0.85rem; color: var(--muted); margin: 1.6rem auto; }


/* 17  Responsive
   ========================================================================== */

@media (max-width: 900px) {
  .contact-wrap { grid-template-columns: 1fr; }
  .entry { grid-template-columns: 1fr; gap: 8px; }
  .entry-date { padding-top: 0; }
}

@media (max-width: 700px) {
  body { font-size: 1rem; }

  .hero-portrait { width: clamp(96px, 26vw, 124px); }

  .nav-links { gap: 2px; padding-inline: 8px; }
  .nav-links a { padding: 6px 10px; font-size: 0.86rem; }

  .card-grid, .achievement-grid, .theme-grid,
  .skills-grid, .obs-grid, .two-col, .img-grid { grid-template-columns: 1fr; }

  .roadmap li { flex-wrap: wrap; gap: 0.5rem 1rem; }
}

@media (max-width: 480px) {
  .btn-pill, .pill, .back-link, .resource-link { font-size: 0.85rem; padding: 9px 15px; }
  .timeline-card { flex-direction: column; }
  th, td { padding-right: 0.7rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* 18  Print
   ========================================================================== */

@media print {
  :root { --bg: #fff; --text: #000; --heading: #000; --muted: #333; --faint: #555; }
  body { background: #fff; color: #000; }
  .site-nav, .header-links, .section-cta, .share-section, footer { display: none; }
  a { color: #000; text-decoration: underline; }
  .card, .entry, .paper-item { break-inside: avoid; }
}


/* 19  Motion & fluid scaling
   ==========================================================================
   A light entrance on load and fluid sizing across every display. All of it
   is pure CSS: nothing here gates content visibility on JavaScript running,
   which is what made the old build render blank pages.
   ========================================================================== */

@keyframes rise-in {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  /* The hero and the first heading of a page ease in on load. `both` holds
     the end state, and because these are CSS animations they always run -
     there is no script that can fail and leave the page invisible. */
  .hero-portrait,
  .hero-text > h1,
  .hero-text > .bio,
  .hero-text > .links,
  body > header > h1,
  .page-head h1,
  .page-head .lede,
  .lab-hero h1 {
    animation: rise-in 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }

  .hero-portrait          { animation-delay: 0.02s; }
  .hero-text > h1         { animation-delay: 0.08s; }
  .hero-text > .bio       { animation-delay: 0.14s; }
  .hero-text > .links     { animation-delay: 0.20s; }
  .page-head .lede        { animation-delay: 0.08s; }

  /* Cards and rows lift very slightly under the pointer. */
  .card, .entry, .paper-item, .timeline li {
    transition: transform 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
  }
  .card:hover, .paper-item:hover { transform: translateY(-2px); }
}

/* Fluid type: scales smoothly between a phone and a large display instead of
   jumping at breakpoints. */
html { font-size: clamp(100%, 0.95rem + 0.15vw, 112.5%); }

/* Images and media scale to their container on every device, never overflow,
   and keep their aspect ratio. Tables and code panels scroll inside
   themselves rather than widening the page. */
img, svg, video, canvas, iframe {
  max-width: 100%;
  height: auto;
}

figure img,
.figure-frame img,
.featured-image img,
.project-img-large img,
.img-grid img,
article img,
main img {
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

@media (max-width: 700px) {
  /* On phones a tall diagram should not fill three screens before the caption. */
  figure img, .figure-frame img, article img, main img { max-height: 62vh; }
  .figure-frame, figure { margin-block: 1.6rem; }
  pre, .code-block, .primer-code, .codelog { padding: 13px 14px; font-size: 0.8rem; }
  table { font-size: 0.86rem; }
}

@media (max-width: 420px) {
  pre, .code-block, .primer-code, .codelog { font-size: 0.75rem; }
}


/* 20  Icon safety net
   ==========================================================================
   Several inline SVGs in the page markup set stroke-width but omit
   fill="none" and stroke="currentColor". An SVG's default fill is black, so
   those rendered as solid black squares and circles beside the date and
   read-time. Rather than hand-editing icons across 20 files, outline icons
   are normalised here.
   ========================================================================== */

.post-meta svg,
.lab-meta svg,
.contact-meta svg,
.timeline-logo-placeholder svg,
.nav-links svg,
.btn-pill svg,
.pill svg,
.back-link svg,
.sec-head svg {
  fill: none;
  stroke: currentColor;
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
}

/* Brand glyphs are solid shapes and must keep their fill. */
.icon-link svg,
.contact-item svg,
.social-links svg { fill: currentColor; stroke: none; }
.icon-link.ic-mail svg { fill: none; stroke: currentColor; }

.post-meta span, .lab-meta span, .contact-meta span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}


/* 21  Tables, motion feedback
   ========================================================================== */

/* Tables shrink to their content and centre in the column. Previously they
   were width:100% block elements, so a narrow table sat hard against the
   left edge with all the leftover width stacked to its right. fit-content
   plus auto margins centres the actual table, and max-width keeps the
   horizontal scroll behaviour intact on phones. */
table {
  width: fit-content;
  min-width: min(100%, 34rem);
  max-width: 100%;
  margin-inline: auto;
}

/* Figures and media centre the same way, whatever wrapper a page uses. */
figure, .figure-frame, .img-grid,
.featured-image, .project-img-large, .cd-shot {
  margin-inline: auto;
}

/* Click feedback: things you can press respond immediately. */
a, button, .card, .entry, .btn-pill, .pill, .back-link,
.resource-link, .icon-link, .nav-links a, .submit-btn {
  -webkit-tap-highlight-color: transparent;
}

@media (prefers-reduced-motion: no-preference) {
  .btn-pill, .pill, .back-link, .resource-link,
  .submit-btn, .error-link, .icon-link, .lang-btn {
    transition: background 0.18s ease, border-color 0.18s ease,
                color 0.18s ease, transform 0.12s ease;
  }
  .btn-pill:active, .pill:active, .back-link:active, .resource-link:active,
  .submit-btn:active, .error-link:active, .icon-link:active, .lang-btn:active {
    transform: scale(0.97);
  }

  .card:active, .entry:active { transform: translateY(0) scale(0.995); }

  /* Links ease rather than snap. */
  article a, main a { transition: color 0.16s ease; }
}

/* Scrolling: keep momentum inside scrollable panels instead of chaining it
   to the page, which is what produces the rubber-band feel on touch devices
   and stops a code block's scroll from dragging the whole page. */
/* Native overscroll bounce is left ON at the page level - setting
   overscroll-behavior:none here is what removed the rubber-band feel.
   Only inner scrollers contain their scroll so they do not drag the page. */
html, body { overscroll-behavior: auto; }
pre, .code-block, .codelog, table, .primer-code {
  overscroll-behavior-x: contain;
  scroll-behavior: smooth;
}


/* 22  Scroll reveal
   ==========================================================================
   Content rises in gently as it scrolls into view.

   This uses CSS scroll-driven animations, NOT JavaScript. That distinction
   matters here: the previous build hid every section at opacity 0 and relied
   on an IntersectionObserver to reveal them, so whenever that script failed
   the page rendered blank. A view() timeline is evaluated by the browser from
   scroll position alone - there is nothing to fail.

   The @supports guard means browsers without scroll timelines never apply the
   opacity:0 start state at all; they just show the content. So the reveal can
   only ever be additive, never a way to lose content.
   ========================================================================== */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes scroll-rise {
      from { opacity: 0; transform: translate3d(0, 16px, 0); }
      to   { opacity: 1; transform: none; }
    }

    .entry,
    .card,
    .paper-item,
    .timeline > li,
    .obs-card,
    .week-card,
    .achievement-card,
    .theme-item,
    .focus-item,
    figure,
    .figure-frame,
    .sec-head {
      animation: scroll-rise 1s linear both;
      animation-timeline: view();
      /* Runs over the first third of the element's entry, so it has settled
         well before it reaches the middle of the screen. Anything already on
         screen at load is past this range and therefore fully visible. */
      animation-range: entry 0% entry 34%;
    }
  }
}

/* Printing has no scroll, so a view() timeline never advances and every
   revealed element would print blank. Force the finished state on paper. */
@media print {
  .entry, .card, .paper-item, .timeline > li, .obs-card, .week-card,
  .achievement-card, .theme-item, .focus-item, figure, .figure-frame,
  .sec-head, .hero-portrait, .hero-text > *, body > header > h1,
  .page-head h1, .page-head .lede, .lab-hero h1 {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* The copy-link footer must centre on the page, not inside the narrower
   reading column it would otherwise inherit - that left the button sitting
   about 100px left of the true page centre on every article page. */
.share-section,
body > article > .share-section,
body > article > section.share-section {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  justify-content: center;
  text-align: center;
}

/* Some pages add an accent rule to a box via an inline border-left, but
   .content-box carries no padding, so the rule sat flush against the first
   letter. Give any box that opts into a left rule room to breathe. */
.content-box[style*="border-left"],
.research-paper[style*="border-left"],
.highlight-box[style*="border-left"] {
  padding: 0.7rem 0 0.7rem 1.3rem;
}

/* Icon tiles were near-black on a near-black page, so the glyph inside was
   barely discernible. Lift both the tile and the glyph. */
.timeline-logo-placeholder {
  background: #1c1c1f;
  border: 1px solid var(--line);
  color: var(--text);
}
.timeline-logo-placeholder svg { stroke: currentColor; fill: none; }

/* The 404 block should sit near the middle of the window, not the top third. */
.error-page { min-height: min(72vh, 560px); }

/* Reading measure on article pages.
   The column is sized in rem, and the fluid root font-size pushed it to ~124
   characters per line - far past comfortable reading. Prose and headings are
   therefore capped in `ch` (a measure that tracks the font, not the viewport)
   and centred inside the column, so the margins stay symmetric while figures,
   tables and code keep the full column width. */
/*  not : several pages nest <article>
   inside <main>, and the direct-child form skipped every one of them, leaving
   their prose left-stuck inside a wider section. */
/* One content column. Everything that flows as content - prose, headings,
   lists, card rows, code, math, captions, tag rows, CTAs - sits on the same
   centred measure. Only genuinely wide media (figures, images, tables, the
   grid layouts) keep the full block width, centred over the column. */
article p,
article ul,
article ol,
article h2,
article h3,
article h4,
article blockquote,
article pre,
article .codelog,
article .code-block,
article .math-block,
article .language-selector,
article .caption,
article .credit,
article .week-card,
article .paper-list,
article .focus-list,
article .timeline,
article .tech-stack,
article .resource-links,
article .links,
article .cd-points,
article .cd-stats,
article .cd-cta,
article .cd-shot-cap,
article .content-box,
article .highlight-box,
article .theory-box,
article .note {
  max-width: min(100%, var(--prose-measure, 76ch));
  margin-inline: auto;
}

/* Article-header furniture - date/read-time meta rows and standfirsts - and
   figure-explainer captions sit on the same centred measure as the prose
   around them, instead of hugging the left edge of the wider block. (The
   .figure-explainer base rule sets margin: 0, which otherwise beats the
   article-p centring by specificity.) */
article .post-meta,
article .lede,
article .cd-lede,
article .curriculum-intro,
article .figure-explainer {
  max-width: min(100%, var(--prose-measure, 76ch));
  margin-inline: auto;
}

/* Headings that introduce a full-width figure or table should still line up
   with the prose, so they share the same centred measure above. */

/* Language-switchable code samples. The markup marks one language .active;
   without this rule every example rendered twice (once per language), which
   is what made the neural-networks guide 45,000px tall. If the toggle script
   never runs, the .active language still shows - it degrades to one language
   rather than to nothing. */
.code-block.javascript,
.code-block.python { display: none; }
.code-block.active { display: block; }

/* .curriculum-header is a content section, not a title block, so its opening
   paragraph is ordinary prose: left-aligned text on the shared centred
   measure, like every other paragraph. */
.curriculum-header > p:first-of-type {
  text-align: left;
  margin-inline: auto;
  max-width: min(100%, var(--prose-measure, 76ch));
}

/* On a narrow screen a table squeezed to container width breaks its first
   column one word per line and clips the last header. Since the table is its
   own horizontal scroll container, letting cells keep their words intact makes
   it scroll sideways instead - readable rows, page width untouched. */
@media (max-width: 620px) {
  table { min-width: 0; }
  table th, table td { white-space: nowrap; padding-right: 1.1rem; }
  /* The label column is the one that legitimately needs to wrap. */
  table td:first-child { white-space: normal; min-width: 11rem; }
}

/* Unclassed wrapper divs holding prose were left-aligned inside their
   section, so the paragraphs within them centred against the wrong box.
   Centre the wrapper too, so ordinary paragraphs line up with everything
   else on the page. */
article section > div:not([class]),
article > div:not([class]) {
  max-width: min(100%, var(--prose-measure, 76ch));
  margin-inline: auto;
}

/* Mobile hero.
   The action row wrapped into three ragged rows - six icons, then a stray
   seventh icon sharing a line with two pills, then one pill alone. The
   problem is that icons and pills wrap as one undifferentiated list.
   Tightening the icon gap to 4px fits all seven round icons on a single row
   (7x44 + 6x4 = 332px, inside the ~350px content width of a 390px phone),
   so the pills wrap cleanly beneath as their own group. Touch targets stay
   at 44px - the gap shrinks, not the buttons. */
@media (max-width: 560px) {
  .hero-inner { gap: 16px; }
  .hero-text h1 { font-size: clamp(1.55rem, 8vw, 2rem); }
  .bio { font-size: 1rem; line-height: 1.55; margin-bottom: 1.25rem; }

  .links { gap: 12px 4px; }
  .links .icon-link { width: 44px; height: 44px; }
  .links .pill {
    font-size: 0.82rem;
    padding: 9px 13px;
    gap: 6px;
  }
  .links .pill svg { width: 14px; height: 14px; }
}

/* Click-to-zoom images */
.zoomable { cursor: zoom-in; }

#lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(5, 5, 6, 0.94);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  padding: 3vmin;
  overflow: auto;
  overscroll-behavior: contain;
}
#lightbox.open { display: flex; }
#lightbox img {
  max-width: 100%;
  max-height: 94vh;
  width: auto;
  border-radius: 6px;
  cursor: zoom-out;
  touch-action: pinch-zoom;
}
/* Zoomed state: the image goes to natural size and the overlay scrolls,
   so wide multi-panel figures can be read panel by panel on a phone. */
#lightbox.zoomed { align-items: flex-start; justify-content: flex-start; }
#lightbox.zoomed img { max-width: none; max-height: none; width: auto; cursor: zoom-out; }

#lightbox-close {
  position: fixed;
  top: 14px;
  right: 16px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: rgba(10, 10, 10, 0.8);
  color: var(--heading);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  z-index: 201;
}
#lightbox-hint {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--faint);
  pointer-events: none;
}

/* Some pages carry inline `display:grid` with a fixed minmax() floor (e.g.
   minmax(280px, 1fr)). Inside a padded box on a 320px screen that floor is
   wider than the space available, so the grid overflowed the page. On small
   screens collapse those to one flexible column and trim the box padding. */
@media (max-width: 700px) {
  article div[style*="grid"],
  main div[style*="grid"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .research-paper { padding-inline: 1rem; }
  article figure, article img, main figure, main img { max-width: 100%; }
}

/* Mobile: entry rows.
   On a phone the logo tile sat to the left of the text, so every description
   wrapped inside a ~270px column with a dead indent beside it. Stacking the
   tile above the text gives the copy the full width and lets the rows breathe
   - closer to the reference's mobile rhythm of full-width text and generous
   vertical space than of narrow indented columns. */
@media (max-width: 560px) {
  .entry-body { flex-direction: column; gap: 10px; }
  .entry-logo { width: 40px; height: 40px; }
  .entry { padding-block: 1.5rem; }
  .entry-date { padding-top: 0; margin-bottom: 0.15rem; }
  .entry-title { font-size: 1.05rem; line-height: 1.35; }
  .entry-text p { font-size: 0.95rem; }

  /* Timeline rows have the same shape and the same problem. */
  .timeline-content { flex-direction: column; gap: 10px; }
  .timeline-logo-placeholder { width: 40px; height: 40px; }

  /* Section headings get a little more air above them. */
  .block { padding-top: clamp(2rem, 8vw, 2.8rem); }
  .sec-head { gap: 10px; }
}

/* The active-page underline must track the label, not the old wider padding.
   This sits at the end of the file deliberately: the base rule that sets
   left/right to 14px appears later in section 04 and would otherwise win. */
@media (min-width: 620px) {
  .nav-links a[aria-current="page"]::after { left: 6px; right: 6px; }
}
