/* ---- hero.css — the homepage's opening screen -----------------------------
   Loaded on the homepage only. --bg and --ink now come from tokens.css, which
   every page loads first; what stays here is hero-specific and used nowhere
   else on the site.

   This used to be a fixed full-screen splash with the page's scrolling switched
   off — fine for a one-screen prototype, useless as the top of a real site. It
   is now the first section of a scrolling page: a tall section containing a
   sticky stage. The stage sits still, filling the screen, while the section
   scrolls past behind it, and that scrolled distance is what the reverse intro
   is scrubbed against (see SCROLL in hero.js). Pinning this way is done by the
   browser, so the hero stays put even while JavaScript is busy. */

:root {
  /* Flattens the wordmark block — scaleY, so the letters get SHORTER and read
     wider. 1 = untouched. Below about 0.6 the pixel grid visibly shears, since
     the glyphs are being scaled off their own pixel pitch.
     (scaleX would make them narrower and therefore look taller, which is the
     opposite of what's wanted here.) */
  --flatten: 0.72;

  /* Permanent chromatic aberration on the wordmark, in em. Pushed hard — the
     glitch flash goes further still. Tagline gets none. */
  --aberration-rest: 0.05em;

  /* How far the red/cyan split is pulled apart at the end of the scrub. The
     wordmark leaves by coming apart into its two colour channels rather than
     by simply fading — the same fault the glitch already flashes, held open. */
  --aberration-exit: 0.5em;

  /* The live value, widening as the hero is scrolled away. .wordmark span reads
     this; the glitch class overrides it with its own bigger offsets. */
  --aberration: calc(
    var(--aberration-rest) + (var(--aberration-exit) - var(--aberration-rest)) * var(--hero-exit)
  );

  /* How far the page scrolls while the hero is pinned — the length of the
     reverse intro, in viewport heights. One screen of scrolling: long enough
     to read the blobs travelling back out, short enough that nobody thinks the
     page is stuck. hero.js does not hardcode this; it measures the element, so
     changing this one number changes the scrub length and nothing else.

     0 would break the effect but not the page: the hero would simply scroll
     away like any other section. */
  --hero-scroll: 100svh;

  /* Set by hero.js, 0 to 1, as the hero is scrolled through: 0 is the hero at
     rest, 1 is fully un-built. Declared here so the styles below have a value
     to work with before any script runs — with JavaScript off it stays 0 and
     the hero is simply a static, fully-composed screen. */
  --hero-exit: 0;
}

/* ---- The section and its pinned stage ------------------------------------
   The section is tall; the stage inside it is one screen and sticks to the top
   of the viewport until the section has finished passing. */

.hero {
  position: relative;
  height: calc(100svh + var(--hero-scroll));

  /* isolate keeps the blobs' blending and stacking to themselves, so nothing
     in the hero can paint over the nav or the sections below it. */
  isolation: isolate;
}

.hero__stage {
  position: sticky;
  top: 0;

  /* svh — the SMALL viewport height, i.e. the screen with the mobile browser's
     address bar showing. 100vh on a phone means the height WITHOUT it, so the
     bottom of the hero starts life hidden behind the browser's own chrome. */
  height: 100svh;

  display: grid;
  place-items: center;

  /* DELIBERATELY TRANSPARENT. This used to be an opaque var(--bg), which was
     right when the hero drew its own dot layer on top of it. The dot field is
     now a fixed canvas behind the whole site, so an opaque stage here would
     mask it and the texture would stop dead at the bottom of the hero — the
     one place the seam would be most obvious. The page colour is on <html>
     anyway, so there is nothing to restate. */

  /* The blobs travel in from off-screen and must not widen the page while they
     are out there. */
  overflow: hidden;
}

/* ---- Layer 1: blobs ----------------------------------------------------- */

/* The blobs are drawn at a fraction of full size (one twelfth — PIXEL.CELL in
   hero.js is the single source of that number) and stretched back up here. This
   one line is what makes them a low-resolution picture rather than a blurry
   one: without it the browser smooths the upscale and every block turns into a
   soft gradient, which is the opposite of the intent. With it, one drawn pixel
   becomes one hard block of one flat colour.

   No width/height: hero.js sets both in whole blocks, so the grid is always an
   exact number of cells and never lands on fractional pixels. */
.hero-blobs {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  image-rendering: pixelated;
  pointer-events: none;
}

/* ---- Layer 2: wordmark -------------------------------------------------- */

.hero-mark {
  position: relative;
  z-index: 2;
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  user-select: none;

  /* The mark leaves faster than the blobs do — gone by the time the scrub is
     ~70% through, so the last part of the scroll is the blobs alone travelling
     back out. Everything overlapping until the end would just read as a fade.
     No transition: the value is driven by scroll position, so it must land on
     exactly the frame the scroll does or it lags behind the finger. */
  opacity: clamp(0, calc(1 - var(--hero-exit) / 0.7), 1);

  /* A small lift as it goes, so the mark reads as being drawn upward off the
     screen rather than dimming in place. Translate only — no scale, which on a
     pixel face would smear the glyphs off their own grid. */
  transform: translate3d(0, calc(var(--hero-exit) * -2.5rem), 0);

  /* No transform on the flatten here — that lives on .wordmark alone, so the
     tagline keeps its natural proportions. */
}

/* The wordmark and tagline are spans inside the page's <h1>, not headings in
   their own right — the heading text a screen reader announces is the
   visually-hidden line beside them, because these two are split into
   per-letter spans that would otherwise be spelled out. Blocks, so the flatten
   transform below has something to apply to. */
.wordmark,
.tagline {
  display: block;
}

.wordmark {
  margin: 0;
  font-family: var(--font-primary, 'Pixelify Sans'), ui-monospace, 'Courier New', monospace;
  /* 400, not 700. argent-pixel-cf ships only a 400 weight (roman + italic), so
     asking for bold makes the browser synthesise one by smearing the glyphs
     sideways — which on a pixel face breaks the pixel grid and looks like a
     rendering fault. The glitch fonts are mixed weights, and letting each show
     its own natural weight is part of the effect. */
  font-weight: 400;

  /* The flatten applies to the WORDMARK ONLY — the tagline keeps its natural
     proportions. transform rather than a font axis: these are pixel faces with
     a single width axis, so font-stretch has nothing to select. */
  transform: scaleY(var(--flatten));
  transform-origin: center bottom;

  /* Compact. The reference wordmark is about a quarter of the viewport width —
     the blobs and the black around them are the composition, not the type. */
  font-size: clamp(1.6rem, 5vw, 4.25rem);
  line-height: 1.05;
  letter-spacing: 0.16em;
  /* letter-spacing adds a trailing gap after the last glyph, which visibly
     shifts the word off-centre against the rule below it. */
  text-indent: 0.16em;
  white-space: nowrap;
  text-align: center;
}

/* One span per letter, so the glitch can re-font them individually. */
.wordmark span,
.tagline span {
  display: inline-block;
}

/* Chromatic aberration on the WORDMARK ONLY. The tagline is deliberately left
   clean — it's a plain proportional face and gets no treatment at all.
   It lives on the spans rather than the parent so a glitching letter can
   override it with a bigger offset. */
.wordmark span {
  text-shadow:
    calc(var(--aberration) * -1) 0 rgba(255, 0, 80, 0.85),
    var(--aberration) 0 rgba(0, 225, 255, 0.85);
}

/* Pre-intro state. Letters are laid out but invisible, so nothing reflows as
   they arrive — the word occupies its final width from the first frame. */
.wordmark span.pending,
.tagline span.pending {
  opacity: 0;
  /* Each letter animates its own font — without a fixed box the word would
     reflow on every swap and the whole line would jitter. */
  transition: none;
  will-change: font-family, transform;
}

/* The glitch flash: chromatic split, plus a horizontal kick set per-flash from
   JS (see GLITCH.JITTER). Pushed harder than a hint — the word still reads,
   because only one to three letters are ever affected at once. */
.wordmark span.glitching {
  text-shadow:
    -0.1em 0 rgba(255, 0, 60, 0.95),
     0.1em 0 rgba(0, 225, 255, 0.95);
}

.tagline {
  /* The rule used to sit here and supply this gap. */
  margin: 0.55em 0 0;
  /* Lexend — proportional, not a pixel face. It sits under the wordmark rather
     than competing with it, which is also why it's excluded from the glitch
     rotation by default (GLITCH.GLITCH_TAGLINE). */
  font-family: 'Lexend', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-weight: 300;
  /* Sized off the wordmark rather than the viewport, so the pair keeps its
     proportions at any width. */
  font-size: clamp(0.55rem, 1.35vw, 1rem);
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  white-space: nowrap;
  text-align: center;
  opacity: 0.9;
}

/* ---- the scroll cue -----------------------------------------------------
   The hero fills the screen, so nothing on it says that the site continues
   below. For an audience that is mostly not technical and often on a phone,
   that is worth one quiet word. It fades out almost immediately once scrolling
   starts, because by then it has done its job.

   aria-hidden: it is a visual hint about a gesture, not information. A screen
   reader user is already being told the page has more content by the page
   itself. */
.hero__cue {
  position: absolute;
  bottom: var(--space-8);
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  z-index: 2;

  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-light);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-faint);

  /* A halo of the page's own background, not a box behind the word. On a narrow
     screen the blobs stack vertically and the lower one settles right where the
     cue sits, which left pale grey text on bright yellow — unreadable at exactly
     the width where the cue matters most. Two soft shadows in --bg keep the
     letterforms legible over any blob colour while staying invisible against the
     near-black everywhere else, which a solid plate would not. */
  text-shadow:
    0 0 6px var(--bg),
    0 0 12px var(--bg);

  /* Gone by a fifth of the way through the scrub — the moment the visitor has
     scrolled at all, they know the page scrolls. */
  opacity: clamp(0, calc(1 - var(--hero-exit) / 0.2), 1);
  pointer-events: none;
}

/* The line beneath it, drifting downward on a loop: the direction of travel,
   shown rather than written. Stopped entirely under reduced motion by the
   global rule in base.css. */
.hero__cue::after {
  content: '';
  display: block;
  width: 1px;
  height: var(--space-6);
  margin: var(--space-2) auto 0;
  background: linear-gradient(to bottom, var(--ink-faint), transparent);
  /* The same halo as the word above it, so the pair reads as one mark rather
     than a legible word over a line that has half vanished into a blob. */
  filter: drop-shadow(0 0 4px var(--bg));
  animation: hero-cue-drift 2.6s var(--ease) infinite;
}

@keyframes hero-cue-drift {
  0%   { transform: translateY(-40%); opacity: 0; }
  40%  { opacity: 1; }
  100% { transform: translateY(40%); opacity: 0; }
}

/* ---- reduced motion -----------------------------------------------------
   hero.js reads the same query and calms the drift, morph and glitch rate.
   This only handles what CSS owns. */
@media (prefers-reduced-motion: reduce) {
  /* Drops the glitch's exaggerated split back to the standing aberration —
     not to none, which would make the wordmark visibly change on every flash. */
  .wordmark span.glitching {
    text-shadow:
      calc(var(--aberration) * -1) 0 rgba(255, 0, 80, 0.85),
      var(--aberration) 0 rgba(0, 225, 255, 0.85);
  }

  :root {
    /* No scrub, so no pinned scrolling distance to scrub against — the extra
       screen of scroll would otherwise be a screen of nothing happening. The
       hero becomes an ordinary full-height section that scrolls away, and the
       page below arrives a screen sooner. hero.js reads the same setting and
       leaves --hero-exit alone. */
    --hero-scroll: 0px;
  }
}

/* ---- small screens ------------------------------------------------------
   Fewer, calmer blobs and no pointer interaction are handled in hero.js
   (see IS_SMALL). Here: keep the mark inside the viewport. */
@media (max-width: 640px) {
  .wordmark { letter-spacing: 0.1em; text-indent: 0.1em; }
  .tagline { letter-spacing: 0.14em; text-indent: 0.14em; }
}
