# AnimatedText

**When to use:** Decorative text entrance or emphasis effect, for hero headlines, empty-state messages, "premium"/"new" callouts, loading/decoding states. Not for body copy that needs to stay readable while animating in bulk; keep it to short strings.

**Keywords:** text animation, text effect, hero text

**Import:** `import { AnimatedText, AnimatedTextVariant, AnimatedTextUnit } from '@ahrowe/ui'`

**Style variants:** `AnimatedTextVariant.RiseUp` (default) | `AnimatedTextVariant.BlurIn` | `AnimatedTextVariant.Bounce` | `AnimatedTextVariant.Typewriter` | `AnimatedTextVariant.Scramble` | `AnimatedTextVariant.Glitch` | `AnimatedTextVariant.Wave` | `AnimatedTextVariant.Shine` | `AnimatedTextVariant.Highlight`

**Units (every variant but `Shine`/`Highlight`):** `AnimatedTextUnit.Letter` (default) | `AnimatedTextUnit.Word`

```tsx
import { AnimatedText, AnimatedTextVariant, AnimatedTextUnit } from '@ahrowe/ui';

// Default: letters rise up, staggered left to right, once
<AnimatedText text="Welcome back" />

// Faster/slower stagger between letters
<AnimatedText text="Fast" staggerDelay={15} />
<AnimatedText text="Slow" staggerDelay={120} />

// Faster/slower per-letter entrance
<AnimatedText text="Snappy" duration={200} />

// Stagger by word instead of by letter
<AnimatedText text="One word after another" unit={AnimatedTextUnit.Word} staggerDelay={150} />

// Fades in from a blur
<AnimatedText text="Fading into focus" variant={AnimatedTextVariant.BlurIn} />

// Drops in from above with a spring overshoot
<AnimatedText text="Bouncing into place" variant={AnimatedTextVariant.Bounce} />

// Typed one letter at a time, with a blinking cursor at the end
<AnimatedText text="Typed one letter at a time" variant={AnimatedTextVariant.Typewriter} staggerDelay={60} />

// Typewriter, word by word instead of letter by letter
<AnimatedText
  text="Typed word by word"
  variant={AnimatedTextVariant.Typewriter}
  unit={AnimatedTextUnit.Word}
  staggerDelay={250}
/>

// Resolves from random characters to the real text, left to right
<AnimatedText text="Decoding the message" variant={AnimatedTextVariant.Scramble} staggerDelay={60} />

// Briefly RGB-splits and jitters before settling
<AnimatedText text="System malfunction" variant={AnimatedTextVariant.Glitch} />

// Continuously bobs in a travelling wave, on a loop
<AnimatedText text="Riding the wave" variant={AnimatedTextVariant.Wave} />

// A highlight sweeps across the text, looping
<AnimatedText text="Premium feature" variant={AnimatedTextVariant.Shine} />

// Slower sweep
<AnimatedText text="Slow shine" variant={AnimatedTextVariant.Shine} duration={3000} />

// A marker-style highlight block wipes in behind the text, once
<AnimatedText text="Marked as important" variant={AnimatedTextVariant.Highlight} />

// Plays immediately on mount instead of waiting for it to scroll into view
// (default behaviour) — e.g. a hero headline that's already above the fold
<AnimatedText text="Welcome back" triggerOnView={false} />

// Changing `text` (or `variant`/`unit`/`staggerDelay`/`duration`) automatically replays,
// so no key is needed for that. A key is only needed to replay with the exact same text again:
<AnimatedText key={replayKey} text="Hello there" />
<button onClick={() => setReplayKey((k) => k + 1)}>Replay</button>
```

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `text` | `string` | The text to animate (required) |
| `variant` | `AnimatedTextVariant` | Which animation plays (default `RiseUp`) |
| `unit` | `AnimatedTextUnit` | Whether entrance/loop variants stagger by letter or by whole word (default `Letter`). Ignored by `Shine`/`Highlight` |
| `staggerDelay` | `number` | Delay in ms between each unit's animation start. Applies to every variant except `Shine`/`Highlight`, which animate as a single run (default `40`) |
| `duration` | `number` | Duration in ms of one animation cycle: a single unit's entrance for `RiseUp`/`BlurIn`/`Bounce`/`Glitch` (default `500`), one bob cycle for `Wave` (default `1200`), one sweep of the highlight for `Shine` (default `2000`), or the one-shot wipe-in for `Highlight` (default `600`). Ignored by `Typewriter` and `Scramble`, since each unit resolves instantly; use `staggerDelay` to control their pacing |
| `triggerOnView` | `boolean` | When `true` (default), the animation doesn't start until the component first scrolls into the viewport. Set `false` to play immediately on mount |

**Word mode:** with `unit={AnimatedTextUnit.Word}`, each whole word animates as one block instead of each letter animating individually; `staggerDelay` then applies between words. Words are split on whitespace, and consecutive spaces collapse to one.

**Typewriter:** each letter (or word) pops in instantly at its staggered delay, with no entrance easing, since it's meant to read as being typed rather than animated in. Untyped units aren't rendered at all yet (rather than pre-rendered invisible), so the blinking cursor always tracks right after the last thing actually "typed"; it stays solid while typing and starts blinking once the sequence finishes.

**Scramble:** every unit starts as a run of random characters (matching the unit's own length, so words keep their shape) and locks in to the real value left to right, one `staggerDelay` apart; still-scrambling units keep flickering between random characters until their turn. Spaces are never scrambled.

**Glitch:** a brief RGB-channel split (`--error-color` / `--info-color`) and positional jitter, settling to the normal render partway through the entrance.

**Wave:** the only other continuously looping variant besides `Shine`. Every unit bobs up and down forever, and each unit's `animation-delay` is a *negative* offset (`-index * staggerDelay`), which starts it already mid-cycle so the whole string reads as a travelling wave from the first frame instead of every letter bobbing in lockstep.

**Shine vs. Highlight:** both animate the whole string as a single run (no letter/word splitting, `unit` is ignored) rather than staggering units, since a highlighter mark is naturally one continuous stroke, not a letter-by-letter reveal. `Shine` loops a light gradient through the text's own color, forever. `Highlight` wipes a solid marker-style background block in behind the (already fully-opaque) text once, and stays, closer to literally highlighting the text than lighting it up.

**Replaying:** every variant automatically replays whenever `text`, `variant`, `unit`, `staggerDelay`, or `duration` changes. CSS entrance variants would otherwise just hold their `animation-fill-mode: forwards` end state and silently swap letters underneath it, so the animated markup remounts on any of those changes to keep the animation and the text in sync. To replay with the exact same text (e.g. a manual "Replay" button), change the component's own `key`; that's the only case not already covered automatically.

**Accessibility:** the full `text` is always exposed to assistive tech via a visually-hidden node; the animated markup is `aria-hidden`. Respects `prefers-reduced-motion`: every variant renders the plain, fully-visible final state immediately with no animation (no blur, no bounce, no scrambling, no bobbing, no cursor blink, no shine sweep, no highlight wipe).

**Slots:** `letter`, each animated unit's span, for every variant but `Shine`/`Highlight` (where it's the single animated text run). `cursor`, the blinking cursor, `Typewriter` only.
