/* ============================================================
   Brendan Ng — portfolio
   Mood: pre-dawn instrument check. Deep indigo drench, one
   illuminated cobalt, one amber signal. Nothing decorative.
   All colour in OKLCH; every pairing contrast-verified.
   ============================================================ */

:root {
  /* palette — text verified: ink/bg 16.0, muted/bg 8.0, primary/bg 6.2,
     accent/bg 11.5, primary vs accent 1.85, bg-on-accent 11.5.
     Component boundaries (SC 1.4.11) verified separately — see --hairline-strong. */
  --bg:        oklch(0.210 0.048 252);
  --surface:   oklch(0.265 0.055 252);
  --surface-2: oklch(0.298 0.055 252);
  --ink:       oklch(0.965 0.012 250);
  --muted:     oklch(0.750 0.035 250);
  --primary:   oklch(0.680 0.150 247);
  --accent:    oklch(0.860 0.135  82);

  --hairline:  oklch(0.965 0.012 250 / 0.14);
  /* 0.45, not 0.28. Composited over --bg this measures 4.13:1; 0.28 measured
     2.37:1 and failed SC 1.4.11's 3:1 floor for UI component boundaries
     (it bounds .btn--ghost). Crossover is ~0.36. */
  --hairline-strong: oklch(0.965 0.012 250 / 0.45);

  /* fluid type — modular, ~1.3 ratio */
  --t--1: 0.875rem;
  --t-0:  1.0625rem;
  --t-1:  1.3rem;
  --t-2:  clamp(1.45rem, 1.15rem + 1.1vw, 1.85rem);
  --t-3:  clamp(1.85rem, 1.4rem + 1.9vw, 2.6rem);
  --t-4:  clamp(2.3rem, 1.6rem + 3vw, 3.4rem);
  --t-display: clamp(2.85rem, 1.6rem + 6vw, 5.5rem);

  /* space */
  --gutter: clamp(1.25rem, 0.6rem + 3vw, 4rem);
  --band-y: clamp(4rem, 2.5rem + 7vw, 9rem);
  --measure: 68ch;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);   /* ease-out-quint */

  --z-base: 0;
  --z-sticky: 100;
}

/* ---------- reset-ish ---------- */

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

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

/* clear the sticky bar on in-page jumps — without this, #now lands 1px under it */
section[id] { scroll-margin-top: 5.5rem; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: Archivo, ui-sans-serif, system-ui, sans-serif;
  font-size: var(--t-0);
  line-height: 1.68;          /* light-on-dark needs the extra room */
  font-variation-settings: 'wdth' 100, 'wght' 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3 { text-wrap: balance; margin: 0; }
p { text-wrap: pretty; margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

a { color: var(--primary); text-decoration-thickness: 1px; text-underline-offset: 0.22em; }
a:hover { color: oklch(0.78 0.13 247); }   /* brighten the link's own cobalt; amber stays the CTA color */

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

.skip {
  position: absolute; left: -9999px;
  background: var(--accent); color: var(--bg);
  padding: 0.7rem 1.1rem; font-weight: 600; z-index: var(--z-sticky);
}
.skip:focus { left: 1rem; top: 1rem; }

/* ---------- shell ---------- */

.topbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2.5rem);
  padding: 0.85rem var(--gutter);
  background: oklch(0.210 0.048 252 / 0.86);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--hairline);
}
.topbar__name {
  font-variation-settings: 'wdth' 90, 'wght' 700;
  letter-spacing: -0.01em;
}
.topbar__nav { display: flex; gap: clamp(0.9rem, 2vw, 1.75rem); margin-left: auto; }
.topbar__nav a {
  color: var(--muted); text-decoration: none; font-size: var(--t--1);
  padding-block: 0.35rem;   /* clears SC 2.5.8's 24px target floor; bare text was 23.5 */
  border-bottom: 2px solid transparent;   /* lit by scroll-spy below; always 2px so no layout shift */
  transition: color 0.2s var(--ease);
}
.topbar__nav a:hover { color: var(--ink); }
.topbar__cta {
  color: var(--accent); text-decoration: none; font-size: var(--t--1);
  font-weight: 600; padding: 0.6rem 0.95rem;   /* 0.6rem clears the 44px target */
  border: 1px solid var(--accent); border-radius: 999px;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.topbar__cta:hover { background: var(--accent); color: var(--bg); }

/* Mobile keeps the jump-nav. The name goes instead — it's redundant against the
   page below and the tab title, and nav is the thing a 5,700px page actually needs. */
@media (max-width: 40rem) {
  .topbar { gap: 0.9rem; padding-inline: 1rem; }
  .topbar__name { display: none; }
  .topbar__nav { margin-left: 0; gap: 0.85rem; }
  .topbar__nav a { font-size: 0.8125rem; }
  .topbar__cta { margin-left: auto; padding: 0.6rem 0.8rem; }
}

/* Scroll-spy: underline the nav link for the section currently on screen.
   Pure CSS via scroll-driven animations — the CSP blocks JS, and this is
   progressive enhancement anyway: browsers without animation-timeline (Firefox
   as of 2026) just keep the plain nav. No transform, so reduced-motion-safe.
   Range is the middle 60% of each section's pass so adjacent links rarely
   double-light at boundaries. */
@supports (animation-timeline: view()) {
  #spine    { view-timeline-name: --tl-spine; }
  #research { view-timeline-name: --tl-research; }
  #work     { view-timeline-name: --tl-work; }
  #now      { view-timeline-name: --tl-now; }
  body { timeline-scope: --tl-spine, --tl-research, --tl-work, --tl-now; }

  .topbar__nav a[href="#spine"]    { animation: navspy linear; animation-timeline: --tl-spine;    animation-range: cover 20% cover 80%; }
  .topbar__nav a[href="#research"] { animation: navspy linear; animation-timeline: --tl-research; animation-range: cover 20% cover 80%; }
  .topbar__nav a[href="#work"]     { animation: navspy linear; animation-timeline: --tl-work;     animation-range: cover 20% cover 80%; }
  .topbar__nav a[href="#now"]      { animation: navspy linear; animation-timeline: --tl-now;      animation-range: cover 20% cover 80%; }

  @keyframes navspy {
    from, to { color: var(--ink); border-bottom-color: var(--accent); }
  }
}

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

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 4rem);
  max-width: 1180px; margin-inline: auto;
  padding: clamp(3.5rem, 2rem + 7vw, 7.5rem) var(--gutter) clamp(3rem, 6vw, 6rem);
}

.hero__meta {
  color: var(--muted); font-size: var(--t--1);
  margin-bottom: 1.5rem;
}

.hero__h1 {
  font-size: var(--t-display);
  font-variation-settings: 'wdth' 87, 'wght' 720;
  letter-spacing: -0.035em;
  line-height: 0.98;
  margin-bottom: 1.6rem;
}

.hero__lede {
  color: var(--muted);
  font-size: var(--t-1);
  line-height: 1.6;
  max-width: 44ch;
  margin-bottom: 2.4rem;
}

.hero__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; }

/* The address in plain text: a mailto: is useless on a machine with no mail
   handler, and otherwise it isn't readable until ~5000px down the page. */
.hero__email { margin-top: 1.1rem; color: var(--muted); font-size: var(--t--1); }
.hero__email a { color: var(--muted); }
.hero__email a:hover { color: var(--primary); }

.btn {
  display: inline-block; text-decoration: none;
  padding: 0.8rem 1.5rem; border-radius: 3px;
  font-size: var(--t-0); font-weight: 600; letter-spacing: 0.005em;
  transition: transform 0.25s var(--ease), background 0.25s var(--ease),
              border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.btn:hover { transform: translateY(-2px); }
.btn--solid { background: var(--accent); color: var(--bg); border: 1px solid var(--accent); }
.btn--solid:hover { background: oklch(0.90 0.13 82); color: var(--bg); }
.btn--ghost { color: var(--ink); border: 1px solid var(--hairline-strong); }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }   /* a button is an action → amber, not the link color */

/* the instrument */

.hero__instrument { justify-self: center; width: 100%; max-width: 420px; }
.hero__instrument svg { width: 100%; height: auto; display: block; }

.dial__ring {
  stroke: var(--hairline); stroke-width: 1;
}
.dial__ring--inner { stroke: var(--hairline); }
.dial__sweep {
  stroke: var(--primary); stroke-width: 2.5; stroke-linecap: round;
  opacity: 0.9;
}
.dial__ticks line { stroke: var(--muted); stroke-width: 2; opacity: 0.65; }
.dial__needle {
  stroke: var(--accent); stroke-width: 3; stroke-linecap: round;
  transform-origin: 200px 200px;
}
.dial__hub { fill: var(--accent); }

/* The dial follows the copy on narrow screens. It was above it, which put an
   aria-hidden decoration in the top 395px and pushed both CTAs past an 812px
   fold — on the one device the audience actually arrives on. */
@media (max-width: 56rem) {
  .hero { grid-template-columns: 1fr; padding-top: clamp(2.5rem, 8vw, 4rem); }
  .hero__instrument { max-width: 200px; justify-self: start; opacity: 0.85; }
}

/* Phone chrome eats ~110px of an 812px screen, so the real fold is nearer 700.
   Tighten the vertical rhythm so both CTAs clear it rather than just clearing
   the nominal viewport height. */
@media (max-width: 30rem) {
  .hero { padding-top: 2rem; }
  .hero__meta { margin-bottom: 1rem; }
  .hero__h1 { margin-bottom: 1.15rem; }
  .hero__lede { margin-bottom: 1.6rem; font-size: var(--t-0); line-height: 1.55; }
  .hero__email { margin-top: 0.9rem; }
}

/* ---------- bands ---------- */

.band { padding: var(--band-y) var(--gutter); }
.band > * { max-width: 1180px; margin-inline: auto; }
.band--raised { background: var(--surface); }

.band__h2 {
  font-size: var(--t-3);
  font-variation-settings: 'wdth' 91, 'wght' 660;
  letter-spacing: -0.022em;
  line-height: 1.1;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--hairline-strong);
  margin-bottom: 2.4rem;
}

.band__intro, .band__note {
  color: var(--muted); max-width: var(--measure);
}
.band__intro { margin-bottom: 2.6rem; }
.band__note { margin-top: 2.6rem; font-size: var(--t--1); }

/* ---------- the four findings ---------- */

.finding {
  display: grid;
  grid-template-columns: minmax(9rem, 15rem) 1fr;
  gap: clamp(0.6rem, 3vw, 2.5rem);
  align-items: baseline;
  padding: 1.35rem 0;
  border-bottom: 1px solid var(--hairline);
}
.finding:first-child { border-top: 1px solid var(--hairline); }

.finding__domain {
  color: var(--primary);
  font-variation-settings: 'wdth' 92, 'wght' 620;
  font-size: var(--t-0);
}
.finding__claim { font-size: var(--t-1); line-height: 1.45; }

/* Stacked, the two spans were both 17px — the page's central argument rendered
   at body weight. Shrink the label instead of the claim so contrast survives. */
@media (max-width: 44rem) {
  .finding { grid-template-columns: 1fr; gap: 0.35rem; padding: 1.5rem 0; }
  .finding__domain { font-size: var(--t--1); }
}

/* ---------- research ---------- */

.paper__title {
  font-size: var(--t-2);
  font-variation-settings: 'wdth' 95, 'wght' 620;
  letter-spacing: -0.015em;
  line-height: 1.22;
  max-width: 26ch;
  margin-bottom: 0.7rem;
}
.paper__venue { color: var(--muted); font-size: var(--t--1); margin-bottom: 1.5rem; }
.paper__body { max-width: var(--measure); margin-bottom: 2.4rem; }

.readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1px;
  background: var(--hairline-strong);
  border: 1px solid var(--hairline-strong);
  margin: 0;
  max-width: 46rem;   /* unbounded, three cells stretched to 388px each */
}
/* --surface-2 exists for exactly this. It was declared and never referenced, so
   the cells matched their own band and the "panel" had no panel. */
.readout__cell { background: var(--surface-2); padding: 1.3rem 1.4rem; }
.readout dt {
  color: var(--muted); font-size: var(--t--1);
  margin-bottom: 0.35rem; line-height: 1.35;
}
.readout dd {
  margin: 0; color: var(--accent);
  font-size: var(--t-3);
  font-variation-settings: 'wdth' 88, 'wght' 680;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em; line-height: 1;
}

/* ---------- work entries ---------- */

/* Work and Now share one row: a hanging figure in the left column, on the same
   two-column rhythm as .finding. Previously these were five near-identical
   prose rows running for 41% of the page with no visual event in any of them. */
.entry, .role {
  display: grid;
  grid-template-columns: minmax(8rem, 11rem) minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.75rem);
  padding: 2.4rem 0;
  border-top: 1px solid var(--hairline);
}
.entry:last-of-type, .role:last-of-type { border-bottom: 1px solid var(--hairline); }

.entry__figure { display: flex; flex-direction: column; gap: 0.45rem; margin: 0; }
.entry__num {
  color: var(--accent);
  font-size: var(--t-3);
  font-variation-settings: 'wdth' 88, 'wght' 680;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.025em;
  line-height: 0.95;
}
.entry__unit { font-size: 0.55em; font-variation-settings: 'wdth' 92, 'wght' 600; }
.entry__label { color: var(--muted); font-size: var(--t--1); line-height: 1.4; }

.entry__body { min-width: 0; }

.entry__title {
  font-size: var(--t-2);
  font-variation-settings: 'wdth' 92, 'wght' 640;
  letter-spacing: -0.018em;
  margin-bottom: 0.9rem;
}
.entry__title a { text-decoration: none; }
.entry__title a:hover { text-decoration: underline; }

.entry__what { color: var(--muted); max-width: var(--measure); margin-bottom: 1.1rem; }
.entry__what strong { color: var(--ink); font-weight: 620; }
.entry__stack { color: var(--muted); font-size: var(--t--1); }

.role__title {
  font-size: var(--t-2);
  font-variation-settings: 'wdth' 92, 'wght' 640;
  letter-spacing: -0.018em;
  margin-bottom: 0.35rem;
}
.role__meta { color: var(--muted); font-size: var(--t--1); margin-bottom: 1rem; }
.role__body { color: var(--muted); max-width: var(--measure); }

/* Stacked, the figure goes horizontal so it costs one line instead of three. */
@media (max-width: 44rem) {
  .entry, .role { grid-template-columns: 1fr; gap: 0.85rem; padding: 2rem 0; }
  .entry__figure { flex-direction: row; align-items: baseline; flex-wrap: wrap; gap: 0.65rem; }
  .entry__num { font-size: var(--t-2); }
  .entry__label { flex: 1 1 12rem; }
}

/* ---------- closing ---------- */

.closing {
  padding: clamp(4.5rem, 3rem + 7vw, 9rem) var(--gutter);
  max-width: 1180px; margin-inline: auto;
}
.closing__h2 {
  font-size: var(--t-4);
  font-variation-settings: 'wdth' 88, 'wght' 700;
  letter-spacing: -0.03em; line-height: 1.06;
  max-width: 18ch; margin-bottom: 1.4rem;
}
.closing__body { color: var(--muted); max-width: var(--measure); margin-bottom: 2.4rem; }
.closing__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; }

.foot {
  padding: 2rem var(--gutter) 3rem;
  border-top: 1px solid var(--hairline);
  color: var(--muted); font-size: var(--t--1);
}
.foot p { max-width: 1180px; margin-inline: auto; }

/* ============================================================
   Motion — one orchestrated page load, nothing on scroll.
   Content is visible by default; these enhance, never gate.
   ============================================================ */

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
@keyframes draw   { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: 0; } }
@keyframes swing  { from { transform: rotate(-38deg); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes appear { from { opacity: 0; } to { opacity: 1; } }

.hero__meta    { animation: rise 0.7s var(--ease) both; }
.hero__h1      { animation: rise 0.8s var(--ease) 0.06s both; }
.hero__lede    { animation: rise 0.8s var(--ease) 0.14s both; }
.hero__actions { animation: rise 0.8s var(--ease) 0.22s both; }

.dial__ring       { animation: appear 1s var(--ease) 0.2s both; }
.dial__ticks      { animation: appear 1s var(--ease) 0.5s both; }
/* 735, not 880 — getTotalLength() on this arc is 735.2. At 880 the stroke
   finished in the first third of the duration and the rest was dead time. */
.dial__sweep      { --len: 735; stroke-dasharray: 735; animation: draw 1.4s var(--ease) 0.25s both; }
.dial__needle     { animation: swing 1s var(--ease) 0.75s both; }
.dial__hub        { animation: appear 0.6s var(--ease) 0.9s both; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
  }
  .dial__sweep { stroke-dasharray: none; }
  .btn:hover { transform: none; }
}
