/* ============================================================================
   SITE.CSS — Harsh Rastogi
   Structure: base -> chrome (grain, curtain, cursor) -> nav -> hero ->
   marquee -> work -> approach -> offer -> writing -> footer -> reading view
   -> motion. Tokens live in tokens.css; nothing here hard-codes a colour.
============================================================================ */

/* ============================================================================
   1. BASE
============================================================================ */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

html { background: var(--background); }
body {
  background: var(--background);
  color: var(--foreground);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--weight-regular);
  line-height: var(--leading-relaxed);
  letter-spacing: var(--tracking-wide);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  min-height: 100vh;
}

img, svg, canvas, video { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
a { color: inherit; text-decoration: none; }
::selection { background: var(--accent); color: var(--ink-1); }

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

.skip-link {
  position: fixed; top: -100px; left: var(--space-4); z-index: var(--z-loading);
  background: var(--accent); color: var(--ink-1);
  padding: var(--space-3) var(--space-5); border-radius: var(--radius-full);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  transition: top var(--duration-base) var(--ease-out-expo);
}
.skip-link:focus { top: var(--space-4); }

.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}

.section { padding-block: var(--space-section); position: relative; }
.section--flush { padding-block-start: 0; }

/* ---- shared type primitives ---------------------------------------- */
.eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--text-9);
  line-height: var(--leading-none);
}
.eyebrow--accent { color: var(--accent); }

.section-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--text-9);
  margin-bottom: var(--space-10);
}

.display {
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-tighter);
  line-height: var(--leading-display);
  color: var(--text-12);
  text-wrap: balance;
}
.h2 { font-size: var(--text-4xl); }
.h3 { font-size: var(--text-3xl); }

.lede {
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
  letter-spacing: var(--tracking-snug);
  color: var(--text-10);
  max-width: var(--measure-lg);
}
@media (max-width: 640px) { .lede { font-size: var(--text-lg); } }

.tag-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-1) var(--space-1);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-9);
}
.tag-row li { list-style: none; display: flex; align-items: center; gap: var(--space-1); }
.tag-row li + li::before { content: "—"; color: inherit; }

/* ============================================================================
   2. CHROME — grain, page curtain, custom cursor
============================================================================ */
/* The grain sits ON TOP of the page, not behind it.

   Behind it (z-index -1) the texture only showed through wherever a section
   happened to be transparent, so every opaque surface — the stacked cards,
   the sticky header behind them, the article cards — punched a flat black
   hole in it. Film grain is a property of the whole image, so it belongs in
   front of everything, blended and faint. It stays under the nav and the
   cursor, which should read as chrome rather than as part of the picture. */
.grain {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  pointer-events: none;
  opacity: .085;
  mix-blend-mode: overlay;
}
/* Jitter the tile between a few offsets so the grain shimmers rather than
   sitting frozen. steps() keeps it snapping between positions instead of
   sliding, which is what reads as film grain. */
.grain.is-animated { animation: grain-shift 1s steps(1, end) infinite; }
@keyframes grain-shift {
  0%   { background-position:   0px   0px; }
  20%  { background-position: -37px  19px; }
  40%  { background-position:  23px -41px; }
  60%  { background-position: -19px -23px; }
  80%  { background-position:  41px  37px; }
  100% { background-position:   0px   0px; }
}

/* ---- intro: the name, then the panel lifts away -------------------- */
.intro {
  position: fixed; inset: 0; z-index: var(--z-loading);
  display: none;
  align-items: center; justify-content: center;
  background: var(--ink-2);
  transform: translateY(0);
  will-change: transform;
}
/* Only ever shown when scripting is running, because only scripting can take
   it away again. With JS off — or if the head's safety timer fires — the
   page renders with no overlay at all rather than behind a panel that can
   never lift. */
.js .intro { display: flex; }
.intro__name {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wider);
  color: var(--text-10);
  text-transform: lowercase;
}
.intro.is-leaving {
  transform: translateY(-100%);
  transition: transform 1s var(--ease-in-out-strong);
}
.intro.is-done { display: none; }

.curtain {
  position: fixed; inset: 0; z-index: var(--z-curtain);
  background: var(--ink-2);
  pointer-events: none;
  transform: translateY(100%);
}
.curtain.is-entering {
  transform: translateY(0);
  transition: transform var(--duration-slower) var(--ease-in-out-strong);
}
.curtain.is-leaving {
  transform: translateY(-100%);
  transition: transform var(--duration-slower) var(--ease-in-out-strong);
}

/* the cursor pill that follows the pointer over work cards */
.cursor-pill {
  position: fixed; top: 0; left: 0; z-index: var(--z-cursor);
  display: inline-flex; align-items: center; gap: 6px;
  height: 31px; padding: 0 var(--space-4);
  background: var(--accent); color: var(--ink-1);
  border-radius: var(--radius-full);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(.6);
  transition: opacity var(--duration-base) var(--ease-out-expo),
              transform var(--duration-medium) var(--ease-spring);
}
.cursor-pill.is-visible { opacity: 1; transform: translate(-50%, -50%) scale(1); }
@media (pointer: coarse) { .cursor-pill { display: none; } }

/* ---- the button with the blob that grows from where you entered ----- */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2); /* also spaces the arrow before JS wraps the content */
  height: 44px; padding: 0 var(--space-5);
  border-radius: var(--radius-full);
  border: 1px solid var(--ink-5);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  color: var(--text-12);
  transition: color var(--duration-medium) var(--ease-out-expo),
              border-color var(--duration-medium) var(--ease-out-expo);
  isolation: isolate;
}
.btn__blob {
  position: absolute; z-index: 0;
  width: 240px; height: 240px; border-radius: 50%;
  background: var(--btn-blob, var(--accent));
  transform: translate(-50%, -50%) scale(0);
  transition: transform var(--duration-slow) var(--ease-out-expo);
  pointer-events: none;
}
.btn__content { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: var(--space-2); }
.btn:hover { color: var(--ink-1); border-color: transparent; }
.btn:hover .btn__blob { transform: translate(-50%, -50%) scale(1); }
.btn__arrow { display: inline-flex; transition: transform var(--duration-base) var(--ease-out-expo); }
.btn:hover .btn__arrow { transform: translateX(3px); }

.btn--solid { background: var(--ink-12); color: var(--ink-1); border-color: transparent; }
.btn--solid { --btn-blob: var(--accent); }
.btn--ghost { border-color: var(--ink-5); }
.btn--lg { height: 52px; padding: 0 var(--space-7); font-size: var(--text-base); }
.btn--on-light { border-color: rgba(16,17,18,.18); color: var(--ink-1); }
.btn--on-light { --btn-blob: var(--ink-1); }
.btn--on-light:hover { color: var(--ink-12); }

/* ============================================================================
   3. NAV — a floating pill that contracts and frosts as you scroll
============================================================================ */
.nav {
  --nav-w: 860px;
  --nav-fg: var(--text-12);
  --nav-fg-sub: var(--text-10);
  position: fixed; top: 20px; left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-nav);
  display: flex; align-items: center; justify-content: space-between;
  width: min(var(--nav-w), calc(100% - 40px));
  min-width: 600px;
  height: var(--nav-height);
  padding: 0 var(--space-3) 0 var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: transparent;
  backdrop-filter: none;
  box-shadow: none;
  transition: background       var(--duration-slow) var(--ease-out-expo),
              border-color     var(--duration-slow) var(--ease-out-expo),
              backdrop-filter  var(--duration-slow) var(--ease-out-expo),
              box-shadow       var(--duration-slow) var(--ease-out-expo),
              width            var(--duration-slow) var(--ease-in-out),
              padding          var(--duration-slow) var(--ease-in-out);
}
.nav.is-scrolled {
  --nav-w: 720px;
  background: var(--nav-scrolled-bg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-color: var(--surface-3);
  padding-right: var(--space-2);
  box-shadow: var(--shadow-nav);
}
/* Put data-nav-light on any full-width light band and the pill inverts its
   ink while it is over that band. Once scrolled the pill has its own dark
   frosted background, so it goes back to light text regardless. Nothing on
   the site is light-on-light today; this is here so a light section can be
   added later without the wordmark disappearing into it. */
.nav.is-on-light { --nav-fg: var(--ink-1); --nav-fg-sub: var(--ink-6); }
.nav.is-on-light.is-scrolled { --nav-fg: var(--text-12); --nav-fg-sub: var(--text-10); }

.nav__logo { display: flex; align-items: center; padding: 0 var(--space-4) 0 0; }
.nav__name {
  display: inline-flex; align-items: center; height: 44px;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wider);
  color: var(--nav-fg);
  white-space: nowrap;
  transition: color var(--duration-medium) ease;
}
/* every character that is not an initial collapses to zero width on scroll,
   so "Harsh Rastogi" resolves to "H R" without a text swap */
.nav__ch { display: inline-block; }
.nav__ch--drop {
  max-width: 18px; overflow: hidden;
  transition: max-width 60ms steps(1, end) var(--duration-fast),
              opacity 50ms ease var(--duration-fast);
}
.nav__ch--space { min-width: 4px; }
.nav.is-scrolled .nav__ch--drop {
  opacity: 0; max-width: 0;
  transition: max-width 60ms step-end, opacity 50ms;
}

.nav__menu { position: absolute; left: 50%; transform: translateX(-50%); display: flex; align-items: center; }
.nav__links { display: flex; align-items: center; gap: 6px; list-style: none; }
.nav__links a {
  display: inline-block; overflow: hidden;
  height: 44px; padding: 0 14px;
  border-radius: var(--radius-full);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--nav-fg);
  white-space: nowrap;
  transition: color var(--duration-medium) var(--ease-out-expo);
}
.nav__links a[aria-current="page"] { color: var(--accent); }
/* two stacked copies of the label; hover rolls the second one up */
.nav__slot { display: block; transition: transform var(--duration-medium) var(--ease-in-out-strong); }
.nav__slot span { display: block; height: 44px; line-height: 44px; user-select: none; }
.nav__links a:hover .nav__slot { transform: translateY(-44px); }

.nav__actions { display: flex; align-items: center; justify-content: flex-end; }
.nav__cta { --btn-blob: var(--accent); height: 44px; }

/* ---- the header row: only laid out as a row on a phone -------------- */
.nav__header { display: contents; }
.nav__caret { display: none; }
.nav__drawer { display: none; }

/* ---- phones: the pill moves to the bottom and folds open ------------
   One nav, restyled — not a second bar duplicated underneath. The pill
   anchors to the bottom of the screen showing the wordmark and a caret, and
   column-reverse means the menu it reveals grows upward out of it rather
   than off the bottom of the screen. An earlier version put a separate row
   of links down there with no name on it at all, which left the site
   unsigned on a phone.
-------------------------------------------------------------------- */
@media (max-width: 768px) and (orientation: portrait), (max-width: 599px) {
  .nav {
    top: auto;
    bottom: calc(var(--space-5) + env(safe-area-inset-bottom));
    left: 0; right: 0; margin: 0 auto;
    transform: none;
    display: flex; flex-direction: column-reverse; align-items: stretch;
    width: min(400px, 100% - 40px);
    min-width: min(320px, 100% - 40px);
    height: auto;
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--surface-3);
    border-radius: var(--radius-xl);
    background: var(--nav-scrolled-bg);
    backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
    box-shadow: 0 4px 24px rgba(0,0,0,.30);
  }
  /* the frosted pill is always frosted here, scrolled or not */
  .nav.is-scrolled { width: min(400px, 100% - 40px); padding: 0; }

  .nav__header {
    display: flex; align-items: center; justify-content: space-between;
    height: 52px;
    padding: 0 var(--space-2) 0 var(--space-5);
    flex-shrink: 0;
  }
  .nav__logo { padding: 0; min-width: 0; }
  .nav__name { font-size: var(--text-sm); height: auto; pointer-events: none; }
  /* the wordmark stays whole down here — there is room, and the pill never
     contracts on a phone, so the initials collapse has nothing to react to */
  .nav.is-scrolled .nav__ch--drop { opacity: 1; max-width: 18px; }

  .nav__caret {
    display: flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px;
    padding: var(--space-2);
    color: var(--text-10);
    transition: transform var(--duration-medium) var(--ease-in-out);
  }
  .nav.is-open .nav__caret { transform: rotate(180deg); }

  .nav__menu, .nav__actions { display: none !important; }

  .nav__drawer {
    display: block; overflow: hidden;
    max-height: 0;
    transition: max-height var(--duration-base) var(--ease-in-out);
  }
  .nav.is-open .nav__drawer { max-height: 320px; }
  .nav__drawer-links { list-style: none; padding: var(--space-4) 0; }
  .nav__drawer-links a {
    display: block;
    padding: var(--space-4) var(--space-7);
    text-align: right;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    color: var(--text-12);
  }
  .nav__drawer-links a[aria-current="page"] { color: var(--accent); }
}

/* ============================================================================
   4. HERO
============================================================================ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding-block: calc(var(--nav-height) + 80px) var(--space-24);
}
.hero__eyebrow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-bottom: var(--space-8); }
.hero__eyebrow .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .6; }
.hero__eyebrow .live { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--green-8); }
.hero__eyebrow .live::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--green-7); box-shadow: 0 0 0 4px rgba(22,163,74,.16);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.78); } }

.hero__title {
  font-family: var(--font-display);
  font-size: var(--text-5xl);
  font-weight: var(--weight-medium);
  line-height: var(--leading-display);
  letter-spacing: var(--tracking-tighter);
  color: var(--text-12);
  margin-bottom: var(--space-8);
}
/* Lines are declared rather than left to wrap: the particle word has to land
   mid-line at every width, and a reflow that drops it onto its own line reads
   as a mistake. Narrow screens still wrap within a line, which is fine. */
.hero__line { display: block; }
.hero__sub { max-width: var(--measure-lg); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-10); }

/* Each word is clipped and slides up from below on load.
   The display line-height is 0.9, which is shorter than the glyph box, so a
   plain overflow:hidden clip shaves ascenders and descenders. The clip is
   padded out vertically and pulled back with an equal negative margin, so it
   occupies the same layout space while giving the glyphs room. Horizontal
   padding is deliberately left alone: a negative inline margin swallows the
   whitespace between words. The rest
   travel is raised to 150% to keep the word fully hidden below the enlarged
   clip before it animates. */
.word-clip {
  display: inline-block; overflow: hidden; vertical-align: bottom;
  padding-block: .22em; margin-block: -.22em;
}
/* Gated behind html.js: with scripting off nothing ever adds .is-ready, so
   an ungated offset would leave the headline parked below its clip forever. */
.word-inner { display: inline-block; }
.js .word-inner { transform: translateY(150%); }
.is-ready .word-inner {
  transform: translateY(0);
  transition: transform var(--duration-slower) var(--ease-out-expo);
  transition-delay: var(--word-delay, 0s);
}
.is-settled .word-inner { transition: none; transform: none; }

/* the particle word: the real text is present for screen readers and for
   no-JS, and is hidden only once the canvas has actually taken over */
.particle-word { position: relative; display: inline-block; vertical-align: bottom; margin-left: .08em; }
/* Punctuation that belongs to the particle word but should stay real type —
   a comma rendered as dots reads as debris, not as a comma. It sits hard up
   against the word, so the pair still reads as one unit. */
.word-clip--tight { margin-left: -.02em; }
/* The text does not vanish the instant the canvas appears — it fades, so the
   word reads as dissolving into dots rather than being swapped for them. */
.particle-word__source { display: inline-block; transition: opacity .45s ease-out; }
.particle-word.is-live .particle-word__source { opacity: 0; }
/* Particles scatter well outside the glyph box. The clip that drove the
   entrance animation has done its job by the time the canvas takes over, so
   it is opened to stop it shearing the field. */
.particle-word.is-live .word-clip { overflow: visible; }
.particle-word__canvas {
  position: absolute; pointer-events: none; z-index: var(--z-raised);
}
.particle-word__canvas canvas { width: 100%; height: 100%; }

/* ============================================================================
   5. MARQUEE
   Separated by a hairline rule between items, not by a dot. The bar itself
   carries no top or bottom border — the rules between the items are the only
   lines in the band.
============================================================================ */
.marquee {
  display: flex; overflow: hidden;
  padding-block: var(--space-5);
  user-select: none;
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0, transparent 100px, #000 200px,
    #000 calc(100% - 200px), transparent calc(100% - 100px), transparent 100%);
          mask-image: linear-gradient(90deg,
    transparent 0, transparent 100px, #000 200px,
    #000 calc(100% - 200px), transparent calc(100% - 100px), transparent 100%);
}
/* The track holds two copies of the list, so translating it exactly half its
   width returns it to where it started with no seam. */
.marquee__track {
  display: flex; width: max-content;
  animation: marquee 55s linear infinite;
}
@media (hover: hover) { .marquee:hover .marquee__track { animation-play-state: paused; } }
@keyframes marquee { to { transform: translateX(-50%); } }

.marquee__item {
  display: flex; align-items: center;
  padding: 0 var(--space-12);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-10);
  white-space: nowrap;
  border-right: 1px solid var(--ink-4);
}

/* ============================================================================
   6. SELECTED WORK
============================================================================ */
.work__list { display: flex; flex-direction: column; gap: var(--space-16); }

/* The anchor wraps the image and nothing else. Wrapping the caption too — as
   this did at first — makes the whole block clickable, so the pointer turns
   into a link and the cursor pill appears while you are reading the title or
   just passing through the gap below the picture. The caption is a sibling of
   the link, not a child of it. */
.work-item { display: block; }
.work-item__link { display: block; text-decoration: none; color: inherit; will-change: transform; }
.work-card {
  position: relative; overflow: hidden;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  background: var(--panel-bg, var(--ink-12));
  transition: transform var(--duration-slow) var(--ease-in-out);
}
.work-card__media { position: absolute; inset: 0; transition: transform var(--duration-slow) var(--ease-in-out); }
.work-card__media img { width: 100%; height: 100%; object-fit: cover; }
.work-card__media svg { width: 100%; height: 100%; }

/* The card lifts a little and the artwork inside it pushes past the frame,
   so the crop tightens as you hover rather than the whole tile just moving. */
@media (hover: hover) and (pointer: fine) {
  .work-item__link:hover .work-card {
    transform: translateY(-4px) scale(1.015);
    transition: transform var(--duration-medium) var(--ease-in-out);
  }
  .work-item__link:hover .work-card__media { transform: scale(1.08); }
}

.work-card__stamp {
  position: absolute; left: var(--space-6); bottom: var(--space-6);
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--panel-ink, var(--ink-6));
}
.work-card__badge {
  position: absolute; right: var(--space-6); top: var(--space-6);
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: 28px; padding: 0 var(--space-4);
  border-radius: var(--radius-full);
  background: rgba(16,17,18,.08);
  border: 1px solid rgba(16,17,18,.12);
  font-size: var(--text-2xs); font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest); text-transform: uppercase;
  color: var(--panel-ink, var(--ink-6));
}
.work-card__badge.is-live { color: var(--green-5); border-color: rgba(17,149,68,.3); background: rgba(17,149,68,.10); }
.work-card__badge.is-live::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--green-6); animation: pulse 2.4s ease-in-out infinite;
}

.work-item__meta { display: flex; flex-direction: column; padding: var(--space-6) 0 var(--space-8); }

/* Body-scale, not display-scale. This line is a caption under a picture, not
   a heading competing with the section above it. */
.work-item__title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-normal);
  color: var(--text-12);
}

/* The finding sits below the title's own line box and rolls up into view
   when the card is hovered, in that card's accent. Kept out of the way at
   rest so the row reads as one clean line; still in the DOM, so it is read
   aloud and indexed either way. */
.work-item__reveal {
  display: inline-block; overflow: hidden;
  height: 1.4em; margin-left: .3em;
  vertical-align: bottom;
}
.work-item__reveal span {
  display: block;
  line-height: var(--leading-normal);
  color: var(--accent-card, var(--teal-9));
  transform: translateY(100%);
  transition: transform var(--duration-medium) var(--ease-in-out-strong);
}
/* Driven from the image link, which is the caption's previous sibling. A
   general sibling combinator does this without :has(), so it works in older
   Safari and Firefox too. */
.work-item__link:hover ~ .work-item__meta .work-item__reveal span { transform: translateY(0); }

/* each card gets its own accent, cycling through the brand ramp */
.work__list > .work-item:nth-child(3n+1) { --accent-card: var(--teal-9); }
.work__list > .work-item:nth-child(3n+2) { --accent-card: var(--blue-9); }
.work__list > .work-item:nth-child(3n+3) { --accent-card: var(--green-9); }

@media (max-width: 768px) {
  .work-card { border-radius: var(--radius-sm); }
  .work-item__title { font-size: clamp(16px, 4.5vw, 24px); }
  /* no hover on a touch screen, so the finding is simply part of the line */
  .work-item__reveal { display: inline; height: auto; overflow: visible; vertical-align: baseline; }
  .work-item__reveal span { display: inline; transform: none; transition: none; }
}

/* ---- the index of everything else --------------------------------- */
.index { border-top: 1px solid var(--surface-3); }
.index__row {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) minmax(0, 1.1fr) auto;
  gap: var(--space-6);
  align-items: baseline;
  padding-block: var(--space-7);
  border-bottom: 1px solid var(--surface-3);
  transition: background var(--duration-medium) var(--ease-out-expo),
              padding var(--duration-medium) var(--ease-out-expo);
}
.index__row:hover { background: var(--surface-2); padding-inline: var(--space-5); }
.index__num { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-8); letter-spacing: var(--tracking-wider); }
.index__name {
  font-family: var(--font-display); font-size: var(--text-xl);
  font-weight: var(--weight-medium); letter-spacing: var(--tracking-snug);
  color: var(--text-12); display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
}
.index__note { font-size: var(--text-sm); color: var(--text-9); line-height: var(--leading-normal); }
.index__go {
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--tracking-wider); text-transform: uppercase;
  color: var(--text-9); white-space: nowrap;
  transition: color var(--duration-base) var(--ease-out-expo), transform var(--duration-base) var(--ease-out-expo);
}
.index__row:hover .index__go { color: var(--accent); transform: translateX(4px); }
.pill {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: 22px; padding: 0 var(--space-3);
  border: 1px solid var(--surface-4); border-radius: var(--radius-full);
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider); text-transform: uppercase;
  color: var(--text-9);
}
.pill.is-live { color: var(--green-8); border-color: var(--green-4); background: var(--green-1); }
@media (max-width: 900px) {
  .index__row { grid-template-columns: 44px 1fr auto; }
  .index__note { grid-column: 2 / -1; }
}
@media (max-width: 560px) {
  .index__row { grid-template-columns: 1fr auto; gap: var(--space-2) var(--space-4); }
  .index__num { grid-column: 1 / -1; }
  .index__note { grid-column: 1 / -1; }
}

/* ============================================================================
   7. APPROACH — text that stacks, not cards that stack

   There is no card here: each panel's background is the page's own colour, so
   as the next one slides up it wipes the last cleanly and the section reads as
   one column of text replacing itself. An earlier version drew bordered,
   rounded, lighter panels, which turned it into a pile of boxes.

   The geometry is deliberate and interdependent. Each panel is half the
   viewport tall and pins just above the middle of the screen. The label block
   is stretched to the height of the whole run and then pulled back up by
   almost exactly the same amount, which leaves it barely any room in the flow
   while giving its sticky header the full run to travel through.
============================================================================ */
.stack { position: relative; }

.stack__label-wrap {
  height: calc(var(--card-count) * 50vh - 50vh + 129px);
  margin-bottom: calc(50vh - var(--card-count) * 50vh + 1rem);
  pointer-events: none;
}
.stack__header {
  position: sticky;
  top: calc(32vh - 170px);
  z-index: var(--z-sticky);
}
.stack__label {
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--ink-9);
  margin-bottom: var(--space-20);
}
/* an odometer: a column of numbers translated by the active index */
.odo { display: flex; align-items: baseline; height: var(--space-7); overflow: hidden; }
.odo__col { display: block; transition: transform var(--duration-slower) var(--ease-out-expo); }
.odo__col span {
  display: block;
  height: var(--space-7); line-height: var(--space-7);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest);
  color: var(--blue-9);
}

.stack__card {
  position: sticky;
  top: calc(32vh - 25px);
  display: flex; flex-direction: column;
  width: 100%; height: 50vh;
  background: var(--surface);
  transform-origin: center top;
}
.stack__card h3 {
  font-family: var(--font-display);
  font-size: var(--text-4xl);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-tighter);
  line-height: var(--leading-tight);
  color: var(--text-12);
  margin-bottom: var(--space-7);
  max-width: 22ch;
}
.stack__card p {
  font-size: var(--text-3xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  color: var(--text-9);
  max-width: 36ch;
}

/* ============================================================================
   8. OFFER — two big colour cards
============================================================================ */
.offer__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-6); }
@media (max-width: 860px) { .offer__grid { grid-template-columns: 1fr; } }

.offer-card {
  display: flex; flex-direction: column;
  min-height: 420px;
  padding: var(--space-10);
  border-radius: var(--radius-xl);
  background: var(--card-bg);
  color: var(--ink-1);
  box-shadow: var(--shadow-light);
  transition: transform var(--duration-slow) var(--ease-out-expo);
}
.offer-card:hover { transform: translateY(-6px); }
.offer-card .eyebrow { color: rgba(16,17,18,.62); }
.offer-card h3 {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
  margin: var(--space-6) 0 var(--space-5);
  max-width: 16ch;
}
.offer-card p { color: rgba(16,17,18,.72); font-size: var(--text-base); line-height: var(--leading-normal); max-width: var(--measure-body); }
.offer-card__foot { margin-top: auto; padding-top: var(--space-10); }

/* ============================================================================
   9. WRITING
============================================================================ */
.writing__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-5); }
@media (max-width: 900px) { .writing__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .writing__grid { grid-template-columns: 1fr; } }

.post-card {
  display: flex; flex-direction: column;
  min-height: 260px;
  padding: var(--space-7);
  border: 1px solid var(--surface-3);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  transition: transform var(--duration-slow) var(--ease-out-expo),
              border-color var(--duration-medium) var(--ease-out-expo),
              background var(--duration-medium) var(--ease-out-expo);
}
.post-card:hover { transform: translateY(-4px); border-color: var(--surface-5); background: var(--surface-3); }
.post-card__meta {
  display: flex; align-items: center; gap: var(--space-3);
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider); text-transform: uppercase;
  color: var(--text-8);
}
.post-card__meta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .6; }
.post-card__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-tight);
  color: var(--text-12);
  margin: var(--space-5) 0 var(--space-3);
  text-wrap: balance;
}
.post-card__dek { font-size: var(--text-sm); line-height: var(--leading-normal); color: var(--text-9); }
.post-card__foot { margin-top: auto; padding-top: var(--space-6); display: flex; flex-wrap: wrap; gap: var(--space-2); }
.post-card.is-featured { grid-column: span 2; }
.post-card.is-featured .post-card__title { font-size: var(--text-3xl); letter-spacing: var(--tracking-tight); }
.post-card.is-featured .post-card__dek { font-size: var(--text-base); max-width: var(--measure-body); }
@media (max-width: 600px) { .post-card.is-featured { grid-column: span 1; } }

.lib-filters { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-10); }
.lib-filter {
  height: 34px; padding: 0 var(--space-4);
  border: 1px solid var(--surface-4); border-radius: var(--radius-full);
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider); text-transform: uppercase;
  color: var(--text-9);
  transition: all var(--duration-base) var(--ease-out-expo);
}
.lib-filter:hover { color: var(--text-12); border-color: var(--surface-6); }
.lib-filter[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--ink-1); }
.lib-filter .count { opacity: .55; margin-left: 7px; }
.lib-empty { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-8); padding-block: var(--space-12); }

/* ============================================================================
   10. FOOTER
============================================================================ */
.footer { position: relative; padding-top: var(--space-28); padding-bottom: var(--space-10); border-top: 1px solid var(--surface-3); overflow: hidden; }
.footer__cta {
  font-family: var(--font-display);
  font-size: var(--text-4xl);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-tighter);
  line-height: var(--leading-tight);
  color: var(--text-12);
  max-width: 18ch;
  margin-bottom: var(--space-8);
  text-wrap: balance;
}
.footer__wordmark {
  font-family: var(--font-display);
  font-size: clamp(56px, 13vw, 190px);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tighter);
  line-height: .82;
  color: var(--surface-4);
  margin-top: var(--space-28);
  white-space: nowrap;
  user-select: none;
}
.footer__tagline { font-size: var(--text-lg); color: var(--text-10); margin-top: var(--space-6); max-width: var(--measure-lg); }
.footer__links { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-8); }
.footer__link {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: 38px; padding: 0 var(--space-5);
  border: 1px solid var(--surface-4); border-radius: var(--radius-full);
  font-size: var(--text-sm); color: var(--text-11);
  transition: all var(--duration-base) var(--ease-out-expo);
}
.footer__link:hover { background: var(--text-12); color: var(--ink-1); border-color: transparent; }
.footer__legal {
  display: flex; flex-wrap: wrap; gap: var(--space-4); justify-content: space-between;
  margin-top: var(--space-16); padding-top: var(--space-6);
  border-top: 1px solid var(--surface-3);
  font-size: var(--text-xs); color: var(--text-8);
}

/* ============================================================================
   11. READING VIEW
============================================================================ */
.progress { position: fixed; top: 0; left: 0; height: 2px; width: 0; background: var(--accent); z-index: var(--z-nav); }

.article-head { padding-block: calc(var(--nav-height) + 90px) var(--space-10); max-width: 860px; }
.article-back { display: inline-flex; align-items: center; gap: var(--space-2); font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: var(--tracking-wider); text-transform: uppercase; color: var(--text-8); margin-bottom: var(--space-8); transition: color var(--duration-base); }
.article-back:hover { color: var(--accent); }
.article-title {
  font-family: var(--font-display);
  font-size: var(--text-4xl);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-tighter);
  line-height: var(--leading-display);
  color: var(--text-12);
  margin-block: var(--space-6) var(--space-6);
  text-wrap: balance;
}
.article-dek { font-size: var(--text-xl); line-height: var(--leading-normal); color: var(--text-10); max-width: var(--measure-lg); }
.article-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: var(--tracking-wider); text-transform: uppercase; color: var(--text-8); }
.article-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .6; }
.article-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-7); }

.reading { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; padding-bottom: var(--space-20); }
@media (min-width: 1100px) { .reading { grid-template-columns: minmax(0, 720px) 200px; gap: var(--space-20); align-items: start; } }

.prose { font-size: var(--text-lg); line-height: var(--leading-loose); color: var(--text-11); max-width: 72ch; }
.prose > * + * { margin-top: var(--space-6); }
.prose h2 { font-family: var(--font-display); font-size: var(--text-3xl); font-weight: var(--weight-medium); letter-spacing: var(--tracking-tight); line-height: var(--leading-tight); color: var(--text-12); margin-top: var(--space-16); scroll-margin-top: 120px; }
.prose h3 { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: var(--weight-medium); letter-spacing: var(--tracking-snug); color: var(--text-12); margin-top: var(--space-12); scroll-margin-top: 120px; }
.prose strong { color: var(--text-12); font-weight: var(--weight-semibold); }
.prose a { color: var(--accent); border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); transition: border-color var(--duration-base); }
.prose a:hover { border-bottom-color: var(--accent); }
.prose ul, .prose ol { padding-left: var(--space-6); }
.prose li + li { margin-top: var(--space-3); }
.prose blockquote { border-left: 2px solid var(--accent); padding-left: var(--space-6); color: var(--text-12); font-size: var(--text-xl); line-height: var(--leading-snug); letter-spacing: var(--tracking-snug); }
.prose code { font-family: var(--font-mono); font-size: .86em; background: var(--surface-3); padding: 2px 6px; border-radius: var(--radius-xs); color: var(--teal-9); }
.prose pre { font-family: var(--font-mono); font-size: var(--text-sm); background: var(--surface-2); border: 1px solid var(--surface-3); padding: var(--space-6); border-radius: var(--radius-md); overflow-x: auto; line-height: var(--leading-normal); }
.prose pre code { background: none; padding: 0; color: var(--text-11); }
.prose hr { border: 0; border-top: 1px solid var(--surface-3); margin-block: var(--space-12); }
.prose img { border-radius: var(--radius-md); }

.toc { display: none; }
@media (min-width: 1100px) {
  .toc { display: block; position: sticky; top: calc(var(--nav-height) + 60px); font-size: var(--text-sm); }
  .toc__label { font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: var(--tracking-widest); text-transform: uppercase; color: var(--text-8); margin-bottom: var(--space-4); }
  .toc ul { list-style: none; border-left: 1px solid var(--surface-3); }
  .toc a { display: block; padding: var(--space-2) 0 var(--space-2) var(--space-4); margin-left: -1px; border-left: 1px solid transparent; color: var(--text-8); line-height: var(--leading-snug); transition: all var(--duration-base) var(--ease-out-expo); }
  .toc a:hover { color: var(--text-11); }
  .toc a.is-active { color: var(--accent); border-left-color: var(--accent); }
  .toc li.is-sub a { padding-left: var(--space-8); font-size: var(--text-xs); }
}

/* ============================================================================
   12. MOTION — scroll reveals, applied only once JS is present
============================================================================ */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity var(--duration-slower) var(--ease-out-expo),
              transform var(--duration-slower) var(--ease-out-expo);
  transition-delay: var(--reveal-delay, 0s);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }
/* once the reveal has played there is nothing left to transition */
.js [data-reveal].is-settled { transition: none; transform: none; }

/* split-line reveal for headings */
.js [data-reveal-lines] .line-inner { display: block; transform: translateY(140%); transition: transform var(--duration-slower) var(--ease-out-expo); transition-delay: var(--line-delay, 0s); }
.js [data-reveal-lines].is-in .line-inner { transform: none; }
.js [data-reveal-lines].is-settled .line-inner { transition: none; transform: none; }
/* same padded-clip trick as .word-clip, for the same reason */
.line-clip { display: block; overflow: hidden; padding: .16em 0; margin: -.16em 0; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  /* These must match or beat the specificity of the rules they undo. The
     base offsets are written as `.js <thing>`, so a bare `.word-inner` here
     would lose the cascade and leave the headline parked below its clip. */
  .js [data-reveal] { opacity: 1; transform: none; }
  .js [data-reveal-lines] .line-inner { transform: none; }
  .js .word-inner { transform: none; }
  .marquee__track { animation: none; width: auto; flex-wrap: wrap; }
  .grain { display: none; }
  .intro { display: none !important; }
  .grain.is-animated { animation: none; }
}

/* ============================================================================
   15. TOUCH
   A touch screen has no hover, so every hover-driven affordance on the site
   simply never happens there and the page can feel inert next to the desktop
   version. A press state gives back the one piece of feedback a finger can
   actually get.
============================================================================ */
@media (hover: none) {
  .work-item__link:active .work-card,
  .offer-card:active,
  .post-card:active {
    transform: scale(.985);
    transition: transform var(--duration-fast) var(--ease-in-out);
  }
  .index__row:active { background: var(--surface-2); }
  .btn:active, .footer__link:active { transform: scale(.97); }
}

/* ============================================================================
   13. UTILITIES
============================================================================ */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.stack-gap > * + * { margin-top: var(--space-6); }
.mt-auto { margin-top: auto; }

/* ============================================================================
   14. NARROW SCREENS
   The desktop hero declares its own line breaks so the particle word always
   lands mid-line. At phone widths those same breaks strand single words, so
   the lines are released and the text is allowed to wrap on its own.
============================================================================ */
@media (max-width: 860px) {
  /* the ticker tightens up and its fade pulls in, or the items are wider
     than the screen and only one is ever legible */
  .marquee { padding-block: var(--space-8); }
  .marquee__item { padding: 0 var(--space-5); font-size: clamp(12px, 3.2vw, 16px); }
  .marquee {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, transparent 20px,
      #000 calc(10% + 20px), #000 calc(90% - 20px), transparent calc(100% - 20px), transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, transparent 20px,
      #000 calc(10% + 20px), #000 calc(90% - 20px), transparent calc(100% - 20px), transparent 100%);
  }

  .hero__line { display: inline; }
  .hero__title { max-width: 16ch; }

  /* the bottom nav bar floats over the page; keep the last of the content
     clear of it, including the home-indicator inset */
  body { padding-bottom: calc(92px + env(safe-area-inset-bottom)); }

  .hero { min-height: auto; padding-block: calc(var(--nav-height) + 72px) var(--space-16); }
  .hero__eyebrow { gap: var(--space-2) var(--space-3); }
  .hero__eyebrow .dot { display: none; }

  .stack__card { height: auto; min-height: 46vh; padding-bottom: var(--space-8); }
  .stack__label { margin-bottom: var(--space-10); }
  .offer-card { min-height: auto; padding: var(--space-7); }
  .work-item__title { font-size: var(--text-2xl); }
  .footer { padding-top: var(--space-20); }
  .footer__wordmark { margin-top: var(--space-16); }
  .footer__legal { flex-direction: column; gap: var(--space-2); }
}

@media (max-width: 560px) {
  .work-card { aspect-ratio: 4 / 3; }
  .work-card__stamp, .work-card__badge { left: var(--space-5); bottom: var(--space-5); right: auto; }
  .work-card__badge { right: var(--space-5); left: auto; top: var(--space-5); bottom: auto; }
  .hero__actions .btn { width: 100%; }
}
