/* ============================================================
   MIRRORLESS CREATIVES — shared stylesheet
   Design tokens lifted from the Figma preview build.
   ============================================================ */

/* Tilt Warp is requested with its XROT/YROT axes so the hero wordmark can be
   driven per-letter by initHeroProximity(). Tilt Warp has no weight axis —
   these two rotate the letterform in 3D, range -45..45, default 0/0, which is
   the same face the static request served. */
@import url('https://fonts.googleapis.com/css2?family=Tilt+Warp:XROT,YROT@-45..45,-45..45&family=Instrument+Serif:ital@0;1&family=Geist:wght@100..900&display=swap');

:root {
  /* ---- palette (sampled from the Figma preview) ---- */
  --ink:        #0F0E0E;
  --ink-2:      #141414;
  --ink-3:      #1E1D1D;
  --paper:      #F6F8FB;
  --paper-2:    #F5F5F5;
  --dim:        #D1D2D8;
  --smoke:      rgba(246, 248, 251, 0.52);
  --hairline:   rgba(246, 248, 251, 0.14);
  --hairline-ink: rgba(15, 14, 14, 0.16);

  /* nav pill accents */
  --red:    #D32F3C;   /* same red as the OUR SERVICES film bg (#D42F3B) */
  --yellow: #FFDD15;
  --teal:   #09CDA7;
  --violet: #7C5CFF;

  /* ---- type ---- */
  --display: 'Tilt Warp', 'Geist', system-ui, sans-serif;
  --serif:   'Instrument Serif', 'Times New Roman', Times, serif;
  --sans:    'Geist', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  /* ---- motion ---- */
  --ease:      cubic-bezier(0.22, 1, 0.36, 1);
  --ease-io:   cubic-bezier(0.65, 0, 0.35, 1);
  --fast:      0.32s;
  --slow:      0.9s;

  /* ---- layout ---- */
  --gutter: clamp(20px, 4vw, 56px);
  --maxw:   1520px;
  --radius: 8px;

  /* hero wordmark tracking — see .hero-word.
     Landed at -0.022em on 2026-09-09 after comparing -0.058 (the old value:
     "rl", "ss" and "Cr" all conjoined), -0.032 (still merging at "ss"),
     -0.022 and +0.014 (open enough to read as airy). -0.022 is the tightest
     setting where every pair still keeps a hair of daylight. */
  --hero-track: -0.022em;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-weight: 600;
  font-size: clamp(15px, 1.15vw, 20px);
  letter-spacing: -0.02em;
  line-height: 1.42;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, video, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; }
h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
::selection { background: var(--accent, var(--yellow)); color: var(--ink); }

.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }

/* ---------- type utilities ---------- */
.display {
  font-family: var(--display);
  font-weight: 400;
  line-height: 0.84;
  letter-spacing: -0.05em;
  text-wrap: balance;
}
.serif {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: -0.016em;
}
.eyebrow {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--smoke);
}
.on-light .eyebrow { color: rgba(15, 14, 14, 0.55); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ============================================================
   Glove cursor. 2026-09-26 (Aurobind): the film-canister cursor (lifted
   from the Figma preview, assets/cursor.png) is gone; this is a big cartoon
   glove, assets/cursor-glove.svg, hotspot on the fingertip. The word ring
   that rolls round it over films (Play, View, Next...) is unchanged.
   ============================================================ */
@media (hover: hover) and (pointer: fine) {
  body, a, button, [role="link"], [data-cursor] { cursor: none; }
}

.cursor {
  position: fixed;
  top: 0; left: 0;
  width: 62px; height: 70px;
  z-index: 9999;
  pointer-events: none;
  will-change: transform;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.cursor img {
  width: 100%; height: 100%;
  object-fit: contain;
  transform-origin: 33.6% 4.9%;             /* the fingertip */
  transition: transform 0.42s var(--ease), filter 0.42s var(--ease);
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
}
.cursor.is-live { opacity: 1; }
.cursor.is-hover img { transform: scale(0.9) rotate(-10deg); }
.cursor.is-media img { transform: scale(1.04); }
.cursor.is-down  img { transform: scale(0.82) rotate(-14deg); }

/* rotating word ring for media targets, round the hand */
.cursor-ring {
  position: absolute;
  left: calc(50% - 76px); top: calc(50% - 76px);
  width: 152px; height: 152px;              /* clear of the glove */
  opacity: 0;
  transform: scale(0.6) rotate(0deg);
  transition: opacity 0.34s var(--ease), transform 0.5s var(--ease);
}
.cursor.is-media .cursor-ring { opacity: 1; transform: scale(1) rotate(0deg); }
.cursor-ring svg { width: 100%; height: 100%; animation: spin 9s linear infinite; }
.cursor-ring text {
  font-family: var(--sans); font-weight: 600;
  font-size: 12px; letter-spacing: 0.22em; fill: var(--paper);
  text-transform: uppercase;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (pointer: coarse), (prefers-reduced-motion: reduce) { .cursor { display: none; } }

/* ============================================================
   Grain
   ============================================================ */
.grain {
  position: fixed; inset: -50%;
  width: 200%; height: 200%;
  pointer-events: none;
  z-index: 400;
  opacity: 0.24;
  mix-blend-mode: overlay;
  background-image: var(--grain-url);
  background-size: 190px 190px;
  animation: grain-shift 0.7s steps(4) infinite;
}
@keyframes grain-shift {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-3%, 2%); }
  50%  { transform: translate(2%, -3%); }
  75%  { transform: translate(-2%, -2%); }
  100% { transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce) { .grain { animation: none; } }

/* ============================================================
   Intro — the MIRRORS_INTRO film
   2026-09-18: replaces the kinetic-type intro (flash lines, letter rise,
   red wipe, counter). video_2/MIRRORS_INTRO.mp4 is shipped untouched as
   assets/intro.mp4 — 1920x1080, 1.875s, no audio, already faststart.
   The overlay, once-per-session rule, Skip and hand-off to the hero are
   the same as before; only what plays inside changed.
   ============================================================ */
.intro {
  position: fixed; inset: 0;
  z-index: 900;
  background: #000;          /* the film's own black, so its frame edge never shows */
  overflow: hidden;
  cursor: none;
}
.intro[hidden] { display: none; }

/* Covers the screen on landscape. On a portrait phone "cover" would blow the
   16:9 frame up until the wordmark (~31% of the frame's width) ran off both
   edges, so the width is capped at 225vw — the wordmark then spans ~70% of
   the screen, and the frame's black meets the overlay's black unseen. */
.intro-film {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(max(100vw, 177.78vh), 225vw);
  height: auto;
  aspect-ratio: 16 / 9;
  max-width: none;
  transform: translate(-50%, -50%);
  object-fit: cover;
}

.intro-skip {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(20px, 4vh, 44px);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-size: 12px;
  color: var(--smoke);
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 3px;
  opacity: 0;
  transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.intro.is-running .intro-skip { animation: fade-up 0.7s var(--ease) 0.4s both; }
.intro-skip:hover { color: var(--paper); border-color: var(--paper); }

@keyframes fade-up {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* hand-off to the hero: the film's last frame blows out past the camera */
.intro.is-out {
  animation: intro-out 0.86s var(--ease) forwards;
}
@keyframes intro-out {
  0%   { opacity: 1; transform: scale(1); filter: blur(0); }
  100% { opacity: 0; transform: scale(1.42); filter: blur(14px); }
}

/* page holds still until the intro is done */
body.intro-locked { overflow: hidden; }

/* reduced motion: the film flickers through typefaces several times a
   second, so it is not played — its first frame holds, then fades */
@media (prefers-reduced-motion: reduce) {
  .intro.is-running .intro-skip { animation: none; opacity: 1; }
  .intro.is-out { animation: intro-out-flat 0.4s linear forwards; }
  @keyframes intro-out-flat { to { opacity: 0; } }
}

/* ============================================================
   Marquee bar
   ============================================================ */

/* ============================================================
   Variable proximity — hero wordmark
   Port of the React Bits <VariableProximity /> component. The
   original drives 'wght'/'opsz' on Roboto Flex; Tilt Warp has
   neither axis, so the same proximity maths drives its XROT and
   YROT rotation axes instead. Nothing else about the wordmark
   changes — at rest every letter sits at 0/0, the default face.
   Letters are the <i> elements splitLetters() already makes.
   ============================================================ */
.hero-word i {
  /* pinned by initHeroProximity() after the fit, so a warped glyph
     cannot push its neighbours and re-flow the fitted line */
  will-change: font-variation-settings;
  transition: font-variation-settings 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* a warped glyph leans outside its own box — let it, and never let it
   become a scrollbar */
.hero-word { overflow: visible; }
@media (prefers-reduced-motion: reduce) {
  .hero-word i { transition: none; font-variation-settings: normal !important; }
}

/* ============================================================
   Footer light mode — reworked 2026-09-08
   This used to be a class flip with an 0.85s colour transition,
   which read as a flashbang. It is now *scrubbed*: paintTheme()
   in assets/site.js interpolates every ground token against how
   far the footer has entered the viewport, so the page fades in
   step with the scroll instead of snapping.
   The light ground is a warm off-white rather than the cool
   #F6F8FB, which is most of what made the old one hurt.
   Accent goes yellow -> red across the same ramp.
   ============================================================ */
:root {
  --accent: var(--yellow);      /* red once the footer is in view */
  --mark: var(--paper);         /* the footer wordmark, -> red */
  --grain-opacity: 1;
}
/* No transition on these: they are driven per-frame from scroll, and a
   transition on top of a scrub only adds lag. */

/* accent, wherever it was hard-coded yellow */
::selection { background: var(--accent); color: var(--ink); }
.cta-link { background-image: linear-gradient(var(--accent), var(--accent)); }
.cta-link:hover { color: var(--accent); }
.foot-mark { color: var(--mark); }
.grain { opacity: var(--grain-opacity); }

/* ============================================================
   Navigation
   ============================================================ */
/* No bar, no backdrop blur — the chrome floats, so nothing smears the
   artwork, the red services flood or the giant type it passes over. */
.nav {
  position: sticky;
  top: 0;
  z-index: 300;
  padding: 18px 0;
  pointer-events: none;
  transition: transform 0.5s var(--ease), opacity 0.4s var(--ease);
}
.nav a, .nav .nav-meta { pointer-events: auto; }
/* with no plate behind it, floating chrome would collide with the display type
   it passes over — so it steps out on the way down and returns on the way up */
.nav.is-away { transform: translateY(-130%); opacity: 0; }
@media (prefers-reduced-motion: reduce) { .nav.is-away { transform: none; opacity: 1; } }
.nav-inner {
  /* logo at the gutter, pill track dead centre, coordinates on the far side —
     the composition the nav has always had. pillnav.css lays the component out
     across the row to hit it. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
}
/* .nav-mark / .pills / .pill lived here until 2026-09-08. The nav is now the
   PillNav component — everything that paints it is in assets/pillnav.css. */
.nav-meta {
  grid-column: -2 / -1;
  grid-row: 1;
  justify-self: end;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--smoke);
  text-align: right;
  transition: opacity 0.45s var(--ease);
}
/* it has no plate behind it, so it steps aside once it would sit on artwork */
.nav.is-stuck .nav-meta { opacity: 0; pointer-events: none; }
@media (max-width: 860px) {
  .nav-inner { grid-template-columns: 1fr; }
  .nav-meta { display: none; }
}
@media (max-width: 560px) {
  .nav { padding: 12px 0; }
  .nav-inner { gap: 10px; }
}

/* ============================================================
   Hero
   ============================================================ */
.hero {
  position: relative;
  /* --nav-h is measured in site.js so the hero always ends on the fold.
     The ticker that used to sit above it was removed 2026-09-08. */
  margin-top: calc(var(--nav-h, 92px) * -1);
  padding-top: var(--nav-h, 92px);
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: hidden;
  isolation: isolate;
}
/* ---- hero collage ----------------------------------------------
   Replaces the hero film. The cluster is absolutely placed inside a
   fixed-ratio box so the percentage layout in initHeroCollage() holds
   at every width; the scrim keeps the wordmark legible over it. */
.hero-collage {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.hero-collage-in {
  position: relative;
  width: min(76vw, 1120px);
  height: min(64vh, 640px);
}
.hero-collage .tile {
  position: absolute;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.82);
  will-change: transform;
}
.hero-collage .tile img,
.hero-collage .tile video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  /* the poster carries the frame until the film has enough to show */
  background: var(--ink-3);
}
/* Lightened 2026-09-08 — the old scrim was hiding the top of the grid.
   It now only does work where type actually sits: a whisper at the very
   top for the nav, clear through the middle, and a late ramp that starts
   below the cluster so the wordmark still reads. */
.hero-collage::after {
  content: '';
  position: absolute; inset: 0;
  z-index: 60;
  background:
    linear-gradient(to bottom,
      rgba(15, 14, 14, 0.30) 0%,
      rgba(15, 14, 14, 0.00) 22%,
      rgba(15, 14, 14, 0.00) 62%,
      rgba(15, 14, 14, 0.55) 84%,
      rgba(15, 14, 14, 0.92) 100%);
}
/* 2026-09-09: the copy used to sit in flow, stacked directly on the wordmark
   at the foot of the hero — a long way under the collage and hard to tie to
   it. It now hangs off the cluster instead: placeHeroCopy() in site.js
   measures the lowest tile's untransformed layout box and writes
   --hero-copy-top, so the line lands just under the grid at every width and
   never collides with the wordmark (the JS clamps it). The percentage is
   only the pre-measure fallback. */
.hero-copy {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--hero-copy-top, 66%);
  z-index: 4;
  text-align: center;
  padding-bottom: 0;
}
.hero-copy p { font-size: clamp(15px, 1.35vw, 20px); }
.hero-copy p + p { color: var(--dim); margin-top: 2px; }

/* ---- typewriter ------------------------------------------------
   initTypewriter() rebuilds the paragraph as a ghost + an overlay:
   the ghost is the finished line at opacity 0, so it holds the box
   at full width (a centred line that grows character by character
   would otherwise shuffle under itself) and stays in the
   accessibility tree; the overlay is what actually types. Without
   JS the paragraph is left alone and simply reads as text. */
.hero-type .tw {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  text-align: left;
}
.hero-type .tw-ghost { opacity: 0; }
.hero-type .tw-out {
  position: absolute;
  left: 0;
  top: 0;
  white-space: pre;
}
.hero-type .tw-out::after {
  content: '';
  display: inline-block;
  width: 0.07em;
  height: 1.05em;
  margin-left: 0.06em;
  vertical-align: -0.18em;
  background: var(--red);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}
.hero-type .is-caret.tw-out::after {
  opacity: 1;
  animation: tw-blink 0.9s steps(1, end) infinite;
}
@keyframes tw-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .hero-type .tw-out::after { animation: none; }
}

.hero-word {
  position: relative;
  font-family: var(--display);
  /* fitHeroWord() in site.js sets --hero-size so the line lands exactly on
     the viewport width; this is only the pre-fit fallback. Measuring beats
     a vw guess because the Tilt Warp metrics decide the real width. */
  font-size: var(--hero-size, 12.4vw);
  line-height: 0.78;
  /* 2026-09-09: was -0.058em. Tilt Warp is already drawn tight and round, so
     that much negative tracking closed the counters up and the line read as
     one conjoined shape — "rl", "ss" and "Cr" in particular. Opened just far
     enough to separate them and no further; fitHeroWord() re-fits the size
     around it, so the line still runs edge to edge. Value in :root. */
  letter-spacing: var(--hero-track);
  color: var(--paper);
  text-align: center;
  white-space: nowrap;
  padding-bottom: clamp(2px, 0.7vh, 12px);
  user-select: none;
  /* letter-spacing adds its space after the LAST letter too, so a centred
     line sits half a step off centre — this puts it back */
  margin-inline: 0;
  text-indent: calc(var(--hero-track) / 2);
}
/* the word gap. Tilt Warp's own space is narrow, so the two words still need
   a set width between them even now the tracking is open. */
.hero-word i.sp { width: 0.17em; }
.hero-word i {
  display: inline-block;
  font-style: normal;
  transform: translateY(0.42em);
  opacity: 0;
}
.hero.is-in .hero-word i {
  animation: hero-letter 1s var(--ease) forwards;
  animation-delay: calc(0.06s + var(--i) * 0.038s);
}
@keyframes hero-letter {
  from { transform: translateY(0.42em); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
.hero.is-in .hero-copy { animation: fade-up 0.9s var(--ease) 0.1s both; }

.hero-scroll {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(80px, 16vh, 180px);
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--smoke);
  z-index: 2;
}
.hero-scroll::after {
  content: '';
  width: 1px; height: 46px;
  background: linear-gradient(var(--paper), transparent);
  animation: drop 1.9s var(--ease-io) infinite;
}
@keyframes drop {
  0%   { transform: scaleY(0); transform-origin: top; }
  45%  { transform: scaleY(1); transform-origin: top; }
  55%  { transform: scaleY(1); transform-origin: bottom; }
  100% { transform: scaleY(0); transform-origin: bottom; }
}
@media (max-width: 860px) { .hero-scroll { display: none; } }

/* ---- phone hero (2026-09-18, Aurobind) ---------------------------
   "Mirrorless Creatives" fitted to a phone's width came out small, so
   the phone sets "Mirrorless" alone — fitHeroWord() measures only the
   letters showing, so it runs edge to edge at about twice the size. The
   h1 still carries the full name in its aria-label.
   The collage gets a box a little wider than the screen, laid out by
   initHeroCollage() (its PHONE table): five tiles since 2026-09-25, was
   three by three; the outer ones bleed off the edges a touch.
   Its height follows its width, because the tiles' heights do. */
@media (max-width: 819px) {
  .hero-word i.sp,
  .hero-word i.sp ~ i { display: none; }
  /* wider than its grid cell, so centring needs the negative margins —
     otherwise the grid's safe alignment pins it left and it only bleeds
     off the right-hand edge */
  .hero-collage-in {
    width: 110vw;
    height: 150vw;
    margin-inline: calc((100% - 110vw) / 2);
  }
  .hero-collage::after {
    background:
      linear-gradient(to bottom,
        rgba(15, 14, 14, 0.30) 0%,
        rgba(15, 14, 14, 0.00) 14%,
        rgba(15, 14, 14, 0.00) 72%,
        rgba(15, 14, 14, 0.60) 86%,
        rgba(15, 14, 14, 0.92) 100%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hero-word i, .hero-copy { animation: none !important; opacity: 1 !important; transform: none !important; }
  .hero-scroll::after { animation: none; }
}

/* ============================================================
   Statement
   2026-09-18: one word over a centred body, and the body types
   itself in as you scroll. At rest (no JS, reduced motion) this is
   an ordinary centred section. initStatementType() in site.js adds
   .is-typed, which pins it for a screen and hides every character
   it has not typed yet — they are all laid out from the start, so
   the centred lines never re-flow while it types.
   ============================================================ */
.statement {
  padding: clamp(80px, 14vh, 190px) 0 clamp(60px, 10vh, 140px);
}
.statement-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  container-type: inline-size;   /* the heading is sized off this width */
}

/* Hanson Bold, by Finn Hanberg (licence: Creative Commons, credit the
   author). about.css declares the same face; this copy is for the index,
   which does not load about.css. Same file, so it downloads once. */
@font-face {
  font-family: 'Hanson';
  src: url('fonts/hanson-bold.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* The heading (2026-09-18, Aurobind; it was "mirrorless.", in the serif).
   Hanson caps, both lines. Line 1 is 28.8em wide (kerned), so it is set at
   72% of the content width and never wraps; the flipping word is 2.745x
   that size, which makes STORYTELLING, the longest, exactly as wide as
   line 1 (10.49em: split into letters for the flip, it loses its kerning).
   The pair stands about as tall as "mirrorless." did. */
.statement-glue {
  --glue: calc(100cqw * 0.72 / 28.8);
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: 'Hanson', var(--display);
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--paper);
}
.glue-top {
  font-size: var(--glue);
  white-space: nowrap;
}
.glue-flip {
  display: grid;
  justify-items: center;
  margin-top: 0.08em;
  font-size: calc(var(--glue) * 2.745);
  white-space: nowrap;
}
/* initGlueFlip() stacks the four words in one cell and splits each into
   letters. A word waits tipped back under its line, turns up to face you,
   then carries on up and over; each letter turns on an axis just behind
   it, a beat after the one before, so the word rolls in like a drum. */
.glue-word { grid-area: 1 / 1; }
.glue-word i {
  display: inline-block;
  font-style: normal;
  transform-origin: 50% 50% -0.4em;
  backface-visibility: hidden;
  transform: perspective(5em) rotateX(-90deg);
  opacity: 0;
}
.glue-word.is-on i {
  transform: perspective(5em) rotateX(0deg);
  opacity: 1;
  transition:
    transform 0.8s cubic-bezier(0.76, 0, 0.24, 1) calc(var(--i) * 35ms),
    opacity 0.25s linear calc(var(--i) * 35ms + 0.12s);
}
.glue-word.is-off i {
  transform: perspective(5em) rotateX(90deg);
  opacity: 0;
  transition:
    transform 0.8s cubic-bezier(0.76, 0, 0.24, 1) calc(var(--i) * 35ms),
    opacity 0.25s linear calc(var(--i) * 35ms + 0.42s);
}
/* back under its line for next time, without turning on the way */
.glue-word.is-reset i { transition: none; }

/* a phone cannot hold line 1 on one line at a readable size, so there it
   wraps (balanced) and the word is sized to the width instead */
@media (max-width: 699px) {
  .glue-top {
    font-size: clamp(15px, 5.6cqw, 22px);
    line-height: 1.15;
    white-space: normal;
    text-wrap: balance;
  }
  .glue-flip { font-size: calc(100cqw / 10.49); }
}
/* reduced motion: the words still change, but by a plain fade */
@media (prefers-reduced-motion: reduce) {
  .glue-word i { transform: none !important; transition: opacity 0.4s linear !important; }
}
.statement-body {
  max-width: 30em;
  margin-top: clamp(22px, 3.6vh, 44px);
  font-size: clamp(19px, 1.9vw, 30px);
  line-height: 1.34;
  letter-spacing: -0.022em;
  text-wrap: balance;
}
.statement-body p + p { margin-top: 0.8em; color: var(--dim); }
.statement-facts {
  margin-top: clamp(26px, 4vh, 46px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

/* pinned: the section is two screens tall and its content holds
   the middle of the first while the scroll types it */
.statement.is-typed {
  height: 200svh;
  padding: 0;
}
.statement.is-typed .statement-pin {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  align-items: center;
}

/* the typewriter. Words never break inside; a character it has not
   reached yet keeps its space but shows nothing. Transparent rather
   than opacity 0, so the caret can still hang off the first one. */
.tw-w { white-space: nowrap; }
.statement.is-typed .tw-c:not(.on) { color: transparent; }
.tw-c.is-cur,
.tw-c.is-pre { position: relative; }
/* the same red bar the hero line types with, a touch heavier at this size */
.tw-c.is-cur::after,
.tw-c.is-pre::before {
  content: '';
  position: absolute;
  top: 0.12em;
  bottom: 0.04em;
  width: 0.085em;
  background: var(--red);
  transition: opacity 0.5s var(--ease);
}
.tw-c.is-cur::after { left: calc(100% + 0.06em); }
.tw-c.is-pre::before { right: calc(100% + 0.06em); }
/* solid while it types, blinking while it waits for more scroll */
.statement.is-idle .tw-c.is-cur::after,
.statement.is-idle .tw-c.is-pre::before { animation: tw-blink 0.9s steps(1, end) infinite; }
/* finished and left alone for a beat, it bows out like the hero's */
.statement.is-rest .tw-c.is-cur::after { opacity: 0; animation: none; }

/* the tags land once the last character has */
.statement.is-typed .statement-facts {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.8s var(--ease);
}
.statement.is-done .statement-facts { opacity: 1; transform: none; }
.tag {
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 7px 16px;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.tag:hover { border-color: var(--paper); color: var(--paper); }
@media (max-width: 900px) { .statement-grid { grid-template-columns: 1fr; } }

/* ============================================================
   Selected work
   ============================================================ */
/* 2026-09-18 (Aurobind): the space above and below the work was too much.
   Above is closed from site.js (initStatementType pulls this section up
   into the statement's empty lower half), which is also why it sits on
   its own layer here; below, the section no longer adds its own padding
   on top of Featured's. */
.work {
  position: relative;
  z-index: 1;
  /* 2026-09-25 (Aurobind): the heading sat right under the wordmark, no
     gap at all; this gives "Selected Work" its own air */
  padding-top: clamp(72px, 12vh, 150px);
  padding-bottom: clamp(16px, 3vh, 36px);
}
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  padding-bottom: clamp(24px, 4vw, 48px);
  border-bottom: 1px solid var(--hairline);
  margin-bottom: clamp(32px, 5vw, 64px);
}
.section-head h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 64px);
  line-height: 0.86;
  letter-spacing: -0.05em;
}
.on-light .section-head { border-color: var(--hairline-ink); }

/* 2026-09-18: the four cards are work.html's card now — same box, same
   hover (Aurobind). These rules are copied from assets/work.css, which
   was measured off the Figma preview; the three bands step the size the
   same way that page does (<800, >=800, >=1280). Change one, change the
   other, or the two pages drift apart. The caption colours are written
   out rather than var(--paper), because that token goes dark when the
   footer lights the page and these sit on photographs. */
.w-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}
.w-card {
  position: relative;
  height: 460px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--ink-2);
}
/* the route link covers the card, so the caption stays a plate */
.w-card-hit {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: block;
  border-radius: 8px;
}
.w-card-hit:focus-visible { outline: 2px solid #F6F8FB; outline-offset: -4px; }
.w-card-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  max-width: none;          /* the hover zoom has to outgrow the card */
  object-fit: cover;
  transition:
    top 0.3s cubic-bezier(0, 0, 0.58, 1), left 0.3s cubic-bezier(0, 0, 0.58, 1),
    width 0.3s cubic-bezier(0, 0, 0.58, 1), height 0.3s cubic-bezier(0, 0, 0.58, 1);
}
/* hover pushes the image 20px past every edge of the clipped card */
.w-card:hover .w-card-img {
  top: -20px;
  left: -20px;
  width: calc(100% + 40px);
  height: calc(100% + 40px);
}
.w-card-scrim {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 120px;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 100%);
}
.w-card-bar {
  position: absolute;
  left: 10px; right: 10px; bottom: 10px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: flex-start;
  height: 69px;
  padding: 12px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.2);
  -webkit-backdrop-filter: blur(25px);
  backdrop-filter: blur(25px);
}
.w-card-bar h3 {
  width: 100%;
  font-weight: 600;
  font-size: 18px;
  line-height: 19.8px;
  letter-spacing: -0.0648px;
  color: #F6F8FB;
}
.w-card-cat {
  display: flex;
  gap: 4px;
  font-weight: 600;
  font-size: 18px;
  line-height: 25.2px;
  letter-spacing: -0.0648px;
  color: #F6F8FB;
}
@media (min-width: 800px) {
  .w-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .w-card { height: 500px; }
  .w-card-bar { height: 74px; }
  .w-card-bar h3 { font-size: 20px; line-height: 22px; letter-spacing: -0.08px; }
  .w-card-cat { font-size: 20px; line-height: 28px; letter-spacing: -0.08px; }
}
@media (min-width: 1280px) {
  .w-card { height: 700px; }
  .w-card-bar { height: 80px; }
  .w-card-bar h3 { line-height: 28px; }
}
@media (prefers-reduced-motion: reduce) {
  .w-card-img { transition: none; }
}

/* ============================================================
   Liquid glass — project frames on hover
   ------------------------------------------------------------
   A lens of glass that rides the pointer across a project frame.
   The mask is what makes it a lens and not a sheet: the backdrop
   only blurs and lifts where the pointer is, so the work under it
   is never hidden — it is refracted. Three layers do the work:
   a broad specular wash centred on the pointer, a small hot
   highlight offset up-left of it (a lens catches its light off
   axis), and a bright rim where the glass falls away.

   initLiquidGlass() in site.js writes --gx/--gy in px and eases
   them toward the pointer, so the lens lags a beat and settles —
   that lag is the whole reason it reads as liquid rather than as
   a spotlight. Position defaults to the centre, so the effect
   still works on :hover alone if that JS never runs.
   ============================================================ */
.glass {
  --gx: 50%;
  --gy: 50%;
  --lens: 158px;
  position: absolute;
  inset: 0;
  border-radius: inherit;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.38s var(--ease);
  -webkit-backdrop-filter: blur(7px) saturate(1.5) brightness(1.12) contrast(1.04);
  backdrop-filter: blur(7px) saturate(1.5) brightness(1.12) contrast(1.04);
  background:
    /* hot spot — a lens catches its light off axis, up and to the left */
    radial-gradient(calc(var(--lens) * 0.30) calc(var(--lens) * 0.30)
      at calc(var(--gx) - 0.20 * var(--lens)) calc(var(--gy) - 0.24 * var(--lens)),
      rgba(255, 255, 255, 0.55) 0%,
      rgba(255, 255, 255, 0.12) 48%,
      rgba(255, 255, 255, 0) 100%),
    /* the body: a slight cool tint so it is glass and not just a bright spot */
    radial-gradient(var(--lens) var(--lens) at var(--gx) var(--gy),
      rgba(226, 238, 255, 0.16) 0%,
      rgba(226, 238, 255, 0.07) 52%,
      rgba(255, 255, 255, 0.02) 74%,
      rgba(255, 255, 255, 0) 92%);
  -webkit-mask-image: radial-gradient(var(--lens) var(--lens) at var(--gx) var(--gy),
      #000 0%, #000 66%, rgba(0, 0, 0, 0.82) 82%, rgba(0, 0, 0, 0) 99%);
  mask-image: radial-gradient(var(--lens) var(--lens) at var(--gx) var(--gy),
      #000 0%, #000 66%, rgba(0, 0, 0, 0.82) 82%, rgba(0, 0, 0, 0) 99%);
}

/* The rim is its own layer because it needs a different backdrop-filter from
   the body — heavier blur and a hard brightness lift is what makes the edge
   of a piece of glass visible. Masked to a ring so only that edge gets it;
   the bright arc sits top-left and the dark one bottom-right, which is how a
   convex edge reads under a single light. */
.glass::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  -webkit-backdrop-filter: blur(13px) brightness(1.3) saturate(1.25);
  backdrop-filter: blur(13px) brightness(1.3) saturate(1.25);
  background:
    linear-gradient(140deg,
      rgba(255, 255, 255, 0.5) 0%,
      rgba(255, 255, 255, 0.06) 42%,
      rgba(10, 10, 12, 0.16) 74%,
      rgba(10, 10, 12, 0.3) 100%);
  -webkit-mask-image: radial-gradient(var(--lens) var(--lens) at var(--gx) var(--gy),
      rgba(0, 0, 0, 0) 62%, #000 80%, #000 88%, rgba(0, 0, 0, 0) 99%);
  mask-image: radial-gradient(var(--lens) var(--lens) at var(--gx) var(--gy),
      rgba(0, 0, 0, 0) 62%, #000 80%, #000 88%, rgba(0, 0, 0, 0) 99%);
}

/* the hairline that catches the very edge of the lens */
.glass::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(var(--lens) var(--lens) at var(--gx) var(--gy),
      rgba(255, 255, 255, 0) 76%,
      rgba(255, 255, 255, 0.62) 83%,
      rgba(255, 255, 255, 0) 90%);
  -webkit-mask-image: linear-gradient(145deg, #000 0%, rgba(0, 0, 0, 0.25) 55%, rgba(0, 0, 0, 0.1) 100%);
  mask-image: linear-gradient(145deg, #000 0%, rgba(0, 0, 0, 0.25) 55%, rgba(0, 0, 0, 0.1) 100%);
}
.post-shot:hover > .glass,
.abt-shot:hover > .glass { opacity: 1; }

/* the post frames are about half the width of a work card, and a lens sized
   for those swallows them whole */
.post-shot > .glass { --lens: 124px; }

/* Coarse pointers have no pointer to follow and no hover to speak of, and the
   blur is the most expensive thing on the page — the lens is desktop only. */
@media (hover: none), (pointer: coarse) {
  .glass { display: none; }
}
/* Without backdrop-filter the mask would leave a plain white smear, so the
   layer opts out entirely rather than degrading into something else. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { display: none; }
}
/* 2026-09-24: "View all" is the nav's pill now (pillnav.css .pill-solo),
   centred under the four cards */
.viewall {
  display: flex;
  justify-content: center;
  margin-top: clamp(36px, 5vw, 64px);
}
/* ============================================================
   Featured — the four latest LinkedIn posts
   Replaced the old stock-frame marquee on 2026-09-15. Cards are links
   straight out to the post; the copy inside them is the post's own.
   ============================================================ */
.featured {
  position: relative;
  padding: clamp(52px, 8vh, 100px) 0 clamp(60px, 10vh, 140px);
  background: var(--ink-2);
  border-block: 1px solid var(--hairline);
}
.featured-title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 64px);
  line-height: 0.86;
  letter-spacing: -0.05em;
  color: var(--paper);
}

.posts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
}
.post { display: block; }

.post-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  text-decoration: none;
  color: inherit;
}

/* --- the frame ---
   2026-09-24 (Aurobind): the cards were "too overstimulating" and read like
   paragraphs. They are Medium's cards now: a landscape thumbnail at one
   shape for all four, a byline, a short bold title, one grey line, the
   date. No lens, no "Read" row. */
.post-shot {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 6px;
  overflow: hidden;
  background: var(--ink-3);
}
.post-shot img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 35%;
  display: block;
  transition: transform 0.9s var(--ease), opacity 0.4s var(--ease);
}
.post-play {
  position: absolute;
  left: 50%; top: 50%;
  width: 42px; height: 42px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(15, 14, 14, 0.58);
  border: 1px solid rgba(246, 248, 251, 0.34);
  pointer-events: none;
  z-index: 2;
}
.post-play::after {
  content: '';
  position: absolute;
  left: 52%; top: 50%;
  transform: translate(-50%, -50%);
  border-left: 11px solid var(--paper);
  border-block: 7px solid transparent;
}
.post-card:hover .post-shot img { transform: scale(1.03); }

/* --- the words --- */
.post-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding-top: 16px;
}
.post-by {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  font-size: 13px;
  line-height: 1.2;
  color: var(--dim);
}
.post-avatar {
  width: 20px; height: 20px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}
.post-title {
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  font-size: clamp(19px, 1.45vw, 23px);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--paper);
  text-wrap: balance;
}
.post-card:hover .post-title { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.16em; }
.post-sub {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  margin: 6px 0 0;
  font-size: 15px;
  line-height: 1.4;
  color: var(--smoke);
}
.post-meta {
  display: flex;
  gap: 6px;
  margin: auto 0 0;
  padding-top: 14px;
  font-size: 13px;
  color: var(--smoke);
}

.post-card:focus-visible { outline: 2px solid var(--paper); outline-offset: 6px; border-radius: 6px; }

@media (max-width: 1100px) {
  .posts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(28px, 4vw, 40px) clamp(20px, 3.4vw, 34px); }
}
@media (max-width: 560px) {
  .posts { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .post-shot img { transition: none; }
}

/* ============================================================
   SERVICES — scroll-driven video bleed
   ============================================================ */
.bleed {
  position: relative;
  background: var(--ink);
}
.bleed-stage { height: 340vh; }

.bleed-sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  --p: 0;      /* 0 → 1 scroll progress, set from JS */
  --open: 0;   /* frame expansion   */
  --flood: 0;  /* red flood         */
  --lift: 0;   /* services list lift */
}

.bleed-frame {
  position: relative;
  width: calc((58 + 42 * var(--open)) * 1vw);
  height: calc((56 + 44 * var(--open)) * 1svh);
  border-radius: calc(20px * (1 - var(--open)));
  overflow: hidden;
  background: var(--red);
  box-shadow: 0 40px 120px rgba(0, 0, 0, calc(0.55 * (1 - var(--open))));
  will-change: width, height, border-radius;
}
.bleed-frame video {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(calc(1.18 - 0.18 * var(--open)));
  will-change: transform;
}
/* the red that bleeds out of the film and becomes the section */
.bleed-flood {
  position: absolute;
  inset: 0;
  background: var(--red);
  opacity: var(--flood);
  pointer-events: none;
}

.bleed-label {
  position: absolute;
  left: var(--gutter); right: var(--gutter);
  bottom: clamp(24px, 5vh, 64px);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--paper);
  opacity: calc(1 - var(--flood) * 1.6);
  z-index: 3;
  pointer-events: none;
}

/* the huge word riding on top of the film */
.bleed-word {
  position: absolute;
  z-index: 4;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(44px, 11.6vw, 168px);
  line-height: 0.82;
  letter-spacing: -0.05em;
  color: var(--paper);
  text-align: center;
  mix-blend-mode: difference;
  transform: scale(calc(0.86 + 0.34 * var(--open)));
  opacity: calc(1 - var(--flood));
  pointer-events: none;
  will-change: transform, opacity;
}

/* services list rises out of the flood */
.bleed-list {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: var(--lift);
  transform: translateY(calc((1 - var(--lift)) * 46px));
  pointer-events: none;
  will-change: transform, opacity;
}
.bleed-list .wrap { width: 100%; }

.svc {
  display: grid;
  grid-template-columns: 4.5ch minmax(0, 1fr) auto;
  align-items: baseline;
  gap: clamp(12px, 3vw, 40px);
  padding: clamp(9px, 1.5vh, 18px) 0;
  border-top: 1px solid rgba(15, 14, 14, 0.28);
  color: var(--ink);
}
.svc:last-child { border-bottom: 1px solid rgba(15, 14, 14, 0.28); }
.svc .n {
  font-variant-numeric: tabular-nums;
  font-size: clamp(11px, 0.9vw, 14px);
  letter-spacing: 0.1em;
  opacity: 0.62;
}
.svc h3 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(20px, 3.4vw, 50px);
  line-height: 0.92;
  letter-spacing: -0.045em;
}
.svc p {
  font-size: clamp(11px, 0.95vw, 15px);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.86;
  text-align: right;
}

/* label above the list, in place of the word that just faded out */
.bleed-list .head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 20px;
  padding-bottom: clamp(10px, 1.8vh, 22px);
  color: var(--ink);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.72;
}

/* ---- the rows as a flowing menu (2026-09-18) ----------------------
   React Bits <FlowingMenu />, ported — see initServiceFlow() in site.js.
   At rest nothing here shows: each band waits just past the row's edge.
   Under the mouse an ink band comes in from the edge the pointer crossed
   while its contents counter-slide, so the name holds still as the
   window opens; inside, the name and a still repeat and drift left.
   The rows only take the mouse once the list has risen out of the red
   (.is-listed, from initBleed). */
.bleed-sticky.is-listed .has-flow .svc { pointer-events: auto; }
.has-flow .svc { position: relative; overflow: hidden; }
.svc-flow,
.svc-flow-in {
  /* the original's 0.6s expo out; the cubic is the fallback where
     linear() is not supported */
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  transition-timing-function: linear(0, 0.159 2.5%, 0.293 5%, 0.405 7.5%, 0.5 10%,
    0.646 15%, 0.75 20%, 0.823 25%, 0.875 30%, 0.912 35%, 0.938 40%, 0.969 50%,
    0.984 60%, 0.992 70%, 0.996 80%, 1);
}
.svc-flow {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #0F0E0E;            /* written out: --ink goes light near the footer */
  transform: translate3d(0, 101%, 0);
  pointer-events: none;
}
.svc-flow-in {
  height: 100%;
  overflow: hidden;
  transform: translate3d(0, -101%, 0);
}
/* initServiceFlow() writes --k (stills in a run) and --n (parts in the
   track): moving the track by k parts brings the same picture back into
   the same place, so the loop point never shows */
.svc-flow-track {
  display: flex;
  align-items: center;
  width: max-content;
  height: 100%;
  animation: svc-flow 45s linear infinite paused;
}
.svc.is-flow .svc-flow-track { animation-play-state: running; }
@keyframes svc-flow {
  to { transform: translate3d(calc(-100% * var(--k, 1) / var(--n, 4)), 0, 0); }
}
.svc-flow-part {
  display: flex;
  flex: none;
  align-items: center;
  height: 100%;
}
.svc-flow-part span {
  padding: 0 0.28em;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(20px, 3.4vw, 50px);   /* the row's own h3 */
  line-height: 1;
  letter-spacing: -0.045em;
  white-space: nowrap;
  color: #F6F8FB;
}
.svc-flow-part i {
  flex: none;
  height: 78%;
  aspect-ratio: 3;
  margin: 0 clamp(10px, 1.6vw, 28px);
  border-radius: 999px;
  background: #1E1D1D center / cover no-repeat;
}
@media (max-width: 760px) {
  .svc { grid-template-columns: 3.5ch minmax(0, 1fr); }
  .svc p { display: none; }
  /* a portrait card would crop the 16:9 film to a slot — start it wide and short */
  .bleed-frame {
    width: calc((88 + 12 * var(--open)) * 1vw);
    height: calc((32 + 68 * var(--open)) * 1svh);
  }
  .bleed-label { font-size: 11px; letter-spacing: 0.12em; }
  /* 2026-09-26 mobile sweep: the names were 20px in a red screen with room
     to spare; a size up gives the rows (and the band that walks them on a
     phone) some weight */
  .svc { padding: clamp(11px, 1.7vh, 16px) 0; }
  .svc h3,
  .svc-flow-part span { font-size: clamp(22px, 6.4vw, 30px); }
  .svc .n { font-size: 12px; }
}

/* .services-tail, the red block the bleed handed off to ("Eight boxes…"),
   was removed 2026-09-19 with its markup; the section ends on the list.
   Old rules: assets/site.css.bak-0919-cta. */

@media (prefers-reduced-motion: reduce) {
  .bleed-stage { height: auto; }
  .bleed-sticky { position: static; height: auto; padding: 60px 0; --open: 1; --flood: 0; --lift: 1; }
  .bleed-frame { width: 100%; height: 60svh; border-radius: 0; }
  .bleed-list { position: static; opacity: 1; transform: none; padding-top: 40px; }
  .bleed-word { position: static; mix-blend-mode: normal; transform: none; padding-bottom: 24px; }
  .bleed-list .svc { color: var(--paper); border-color: var(--hairline); }
  .bleed-list .head { color: var(--paper); }
  .bleed-label { display: none; }   /* nothing to caption once the film is static */
}

/* ============================================================
   Contact CTA
   ============================================================ */
.cta {
  position: relative;
  padding: clamp(80px, 15vh, 200px) 0;
  background: var(--ink);
  overflow: hidden;
}
.cta-inner { text-align: center; }
.cta h2 {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(38px, 7vw, 96px);
  line-height: 0.92;
  letter-spacing: -0.016em;
}
.cta p { color: var(--dim); margin-top: 18px; }
/* 2026-09-19: was .cta-mail (the email); now opens WhatsApp. Same type and
   hover underline, with the WhatsApp mark in front. */
.cta-link {
  display: inline-block;
  margin-top: clamp(28px, 4vw, 46px);
  font-family: var(--display);
  font-size: clamp(20px, 3.6vw, 56px);
  letter-spacing: -0.05em;
  line-height: 1;
  padding-bottom: 0.1em;
  background-image: linear-gradient(var(--accent, var(--yellow)), var(--accent, var(--yellow)));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 3px;
  transition: background-size 0.55s var(--ease), color 0.4s var(--ease);
}
.cta-link:hover { background-size: 100% 3px; color: var(--accent, var(--yellow)); }
.cta-link__icon {
  display: inline-block;
  width: 0.72em; height: 0.72em;
  margin-right: 0.28em;
  vertical-align: -0.04em;
}
/* 2026-09-26 (Aurobind): on a phone the CTA sat under ~210px of empty dark
   (the statement's tail plus 126px of its own padding) and the block did not
   read as centred in its space. Less above, the same below, so it sits in
   the middle between the tags and the footer. */
@media (max-width: 760px) {
  .cta { padding: 48px 0 72px; }
  .cta-inner { display: flex; flex-direction: column; align-items: center; }
  .cta p { max-width: 30ch; }
}
/* on a phone the line is ~22px tall; widen what a thumb can hit, not the layout */
@media (pointer: coarse) {
  .cta-link { position: relative; }
  .cta-link::after { content: ''; position: absolute; inset: -12px -8px; }
  /* the same for Featured's 17px "All writing" (2026-09-26 mobile sweep) */
  .featured-all { position: relative; }
  .featured-all::after { content: ''; position: absolute; inset: -12px -10px; }
}

/* ============================================================
   Footer
   ============================================================ */
.foot {
  border-top: 1px solid var(--hairline);
  padding: clamp(44px, 7vh, 90px) 0 clamp(26px, 4vh, 44px);
  background: var(--ink);
}
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 5vw, 70px);
  align-items: start;
}
.foot-mark {
  font-family: var(--display);
  font-size: clamp(34px, 6vw, 84px);
  line-height: 0.82;
  letter-spacing: -0.05em;
}
/* both lines walk to red with --mark; the second stays a shade back so the
   two-tone lockup survives the transition */
.foot-mark span { display: block; color: var(--mark); opacity: 0.62; }
.foot h4 {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--smoke);
  margin-bottom: 18px;
}
.foot li + li { margin-top: 9px; }
/* a thumb needs more than the 19px line (2026-09-18 mobile pass): the
   padding makes each link ~36px tall and the margin gives the space back */
@media (pointer: coarse) {
  .foot li a { display: inline-block; padding: 8px 0; }
  .foot li + li { margin-top: -7px; }
}
/* 2026-09-26 (Aurobind): every footer, dark or light, on every page: the
   link turns brand red on hover and that is all. It used to take the page's
   accent (yellow, green or red depending on the page and the scroll) and
   slide right behind a → that sat too close to the word. */
.foot li a {
  position: relative;
  transition: color var(--fast) var(--ease);
}
.foot li a:hover { color: var(--red); }
.foot-base {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: clamp(40px, 6vw, 80px);
  padding-top: 20px;
  border-top: 1px solid var(--hairline);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--smoke);
}
@media (max-width: 760px) { .foot-grid { grid-template-columns: 1fr 1fr; } .foot-mark { grid-column: 1 / -1; } }

/* ============================================================
   Reveal on scroll
   ============================================================ */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.95s var(--ease), transform 0.95s var(--ease);
  transition-delay: var(--d, 0s);
}
.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; transition: none; } }

/* ============================================================
   Placeholder pages (Work / About / Fun)
   ============================================================ */
.stub {
  min-height: 78svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(70px, 12vh, 150px) 0;
}
.stub-kicker {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--smoke);
  margin-bottom: clamp(20px, 3vw, 34px);
}
.stub-kicker::before {
  content: '';
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--accent, var(--red));
  animation: pulse 2.2s var(--ease-io) infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.7); } }
.stub h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(56px, 17vw, 260px);
  line-height: 0.8;
  letter-spacing: -0.06em;
  color: var(--paper);
}
.stub h1 i { display: inline-block; font-style: normal; }
.stub-lede {
  margin-top: clamp(26px, 4vw, 48px);
  max-width: 44ch;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(24px, 3.4vw, 46px);
  line-height: 0.98;
  letter-spacing: -0.016em;
  color: var(--paper);
}
.stub-note { margin-top: 22px; max-width: 52ch; color: var(--dim); }
.stub-back {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: clamp(34px, 5vw, 60px);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 5px;
  transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease), gap var(--fast) var(--ease);
}
.stub-back:hover { color: var(--accent, var(--red)); border-color: currentColor; gap: 16px; }
.stub-strip {
  display: flex;
  gap: 8px;
  margin-top: clamp(40px, 6vw, 76px);
  flex-wrap: wrap;
}
.stub-strip span {
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 7px 16px;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--smoke);
}
