/* ==========================================================================
   Design tokens — dark neo-grotesque system
   ========================================================================== */
:root {
  --bg: #050506;
  --surface: #0c0c0e;
  --surface-2: #121215;
  --border: rgba(255, 255, 255, 0.14);
  --border-strong: rgba(255, 255, 255, 0.34);
  --text: #ffffff;
  --text-muted: rgba(255, 255, 255, 0.54);
  --accent: #ffffff;
  --accent-soft: rgba(255, 255, 255, 0.08);
  --accent-text: #050506;
  --code-bg: #121215;

  /* One family throughout. Switzer is the closest freely licensed stand-in
     for the PP Neue Montreal used in the reference design. */
  --font-stack: "Switzer", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-display: var(--font-stack);
  --font-sans: var(--font-stack);
  --font-mono: var(--font-stack);
  --font-signature: var(--font-stack);

  --max-width: 940px;
  --max-width-wide: 1560px;
  --header-h: 72px;
  --radius: 14px;
  --radius-sm: 8px;

  /* Faster through the middle, eased at both ends — used by the card outline. */
  --ease-draw: cubic-bezier(0.65, 0, 0.35, 1);
}

/* ==========================================================================
   Reset & base
   ========================================================================== */
* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

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

img { max-width: 100%; display: block; }

/* ==========================================================================
   Auto-hiding "overlay" scrollbar — fades in while scrolling, fades out at rest
   ========================================================================== */
html {
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}

html.is-scrolling {
  scrollbar-color: rgba(255, 255, 255, 0.38) transparent;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background-color: transparent;
  border-radius: 999px;
  transition: background-color 0.4s ease;
}

html.is-scrolling::-webkit-scrollbar-thumb,
.is-scrolling::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.38);
}

a { color: inherit; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 500;
  text-transform: none;
  letter-spacing: -0.025em;
  line-height: 1.05;
  text-wrap: balance;
}

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

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 8px;
}

.eyebrow::before {
  content: "";
  width: 14px;
  height: 1px;
  background: var(--accent);
  display: inline-block;
}

/* ==========================================================================
   Header / nav
   ========================================================================== */
.site-header {
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 10;
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 76px;
}

.site-title {
  font-family: var(--font-signature);
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0.01em;
  text-decoration: none;
  font-size: 2rem;
  color: var(--text);
}

.site-nav {
  display: flex;
  gap: 24px;
}

.site-nav a {
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  transition: color 0.15s ease;
}

.site-nav a:hover,
.site-nav a.active {
  color: var(--accent);
}

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
}

.nav-item > a::after {
  content: "\25BE";
  display: inline-block;
  margin-left: 4px;
  font-size: 0.7em;
  transform: translateY(-1px);
}

.dropdown {
  position: absolute;
  top: 100%;
  left: -14px;
  min-width: 240px;
  padding: 16px 8px 8px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
  opacity: 0;
  transform: translateY(-18px);
  pointer-events: none;
  transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.nav-item:hover .dropdown,
.nav-item:focus-within .dropdown {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.dropdown a {
  padding: 8px 10px !important;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.dropdown a:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  width: 40px;
  height: 40px;
  font-size: 1.1rem;
  cursor: pointer;
}

@media (max-width: 640px) {
  .nav-toggle { display: block; }

  .site-nav {
    display: none;
    position: absolute;
    top: 64px;
    left: 0;
    right: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    flex-direction: column;
    gap: 0;
    padding: 8px 0;
  }

  .site-nav.open { display: flex; }

  .site-nav a {
    padding: 12px 24px;
  }

  .nav-item {
    flex-direction: column;
    align-items: stretch;
  }

  .dropdown {
    position: static;
    display: flex;
    opacity: 1;
    transform: none;
    pointer-events: auto;
    margin: 0 0 4px;
    padding: 0 0 0 16px;
    border: none;
    box-shadow: none;
    background: transparent;
  }

  .dropdown a {
    color: var(--text-muted);
    font-size: 0.78rem;
  }
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  position: relative;
  padding: 72px 0 56px;
  border-bottom: 1px solid var(--border);
}

.hero .wrap { position: relative; }

.hero h1 {
  font-size: clamp(2.75rem, 7vw, 4.25rem);
  margin: 10px 0 14px;
}

/* The projects page sets its hero in .wrap-wide so the title and lede
   start on the same left edge as the sticky section nav below them. */
.projects-hero .lede { max-width: 64ch; }

.hero p.lede {
  max-width: 58ch;
  color: var(--text-muted);
  font-size: 1.05rem;
  margin: 0 0 28px;
}

.hero-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 22px;
  border-radius: 999px;
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  border: 1px solid var(--border);
  color: var(--text);
  background: var(--surface);
  transition: transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.3), border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
  will-change: transform;
}

.btn-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

.btn-icon { flex: none; display: block; }

.btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.btn-accent:hover { color: var(--accent-text); background: var(--accent); opacity: 0.9; }

/* The buttons used to follow the cursor from JS; the lift and the press are
   pure CSS now so they still animate without tracking the pointer. */
.btn:hover { transform: scale(1.04); }
.btn:active { transform: scale(0.97); transition-duration: 0.08s; }

@media (prefers-reduced-motion: reduce) {
  .btn, .card { transition: none !important; }
}

/* ==========================================================================
   Section headings
   ========================================================================== */
.section {
  padding: 56px 0;
}

.section + .section {
  border-top: 1px solid var(--border);
}

.section-head {
  margin-bottom: 32px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.section-head h2 {
  font-size: 2rem;
  margin: 4px 0 0;
}

.plain-heading {
  font-size: 2rem;
  margin: 0 0 28px;
}

.sheet-no {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-muted);
  white-space: nowrap;
}

/* ==========================================================================
   Project thumbnails (used inside project-row and project-gallery)
   ========================================================================== */
.card-thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--code-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  overflow: hidden;
}

.card-thumb img,
.card-thumb video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-thumb video {
  object-position: center bottom;
}

.thumb-placeholder {
  position: relative;
  z-index: 0;
}

.card-index {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--accent);
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 2px 8px;
  border-radius: 999px;
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}

.tag {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--code-bg);
  color: var(--text-muted);
}

.badge-wip {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent);
}

/* ==========================================================================
   Project cards — two-per-row grid on the home page
   ========================================================================== */
/* The list bleeds 16px past the text column on each side so the thumbnails'
   edges line up with the section heading, as the old stacked rows did. */
.project-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  margin: -16px -16px 0;
}

.project-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 14px;
  border-radius: var(--radius);
  background-color: var(--surface);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  text-decoration: none;
  color: inherit;
  transition: box-shadow 0.2s ease;
}

.project-row .card-thumb {
  border-radius: var(--radius);
  aspect-ratio: 4 / 3;
}

/* The Baker Bot photo is near-square; anchor the crop at the top so the
   robot's head stays in frame. */
.project-row .card-thumb img[src*="baker-bot"] {
  object-position: center top;
}

.project-row:hover {
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.6);
}

.project-row-body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 4px 4px;
}

.project-row-body h3 {
  font-family: var(--font-sans);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
  font-size: 1.15rem;
  line-height: 1.25;
  margin: 0;
}

.project-row-body p {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.55;
  margin: 0;
}

@media (max-width: 720px) {
  .project-list {
    grid-template-columns: 1fr;
    margin: -16px 0 0;
  }
}

/* ==========================================================================
   About / skills
   ========================================================================== */
.skills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 24px;
}

.skills-grid h3 {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-muted);
  margin: 0 0 10px;
}

.skills-grid ul {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.92rem;
  border-left: 1px solid var(--border);
}

.skills-grid li {
  padding: 4px 0 4px 12px;
}

/* ==========================================================================
   Project sidebar — table of contents for projects/index.html
   main.js builds the list from the h2/h3 structure and runs the scrollspy;
   this just lays out and states whatever it produces. Collapses away below
   the breakpoint, where the page reads exactly as it does without JS.
   ========================================================================== */
.project-shell { display: block; }

.project-sidenav { display: none; }

@media (min-width: 1360px) {
  .project-shell {
    display: grid;
    grid-template-columns: 240px minmax(0, 1fr);
    align-items: start;
    column-gap: 40px;
  }

  .project-sidenav {
    display: block;
    position: sticky;
    top: calc(var(--header-h) + 40px);
    max-height: calc(100vh - var(--header-h) - 72px);
    overflow-y: auto;
    padding: 4px 12px 4px 0;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
  }

  .project-sidenav::-webkit-scrollbar { width: 6px; }
  .project-sidenav::-webkit-scrollbar-thumb {
    background-color: var(--border-strong);
    border-radius: 999px;
  }
}

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

.sidenav-item + .sidenav-item { margin-top: 18px; }

.sidenav-title {
  display: block;
  text-decoration: none;
  color: var(--text-muted);
  font-size: 0.86rem;
  line-height: 1.35;
  letter-spacing: -0.005em;
  transition: color 0.2s ease;
}

.sidenav-title:hover { color: var(--text); }

/* The current project's title lights up, independent of whether a
   sub-heading within it is also current (e.g. right as it scrolls in). */
.sidenav-item.is-active > .sidenav-title {
  color: var(--text);
  font-weight: 600;
}

/* Sub-list height is animated via the grid-rows trick so it works for any
   amount of content without a hardcoded max-height, and collapses to
   nothing (no padding/margin bleed) when not the active project. */
.sidenav-sub {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.35s var(--ease-draw);
}

.sidenav-item.is-active .sidenav-sub {
  grid-template-rows: 1fr;
}

.sidenav-sub > ol {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: hidden;
}

.sidenav-item.is-active .sidenav-sub > ol {
  padding-top: 10px;
}

.sidenav-sub a {
  display: block;
  padding: 5px 0 5px 14px;
  border-left: 1px solid var(--border);
  text-decoration: none;
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1.3;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.sidenav-sub a:hover { color: var(--text); }

.sidenav-sub a.is-current {
  color: var(--text);
  border-left-color: var(--text);
}

/* ==========================================================================
   Project detail sections — stacked on projects/index.html
   ========================================================================== */
.project-hero {
  padding: 48px 0 0;
  /* Exactly the sticky header's height plus its 1px border, so a jump puts
     the section flush under the header and hides the divider above it. */
  scroll-margin-top: calc(var(--header-h) + 1px);
}

/* Sub-headings are jump targets from the sidenav too, so they need the same
   clearance under the sticky header, just a little looser since they are
   mid-read rather than a section start. */
.project-body h3 {
  scroll-margin-top: calc(var(--header-h) + 24px);
}

/* Drawn inside .wrap rather than on the section, so it spans only the text
   column and does not read as one of the full-bleed project separators. */
.project-hero > .wrap::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: 32px;
  background-color: var(--border);
}

.project-hero h2 {
  font-size: clamp(2rem, 5vw, 2.9rem);
  margin: 8px 0 20px;
}

.project-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.project-meta > div {
  padding: 10px 16px;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--text-muted);
  border-right: 1px solid var(--border);
}

.project-meta > div:last-child { border-right: none; }

.project-meta strong {
  display: block;
  color: var(--text);
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.68rem;
  margin-bottom: 4px;
}

.project-body {
  padding: 40px 0;
}

.project-body h3 {
  font-size: 1.4rem;
  margin: 40px 0 12px;
}

.project-body h3:first-child { margin-top: 0; }

.project-body p { margin: 0 0 16px; }

.project-body ul { margin: 0 0 16px; padding-left: 20px; }

.project-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin: 16px 0 8px;
}

.project-gallery .card-thumb { border-radius: var(--radius); }

.model-viewer-embed {
  display: block;
  width: 50%;
  height: 900px !important;
  background-color: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: 4px auto;
}

.model-viewer-embed-large {
  width: min(calc(78% + 192px), 100%);
  height: 480px !important;
}

@media (max-width: 720px) {
  .model-viewer-embed,
  .model-viewer-embed-large {
    height: 70vh !important;
  }
}

@media (max-width: 640px) {
  .model-viewer-embed,
  .model-viewer-embed-large {
    width: 85%;
  }
}

.model-viewer-embed [slot="poster"] {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--text-muted);
}

.model-caption {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 8px !important;
}

/* Model viewer sized to sit beside a still image in a media row, matching
   that image's aspect so the two line up. */
.model-viewer-inline {
  display: block;
  width: 100%;
  aspect-ratio: 1400 / 919;
  height: auto !important;
  background-color: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

@media (max-width: 720px) {
  .model-viewer-inline {
    aspect-ratio: 4 / 3;
  }
}

/* Portrait variant, for the tall U-joint model sitting beside body text. */
.model-viewer-inline.model-viewer-portrait {
  aspect-ratio: 633 / 955;
}

/* ==========================================================================
   Case-study extras — figures, media rows, callouts, contrib list, BOM
   ========================================================================== */
.project-body h4 {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: none;
  letter-spacing: -0.015em;
  font-size: 1.1rem;
  margin: 28px 0 10px;
  color: var(--text);
}

.project-body h4:first-child { margin-top: 0; }

.figure {
  margin: 16px 0 24px;
}

.figure img {
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

/* Cover-cropped figures default to a centre crop; bias one left when the
   detail that matters sits near the left edge of the source image. */
.figure img.crop-left {
  object-position: 30% 50%;
}

.figure-caption {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 8px;
  text-align: center;
}

/* A figure the prose wraps around, so the text closes back up underneath it
   instead of leaving a tall empty column beside a short image. */
.flow-figure::after {
  content: "";
  display: block;
  clear: both;
}

.flow-figure > p:last-child {
  margin-bottom: 0;
}

.figure-float {
  float: right;
  width: 34%;
  margin: 6px 0 20px 28px;
}

.figure-float.video-figure-portrait {
  width: 300px;
  max-width: 40%;
  margin-left: 28px;
  margin-right: 0;
}

@media (max-width: 720px) {
  .figure-float,
  .figure-float.video-figure-portrait {
    float: none;
    width: 100%;
    max-width: 100%;
    margin: 16px 0 24px;
  }
}

.video-figure video {
  width: 100%;
  display: block;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--code-bg);
}

/* Phone-shot clips are low-resolution at source, so cap the display width
   rather than letting the grid stretch and soften them. */
.video-figure-portrait {
  max-width: 330px;
  margin-left: auto;
  margin-right: auto;
}

.media-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin: 16px 0 24px;
  align-items: start;
}

.media-row .figure {
  margin: 0;
}

@media (max-width: 720px) {
  .media-row {
    grid-template-columns: 1fr;
  }
}

.media-row-narrow {
  width: 85%;
  margin-left: auto;
  margin-right: auto;
}

.media-row-narrow img {
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

@media (max-width: 720px) {
  .media-row-narrow {
    width: 100%;
  }
}

/* Columns are proportional to each image's aspect ratio, so the two figures
   land on exactly the same height whatever the container width. */
.media-row-lead {
  grid-template-columns: 0.83fr 1.5fr;
}

.media-row-lead .figure:first-child img {
  aspect-ratio: 0.831 / 1;
  object-fit: cover;
}

.media-row-lead .figure:last-child img {
  aspect-ratio: 1.5 / 1;
  object-fit: cover;
}

@media (max-width: 720px) {
  .media-row-lead {
    grid-template-columns: 1fr;
  }
}

.media-row-match img {
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* Car photo beside the wide frame CAD: columns follow each image's aspect
   ratio so both land on the same height. */
.media-row-baja {
  grid-template-columns: 1.66fr 1.521fr;
}

.media-row-baja .figure:first-child img {
  aspect-ratio: 415 / 250;
  object-fit: cover;
}

.media-row-baja .figure:last-child img {
  aspect-ratio: 1567 / 1030;
  object-fit: cover;
}

@media (max-width: 720px) {
  .media-row-baja {
    grid-template-columns: 1fr;
  }
}

.split-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  align-items: stretch;
  margin: 16px 0 24px;
}

.split-row-main {
  display: flex;
  flex-direction: column;
}

.split-row-main .figure {
  margin: 16px 0 0;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

.split-row-main .figure img {
  flex: 1 1 auto;
  object-fit: cover;
  min-height: 0;
}

.split-row-side {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.split-row-side img {
  flex: 1 1 auto;
  object-fit: cover;
  min-height: 0;
}

@media (max-width: 720px) {
  .split-row {
    grid-template-columns: 1fr;
  }

  .split-row-main .figure img,
  .split-row-side img {
    aspect-ratio: 4 / 3;
    flex: none;
  }
}

.text-figure-row {
  display: grid;
  grid-template-columns: 2.3fr 1.4fr;
  gap: 24px;
  align-items: stretch;
  margin: 16px 0 24px;
}

.text-figure-row-text p:last-child,
.text-figure-row-text ul:last-child {
  margin-bottom: 0;
}

.text-figure-row-2fr {
  grid-template-columns: 2fr 1fr;
}

/* For a very tall figure (the drive shaft bottom view) beside text. */
.text-figure-row-narrow {
  grid-template-columns: 2.6fr 1fr;
}

/* Keep the figure at its natural height rather than stretching it to the
   text, so tall diagrams are never cropped. */
.text-figure-row.text-figure-row-narrow {
  align-items: start;
}

.text-figure-row.text-figure-row-narrow .figure img {
  flex: none;
  height: auto;
  object-fit: contain;
  background: #fff;
}

/* Wider figure column for the IR robot course diagram. */
.text-figure-row.text-figure-row-path {
  grid-template-columns: 1.7fr 1fr;
}

@media (max-width: 720px) {
  .text-figure-row.text-figure-row-narrow .figure img {
    aspect-ratio: auto;
    width: 60%;
    margin: 0 auto;
  }
}

.text-figure-row-3col {
  grid-template-columns: 2.1fr 1fr 0.95fr;
}

.text-figure-row-3col .figure:nth-child(2) img {
  aspect-ratio: 1.12 / 1;
}

.text-figure-row-3col .figure:nth-child(3) img {
  aspect-ratio: 0.95 / 1;
}

.text-figure-row .figure,
.figure-stack .figure {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.text-figure-row .figure img,
.figure-stack .figure img {
  flex: 1 1 auto;
  object-fit: cover;
  min-height: 0;
}

/* A model viewer keeps its own aspect ratio rather than stretching to the
   text column, and sits at the top of it. */
.text-figure-row .figure model-viewer {
  flex: none;
}

.text-figure-row .figure:has(model-viewer) {
  justify-content: flex-start;
}

@media (max-width: 720px) {
  .text-figure-row {
    grid-template-columns: 1fr;
  }

  .text-figure-row .figure img,
  .figure-stack .figure img {
    aspect-ratio: 4 / 3;
    flex: none;
  }
}

.callout {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px 28px;
  margin: 16px 0 24px;
}

.callout .eyebrow { margin-bottom: 14px; }

.callout p:last-child,
.callout ul:last-child { margin-bottom: 0; }

.contrib-list {
  margin: 0 0 16px;
  padding-left: 20px;
}

@media (min-width: 640px) {
  .contrib-list {
    column-count: 2;
    column-gap: 32px;
  }
}

.contrib-list li {
  break-inside: avoid;
  margin-bottom: 10px;
  padding-right: 8px;
}

.bom-table-wrap {
  overflow-x: auto;
  max-width: 480px;
  margin: 16px 0 8px;
}

.bom-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.76rem;
  min-width: 380px;
}

.bom-table th,
.bom-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--border);
  text-align: left;
}

.bom-table th {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 500;
}

.bom-table td.num,
.bom-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.bom-table tr.total td {
  font-weight: 700;
  border-top: 2px solid var(--text);
  border-bottom: none;
}

.project-hero .lede {
  max-width: 62ch;
  color: var(--text-muted);
  font-size: 1.05rem;
  margin: 18px 0 0;
}

/* ==========================================================================
   Annotated hydrogen/oxygen electrolysis video
   ========================================================================== */
.hp-video {
  width: 100%;
  max-width: 100%;
  margin: 0;
}

.hp-video-media {
  display: block;
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.hp-video-row {
  position: relative;
  height: 58px;
}

.hp-sign {
  position: absolute;
  bottom: 8px;
  transform: translateX(-50%);
  z-index: 1;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 2px solid var(--accent);
  background: var(--surface);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
  font-family: var(--font-sans);
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
  padding: 0;
}

.hp-sign-neg { left: 28%; }
.hp-sign-pos { left: 77%; }

.hp-label {
  position: absolute;
  top: 6px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 1rem;
  line-height: 1.1;
  color: var(--text-muted);
  white-space: nowrap;
}

.hp-label-arrow {
  font-size: 1.3rem;
  line-height: 1;
  color: var(--accent);
}

.hp-label-hydrogen { left: 28%; }
.hp-label-oxygen { left: 77%; }

/* ==========================================================================
   Presentation viewer — clickable trigger + full-screen slide overlay
   ========================================================================== */
.figure-clickable {
  position: relative;
  display: block;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 0;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.figure-clickable img {
  transition: opacity 0.15s ease;
}

.figure-clickable:hover img,
.figure-clickable:focus-visible img {
  opacity: 0.88;
}

.figure-caption-center {
  text-align: center;
}

.figure-clickable::after {
  content: "View Presentation \2192";
  position: absolute;
  bottom: 16px;
  left: 16px;
  background: rgba(20, 18, 15, 0.85);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  pointer-events: none;
}

.figure-clickable:hover::after,
.figure-clickable:focus-visible::after {
  opacity: 1;
  transform: translateY(0);
}

/* Inline document reader: scroll a multi-page PDF in place rather than
   opening it in an overlay. Pages are pre-rendered images in a scroll box. */
.doc-reader {
  max-width: 520px;
  margin: 16px auto 8px;
}

.doc-reader .figure-caption {
  text-align: center;
}

.doc-frame {
  position: relative;
}

/* Hover cue for a control that is also a scroll region: lighten just the
   perimeter, so the page under the cursor stays readable while scrolling. */
.doc-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  pointer-events: none;
  box-shadow: inset 0 0 28px 14px rgba(5, 5, 6, 0);
  transition: box-shadow 0.18s ease;
}

.doc-frame:hover::after {
  box-shadow: inset 0 0 28px 14px rgba(5, 5, 6, 0.92);
}

.doc-open-hint {
  position: absolute;
  z-index: 1;                 /* above the edge wash, which paints after it */
  bottom: 16px;
  left: 16px;
  background: rgba(20, 18, 15, 0.85);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  pointer-events: none;
}

.doc-frame:hover .doc-open-hint {
  opacity: 1;
  transform: translateY(0);
}

.doc-scroll {
  height: 640px;
  margin: 0;
  cursor: pointer;
  overflow-y: auto;
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}

.doc-scroll::-webkit-scrollbar {
  width: 9px;
}

.doc-scroll::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.25);
  border-radius: 999px;
}

.doc-scroll::-webkit-scrollbar-thumb:hover {
  background-color: rgba(255, 255, 255, 0.45);
}

.doc-scroll img {
  width: 100%;
  height: auto;
  display: block;
  border-bottom: 1px solid var(--border);
}

.doc-scroll img:last-child {
  border-bottom: none;
}

.doc-scroll:hover {
  border-color: var(--accent);
}

/* Keyboard focus gets a ring instead, so a click does not leave the hover
   treatment stuck on. */
.doc-scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 640px) {
  .doc-scroll {
    height: 460px;
  }
}

.ppt-viewer-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: none;
}

.ppt-viewer-overlay.is-open {
  display: block;
}

body.viewer-open {
  overflow: hidden;
}

.ppt-viewer-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 18, 15, 0.55);
}

.ppt-viewer-panel {
  position: relative;
  z-index: 1;
  width: min(1196px, calc(100% - 48px));
  height: calc(100vh - 48px);
  margin: 24px auto;
  background: #111;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
}

.ppt-viewer-close {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 3;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

.ppt-viewer-close:hover,
.ppt-viewer-close:focus-visible {
  background: rgba(0, 0, 0, 0.8);
}

.ppt-viewer-scroll {
  height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.35) transparent;
}

.ppt-viewer-scroll::-webkit-scrollbar {
  width: 10px;
}

.ppt-viewer-scroll::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.35);
  border-radius: 999px;
}

.ppt-viewer-scroll::-webkit-scrollbar-thumb:hover {
  background-color: rgba(255, 255, 255, 0.55);
}

.ppt-viewer-scroll img {
  width: 100%;
  height: auto;
  display: block;
}

@media (max-width: 640px) {
  .ppt-viewer-panel {
    width: 100%;
    height: 100vh;
    margin: 0;
    border-radius: 0;
  }

  .ppt-viewer-close {
    top: 12px;
    right: 12px;
  }
}

.project-nav {
  display: flex;
  justify-content: space-between;
  padding: 8px 0 32px;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

.project-nav a {
  text-decoration: none;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.85rem;
}
.project-nav a:hover { color: var(--accent); }

.wip-note {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 24px;
  color: var(--text-muted);
  font-size: 0.95rem;
}

.wip-note strong { color: var(--text); }

/* ==========================================================================
   Combat robotics — embedded footage, build comparison, org chart, roadmap
   ========================================================================== */
.video-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--code-bg);
  overflow: hidden;
}

.video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.build-compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin: 16px 0 24px;
}

.build-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px 20px;
}

.build-card img {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 16px;
}

.build-card .eyebrow { margin-bottom: 4px; }

.project-body .build-card h4 { margin: 0 0 10px; }

.build-card p:last-child,
.build-card ul:last-child { margin-bottom: 0; }

@media (max-width: 720px) {
  .build-compare { grid-template-columns: 1fr; }
}

.roadmap {
  position: relative;
  list-style: none;
  margin: 16px 0 24px;
  padding: 0 0 0 30px;
}

.roadmap::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 8px;
  bottom: 8px;
  width: 2px;
  background: var(--border);
}

.roadmap li {
  position: relative;
  padding-bottom: 20px;
}

.roadmap li:last-child { padding-bottom: 0; }

.roadmap li::before {
  content: "";
  position: absolute;
  left: -29px;
  top: 5px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--text-muted);
}

.roadmap li.is-current::before {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

.roadmap-when {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.roadmap li.is-current .roadmap-when { color: var(--accent); }

.roadmap strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.05rem;
  margin: 2px 0;
}

.project-body .roadmap p { margin: 0; }

.credits {
  margin-top: 40px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  color: var(--text-muted);
}

.project-body .credits h4 { color: var(--text-muted); margin-top: 0; }

.credits a { color: var(--text); }

/* ==========================================================================
   Footer — title block
   ========================================================================== */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 0;
  color: var(--text-muted);
  font-size: 0.78rem;
  font-family: var(--font-mono);
}

.site-footer .wrap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

.site-footer .tb-cell {
  padding: 14px 16px;
  border-right: 1px solid var(--border);
}

.site-footer .tb-cell:last-child { border-right: none; }

.site-footer .tb-cell span {
  display: block;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.65rem;
  color: var(--text-muted);
  opacity: 0.7;
  margin-bottom: 3px;
}

.site-footer .tb-cell strong {
  color: var(--text);
  font-weight: 500;
}

.site-footer a { text-decoration: none; color: var(--text); }
.site-footer a:hover { color: var(--accent); }

/* ==========================================================================
   Dark shell — header, home hero, work grid, footer, about
   Everything below is the current site chrome and overrides the older
   light-theme rules above where class names overlap.
   ========================================================================== */

.wrap-wide {
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding: 0 56px;
}

@media (max-width: 860px) {
  .wrap-wide { padding: 0 24px; }
}

/* --------------------------------------------------------------------------
   Header — nav left, monogram centred, social buttons right
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.header-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: var(--header-h);
}

.nav-main {
  display: flex;
  align-items: center;
  gap: 30px;
  justify-self: start;
}

.nav-main a {
  position: relative;
  text-decoration: none;
  font-size: 0.92rem;
  letter-spacing: -0.01em;
  color: var(--text-muted);
  padding: 4px 0;
  transition: color 0.2s ease;
}

.nav-main a:hover { color: var(--text); }

/* The rule under the current page grows out from the left, matching the
   short underline under "Home" in the reference header. It is a keyframe
   animation rather than a transition: main.js adds .active on
   DOMContentLoaded, and on a light page like About that lands before the
   first style pass, so a transition had no starting state and the rule
   just appeared. An animation plays whenever the class arrives. */
.nav-main a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -14px;
  height: 1px;
  width: 100%;
  background: var(--text);
  transform: scaleX(0);
  transform-origin: left;
}

.nav-main a.active { color: var(--text); }

.nav-main a.active::after {
  transform: scaleX(1);
  animation: nav-rule-draw 0.35s var(--ease-draw);
}

@keyframes nav-rule-draw {
  from { transform: scaleX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .nav-main a.active::after { animation: none; }
}

.site-monogram {
  justify-self: center;
  display: flex;
  text-decoration: none;
  color: var(--text);
  padding: 6px 10px;
  transition: opacity 0.2s ease;
}

.site-monogram:hover { opacity: 0.6; }

.site-monogram .monogram-mark { width: 44px; height: 20px; }
.footer-mark .monogram-mark { width: 37px; height: 17px; }

/* Two M's sharing their touching leg, drawn as one filled path: three leg
   rectangles and four diagonal parallelograms, unioned by the nonzero fill
   rule. Built rather than set in a typeface because the shared middle leg
   has to be exactly as wide as the two outer ones — here all three are the
   same rectangle, so that holds by construction. Drawn heavy and short:
   legs 54 units wide and diagonals 48 on a 368 x 170 box, so the strokes
   read bolder and the mark sits lower than the letterforms beside it. Each
   M's valley runs to the baseline midway between its legs. */
.monogram-mark {
  display: block;
  fill: currentColor;
  stroke: none;
}

.header-social {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 10px;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 13px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.icon-btn > svg:first-child {
  width: 16px;
  height: 16px;
  display: block;
  flex: none;
}

.icon-btn:hover {
  color: var(--text);
  border-color: var(--border-strong);
  background: var(--accent-soft);
}

/* The small outbound arrow carried over from the reference header. */
.icon-btn .icon-arrow {
  width: 10px;
  height: 10px;
  flex: none;
  opacity: 0.55;
  transition: opacity 0.2s ease, transform 0.25s ease;
}

.icon-btn:hover .icon-arrow {
  opacity: 1;
  transform: translate(1px, -1px);
}

@media (max-width: 720px) {
  .nav-main { gap: 18px; }
  .nav-main a { font-size: 0.82rem; }
  .icon-btn { height: 32px; padding: 0 10px; gap: 6px; }
  .icon-btn > svg:first-child { width: 15px; height: 15px; }
}

@media (max-width: 520px) {
  .site-monogram { display: none; }
  .header-inner { grid-template-columns: 1fr auto; }
}

/* --------------------------------------------------------------------------
   Home hero
   -------------------------------------------------------------------------- */
.home-hero {
  padding: 86px 0 36px;
}

.home-title {
  font-size: clamp(2.6rem, 7.2vw, 5.4rem);
  line-height: 0.98;
  letter-spacing: -0.035em;
  font-weight: 500;
  margin: 0 0 18px;
}

.home-sub {
  max-width: 62ch;
  margin: 0;
  color: var(--text-muted);
  font-size: clamp(1rem, 1.35vw, 1.15rem);
  line-height: 1.5;
}

@media (max-width: 860px) {
  .home-hero { padding: 52px 0 28px; }
}

/* --------------------------------------------------------------------------
   Work grid — three across, no shadows
   -------------------------------------------------------------------------- */
.work-section { padding: 36px 0 90px; }

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px;
}

@media (max-width: 1040px) {
  .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 680px) {
  .work-grid { grid-template-columns: minmax(0, 1fr); }
}

.work-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 14px 14px 20px;
  border-radius: 16px;
  background: var(--surface-2);
  box-shadow: none;
  text-decoration: none;
  color: var(--text);
}

.work-card-media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface);
}

.work-card-media img,
.work-card-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}

/* The Baker Bot photo is near-square; anchor the crop so the head stays in. */
.work-card-media img[src*="baker-bot"] { object-position: center top; }

/* The electrolysis clip is a tall portrait video, so a centred crop lands on
   the dry top of the jar. Pull it down to the waterline, where both
   electrodes sit submerged and visibly bubbling. */
.work-card-media video[poster*="hydrogen-production"] { object-position: 50% 80%; }

.work-card:hover .work-card-media img,
.work-card:hover .work-card-media video { transform: scale(1.045); }

.work-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: calc(100% - 24px);
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(6, 6, 8, 0.78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fff;
  font-size: 0.74rem;
  letter-spacing: -0.005em;
  line-height: 1.2;
}

.work-card-body {
  margin-top: 18px;
  padding-left: 13px;
  border-left: 2px solid var(--border-strong);
}

.work-card-body h3 {
  margin: 0 0 9px;
  font-size: clamp(1.15rem, 1.6vw, 1.42rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.12;
}

.work-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--text-muted);
  font-size: 0.84rem;
}

/* The hover outline: one continuous stroke drawn clockwise from the
   top-left corner. main.js measures each card and writes --outline-len so
   the dash length matches the real perimeter; the fallback here is long
   enough to stay hidden if scripting is off. */
.work-card-outline {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  z-index: 2;
}

.work-card-outline path {
  fill: none;
  stroke: #ffffff;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: var(--outline-len, 3000);
  stroke-dashoffset: var(--outline-len, 3000);
  /* No transition until main.js has written the measured length. Animating
     the dash offset while that value is still changing would draw the border
     once on page load, which should only ever happen on hover. */
  transition: none;
}

.work-card.is-outline-ready .work-card-outline path {
  transition: stroke-dashoffset 0.85s var(--ease-draw);
}

.work-card:hover .work-card-outline path,
.work-card:focus-visible .work-card-outline path {
  stroke-dashoffset: 0;
}

.work-card:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .work-card.is-outline-ready .work-card-outline path,
  .work-card-media img,
  .work-card-media video { transition: none; }
}

/* --------------------------------------------------------------------------
   Footer — Explore / Connect columns, right aligned
   -------------------------------------------------------------------------- */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 48px 0 40px;
}

.footer-inner {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 48px;
}

.footer-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(120px, auto));
  gap: 0 72px;
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.footer-head {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.88rem;
}

.footer-col a {
  text-decoration: none;
  color: var(--text);
  font-size: 0.92rem;
  width: fit-content;
  transition: opacity 0.2s ease;
}

.footer-col a:hover { opacity: 0.55; }

.footer-mark {
  display: flex;
  text-decoration: none;
  color: var(--text);
  opacity: 0.85;
}

.footer-mark .monogram-mark { font-size: 1.3rem; }

@media (max-width: 680px) {
  .footer-inner { align-items: flex-start; gap: 36px; }
  .footer-cols { gap: 0 48px; }
}

/* --------------------------------------------------------------------------
   About — sticky bio left, scrolling image column right
   -------------------------------------------------------------------------- */
.about-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.62fr);
  gap: 80px;
  /* Top and bottom only: the shorthand would zero the side padding that
     .wrap-wide gives every page, which left the bio flush against the
     window edge and cut off the email button as it grew on hover. */
  padding-top: 72px;
  padding-bottom: 90px;
  align-items: start;
}

.about-intro {
  position: sticky;
  top: calc(var(--header-h) + 56px);
}

.about-intro h1 {
  margin: 0 0 22px;
  font-size: clamp(2.4rem, 5vw, 4rem);
  line-height: 1;
  letter-spacing: -0.035em;
  font-weight: 500;
}

/* The heading with a round portrait beside it. The photo is framed wide
   (head, shoulders and chest), so the circle runs a little larger than the
   heading's cap height to keep the face readable. */
.about-title {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2vw, 26px);
  margin: 0 0 22px;
}

.about-intro .about-title h1 { margin: 0; }

.about-portrait {
  flex: none;
  width: clamp(76px, 8vw, 118px);
  height: auto;
  aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
}

.about-lede {
  max-width: 54ch;
  margin: 0 0 18px;
  color: var(--text-muted);
  font-size: 1.02rem;
  line-height: 1.55;
}

.about-facts {
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  gap: 16px;
}

.about-fact {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  gap: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 0.92rem;
}

.about-fact dt {
  margin: 0;
  color: var(--text-muted);
}

.about-fact dd {
  margin: 0;
  color: var(--text);
}

.about-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 36px;
}

/* The image column scrolls on its own so the idle ticker can run without
   dragging the whole page with it. */
.about-gallery {
  height: calc(100vh - var(--header-h) - 96px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.about-gallery::-webkit-scrollbar { width: 0; height: 0; }

.about-gallery-track {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.about-shot {
  margin: 0;
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface-2);
}

.about-shot img {
  width: 100%;
  height: auto;
  display: block;
  transition: opacity 0.3s ease;
}

/* Dim the column on hover and bring back only the image under the pointer,
   so the caption that follows the cursor reads against a quiet backdrop. */
.about-gallery:hover .about-shot img { opacity: 0.6; }
.about-gallery .about-shot:hover img { opacity: 1; }

/* The About page is deliberately a single screen: the bio sits still on
   the left, the photo column scrolls on its own on the right, and the page
   itself never scrolls (which is why it carries no footer). */
body.about-page { overflow: hidden; }

.about-page main { height: calc(100vh - var(--header-h)); }

.about-page .about-layout {
  height: 100%;
  padding-top: 48px;
  padding-bottom: 44px;
  align-items: stretch;
}

.about-page .about-intro {
  position: static;
  align-self: center;
  /* On a short window the bio is the one thing that can outgrow its half
     of the screen, so it takes the overflow rather than getting clipped
     by the page that no longer scrolls. */
  max-height: calc(100% + 24px);
  overflow-y: auto;
  scrollbar-width: none;
  /* A scroll container clips on both axes, which cut the edges off the
     email button as it scales up on hover. The padding gives it room to
     grow; the matching negative margin keeps the text where it was. */
  padding: 12px;
  margin: -12px;
}

.about-page .about-intro::-webkit-scrollbar { width: 0; }

.about-page .about-gallery { height: 100%; }

@media (max-width: 900px) {
  .about-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
    padding-top: 44px;
    padding-bottom: 64px;
  }

  .about-intro { position: static; }

  .about-gallery { height: 70vh; }

  /* Stacked, it no longer fits on one screen, so give the page back. */
  body.about-page { overflow: auto; }
  .about-page main { height: auto; }

  .about-page .about-layout {
    height: auto;
    padding-top: 44px;
    padding-bottom: 64px;
    align-items: start;
  }

  .about-page .about-gallery { height: 70vh; }
}

/* Over a gallery the caption stands in for the pointer, so the pointer
   itself is hidden. main.js sets the class only once the caption exists,
   so without scripting the normal cursor stays. */
.has-cursor-caption [data-gallery] { cursor: none; }

/* The caption that rides along with the pointer over a gallery image. */
.cursor-caption {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 60;
  max-width: 320px;
  padding: 5px 11px;
  background: #ffffff;
  color: #050506;
  font-size: 0.95rem;
  line-height: 1.35;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease;
}

.cursor-caption.is-visible { opacity: 1; }

@media (hover: none) {
  .cursor-caption { display: none; }
}

/* --------------------------------------------------------------------------
   Other projects — the last entry on the Projects page: a strip that ticks
   sideways on its own, like the About photo column turned on its side
   -------------------------------------------------------------------------- */
/* Its own horizontal scroller. A vertical wheel over it still scrolls the
   page, since it has nowhere to go on that axis. */
.other-strip {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  border-radius: 12px;
}

.other-strip::-webkit-scrollbar { width: 0; height: 0; }

.other-track {
  display: flex;
  gap: 18px;
  width: max-content;
}

/* Fixed height and a 4:3 crop, so any photo dropped in later lines up with
   the rest. Four of them are always wider than the strip, which the
   seamless loop in main.js needs. */
.other-shot {
  flex: none;
  height: 260px;
  aspect-ratio: 4 / 3;
  margin: 0;
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface-2);
}

.other-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.3s ease;
}

.other-strip:hover .other-shot img { opacity: 0.6; }
.other-strip .other-shot:hover img { opacity: 1; }

@media (max-width: 640px) {
  .other-shot { height: 200px; }
}

/* --------------------------------------------------------------------------
   Cursor buddy canvas (home page only) — sits above the sticky header so the
   figure can start out perched on the monogram, and never takes a click.
   -------------------------------------------------------------------------- */
#cursor-buddy {
  position: fixed;
  inset: 0;
  z-index: 45;
  pointer-events: none;
}
