/* ==========================================================================
   Yuvraj Chahal — portfolio
   Design system. Palette is derived from the Jupiter render (see
   JUPITER_HERO_BRIEF.md §2). Every colour on the site comes from these tokens.
   ========================================================================== */

:root {
  /* Warmth comes from light, not pigment: the ground is a brown-black (hue ~20°,
     lightness ~4%), the ink stays cream, and --cool is kept alive as a counterpoint. */
  --ground: #0F0906;
  --panel:  #160E0A;
  --ink:    #F2EDE4;
  --dim:    #A8A096;   /* 7.66:1 on --ground */
  --faint:  #8A8276;   /* 5.21:1 on --ground, 5.03:1 on --panel — clears AA for 11–13px mono labels */
  --accent: #E4A44E;   /* 9.15:1 on --ground, 8.82:1 on --panel */
  --ember:  #D9724E;
  --cool:   #7E90A8;   /* 6.07:1 on --ground */

  /* RGB triplets for alpha use — every rgba() on the site derives from these. */
  --ground-rgb: 15, 9, 6;
  --panel-rgb:  22, 14, 10;
  --ink-rgb:    242, 237, 228;
  --accent-rgb: 228, 164, 78;

  --rule:        rgba(var(--ink-rgb), .10);
  --rule-strong: rgba(var(--ink-rgb), .18);
  --accent-rule: rgba(var(--accent-rgb), .28);
  --bleed-opacity: 1;   /* runtime hook for the light bleed (scroll.js may drive it) */

  --sans: "Archivo", "Helvetica Neue", Arial, system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

  --gutter:  clamp(20px, 5vw, 64px);
  --wrap:    1320px;
  --measure: 65ch;

  --step--1: 0.8125rem;                       /* 13px */
  --step-0:  clamp(1rem, 0.95rem + 0.3vw, 1.125rem);   /* 16–18 */
  --step-1:  clamp(1.125rem, 1rem + 0.6vw, 1.375rem);
  --step-2:  clamp(1.5rem, 1.2rem + 1.4vw, 2.25rem);
  --step-3:  clamp(2rem, 1.4rem + 2.8vw, 3.25rem);
  --step-4:  clamp(2.5rem, 1.6rem + 4vw, 4.5rem);
  --display: clamp(3.5rem, 9.6vw, 8.75rem);    /* 56–140 */

  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --nav-h: 64px;

  /* Corner radii — one scale so every box reads as the same family as the pill buttons. */
  --r-sm: 6px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-pill: 999px;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
img, svg, video, canvas { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4, p, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
::selection { background: rgba(var(--accent-rgb), .30); color: var(--ink); }

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

/* ---------- layout ---------- */
.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: clamp(80px, 12vw, 152px); }
.section--tight { padding-block: clamp(56px, 8vw, 104px); }

/* ---------- type ---------- */
.eyebrow {
  font-family: var(--mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--faint);
}
.eyebrow--accent { color: var(--accent); }
.eyebrow--dash::before { content: "— " / ""; }   /* empty alt: the dash is decoration, not part of the name */

.display {
  font-size: var(--display);
  font-weight: 800;
  line-height: .9;
  letter-spacing: -.04em;
  text-wrap: balance;
}
.h-1 { font-size: var(--step-4); font-weight: 700; line-height: 1.02; letter-spacing: -.03em; text-wrap: balance; }
.h-2 { font-size: var(--step-3); font-weight: 700; line-height: 1.05; letter-spacing: -.03em; text-wrap: balance; }
.h-3 { font-size: var(--step-2); font-weight: 700; line-height: 1.15; letter-spacing: -.02em; }
.lede { font-size: var(--step-1); line-height: 1.5; color: var(--dim); max-width: var(--measure); text-wrap: pretty; }
.prose { max-width: var(--measure); color: var(--dim); }
.prose p + p { margin-top: 1.1em; }
.prose strong, .prose b { color: var(--ink); font-weight: 600; }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.num { font-variant-numeric: tabular-nums; }
.outline {
  color: transparent;
  -webkit-text-stroke: 1.5px var(--cool);   /* the cool note: keeps the warm ground reading warm */
}

/* ---------- controls ----------
   One rule: things you read stay technical (mono, uppercase, tracked); things you
   press become soft (full-radius pill, sentence case, Archivo, no tracking).
   State changes are a brightness lift + ~2% scale — never a colour inversion. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45em;
  height: 46px; padding: 0 28px;
  font-family: var(--sans); font-size: 15px; font-weight: 500;
  letter-spacing: 0; text-transform: none; line-height: 1;
  border: 1px solid var(--rule-strong); border-radius: 999px;
  background: rgba(var(--ink-rgb), .06);
  color: var(--ink);
  white-space: nowrap;
  transition: filter .18s ease, transform .18s ease, background-color .18s ease;
}
.btn:hover, .btn:focus-visible { filter: brightness(1.08); transform: scale(1.02); }
.btn:active { filter: brightness(.96); transform: scale(.99); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.btn--primary { background: var(--accent); border-color: var(--accent); color: var(--ground); font-weight: 600; }
.btn--primary:focus-visible { outline-color: var(--ink); }
.btn--sm { height: 36px; padding: 0 18px; font-size: 13px; }

.link {
  font-family: var(--mono); font-size: var(--step--1); letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim); transition: color .25s ease;
}
.link:hover, .link:focus-visible { color: var(--accent); }
.link--under { position: relative; color: var(--ink); transition: color .25s ease; }
/* The rule is drawn by ::after rather than a border so the 44px tap target below
   can grow around the text without pushing the underline away from it. */
.link--under::after {
  content: ""; position: absolute; left: 0; right: 0; top: calc(50% + 16px);
  border-bottom: 1px solid var(--rule-strong); transition: border-color .25s ease;
}
.link--under:hover, .link--under:focus-visible { color: var(--accent); }
.link--under:hover::after, .link--under:focus-visible::after { border-color: var(--accent); }

/* Minimum 44px tap target for the 11–13px text links. line-height:1 keeps the
   glyphs on the same baseline they had; the nav's padding-block is reduced by
   the same amount the links grew, so the bar's height does not change. */
.nav__links .link, .footer__meta a, .link--under, .nav__brand {
  display: inline-flex; align-items: center; min-height: 44px; line-height: 1;
}

.chip {
  display: inline-block; padding: 6px 10px;
  font-family: var(--mono); font-size: 11px; font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); border: 1px solid var(--accent-rule); border-radius: var(--r-pill);
}
.chips { display: flex; flex-wrap: wrap; gap: 10px; }

/* Status readout: shares the button family's radius and height, but it is read,
   not pressed, so the label stays mono/uppercase. */
.pill {
  display: inline-flex; align-items: center; gap: 10px;
  height: 42px; padding: 0 20px 0 16px;
  font-family: var(--mono); font-size: 12px; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  line-height: 1;
  color: var(--dim); border: 1px solid var(--rule-strong); border-radius: 999px;
  background: rgba(var(--ground-rgb), .45);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.pill__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); animation: pulse 2.2s ease infinite; }

/* ---------- nav ---------- */
.nav {
  position: sticky; top: 0; z-index: 50;
  display: flex; justify-content: space-between; align-items: center; gap: 24px;
  padding: 12px var(--gutter);
  background: rgba(var(--ground-rgb), .82);   /* .55 measured 3.86–4.21:1 for the mono links over the planet */
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--rule);
}
.nav__brand {
  display: inline-flex; align-items: center; gap: 10px; white-space: nowrap;
  font-family: var(--mono); font-size: 13px; font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
}
.nav__mark { width: 9px; height: 9px; background: var(--accent); border-radius: 3px; }
.nav__links { display: flex; align-items: center; gap: clamp(16px, 2.6vw, 32px); }
.nav__links .link { color: var(--dim); font-size: 12px; }
.nav__links .link:hover, .nav__links .link:focus-visible { color: var(--accent); }

/* ---------- Jupiter background ---------- */
.jupiter-stage { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.jupiter {
  position: absolute; inset: 0; width: 100%; height: 100%;
  will-change: filter, transform;
}
.jupiter-fallback {
  position: absolute; inset: 0; display: none;
  background:
    radial-gradient(circle at 78% 48%, #D0853A 0%, #944426 22%, #40190F 44%, rgba(var(--ground-rgb),0) 62%),
    radial-gradient(circle at 78% 48%, rgba(249, 244, 232, .45) 0%, rgba(249, 244, 232, 0) 12%);
  filter: blur(6px) brightness(.85);   /* sharp-ish at rest; the veil dims it on scroll */
}
html:not(.js) .jupiter-fallback { display: block; }   /* JS off: show the gradient stand-in */
.jupiter-stage.is-fallback .jupiter { display: none; }
.jupiter-stage.is-fallback .jupiter-fallback { display: block; }
.veil {
  position: absolute; inset: 0;
  /* Hero-shaped at rest, but it is also the only thing between small mono
     labels and the planet's lit limb once the page scrolls — so the top stop
     is strong enough to keep those labels legible, not just decorative. */
  background: linear-gradient(180deg, rgba(var(--ground-rgb), .62) 0%, rgba(var(--ground-rgb), .82) 45%, rgba(var(--ground-rgb), .92) 100%);
  opacity: 0;
}
/* Still-frame pages: the planet sits top-right behind a wide headline, so the veil
   is left-weighted and always partly on — the type column stays clean. */
body:not(.page-home) .veil {
  background: linear-gradient(90deg, rgba(var(--ground-rgb),.88) 0%, rgba(var(--ground-rgb),.62) 38%, rgba(var(--ground-rgb),.22) 62%, rgba(var(--ground-rgb),.05) 100%),
              linear-gradient(180deg, rgba(var(--ground-rgb),0) 0%, rgba(var(--ground-rgb),.55) 100%);
}
/* Light bleed: a very wide, very faint warm radial spilling off the planet's lit limb.
   The empty side of the hero wants light, not objects. Pure CSS; --bleed-opacity is a
   runtime hook so the choreography can fade it with the zoom. */
.bleed {
  position: absolute; inset: 0; pointer-events: none;
  opacity: var(--bleed-opacity);
  background: radial-gradient(ellipse 48vw 62vh at 72% 50%,
    rgba(var(--accent-rgb), .12) 0%,
    rgba(var(--accent-rgb), .06) 38%,
    rgba(var(--accent-rgb), .02) 70%,
    rgba(var(--accent-rgb), 0) 100%);
}
/* Film grain: fixed-size fine monochrome noise, static, ~3.5%. Kills residual banding
   and gives the black some tooth. Above the canvas, below .page. */
.grain {
  position: absolute; inset: 0; pointer-events: none;
  opacity: .035;
  /* Deliberately no mix-blend-mode: overlay forced a full-screen compositor
     blend every frame (~4fps at 2×). */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='table' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 180px 180px; background-repeat: repeat;
}
.page { position: relative; z-index: 1; }

/* ---------- hero ---------- */
.hero {
  position: relative;
  min-height: calc(100vh - var(--nav-h));    /* older iOS drops the whole svh declaration */
  min-height: calc(100svh - var(--nav-h));   /* the nav is in-flow above it */
  isolation: isolate;
  display: grid; align-items: center;
  padding-block: clamp(88px, 11vh, 128px) clamp(72px, 10vh, 112px);
}
/* Text protection: a soft left-to-right scrim so type stays legible where the disc
   edge meets the column on mid-width screens. Sits under the text, over the canvas. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(90deg, rgba(var(--ground-rgb),.78) 0%, rgba(var(--ground-rgb),.55) 34%, rgba(var(--ground-rgb),.18) 52%, rgba(var(--ground-rgb),0) 66%);
}
.hero__inner { position: relative; max-width: 660px; }
.hero__eyebrow { display: block; margin-bottom: 22px; color: var(--dim); }
.hero__name { display: block; }
.hero__hook {
  margin-top: 30px;
  font-size: clamp(1.375rem, 1.05rem + 1.3vw, 1.95rem);
  font-weight: 500;
  line-height: 1.28;
  letter-spacing: -.02em;
  max-width: 28ch;
  text-wrap: balance;
}
.hero__support {
  margin-top: 18px;
  color: var(--dim);
  max-width: 52ch;
  text-wrap: pretty;
}
.hero__support .mono { color: var(--accent); font-size: var(--step--1); letter-spacing: .1em; text-transform: uppercase; }
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 34px; }
.hero__status { display: flex; margin-top: 20px; }
.hero__cue {
  position: absolute; bottom: 28px; left: var(--gutter);
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em; color: var(--faint);
}
.hero__cue::after { content: ""; width: 1px; height: 40px; background: linear-gradient(var(--faint), transparent); animation: cue 2.4s ease infinite; }

.hero-in { animation: heroIn .9s var(--ease-out) both; }
.hero-in--2 { animation-delay: .08s; }
.hero-in--3 { animation-delay: .16s; }
.hero-in--4 { animation-delay: .24s; }
.hero-in--5 { animation-delay: .32s; }

/* ---------- ticker ---------- */
.ticker {
  position: relative; overflow: hidden;
  padding-block: 16px;
  border-block: 1px solid var(--rule);
  background: rgba(var(--panel-rgb), .55);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.ticker__track { display: inline-flex; white-space: nowrap; animation: tick 40s linear infinite; }
.ticker__item { padding-right: 24px; font-family: var(--mono); font-size: 13px; letter-spacing: .2em; color: var(--faint); }

/* ---------- section headers ---------- */
.section-head { display: flex; justify-content: space-between; align-items: baseline; gap: 24px; margin-bottom: 28px; }
.section-head .eyebrow { white-space: nowrap; color: var(--ink); }   /* sits over the planet's brightest band; --dim measured 4.2:1 there */

/* ---------- project rows (home) ----------
   Each row is one anchor. At rest it is a hairline-divided list; on hover/focus it
   becomes a soft rounded surface that reaches --row-pad beyond the content on each
   side (negative inline margin + matching padding), so the surface reads as a card
   while the dividers — drawn by ::before, inset by the same --row-pad — stay flush
   with the section header above and the .rows rule below. */
.rows { border-bottom: 1px solid var(--rule); }
.row {
  --row-pad: clamp(12px, 2vw, 24px);
  position: relative;
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) minmax(280px, 380px) 48px;
  gap: clamp(20px, 3vw, 40px); align-items: center;
  margin-inline: calc(-1 * var(--row-pad));
  padding: clamp(26px, 2.6vw, 34px) var(--row-pad);
  border-radius: var(--r-lg);
  color: inherit;
  transition: background-color .3s ease;
}
.row::before {
  content: ""; position: absolute; top: 0; left: var(--row-pad); right: var(--row-pad);
  border-top: 1px solid var(--rule);
}
.row:hover, .row:focus-visible { background: rgba(var(--panel-rgb), .88); }
.row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.row__index { font-family: var(--mono); font-size: 14px; color: var(--faint); }
.row__title { display: block; font-size: var(--step-3); font-weight: 700; line-height: 1.05; letter-spacing: -.025em; transition: color .3s ease; }
.row__blurb { display: block; margin-top: 8px; color: var(--dim); line-height: 1.5; max-width: 52ch; text-wrap: pretty; }

/* Facts: the project page's own feature headings, indexed like the page itself,
   then a ROLE · CONTEXT readout in the mono label voice. */
.row__facts { display: grid; gap: 4px; margin-top: 16px; max-width: 52ch; }
.row__fact {
  display: grid; grid-template-columns: 24px minmax(0, 1fr); align-items: baseline;
  font-size: 14px; line-height: 1.35; color: var(--dim); letter-spacing: -.005em;
}
.row__fact-i { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; color: var(--accent); font-variant-numeric: tabular-nums; }
.row__meta {
  display: block; margin-top: 10px;
  font-family: var(--mono); font-size: 11px; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  color: var(--dim);   /* --faint measured 3.83:1 over the zoomed planet */
}
.row__chips { margin-top: 14px; }

/* Figure column: the diagram plus the visible call-to-action beneath it. The CTA is a
   span (a button inside a link is invalid), styled by the .btn family. */
.row__side { display: grid; gap: 14px; justify-items: start; }
.row__fig {
  display: block; aspect-ratio: 380 / 210; width: 100%; max-width: 100%;
  border: 1px solid var(--rule); background: rgba(var(--panel-rgb), .7); border-radius: var(--r-lg);
  overflow: hidden; opacity: .8;
  transition: opacity .4s ease, transform .4s var(--ease-out);
}
.row__fig svg { width: 100%; height: 100%; }
.row__cta { height: 38px; padding: 0 20px; font-size: 13px; transition: filter .18s ease, transform .18s ease, background-color .3s ease, border-color .3s ease; }
.row__cta span { transition: transform .3s var(--ease-out); }
.row__arrow { font-size: 30px; color: var(--faint); transition: transform .3s var(--ease-out), color .3s ease; }
.row:hover .row__title, .row:focus-visible .row__title { color: var(--accent); }
.row:hover .row__arrow, .row:focus-visible .row__arrow { color: var(--accent); transform: translate(6px, -6px); }
.row:hover .row__fig, .row:focus-visible .row__fig { opacity: 1; transform: scale(1.02); }
.row:hover .row__cta, .row:focus-visible .row__cta { background: rgba(var(--ink-rgb), .12); border-color: rgba(var(--ink-rgb), .3); }
.row:hover .row__cta span, .row:focus-visible .row__cta span { transform: translateX(3px); }

/* ---------- figures (svg diagrams) ---------- */
.fig-stroke { stroke: var(--accent); }
.fig-fill { fill: var(--accent); }
.fig-ink { stroke: rgba(var(--ink-rgb), .55); }
.fig-grid { stroke: rgba(var(--ink-rgb), .06); }
.fig-bg { fill: var(--ground); }

/* ---------- about teaser ---------- */
.teaser { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 48px; align-items: start; }
.teaser__text { font-size: var(--step-2); line-height: 1.4; font-weight: 500; letter-spacing: -.01em; max-width: 30ch; text-wrap: pretty; }
.teaser .link--under { display: inline-flex; margin-top: 20px; }   /* the link box grew 12px upward for its tap target */

/* ---------- footer / contact ----------
   The headline is the mailto; under it the three ways to reach me are full-size pills
   with the address spelled out, plus a Copy pill that only exists when JS can copy. */
.footer { border-top: 1px solid var(--rule); padding-block: clamp(80px, 12vw, 140px) 40px; }
.footer__cta {
  display: inline-block;
  font-size: clamp(2.75rem, 7vw, 6.5rem); font-weight: 800; line-height: 1; letter-spacing: -.035em;
  color: var(--ink); transition: color .3s ease;
}
.footer__cta:hover, .footer__cta:focus-visible { color: var(--accent); }
.footer__status { display: block; margin-bottom: 20px; }
.contact-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 48px; }
.contact-actions__group { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.copy-email { padding: 0 18px; min-width: 96px; }
.copy-email.is-copied { color: var(--accent); border-color: var(--accent-rule); }
html:not(.js) .copy-email { display: none; }
.contact-note { margin-top: 24px; color: var(--dim); font-size: var(--step-0); }
.contact-note span { white-space: nowrap; }   /* wraps at the separator, never inside "co-op" */
.footer__meta {
  display: flex; justify-content: space-between; flex-wrap: wrap; align-items: center; gap: 12px;
  margin-top: 88px; padding-top: 24px; border-top: 1px solid var(--rule);
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  color: var(--dim);   /* --faint measured 4.26:1 here over the planet */
}
.footer__meta a { transition: color .25s ease; }
.footer__meta a:hover, .footer__meta a:focus-visible { color: var(--accent); }

/* Visually hidden, still read by assistive tech. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Skip link: clipped like any .sr-only until it takes focus, then it unclips
   into a pill in the top-left corner, above the sticky nav. */
.skip-link:focus {
  position: fixed; top: 12px; left: 12px; z-index: 100;
  width: auto; height: auto; margin: 0; padding: 0 22px;
  clip: auto; clip-path: none; overflow: visible;
  display: inline-flex; align-items: center; min-height: 44px;
  font-family: var(--sans); font-size: 15px; font-weight: 500; line-height: 1;
  color: var(--ink); background: var(--panel);
  border: 1px solid var(--rule-strong); border-radius: var(--r-pill);
}

/* ---------- project pages ---------- */
.phero { padding-block: clamp(72px, 10vw, 120px) clamp(40px, 6vw, 72px); }
.phero__meta { display: flex; gap: 24px; flex-wrap: wrap; margin-bottom: 28px; }
.phero__title { margin-top: 8px; }
.phero__lede { margin-top: 28px; }
.meta {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px;
  margin-top: clamp(40px, 5vw, 64px); padding-top: 28px; border-top: 1px solid var(--rule);
}
.meta__key { font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.meta__val { margin-top: 8px; font-size: var(--step-0); color: var(--ink); }

.band {
  position: relative; aspect-ratio: 1400 / 440; max-width: 100%;
  border: 1px solid var(--rule); background: rgba(var(--panel-rgb), .7); overflow: hidden; border-radius: var(--r-lg);
}
.band svg { width: 100%; height: 100%; }
.band__cap, .band__tag {
  position: absolute; bottom: 14px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--faint);
}
.band__cap { left: 16px; } .band__tag { right: 16px; }
.band__label {
  position: absolute;
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em; color: var(--dim);
}
.band__label--accent { color: var(--accent); }
.band__label--tagging { top: 14.5%; left: 27.5%; }
.band__label--api { bottom: 14.5%; left: 41.5%; }

.block { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 48px; align-items: start; }
.block + .block { margin-top: clamp(56px, 8vw, 96px); }
.block .prose { font-size: var(--step-1); line-height: 1.55; }
.block__sub { margin-top: 36px; }
.block__sub .h-3 { margin-bottom: 10px; }

.features { display: grid; gap: 0; }
.feature {
  display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 20px;
  padding-block: 28px; border-top: 1px solid var(--rule);
}
.feature:last-child { border-bottom: 1px solid var(--rule); }
.feature__num { font-family: var(--mono); font-size: 13px; color: var(--accent); padding-top: 4px; }
.feature__title { font-size: var(--step-1); font-weight: 600; letter-spacing: -.01em; }
.feature__body { margin-top: 8px; color: var(--dim); max-width: var(--measure); text-wrap: pretty; }

.next {
  border-top: 1px solid var(--rule);
  padding-block: clamp(48px, 8vw, 96px) 40px;
}
.next__link { display: flex; justify-content: space-between; align-items: center; gap: 24px; }
.next__title { display: block; margin-top: 10px; font-size: var(--step-3); font-weight: 700; letter-spacing: -.025em; line-height: 1.05; transition: color .3s ease; }
.next__arrow { font-size: 44px; color: var(--faint); transition: transform .3s var(--ease-out), color .3s ease; }
.next__link:hover .next__title, .next__link:focus-visible .next__title { color: var(--accent); }
.next__link:hover .next__arrow, .next__link:focus-visible .next__arrow { color: var(--accent); transform: translateX(10px); }

/* ---------- about page ---------- */
.about-grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(280px, .8fr); gap: clamp(32px, 5vw, 72px); align-items: start; margin-top: clamp(40px, 6vw, 72px); }
.portrait {
  position: relative; aspect-ratio: 4 / 5; max-width: 100%;
  border: 1px solid var(--rule); background: rgba(var(--panel-rgb), .7); border-radius: var(--r-lg);
  display: grid; place-items: center; overflow: hidden;
}
.portrait__grid { position: absolute; inset: 0; background-image: linear-gradient(var(--rule) 1px, transparent 1px), linear-gradient(90deg, var(--rule) 1px, transparent 1px); background-size: 48px 48px; }
.portrait__label { position: relative; font-family: var(--mono); font-size: 11px; letter-spacing: .14em; color: var(--faint); }

.skills { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 32px; margin-top: 40px; }
.skills__group .eyebrow { display: block; margin-bottom: 16px; color: var(--accent); }
.skills__group li { padding-block: 9px; border-top: 1px solid var(--rule); color: var(--dim); }
.skills__group li:last-child { border-bottom: 1px solid var(--rule); }

.timeline { display: grid; }
.timeline__item {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 24px; align-items: start;
  padding-block: 24px; border-top: 1px solid var(--rule);
}
.timeline__item:last-child { border-bottom: 1px solid var(--rule); }
.timeline__title { font-size: var(--step-1); font-weight: 600; letter-spacing: -.01em; }
.timeline__title small { font-family: var(--mono); font-size: 12px; font-weight: 500; color: var(--accent); margin-left: 8px; letter-spacing: .1em; }
.timeline__sub { margin-top: 6px; color: var(--dim); max-width: var(--measure); }
.timeline__when { font-family: var(--mono); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--faint); padding-top: 6px; white-space: nowrap; }

.course { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; }
.course__item .eyebrow { display: block; margin-bottom: 12px; color: var(--accent); }
.course__item p { color: var(--dim); text-wrap: pretty; }

/* ---------- section rail ----------
   A gauge in the left gutter: a hairline track that fills with scroll progress,
   a tick per section, and the active section's label. Labels stay hidden until
   the rail is hovered or focused so the gutter reads as a rule, not a column.
   Desktop only — below 1100px the gutter is too narrow to hold it. */
.rail {
  position: fixed;
  left: clamp(20px, 2.4vw, 34px);
  top: calc(var(--nav-h) + 64px);   /* clear of the nav, so the first dot never crowds it */
  bottom: 56px;
  width: 1px;
  z-index: 40;
  display: none;
}
.rail__track {
  position: absolute; inset: 0;
  background: rgba(var(--ink-rgb), .16);
}
.rail__fill {
  position: absolute; left: 0; top: 0; width: 1px;
  height: 0;
  background: linear-gradient(180deg, rgba(var(--accent-rgb), .35), var(--accent));
  transform-origin: top;
}
.rail__item {
  position: absolute; left: 0;
  transform: translateY(-50%);
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px 10px 0;       /* a 44px-tall hit area around a 4px dot */
  margin-left: -10px; padding-left: 10px;
  color: var(--faint);
  transition: color .3s ease;
}
.rail__dot {
  position: relative; flex: none;
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(var(--ink-rgb), .22);
  transition: background-color .3s ease, transform .3s var(--ease-out), box-shadow .3s ease;
}
.rail__label {
  display: none;                   /* shown only where the gutter can hold it — see below */
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
  opacity: 0; transform: translateX(-4px);
  transition: opacity .3s ease, transform .3s var(--ease-out), color .3s ease;
}
/* Passed sections read as done; the current one is lit and named. */
.rail__item.is-past .rail__dot { background: rgba(var(--accent-rgb), .45); }
.rail__item.is-current { color: var(--ink); }
.rail__item.is-current .rail__dot {
  background: var(--accent);
  transform: scale(1.5);
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), .14);
}
.rail__item.is-current .rail__label { opacity: 1; transform: none; }
.rail:hover .rail__label,
.rail:focus-within .rail__label { opacity: 1; transform: none; }
.rail__item:hover, .rail__item:focus-visible { color: var(--ink); }
.rail__item:hover .rail__dot { background: var(--accent); }
.rail__item:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--r-sm); }

/* The gutter is what limits this. Below 1440 the wrap stops centring and the
   gutter collapses to ~60px, which holds the track and dots but not a label —
   so the rail degrades to a pure progress gauge rather than overlapping type. */
@media (min-width: 1100px) { .js .rail { display: block; } }
@media (min-width: 1440px) { .rail__label { display: block; } }
@media (prefers-reduced-motion: reduce) {
  .rail__dot, .rail__label, .rail__item, .rail__fill { transition: none !important; }
}
@media print { .rail { display: none !important; } }

/* ---------- reveal on scroll (gated on JS, so no-JS stays readable) ---------- */
.reveal { transition: opacity .9s var(--ease-out), transform .9s var(--ease-out); }
.js .reveal { opacity: 0; transform: translateY(22px); }
.js .reveal.is-in { opacity: 1; transform: none; }
.js .reveal:focus-within { opacity: 1; transform: none; transition: none; }   /* keyboard focus must never land on an invisible element */

/* ---------- keyframes ---------- */
@keyframes heroIn { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@keyframes tick { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes cue { 0% { transform: scaleY(0); transform-origin: top; } 50% { transform: scaleY(1); transform-origin: top; } 51% { transform-origin: bottom; } 100% { transform: scaleY(0); transform-origin: bottom; } }

/* ---------- responsive ---------- */
@media (max-width: 1080px) {
  .row { grid-template-columns: 48px minmax(0, 1fr) 40px; }
  .row__side { grid-column: 2; max-width: 420px; }
  .row__arrow { grid-row: 1; grid-column: 3; }
  .block, .teaser { grid-template-columns: 1fr; gap: 20px; }
  .skills { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Short viewports (e.g. 1280×720): tighten the stack so name, hook, both buttons and
   the status pill all sit above the fold and clear the scroll cue. */
@media (max-height: 760px) {
  .hero { padding-block: 40px 48px; }
  .hero__eyebrow { margin-bottom: 14px; }
  .hero__hook { margin-top: 18px; }
  .hero__support { margin-top: 14px; }
  .hero__actions { margin-top: 22px; }
  .hero__status { margin-top: 14px; }
  .hero__cue { bottom: 20px; }
  .hero__cue::after { height: 24px; }
}
@media (max-width: 760px) {
  .section-head { flex-wrap: wrap; }
  .section-head .eyebrow { white-space: normal; }
  .band__tag { display: none; }
  .nav__links .link { white-space: nowrap; }
  /* Phone: the planet peeks in above the type, so the scrim runs top-to-bottom. */
  .hero::before {
    background: linear-gradient(180deg, rgba(var(--ground-rgb),0) 0%, rgba(var(--ground-rgb),.35) 18%, rgba(var(--ground-rgb),.82) 34%, rgba(var(--ground-rgb),.94) 55%, rgba(var(--ground-rgb),.94) 100%);
  }
  .band__label { display: none; }
  .nav { padding-block: 8px; }
  .nav__brand { font-size: 12px; }
  .nav__links { gap: 14px; }
  .nav__links .link { font-size: 11px; }
  .btn--sm { padding: 8px 12px; font-size: 11px; }
  .nav__links .link--hide-sm { display: none; }
  .hero { padding-top: 96px; }
  /* Portrait: the planet is centred in the top third, so the bleed follows it. */
  .bleed {
    background: radial-gradient(ellipse 70vw 34vh at 50% 30%,
      rgba(var(--accent-rgb), .12) 0%,
      rgba(var(--accent-rgb), .05) 45%,
      rgba(var(--accent-rgb), 0) 100%);
  }
  .hero__hook { max-width: none; }
  .row { grid-template-columns: 40px minmax(0, 1fr); gap: 16px 12px; }
  .row__arrow { display: none; }
  .row__side { grid-column: 1 / -1; max-width: none; justify-items: stretch; }
  .row__fig { aspect-ratio: 380 / 190; }
  .row__cta.btn--sm { height: 44px; padding: 0 20px; font-size: 14px; }   /* the nav's compact btn--sm sizing does not apply here */
  .row__facts { max-width: none; }
  .contact-actions { margin-top: 36px; }
  .meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .about-grid, .course { grid-template-columns: 1fr; }
  .timeline__item { grid-template-columns: 1fr; gap: 8px; }
  .footer__meta { flex-direction: column; }
}
/* Below ~375px the nav row cannot hold brand + links + Resume. Wrap to two rows
   rather than dropping the pill: a project page's footer is the next-project
   link, so hiding it would leave those pages with no resume link at all. */
@media (max-width: 374px) {
  .nav { flex-wrap: wrap; row-gap: 8px; }
  .nav__links { width: 100%; justify-content: space-between; }
}
@media (max-width: 480px) {
  .nav { gap: 12px; padding-inline: 16px; }
  .nav__links { gap: 10px; }
  .btn--sm { padding: 8px 11px; }
  .skills { grid-template-columns: 1fr; }
  .hero__actions .btn { width: 100%; }
  .hero__status .pill { width: 100%; justify-content: center; }
  .contact-actions, .contact-actions__group { width: 100%; }
  .contact-actions > .btn { width: 100%; }
  .contact-actions__group { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
  .contact-actions__group .btn { min-width: 0; }
}

/* Landscape laptop windows (e.g. 1280×620 once browser chrome is subtracted): the
   760px tier is not enough, so the display type steps down one size and the stack
   closes up until the name, hook, both buttons and the status pill clear the fold.
   Scoped above 760px so phones keep their own hero. */
@media (max-height: 660px) and (min-width: 761px) {
  .hero { padding-block: 28px 32px; }
  .hero__name { font-size: clamp(2.5rem, 6.2vw, 5rem); }
  .hero__eyebrow { margin-bottom: 10px; }
  .hero__hook { margin-top: 14px; font-size: clamp(1.25rem, 1rem + .9vw, 1.6rem); }
  .hero__support { margin-top: 10px; }
  .hero__actions { margin-top: 18px; }
  .hero__status { margin-top: 10px; }
  .hero__cue { bottom: 14px; }
  .hero__cue::after { height: 16px; }
}

/* Portrait tablets (e.g. 768×1024) get the same centred planet as phones — scroll.js frames
   on aspect < 0.9 — so the hero scrim and the bleed must follow the aspect, not the width. */
@media (max-aspect-ratio: 9/10) and (min-width: 761px) {
  .hero::before {
    background: linear-gradient(180deg, rgba(var(--ground-rgb),0) 0%, rgba(var(--ground-rgb),.35) 18%, rgba(var(--ground-rgb),.82) 34%, rgba(var(--ground-rgb),.94) 55%, rgba(var(--ground-rgb),.94) 100%);
  }
  .bleed {
    background: radial-gradient(ellipse 70vw 34vh at 50% 30%,
      rgba(var(--accent-rgb), .12) 0%,
      rgba(var(--accent-rgb), .05) 45%,
      rgba(var(--accent-rgb), 0) 100%);
  }
  .hero__hook { max-width: none; }
}

/* ---------- motion preferences ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .ticker__track { animation: none; }
}

/* ---------- print ----------
   Chrome prints with "Background graphics" off by default, so the ground never
   lands on the paper and cream ink would print at ~1.05:1 on white. Print
   re-points the palette tokens at ink-on-paper values — every colour still comes
   from a token — drops the decorative layers, and spells out external links. */
@media print {
  :root {
    --ground: #FFFFFF;
    --panel:  #FFFFFF;
    --ink:    #14100C;
    --dim:    #3A342C;
    --faint:  #4A4338;
    --accent: #6B4408;
    --ember:  #7A3418;
    --cool:   #2F3A46;
    --ground-rgb: 255, 255, 255;
    --panel-rgb:  255, 255, 255;
    --ink-rgb:    20, 16, 12;
    --accent-rgb: 107, 68, 8;
    --rule:        rgba(20, 16, 12, .28);
    --rule-strong: rgba(20, 16, 12, .45);
    --accent-rule: rgba(107, 68, 8, .45);
  }
  html, body { background: var(--ground); color: var(--ink); }
  .jupiter-stage, .ticker, .hero__cue, .grain, .bleed, .veil,
  .row__arrow, .next__arrow, .pill__dot { display: none !important; }
  .hero::before { display: none; }
  .nav {
    position: static; background: none; border-bottom: 1px solid var(--rule);
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  .hero { min-height: 0; padding-block: 0 24px; }
  .section { padding-block: 24px; }
  .footer { padding-block: 24px; }
  /* The stroked surname is invisible once the ground is paper. */
  .outline { color: var(--ink); -webkit-text-stroke: 0; }
  /* Filled buttons rely on a background that will not print: draw them as outlines. */
  .btn, .btn--primary, .pill, .chip, .row__cta {
    background: none; color: var(--ink); border-color: var(--rule-strong);
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  .pill, .chip { color: var(--faint); }
  .row__fig, .band, .portrait { background: none; }
  /* Reveals are opacity:0 until they scroll into view — print them all. */
  .reveal, .js .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .hero-in { animation: none; }
  .row, .feature, .timeline__item, .block, .skills__group, .course__item, .teaser { break-inside: avoid; }
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-family: var(--mono); font-size: .8em; word-break: break-all;
  }
}
