# <motion-perspective>

Vanishing-point text. Each character shrinks and fades along the
vanish direction, simulating a 3D recession. Optionally animates the
recession back and forth like a depth oscillation.

Text can be set with the `text` attribute or as child text. Child text
doubles as a pre-upgrade fallback: the browser shows it before the
element is defined, so the page never paints an empty gap.

**Use it for:** a short word or title that should look as if it goes back
into the distance, either still or with a slow back-and-forth depth motion
(`loop`, previously `oscillate`).

**Avoid it for:** long text or sentences, because the characters sit on
one line and never wrap. For content that should tilt in 3D toward the
pointer, use `motion-tilt`.

**Accessibility:** the full text is in a visually hidden span and the
character spans have `aria-hidden`, so screen readers read the text once,
as normal words. The far characters are smaller and fainter, so check that
they are still readable, or lower `depth`. With `loop`, the motion
starts at once and does not end on its own. `pause-on-hover` reacts to the
mouse only, so give other users a way to stop it, for example a button
that calls `pause()`.

**Reduced motion:** with `loop`, the oscillation does not start and
the text keeps the still perspective layout. Without `loop` nothing
changes, because that layout does not move.

**Common mistakes:** placing an oscillating `motion-perspective` inside
running text: the characters change `font-size` on every frame, so the
element changes size and the text around it moves. Changing the child text
after the element connects has no effect; set `text` instead.

Docs: https://www.motion-components.dev/docs/text/motion-perspective/

## Install

```js
import 'motion-components/motion-perspective'
```

Or without a build step:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-perspective.js/+esm"></script>
```

## Example

```html
<motion-perspective depth="0.7" vanish="right" loop>HORIZON</motion-perspective>
```

## Attributes

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `oscillate` |  |  |  |
| `text` | `string \| undefined` |  | Text to render with depth. Falls back to the element's child text when unset. |
| `depth` | `number` | `0.65` | How far the far end recedes (0 = flat, 1 = full vanish). |
| `vanish` | `'left' \| 'right'` | `'left'` | Direction the text recedes towards: `'left'` or `'right'`. |
| `loop` | `boolean` | `false` | When `true`, animate a back-and-forth depth oscillation on repeat. `oscillate` is kept as an alias for `loop`. |
| `duration` | `number` | `3` | Seconds per oscillation cycle when `loop` is set. Lower is faster. |
| `pause-on-hover` | `boolean` | `false` | When `true`, pause the oscillation while the cursor is over the element. |

## Properties

| Property | Type | Description |
| --- | --- | --- |
| `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. |
| `finished` | `Promise<void>` | Resolves when the current run settles. A new run replaces the promise. |

## Methods

| Method | Description |
| --- | --- |
| `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. |
| `pause()` | Holds the animation at its current position. No-op unless running. |
| `finish()` | Jumps to the final state and settles. Fires `motion-finish`. |
| `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. |

## Events

The `motion-*` events bubble and are composed.

| Event | Description |
| --- | --- |
| `motion-start` | When a run starts. |
| `motion-finish` | When a run finishes, or `finish()` jumps to the end. |
| `motion-cancel` | When `cancel()` stops a run and resets it. |
