/* ============================================================
   Qaiser Farooq — Portfolio
   Design tokens, layout, and motion.
   Motion follows: strong ease-out, <300ms for UI, transform/
   opacity only, hover gated to fine pointers, reduced-motion
   honoured. See the bottom of this file for the motion rules.
   ============================================================ */

:root {
  /* ---------- palette ----------
     A warm near-black base so gold reads as light, not as paint.
     One signature accent (gold), one secondary (rose), used sparingly. */
  /* Sampled from the intro clip's studio backdrop so the video sits on
     the page without a visible panel edge: #8f2542 at its brightest
     through #51071b in the darkened corners. */
  --ink-900: #2A0312;
  --ink-850: #3D0619;
  --ink-800: #51071B;
  --ink-700: #7A1F37;
  --video-lit: #8F2542;
  --paper:   #F7F4F2;
  --paper-2: #EDE7E3;
  --gold:    #F2B441;
  --gold-hi: #FFD37A;
  --rose:    #D14D70;

  /* ---------- text ---------- */
  --on-dark:        rgba(255, 255, 255, .94);
  /* Lifted from .60/.36: the hero is lit crimson now rather than near
     black, so the old values fell to 2.9:1 and 2.3:1 against it. These
     clear 4.5:1 on the brightest part of the backdrop. */
  --on-dark-mut:    rgba(255, 255, 255, .72);
  --on-dark-faint:  rgba(255, 255, 255, .62);
  --on-paper:       #2A0312;
  --on-paper-mut:   rgba(42, 3, 18, .62);
  --on-paper-faint: rgba(42, 3, 18, .40);

  /* ---------- depth ----------
     Semi-transparent shadows instead of solid borders. The
     0 0 0 1px layer is a hairline drawn as a shadow, so it
     sits on the elevation scale rather than fighting it. */
  --shadow-1: 0 1px 2px rgba(0,0,0,.18), 0 0 0 1px rgba(255,255,255,.06);
  --shadow-2: 0 8px 24px -8px rgba(0,0,0,.42), 0 0 0 1px rgba(255,255,255,.07);
  --shadow-3: 0 28px 64px -20px rgba(0,0,0,.60), 0 0 0 1px rgba(255,255,255,.09);
  --shadow-p1: 0 1px 2px rgba(30,18,25,.06), 0 0 0 1px rgba(30,18,25,.07);
  --shadow-p2: 0 10px 28px -12px rgba(30,18,25,.20), 0 0 0 1px rgba(30,18,25,.08);
  --shadow-p3: 0 30px 64px -24px rgba(30,18,25,.30), 0 0 0 1px rgba(30,18,25,.09);

  /* ---------- motion ---------- */
  --ease-out:    cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);
  --t-press: 140ms;
  --t-hover: 200ms;
  --t-enter: 420ms;

  /* ---------- layout ---------- */
  --max: 1320px;
  --gut: clamp(20px, 5vw, 72px);
  --sec-y: clamp(72px, 9vw, 132px);

  /* ---------- type ---------- */
  --sans:  "DM Sans", system-ui, -apple-system, sans-serif;
  --serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --mono:  "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ---------- base ---------- */

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  background: var(--ink-900);
  /* keep anchored sections clear of the fixed nav */
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  background: var(--ink-900);
  color: var(--on-dark);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

::selection { background: var(--gold); color: var(--ink-900); }

a { color: inherit; text-decoration: none; }

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

h1, h2, h3, h4 { margin: 0; font-weight: 700; letter-spacing: -.03em; }

/* Visible focus for keyboard users only — the old build removed
   outlines entirely, which left keyboard nav untraceable. */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: 4px;
}
:focus:not(:focus-visible) { outline: none; }

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
}

/* shared small-caps / mono label */
.mono {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* Nav sentinel: sits at the top of the hero, so the nav turns solid
   after ~72px of scroll and stays solid. Anchoring it to the BOTTOM of
   the hero instead makes the nav go transparent again when the hero
   base scrolls back into view, dropping hero text under bare nav links. */
#nav-sentinel {
  position: absolute;
  top: 0; left: 0;
  width: 1px; height: 72px;
  pointer-events: none;
}

/* ---------- nav ---------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 16px var(--gut);
  background: transparent;
  transition: background var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              padding var(--t-hover) var(--ease-out);
}

/* condensed state — set by JS from a sentinel, not a scroll handler */
.nav.is-stuck {
  padding-block: 11px;
  background: rgba(18, 10, 15, .72);
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  box-shadow: 0 1px 0 rgba(255,255,255,.08), 0 12px 32px -16px rgba(0,0,0,.7);
}

.brand { display: flex; align-items: center; gap: 11px; }

.monogram {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--gold);
  color: var(--ink-900);
  font-family: var(--mono);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: .02em;
  transition: transform var(--t-hover) var(--ease-out);
}

.brand-name {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--on-dark-mut);
  transition: color var(--t-hover) ease;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 30px);
}

.nav-links a {
  position: relative;
  padding-block: 4px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--on-dark-mut);
  transition: color var(--t-hover) ease;
}

/* active-section indicator — scales from the left, so it reads
   as the underline growing rather than fading in place */
.nav-links a:not(.nav-cta)::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-hover) var(--ease-out);
}
.nav-links a.is-active { color: var(--on-dark); }
.nav-links a.is-active::after { transform: scaleX(1); }

.nav-cta {
  padding: 9px 18px;
  border-radius: 999px;
  background: var(--rose);
  color: #fff;
  transition: background var(--t-hover) ease,
              transform var(--t-press) var(--ease-out);
}
.nav-cta::after { display: none; }

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: stretch;   /* the grid owns the full height, so the clip
                             can be sized structurally rather than with
                             a viewport calc that svh rounds differently */
  overflow: hidden;
  /* Sits on the site's own palette and lands on --ink-900 at the base,
     which is exactly what the section below it uses, so the hero reads
     as part of the page rather than a lit panel dropped onto it.

     It used to be sampled from the clip's studio backdrop, back when
     the clip still showed that backdrop and the two had to agree. The
     subject is cut out now, so there is nothing left to match — keeping
     those studio colours only made the hero clash with everything
     below it. */
  background:
    radial-gradient(78% 58% at 70% 26%, rgba(209,77,112,.18), transparent 64%),
    linear-gradient(to bottom,
      var(--ink-800) 0%,
      var(--ink-850) 48%,
      var(--ink-900) 100%)
}



.hero-grid {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding: 96px var(--gut) 0;
  display: grid;
  grid-template-columns: 1.1fr .9fr;
  /* Both columns start from the same edge; the copy's own padding-top
     then drops the location line to the subject's hairline (~6.5% down
     the trimmed art) instead of the crown. */
  align-items: start;
  grid-template-rows: 1fr;
  gap: clamp(24px, 4vw, 56px);
}

.hero-copy { grid-column: 1; grid-row: 1; padding-top: 47px; padding-bottom: 0; }

.eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold);
}
.rule { width: 36px; height: 1px; background: var(--gold); opacity: .7; }

h1 {
  /* Capped at 92px, not 112px: past ~1400px the type outgrew its column
     and "Qaiser Farooq." broke onto a second line, so the hero looked
     different at 1280 than at 1440+. This keeps it one line across the
     whole desktop range. */
  font-size: clamp(52px, 7.4vw, 92px);
  line-height: .92;
  letter-spacing: -.045em;
}

/* Sans, bold. It matches the headline's weight, so the gold and the
   smaller size are what keep it subordinate to the name. */
.role {
  margin: 10px 0 0;
  font-family: var(--sans);
  font-style: normal;
  font-weight: 700;
  font-size: clamp(26px, 3.6vw, 50px);
  line-height: 1.06;
  letter-spacing: -.028em;
  color: var(--gold);
}

.lede {
  margin: 26px 0 0;
  max-width: 46ch;
  font-size: 16.5px;
  line-height: 1.68;
  color: var(--on-dark-mut);
  text-wrap: pretty;
}

.cta-row { display: flex; align-items: center; gap: 12px; margin-top: 26px; }

.btn-primary {
  padding: 15px 28px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--ink-900);
  font-size: 14.5px;
  font-weight: 600;
  box-shadow: var(--shadow-2);
  transition: background var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-press) var(--ease-out);
}

.btn-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 50px; height: 50px;
  border-radius: 50%;
  background: rgba(255,255,255,.06);
  color: var(--on-dark);
  font-size: 17px;
  box-shadow: var(--shadow-1);
  transition: background var(--t-hover) ease,
              transform var(--t-hover) var(--ease-out);
}

.stats {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(28px, 4vw, 56px);
  margin-top: clamp(38px, 5vh, 62px);
}
.stat-value {
  font-size: clamp(32px, 3.4vw, 44px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--on-dark);
  font-variant-numeric: tabular-nums;
}
.stat-label {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-dark-faint);
}

.hero-photo {
  grid-column: 2; grid-row: 1;
  align-self: stretch;
  position: relative;
  /* Bleed past the page gutter to the viewport edge. The clip already
     meets the nav at the top and the ticker at the bottom, so this
     leaves only its left side as an internal boundary — and that one
     edge is matched exactly by the sampled backdrop. Three of four
     edges simply stop existing, which is why the panel disappears. */
  /* Past --max the grid stops growing and centres, so pulling back by
     the gutter alone leaves a gap on wide screens. Also pull back by
     half the leftover width. */
  margin-right: calc((var(--gut) + max(0px, (100vw - var(--max)) / 2)) * -1);
}


.hero-photo {
  display: flex;
  align-items: flex-end;      /* keeps him standing on the ticker */
  justify-content: flex-end;
}

.hero-photo .hero-canvas {
  /* Pulled back from filling the full nav-to-ticker band so he reads at
     a more natural distance rather than looming. contain (not cover)
     means nothing is cropped at this smaller size — the whole figure
     stays in frame. */
  height: 86%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: bottom center;
}

/* Kept off-screen rather than display:none. WebKit (Safari, and iOS
   Chrome/Firefox which sit on the same engine) suspends decoding for
   display:none video — the element loads metadata but never actually
   plays, which reads as the intro silently never starting on a real
   phone even though it works in every desktop test.

   opacity: 0 alone was not enough: iOS Safari's autoplay allowance
   appears to weigh effective opacity, and rejected play() on a fully
   transparent element — which our own code then handled exactly as
   designed for a rejected autoplay, by jumping straight to the last
   frame and freezing there. That produced a real, correctly rendered
   cut-out, just with no visible turn — the still frame the report
   described. 1% opacity is not perceptible against the canvas sitting
   directly on top of it, but is enough to read as "visible" to that
   heuristic. */
.hero-clip {
  /* Rendered, but 1x1 and effectively invisible. It cannot be
     display:none — WebKit suspends decoding for video that is not in
     the render tree, which stops the clip ever playing. But it also
     cannot cover the hero at 1% opacity, as it did before: that laid a
     faint wash of the studio backdrop over the whole area, which read
     as a rectangle. Decode resolution is independent of display size,
     so 1x1 costs the shader nothing. */
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0.01;
  pointer-events: none;
  z-index: -1;
}
.js .hero-photo .hero-still { display: none; }
.hero-photo.clip-failed .hero-still { display: block; }
.hero-photo.clip-failed .hero-canvas { display: none; }

.hero-photo canvas,
.hero-photo img {
  display: block;
  width: 100%;
  /* explicit, or the HTML height attribute acts as a presentational
     hint and max-height clamps it into a box the wrong shape,
     letterboxing the cutout with dead space above it */
  height: auto;
  max-height: calc(100svh - 96px);
  object-fit: contain;
  object-position: bottom center;
  /* one shadow pass instead of three stacked drop-shadows */
  filter: drop-shadow(0 24px 48px rgba(0,0,0,.62));
}

/* ---------- ticker ---------- */

.ticker {
  position: relative;
  overflow: hidden;
  padding: 15px 0;
  background: var(--gold);
}
.ticker-track {
  display: flex;
  width: max-content;
  animation: marqueeX 34s linear infinite;
}
.ticker-item {
  display: flex;
  align-items: center;
  gap: 22px;
  padding-right: 22px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--ink-900);
  white-space: nowrap;
}
.ticker-dot { width: 5px; height: 5px; border-radius: 50%; background: rgba(18,10,15,.45); }

/* ---------- section shells ---------- */

.section { position: relative; padding: var(--sec-y) var(--gut); }
.section > * { max-width: var(--max); margin-inline: auto; }

.section-light { background: var(--paper); color: var(--on-paper); }
.section-deep  { background: var(--ink-850); }
.section-deep-alt { background: var(--ink-900); }

/* hairline seam between adjacent dark sections */
.section-deep::before,
.section-deep-alt::before {
  content: "";
  position: absolute;
  inset: 0 var(--gut) auto;
  height: 1px;
  background: rgba(255,255,255,.07);
}

.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 5vw, 64px);
}
.section-head-stack { position: relative; }

.label {
  display: inline-block;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
}
.label-dark { color: var(--rose); }
.label-gold { color: var(--gold); }

h2 {
  margin: 14px 0 0;
  font-size: clamp(36px, 4.6vw, 64px);
  line-height: 1.02;
  letter-spacing: -.038em;
}
.h2-dark  { color: var(--on-paper); }
.h2-light { color: var(--on-dark); }

.section-note {
  margin: 0;
  max-width: 34ch;
  font-size: 15px;
  line-height: 1.7;
  color: var(--on-paper-mut);
  text-wrap: pretty;
}

/* ---------- projects ----------
   Two cinematic columns instead of three cramped squares: the
   videos are the actual content, so give them room. */

.project-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 26px);
  margin-top: clamp(40px, 5vw, 64px);
}

.project-card {
  position: relative;
  aspect-ratio: 16 / 11;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-width: 0;
  border-radius: 18px;
  overflow: hidden;
  background: linear-gradient(150deg, var(--ink-700), var(--ink-900));
  box-shadow: var(--shadow-p2);
  transition: box-shadow var(--t-hover) ease,
              transform var(--t-hover) var(--ease-out);
}

.card-video {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: .95;
}

/* scrim stays — it is legibility, not decoration */
.card-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
    rgba(18,10,15,.94) 2%,
    rgba(18,10,15,.55) 34%,
    rgba(18,10,15,.10) 62%,
    transparent 82%);
  pointer-events: none;
}

.card-top {
  position: absolute;
  inset: 0 0 auto 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px;
  pointer-events: none;
}
.card-num {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .14em;
  color: var(--gold);
  text-shadow: 0 1px 6px rgba(0,0,0,.6);
}
.card-date {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .14em;
  color: rgba(255,255,255,.66);
  text-shadow: 0 1px 6px rgba(0,0,0,.6);
}

.card-body { position: relative; padding: 20px 22px 22px; }

.card-title {
  font-size: clamp(20px, 2vw, 28px);
  line-height: 1.12;
  letter-spacing: -.028em;
  color: #fff;
  text-shadow: 0 1px 12px rgba(0,0,0,.45);
}

.card-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 13px;
}
.tag {
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,.11);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.13);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 400;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: rgba(255,255,255,.9);
  white-space: nowrap;
}
.code-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--ink-900);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: .11em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background var(--t-hover) ease,
              transform var(--t-press) var(--ease-out);
}

/* ---------- research ----------
   Was three glass boxes with 15.5px body copy. These are dense
   research abstracts; an editorial list gives them a real
   measure and a readable rhythm. */

.research-list { margin-top: clamp(40px, 5vw, 64px); }

.research-item {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr) minmax(0, 1.35fr);
  align-items: start;
  gap: clamp(20px, 3vw, 48px);
  padding: clamp(28px, 3.4vw, 44px) 0;
  border-top: 1px solid rgba(255,255,255,.10);
}
.research-item:last-child { border-bottom: 1px solid rgba(255,255,255,.10); }

.research-num {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .16em;
  color: var(--gold);
  padding-top: .35em;
}
.research-item h3 {
  font-size: clamp(21px, 2vw, 30px);
  line-height: 1.16;
  letter-spacing: -.028em;
  color: var(--on-dark);
  text-wrap: balance;
}
.research-item p {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.78;
  color: var(--on-dark-mut);
  text-wrap: pretty;
}

/* ---------- about ---------- */

.about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}
.about-body {
  margin: 18px 0 0;
  max-width: 46ch;
  font-size: 16.5px;
  line-height: 1.75;
  color: var(--on-paper-mut);
  text-wrap: pretty;
}

.bars { display: grid; gap: 16px; margin-top: 34px; }
.bar-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 8px;
  font-size: 13.5px;
  color: var(--on-paper-mut);
}
.bar-head b {
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--rose);
  font-variant-numeric: tabular-nums;
}
.bar-track {
  height: 5px;
  border-radius: 999px;
  background: rgba(30,18,25,.10);
  overflow: hidden;
}
.bar-fill {
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--rose), #A83A58);
  transform: scaleX(0);
  transform-origin: left;
  /* scaleX instead of animating width — width triggers layout
     on every frame, transform does not */
  transition: transform 900ms var(--ease-out);
}

.col-label {
  margin: 0 0 10px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--rose);
}
.col-label-spaced { margin-top: 34px; }

.exp-item { padding: 20px 0; border-top: 1px solid rgba(30,18,25,.11); }
.exp-head { display: flex; justify-content: space-between; gap: 14px; align-items: baseline; }
.exp-head h4 { font-size: clamp(18px, 1.6vw, 22px); letter-spacing: -.025em; color: var(--on-paper); }
.exp-dates {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--rose);
  white-space: nowrap;
}
.exp-org { margin-top: 4px; font-size: 14px; color: var(--on-paper-faint); }
.exp-body { margin: 9px 0 0; font-size: 14.5px; line-height: 1.7; color: var(--on-paper-mut); text-wrap: pretty; }

.chips { display: flex; flex-wrap: wrap; gap: 7px; }
.chip {
  padding: 7px 14px;
  border-radius: 999px;
  background: transparent;
  box-shadow: var(--shadow-p1);
  font-size: 12.5px;
  color: var(--on-paper-mut);
  transition: background var(--t-hover) ease,
              color var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-press) var(--ease-out);
}

/* ---------- education ---------- */

.edu-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 24px);
  margin-top: clamp(40px, 5vw, 60px);
}
.edu-card {
  padding: clamp(26px, 2.6vw, 36px);
  border-radius: 18px;
  background: rgba(255,255,255,.035);
  box-shadow: var(--shadow-1);
  transition: background var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-hover) var(--ease-out);
}
.edu-top { display: flex; justify-content: space-between; align-items: center; gap: 14px; }
.edu-status {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-dark-faint);
}
.edu-score {
  flex-shrink: 0;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--ink-900);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  white-space: nowrap;
}
.edu-card h3 { margin: 22px 0 0; font-size: clamp(21px, 2vw, 26px); line-height: 1.12; letter-spacing: -.028em; }
.edu-field {
  margin-top: 7px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 19px;
  line-height: 1.3;
  color: var(--gold);
}
.edu-school { margin-top: 22px; padding-top: 18px; border-top: 1px solid rgba(255,255,255,.10); font-size: 14px; line-height: 1.6; color: var(--on-dark-mut); }
.edu-meta { margin-top: 6px; font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; color: var(--on-dark-faint); }

/* ---------- contact ---------- */

.contact { text-align: center; padding-bottom: clamp(64px, 8vw, 96px); }
.contact-h2 {
  margin: 14px auto 0;
  max-width: 16ch;
  font-size: clamp(38px, 5.6vw, 78px);
  line-height: 1.0;
  letter-spacing: -.042em;
  text-wrap: balance;
}
.contact-form { display: grid; gap: 12px; max-width: 580px; margin: clamp(32px, 4vw, 48px) auto 0; text-align: left; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.contact-form input,
.contact-form textarea {
  padding: 14px 17px;
  border: none;
  border-radius: 11px;
  background: #fff;
  box-shadow: var(--shadow-p1);
  color: var(--on-paper);
  font-family: inherit;
  font-size: 14.5px;
  transition: box-shadow var(--t-hover) ease;
}
.contact-form textarea { resize: vertical; min-height: 108px; }
.contact-form input:hover,
.contact-form textarea:hover { box-shadow: 0 0 0 1px rgba(30,18,25,.16); }
.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
  box-shadow: 0 0 0 1px var(--rose), 0 0 0 4px rgba(209,77,112,.14);
}
.contact-form input::placeholder,
.contact-form textarea::placeholder { color: var(--on-paper-faint); }

.contact-form button {
  padding: 16px 24px;
  border: none;
  border-radius: 999px;
  background: var(--on-paper);
  color: var(--paper);
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: var(--shadow-p2);
  transition: background var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-press) var(--ease-out);
}

.socials { display: flex; justify-content: center; gap: 12px; margin-top: clamp(34px, 4vw, 46px); }
.socials a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px; height: 50px;
  border-radius: 50%;
  background: transparent;
  color: var(--on-paper-mut);
  box-shadow: var(--shadow-p1);
  transition: background var(--t-hover) ease,
              color var(--t-hover) ease,
              box-shadow var(--t-hover) ease,
              transform var(--t-hover) var(--ease-out);
}

footer {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding: 28px var(--gut);
  background: var(--ink-900);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--on-dark-faint);
}

/* ============================================================
   MOTION
   ============================================================ */

/* Scroll reveal. Hidden state is only applied when JS is running
   (html.js), so the page stays readable if the script fails. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
}
.js [data-reveal] {
  transition: opacity var(--t-enter) var(--ease-out),
              transform var(--t-enter) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 55ms);
}
.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Hero headline: clip-path wipe reveals the type from the
   baseline up, paired with a short lift. First-time-only, so a
   little delight is affordable here. */
.js .hero-reveal > * {
  clip-path: inset(0 0 105% 0);
  transform: translateY(18px);
  opacity: 0;
  transition: clip-path 720ms var(--ease-out),
              transform 720ms var(--ease-out),
              opacity 400ms ease;
  transition-delay: calc(var(--i, 0) * 70ms);
}
.js .hero-reveal.is-in > * {
  clip-path: inset(0 0 -18% 0);
  transform: none;
  opacity: 1;
}

/* The name writes in left to right across the length of the intro clip,
   so the turn and the headline resolve together. Long for UI motion,
   but this is a once-per-visit title sequence, not a control. */
.js .hero-reveal > h1 {
  clip-path: inset(0 100% -18% 0);
  transform: none;
  opacity: 1;
  transition: clip-path 3400ms cubic-bezier(.22,.68,.24,1);
  transition-delay: 120ms;
}
.js .hero-reveal.is-in > h1 { clip-path: inset(0 -2% -18% 0); }

@media (prefers-reduced-motion: reduce) {
  .js .hero-reveal > h1 {
    clip-path: none;
    transition: opacity 240ms ease;
    transition-delay: 0ms;
  }
}

/* Hover motion is gated to fine pointers — on touch, :hover
   sticks after a tap and leaves elements in a hovered state. */
@media (hover: hover) and (pointer: fine) {
  .brand:hover .monogram { transform: rotate(-8deg); }
  .brand:hover .brand-name { color: var(--on-dark); }
  .nav-links a:hover { color: var(--on-dark); }
  .nav-links a:not(.nav-cta):hover::after { transform: scaleX(1); }
  .nav-cta:hover { background: var(--gold); color: var(--ink-900); }

  .btn-primary:hover { background: var(--gold-hi); box-shadow: var(--shadow-3); }
  .btn-arrow:hover { background: rgba(255,255,255,.12); transform: translateY(-2px) rotate(45deg); }

  .project-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-p3); }
  .code-btn:hover { background: var(--gold-hi); }

  .chip:hover { background: var(--on-paper); color: var(--paper); box-shadow: none; }

  .edu-card:hover { background: rgba(255,255,255,.06); box-shadow: var(--shadow-2); transform: translateY(-3px); }

  .contact-form button:hover { background: var(--rose); box-shadow: var(--shadow-p3); }
  .socials a:hover { background: var(--on-paper); color: var(--paper); transform: translateY(-3px); box-shadow: var(--shadow-p2); }

  /* pause the marquee while it is being read */
  .ticker:hover .ticker-track { animation-play-state: paused; }
}

/* Press feedback — every pressable surface confirms the press.
   Absent from the previous build entirely. */
.btn-primary:active,
.contact-form button:active,
.nav-cta:active,
.code-btn:active,
.chip:active { transform: scale(.97); }
.btn-arrow:active,
.socials a:active { transform: scale(.94); }

@keyframes marqueeX {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Reduced motion: fewer and gentler, not zero. Position and
   scale changes go; opacity and colour stay, because they still
   communicate state. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .js [data-reveal] {
    transform: none;
    transition: opacity 240ms ease;
    transition-delay: 0ms;
  }
  .js .hero-reveal > * {
    clip-path: none;
    transform: none;
    transition: opacity 240ms ease;
    transition-delay: 0ms;
  }
  .ticker-track { animation: none; }
  .bar-fill { transition: transform 240ms ease; }

  .hero-photo, .hero-copy { transform: none !important; }

  .project-card:hover,
  .edu-card:hover,
  .socials a:hover,
  .btn-arrow:hover { transform: none; }

  .btn-primary:active,
  .contact-form button:active,
  .nav-cta:active,
  .code-btn:active,
  .chip:active,
  .btn-arrow:active,
  .socials a:active { transform: none; }
}

/* ---------- responsive ---------- */

@media (max-width: 1080px) {
  .hero {
    min-height: auto;
    /* background inherited — one gradient for every width now that it
       no longer has to line up with wherever the clip happens to sit */
  }

  /* Float rather than grid. A grid column leaves an empty block beside
     the bio's last lines, because the bio is taller than the photo.
     Floating lets the text sit level with the top of the photo, run
     alongside it, then close under it at full width. */
  .hero-grid {
    display: block;
    padding-top: 92px;
    padding-bottom: 0;
  }
  .hero-copy { display: block; padding-top: 18px; padding-bottom: 0; }

  /* Stacked here rather than floated. Side-by-side could not reach the
     ticker: at phone width a full-height figure would have to be
     cropped hard enough to lose his arms, which is why it was ending
     mid-torso. Text first, then the clip running to the section edge. */
  /* Stats sit beside the clip rather than above it. Stacked in a
     column they fill the gap that was empty to the left of the figure,
     and giving that gap to the clip lets it run noticeably larger in
     the same hero. display: contents on the copy dissolves the wrapper
     so the headline, bio, buttons and stats can be placed on the grid
     individually. */
  .hero-grid {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "head  head"
      "bio   bio"
      "cta   cta"
      "stats photo";
    align-items: end;
  }
  .hero-copy { display: contents; }
  .hero-reveal { grid-area: head; }
  .lede    { grid-area: bio; }
  .cta-row { grid-area: cta; }

  .stats {
    grid-area: stats;
    grid-template-columns: 1fr;        /* one per row */
    gap: 20px;
    /* Top-aligned with the figure, then nudged down so the first
       number sits level with his forehead — the hairline is ~6.5% down
       the clip, measured off the source's alpha channel. The rest fall
       below it. */
    align-self: start;
    /* negative, because the row starts above the figure's own top edge
       and the number's glyphs sit lower again inside their line box */
    margin-top: -15px;
  }

  .hero-photo {
    grid-area: photo;
    float: none;
    width: 100%;
    max-width: none;
    align-self: end;
    /* still bleeds to the viewport edge and lands on the ticker */
    margin: 0 calc(var(--gut) * -1) 0 0;
  }
  /* the canvas is the visible element; .hero-clip is the hidden 1x1
     source feeding it and must stay that size */
  .hero-photo .hero-canvas {
    height: auto;
    width: 100%;
    max-height: 54vh;
    object-fit: contain;
    object-position: bottom right;
  }

  .lede { margin-top: 14px; max-width: none; font-size: 14.5px; line-height: 1.6; }
  /* buttons start a clean line under the photo */
  .cta-row { clear: none; margin-top: 16px; padding-top: 0; }

  .hero-photo img {
    width: 100%;
    max-width: none;
    max-height: min(42vh, 320px);
    object-position: top center;
    filter: drop-shadow(0 16px 32px rgba(0,0,0,.55));
  }

  h1 { font-size: clamp(29px, 7.8vw, 54px); letter-spacing: -.038em; }
  .role { font-size: clamp(20px, 5.2vw, 38px); }
  /* has to clear a half-width column on one line */
  .eyebrow { margin-bottom: 12px; font-size: 9.5px; letter-spacing: .1em; gap: 7px; white-space: nowrap; }
  .rule { width: 20px; }

  .about-grid { grid-template-columns: 1fr; }
  .edu-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .research-item { grid-template-columns: 56px minmax(0, 1fr); }
  .research-item p { grid-column: 2; }
}

@media (max-width: 760px) {
  html { scroll-padding-top: 76px; }
  /* below this the column is too narrow for the leading rule too */
  .eyebrow .rule { display: none; }
  /* One per row, beside the clip. Three across took the full width and
     left the clip no column to sit in. */
  .stats { display: grid; grid-template-columns: 1fr; gap: 18px; margin-top: 30px; }
  .stat-value { font-size: 30px; }
  .stat-label { font-size: 9px; letter-spacing: .1em; margin-top: 5px; white-space: nowrap; }
  .nav { padding-inline: 16px; gap: 12px; }
  .brand-name { display: none; }
  .nav-links { gap: 11px; }
  .nav-links a { font-size: 9.5px; letter-spacing: .07em; }
  .nav-cta { padding: 7px 12px; }

  .project-grid { grid-template-columns: 1fr; }
  .edu-grid { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; }
  .section-head { flex-direction: column; align-items: flex-start; gap: 18px; }
  .research-item { grid-template-columns: 1fr; gap: 12px; }
  .research-item p { grid-column: 1; }
  .research-num { padding-top: 0; }
  footer { flex-direction: column; gap: 8px; }
}

/* Narrow phones: the five nav items have to clear the monogram in
   375px of width, so tracking and padding tighten one more step. */
@media (max-width: 440px) {
  .nav { padding-inline: 13px; gap: 9px; }
  .nav-links { gap: 8px; }
  .nav-links a { font-size: 8.8px; letter-spacing: .03em; }
  .nav-cta { padding: 7px 9px; }
  .monogram { width: 29px; height: 29px; font-size: 11px; }
}
