/* ==========================================================================
   ExGrowth — hero word system (css/words.css)
   Final direction: "Swap · out = in"
   --------------------------------------------------------------------------
   The frame is fixed:      Ex   [ infix ]   Growth

   One word hands off to the next without a mask, filter or ghost layer. The
   outgoing word fades down at the old width; the incoming word is introduced
   already fitted to the current gap width with scaleX and relaxes to natural
   width exactly as the gap glides to the new measured width. "Ex" and
   "Growth" move continuously; there is no heartbeat pause.

   .word__letters clips horizontally to the animated gap as a safety net, and
   the scale fit keeps the incoming word inside it at every frame. Letter
   widths are measured from real inline-block spans, so settled words are
   never clipped. The rotating infix intentionally uses a different colour
   (cool pearl) from the fixed warm-ink anchors.

   Markup (index.html):
     <div class="hero__word">
       <h1 class="word" id="hero-word" aria-label="Exponential Growth">
         <span class="word__anchor word__anchor--ex" aria-hidden="true">Ex</span><span
         class="word__gap" aria-hidden="true"><span class="word__letters"></span></span><span
         class="word__anchor word__anchor--growth" aria-hidden="true">Growth</span>
       </h1>
     </div>
   ========================================================================== */

/* --------------------------------------------------------------------------
   The line
   -------------------------------------------------------------------------- */

.word,
.hero__word .word {
  /* authoritative size is written by js/words.js; this is the pre-JS guess */
  --word-size: clamp(39px, 8.25vw, 150px);

  display: flex;
  justify-content: center;
  align-items: flex-start;
  width: 100%;
  margin: 0;
  padding: 0;
  font-family: var(--font-display);
  font-weight: 470;
  font-size: var(--word-size);
  line-height: 0.95;
  letter-spacing: -0.045em;
  font-kerning: normal;
  color: var(--ink);
  --word-rotating: #d8d2f2;
  white-space: nowrap;
  text-wrap: nowrap;
  -webkit-user-select: none;
  user-select: none;

  /* one-time entrance; .is-ready resolves it */
  opacity: 0;
  filter: blur(11px);
  transform: translateY(0.045em);
  transition:
    opacity 1200ms var(--ease-expo),
    filter 1250ms var(--ease-expo),
    transform 1350ms var(--ease-expo);
}

.hero__word.is-ready .word,
.word.is-ready {
  opacity: 1;
  filter: none;
  transform: none;
}

/* The fixed frame ---------------------------------------------------------- */

.word__anchor,
.hero__word .word__anchor {
  display: block;
  flex: 0 0 auto;
  color: var(--ink);
}

/* A normal word space before "Growth": the line also reads "Ex Growth". */
.word__anchor--growth::before {
  content: "\00a0";
}

/* The animated gap --------------------------------------------------------- */

.word__gap,
.hero__word .word__gap {
  position: relative;
  display: block;
  flex: 0 0 auto;
  align-self: flex-start;
  width: 0;
  height: 0.95em;
  overflow: visible;
}

/* Horizontal clip at the animated gap width. The extra vertical room keeps
   ascenders and descenders intact. The swap fits the incoming word to the
   current gap, so this clip is a safety net rather than a visible edge. */
.word__letters {
  position: absolute;
  top: -0.3em;
  right: 0;
  bottom: -0.22em;
  left: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Backspace caret: sits at the measured end of the current prefix. It only
   exists during the delete/type pass, then leaves the line completely. */
.word__caret {
  position: absolute;
  top: 0.06em;
  left: 0;
  z-index: 2;
  width: 1px;
  height: 0.86em;
  background: var(--ink);
  opacity: 0;
  pointer-events: none;
  transform: translateX(-0.5px);
  transition: opacity 120ms linear;
}

.word__caret.is-on {
  opacity: 0.9;
  animation: word-caret-blink 0.9s steps(1, end) infinite;
}

@keyframes word-caret-blink {
  0%, 48% { opacity: 0.9; }
  49%, 100% { opacity: 0; }
}

.word__group {
  position: absolute;
  top: 0.3em;
  left: 0;
  display: block;
  white-space: nowrap;
  visibility: hidden;
  transform-origin: 0 50%;
  color: var(--ink);
}

.word__group.is-active {
  visibility: visible;
}

.word__letter {
  display: inline-block;
  position: relative;
  /* the rotating word is intentionally a different colour from the fixed
     anchors; fixed anchors stay warm ink, the infix reads as cool pearl */
  color: var(--word-rotating);
  font: inherit;
  letter-spacing: inherit;
  transform-origin: 50% 62%;
}

/* --------------------------------------------------------------------------
   Measurement probe: real inline-block letters, so widths match the rendered
   word exactly (plain-text measurement kerns differently and can clip the
   final glyph). Never visible, never interactive.
   -------------------------------------------------------------------------- */

.word__measure {
  position: fixed;
  top: 0;
  left: -20000px;
  visibility: hidden;
  pointer-events: none;
  white-space: pre;
  font-family: var(--font-display);
  font-weight: 470;
  font-size: 100px;
  line-height: 0.95;
  letter-spacing: -0.045em;
  font-kerning: normal;
  color: transparent;
}

.word__measure .word__letter {
  display: inline-block;
}

/* --------------------------------------------------------------------------
   Reduced motion — entrance never animates; JS paints one static word.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .word,
  .hero__word .word {
    transition: none;
  }

  .word__caret {
    display: none;
  }
}
