/* =========================================================
   Ahmed Anbar - Portfolio
   "Signal": Ektachrome dusk over a Riyadh skyline.

   COLOR STRATEGY: Full palette, 4 named roles, warm-dominant with
   one cool counterweight. Deliberately NOT neon-on-black (see
   PRODUCT.md anti-references) and NOT monochrome amber (the old site).

     gold      amber sodium-vapour   hero, contact
     ember     terracotta stone      selected work
     plum      dusk shadow           experience
     verdigris the one signal light  skills

   Each section owns exactly ONE dominant role via --accent, so colour
   never drifts arbitrarily. The skills bento is the single deliberate
   exception: its cards cycle all four roles as a "spectrum of the stack".

   THEME: dark only. The scenes emit light in a dark room (additive
   blending). A light "ink on paper" mode existed and was removed on
   request; the WebGL layer still carries the machinery for it, so
   re-enabling is a small change rather than a rewrite.
   Every value below is verified for WCAG AA (body >=4.5:1,
   large/accent >=3:1, interactive borders >=3:1) and clamped inside
   the sRGB gamut.
   ========================================================= */

:root {
  color-scheme: dark;

  /* ---- Ground ---- */
  --bg:        oklch(0.145 0.014 62);
  --bg-2:      oklch(0.185 0.018 60);
  --surface:   oklch(0.225 0.022 58);
  /* --line is decorative hairlines only. Anything interactive uses
     --line-strong, which clears the 3:1 WCAG 1.4.11 floor. */
  --line:        oklch(0.34 0.022 60);
  --line-strong: oklch(0.48 0.022 60);

  /* ---- Ink ---- */
  --ink:      oklch(0.96 0.010 82);   /* 17.6:1 on bg */
  --ink-soft: oklch(0.84 0.016 78);   /* 12.1:1 */
  --muted:    oklch(0.76 0.020 74);   /*  9.6:1 on bg; 4.7:1 worst case over a lit scene */

  /* ---- The four roles ---- */
  --gold:      oklch(0.83 0.145 78);
  --ember:     oklch(0.72 0.170 45);
  --plum:      oklch(0.68 0.185 355);
  --verdigris: oklch(0.78 0.115 190);

  /* Text that sits ON an accent fill */
  --on-accent: oklch(0.18 0.020 60);

  /* How much of the WebGL layer each kind of section lets through. Sections
     reference these tokens rather than hardcoding numbers, so the balance
     between scene and copy is tuned in one place. */
  --veil-body: 76%;
  --veil-contact: 70%;
  --veil-wall: 88%;
  --veil: var(--veil-body);

  --accent: var(--gold);

  /* ---- Shape: one system. interactive = pill, containers = md, chips = sm ---- */
  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-pill: 100px;

  /* ---- Semantic z-scale. No arbitrary values anywhere. ---- */
  --z-gl: 0;
  --z-veil: 1;
  --z-content: 2;
  --z-nav: 50;
  --z-grain: 80;
  --z-preloader: 100;

  --maxw: 1240px;
  --gutter: clamp(1.25rem, 4vw, 4rem);

  /* Modular scale, ratio >=1.25 between steps */
  --step--1: clamp(0.82rem, 0.78rem + 0.2vw, 0.94rem);
  --step-0:  clamp(1rem, 0.95rem + 0.25vw, 1.15rem);
  --step-1:  clamp(1.25rem, 1.08rem + 0.75vw, 1.65rem);
  --step-2:  clamp(1.65rem, 1.3rem + 1.5vw, 2.7rem);
  --step-3:  clamp(2.2rem, 1.6rem + 3vw, 4rem);
  --step-4:  clamp(2.8rem, 1.9rem + 4.6vw, 5.4rem);  /* ceiling under 6rem */

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  --font-display: 'Clash Display', system-ui, sans-serif;
  --font-body: 'General Sans', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;
}

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

html {
  -webkit-text-size-adjust: 100%;
  /* Deliberately NO scroll-behavior: smooth.

     This page uses a JS smooth-scroll library, which interpolates by writing
     scrollTop every frame. Native smooth scrolling then animates toward each of
     those writes, so the two interpolators fight and the page stalls: measured
     here as 28 stalled frames out of 30 and roughly a 500ms dead zone before the
     wheel did anything. Anchor links are already animated in js/main.js, through
     the library when it is present and scrollIntoView({behavior:'smooth'}) when
     it is not, so this declaration bought nothing and cost the whole scroll. */
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  transition: background 0.5s var(--ease), color 0.5s var(--ease);
}
body.is-loading { overflow: hidden; }

img, video, canvas { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.03em;   /* floor is -0.04em */
  text-wrap: balance;
  margin: 0;
}
p { text-wrap: pretty; }

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

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* =========================================================
   Fixed page layers
   ========================================================= */

/* One shared WebGL context for every scene on the page. Sections opt in
   with data-gl and the engine renders each into its own scissor rect. */
.glstage {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: var(--z-gl);
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}
.gl-ready .glstage { opacity: 1; }

/* Static stand-in for the WebGL layer. Present by default and faded out once the
   canvas is live, so it covers all three cases with one rule: no WebGL at all,
   the canvas still starting up, and the engine giving up on a machine that cannot
   hold the frame rate. Pure gradients, so it costs nothing per frame. */
.hero::before,
.contact::before {
  content: '';
  position: absolute; inset: 0; z-index: var(--z-gl);
  pointer-events: none;
  opacity: 1;
  transition: opacity 1.2s var(--ease);
  background:
    radial-gradient(38% 44% at 72% 34%, color-mix(in oklch, var(--gold) 20%, transparent), transparent 70%),
    radial-gradient(60% 64% at 82% 22%, color-mix(in oklch, var(--ember) 13%, transparent), transparent 72%);
}
.contact::before {
  background:
    radial-gradient(46% 50% at 26% 46%, color-mix(in oklch, var(--gold) 17%, transparent), transparent 72%),
    radial-gradient(64% 60% at 20% 40%, color-mix(in oklch, var(--plum) 11%, transparent), transparent 76%);
}
.gl-ready .hero::before,
.gl-ready .contact::before { opacity: 0; }

/* Grain lives on ONE fixed layer. Putting it on scrolling containers
   forces continuous GPU repaints and wrecks mobile frame rate. */
.grain {
  position: fixed; inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  /* No mix-blend-mode. On a fixed full-viewport layer it forces the compositor to
     flatten everything beneath (including the animating canvas and the smooth-scroll
     transform) and blend it every frame, which is what made scrolling heavy. */
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* =========================================================
   Preloader
   ========================================================= */
.preloader {
  position: fixed; inset: 0; z-index: var(--z-preloader);
  background: var(--bg);
  display: grid; place-items: center;
}
.preloader__inner {
  width: min(90vw, var(--maxw));
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 0 var(--gutter);
}
.preloader__name {
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--step-1); letter-spacing: 0.01em; color: var(--ink);
}
.preloader__count {
  font-family: var(--font-mono); font-size: var(--step-3);
  color: var(--gold); font-variant-numeric: tabular-nums;
}

/* =========================================================
   Nav: one line, 72px tall
   ========================================================= */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: var(--z-nav);
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  height: 72px;
  padding-inline: var(--gutter);
  transition: background 0.4s var(--ease), backdrop-filter 0.4s var(--ease),
              border-color 0.4s var(--ease), height 0.4s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  height: 62px;
  background: color-mix(in oklch, var(--bg) 78%, transparent);
  backdrop-filter: blur(16px) saturate(1.3);
  border-bottom-color: color-mix(in oklch, var(--line) 60%, transparent);
}
.nav__brand { display: flex; align-items: center; gap: 0.7rem; flex-shrink: 0; }
.nav__mark {
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  display: grid; place-items: center; width: 2.15rem; height: 2.15rem;
  border: 1.5px solid var(--gold); border-radius: 50%; color: var(--gold);
}
.nav__brandtext { font-family: var(--font-display); font-weight: 500; font-size: 1rem; }
.nav__links { display: flex; align-items: center; gap: clamp(0.9rem, 2vw, 2rem); }
.nav__links a {
  font-size: var(--step--1); color: var(--ink-soft); position: relative;
  white-space: nowrap;
  transition: color 0.25s var(--ease);
}
.nav__links a:not(.nav__cta)::after {
  content: ''; position: absolute; left: 0; bottom: -4px; height: 1.5px; width: 0;
  background: var(--gold); transition: width 0.3s var(--ease);
}
.nav__links a:not(.nav__cta):hover { color: var(--ink); }
.nav__links a:not(.nav__cta):hover::after { width: 100%; }
.nav__cta {
  border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  padding: 0.5rem 1.1rem; color: var(--ink);
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease),
              color 0.25s var(--ease), transform 0.2s var(--ease);
}
.nav__cta:hover { background: var(--gold); border-color: var(--gold); color: var(--on-accent); }
.nav__cta:active { transform: scale(0.97); }

/* Theme switch: a real two-state control, labelled for screen readers */
.themeswitch {
  display: inline-flex; align-items: center; gap: 0.15rem;
  padding: 3px; border: 1px solid var(--line-strong);
  border-radius: var(--r-pill); background: color-mix(in oklch, var(--bg) 60%, transparent);
  flex-shrink: 0;
}
.themeswitch__btn {
  display: grid; place-items: center;
  width: 1.85rem; height: 1.85rem; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: transparent; color: var(--muted);
  transition: background 0.25s var(--ease), color 0.25s var(--ease);
}
.themeswitch__btn svg { width: 1rem; height: 1rem; fill: currentColor; }
.themeswitch__btn:hover { color: var(--ink); }
.themeswitch__btn[aria-pressed='true'] {
  background: var(--gold); color: var(--on-accent);
}

@media (max-width: 720px) {
  .nav__brandtext { display: none; }
  .nav__links a:not(.nav__cta) { display: none; }
}

/* =========================================================
   Section primitives
   ========================================================= */
section { position: relative; }

/* Veil sits between the WebGL layer and the content. Sections that opt into
   GL get one so verified text contrast survives a glow behind it. */
.veil {
  position: absolute; inset: 0; z-index: var(--z-veil);
  background: color-mix(in oklch, var(--bg) var(--veil), transparent);
  pointer-events: none;
}
.shell {
  position: relative; z-index: var(--z-content);
  max-width: var(--maxw); margin-inline: auto;
  padding-inline: var(--gutter);
}
.section__title { font-size: var(--step-3); }
.section__lead {
  color: var(--ink-soft); font-size: var(--step-1);
  max-width: 62ch; margin: 1.2rem 0 0;
}

/* =========================================================
   Hero
   ========================================================= */
.hero {
  --accent: var(--gold);
  --veil: 0%;
  position: relative; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: var(--gutter);
  padding-top: min(6rem, 12vh);
  overflow: hidden;
}
/* Directional scrim rather than a flat veil: the mesh stays vivid on the
   right while the headline sits on near-solid ground. */
.hero__scrim {
  position: absolute; inset: 0; z-index: var(--z-veil); pointer-events: none;
  background:
    linear-gradient(to top, var(--bg) 1%, transparent 46%),
    linear-gradient(to right,
      color-mix(in oklch, var(--bg) 92%, transparent),
      color-mix(in oklch, var(--bg) 64%, transparent) 34%,
      color-mix(in oklch, var(--bg) 22%, transparent) 54%,
      transparent 68%);
}
.hero__content {
  position: relative; z-index: var(--z-content);
  max-width: 54rem;
  padding-bottom: clamp(2rem, 8vh, 5rem);
}
.hero__eyebrow {
  font-family: var(--font-mono); font-size: var(--step--1);
  color: var(--gold); margin: 0 0 1.3rem;
}
.hero__title {
  font-size: var(--step-4); font-weight: 600; letter-spacing: -0.038em;
  line-height: 0.94; margin: 0 0 1.5rem;
}
.hero__title .line { display: block; overflow: hidden; padding-bottom: 0.06em; }
.hero__title .word { display: inline-block; will-change: transform; }
.hero__title .word--accent { color: var(--gold); }
.hero__lede {
  font-size: var(--step-1); color: var(--ink-soft);
  max-width: 44ch; line-height: 1.5; margin: 0 0 2.2rem;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; }

.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-body); font-weight: 500; font-size: var(--step-0);
  padding: 0.85rem 1.6rem; border-radius: var(--r-pill); cursor: pointer;
  white-space: nowrap;
  transition: transform 0.2s var(--ease), background 0.25s var(--ease),
              border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.btn--solid {
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent);
}
.btn--solid:hover { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.btn--ghost { border: 1px solid var(--line-strong); color: var(--ink); background: transparent; }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn:active { transform: scale(0.975); }

/* =========================================================
   Stack wall: real marks, no labels. Lives under the hero,
   never inside it.
   ========================================================= */
.stackwall {
  --veil: var(--veil-wall);
  border-block: 1px solid var(--line);
  padding-block: clamp(2rem, 5vw, 3rem);
  overflow: hidden;
}
.stackwall__row {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between;
  gap: clamp(1.5rem, 4vw, 3rem);
}
.stackwall__mark {
  width: clamp(1.6rem, 2.4vw, 2.1rem); height: auto;
  fill: var(--ink-soft);
  opacity: 0.62;
  transition: opacity 0.3s var(--ease), fill 0.3s var(--ease), transform 0.3s var(--ease);
}
.stackwall__item { display: block; line-height: 0; }
.stackwall__item:hover .stackwall__mark,
.stackwall__item:focus-visible .stackwall__mark {
  opacity: 1; fill: var(--gold); transform: translateY(-3px);
}

/* =========================================================
   About: the systems video stays exactly as it was
   ========================================================= */
.about {
  --accent: var(--gold);
  padding-block: clamp(4.5rem, 12vw, 9rem);
  background: var(--bg);
}
.about__grid {
  display: grid; grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2rem, 5vw, 5rem); align-items: start;
}
.about__photo {
  position: relative; margin: 0; border-radius: var(--r-md); overflow: hidden;
  border: 1px solid var(--line);
}
.about__photo video {
  width: 100%; height: 100%; object-fit: cover;
  aspect-ratio: 4/5; filter: saturate(1.05) contrast(1.02);
}
.about__tag {
  position: absolute; left: 0; bottom: 0; right: 0;
  font-family: var(--font-mono); font-size: var(--step--1); color: oklch(0.96 0.01 82);
  padding: 1.4rem 1.2rem 1rem;
  background: linear-gradient(to top, oklch(0.145 0.014 62 / 0.92), transparent);
}
.about__body .section__title { margin-bottom: 1.6rem; }
.about__p { color: var(--ink-soft); max-width: 62ch; margin: 0 0 1.3rem; }
.about__meta {
  display: flex; flex-wrap: wrap; gap: clamp(1.5rem, 4vw, 3rem); margin-top: 2.4rem;
}
.about__stat { display: flex; flex-direction: column; }
.about__num {
  font-family: var(--font-display); font-weight: 600; font-size: var(--step-2);
  color: var(--accent); line-height: 1; font-variant-numeric: tabular-nums;
}
.about__lbl {
  font-family: var(--font-mono); font-size: var(--step--1);
  color: var(--muted); margin-top: 0.5rem;
}
@media (max-width: 820px) {
  .about__grid { grid-template-columns: 1fr; }
  .about__photo { max-width: 380px; }
}

/* =========================================================
   Statement: the window video stays exactly as it was
   ========================================================= */
.statement {
  position: relative; min-height: 78svh; display: grid; place-items: center;
  overflow: hidden; padding: 6rem var(--gutter);
}
.statement__media { position: absolute; inset: 0; z-index: var(--z-gl); }
.statement__video { width: 100%; height: 100%; object-fit: cover; }
.statement__scrim {
  position: absolute; inset: 0;
  background:
    radial-gradient(62% 58% at 50% 50%, oklch(0.145 0.014 62 / 0.62), transparent 78%),
    oklch(0.145 0.014 62 / 0.52);
}
.statement__quote {
  position: relative; z-index: var(--z-content);
  margin: 0; max-width: 24ch; text-align: center;
  font-family: var(--font-display); font-weight: 500;
  font-size: var(--step-3); line-height: 1.12; letter-spacing: -0.02em;
  color: oklch(0.97 0.01 82);
  text-shadow: 0 2px 30px oklch(0.145 0.014 62 / 0.88), 0 1px 6px oklch(0.145 0.014 62 / 0.7);
}
.statement__quote p { margin: 0; overflow: hidden; padding-bottom: 0.08em; }
.statement__quote p > span { display: inline-block; will-change: transform; }
.statement__quote em { color: var(--gold); font-style: italic; }

/* =========================================================
   Marquee: the only one on the page
   ========================================================= */
.marquee {
  border-block: 1px solid var(--line); overflow: hidden;
  padding: 1.1rem 0; background: var(--bg-2);
}
.marquee__track {
  display: inline-flex; gap: 1.6rem; white-space: nowrap; will-change: transform;
  font-family: var(--font-display); font-weight: 500; font-size: var(--step-1);
  color: var(--ink-soft);
}
.marquee__track span:nth-child(4n+2) { color: var(--gold); }
.marquee__track span:nth-child(4n) { color: var(--verdigris); }

/* =========================================================
   Skills: bento, 9 items in 9 cells, no empties.
   Cards cycle the four palette roles: the one section where the
   full palette is the point.
   ========================================================= */
.skills {
  --accent: var(--verdigris);
  --veil: var(--veil-body);
  padding-block: clamp(4.5rem, 12vw, 9rem);
  overflow: hidden;
}
.skills__grid {
  display: grid; gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  margin-top: clamp(2.5rem, 6vw, 4rem);
}
.skillcard {
  /* Deliberately no backdrop-filter. Its backdrop is the WebGL layer, which
     repaints every frame, so the blur can never be cached and 9 cards meant 9
     uncacheable full-region blurs per frame. An opaque tint reads the same. */
  background: color-mix(in oklch, var(--bg) 95%, transparent);
  padding: clamp(1.5rem, 3vw, 2.3rem);
  transition: background 0.4s var(--ease);
}
.skillcard:hover { background: color-mix(in oklch, var(--bg-2) 97%, transparent); }
.skillcard h3 {
  font-size: var(--step-1); margin-bottom: 1.1rem; color: var(--ink);
  display: flex; align-items: baseline; gap: 0.55rem;
}
.skillcard h3::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--card-accent, var(--accent)); flex-shrink: 0;
}
.skillcard--feature h3 { font-size: var(--step-2); }
.skillcard--feature { background: color-mix(in oklch, var(--card-accent) 8%, var(--bg) 90%); }

/* Systematic hue rotation, not random: backend/data/testing carry gold,
   frontend/devops/observability verdigris, architecture/integrations ember,
   security plum. */
.skillcard:nth-child(1) { --card-accent: var(--gold); }
.skillcard:nth-child(2) { --card-accent: var(--verdigris); }
.skillcard:nth-child(3) { --card-accent: var(--gold); }
.skillcard:nth-child(4) { --card-accent: var(--ember); }
.skillcard:nth-child(5) { --card-accent: var(--verdigris); }
.skillcard:nth-child(6) { --card-accent: var(--plum); }
.skillcard:nth-child(7) { --card-accent: var(--gold); }
.skillcard:nth-child(8) { --card-accent: var(--verdigris); }
.skillcard:nth-child(9) { --card-accent: var(--ember); }

@media (min-width: 900px) {
  .skills__grid { grid-template-columns: repeat(12, 1fr); }
  /* 5+4+3 | 4+5+3 | 4+3+5 = three full rows, nine cells */
  .skillcard:nth-child(1) { grid-column: span 5; }
  .skillcard:nth-child(2) { grid-column: span 4; }
  .skillcard:nth-child(3) { grid-column: span 3; }
  .skillcard:nth-child(4) { grid-column: span 4; }
  .skillcard:nth-child(5) { grid-column: span 5; }
  .skillcard:nth-child(6) { grid-column: span 3; }
  .skillcard:nth-child(7) { grid-column: span 4; }
  .skillcard:nth-child(8) { grid-column: span 3; }
  .skillcard:nth-child(9) { grid-column: span 5; }
}
@media (max-width: 899px) {
  .skills__grid { grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); }
}

.tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.45rem; }
.tags li {
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: 0.32rem 0.65rem;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.tags li:hover { border-color: var(--card-accent, var(--accent)); color: var(--card-accent, var(--accent)); }

/* =========================================================
   Experience: timeline. Dates are a real sequence, so they earn
   their position as the leading column.
   ========================================================= */
.experience {
  --accent: var(--plum);
  --veil: var(--veil-body);
  padding-block: clamp(4.5rem, 12vw, 9rem);
  overflow: hidden;
}
.timeline { list-style: none; margin: clamp(2.5rem, 6vw, 4rem) 0 0; padding: 0; }
.tl {
  display: grid; grid-template-columns: 0.42fr 1fr; gap: clamp(1rem, 4vw, 3rem);
  padding-block: clamp(1.6rem, 3vw, 2.4rem);
  border-top: 1px solid var(--line);
  position: relative;
}
.tl:last-child { border-bottom: 1px solid var(--line); }
.tl::after {
  content: ''; position: absolute; left: 0; top: -1px; height: 1px; width: 0;
  background: var(--accent); transition: width 0.6s var(--ease);
}
.tl:hover::after { width: 100%; }
.tl__when { display: flex; flex-direction: column; gap: 0.35rem; }
.tl__dates {
  font-family: var(--font-mono); font-size: var(--step-0); color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.tl__place { font-size: var(--step--1); color: var(--muted); }
.tl__what h3 { font-size: var(--step-1); margin-bottom: 0.6rem; }
.tl__what p { color: var(--ink-soft); margin: 0; max-width: 64ch; }
@media (max-width: 700px) {
  .tl { grid-template-columns: 1fr; gap: 0.7rem; }
}

/* =========================================================
   Selected work: three featured, then five compact.
   Deliberately not eight identical cards.
   ========================================================= */
.work {
  --accent: var(--ember);
  --veil: var(--veil-body);
  padding-block: clamp(4.5rem, 12vw, 9rem);
  overflow: hidden;
}
.work__featured {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(285px, 1fr));
  gap: 1.1rem;
  margin-top: clamp(2.5rem, 6vw, 4rem);
}
.pcard {
  position: relative;
  display: flex; flex-direction: column;
  padding: clamp(1.5rem, 3vw, 2.2rem);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: color-mix(in oklch, var(--bg) 94%, transparent);
  overflow: hidden;
  transition: border-color 0.35s var(--ease), transform 0.35s var(--ease);
}
.pcard::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(90% 70% at 82% 4%,
    color-mix(in oklch, var(--accent) 16%, transparent), transparent 62%);
  opacity: 0; transition: opacity 0.45s var(--ease);
}
.pcard:hover { border-color: color-mix(in oklch, var(--accent) 55%, var(--line)); transform: translateY(-4px); }
.pcard:hover::before { opacity: 1; }
.pcard__name { font-size: var(--step-2); margin-bottom: 0.45rem; position: relative; }
.pcard__role {
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--accent);
  margin: 0 0 0.9rem; position: relative;
}
.pcard__desc { color: var(--ink-soft); margin: 0 0 1.4rem; font-size: var(--step-0); position: relative; }
.pcard__stack {
  list-style: none; margin: auto 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.4rem; position: relative;
}
.pcard__stack li {
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: 0.28rem 0.6rem;
}

.work__rest { list-style: none; margin: 1.1rem 0 0; padding: 0; }
.prow {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: 1rem clamp(1rem, 3vw, 2.5rem);
  padding-block: clamp(1.1rem, 2.2vw, 1.6rem);
  border-top: 1px solid var(--line);
  transition: padding-inline 0.35s var(--ease);
}
.prow:last-child { border-bottom: 1px solid var(--line); }
.prow:hover { padding-inline: 0.6rem; }
.prow__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 0.9rem; }
.prow__name { font-family: var(--font-display); font-weight: 500; font-size: var(--step-1); }
.prow__role { font-family: var(--font-mono); font-size: var(--step--1); color: var(--accent); }
.prow__desc {
  grid-column: 1 / -1; color: var(--ink-soft); margin: 0.5rem 0 0;
  max-width: 68ch; font-size: var(--step-0);
}
.prow__stack {
  list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.4rem;
}
.prow__stack li {
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: 0.26rem 0.58rem;
}
@media (max-width: 620px) {
  .prow { grid-template-columns: 1fr; }
}

/* =========================================================
   Contact
   ========================================================= */
.contact {
  --accent: var(--gold);
  --veil: var(--veil-contact);
  position: relative; overflow: hidden;
  min-height: 100svh; display: flex; flex-direction: column; justify-content: center;
}
.contact__inner { padding-block: clamp(4rem, 10vw, 7rem); width: 100%; }
.contact__title { font-size: var(--step-3); margin-bottom: 2rem; max-width: 30ch; }
.contact__email {
  display: inline-block; font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.5rem, 1rem + 3.2vw, 3.2rem); color: var(--accent);
  letter-spacing: -0.02em; position: relative; margin-bottom: 1.6rem;
}
.contact__email::after {
  content: ''; position: absolute; left: 0; bottom: 2px; height: 2px; width: 100%;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform 0.45s var(--ease);
}
.contact__email:hover::after { transform: scaleX(1); }
.contact__row {
  display: flex; flex-wrap: wrap; gap: 0.5rem clamp(1.2rem, 3vw, 2.4rem);
  font-family: var(--font-mono); font-size: var(--step-0);
  color: var(--ink-soft); margin-bottom: 2rem;
}
.contact__row > span { display: inline-flex; align-items: baseline; gap: 0.5rem; }
.contact__row small { color: var(--muted); font-size: var(--step--1); }
.contact__row a { transition: color 0.2s var(--ease); }
.contact__row a:hover { color: var(--accent); }
.contact__links { list-style: none; display: flex; flex-wrap: wrap; gap: 0.7rem; margin: 0; padding: 0; }
.contact__links a {
  display: inline-block; font-size: var(--step-0);
  border: 1px solid var(--line-strong); border-radius: var(--r-pill); padding: 0.6rem 1.4rem;
  transition: background 0.25s var(--ease), color 0.25s var(--ease),
              border-color 0.25s var(--ease), transform 0.2s var(--ease);
}
.contact__links a:hover {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}
.contact__links a:active { transform: scale(0.97); }

.footer {
  position: relative; z-index: var(--z-content);
  max-width: var(--maxw); margin-inline: auto; width: 100%;
  padding: 2rem var(--gutter) 2.4rem; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; align-items: center;
  font-family: var(--font-mono); font-size: var(--step--1); color: var(--muted);
}
.footer__top { color: var(--ink-soft); transition: color 0.2s var(--ease); }
.footer__top:hover { color: var(--accent); }

/* =========================================================
   Reveals. Content is visible by default; the .js class opts in,
   so a headless render or a failed script never ships a blank page.
   ========================================================= */
/* No blanket will-change here. It was promoting 48 elements to their own
   compositor layers permanently, 42 of them one-shot reveals that never animate
   again. GSAP promotes an element for the duration of its tween by itself; a
   standing hint just makes the compositor carry the layer for the whole session. */
.js .reveal-up { opacity: 0; transform: translateY(26px); }
.js .reveal-line > span { opacity: 0; }

/* =========================================================
   Reduced motion: every animation has a still alternative
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js .reveal-up, .js .reveal-line > span { opacity: 1 !important; transform: none !important; }
  .glstage { transition: none; }
}
