/* ==========================================================================
   V9 · English redesign — visual language references brandonbartram.dev,
   colors & fonts stay in the Portfolio-2026 PDF system.
   Layout: huge serif display type · mono meta labels · numbered sections ·
   hairline rules · pill buttons · uniform square project tiles.
   ========================================================================== */

:root {
  --ink: #1E4A50;
  --ink-2: #245A60;
  --muted: #5C7980;
  --accent: #FC8484;
  --accent-ink: #B04A44;
  --paper: #FDFBF6;
  --cream: #FAF3CF;
  --pink: #FED9D4;
  --mint: #D3FAD6;
  --azure: #B3EAEE;
  --line: rgba(30, 74, 80, .16);
  --serif: "Cormorant Garamond", "Noto Sans SC", serif;
  --sans: "Noto Sans SC", -apple-system, "PingFang SC", sans-serif;
  --mono: "JetBrains Mono", "Noto Sans SC", monospace;
  --pad: clamp(20px, 5vw, 64px);
  --ease: cubic-bezier(.22, .61, .21, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
html, body { overscroll-behavior-x: none; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  /* clip (not hidden): hidden would make body the page's scroll container,
     which breaks ScrollTrigger's window-scroll measurements */
  overflow-x: clip;
}

::selection { background: var(--accent); color: #fff; }

img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

.mono {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
}
.tnum { font-variant-numeric: tabular-nums; }
.d-serif { font-family: var(--serif); }

:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 3px; border-radius: 2px; }

/* ---------- background: WebGL grainient + waves; CSS mesh is the
   no-WebGL/no-JS fallback (hidden by JS after the first shader frame).
   The old SVG grain layer was replaced by the shader's own grain. */
.bg { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
.bg__mesh {
  position: absolute; inset: 0;
  transition: opacity .6s;
  background:
    radial-gradient(42% 38% at 12% 8%, rgba(250, 243, 207, .9), transparent 70%),
    radial-gradient(40% 36% at 88% 12%, rgba(254, 217, 212, .65), transparent 70%),
    radial-gradient(46% 42% at 8% 88%, rgba(211, 250, 214, .6), transparent 70%),
    radial-gradient(44% 40% at 90% 86%, rgba(179, 234, 238, .6), transparent 70%);
}
.bg__gl { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.bg__waves {
  position: absolute; inset: 0;
  background: url("../assets/img/waves.svg") center/cover no-repeat;
  opacity: .18;
}

/* ---------- custom cursor (fine pointers only) ---------- */
.cursor { position: fixed; z-index: 200; pointer-events: none; opacity: 0; }
.cursor--core i {
  position: absolute; left: -3px; top: -3px; width: 6px; height: 6px;
  border-radius: 50%; background: var(--accent-ink);
}
.cursor--orbit span {
  position: absolute; left: -15px; top: -15px; width: 30px; height: 30px;
  color: var(--accent);
  background: currentColor;
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  transition: transform .25s var(--ease);
}
.cursor--orbit.is-hover span { transform: scale(1.6) rotate(45deg); }
@media (pointer: coarse) { .cursor { display: none; } }
/* native cursor is hidden only once the custom cursor is live (JS adds
   .has-cursor on first mousemove) — reduced-motion / no-JS keep the OS cursor */
@media (pointer: fine) {
  body.has-cursor, body.has-cursor * { cursor: none !important; }
}

/* ---------- loader ---------- */
.loader {
  position: fixed; inset: 0; z-index: 100;
  background: var(--paper);
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 28px var(--pad);
  transition: opacity .7s var(--ease), visibility .7s;
}
.loader.is-done { opacity: 0; visibility: hidden; }
.loader__row { display: flex; align-items: center; gap: 16px; }
.loader__rule { flex: 1; height: 1px; background: var(--line); }
.loader__center { text-align: center; }
.loader__mark { position: relative; width: 46px; height: 42px; margin: 0 auto; }
/* viewfinder bloom (ref: brandonbartram.dev loader): a center dot appears,
   the four corner brackets spring OUTWARD from that point, hold, then hand
   off to the spinning flower. Pure CSS, timed to finish well before the
   counter reaches 100. */
.vf { position: absolute; inset: 0; animation: vfOut .45s 1.05s var(--ease) both; }
.vf__dot {
  position: absolute; left: 50%; top: 50%; width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  border-radius: 50%; background: var(--ink);
  animation: vfDot .35s .05s var(--ease) both;
}
.vf__c { position: absolute; width: 11px; height: 11px; border: 1.5px solid var(--ink); }
.vf__c--tl { top: 0; left: 0; border-right: 0; border-bottom: 0; animation: vfTl .55s .12s cubic-bezier(.2,1.35,.3,1) both; }
.vf__c--tr { top: 0; right: 0; border-left: 0; border-bottom: 0; animation: vfTr .55s .17s cubic-bezier(.2,1.35,.3,1) both; }
.vf__c--bl { bottom: 0; left: 0; border-right: 0; border-top: 0; animation: vfBl .55s .17s cubic-bezier(.2,1.35,.3,1) both; }
.vf__c--br { bottom: 0; right: 0; border-left: 0; border-top: 0; animation: vfBr .55s .22s cubic-bezier(.2,1.35,.3,1) both; }
@keyframes vfDot { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }
@keyframes vfTl { from { opacity: 0; transform: translate(17px, 15px); } to { opacity: 1; transform: translate(0, 0); } }
@keyframes vfTr { from { opacity: 0; transform: translate(-17px, 15px); } to { opacity: 1; transform: translate(0, 0); } }
@keyframes vfBl { from { opacity: 0; transform: translate(17px, -15px); } to { opacity: 1; transform: translate(0, 0); } }
@keyframes vfBr { from { opacity: 0; transform: translate(-17px, -15px); } to { opacity: 1; transform: translate(0, 0); } }
@keyframes vfOut { to { opacity: 0; transform: scale(1.3); } }
.loader__flower {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--accent); font-size: 30px; opacity: 0;
  animation: flowerIn .5s 1.15s var(--ease) both, spin 9s linear 1.15s infinite;
}
@keyframes flowerIn { from { opacity: 0; transform: scale(.35); } to { opacity: 1; transform: scale(1); } }
.reduced .vf { display: none; }
.reduced .loader__flower { opacity: 1; animation: spin 9s linear infinite; }
.loader__name {
  font-family: var(--serif);
  font-size: clamp(44px, 8vw, 96px);
  line-height: 1.05; letter-spacing: -.02em; margin: 8px 0 10px;
}
.loader__bar {
  width: min(320px, 60vw); height: 2px; margin: 22px auto 0;
  background: var(--line); border-radius: 2px; overflow: hidden;
}
.loader__bar i { display: block; height: 100%; width: 0; background: var(--accent); }
body.is-loading { overflow: hidden; }

/* ---------- nav ---------- */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  display: flex; align-items: center; gap: 18px;
  padding: 20px var(--pad);
}
.nav__mark {
  font-family: var(--serif); font-size: 22px; letter-spacing: .01em;
  text-transform: uppercase; white-space: nowrap;
}
.nav__rule { flex: 1; height: 1px; background: var(--line); transition: opacity .4s; }
.nav.is-scrolled .nav__rule { opacity: 0; }
.nav__links { display: flex; align-items: center; gap: 26px; }
.nav__links a {
  position: relative;
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2); padding: 4px 0;
}
.nav__links a::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px; background: var(--ink);
  transform: scaleX(0); transform-origin: left;
  transition: transform .35s var(--ease);
}
.nav__links a:hover::after { transform: scaleX(1); }
.nav__flower { color: var(--accent); font-size: 17px; animation: spin 9s linear infinite; }
.nav__toggle { display: none; }

@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 899px) {
  .nav__links {
    position: fixed; inset: 0; z-index: -1;
    flex-direction: column; justify-content: center; gap: 30px;
    background: color-mix(in srgb, var(--ink) 94%, transparent);
    opacity: 0; visibility: hidden;
    transition: opacity .45s var(--ease), visibility .45s;
  }
  .nav__links a {
    font-family: var(--serif); font-size: 40px; letter-spacing: -.01em;
    text-transform: uppercase; color: var(--paper);
  }
  .nav__links a::after { background: var(--accent); }
  .nav__links.is-open { opacity: 1; visibility: visible; z-index: 60; }
  .nav__toggle {
    display: block; font-family: var(--mono); font-size: 11px;
    letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
    padding: 6px 0; position: relative; z-index: 70;
  }
  .nav.menu-open .nav__toggle { color: var(--paper); }
  .nav.menu-open { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; }
  .nav.menu-open .nav__mark, .nav.menu-open .nav__rule { opacity: 0; }
  .nav__flower { display: none; }
}

/* ---------- shared section scaffolding ---------- */
.wrap { padding-left: var(--pad); padding-right: var(--pad); }
.section { padding-top: clamp(90px, 12vh, 150px); padding-bottom: clamp(70px, 10vh, 120px); }

.eyebrow { display: flex; align-items: baseline; gap: 14px; margin-bottom: 18px; }
.eyebrow .rule { flex: 0 0 60px; height: 1px; background: var(--line); align-self: center; }

.sec-title {
  font-family: var(--serif);
  font-size: clamp(56px, 9vw, 128px);
  line-height: .95; letter-spacing: -.025em; font-weight: 500;
  display: flex; align-items: center; gap: .18em;
}
.sec-title .flower { color: var(--accent); font-size: .42em; animation: spin 9s linear infinite; }
.sec-title em { font-style: italic; color: var(--accent-ink); }

/* ---------- reveal ---------- */
.rise { opacity: 0; transform: translateY(34px); }
.no-js .rise, .reduced .rise { opacity: 1; transform: none; }

/* ---------- hero ---------- */
.hero {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: space-between;
  padding-top: clamp(88px, 12vh, 124px);
  padding-bottom: 34px;
  position: relative;
}
.hero__top {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 20px;
  margin-bottom: clamp(14px, 2.5vh, 26px);
}
.pill {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2);
  background: #F5EDDD;
  border: 1px solid var(--line);
  border-radius: 999px; padding: 9px 18px;
}
.pill i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(252, 132, 132, .55); }
  50% { box-shadow: 0 0 0 7px rgba(252, 132, 132, 0); }
}
.hero__meta { text-align: right; line-height: 1.9; }

.hero__stage {
  display: grid; grid-template-columns: 1.35fr .9fr;
  gap: clamp(24px, 5vw, 72px); align-items: center; flex: 1;
}
.hero__kicker { margin-bottom: 18px; }
.hero__title {
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(46px, 6vw, 108px);
  line-height: .94; letter-spacing: -.03em;
}
.hero__title .line { display: block; overflow: hidden; }
.hero__title .line > span { display: inline-block; }
.hero__title .ch { display: inline-block; will-change: transform, opacity; }
.hero__title em { font-style: italic; color: var(--accent-ink); }
.hero__sub {
  font-family: var(--serif);
  font-size: clamp(19px, 2vw, 28px);
  line-height: 1.35; letter-spacing: -.01em;
  color: var(--ink-2);
  margin-top: clamp(16px, 2.5vh, 24px);
  max-width: 24em;
}
.hero__sub .line { display: block; overflow: hidden; }
.hero__sub .line > span { display: inline-block; }

.hero__visual { position: relative; justify-self: end; width: 100%; max-width: 390px; }
.hero__visual img {
  width: 100%; height: auto; /* full uncropped photo — never crop the owner's portrait */
  border-radius: 4px;
}
.hero__visual figcaption {
  position: absolute; left: 14px; bottom: 12px;
  color: rgba(255, 255, 255, .85); mix-blend-mode: screen;
}
.hero__visual::before {
  content: ""; position: absolute; inset: -14px auto auto -14px;
  width: 84px; height: 84px;
  border-top: 1px solid var(--accent); border-left: 1px solid var(--accent);
}
.hero__visual::after {
  content: "✦"; position: absolute; right: -20px; top: -26px;
  color: var(--accent); font-size: 26px; animation: spin 12s linear infinite;
}

.hero__cta { display: flex; gap: 14px; margin-top: clamp(20px, 3vh, 28px); }
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
  padding: 14px 26px; border-radius: 999px;
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--ink);
  transition: background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease), transform .35s var(--ease);
}
.btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; transform: translateY(-2px); }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }

.hero__foot {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
  border-top: 1px solid var(--line);
  padding-top: 22px; margin-top: clamp(26px, 4vh, 44px);
}
.hero__foot dt { margin-bottom: 6px; }
.hero__foot dd { font-size: 14px; color: var(--ink-2); line-height: 1.7; }

@media (max-width: 899px) {
  .hero__stage { grid-template-columns: 1fr; gap: 30px; }
  .hero__visual { justify-self: start; max-width: 320px; }
  .hero__meta { display: none; }
  .hero__foot { grid-template-columns: 1fr; gap: 14px; }
}

/* ---------- marquee ---------- */
.marquee {
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  overflow: hidden; padding: 16px 0;
}
.marquee__track {
  display: flex; align-items: center; gap: 34px; width: max-content;
  animation: marquee 26s linear infinite;
}
.marquee__track span {
  font-family: var(--serif); font-size: 26px; letter-spacing: -.01em; white-space: nowrap;
}
.marquee__track i { color: var(--accent); font-style: normal; font-size: 15px; }
@keyframes marquee { to { transform: translateX(-50%); } }

/* ---------- about ---------- */
/* Single big-serif lede (brandonbartram.dev pattern): full-width
   condensed intro with italic accent phrases, borderless stat row below. */
.about__lede {
  font-family: var(--serif);
  font-size: clamp(24px, 3vw, 42px);
  line-height: 1.35; letter-spacing: -.01em;
  margin-top: clamp(30px, 5vh, 54px);
}
.about__lede em { font-style: italic; color: var(--accent-ink); }

.stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(20px, 4vw, 60px);
  margin-top: clamp(36px, 6vh, 64px); padding-top: clamp(24px, 4vh, 40px);
  border-top: 1px solid var(--line);
}
.stat__n { font-family: var(--serif); font-size: clamp(40px, 4.5vw, 58px); line-height: 1; letter-spacing: -.02em; }
.stat__n sup { font-size: .35em; color: var(--muted); }
.stat__l { margin-top: 8px; }

@media (max-width: 899px) {
  .stats { grid-template-columns: 1fr 1fr; gap: 28px 20px; }
}

/* ---------- experience ---------- */
/* blue→pink drift gradient behind the section, revealed when scrolled into
   view (ref: services color shift on brandonbartram.dev). Hues stay inside
   the site's own palette: azure #B3EAEE → violet bridge → accent pink. */
#experience { position: relative; }
#experience::before {
  content: ""; position: absolute; inset: -18% 0; z-index: -1;
  background: linear-gradient(115deg,
    rgba(158, 205, 235, .55) 0%,
    rgba(206, 190, 233, .45) 45%,
    rgba(252, 178, 186, .55) 100%);
  background-size: 200% 200%;
  /* bleed past the section box and feather both ends, so the band melts
     into the paper instead of cutting off at a hard edge */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 26%, #000 74%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 26%, #000 74%, transparent);
  opacity: 0;
  transition: opacity 1.2s var(--ease);
  animation: expDrift 16s ease-in-out infinite alternate;
}
#experience.is-glow::before { opacity: 1; }
@keyframes expDrift {
  from { background-position: 0% 40%; }
  to { background-position: 100% 60%; }
}
.no-js #experience::before, .reduced #experience::before { opacity: 1; animation: none; }

/* journey timeline cards (heynesh.com-inspired): cards wander around the
   center line instead of two rigid columns; vertical rhythm is irregular.
   Desktop positions are fractions of (container - card) via calc.
   Structure per card: .journey__card (static positioning context) >
   .journey__body (the white card, JS animates it in) + .journey__rail /
   .journey__dot (STATIC spine: 2px rail beside the card, dot at its foot).
   The connector ribbon lives in .journey__curtain (JS-built), an
   overflow:hidden veil whose height is scrubbed by scroll — the line is
   revealed top-down like a curtain, reversible, never time-based. */
.journey { margin-top: clamp(30px, 5vh, 54px); position: relative; padding-bottom: 150px; /* room for the dashed tail past the last dot */ }
.journey__card {
  position: relative; z-index: 1;
  width: min(540px, 100%);
}
.journey__body {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: clamp(22px, 3vw, 30px) clamp(24px, 3.4vw, 36px);
  box-shadow: 0 18px 40px -28px rgba(30, 74, 80, .28);
  opacity: 0; /* JS reveals; no-js/reduced overrides below */
}
.journey__card + .journey__card { margin-top: clamp(80px, 14vh, 140px); }
@media (min-width: 900px) {
  /* heynesh.com's exact scatter (measured: card lefts 60/19/11/49/54% of the
     container, gaps 0-29px — cards nearly touch, separation comes from the
     horizontal offset). min() keeps the card inside on narrower viewports. */
  .journey__card:nth-child(1) { margin-left: min(60%, calc(100% - 540px)); }
  .journey__card:nth-child(2) { margin-left: min(19%, calc(100% - 540px)); margin-top: 32px; }
  .journey__card:nth-child(3) { margin-left: min(11%, calc(100% - 540px)); margin-top: 56px; }
  .journey__card:nth-child(4) { margin-left: min(49%, calc(100% - 540px)); margin-top: 36px; }
  .journey__card:nth-child(5) { margin-left: min(54%, calc(100% - 540px)); margin-top: 56px; }
}
/* curtain veil: JS sets the inner SVG to the journey's full pixel height,
   then scrubs this wrapper's height 0 → 100% (fx) or leaves it full
   (reduced). z-index below the cards so the ribbon passes BEHIND them. */
.journey__curtain {
  /* left:-32px so the clip window also covers the rail/dot zone left of the
     cards (mobile spine sits at x≈-19 rel journey) — vertical clipping is
     the reveal mechanism, horizontal reach just needs to contain the ink */
  position: absolute; top: 0; left: -32px; right: 0; height: 0;
  overflow: hidden; z-index: 0; pointer-events: none;
}
.no-js .journey__curtain, .reduced .journey__curtain { height: 100%; }
.journey__line { position: absolute; top: 0; left: 0; width: 100%; display: block; }
.journey__line path { fill: none; stroke: rgba(30, 74, 80, .8); stroke-width: 2; }
/* dashed tail after the last dot ("the journey continues" — heynesh detail);
   same ink/weight as the ribbon, revealed by the same curtain */
.journey__line .journey__tail {
  stroke-dasharray: 18 16; stroke-linecap: round;
}
/* static spine: rail beside the card, roughly the card's own height
   (top edge → just past the bottom corner where the dot sits), top third
   fading in from transparent (heynesh.com detail). No animation — the
   motion comes from the curtain, not the spine. */
.journey__rail {
  position: absolute; top: 0; bottom: -14px; width: 2px; border-radius: 2px;
  background: rgba(30, 74, 80, .8);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 32%);
  mask-image: linear-gradient(to bottom, transparent, #000 32%);
}
/* rail hugs the card: ~24px outside the edge, measured on heynesh.com */
.journey__rail--l { left: -24px; }
.journey__rail--r { right: -24px; }
.journey__dot {
  position: absolute; bottom: -19px; width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent); border: 2px solid #fff;
}
.journey__dot--l { left: -28px; }
.journey__dot--r { right: -28px; }
@media (max-width: 899px) {
  /* mobile: single column, all rails/dots on the left, tighter offset;
     journey shifts right so the spine stays inside the viewport */
  .journey { margin-left: 26px; }
  .journey__rail--l, .journey__rail--r { left: -20px; right: auto; }
  .journey__dot--l, .journey__dot--r { left: -24px; right: auto; }
}
.no-js .journey__body, .reduced .journey__body { opacity: 1; }
.journey__year {
  display: block;
  font-family: var(--serif); font-size: clamp(44px, 5vw, 62px); line-height: 1;
  letter-spacing: -.02em; color: var(--accent);
}
.journey__role {
  display: block; margin-top: 12px;
  font-family: var(--serif); font-size: clamp(22px, 2.4vw, 29px);
  line-height: 1.15; letter-spacing: -.015em;
}
.journey__org { display: block; margin-top: 10px; }
.journey__when { display: block; margin-top: 4px; font-size: 11px; letter-spacing: .1em; color: var(--muted); }
.journey__note { margin-top: 14px; font-size: 14.5px; color: var(--ink-2); }
/* odometer year digits (JS-built, fx mode only; plain text otherwise).
   Cell is 1.12em, not 1em: Cormorant's oldstyle figures dip ~0.28em below
   the baseline, and with line-height:1 the baseline sits ~0.81em from the
   cell top — a 1em cell let descender ink bleed into the next cell (and the
   mask window). 1.12em = 0.81em baseline + 0.28em descender + buffer.
   JS uses the same 1.12 step (translate distances) — keep in sync. */
.journey__digit { display: inline-block; height: 1.12em; overflow: hidden; vertical-align: top; }
.journey__digit-track { display: block; will-change: transform; }
.journey__digit-track span { display: block; height: 1.12em; line-height: 1; overflow: hidden; }

/* ---------- selected work : uniform square tiles ---------- */
.work { padding-top: clamp(90px, 12vh, 150px); }
.work__scroll { position: relative; }
.work__head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
  padding: 0 var(--pad) clamp(22px, 4vh, 40px);
}
.work__hint { white-space: nowrap; padding-bottom: 14px; }

/* narrow phones: "Projects" + nowrap hint exceed the padded row (~10px at
   390px), which silently expands the mobile layout viewport and makes every
   fixed inset:0 layer (bg/loader/nav) 9px too wide — wrap the hint onto its
   own right-aligned row instead of letting it overflow */
@media (max-width: 480px) {
  .work__head { flex-wrap: wrap; }
  .work__hint { margin-left: auto; padding-bottom: 0; }
}

/* Vertical scroll-stack (effect ported from React Bits ScrollStack, no
   React/Lenis): two independent .stack containers (AI 2026 / Course &
   Competition), each its own sticky context so the two groups can never
   pile into the same stack. Card heads sit on top so a covered card
   reveals a readable title tab instead of a strip of image colour. */
.stack {
  --card-head: clamp(44px, 6.5svh, 56px);
  display: flex; flex-direction: column; gap: clamp(18px, 3vh, 32px);
  padding: 0 var(--pad) clamp(46px, 7vh, 72px);
}
.stack__group {
  display: flex; align-items: center; gap: 14px;
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
  padding: 0 var(--pad); margin: 0 0 clamp(18px, 3vh, 28px);
}
.stack__group .rule { flex: 1; height: 1px; background: var(--line); }

.card {
  position: relative; display: grid;
  grid-template-columns: minmax(0, 46%) 1fr;
  grid-template-rows: var(--card-head) minmax(0, 1fr);
  min-height: calc(var(--card-head) + clamp(240px, 38svh, 350px));
  background: #fff; border: 1px solid var(--line); border-radius: 18px;
  overflow: hidden; color: var(--ink);
  transition: border-color .35s, box-shadow .35s;
}
.card:hover, .card:focus-visible { border-color: var(--accent); box-shadow: 0 18px 50px -24px rgba(30, 74, 80, .35); }
.card__head {
  grid-column: 1 / -1; display: flex; align-items: center; gap: clamp(12px, 1.6vw, 22px);
  padding: 0 clamp(18px, 2.6vw, 36px);
  border-bottom: 1px solid var(--line);
}
.card__num { font-size: 11px; letter-spacing: .14em; color: var(--muted); }
.card__title { font-family: var(--serif); font-size: clamp(19px, 2vw, 26px); line-height: 1.15; letter-spacing: -.015em; }
.card__tag {
  margin-left: auto; font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap;
}
.card__media { position: relative; overflow: hidden; }
.card__media img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform .8s var(--ease);
}
.card:hover .card__media img, .card:focus-visible .card__media img { transform: scale(1.04); }
.card__body { display: flex; flex-direction: column; gap: 12px; padding: clamp(20px, 2.6vw, 36px); }
.card__desc { font-size: 15px; line-height: 1.6; color: var(--ink-2); max-width: 52ch; }
.card__foot {
  margin-top: auto; display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
  font-size: 11px; letter-spacing: .1em; color: var(--muted);
}
.card__cta {
  color: var(--accent-ink); text-transform: uppercase; letter-spacing: .14em; white-space: nowrap;
  border-bottom: 1px solid transparent; transition: border-color .3s;
}
.card:hover .card__cta, .card:focus-visible .card__cta { border-color: var(--accent-ink); }

@media (max-width: 899px) {
  .card { grid-template-columns: 1fr; grid-template-rows: var(--card-head) auto auto; min-height: 0; }
  .card__media { aspect-ratio: 16 / 10; }
}
@media (max-width: 600px) {
  .card__tag { display: none; }
}

/* Sticky stacking — JS-gated (.stack--fx only when GSAP runs and motion is
   allowed). Pure CSS sticky does the pinning so anchor jumps can never
   corrupt measurements (V9.1 pattern); scale-down is scrubbed by GSAP.
   position:relative makes card.offsetTop stable for the JS triggers —
   getBoundingClientRect of a stuck element lies. The per-card offset
   tracks --card-head so each covered card reveals exactly its title tab. */
.stack--fx { position: relative; --stack-top: clamp(70px, 14svh, 130px); }
.stack--fx .card {
  position: sticky;
  /* 5px overlap (kept in sync with stackGapPx() in v2.js): the scrubbed
     scale-down shrinks a covered card's head below --card-head, so the
     next card must overlap slightly or a sliver of image shows through */
  top: calc(var(--stack-top) + var(--stack-i, 0) * (var(--card-head) - 5px));
  will-change: transform;
}
/* Dwell spacer: pushes the container bottom past the last card so the
   final card actually gets pinned — the completed pile holds still for
   ~40svh of scrolling before it slides away. Fx-only, so the reduced-
   motion plain list keeps no trailing gap. */
.stack--fx::after {
  content: ""; display: block; flex: none;
  height: clamp(240px, 40svh, 420px);
}

/* ---------- skills ---------- */
.skills { margin-top: clamp(30px, 5vh, 54px); border-top: 1px solid var(--line); }
.skills__row {
  display: grid; grid-template-columns: 220px 1fr; gap: 20px; align-items: baseline;
  padding: 22px 0; border-bottom: 1px solid var(--line);
}
.skills__tags { display: flex; flex-wrap: wrap; gap: 10px; }
.tag {
  font-size: 13.5px; color: var(--ink-2);
  border: 1px solid var(--line); border-radius: 999px; padding: 7px 16px;
  transition: border-color .3s, color .3s, transform .3s var(--ease);
}
.tag:hover { border-color: var(--accent); color: var(--accent-ink); transform: translateY(-2px); }
.tag--key { border-color: var(--accent); color: var(--accent-ink); }

@media (max-width: 899px) { .skills__row { grid-template-columns: 1fr; gap: 12px; } }

/* ---------- contact ---------- */
.contact {
  display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: clamp(24px, 4vw, 60px);
  margin-top: clamp(30px, 5vh, 54px);
  border-top: 1px solid var(--line); padding-top: clamp(26px, 4vh, 44px);
}
.contact .mono { margin-bottom: 12px; }
.contact__big {
  font-family: var(--serif); font-size: clamp(26px, 3vw, 40px); letter-spacing: -.015em;
  border-bottom: 1px solid var(--line);
  transition: color .3s, border-color .3s;
}
.contact__big:hover { color: var(--accent-ink); border-color: var(--accent); }
.contact p { color: var(--ink-2); font-size: 15px; }
.resume-links { display: flex; gap: 12px; flex-wrap: wrap; }

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

/* ---------- footer ---------- */
.foot {
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding: 26px 0 34px;
}

/* ==========================================================================
   Project detail pages
   ========================================================================== */
.pj { min-height: 100svh; display: flex; flex-direction: column; }
.pj__bar {
  display: flex; align-items: center; gap: 18px;
  padding: 20px var(--pad);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--paper) 85%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.pj__back { font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2); }
.pj__back:hover { color: var(--accent-ink); }
.pj__rule { flex: 1; height: 1px; background: var(--line); }

.pj__grid {
  flex: 1;
  display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(26px, 4.5vw, 72px);
  padding: clamp(30px, 5vh, 60px) var(--pad) clamp(50px, 8vh, 90px);
  align-items: start;
}
.pj__media { position: sticky; top: 96px; }
.pj__media img, .pj__media video {
  display: block; width: auto; max-width: 100%;
  max-height: calc(100svh - 140px); /* whole poster/video fits one screen — no scrolling to see it */
  border-radius: 4px; background: #0d2226;
}
.pj__media .mono { margin-top: 12px; }

.pj__cat { margin-bottom: 16px; display: block; }
.pj__title {
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(40px, 4.6vw, 72px);
  line-height: 1; letter-spacing: -.025em; margin-bottom: 22px;
}
.pj__tags { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 30px; }
.pj__tags .tag { font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; padding: 6px 14px; }

.pj__meta { border-top: 1px solid var(--line); margin-bottom: 30px; }
.pj__meta > div {
  display: grid; grid-template-columns: 130px 1fr; gap: 16px;
  padding: 13px 0; border-bottom: 1px solid var(--line);
  font-size: 14.5px; color: var(--ink-2);
}
.pj__body p { margin-bottom: 1.1em; color: var(--ink-2); max-width: 64ch; }
.pj__body p strong { color: var(--ink); font-weight: 600; }
.pj__body ul { margin: 0 0 1.1em; padding-left: 1.1em; list-style: disc; color: var(--ink-2); max-width: 64ch; }
.pj__body li { margin-bottom: .4em; }
.pj__cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 28px; }

.pj__next {
  display: flex; justify-content: space-between; align-items: baseline; gap: 20px;
  border-top: 1px solid var(--line);
  padding: 26px var(--pad) 30px;
}
.pj__next a { font-family: var(--serif); font-size: clamp(22px, 2.4vw, 32px); letter-spacing: -.015em; }
.pj__next a:hover { color: var(--accent-ink); }

@media (max-width: 899px) {
  .pj__grid { grid-template-columns: 1fr; }
  .pj__media { position: static; }
  .pj__next { flex-direction: column; gap: 6px; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
  .rise { opacity: 1; transform: none; }
  .journey__body { opacity: 1; transform: none; }
  .journey__curtain { height: 100%; }
}
