docs/components/animation.md

# Animation

22 animation primitives — marquee, typewriter, particle systems, text effects, number tickers, and motion wrappers for landing pages, dashboards, and interactive UIs.

**Module**: `PhiaUi.Components.Animation`

```elixir
import PhiaUi.Components.Animation
```

All components:
- Respect `prefers-reduced-motion` — animations disable automatically for users who prefer reduced motion
- Use vanilla JS hooks — no npm packages
- Are server-rendered — content is in the DOM before JavaScript runs

> **Background patterns** (gradient mesh, dot grids, bokeh, SVG waves, etc.) live in their own module — see [Background](background.md).

---

## Table of Contents

**Marquee & Orbit**
- [marquee](#marquee)
- [orbit](#orbit)

**Background Effects**
- [aurora](#aurora)
- [meteor_shower](#meteor_shower)
- [dot_pattern](#dot_pattern)
- [grid_pattern](#grid_pattern)
- [ripple_bg](#ripple_bg)
- [particle_bg](#particle_bg)

**Text Effects**
- [shimmer_text](#shimmer_text)
- [typewriter](#typewriter)
- [word_rotate](#word_rotate)
- [text_scramble](#text_scramble)
- [gradient_text](#gradient_text) — see [Typography](typography.md)

**Motion Wrappers**
- [fade_in](#fade_in)
- [float](#float)
- [tilt_card](#tilt_card)
- [spotlight](#spotlight)
- [animated_border](#animated_border)
- [pulse_ring](#pulse_ring)

**UI Animations**
- [number_ticker](#number_ticker)
- [typing_indicator](#typing_indicator)
- [wave_loader](#wave_loader)
- [confetti_burst](#confetti_burst)

---

## marquee

Infinite horizontal scrolling ticker. Loops child content seamlessly. Hook: `PhiaMarquee`.

```heex
<%!-- Logo cloud --%>
<.marquee speed={40} gap={32} class="py-4">
  <%= for logo <- @logos do %>
    <img src={logo.url} alt={logo.name} class="h-8 grayscale hover:grayscale-0 transition-all" />
  <% end %>
</.marquee>

<%!-- Testimonial ticker --%>
<.marquee speed={30} pause_on_hover={true}>
  <%= for quote <- @quotes do %>
    <.testimonial_card {quote} class="w-72 mx-4 shrink-0" />
  <% end %>
</.marquee>

<%!-- Reverse direction --%>
<.marquee reverse={true} speed={25}>
  <%= for tag <- @tags do %>
    <.badge class="mx-2"><%= tag %></.badge>
  <% end %>
</.marquee>
```

**Attrs**: `speed` (px/s, default 30), `gap` (px), `reverse` (boolean), `pause_on_hover` (boolean), `class`

---

## orbit

Elements orbit around a central point. Hook: `PhiaOrbit`.

```heex
<.orbit id="skills-orbit" radius={120} speed={20}>
  <:center>
    <.avatar size="xl"><.avatar_image src={@profile.avatar} /></.avatar>
  </:center>
  <:items>
    <img src="/icons/elixir.svg" class="h-8 w-8" />
    <img src="/icons/phoenix.svg" class="h-8 w-8" />
    <img src="/icons/tailwind.svg" class="h-8 w-8" />
    <img src="/icons/postgresql.svg" class="h-8 w-8" />
  </:items>
</.orbit>
```

**Attrs**: `id` (required), `radius` (px), `speed` (seconds for full orbit), `class`

---

## aurora

Animated aurora borealis gradient — soft, shifting colours. Reuses the `--animate-aurora` theme token.

```heex
<div class="relative h-64 overflow-hidden rounded-xl bg-zinc-900">
  <.aurora colors={["#3b82f6", "#8b5cf6", "#ec4899"]} />
  <div class="relative z-10 p-8 text-white">Hero content</div>
</div>
```

**Attrs**: `colors` (list of CSS colors), `class`

---

## meteor_shower

Animated shooting meteors across a dark background. Hook: `PhiaMeteor`.

```heex
<div class="relative h-96 bg-black overflow-hidden rounded-xl">
  <.meteor_shower count={30} color="rgba(255,255,255,0.8)" />
  <div class="relative z-10">Content</div>
</div>
```

**Attrs**: `count` (integer, default 20), `color`, `class`

---

## dot_pattern

Repeating SVG dot pattern background with optional radial fade mask.

```heex
<div class="relative bg-white dark:bg-zinc-950">
  <.dot_pattern class="opacity-50" />
  <div class="relative z-10 p-12">Page content</div>
</div>
```

---

## grid_pattern

SVG grid line pattern with optional fade mask.

```heex
<div class="relative">
  <.grid_pattern stroke_color="rgba(0,0,0,0.08)" />
  <div class="relative z-10">Content</div>
</div>
```

---

## ripple_bg

Expanding concentric circle ripples from a centre point.

```heex
<div class="relative h-64 flex items-center justify-center bg-primary/5 rounded-xl overflow-hidden">
  <.ripple_bg color="rgba(99,102,241,0.15)" count={4} duration={3} />
  <.icon name="wifi" size="lg" class="relative z-10 text-primary" />
</div>
```

---

## particle_bg

Canvas particle system with connecting lines. Hook: `PhiaParticleBg`.

```heex
<div class="relative h-screen bg-zinc-950">
  <.particle_bg
    id="hero-particles"
    count={80}
    color="rgba(99,102,241,0.5)"
    connect={true}
  />
  <div class="relative z-10 flex items-center justify-center h-full">
    <h1 class="text-white text-5xl font-bold">PhiaUI</h1>
  </div>
</div>
```

**Attrs**: `id` (required), `count` (integer), `color`, `connect` (boolean), `speed` (float)

---

## shimmer_text

Text with a sweeping shimmer highlight.

```heex
<.shimmer_text class="text-4xl font-bold">
  PhiaUI
</.shimmer_text>
```

---

## typewriter

Types text character by character, with optional blinking cursor. Hook: `PhiaTypewriter`.

```heex
<.typewriter
  id="hero-type"
  phrases={["Build faster.", "Ship confidently.", "Own your UI."]}
  speed={80}
  pause={2000}
/>
```

**Attrs**: `id` (required), `phrases` (list of strings), `speed` (ms per char), `pause` (ms between phrases), `loop` (boolean)

---

## word_rotate

Rotates through a list of words with a fade transition. Hook: `PhiaWordRotate`.

```heex
<h1 class="text-4xl font-bold flex gap-3 items-center">
  Build
  <.word_rotate
    id="rotating-word"
    words={["faster", "smarter", "better", "with confidence"]}
    class="text-primary"
  />
</.h1>
```

---

## text_scramble

Scrambles and resolves text character by character (Matrix-style). Hook: `PhiaTextScramble`.

```heex
<.text_scramble id="scramble-1" text="PhiaUI" class="text-5xl font-mono font-bold" />
```

---

## fade_in

Fades and slides content into view on scroll. Hook: `PhiaScrollReveal`.

```heex
<.fade_in id="feature-1" direction={:up} delay={0}>
  <.feature_card title="Fast" description="829 components, zero bloat." />
</.fade_in>
<.fade_in id="feature-2" direction={:up} delay={100}>
  <.feature_card title="Accessible" description="Full WAI-ARIA on all interactive components." />
</.fade_in>
```

**Attrs**: `id` (required), `direction` (`:up` | `:down` | `:left` | `:right`), `delay` (ms), `duration` (ms)

---

## float

Gently floats content up and down continuously.

```heex
<.float amplitude={8} duration={3}>
  <img src="/hero-graphic.svg" class="w-64" />
</.float>
```

**Attrs**: `amplitude` (px, default 6), `duration` (seconds, default 4), `class`

---

## tilt_card

Card that tilts in 3D on mouse movement. Hook: `PhiaTiltCard`.

```heex
<.tilt_card id="product-card" max_tilt={15} class="rounded-xl overflow-hidden">
  <.image_card src="/product.jpg" title="Product Name" />
</.tilt_card>
```

---

## spotlight

Radial spotlight that follows cursor inside the container. Hook: `PhiaSpotlight`.

```heex
<.spotlight id="hero-spotlight" color="rgba(99,102,241,0.15)">
  <div class="p-12">
    <.heading level={1}>Build something great</.heading>
  </div>
</.spotlight>
```

---

## animated_border

Animates a gradient around the element border.

```heex
<.animated_border class="rounded-xl p-0.5">
  <div class="bg-card rounded-xl p-6">
    <h3>Special offer</h3>
  </div>
</.animated_border>
```

---

## pulse_ring

Pulsing ring around an element — draws attention.

```heex
<div class="relative">
  <.pulse_ring color="rgba(99,102,241,0.4)" />
  <.button variant="default">New feature</.button>
</div>
```

---

## number_ticker

Counts up to a target value with easing. Hook: `PhiaNumberTicker`.

```heex
<.number_ticker id="mrr" value={24500} prefix="$" duration={1500} />
<.number_ticker id="users" value={18723} suffix=" users" duration={2000} />
```

**Attrs**: `id` (required), `value` (number), `prefix`, `suffix`, `duration` (ms), `start` (initial value)

---

## typing_indicator

Three animated dots — for chat "is typing" states.

```heex
<%= if @contact_is_typing do %>
  <div class="flex items-center gap-2">
    <.avatar size="xs"><.avatar_fallback name={@contact.name} /></.avatar>
    <.typing_indicator />
  </div>
<% end %>
```

---

## wave_loader

Five vertical bars that animate like a sound wave.

```heex
<.wave_loader class="text-primary" />
<.wave_loader size="lg" class="text-muted-foreground" />
```

---

## confetti_burst

Canvas confetti explosion. Hook: `PhiaConfetti`.

```heex
<.confetti_burst id="success-confetti" />

<%!-- Trigger from LiveView --%>
<%!-- push_event(socket, "confetti", %{id: "success-confetti"}) --%>
```

---

## Real-world: Hero section with animation stack

```heex
<section class="relative min-h-screen overflow-hidden bg-zinc-950">
  <%!-- Background layers --%>
  <.particle_bg id="hero-bg" count={60} color="rgba(99,102,241,0.3)" connect={true} />

  <%!-- Foreground content --%>
  <div class="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 text-center">
    <.fade_in id="hero-badge" direction={:down} delay={0}>
      <.badge variant="outline" class="text-white border-white/20 mb-6">
        v0.1.17 — 829 components
      </.badge>
    </.fade_in>

    <.fade_in id="hero-title" direction={:up} delay={100}>
      <h1 class="text-6xl font-bold text-white mb-4">
        Build Phoenix UIs
        <br />
        <.shimmer_text class="text-indigo-400">in minutes</.shimmer_text>
      </h1>
    </.fade_in>

    <.fade_in id="hero-cta" direction={:up} delay={300}>
      <div class="flex gap-4 mt-8">
        <.glow_button color="#6366f1" phx-click="get_started">Get started</.glow_button>
        <.button variant="outline" class="text-white border-white/20">View docs</.button>
      </div>
    </.fade_in>
  </div>
</section>
```