/* ============================================================
   INTRO — particle wordmark, tagline, scroll cue
   ============================================================ */

.intro {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto auto;
  align-items: center;
  justify-items: center;
  padding: clamp(5rem, 12vh, 9rem) var(--gutter) clamp(2.4rem, 6vh, 4rem);
  text-align: center;
  overflow: hidden;
}

/* ---- the canvas the wordmark is drawn on ---- */
.intro__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ---- markup wordmark: the fallback when canvas can't run ---- */
.intro__wordmark {
  grid-row: 1;
  align-self: center;
  font-family: var(--display);
  font-size: var(--step-5);
  line-height: 0.82;
  letter-spacing: 0.012em;
  text-transform: uppercase;
  display: flex;
  gap: 0.28em;
  flex-wrap: wrap;
  justify-content: center;
  transition: color 900ms var(--ease), text-shadow 900ms var(--ease);
}
/* Hidden from first paint, not just once js/intro.js gets around to
   adding "has-particles" — that class only lands after the display
   font finishes loading, which can take a beat, and until then this
   rule is what keeps the raw heading from flashing on screen before
   a single grain has flown. (The "body" prefix isn't decorative —
   it's what outranks the .ink utility class also on this element,
   regardless of stylesheet order.) text-shadow is reset here too:
   the .glow utility class also on this element paints a shadow off
   the glyphs' own shape, which doesn't disappear just because the
   fill colour goes transparent — left alone it reads as a faint grey
   ghost of the name sitting there the whole time, exactly the "you
   shouldn't be able to see the text" bug this rule exists to kill.

   It only turns opaque on "is-assembled", deliberately not the more
   general "is-live" — main.js sets is-live on its own 1500ms safety
   net for any chrome that never got its proper signal, and if this
   rule keyed off that too, a slow font load could let that generic
   timer reveal the flat heading while the grains are still mid-air.
   is-assembled is arrive()'s signal specifically, set only once the
   grain has actually landed (or immediately, under reduced motion),
   so this can't jump ahead of the animation it's supposed to follow. */
body .intro__wordmark { color: transparent; text-shadow: none; }
body.is-assembled .intro__wordmark {
  color: var(--ink);
  text-shadow: 0 0 clamp(10px, 1.4vw, 30px) var(--glow-ink);
}

/* On a phone the name stacks and goes big — one word per line fills
   the frame the way a title card should, instead of sitting small in
   the middle of a lot of nothing. */
@media (max-width: 620px) {
  .intro__wordmark {
    flex-direction: column;
    gap: 0;
    font-size: min(33vw, 8.5rem);
    line-height: 0.86;
  }
}

/* ---- tagline ---- */
.intro__tagline {
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: clamp(0.7rem, 1.8vw, 1.4rem);
  font-family: var(--mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-top: clamp(1.4rem, 4vh, 2.6rem);

  opacity: 0;
  transform: translate3d(0, 10px, 0);
  transition: opacity 1s var(--ease) 260ms, transform 1s var(--ease) 260ms;
}
body.is-live .intro__tagline { opacity: 1; transform: none; }

.intro__tagline i {
  display: block;
  width: 4px; height: 4px;
  background: var(--hot);
  border-radius: 50%;
  opacity: 0.9;
}

/* ---- scroll cue ---- */
.intro__cue {
  grid-row: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  margin-top: clamp(2.2rem, 7vh, 4.5rem);
  font-family: var(--mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-faint);

  opacity: 0;
  transition: opacity 1s var(--ease) 620ms, color var(--t-fast) var(--ease);
}
body.is-live .intro__cue { opacity: 1; }
.intro__cue:hover, .intro__cue:focus-visible { color: var(--hot); }

.intro__cue-line {
  position: relative;
  display: block;
  width: 1px;
  height: clamp(38px, 6vh, 62px);
  background: linear-gradient(to bottom, currentColor, transparent);
  overflow: hidden;
}
.intro__cue-line::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: -40%;
  height: 40%;
  background: var(--hot);
  animation: cue-run 2.4s var(--ease-in-out) infinite;
}
@keyframes cue-run {
  0%   { transform: translateY(0);    opacity: 0; }
  22%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { transform: translateY(350%); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .intro__cue-line::after { animation: none; opacity: 0.6; top: 0; }
}

/* The intro drifts back slightly as you leave it. */
.intro__stack-fade { will-change: opacity; }
