/* ===== Hero =====
   Was a particle-shape canvas + the fidget-mode tool tiles, no text; the
   canvas was removed (see PARTICLE_ANIMATION_BACKUP.md — it wasn't loading
   reliably on mobile) and replaced with a plain headline. */
.hero {
  position: relative;
  /* PINNED to one screen, not min-height. A conversation opens in an absolute
     zone that changes nothing about this box (see components/hero-chat.css),
     so the section must not be allowed to grow — if it did, the page would
     gain scroll and the sections below would move, which the chat spec
     forbids. A faded headline that overruns the top is clipped here. */
  height: 100vh;
  overflow: hidden;
}

/* Headline where the particle shape used to sit. Absolute + centered rather
   than in normal flow, same as the canvas it replaces, so it shares that
   layer's spot above the fidget tiles' resting area. z-index is deliberately
   below --z-hero-fidget (102): a tile falling past on its way down should
   cover the text, not the other way round.

   Same two-font combo as the logo wordmark (logo.css): --font-display
   (Georgia, italic) for the accent-coloured half, --font-body (system-ui)
   for the rest — here that split is "blue" vs "everything else" rather than
   "cristina" vs "iftodi", but it's the same pairing. The base rule below
   carries --font-body for the black/white majority of the text; the two
   blue spans (em and .hero__title-accent) opt back into --font-display so
   they keep reading as the logo's serif half regardless of which one it is. */
/* THE HEADLINE IS FLOW CONTENT, and that is what makes the whole Hero work.

   It used to be `position: absolute; top: 40vh`, painted over the section
   like the particle canvas it replaced. That is why an early version of the
   chat had to park and shrink it by hand: nothing in the document could push
   it, so it just sat there while the conversation piled up underneath.

   In flow it needs no help. The Hero is a bottom-anchored column —
   headline, thread, field, in that order (see components/hero-chat.css) — so
   every new turn makes the stack taller and the headline rises by exactly
   the height of what arrived. It keeps its size the whole way and eventually
   leaves through the top of the screen, where scrolling up finds it again.

   `position: relative` only so the z-index below still applies. That z-index
   is deliberately BELOW --z-hero-fidget (102): a tile falling past on its
   way down should cover the text, not the other way round. */
.hero__title {
  position: relative;
  z-index: var(--z-content);
  width: 90%;
  max-width: 900px;
  text-align: center;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--hero-title-size);      /* ≈76px @1440, was --fs-600 (88) */
  line-height: var(--hero-title-leading); /* 1.05 */
  color: var(--c-ink);
  /* only opacity transitions — the dissolve below moves nothing */
  transition: opacity 500ms var(--ease-out);
}
/* On the first question the headline dissolves IN PLACE: opacity only, no
   transform, no shift up. .is-chatting is added to .hero by hero-chat.js and
   never removed until the page reloads, so it does not come back mid-visit. */
.hero.is-chatting .hero__title {
  opacity: 0;
  pointer-events: none;
}
/* --- headline drop-in, once, after the fidget tiles have landed ---
   Each word starts 40px high and transparent and falls into place. Only
   opacity and transform animate, and every word is inline-block at its
   natural size from the first frame, so nothing reflows as the words
   arrive — a word landing never nudges its neighbours.

   The steps overlap rather than queue: each starts when the one before it
   is 85% done, i.e. 500ms × (1 − 0.85) = 75ms apart. data-step (not
   :nth-child) carries the order because the <br>s between lines are
   siblings too and would throw the counting off — and because two words
   can then share a step to land together.

   Gated behind .is-ready, which js/modules/hero-title.js adds once the
   tiles have settled, so the headline drops in after them rather than
   through them. The words hold the from-state until then. */
/* The drop-in animates `translate`, NOT `transform`, and that is load-bearing
   now that the words are shy. shy.css owns their `transform`, and this
   animation carries `animation-fill-mode: both` — so written as a transform it
   would hold `translateY(0)` forever after landing and the flee offset would
   never render at all. The individual `translate` property is a separate
   channel that composes with `transform` (it applies first), so the two
   effects add instead of one silently cancelling the other. */
@keyframes hero-word-drop {
  from { opacity: 0; translate: 0 -40px; }
  to   { opacity: 1; translate: 0 0; }
}
/* Everything EXCEPT animation-name lives here, and the .is-ready rule below
   supplies only the name. Deliberately not the `animation:` shorthand there:
   the shorthand resets every animation-* it omits, so its implicit
   `animation-delay: 0s` — at (0,3,0), above the (0,2,0) [data-step] rules —
   would silently flatten the stagger and land every word at once. */
.hero__word {
  display: inline-block;
  opacity: 0;
  translate: 0 -40px;
  animation-duration: var(--dur-hero-drop);
  animation-timing-function: ease;
  animation-fill-mode: both;
}
.hero__word[data-step="0"] { animation-delay:   0ms; }
.hero__word[data-step="1"] { animation-delay:  75ms; }
.hero__word[data-step="2"] { animation-delay: 150ms; }   /* product + thinking */
.hero__word[data-step="3"] { animation-delay: 225ms; }

.hero__title.is-ready .hero__word { animation-name: hero-word-drop; }

/* reset.css globally clamps animation-duration to 0.01ms under reduced
   motion, but leaves delays alone — which would still blink the words in
   one by one over 300ms. Skip the animation and just show the text; the
   from-state above has to be overridden or it would stay invisible. */
@media (prefers-reduced-motion: reduce) {
  /* `translate` is the from-state's channel now; `transform` is cleared too
     because shy.css hands these elements one, and shy-cards.js bails out
     under reduced motion without ever writing --dx/--dy. */
  .hero__word { opacity: 1; translate: none; transform: none; animation: none; }
}

/* "ai-assisted" — same italic treatment as .section__title, on this one word
   rather than the whole line, plus the brand highlight colour and the
   logo's serif half of its font pairing. */
.hero__title em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  color: var(--c-highlight);
}
/* "designer" — highlight colour without the italic, matching the mock;
   same serif as "ai-assisted" since both are the blue half of the pairing. */
.hero__title-accent {
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--c-highlight);
}

/* fidget-mode physics layer (Matter.js draws its canvas here).
   z-index sits ABOVE --z-nav (see tokens.css): tiles can fall onto and rest
   on the navbar, but must always paint over it where they overlap, never
   get clipped behind it. The canvas is transparent everywhere except where
   a tile is actually drawn, so this only affects the real overlap pixels. */
.hero__fidget {
  position: absolute;
  /* PINNED TO THE HERO'S BOTTOM, one screen tall — not `inset: 0`.

     The Hero is only 100vh while nobody is talking to it; a conversation
     makes it as tall as it needs to be, and it grows DOWNWARD because the
     headline's position in the document never changes (see .hero__title
     above). So the section's top is the one edge that stands still, and the
     field is the one that moves — and hero-chat.js scrolls the window by the
     same amount so the field looks motionless.

     Anchoring the physics layer to the top would therefore walk the tile
     pile up the screen, a message at a time, away from the field it is
     supposed to sit under. Anchored to the bottom it travels with the field
     and the world never moves at all — no re-settling, no wobble, the tiles
     carry on exactly as they were.

     At rest the two are the same thing: the Hero is 100vh, so `bottom: 0`
     plus `height: 100vh` lands precisely where `inset: 0` did. */
  bottom: 0;
  left: 0;
  right: 0;
  height: 100vh;
  z-index: var(--z-hero-fidget);
}
.hero__fidget canvas {
  position: absolute;
  inset: 0;
}
/* Only while a tile is in the hand or in flight (fidget-hero.js). The canvas
   stays under the chat the rest of the time, or it would swallow every tap on
   the field. */
.hero__fidget.is-held { z-index: var(--z-hero-fidget-held); }

/* The hovered-tile tooltip is the shared .glass-tooltip now (css/layout.css,
   driven by js/modules/tooltip.js) — one shape and one Glass across the whole
   site. The old .fidget-tip (its own bordered/shadowed capsule with a CSS
   triangle tail) was removed so the Hero tiles and the Nav Dock can't drift
   into two different tooltips. */

/* The Hero's top bar MOVED OUT. It was .hero__topbar — logo on the left,
   controls on the right, absolutely positioned inside this section — and it
   is now a page-level fixed header (<header class="site-header"> in
   index.html, styled in layout.css). The reason is the one this file used to
   record as a dead end: `fixed` from inside the Hero worked mechanically but
   left a bare logo colliding with every section heading that scrolled under
   it. The header solves that with a plate it fades in once the page moves,
   which is a thing a bare absolute corner could not do. */
