/* ==========================================================================
   Sourabh Prasad — portfolio
   Implementation of "Portfolio Prototype.dc.html" (Claude Design handoff).
   Tokens, spacing and type are transcribed from the prototype 1:1.
   ========================================================================== */

/* ---------------------------------- tokens -------------------------------- */

:root,
:root[data-theme="dark"] {
  --bg: #0c0d0c;
  --panel: #101211;
  --ink: #f0f2ef;
  --mute: #a8afaa;
  --line: #1e201f;
  --line2: #26282a;
  --axis: #3d423f;
  --grid: #141614;
  --accent: #7de2a8;
  --onAccent: #0c0d0c;
  --stripe: #141715;
  --navbg: #0c0d0c;

  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sans: 'IBM Plex Sans', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;

  --shell: 1180px;
  --gut: 28px;
  --navh: 58px;   /* fallback; main.js replaces it with the measured height */
  --navctl: 34px; /* shared height for the CV button and the theme toggle */
}

:root[data-theme="light"] {
  --bg: #f4f2ee;
  --panel: #eae7e0;
  --ink: #14140f;
  --mute: #55534c;
  --line: #d8d3c8;
  --line2: #cec8bd;
  --axis: #9c968b;
  --grid: #e2ddd3;
  --accent: #14603f;
  --onAccent: #f4f2ee;
  --stripe: #e2ded5;
  --navbg: #f4f2ee;
}

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

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  min-height: 100vh;
}

a { color: var(--accent); }
a:hover { color: color-mix(in oklab, var(--accent) 78%, #ffffff); }

h1, h2, h3, p { margin: 0; }

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

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

.shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding-left: var(--gut);
  padding-right: var(--gut);
}

/* -------------------------------- keyframes ------------------------------- */

@keyframes bob       { 0%, 100% { transform: translateY(0); }    50% { transform: translateY(9px); } }
@keyframes mq        { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-50%, 0, 0); } }
@keyframes pulseGlow { 0%, 100% { opacity: .12; }                50% { opacity: .36; } }
@keyframes dotBlink  { 0%, 100% { opacity: .35; }                50% { opacity: 1; } }
@keyframes caret     { 0%, 45% { opacity: 1; } 50%, 95% { opacity: 0; } 100% { opacity: 1; } }

/* --------------------------------- progress ------------------------------- */

.progress {
  position: fixed;
  left: 0;
  top: 0;
  height: 2px;
  z-index: 40;
  background: var(--accent);
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}

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

.nav {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--navbg);
  border-bottom: 1px solid var(--line);
  transition: background .25s ease, border-color .25s ease;
}

/* Once the page has moved, the bar lets the content run under it. Gated on
   backdrop-filter: without the blur behind it the links would sit unreadable
   over the hero hatch and the project cards, so those engines keep the solid
   bar instead. color-mix failing falls back to the opaque rule above. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav.is-scrolled {
    background: color-mix(in oklab, var(--navbg) 68%, transparent);
    border-bottom-color: color-mix(in oklab, var(--line) 70%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    backdrop-filter: blur(14px) saturate(140%);
  }
}

/* Three tracks rather than space-between: the outer 1fr columns are equal, so
   the links sit dead centre of the bar regardless of how much wider the
   actions are than the logo. */
.nav__inner {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 14px var(--gut);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
}

.nav__logo {
  font: 500 14px/1 var(--mono);
  letter-spacing: .1em;
  color: var(--accent);
  text-decoration: none;
}

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

.nav__actions {
  display: flex;
  align-items: center;
  gap: 20px;
  justify-self: end;
}

.nav__link {
  font: 400 13px/1 var(--mono);
  letter-spacing: .12em;
  color: var(--mute);
  text-decoration: none;
  transition: color .18s ease;
}
.nav__link:hover { color: var(--accent); }

/* Both nav controls take their height from --navctl rather than from padding
   arithmetic, so they stay identical as the type size changes. The 1px border
   matches the toggle's, keeping the two border boxes the same. */
.nav__cv {
  display: inline-flex;
  align-items: center;
  height: var(--navctl);
  padding: 0 14px;
  font: 500 13px/1 var(--mono);
  letter-spacing: .1em;
  color: var(--onAccent);
  background: var(--accent);
  border: 1px solid var(--accent);
  text-decoration: none;
  border-radius: 3px;
  white-space: nowrap;   /* never break "CV.PDF" off its arrow */
  transition: background .18s ease, border-color .18s ease;
}
.nav__cv:hover {
  color: var(--onAccent);
  background: color-mix(in oklab, var(--accent) 82%, #ffffff);
  border-color: color-mix(in oklab, var(--accent) 82%, #ffffff);
}

.themetoggle {
  display: flex;
  align-items: stretch;
  height: var(--navctl);
  font: 500 12px/1 var(--mono);
  border: 1px solid var(--line2);
  background: transparent;
  border-radius: 3px;
  overflow: hidden;
  padding: 0;
  cursor: pointer;
  transition: border-color .18s ease;
}
.themetoggle:hover { border-color: var(--accent); }

.themetoggle__seg {
  display: flex;
  align-items: center;
  padding: 0 10px;
  color: var(--mute);
  background: transparent;
  transition: color .18s ease;
}

/* Only the inactive chip picks up the accent — the active one is set by the
   higher-specificity theme rules below and keeps its own colours. */
.themetoggle:hover .themetoggle__seg { color: var(--accent); }

/* Active-segment colouring mirrors the prototype exactly: the light chip
   lights up in accent, the dark chip in --mute. */
:root[data-theme="light"] .themetoggle__seg[data-seg="light"] { color: var(--onAccent); background: var(--accent); }
:root[data-theme="dark"]  .themetoggle__seg[data-seg="dark"]  { color: var(--onAccent); background: var(--mute); }

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

.hero {
  position: relative;
  overflow: hidden;
  /* --navh is measured from the real nav in main.js; the nav wraps to two rows
     on narrow screens, so it can't be the constant the prototype assumed.
     svh is the small viewport — with mobile toolbars shown — so the scroll cue
     stays above the fold instead of hiding under Safari's chrome. */
  min-height: calc(100vh - var(--navh));
  min-height: calc(100svh - var(--navh));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Drafting-sheet backdrop: hatch, glow, frame, crosshair, datum labels. */
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  transform: translateZ(0);

  --frame-x: clamp(24px, 6vw, 92px);
  --frame-y: clamp(24px, 7vh, 74px);
}

.hero__hatch {
  position: absolute;
  inset: 0;
  /* fallback for engines without color-mix() */
  background-image: repeating-linear-gradient(115deg, var(--grid) 0 1px, transparent 1px 14px);
  background-image: repeating-linear-gradient(
    115deg,
    color-mix(in oklab, var(--accent) 7%, transparent) 0 1px,
    transparent 1px 14px
  );
}

.hero__glow {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse at 50% 40%,
    color-mix(in oklab, var(--accent) 11%, transparent),
    transparent 62%
  );
}

.hero__frame {
  position: absolute;
  left: var(--frame-x);
  right: var(--frame-x);
  top: var(--frame-y);
  bottom: var(--frame-y);
  border: 1px solid var(--line2);
  border: 1px solid color-mix(in oklab, var(--accent) 18%, transparent);
}

.hero__crosshair { position: absolute; background: color-mix(in oklab, var(--accent) 12%, transparent); }
.hero__crosshair--h {
  left: var(--frame-x);
  right: var(--frame-x);
  top: 50%;
  height: 1px;
}
.hero__crosshair--v {
  left: 50%;
  top: var(--frame-y);
  bottom: var(--frame-y);
  width: 1px;
}

.hero__datum {
  position: absolute;
  bottom: calc(var(--frame-y) + 14px);
  font: 400 10px/1 var(--mono);
  letter-spacing: .2em;
  color: var(--axis);
}
.hero__datum--left  { left: var(--frame-x); }
.hero__datum--right { right: var(--frame-x); }

/* hero content */
.hero__body {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  /* The prototype sizes the hero's vertical rhythm against viewport height so
     it compresses on short screens. Bottom padding is the one departure: the
     scroll cue is absolutely positioned here, so the text has to clear it. */
  padding: clamp(18px, 3.6vh, 40px) var(--gut) clamp(76px, 12vh, 104px);
  text-align: center;
}

.hero__eyebrow {
  display: flex;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  font: 400 10px/1.6 var(--mono);
  letter-spacing: .14em;
  color: var(--mute);
  margin-bottom: clamp(12px, 2.4vh, 26px);
}

.hero__title {
  /* min() adds the prototype's viewport-height cap, so the name can't eat a
     short screen — it only bites in landscape, where 15.5vh < 8.2vw. */
  font: 600 clamp(46px, min(8.2vw, 15.5vh), 96px)/.9 var(--mono);
  letter-spacing: -.04em;
  color: var(--ink);
}
.hero__title-accent { color: var(--accent); }

/* The prototype's rotating role line: prompt, typed text, blinking block. */
.hero__role {
  display: flex;
  /* Baseline, not centre: the text reserves two lines on a phone, so centring
     would float the ">" halfway down the empty box instead of sitting on the
     first line of type. */
  align-items: baseline;
  justify-content: center;
  gap: 12px;
  margin-top: clamp(14px, 2.4vh, 26px);
  font: 500 clamp(13px, 1.5vw, 17px)/1.4 var(--mono);
  letter-spacing: .02em;
}
.hero__role-prompt { color: var(--axis); }
.hero__role-text {
  min-height: 1.4em;
  color: var(--ink);
  white-space: pre-wrap;
}

/* The caret rides the text as an ::after rather than sitting beside it as a
   sibling: once the line wraps on a narrow screen it has to follow the last
   typed character, not float against a two-line block. main.js only ever sets
   textContent, so it can't clobber this. */
.hero__role-text::after {
  content: '';
  display: inline-block;
  width: 9px;
  height: 1.05em;
  margin-left: 5px;
  vertical-align: text-bottom;
  background: var(--accent);
  animation: caret 1.1s steps(1, end) infinite;
}

.statuspill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: clamp(16px, 2.6vh, 30px);
  text-decoration: none;
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 8px 14px;
  font: 500 10px/1 var(--mono);
  letter-spacing: .14em;
  color: var(--accent);
  cursor: pointer;
  transition: color .18s ease, background .18s ease, transform .18s ease;
  position: relative;
  isolation: isolate;
}
.statuspill::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 999px;
  box-shadow: 0 0 0 4px var(--accent), 0 0 18px 5px var(--accent);
  animation: pulseGlow 3.2s ease-in-out infinite;
  will-change: opacity;
  pointer-events: none;
}
.statuspill:hover {
  color: var(--onAccent);
  background: var(--accent);
  transform: translateY(-1px);
}
.statuspill__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: dotBlink 3.2s ease-in-out infinite;
}
.statuspill__arrow { font-size: 11px; line-height: 1; }

.hero__lede-wrap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 40px;
  align-items: end;
  justify-items: center;
  margin-top: clamp(20px, 3.4vh, 48px);
}
.hero__lede {
  margin: 0 auto;
  max-width: 560px;
  font: 400 15px/1.65 var(--sans);
  color: var(--mute);
  text-wrap: pretty;
}

.scrollcue {
  position: absolute;
  z-index: 2;
  left: 0; right: 0; bottom: 28px;
  display: flex;
  justify-content: center;
  text-decoration: none;
}
.scrollcue__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  animation: bob 1.6s ease-in-out infinite;
}
.scrollcue__label {
  font: 500 10px/1 var(--mono);
  letter-spacing: .2em;
  color: var(--mute);
}
.scrollcue__chev {
  font: 400 18px/1 var(--mono);
  color: var(--accent);
}

/* --------------------------------- marquee -------------------------------- */

.marquee {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  padding: 15px 0;
  overflow: hidden;
  position: relative;
  contain: paint;
  isolation: isolate;
}
.marquee#timeline { scroll-margin-top: 57px; }

.marquee__track {
  display: flex;
  width: max-content;
  animation: mq 46s linear infinite;
  backface-visibility: hidden;
  transform: translateZ(0);
}
.marquee__track--rev { animation-direction: reverse; }

.marquee__group { display: flex; align-items: center; }
.marquee__item { display: flex; align-items: center; }

.marquee__label {
  font: 500 12px/1 var(--mono);
  letter-spacing: .06em;
  color: var(--ink);
  padding: 0 22px;
  white-space: nowrap;
}
.marquee__label.is-hl { color: var(--accent); }
.marquee__sep { color: var(--axis); }

.marquee__fade {
  position: absolute;
  top: 0; bottom: 0;
  width: 90px;
  pointer-events: none;
}
.marquee__fade--l { left: 0;  background: linear-gradient(90deg, var(--panel), transparent); }
.marquee__fade--r { right: 0; background: linear-gradient(270deg, var(--panel), transparent); }

/* -------------------------------- timeline -------------------------------- */

.tl {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--gut);
}

.tl__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
.tl__headlabel {
  font: 500 10px/1 var(--mono);
  letter-spacing: .16em;
  color: var(--mute);
}

.tl__body { padding: 40px 0 12px; }

.tl-row {
  --tl-axis: 112px;   /* centre column carrying the year rail */
  --tl-inset: 34px;   /* gap between a card's edge and that column */

  display: grid;
  /* minmax(0, 1fr) rather than a bare 1fr: 1fr means minmax(auto, 1fr), which
     keeps an automatic minimum, so a wide cell can make the two outer tracks
     unequal. The popup offsets below place a box by measuring across from its
     own column, which only lands on the opposite card if the two match. */
  grid-template-columns: minmax(0, 1fr) var(--tl-axis) minmax(0, 1fr);
}

.tl-cell--card {
  position: relative;
  cursor: pointer;
}
.tl-row--left  > .tl-cell--card { padding: 0 var(--tl-inset) 46px 0; }
.tl-row--right > .tl-cell--card { padding: 0 0 46px var(--tl-inset); }

/* connector + node dot */
.tl-connector {
  position: absolute;
  z-index: 2;
  top: 9px;
  width: 90px;
  height: 1px;
  background: var(--axis);
}
.tl-row--left  .tl-connector { right: -56px; }
.tl-row--right .tl-connector { left: -56px; }

.tl-dot {
  position: absolute;
  z-index: 2;
  background: var(--accent);
}
.tl-dot--round  { width: 9px; height: 9px; border-radius: 50%; top: 5px; }
.tl-dot--square { width: 7px; height: 7px; top: 6px; }
.tl-row--left  .tl-dot { right: -60px; }
.tl-row--right .tl-dot { left: -60px; }

/* node card */
.tl-card {
  border: 1px solid var(--line2);
  border-radius: 4px;
  padding: 18px 20px;
  background: var(--panel);
  transition: border-color .18s ease;
}
.tl-row--left .tl-card { text-align: right; }
.tl-cell--card:hover .tl-card,
.tl-cell--card:focus-visible .tl-card,
.tl-cell--card.is-pinned .tl-card { border-color: var(--accent); }

.tl-card__meta {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font: 500 10px/1 var(--mono);
  letter-spacing: .14em;
  color: var(--mute);
  margin-bottom: 12px;
}
.tl-card__title {
  font: 500 21px/1.25 var(--mono);
  letter-spacing: -.02em;
  color: var(--ink);
}
.tl-card__sub {
  font: 400 14px/1.4 var(--sans);
  color: var(--mute);
  margin-top: 8px;
}

.tl-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 14px;
}
.tl-tags--end { justify-content: flex-end; }

.tl-tag {
  font: 400 10px/1 var(--mono);
  letter-spacing: .08em;
  color: var(--mute);
  border: 1px solid var(--line2);
  border-radius: 3px;
  padding: 6px 8px;
}
.tl-tag--accent {
  color: var(--onAccent);
  background: var(--accent);
  border-color: transparent;
}

.tl-media {
  margin-top: 16px;
  height: 104px;
  border-radius: 3px;
  border: 1px solid var(--line2);
  background: repeating-linear-gradient(135deg, var(--stripe) 0 8px, var(--panel) 8px 16px);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.tl-media__label {
  font: 400 10px/1 var(--mono);
  letter-spacing: .12em;
  color: var(--mute);
  text-align: center;
  padding: 0 10px;
}
.tl-media__img { width: 100%; height: 100%; object-fit: cover; }

/* The box is about 4.5:1 against source photos nearer 1.5:1, so cover throws
   most of the frame away. This pulls the crop above centre for images whose
   subject sits high — the detection labels, in NODE_03's case. */
.tl-media__img--crop-high { object-position: center 38%; }
.tl-media__img--crop-low  { object-position: center 62%; }

/* Action link inside a popup card — the demo video, the project report. The
   card is already accent-filled, so the button inverts — --onAccent ground,
   accent text — which holds up in both themes without hard-coding a colour. */
.tl-popcard__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.tl-popcard__action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: 3px;
  background: var(--onAccent);
  /* The solid variant carries a border in its own background colour, so it
     can't end up a couple of pixels shorter than the outlined one beside it. */
  border: 1px solid var(--onAccent);
  color: var(--accent);
  font: 600 10px/1 var(--mono);
  letter-spacing: .14em;
  text-decoration: none;
  transition: opacity .18s ease, transform .18s ease;
}
.tl-popcard__action:hover {
  color: var(--accent);   /* hold the accent; the global a:hover lightens it */
  opacity: .88;
  transform: translateY(-1px);
}

/* Secondary action — same footprint, outlined rather than filled. */
.tl-popcard__action--ghost {
  background: transparent;
  color: var(--onAccent);
}
.tl-popcard__action--ghost:hover { color: var(--onAccent); }
.tl-popcard__action-icon { font-size: 12px; line-height: 1; }

/* hover / pin popup */
/* The popup mirrors the opposite column's card exactly: same width as a card,
   offset across its own column, then the axis column, then that card's inset.
   Written out of the same two tokens as the grid so the three can't drift. */
.tl-popwrap {
  position: absolute;
  top: 0;
  width: calc(100% - var(--tl-inset));
  z-index: 6;
}
.tl-row--left  .tl-popwrap { left: calc(100% + var(--tl-axis) + var(--tl-inset)); }
.tl-row--right .tl-popwrap { right: calc(100% + var(--tl-axis) + var(--tl-inset)); }

.tl-pop {
  transition: opacity .2s ease, transform .34s cubic-bezier(.2, .8, .2, 1);
  opacity: 0;
  pointer-events: none;
}
/* motion variants mirror the prototype's `popupMotion` prop */
:root[data-popup-motion="slide"] .tl-row--left  .tl-pop { transform: translateX(-40px); }
:root[data-popup-motion="slide"] .tl-row--right .tl-pop { transform: translateX(40px); }
:root[data-popup-motion="lift"] .tl-pop { transform: translateY(24px); }
:root[data-popup-motion="fade"] .tl-pop { transform: none; }

.tl-pop.is-open { opacity: 1; transform: none; }
.tl-pop.is-pinned { pointer-events: auto; }

.tl-popcard {
  background: var(--accent);
  border-radius: 4px;
  padding: 18px 20px;
  box-shadow: 0 14px 30px rgba(0, 0, 0, .5);
  text-align: left;
}
.tl-popcard__meta {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font: 600 10px/1 var(--mono);
  letter-spacing: .14em;
  color: var(--onAccent);
  margin-bottom: 12px;
}
.tl-popcard__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font: 400 13px/1.55 var(--sans);
  color: var(--onAccent);
}
.tl-popcard__body--tight { line-height: 1.5; }

/* year axis */
.tl-axis {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
.tl-rail {
  position: absolute;
  top: 0; bottom: 0;
  width: 1px;
  background: var(--axis);
}
.tl-year {
  position: relative;
  background: var(--bg);
  padding: 26px 6px 14px;
  font: 500 11px/1 var(--mono);
  letter-spacing: .08em;
  color: var(--mute);
}
.tl-year--accent { color: var(--accent); }

.tl-axis--tail {
  height: 26px;
  align-items: flex-end;
}
.tl-tailcap {
  position: relative;
  width: 38px;
  height: 1px;
  background: var(--axis);
}

/* -------------------------------- projects -------------------------------- */

.projects {
  scroll-margin-top: 64px;
  border-top: 1px solid var(--line);
  background: var(--panel);
}
.projects__inner {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 56px var(--gut) 64px;
}

.projects__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}
.projects__kicker {
  font: 500 10px/1 var(--mono);
  letter-spacing: .16em;
  color: var(--mute);
  margin-bottom: 12px;
}
.projects__title {
  font: 500 30px/1.2 var(--mono);
  letter-spacing: -.02em;
  color: var(--ink);
}

.projects__filters {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.filter {
  font: 500 10px/1 var(--mono);
  letter-spacing: .12em;
  cursor: pointer;
  padding: 10px 12px;
  border-radius: 3px;
  border: 1px solid var(--line2);
  background: transparent;
  color: var(--mute);
  transition: border-color .18s ease, color .18s ease, background .18s ease;
}
.filter:hover { border-color: var(--accent); color: var(--ink); }
.filter.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--onAccent);
}

.projects__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: 18px;
}

.pcard {
  border: 1px solid var(--line2);
  border-radius: 4px;
  padding: 20px 20px 22px;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: transform .2s ease, border-color .2s ease;
}
.pcard:hover {
  transform: translateY(-4px);
  border-color: var(--accent);
}
.pcard[hidden] { display: none; }

.pcard__where {
  font: 500 10px/1 var(--mono);
  letter-spacing: .14em;
  color: var(--mute);
}
.pcard__title {
  font: 500 18px/1.3 var(--mono);
  letter-spacing: -.02em;
  color: var(--ink);
}
.pcard__body {
  font: 400 13px/1.55 var(--sans);
  color: var(--mute);
}
.pcard__stack {
  font: 400 11px/1.4 var(--mono);
  color: var(--mute);
  margin-top: 2px;
}
.pcard__metric {
  font: 500 10px/1 var(--mono);
  letter-spacing: .1em;
  color: var(--onAccent);
  background: var(--accent);
  border-radius: 3px;
  padding: 7px 9px;
  align-self: flex-start;
}

.projects__count {
  margin-top: 18px;
  font: 400 11px/1 var(--mono);
  letter-spacing: .1em;
  color: var(--mute);
}

/* ---------------------------------- blog ---------------------------------- */

.blog {
  scroll-margin-top: 64px;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 56px var(--gut) 64px;
}

.blog__box {
  border: 1px solid var(--line2);
  border-radius: 4px;
  padding: 40px 32px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
  background: repeating-linear-gradient(135deg, var(--stripe) 0 10px, var(--bg) 10px 20px);
}
.blog__kicker {
  font: 500 10px/1 var(--mono);
  letter-spacing: .16em;
  color: var(--mute);
}
.blog__title {
  font: 500 clamp(26px, 4vw, 38px)/1.2 var(--mono);
  letter-spacing: -.02em;
  color: var(--ink);
}

.blog__slot {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.blog__cta {
  font: 500 11px/1 var(--mono);
  letter-spacing: .1em;
  color: var(--onAccent);
  background: var(--accent);
  text-decoration: none;
  border-radius: 3px;
  padding: 13px 16px;
  transition: transform .18s ease;
}
.blog__cta:hover { color: var(--onAccent); transform: translateY(-1px); }
.blog__hint {
  font: 400 11px/1.4 var(--mono);
  color: var(--mute);
}

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

.contact {
  scroll-margin-top: 64px;
  border-top: 1px solid var(--line);
  background: var(--panel);
}
.contact__inner {
  /* Buttons and social icons share a row, so they share a height rather than
     each arriving at one through its own padding arithmetic. */
  --ctl: 46px;

  max-width: var(--shell);
  margin: 0 auto;
  padding: 72px var(--gut) 48px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
}
.contact__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 640px;
}
.contact__eyebrow {
  font: 500 10px/1 var(--mono);
  letter-spacing: .18em;
  color: var(--mute);
}
.contact__title {
  margin: 0;
  font: 600 clamp(30px, 5vw, 52px)/1.08 var(--mono);
  letter-spacing: -.03em;
  color: var(--ink);
  text-align: center;
}

.contact__actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}
.cbtn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: var(--ctl);
  padding: 0 22px;
  text-decoration: none;
  border-radius: 3px;
  font: 600 12px/1 var(--mono);
  letter-spacing: .1em;
}
.cbtn--primary {
  background: var(--accent);
  color: var(--onAccent);
  border: 1px solid var(--accent);
  transition: transform .18s ease, opacity .18s ease;
}
.cbtn--primary:hover { transform: translateY(-2px); opacity: .9; }
.cbtn--ghost {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--axis);
  font-weight: 500;
  transition: transform .18s ease, border-color .18s ease;
}
.cbtn--ghost:hover { transform: translateY(-2px); border-color: var(--accent); }
.cbtn__arrow { font-size: 13px; line-height: 1; }

.social {
  width: var(--ctl);
  height: var(--ctl);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--axis);
  border-radius: 3px;
  text-decoration: none;
  color: var(--ink);
  font-family: var(--mono);
  transition: border-color .18s ease, color .18s ease;
}
.social:hover { border-color: var(--accent); color: var(--accent); }
.social svg { display: block; }

.contact__meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}
.contact__copy {
  font: 500 13px/1 var(--mono);
  color: var(--ink);
}
.contact__tag {
  font: 400 12px/1 var(--mono);
  letter-spacing: .06em;
  color: var(--mute);
}

/* ================================ responsive ==============================
   The prototype is desktop-only (no breakpoints). Below 860px the split
   timeline can't hold its side popups, so it collapses to a single left rail
   with the popup expanding inline under the node. Everything above that
   width renders exactly as designed.
   ========================================================================== */

@media (max-width: 860px) {
  :root { --gut: 20px; }

  .tl-row {
    display: flex;
    flex-direction: column;
  }
  .tl-cell--empty { display: none; }

  .tl-axis {
    order: -1;
    justify-content: flex-start;
    padding-left: 0;
  }
  .tl-axis .tl-rail { display: none; }
  .tl-year {
    background: transparent;
    padding: 22px 0 12px;
  }

  .tl-row--left  > .tl-cell--card,
  .tl-row--right > .tl-cell--card {
    padding: 0 0 30px 20px;
    border-left: 1px solid var(--axis);
  }

  /* Node dots straddle the 1px rail. Absolute offsets are measured from the
     cell's padding box, so the rail itself sits at left: -1px. */
  .tl-connector { display: none; }
  .tl-row--left .tl-dot,
  .tl-row--right .tl-dot { right: auto; }
  .tl-row--left .tl-dot--round,
  .tl-row--right .tl-dot--round  { left: -5px; top: 14px; }
  .tl-row--left .tl-dot--square,
  .tl-row--right .tl-dot--square { left: -4px; top: 15px; }

  .tl-row--left .tl-card { text-align: left; }
  .tl-tags--end { justify-content: flex-start; }

  .tl-popwrap {
    position: static;
    width: auto;
    margin-top: 12px;
  }
  .tl-pop { transform: none !important; }
  .tl-pop:not(.is-open) { display: none; }

  .tl-row--tail { display: none; }

  .tl__headlabel:last-child { display: none; }
}

@media (max-width: 820px) {
  /* Logo + links + actions stop fitting on one line here — the threshold moved
     up with the larger nav type. Logo and actions keep the top row; the links
     drop to their own row, still centred. */
  .nav__inner {
    grid-template-columns: 1fr auto;
    gap: 12px 16px;
  }
  .nav__logo    { grid-area: 1 / 1; }
  .nav__actions { grid-area: 1 / 2; }
  .nav__links   { grid-area: 2 / 1 / 3 / -1; }
}

@media (max-width: 560px) {
  .nav__inner { padding: 12px var(--gut); }
  .nav__links { gap: 14px; }

  /* The logo + CV + toggle row has to clear the right edge on a phone. Trim a
     notch off the type and the padding — still a step up from the original
     sizes, just not the full desktop bump. */
  :root             { --navctl: 30px; }
  .nav__logo        { font-size: 13px; }
  .nav__cv          { font-size: 12px; padding: 0 10px; }
  .themetoggle      { font-size: 11px; }
  .themetoggle__seg { padding: 0 8px; }
  .nav__actions     { gap: 10px; }

  /* Drafting annotations need room around them to read as annotations; this
     narrow they just collide with the scroll cue. */
  .hero__datum { display: none; }

  /* The separator only means anything between two items sharing a line. Once
     the job-title span wraps, it strands itself at the head of the next one. */
  .hero__eyebrow-sep { display: none; }

  .tl-card__meta {
    flex-direction: column;
    gap: 6px;
  }
  /* Same reason as .tl-card__meta: the node label plus the PINNED hint can't
     share a row this narrow, so let the hint drop below it. */
  .tl-popcard__meta {
    flex-wrap: wrap;
    gap: 4px 10px;
  }
  .blog__box { padding: 32px 20px; }
}

@media (max-width: 480px) {
  /* Below this the longest role string wraps to two lines while the shortest
     still fits on one, so the hero would jolt up and down on every cycle of
     the typing loop. Reserving both lines keeps the layout still. */
  .hero__role-text { min-height: 2.8em; }
}

/* --------------------------- reduced motion ------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee__track,
  .scrollcue__inner,
  .statuspill::before,
  .statuspill__dot,
  .hero__role-text::after {
    animation: none;
  }
  .statuspill::before { opacity: .24; }
  .nav,
  .tl-pop,
  .pcard,
  .tl-card,
  .cbtn,
  .tl-popcard__action,
  .filter { transition-duration: .01ms; }
}

/* ------------------------------- print ----------------------------------- */

@media print {
  .progress, .nav, .scrollcue, .marquee, .hero__bg { display: none; }
  .hero { min-height: 0; }
  .tl-pop { opacity: 1 !important; transform: none !important; position: static; }
  .tl-popwrap { position: static; width: auto; margin-top: 12px; }
}
