/* ==========================================================================
   Ankith Reddy - title card
   A press masthead going to print, shot like a film title. Newsprint grain,
   gold on ink blue, restless cutting. Not a dark-tech founder hero.

   Everything that moves here moves on `opacity` or `transform`. The name is
   cut into four letter groups, each carrying its own picture, each with two
   stacked buffers. The incoming image is painted onto a group's hidden buffer
   one cut ahead, so the cut itself is a compositor flip with no paint behind
   it. Nothing animates a layout property.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Neutrals, every one tinted toward the ink blue. Never a pure black.
     Lightness values are unchanged from the previous palette, so every
     contrast relationship on the page survives the recolour; only the hue
     moves. */
  --ink-0: oklch(18%   0.045 262);   /* #071125, the ground */
  --ink-1: oklch(22.5% 0.048 262);

  /* The neutrals are WARM, not blue. A cool ground with cool greys on it reads
     flat and clinical, which is what the first pass at this palette looked
     like. Biasing every grey a little toward the gold instead gives the page a
     warm/cool tension it did not have: cool ground, warm type, gold accent. */
  --ink-2: oklch(64%   0.014 75);    /* #918B83, 5.6:1 on the ground */
  --ink-3: oklch(80%   0.010 80);    /* #C1BDB7 */
  --paper: oklch(94%   0.008 85);    /* #EEEBE5 */

  /* Gold, #D4A24A, with a lighter #E3BC64 for anything that glows. It clears
     8.08:1 on the ground, so highlight words pass AA comfortably rather than
     scraping it, which the old red never did. */
  --accent:     oklch(74.2% 0.120 79);
  --accent-hot: oklch(81.2% 0.116 86);

  /* Motion. Exponential deceleration only, no bounce anywhere. */
  --e-out:   cubic-bezier(0.16, 1, 0.3, 1);
  --e-quart: cubic-bezier(0.25, 1, 0.5, 1);

  /* Two families, each with a job. Archivo is the name and the furniture:
     heavy, wide, structural. Bebas Neue is the voice: condensed caps, which
     puts the page in the same register as the wordmark. */
  --font-name:  Archivo, system-ui, sans-serif;
  --font-voice: 'Bebas Neue', Impact, 'Arial Narrow', sans-serif;

  --gutter: clamp(1.15rem, 5vw, 4.5rem);
  --nav-h: 4.4rem;
}

*, *::before, *::after { box-sizing: border-box; }

html { background: var(--ink-0); }

body {
  margin: 0;
  /* Not one flat field. A very shallow lift toward the top gives the page a
     sense of light coming from somewhere, which a single solid colour never
     has, and it costs one gradient. */
  background:
    linear-gradient(oklch(22% 0.050 262) 0%, var(--ink-0) 42%, var(--ink-0) 100%)
    no-repeat fixed;
  background-color: var(--ink-0);
  color: var(--ink-3);
  font-family: var(--font-name);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

/* While the card is running, the page underneath is not merely invisible,
   it is inert: no scroll, no tab targets reachable behind the overlay. */
.intro-pending body { overflow: hidden; }
.intro-pending .nav,
.intro-pending main { opacity: 0; pointer-events: none; }

/* No JS, or the card already played this session: the page is just the page. */
.card { display: none; }
.intro-pending .card { display: block; }

/* --------------------------------------------------------------------------
   2. The card
   -------------------------------------------------------------------------- */

.card {
  position: fixed;
  inset: 0;
  z-index: 100;
  overflow: hidden;
  /* Isolate the whole overlay so its blurs and blends never cost the page. */
  contain: strict;
}

/* The backdrop is its own layer so it can dissolve during the hand-off while
   the wordmark stays solid and keeps flying. */
.card__bg {
  position: absolute;
  inset: 0;
  background: var(--ink-0);
  will-change: opacity;
}

/* Newsprint. A static dot screen that drifts on a transform, which buys a
   live shimmer without repainting a single pixel of the pattern. */
.card__grain {
  position: absolute;
  inset: -6%;
  background-image: radial-gradient(oklch(100% 0 0 / 0.20) 0.7px, transparent 0.9px);
  background-size: 3px 3px;
  mix-blend-mode: screen;
  opacity: 0.07;
  will-change: transform, opacity;
  animation: drift 5.5s steps(6, end) infinite;
}
@keyframes drift {
  0%   { transform: translate3d(0, 0, 0); }
  20%  { transform: translate3d(-1px, 1px, 0); }
  40%  { transform: translate3d(1px, -1px, 0); }
  60%  { transform: translate3d(-1px, -1px, 0); }
  80%  { transform: translate3d(1px, 1px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}
.card.is-surge .card__grain { opacity: 0.22; }

/* Decode progress, as a hairline across the foot of the frame. It reports the
   real figure rather than performing one, and it sits well clear of the name,
   where an earlier sweeping version read as a scratch. */
.card__bar {
  position: absolute;
  inset-inline: var(--gutter);
  bottom: max(1.6rem, env(safe-area-inset-bottom));
  height: 1px;
  background: oklch(64% 0.014 75 / 0.24);
  opacity: 0;
  transition: opacity 300ms var(--e-quart);
}
.card.is-boot .card__bar { opacity: 1; }

.card__fill {
  transform-origin: 0 50%;
  transform: scaleX(0);
  height: 100%;
  background: var(--accent);
  box-shadow: 0 0 22px 1px oklch(74.2% 0.120 79 / 0.55);
  will-change: transform;
  transition: transform 320ms var(--e-out);
}

.card__center {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  will-change: transform;
}

.card__stage {
  position: relative;
  /* Wide enough to hold the name at its largest. The cap here and the font-size
     cap below are a matched pair: the full line measures 9.13em, so the biggest
     font (11rem = 176px) needs 1607px of room. Cap the stage lower than that and
     the wordmark overflows its own box and wraps. */
  width: min(93vw, 1680px);
}


/* --------------------------------------------------------------------------
   3. The wordmark
   -------------------------------------------------------------------------- */

.wm {
  position: relative;
  margin: 0;
  /* Shrink-wrapped to the glyphs. Two things depend on this: every montage
     layer gets an identical box so one photograph maps the same way through
     all of them, and the hand-off can measure the wordmark itself rather
     than the stage it sits in. */
  display: grid;
  width: max-content;
  max-width: 100%;
  margin-inline: auto;
  /* Archivo's width axis pushed wide: fatter letterforms mean more image
     area visible through them, which is the whole point of the montage.
     Sizes are set against the measured advance width of the real font,
     4.72em for ANKITH and 9.13em for the full line, not guessed. */
  font-size: clamp(2.4rem, 16.5vw, 9rem);
  font-weight: 900;
  font-stretch: 118%;
  line-height: 0.84;
  letter-spacing: -0.018em;
  text-transform: uppercase;
  text-align: center;
}

/* Two lines on phones so each word stays huge; one line from 760px up.
   `nowrap` matters more than it looks: the letter groups are separate
   inline-grid boxes, so without it a line can break between RED and DY and
   split the surname down the middle. If anything ever does overflow, it now
   fails as a whole word rather than as a broken one. */
.wm i,
.nav__brand i { display: block; font-style: normal; white-space: nowrap; }

@media (min-width: 760px) {
  /* 9.4vw keeps 9.13em inside 93vw; 11rem keeps it inside the 1680px stage. */
  .wm { font-size: clamp(3rem, 9.4vw, 11rem); }
  .wm i { display: inline; }
  .wm i + i::before { content: " "; }
}

/* Every stacked copy occupies the same cell, so they overlay exactly.
   These must be `block`, never `inherit`: a grid container would blockify
   the two words and force them onto separate lines at every width. */
.wm__glow,
.wm__row {
  grid-area: 1 / 1;
  display: block;
}

/* The breathing halo: a blurred duplicate behind the ink. The blur is painted
   once and cached; only its opacity ever changes. */
.wm__glow {
  color: var(--accent-hot);
  /* Kept tight. Blur much past this and the letterforms merge into a single
     rounded slab, which reads as a box behind the name rather than heat
     coming off it. */
  filter: blur(clamp(7px, 1.5vw, 20px));
  opacity: 0;
  will-change: opacity;
}
.card.is-boot .wm__glow,
.card.is-hold .wm__glow { animation: breathe 2.6s ease-in-out infinite; }
@keyframes breathe {
  0%, 100% { opacity: 0.34; }
  50%      { opacity: 0.72; }
}

/* A letter group: two or three characters carrying one picture. Because each
   group only has to fill a box a few hundred pixels wide, portraits that were
   far too small to span the whole name read perfectly well inside one. */
.grp {
  display: inline-grid;
  vertical-align: baseline;
}
.grp em {
  grid-area: 1 / 1;
  font-style: normal;
}

/* The red letters. Always present underneath; a picture layer at full opacity
   covers them completely, so no extra bookkeeping is needed to hide them. */
.grp em:first-child { color: var(--accent); }

/* The two picture buffers. Only ever opacity is animated: the incoming image
   is painted onto the hidden buffer one cut ahead, so the cut itself is a
   compositor flip with no paint attached to it. */
.grp em.a,
.grp em.b {
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  opacity: 0;
  will-change: opacity;
}

/* The lock used to snap tracking tighter and, on phones, collapse two lines
   into one. Both were layout changes, and both needed a full-screen white
   flash to hide them, which is what read as the screen flickering. The
   wordmark now holds exactly one shape from boot through to the nav, so there
   is nothing to hide and the flash could go. */

/* --------------------------------------------------------------------------
   4. Card furniture
   -------------------------------------------------------------------------- */

.card__skip {
  position: absolute;
  bottom: max(1.5rem, env(safe-area-inset-bottom));
  margin: 0;
  font-size: 0.66rem;
  font-weight: 600;
  font-stretch: 92%;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-2);
  transition: opacity 260ms var(--e-quart);
}
.card__skip {
  right: var(--gutter);
  padding: 0.5rem 0;
  background: none;
  border: 0;
  border-bottom: 1px solid oklch(64% 0.014 75 / 0.42);
  font-family: inherit;
  cursor: pointer;
}
.card__skip:hover,
.card__skip:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }

/* Decode is done by the first cut, so the progress hairline retires with it.
   There is no numeric readout: a percentage counting to 100 was the one piece
   of furniture on screen that had nothing to do with the name. */
.card.is-run .card__bar { opacity: 0; }
.card.is-lock .card__skip { opacity: 0; pointer-events: none; }

/* --------------------------------------------------------------------------
   5. The page
   -------------------------------------------------------------------------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  height: var(--nav-h);
  padding-inline: var(--gutter);
  background: oklch(18% 0.045 262 / 0.88);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid oklch(100% 0 0 / 0.07);
  transition: opacity 500ms var(--e-quart);
}

.nav__brand {
  font-size: clamp(0.95rem, 2.6vw, 1.22rem);
  font-weight: 900;
  font-stretch: 118%;
  line-height: 0.84;
  letter-spacing: -0.018em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--accent);
  white-space: nowrap;
}
.nav__brand i { display: block; }
@media (min-width: 760px) {
  .nav__brand i { display: inline; }
  .nav__brand i + i::before { content: " "; }
}

/* Held empty until the card finishes flying the wordmark into this slot. */
.intro-pending .nav__brand { visibility: hidden; }

.nav__links {
  display: flex;
  gap: clamp(1rem, 3vw, 2.4rem);
  /* A flex item will not shrink below its content by default, and three
     uppercase words with open tracking are wider than a small phone. Without
     this the nav pushes the document wider than the viewport and the whole
     page scrolls sideways. */
  min-width: 0;
}
.nav__links a {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-2);
  white-space: nowrap;
  transition: color 200ms var(--e-quart);
}
.nav__links a:hover { color: var(--paper); }

/* --- The phone menu -------------------------------------------------------

   Three labels in open uppercase tracking are simply wider than a phone once
   the brand has taken its half of the bar. Shrinking them to fit was the old
   approach and it only bought a few pixels before the type stopped being
   legible, so below 560px the links collapse behind a button instead.

   560px is the boundary because the widest phone in portrait is 430px and the
   narrowest thing above this line is a small tablet, which has room to spare.

   The panel is `absolute`, not `fixed`. `.nav` carries a backdrop-filter, and
   a filter or backdrop-filter makes an element the containing block for fixed
   descendants — so `fixed` here would anchor to the nav anyway, just with the
   viewport's scroll behaviour lost. Absolute against the sticky bar is honest
   about what it actually does. */

.nav__toggle { display: none; }

@media (max-width: 560px) {

  .nav__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 44px is the smallest target a thumb hits reliably. The bars inside are
       far smaller; the button is padded out to meet it. */
    width: 44px;
    height: 44px;
    margin-right: -0.5rem;      /* optical: pulls the bars level with the gutter */
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .nav__toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
  }

  .nav__burger {
    position: relative;
    display: block;
    width: 21px;
    height: 14px;
  }
  .nav__burger i {
    position: absolute;
    left: 0;
    width: 100%;
    height: 1.5px;
    background: currentColor;
    border-radius: 1px;
    transition: transform 320ms var(--e-quart), opacity 180ms var(--e-quart);
  }
  .nav__burger i:nth-child(1) { top: 0; }
  .nav__burger i:nth-child(2) { top: 50%; margin-top: -0.75px; }
  .nav__burger i:nth-child(3) { bottom: 0; }

  /* Open: the outer bars meet in the middle and cross, the middle one leaves.
     Rotating about the centre is why they have to translate first. */
  .nav__toggle[aria-expanded="true"] .nav__burger i:nth-child(1) {
    transform: translateY(6.25px) rotate(45deg);
  }
  .nav__toggle[aria-expanded="true"] .nav__burger i:nth-child(2) {
    opacity: 0;
    transform: scaleX(0.2);
  }
  .nav__toggle[aria-expanded="true"] .nav__burger i:nth-child(3) {
    transform: translateY(-6.25px) rotate(-45deg);
  }

  .nav__links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    padding: 0.4rem var(--gutter) 1rem;
    /* Opaque, unlike the bar above it. The bar is translucent because the
       page scrolls under it and that motion is worth seeing; the panel is a
       surface laid on top, and it can be opened over any section on the page
       including the bright ones. At 97% the hero read faintly through the
       labels, which is a blemish with nothing bought for it. */
    background: var(--ink-0);
    border-bottom: 1px solid oklch(100% 0 0 / 0.07);
    box-shadow: 0 18px 40px oklch(0% 0 0 / 0.45);

    /* Shut. `visibility` is what takes the links out of the tab order, which
       `opacity: 0` alone would not do — a hidden menu you can still tab into
       is the usual bug here. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    pointer-events: none;
    transition: opacity 260ms var(--e-quart),
                transform 320ms var(--e-quart),
                visibility 0s linear 320ms;
  }
  .nav__links.is-open {
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    transition: opacity 220ms var(--e-quart),
                transform 320ms var(--e-quart),
                visibility 0s;
  }

  /* Full-width rows, so the target is the whole line rather than the word. */
  .nav__links a {
    display: block;
    padding: 0.85rem 0;
    font-size: 0.78rem;
    letter-spacing: 0.16em;
    border-bottom: 1px solid oklch(100% 0 0 / 0.06);
  }
  .nav__links a:last-child { border-bottom: 0; }
}

main { transition: opacity 700ms var(--e-quart) 120ms; }

/* --------------------------------------------------------------------------
   6. Hero
   The portrait is the still point. It fades up once and then never moves:
   no hover transform, no parallax, no scroll coupling. Every bit of motion in
   this section belongs to the things arriving around it.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  /* Own stacking context, so the beam can sit behind the content without
     falling through to behind the page itself. */
  isolation: isolate;
  min-height: calc(100svh - var(--nav-h));
  display: grid;
  align-content: center;
  justify-items: center;
  gap: clamp(1.6rem, 4.5vh, 3.4rem);
  padding: clamp(2.4rem, 5vh, 4rem) clamp(1rem, 3vw, 2.5rem) clamp(2.5rem, 6vh, 4.5rem);
}

.hero__kicker {
  margin: 0;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Newsprint, the same dot screen the title card uses, so the two halves of the
   page share a surface. Static pattern, drifted on a transform: the texture is
   painted once and never recomputed. */
/* The shader sits at the very back of the hero. It replaced two blurred beams:
   with a moving field behind the portrait, another two sweeps crossing it was
   simply too much happening at once. */
.hero__shader {
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1200ms var(--e-quart);
}
.hero__shader.is-on { opacity: 1; }
.hero__shader canvas { display: block; width: 100%; height: 100%; }

.hero__grain {
  position: absolute;
  inset: -5%;
  z-index: -1;
  pointer-events: none;
  background-image: radial-gradient(oklch(100% 0 0 / 0.18) 0.6px, transparent 0.8px);
  background-size: 3px 3px;
  /* No blend mode. Screening this over the vignette that sits above it turned
     the entire hero white: the two together produced a saturated composite
     that neither did alone. At five per cent opacity a normal composite is
     indistinguishable, so the blend bought nothing and cost the section. */
  opacity: 0.05;
}

/* Seats the composition. Entirely static, no cost, and it stops the beams
   running off a hard edge. */
.hero__vig {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(125% 95% at 50% 46%,
    transparent 40%, oklch(11% 0.040 262 / 0.66) 100%);
}



.hero__grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(1.5rem, 3.5vw, 4rem);
  width: 100%;
  /* Wide. The portrait is small by necessity, so the composition has to be
     carried by the type either side of it rather than by the photograph. */
  max-width: min(96vw, 1680px);
}

.hero__figure { margin: 0; }
/* The rule has to anchor to the photograph, not to the figure: the figure now
   also contains the caption, so a bottom-anchored rule landed underneath it. */
.hero__plate { position: relative; display: block; }
.hero__figure img {
  display: block;
  /* A plate edge. Enough to say the photograph is a placed object rather than
     a hole cut in the page. */
  outline: 1px solid oklch(100% 0 0 / 0.09);
  outline-offset: 0;
  width: min(62vw, 362px);
  height: auto;
  /* The source is 392px wide, so this is close to native. Displayed larger it
     would only advertise how small it is. */
  border-radius: 2px;
}

/* A hairline that opens out from under the portrait. Motion beside the
   photograph rather than on it. */
.hero__rule {
  position: absolute;
  inset-inline: 0;
  bottom: -0.85rem;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
}

/* No character cap: the column decides the measure. The portrait is small, so
   the type either side has to reach out toward the edges or the composition
   collapses into a stamp in the middle of an empty screen. */
.hero__side { display: grid; gap: clamp(1.1rem, 3.6vh, 2.6rem); max-width: none; }
.hero__side--l { justify-self: end; text-align: right; }
.hero__side--r { justify-self: start; text-align: left; }

.hero__side p {
  margin: 0;
  font-size: clamp(1.35rem, 2.75vw, 2.65rem);
  line-height: 1.06;
  letter-spacing: 0.015em;
  color: var(--paper);
  text-wrap: balance;
}

.hero__foot {
  margin: 0;
  max-width: 72ch;
  text-align: center;
  font-size: clamp(1.1rem, 1.95vw, 1.8rem);
  line-height: 1.22;
  letter-spacing: 0.03em;
  color: var(--ink-3);
}
/* Two deliberate lines rather than whatever the measure happens to give. */
.hero__foot span { display: block; }
.hero__foot span + span { margin-top: 0.35em; }

/* Narrower and less blurred on phones: the beam is a big composited layer and
   there is no reason to pay for a 26px blur on a small screen. */
@media (max-width: 760px) {
  .hero__grid { grid-template-columns: 1fr; justify-items: center; gap: clamp(1.6rem, 4vh, 2.4rem); }
  .hero__figure { order: -1; }
  .hero__side { max-width: 32ch; text-align: center; justify-self: center; gap: 1.1rem; }
  .hero__side--l, .hero__side--r { text-align: center; }
}

/* Voice. Scoped to the hero so it cannot reach the card, whose letter groups
   also use <em> for an entirely different purpose. */
.hero__side p,
.hero__foot {
  font-family: var(--font-voice);
  font-weight: 400;
}

/* Bebas Neue ships one weight, and `font-synthesis-weight: none` is set on the
   body, so asking for 700 here would change nothing. The highlight is carried
   by colour alone, which is enough at this size. */
.hero__side p em,
.hero__foot em {
  font-style: normal;
  color: var(--accent);
}

/* --- the arrival ---------------------------------------------------------
   Held back only when JS is present to release them, so a failed script can
   never leave the section blank. Transforms and opacity only. */

.js [data-in] { opacity: 0; will-change: transform, opacity; }
.js [data-in="left"]  { transform: translate3d(-44px, 0, 0); }
.js [data-in="right"] { transform: translate3d(44px, 0, 0); }
.js [data-in="up"]    { transform: translate3d(0, 26px, 0); }
.js [data-in="rule"]  { transform: scaleX(0); }
.js [data-in="fade"]  { transform: none; }        /* the portrait only fades */

.hero-in [data-in] {
  opacity: 1;
  transform: none;
  transition:
    opacity   820ms var(--e-out) calc(var(--d, 0) * 110ms),
    transform 1080ms var(--e-out) calc(var(--d, 0) * 110ms);
}
.hero-in [data-in="fade"] { transition: opacity 1000ms var(--e-quart) calc(var(--d, 0) * 110ms); }

/* Phones bring the side lines up from below with everything else; sliding them
   in sideways on a narrow screen reads as a sideways scroll. */
@media (max-width: 760px) {
  .js [data-in="left"],
  .js [data-in="right"] { transform: translate3d(0, 26px, 0); }
}

.lede {
  max-width: 62rem;
  padding: clamp(3.5rem, 12vh, 9rem) var(--gutter) 6rem;
}
.lede__kicker {
  margin: 0 0 1.6rem;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--accent);
}
.lede__text {
  margin: 0 0 3.5rem;
  max-width: 34ch;
  font-size: clamp(1.55rem, 4.4vw, 2.9rem);
  font-weight: 500;
  font-stretch: 104%;
  line-height: 1.18;
  letter-spacing: -0.02em;
  color: var(--paper);
  text-wrap: balance;
}

.vents { margin: 0 0 3rem; padding: 0; list-style: none; }
.vents li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.2rem;
  padding: 0.95rem 0;
  border-top: 1px solid oklch(100% 0 0 / 0.08);
  font-size: 0.86rem;
  color: var(--ink-2);
}
.vents li:last-child { border-bottom: 1px solid oklch(100% 0 0 / 0.08); }
.vents span {
  min-width: 15ch;
  font-weight: 700;
  font-stretch: 108%;
  color: var(--paper);
}

.lede__note { max-width: 52ch; font-size: 0.86rem; line-height: 1.7; }
.lede__note em { color: var(--ink-3); }
.lede__note a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* --------------------------------------------------------------------------
   6. Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .js [data-in] { opacity: 1 !important; transform: none !important; }
  /* The shader sits at the very back of the hero. It replaced two blurred beams:
   with a moving field behind the portrait, another two sweeps crossing it was
   simply too much happening at once. */
.hero__shader {
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1200ms var(--e-quart);
}
.hero__shader.is-on { opacity: 1; }
.hero__shader canvas { display: block; width: 100%; height: 100%; }

.hero__grain { display: none !important; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .card { display: none !important; }
  .nav, main { opacity: 1 !important; }
  .nav__brand { visibility: visible !important; }
}

/* Visually hidden, but it is the page's real h1. The wordmark in the card is
   presentational and marked aria-hidden. */
.sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   7. Cursor
   A ring that eases behind the pointer plus a dot pinned to it exactly. The
   native cursor is hidden only once JS has confirmed it can put something in
   its place, so a failed script leaves the normal arrow alone.

   Position comes from the `translate` property rather than `transform`, which
   leaves `scale` free to animate the ring open without dragging the centring
   offset around with it. Centring is done with static margins for the same
   reason. Neither is a layout property, so both stay on the compositor.
   -------------------------------------------------------------------------- */

.has-cur,
.has-cur * { cursor: none; }

.cur,
.cur__dot {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 200;
  pointer-events: none;
  opacity: 0;
  will-change: translate;
}
.cur-on .cur,
.cur-on .cur__dot { opacity: 1; }

.cur {
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  border: 1px solid oklch(74.2% 0.120 79 / 0.72);
  border-radius: 50%;
  background: oklch(74.2% 0.120 79 / 0);
  scale: 1;
  transition:
    opacity 220ms var(--e-quart),
    scale 300ms var(--e-out),
    background-color 300ms var(--e-out),
    border-color 300ms var(--e-out);
}

.cur__dot {
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: var(--accent);
  transition: opacity 220ms var(--e-quart), scale 300ms var(--e-out);
  scale: 1;
}

/* Over anything you can act on: the ring opens and fills, the dot shrinks
   away into it. */
.cur-hot .cur {
  scale: 1.6;
  background: oklch(74.2% 0.120 79 / 0.14);
  border-color: oklch(74.2% 0.120 79 / 0.95);
}
.cur-hot .cur__dot { scale: 0.4; }

@media (hover: none), (pointer: coarse) {
  .cur, .cur__dot { display: none; }
}

/* --------------------------------------------------------------------------
   8. Section furniture
   Six sections, six different layout shapes on purpose: an asymmetric split,
   a two-column ledger, a sticky stack, an uneven trio, a single focal block,
   and a dense link row. Nothing here repeats another section's structure.
   -------------------------------------------------------------------------- */

.about, .arc, .beyond, .say { padding-inline: var(--gutter); }
.about, .arc, .beyond { padding-block: clamp(4.5rem, 13vh, 9rem); }

/* Section headings share one voice. Bebas has no lowercase, so these are caps
   by the face rather than by a text-transform. */
.about h2, .beyond h2, .say h2 {
  margin: 0 0 clamp(1.4rem, 3vh, 2.4rem);
  font-family: var(--font-voice);
  font-size: clamp(1.7rem, 4vw, 3.4rem);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: 0.01em;
  color: var(--paper);
}

/* --- About: asymmetric split, portrait smaller than the text it supports --- */

.about {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  align-items: center;
  gap: clamp(2rem, 6vw, 6rem);
  max-width: 82rem;
  margin-inline: auto;
}
.about__plate { margin: 0; justify-self: end; }
.about__plate img {
  display: block;
  width: min(76vw, 34vw);
  min-width: 15rem;
  height: auto;
  outline: 1px solid oklch(100% 0 0 / 0.09);
}
.about__body p {
  margin: 0 0 1.1rem;
  max-width: 54ch;
  font-size: clamp(0.98rem, 1.2vw, 1.12rem);
  line-height: 1.68;
  color: var(--ink-3);
}
.about__lead { font-size: clamp(1.05rem, 1.45vw, 1.3rem) !important; color: var(--paper) !important; }
.about__lead strong { color: var(--accent); font-weight: 600; }

@media (max-width: 860px) {
  .about { grid-template-columns: 1fr; justify-items: center; text-align: left; }
  .about__plate { justify-self: center; }
  .about__plate img { width: min(76vw, 22rem); }
}

/* --- Arc: a spine, not a table.
       The first version put a hairline under all seven rows, which is the
       laziest possible shape for a list and read like a spec sheet. This hangs
       the entries off one continuous rule instead, each stepping a little
       further right than the last, so the section descends rather than stacks.
       No numbering: it is a sequence, and the order already says so. --- */

.arc { max-width: 68rem; margin-inline: auto; }

.arc__list {
  position: relative;
  margin: 0;
  padding: 0 0 0 clamp(1.5rem, 4vw, 3.5rem);
  list-style: none;
}
/* the spine */
.arc__list::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  bottom: 2.2em;
  width: 1px;
  background: linear-gradient(
    oklch(74.2% 0.120 79 / 0.55),
    oklch(74.2% 0.120 79 / 0.28) 55%,
    transparent);
}

.arc__list li {
  position: relative;
  padding: 0 0 clamp(2rem, 5vh, 3.4rem);
  transition: opacity 260ms var(--e-quart);
}
.arc__list li:last-child { padding-bottom: 0; }

/* the tick that joins an entry to the spine */
.arc__list li::before {
  content: "";
  position: absolute;
  left: calc(-1 * clamp(1.5rem, 4vw, 3.5rem));
  top: 0.62em;
  width: clamp(1rem, 2.6vw, 2.2rem);
  height: 1px;
  background: oklch(74.2% 0.120 79 / 0.5);
  transform-origin: left;
  transition: transform 320ms var(--e-out), background-color 320ms var(--e-quart);
}

.arc__k {
  margin: 0 0 0.35rem;
  font-family: var(--font-voice);
  font-size: clamp(1.5rem, 3.4vw, 2.9rem);
  line-height: 1;
  letter-spacing: 0.015em;
  color: var(--paper);
}
.arc__v {
  margin: 0;
  max-width: 48ch;
  font-size: clamp(0.9rem, 1.05vw, 1.02rem);
  line-height: 1.66;
  color: var(--ink-2);
}

/* where the arc has got to */
.arc__now .arc__k { color: var(--accent); }
.arc__now::before { background: var(--accent); width: clamp(1.6rem, 3.6vw, 3rem); }

/* The spine fills as the reader moves down it. Driven off --p, which
   js/scroll.js sets from how many entries have been revealed, so the arc
   literally fills in as you read it. Transform only, so it is free. */
.arc__fill {
  position: absolute;
  left: 0;
  top: 0.6em;
  bottom: 2.2em;
  width: 1px;
  background: var(--accent);
  transform-origin: top;
  transform: scaleY(var(--p, 0));
  transition: transform 900ms var(--e-out);
}

/* Interactive: point at an entry and it steps forward. The whole row responds,
   not just the heading, so the target is generous. */
.arc__list li:hover .arc__k,
.arc__list li:focus-within .arc__k { color: var(--accent); }
.arc__list li:hover .arc__v,
.arc__list li:focus-within .arc__v { color: var(--ink-3); }
.arc__list li:hover::before,
.arc__list li:focus-within::before {
  transform: scaleX(1.7);
  background: var(--accent);
}
.arc__list:hover li:not(:hover) { opacity: 0.55; }
.arc__k, .arc__v { transition: color 260ms var(--e-quart); }

/* --- Companies: five sticky siblings. Each pins at the top of the viewport
       and the next rises over it. Pure CSS; the observer only times the
       contents arriving. --- */

.cos { position: relative; }
.co {
  position: sticky;
  top: 0;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: calc(var(--nav-h) + 2rem) var(--gutter) clamp(3rem, 8vh, 6rem);
  /* Opaque, or the pinned card underneath shows through the one on top. */
  background: var(--ink-0);
  border-top: 1px solid oklch(100% 0 0 / 0.07);
}

.co__in {
  width: 100%;
  max-width: 46rem;
  text-align: center;
}

.co__logo {
  display: block;
  width: auto;
  max-width: min(62vw, 15rem);
  max-height: 6.5rem;
  margin: 0 auto clamp(1.6rem, 4vh, 2.6rem);
  object-fit: contain;
  opacity: 0.92;
}
.co__logo--wide { max-width: min(72vw, 20rem); max-height: 4rem; }

/* Shown in its own colours on the light ground it was drawn for. Inverting it
   turned the photograph inside the badge into a negative. */
.co__logo--plate {
  max-width: min(38vw, 8.5rem);
  max-height: 8.5rem;
  border-radius: 50%;
  opacity: 1;
}

.co h3 {
  margin: 0 0 0.7rem;
  font-family: var(--font-voice);
  font-size: clamp(2.1rem, 6.5vw, 5rem);
  font-weight: 400;
  line-height: 0.98;
  letter-spacing: 0.012em;
  color: var(--paper);
}
.co__line em { font-style: normal; color: var(--paper); }
.co__line {
  margin: 0 0 1.5rem;
  font-family: var(--font-voice);
  font-size: clamp(1.05rem, 2.1vw, 1.8rem);
  line-height: 1.2;
  letter-spacing: 0.02em;
  color: var(--accent);
}
.co__meta {
  margin: 0 auto;
  max-width: 46ch;
  font-size: clamp(0.9rem, 1.05vw, 1.02rem);
  line-height: 1.68;
  color: var(--ink-2);
}
.co__go {
  display: inline-block;
  margin-top: 1.8rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid oklch(74.2% 0.120 79 / 0.5);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-3);
  transition: color 220ms var(--e-quart), border-color 220ms var(--e-quart);
}
.co__go:hover, .co__go:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }

/* The one company with no mark of its own. Rather than leave a hole where a
   logo should be, the name takes the space the logo would have had. */
.co--type h3 { font-size: clamp(2.4rem, 8vw, 6.2rem); }

/* Contents arrive as each company pins. */
.co.co-armed:not(.is-on) .co__in > * { opacity: 0; transform: translate3d(0, 22px, 0); will-change: transform, opacity; }
.co.is-on .co__in > * {
  opacity: 1;
  transform: none;
  transition: opacity 700ms var(--e-out) calc(var(--i, 0) * 90ms),
              transform 900ms var(--e-out) calc(var(--i, 0) * 90ms);
}
.co__in > :nth-child(1) { --i: 0; }
.co__in > :nth-child(2) { --i: 1; }
.co__in > :nth-child(3) { --i: 2; }
.co__in > :nth-child(4) { --i: 3; }
.co__in > :nth-child(5) { --i: 4; }

/* --- Credits: a labelled record.
       The previous version was three unlike blocks on a twelve column grid,
       which left holes and read as trivia because nothing said what any of it
       was. Labelling each line turns the same facts into corroboration, which
       is what this page is for. Two columns, no rule under every row. --- */

.beyond { max-width: 62rem; margin-inline: auto; }

.credits { margin: 0; }
.credits__row {
  display: grid;
  grid-template-columns: minmax(6rem, 11ch) 1fr;
  gap: clamp(0.9rem, 3vw, 2.6rem);
  padding: clamp(1.1rem, 2.6vh, 1.7rem) 0;
}
/* One rule between rows, not a box around every one. */
.credits__row + .credits__row { border-top: 1px solid oklch(100% 0 0 / 0.07); }

.credits dt {
  font-family: var(--font-name);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1.9;
  color: var(--accent);
}
.credits dd {
  margin: 0;
  font-family: var(--font-voice);
  font-size: clamp(1.05rem, 1.9vw, 1.5rem);
  line-height: 1.24;
  letter-spacing: 0.015em;
  color: var(--paper);
}
.credits dd cite { font-style: normal; }
.credits__note {
  display: block;
  margin-top: 0.5rem;
  font-family: var(--font-name);
  font-size: clamp(0.85rem, 1vw, 0.95rem);
  line-height: 1.6;
  letter-spacing: 0;
  color: var(--ink-2);
}
.credits__note a {
  color: var(--ink-3);
  text-underline-offset: 3px;
  text-decoration-color: oklch(74.2% 0.120 79 / 0.5);
}
.credits__note a:hover { color: var(--accent); }

@media (max-width: 600px) {
  .credits__row { grid-template-columns: 1fr; gap: 0.4rem; }
}

/* --- Contact: one focal block, nothing competing with the single action. --- */

.say {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: 0.4rem;
  padding-block: clamp(5rem, 16vh, 11rem);
  max-width: 46rem;
  margin-inline: auto;
}
.say h2 { font-size: clamp(2.2rem, 7vw, 5.4rem); margin-bottom: 0.6rem; }
.say__line {
  margin: 0 0 clamp(1.8rem, 4vh, 2.8rem);
  font-size: clamp(0.95rem, 1.15vw, 1.08rem);
  line-height: 1.6;
  color: var(--ink-2);
}
.say__cta {
  display: inline-block;
  padding: 0.95rem 1.9rem;
  border: 1px solid var(--accent);
  border-radius: 2px;
  background: oklch(74.2% 0.120 79 / 0);
  font-family: var(--font-name);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  color: var(--paper);
  transition: background-color 260ms var(--e-out), transform 180ms var(--e-quart);
}
.say__cta:hover, .say__cta:focus-visible { background: oklch(74.2% 0.120 79 / 0.16); }
.say__cta:active { transform: translateY(1px); }
.say__cta[disabled] { opacity: 0.45; pointer-events: none; }

/* Compact by intent: two short fields side by side, one message, one action.
   Anything longer than this and people stop filling it in. */
.say__form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem clamp(0.8rem, 2vw, 1.2rem);
  width: 100%;
  max-width: 34rem;
  text-align: left;
  margin-bottom: 1.6rem;
}
.field { display: grid; gap: 0.4rem; }
.field--wide { grid-column: 1 / -1; }

/* Label above the input, always. Never a placeholder standing in for one. */
.field label {
  font-family: var(--font-name);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.field input,
.field textarea {
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid oklch(100% 0 0 / 0.16);
  border-radius: 2px;
  background: oklch(100% 0 0 / 0.035);
  font-family: var(--font-name);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--paper);
  transition: border-color 200ms var(--e-quart), background-color 200ms var(--e-quart);
}
.field textarea { resize: vertical; min-height: 5.5rem; }
.field input:hover, .field textarea:hover { border-color: oklch(100% 0 0 / 0.28); }
.field input:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  background: oklch(100% 0 0 / 0.06);
}
.field.is-bad input, .field.is-bad textarea { border-color: var(--accent); }
.field .err {
  font-family: var(--font-name);
  font-size: 0.72rem;
  color: var(--accent);
}

/* Centred under the fields, to sit with the rest of the section rather
   than hanging off the left edge of the form. */
.say__send {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.5rem;
}
.say__status {
  margin: 0;
  font-family: var(--font-name);
  font-size: 0.78rem;
  color: var(--ink-2);
}
.say__status.is-good { color: var(--paper); }

.say__alt {
  margin: 0;
  font-family: var(--font-name);
  font-size: 0.84rem;
  line-height: 1.7;
  color: var(--ink-2);
}
.say__alt a { color: var(--ink-3); text-underline-offset: 3px; }
.say__alt a:hover { color: var(--accent); }

@media (max-width: 520px) {
  .say__form { grid-template-columns: 1fr; }
}

/* --- Footer --- */

.foot {
  display: grid;
  gap: 1.1rem;
  justify-items: center;
  text-align: center;
  padding: clamp(2.5rem, 7vh, 4rem) var(--gutter) clamp(2.5rem, 6vh, 3.5rem);
  border-top: 1px solid oklch(100% 0 0 / 0.08);
}
.foot__name {
  margin: 0;
  font-family: var(--font-name);
  font-size: 0.8rem;
  font-weight: 900;
  font-stretch: 118%;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--accent);
}
.foot__links { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem 1.6rem; }
.foot__links a {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-2);
  transition: color 200ms var(--e-quart);
}
.foot__links a:hover, .foot__links a:focus-visible { color: var(--paper); }
.foot__fine { margin: 0; font-size: 0.74rem; color: oklch(52% 0.012 78); }
.foot__fine a { color: var(--ink-2); text-underline-offset: 3px; }
.foot__fine a:hover { color: var(--accent); }

/* --- Scroll reveal, shared by every section below the hero --- */

[data-r].r-armed { opacity: 0; will-change: transform, opacity; }
[data-r="up"].r-armed   { transform: translate3d(0, 26px, 0); }
[data-r="left"].r-armed { transform: translate3d(-34px, 0, 0); }
[data-r].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 760ms var(--e-out) calc(var(--d, 0) * 90ms),
              transform 950ms var(--e-out) calc(var(--d, 0) * 90ms);
}

/* --- Focus, for anyone driving this from a keyboard --- */

a:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

@media (prefers-reduced-motion: reduce) {
  [data-r], .co__in > * { opacity: 1 !important; transform: none !important; }
  .co { position: static; min-height: 0; padding-block: clamp(3rem, 9vh, 5rem); }
}



@media (prefers-reduced-motion: reduce) {
}

@media (prefers-reduced-motion: reduce) {
  .cur, .cur__dot { display: none !important; }
  .has-cur, .has-cur * { cursor: auto; }
}
