/* ============================================================
   Fonts — self-hosted latin subsets (SIL Open Font License).
   Variable files, so one file covers each face's weight range.
   ============================================================ */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/space-grotesk-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================
   Tokens
   Sage green (#5f6f52) on warm cream (#fefae0). One light
   palette: the colours are the identity, so no dark variant.
   ============================================================ */
:root {
  color-scheme: light;

  --cream:     oklch(0.9825 0.0341 82.89);
  --sage:      oklch(0.5205 0.0455 127.44);
  --sage-deep: oklch(0.3910 0.0392 128.83);
  --sage-soft: oklch(0.6655 0.0446 123.96);

  --bg:        var(--cream);
  --card:      oklch(0.995 0.022 82.89);
  --soft:      oklch(0.952 0.03 95);       /* chips, strips, image grounds */
  --text:      oklch(0.3082 0.0256 130.28);
  --text-dim:  oklch(0.534 0.038 130);
  --rule:      oklch(0.905 0.028 110);
  --line:      color-mix(in oklab, var(--sage) 18%, transparent);  /* card edges */

  --display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --sans:    "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --radius:  1.25rem;
  --ease:    cubic-bezier(0.16, 1, 0.3, 1);
  --measure: 72rem;
  --pad:     1.5rem;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Fine dot grain over the whole page, for warmth. Fixed, so it stays put
   while the content scrolls over it. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: .04;
  background-image: radial-gradient(var(--sage-deep) 1px, transparent 1px);
  background-size: 3px 3px;
  pointer-events: none;
}
main, .footer { position: relative; z-index: 1; }

h1, h2, h3, h4 { margin: 0; font-size: inherit; font-weight: inherit; line-height: 1.2; }
p, figure, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; }

::selection {
  background: color-mix(in oklab, var(--sage) 30%, transparent);
  color: var(--text);
}

/* No border-radius here: it would square off the pill buttons on focus. */
:focus-visible { outline: 2px solid var(--sage); outline-offset: 3px; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: .6rem 1rem;
  border-radius: 999px;
  background: var(--card);
}
.skip:focus { left: .75rem; top: .75rem; }

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

/* ============================================================
   Layout
   ============================================================ */
.wrap {
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.section { padding-block: 5rem; }
@media (min-width: 40rem) { .section { padding-block: 7rem; } }

/* ============================================================
   Shared pieces
   ============================================================ */
/* "// skills" — the mono label above every heading. */
.label {
  margin-bottom: 1rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .25em;
  text-transform: uppercase;
  color: var(--sage);
}

.section-head { margin-bottom: 3rem; }

.section-title {
  font-family: var(--display);
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.025em;
  color: var(--text);
}
@media (min-width: 40rem) { .section-title { font-size: 3rem; } }

/* Pill buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .75rem 1.75rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: transform .2s ease, background-color .2s ease;
}
.btn-sm { padding: .5rem 1.25rem; }
.btn-solid { background: var(--sage); color: var(--cream); }
.btn-ghost {
  border-color: color-mix(in oklab, var(--sage) 40%, transparent);
  color: var(--sage);
}
.btn-ghost:hover { background: color-mix(in oklab, var(--sage) 10%, transparent); }
@media (prefers-reduced-motion: no-preference) {
  .btn-solid:hover { transform: scale(1.05); }
}

/* Cards. Only cards that are links lift on hover — a lift promises a click. */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease), border-color .4s ease;
}
a.card:hover {
  border-color: color-mix(in oklab, var(--sage) 45%, transparent);
  box-shadow: 0 18px 40px -20px color-mix(in oklab, var(--sage-deep) 40%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  a.card:hover { transform: translateY(-4px); }
}

.chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.chips li {
  padding: .25rem .75rem;
  border-radius: 999px;
  background: var(--soft);
  color: var(--sage-deep);
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
}

.icon-btn {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid color-mix(in oklab, var(--sage) 40%, transparent);
  border-radius: 999px;
  background: none;
  color: var(--sage);
  cursor: pointer;
  transition: background-color .2s ease, opacity .2s ease;
}
.icon-btn:hover { background: color-mix(in oklab, var(--sage) 10%, transparent); }
.icon-btn svg {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon-btn[disabled] { opacity: .35; cursor: default; }
.icon-btn[disabled]:hover { background: none; }

/* Reveal on scroll. Hidden only while main.js is there to reveal it again
   (the <head> script adds .js), and only when motion is welcome. Stagger
   with style="--delay: 120ms". */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity .7s var(--ease), transform .7s var(--ease);
    transition-delay: var(--delay, 0ms);
  }
  .js .reveal.in { opacity: 1; transform: none; }
}

/* ============================================================
   Top bar
   ============================================================ */
.topbar {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  border-bottom: 1px solid transparent;
  transition: background-color .3s ease, border-color .3s ease;
}
/* Turns to glass once content scrolls under it (main.js adds .scrolled). */
.topbar.scrolled {
  background: color-mix(in oklab, var(--bg) 80%, transparent);
  border-bottom-color: color-mix(in oklab, var(--rule) 60%, transparent);
  -webkit-backdrop-filter: blur(12px);
          backdrop-filter: blur(12px);
}
@supports not (backdrop-filter: blur(12px)) {
  .topbar.scrolled { background: var(--bg); }
}

.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: var(--measure);
  margin-inline: auto;
  padding: 1rem var(--pad);
}

.wordmark {
  font-family: var(--display);
  font-size: 1.125rem;
  line-height: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--sage);
}
.wordmark span { color: color-mix(in oklab, var(--text) 40%, transparent); }

/* Section links, Contact included, in the top-right corner. Whichever
   section you're in gets the sage pill (main.js sets aria-current as you
   scroll; the project pages mark theirs in the HTML). */
.nav {
  display: flex;
  margin-left: auto;
  gap: .25rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.nav a {
  padding: .5rem .75rem;
  border-radius: 999px;
  white-space: nowrap;
  transition: color .2s ease, background-color .25s ease;
}
.nav a:hover { color: var(--sage); }
.nav a[aria-current="true"] { background: var(--sage); color: var(--cream); }
@media (min-width: 40rem) {
  .nav { gap: .75rem; }
  .nav a { padding-inline: 1.25rem; }
}
/* Small phones (under ~390px): tighter tracking and pills so all three
   links still share the row with the wordmark. */
@media (max-width: 24.4rem) {
  .topbar-inner { gap: .5rem; }
  .nav { gap: 0; letter-spacing: .04em; }
  .nav a { padding-inline: .625rem; }
}
/* The very smallest phones (~320px): narrower side margins too. */
@media (max-width: 21rem) {
  .topbar-inner { padding-inline: 1rem; }
  .nav a { padding-inline: .5rem; }
}

/* ============================================================
   Hero
   ============================================================ */
/* A column: the intro with the skills strip just beneath it, centred as
   one group in the first screen so the strip is in view on arrival. */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  min-height: 100vh;
  min-height: 100svh;
}

/* Soft sage glows drifting behind the hero. */
.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(64px);
  pointer-events: none;
}
.blob-a {
  top: 8rem;
  right: -5rem;
  width: 18rem;
  height: 18rem;
  background: color-mix(in oklab, var(--sage-soft) 40%, transparent);
}
.blob-b {
  bottom: 6rem;
  left: -6rem;
  width: 20rem;
  height: 20rem;
  background: color-mix(in oklab, var(--sage-soft) 30%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  .blob { animation: float 18s ease-in-out infinite; }
  .blob-b { animation-delay: 4s; }
}
@keyframes float {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(20px, -24px) scale(1.06); }
  66%      { transform: translate(-16px, 16px) scale(.96); }
}

.hero-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding: 7rem var(--pad) 2.5rem;
}
/* From tablet up: intro on the left, photo on the right, the photo centred
   against the intro above the buttons (the buttons get a row of their
   own). The intro never gets narrower than the name. */
@media (min-width: 40rem) {
  .hero-inner {
    display: grid;
    grid-template-columns: minmax(min-content, 1fr) minmax(0, 26rem);
    align-items: center;
    column-gap: 3rem;
  }
  .hero-intro { grid-area: 1 / 1; }
  .hero-cta   { grid-area: 2 / 1; }
  .hero-photo { grid-area: 1 / 2; }
}
.hero .label { margin-bottom: 1.5rem; }

/* A touch smaller once the photo shows, to leave it room. */
.hero-name { font-size: min(12rem, 18vw); }
@media (min-width: 40rem) { .hero-name { font-size: min(10rem, 12vw); } }
.hero-name h1 {
  font-family: var(--display);
  font-size: 1em;
  font-weight: 700;
  line-height: .95;
  letter-spacing: -0.04em;
  color: var(--sage);
}

/* The photo column; phones don't get it. A container, so the circle and
   the cut scale with the photo. */
.hero-photo {
  position: relative;
  display: none;
  justify-self: end;
  width: 100%;
  container-type: inline-size;
  pointer-events: none;
  user-select: none;
}
@media (min-width: 40rem) { .hero-photo { display: block; } }

/* Sage disc behind the shoulders, 87% of the photo's width. */
.hero-photo::before {
  content: "";
  position: absolute;
  bottom: 0;
  left: 6.5%;
  width: 87%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: color-mix(in oklab, var(--sage-soft) 45%, var(--cream));
}

/* The torso is cut along the disc's lower curve; above the disc's centre
   line everything shows, so the head rises out of the top. The disc's
   radius is 43.5cqi (half of 87%). */
.hero-photo img {
  position: relative;
  width: 100%;
  -webkit-mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - 43.5cqi) no-repeat,
    radial-gradient(circle 43.5cqi at 50% calc(100% - 43.5cqi), #000 99%, transparent 100%);
          mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - 43.5cqi) no-repeat,
    radial-gradient(circle 43.5cqi at 50% calc(100% - 43.5cqi), #000 99%, transparent 100%);
}

.hero-role {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
  margin-top: 2rem;
}
.role-title {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 2rem;
  font-weight: 600;
}
@media (min-width: 40rem) { .role-title { font-size: 1.875rem; line-height: 2.25rem; } }
.role-org {
  font-family: var(--mono);
  font-size: .875rem;
  line-height: 1.25rem;
  color: var(--text-dim);
}

/* Typed in by main.js. Hidden until it starts, so the full title doesn't
   flash first; the animation unhides it anyway if the script never runs. */
@media (prefers-reduced-motion: no-preference) {
  .js [data-type]:not(.typing) {
    visibility: hidden;
    animation: unhide 0s 1.5s forwards;
  }
  .cursor { animation: blink 1.1s steps(2) infinite; }
}
@keyframes unhide { to { visibility: visible; } }
@keyframes blink { 50% { opacity: 0; } }
.cursor { color: var(--sage); font-weight: 400; }

.hero-lede {
  max-width: 36rem;
  margin-top: 2rem;
  font-size: 1.125rem;
  line-height: 1.625;
  color: var(--text-dim);
  text-wrap: pretty;
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 2.5rem;
}

/* Short screens (e.g. 1366×768 laptops): tighten the intro so the skills
   strip still lands on the first screen. The name also scales with height. */
@media (max-height: 50rem) {
  .hero-inner { padding-top: 5.5rem; padding-bottom: 1.5rem; }
  .hero-role, .hero-lede { margin-top: 1.5rem; }
  .hero-cta { margin-top: 1.75rem; }
}
@media (min-width: 40rem) and (max-height: 50rem) {
  .hero-name { font-size: min(10rem, 12vw, 21vh); }
  /* Capped by height too, so the head stays clear of the top bar. */
  .hero-photo { max-width: 44vh; }
}

/* ============================================================
   Projects — tiles on the home page
   ============================================================ */
.projects { display: grid; gap: 1.5rem; }
@media (min-width: 48rem) { .projects { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.tile { display: flex; flex-direction: column; height: 100%; padding: 1.75rem; }
.tile-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.tile-num { color: var(--sage); }
.tile-arrow { display: inline-block; margin-left: .25rem; transition: transform .3s var(--ease), color .3s ease; }

/* Kept short: a glimpse of the project, not the showcase. */
.tile-img {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  margin-bottom: 1.5rem;
  border: 1px solid var(--line);
  border-radius: .875rem;
  background: var(--soft);
}
.tile-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  transition: transform .6s var(--ease);
}

.tile h3 {
  margin-bottom: .75rem;
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 1.25;
  font-weight: 600;
  color: var(--text);
  text-wrap: balance;
}
.tile p { line-height: 1.625; color: var(--text-dim); text-wrap: pretty; }
.tile-more {
  margin-top: auto;
  padding-top: 1.5rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sage);
}
@media (prefers-reduced-motion: no-preference) {
  .tile:hover .tile-arrow { transform: translate(2px, -2px); color: var(--sage); }
  .tile:hover .tile-img img { transform: scale(1.03); }
}

/* ============================================================
   Project pages
   ============================================================ */
.detail { padding-block: 7rem 5rem; }
@media (min-width: 40rem) { .detail { padding-top: 8rem; } }

.back {
  display: inline-block;
  margin-bottom: 2.5rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sage);
}
.back:hover { text-decoration: underline; text-underline-offset: 3px; }

.detail-title {
  max-width: 18ch;
  font-family: var(--display);
  font-size: clamp(2.5rem, 7vw, 4.5rem);
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--text);
  text-wrap: balance;
}
.detail-lede {
  max-width: 44rem;
  margin-top: 1.5rem;
  font-size: 1.125rem;
  line-height: 1.625;
  color: var(--text-dim);
  text-wrap: pretty;
}
.detail-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: 2rem;
}

.detail-media { margin-top: 3rem; }
/* The mockup is nearly square; capped so it doesn't fill two screens. */
.figure.detail-media { max-width: 44rem; }
.shots.detail-media { max-width: 60rem; }

.detail-section {
  margin-top: 4rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--line);
}
.detail-section .highlights { margin-top: 1.5rem; padding-top: 0; border-top: 0; }

.next {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1.25rem;
  margin-top: 5rem;
  padding: 1.5rem 1.75rem;
}
.next-label {
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sage);
}
.next-title { font-family: var(--display); font-size: 1.25rem; font-weight: 600; }
.next-arrow { margin-left: auto; font-size: 1.25rem; color: var(--sage); }

.figure img {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: .875rem;
  background: var(--soft);
}
.figure figcaption {
  margin-top: .75rem;
  font-size: .8125rem;
  line-height: 1.5;
  color: var(--text-dim);
  text-wrap: pretty;
}

/* Screenshot carousel: horizontal scroll-snap, so touch gets native swipe
   and the arrows drive scrollTo() on desktop. No library. */
.shots-frame {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  border: 1px solid var(--line);
  border-radius: .875rem;
  background: var(--soft);
  scrollbar-width: none;
}
.shots-frame::-webkit-scrollbar { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .shots-frame { scroll-behavior: smooth; }
}
/* The screenshots differ in aspect ratio; centre the shorter ones. */
.shot {
  display: flex;
  align-items: center;
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: center;
}
.shot img { width: 100%; }

.shots-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: .75rem;
}
.shots-caption {
  min-height: 1.25em;
  font-size: .8125rem;
  line-height: 1.5;
  color: var(--text-dim);
}
.shots-nav { display: flex; flex-shrink: 0; align-items: center; gap: .5rem; }
.shots-count {
  margin-right: .25rem;
  font-family: var(--mono);
  font-size: .75rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Engineering highlights: the lead-in on its own line, then the detail. */
.highlights {
  display: grid;
  gap: 1.5rem;
  margin-top: 2.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
}
@media (min-width: 60rem) {
  .highlights { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2.5rem; }
}
.highlights li {
  font-size: .9375rem;
  line-height: 1.65;
  color: var(--text-dim);
  text-wrap: pretty;
}
.highlights strong {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  margin-bottom: .375rem;
  font-family: var(--display);
  font-size: 1.0625rem;
  line-height: 1.4;
  font-weight: 600;
  color: var(--text);
}
/* Dot centred on the first line, however the lead-in wraps. */
.highlights strong::before {
  content: "";
  flex-shrink: 0;
  width: .375rem;
  height: .375rem;
  margin-top: calc((1.4em - .375rem) / 2);
  border-radius: 50%;
  background: color-mix(in oklab, var(--sage) 60%, transparent);
}

/* Stack: what each service in a project is actually responsible for. */
.stack { display: grid; gap: .75rem; margin-top: 1rem; }
@media (min-width: 48rem) {
  .stack { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.stack div {
  padding: 1rem 1.25rem;
  border: 1px solid var(--line);
  border-radius: .875rem;
  background: color-mix(in oklab, var(--soft) 55%, transparent);
}
.stack dt {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--display);
  font-weight: 600;
  color: var(--text);
}
/* Brand marks are solid shapes, unlike the stroked UI icons. */
.stack dt svg {
  flex-shrink: 0;
  width: .875rem;
  height: .875rem;
  fill: currentColor;
  color: var(--sage);
}
.stack dd {
  margin-top: .25rem;
  font-size: .875rem;
  line-height: 1.5;
  color: var(--text-dim);
}

/* Caveat line, e.g. confidentiality on client work. */
.note {
  margin-top: 2rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1.5;
  color: var(--text-dim);
}

/* ============================================================
   Experience timeline
   A spine through the centred column: where (company or school,
   dates) on the left, what (the role and its detail) on the right.
   On narrow screens the spine moves to the left edge and each
   entry stacks.
   ============================================================ */
.section-head.center { text-align: center; }

.timeline {
  --spine: .5rem;          /* spine's x position on narrow screens */
  position: relative;
  display: grid;
  gap: 3.5rem;
}
/* About 70% of the screen, centred. Never narrower than the column (or
   60rem) allows, and on very wide screens it may reach past the page's
   72rem column; the negative margins keep it centred when it does. */
@media (min-width: 48rem) {
  .timeline {
    --width: max(70vw, min(100%, 60rem));
    width: var(--width);
    margin-inline: calc((100% - var(--width)) / 2);
  }
}
.timeline::before {
  content: "";
  position: absolute;
  top: .5rem;
  bottom: 0;
  left: var(--spine);
  width: 2px;
  transform: translateX(-50%);
  background: color-mix(in oklab, var(--sage) 25%, transparent);
}

.role {
  position: relative;
  display: grid;
  gap: .75rem;
  padding-left: 2.5rem;
}
/* Ring on the spine, level with the first line. */
.role::before {
  content: "";
  position: absolute;
  top: .5rem;
  left: var(--spine);
  width: 1rem;
  height: 1rem;
  transform: translateX(-50%);
  border: 2px solid var(--sage);
  border-radius: 50%;
  background: var(--bg);
}

/* Names get 28% of the width and the detail 72%: the right carries far
   more text, so the spine sits left of centre and longer names wrap.
   Every entry shares those columns (subgrid). Because the spine's position
   now depends on the names, the rings and spine hang off the detail column
   instead of a fixed x. */
@media (min-width: 48rem) {
  .timeline {
    --gap: 5rem;
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);   /* tablets: names need room */
    column-gap: var(--gap);
  }
  .timeline::before, .role::before { display: none; }
  .role {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    column-gap: var(--gap);
    padding-left: 0;
  }
  .role-side { text-align: right; }
  .role-main { position: relative; }
  /* Ring, level with the first line. */
  .role-main::before {
    content: "";
    position: absolute;
    z-index: 1;
    top: .5rem;
    left: calc(var(--gap) / -2);
    width: 1rem;
    height: 1rem;
    transform: translateX(-50%);
    border: 2px solid var(--sage);
    border-radius: 50%;
    background: var(--bg);
  }
  /* Spine: from this ring down through the row gap to the next one. */
  .role-main::after {
    content: "";
    position: absolute;
    top: .5rem;
    bottom: -3.5rem;
    left: calc(var(--gap) / -2);
    width: 2px;
    transform: translateX(-50%);
    background: color-mix(in oklab, var(--sage) 25%, transparent);
  }
  .role:last-child .role-main::after { bottom: 0; }
}
@media (min-width: 64rem) {
  .timeline { grid-template-columns: minmax(0, 28fr) minmax(0, 72fr); }
}

.org {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 2rem;
  font-weight: 600;
  color: var(--text);
  text-wrap: balance;
}
.when {
  margin-top: .25rem;
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1.5rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sage);
}

.role h3 {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 2rem;
  font-weight: 600;
  color: var(--sage);
}
.role-main > p {
  margin-top: .75rem;
  line-height: 1.625;
  color: var(--text-dim);
  text-wrap: pretty;
}

/* Featured films: supporting evidence inside the entry, not a showcase. */
.role-main > .films-lede { margin: 1.5rem 0 1rem; font-size: .9375rem; }
/* Always two across: stacked, each 9:16 card fills a phone screen. */
.films {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  max-width: 22rem;
}
.film-card {
  position: relative;
  display: block;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  color: #fff;
}
.film-card img { width: 100%; height: 100%; object-fit: cover; }
/* Scrim so the caption stays readable over any frame. */
.film-meta {
  position: absolute;
  inset: auto 0 0;
  display: grid;
  gap: .15rem;
  padding: 2.5rem .75rem .75rem;
  background: linear-gradient(to top, rgba(0,0,0,.8), rgba(0,0,0,.4) 45%, transparent);
}
.film-kicker { font-family: var(--mono); font-size: .625rem; color: rgba(255,255,255,.8); }
.film-title { font-family: var(--display); font-size: .875rem; font-weight: 600; line-height: 1.3; }

/* ---- Lightbox ---- */
.film-modal {
  width: min(30rem, 94vw);
  max-width: none;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--text);
}
.film-modal::backdrop {
  background: color-mix(in oklab, var(--sage-deep) 70%, transparent);
  -webkit-backdrop-filter: blur(4px);
          backdrop-filter: blur(4px);
}
.film-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .625rem .625rem .625rem 1.25rem;
  border-bottom: 1px solid var(--line);
}
.film-bar-title { font-family: var(--display); font-weight: 600; }
.film-frame iframe {
  display: block;
  width: 100%;
  height: min(76vh, 44rem);
  border: 0;
  /* LinkedIn's embed is always white; match it so a slow load doesn't flash. */
  background: #fff;
}
.film-fallback {
  padding: .625rem 1.25rem .75rem;
  font-size: .8125rem;
  color: var(--text-dim);
}
.film-fallback a { color: var(--sage); text-decoration: underline; text-underline-offset: 2px; }
/* The dialog blocks interaction but not scroll. */
html:has(.film-modal[open]) { overflow: hidden; }

/* ============================================================
   Skills strip — along the foot of the hero
   ============================================================ */
.marquee {
  position: relative;
  z-index: 1;
  overflow: hidden;
  padding-block: 1.25rem;
}
/* Static: a wrapped, centred list (no JS, or reduced motion). */
.marquee-track {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .5rem 0;
  padding-inline: var(--pad);
}
/* Spacing lives on each item, not in a gap, so the doubled track is exactly
   two identical halves and the loop has no jump. */
.marquee-track li {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding-right: 1rem;
  white-space: nowrap;
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 2rem;
  font-weight: 600;
  color: color-mix(in oklab, var(--text) 80%, transparent);
}
.marquee-track li::after { content: "✦"; color: var(--sage); }

/* Moving: main.js slides the track, pauses it under the mouse, and lets
   the wheel or a drag move it. Vertical touch still scrolls the page. */
.marquee-track.is-moving {
  flex-wrap: nowrap;
  justify-content: flex-start;
  width: max-content;
  padding-inline: 0;
  will-change: transform;
}
.marquee:has(.is-moving) {
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  /* No rules or band: items drift in and out through soft edges. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
.marquee.dragging { cursor: grabbing; }

/* Short screens: a slimmer strip, so it still fits on the first screen. */
@media (max-height: 50rem) {
  .marquee { padding-block: .75rem; }
  .marquee-track li { font-size: 1.25rem; line-height: 1.75rem; }
}

/* ============================================================
   Contact
   ============================================================ */
.contact-title {
  font-family: var(--display);
  font-size: 3rem;
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.025em;
}
@media (min-width: 40rem) { .contact-title { font-size: 4.5rem; } }
.contact-title span { color: var(--sage); }
.contact-lede {
  max-width: 28rem;
  margin-top: 1.5rem;
  font-size: 1.125rem;
  line-height: 1.75rem;
  color: var(--text-dim);
}
/* Centred: heading, line and link cards all sit on the page's middle. */
.contact { text-align: center; }
.contact .contact-lede { margin-inline: auto; }
.contact-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem; margin-top: 3rem; }
.contact-links a {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.25rem 1.75rem;
  font-family: var(--display);
  font-size: 1.125rem;
  font-weight: 600;
}
.contact-links a span {
  font-family: var(--mono);
  font-size: .75rem;
  font-weight: 400;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sage);
}

/* ============================================================
   Footer
   ============================================================ */
.footer { border-top: 1px solid color-mix(in oklab, var(--rule) 50%, transparent); }
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: var(--measure);
  margin-inline: auto;
  padding: 2.5rem var(--pad);
  font-family: var(--mono);
  font-size: .75rem;
  line-height: 1rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.footer-inner a { color: var(--sage); }
.footer-inner a:hover { text-decoration: underline; }

/* ============================================================
   404
   ============================================================ */
.notfound {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1.5rem;
  min-height: 100vh;
  min-height: 100svh;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.notfound .label { margin-bottom: 0; }
.notfound h1 {
  font-family: var(--display);
  font-size: min(9rem, 20vw);
  line-height: .95;
  font-weight: 700;
  letter-spacing: -0.04em;
  color: var(--sage);
}
.notfound > p:not(.label) { font-size: 1.125rem; color: var(--text-dim); }

@media print {
  body::before, .topbar, .blob, .icon-btn { display: none; }
  .js .reveal { opacity: 1 !important; transform: none !important; }
}
