# API Reference

## Package entry points

| Import | Description |
| ------ | ----------- |
| `lascii` | Main entry. Side-effect free; exports classes and `init` / `autoInitDom`. |
| `lascii/auto` | Auto-initializes on `DOMContentLoaded` via `autoInitDom()` (side-effectful). |
| `lascii/dom` | DOM adapter exports (`LasciiTextEffect`, `LasciiImageEffect`, `initDom`, `autoInitDom`). Side-effect free. |
| `lascii/core/text` | `LasciiTextEffect` only. |
| `lascii/core/image` | `LasciiImageEffect` only. |

Use `lascii` or `lascii/core/*` when you want full control and no side effects on import. Use `lascii/auto` for declarative `data-lascii-*` setup.

---

## `lascii` exports

```ts
import lascii, {
  LasciiTextEffect,
  LasciiImageEffect,
  init,
  autoInitDom,
} from "lascii";
```

| Export | Type | Description |
| ------ | ---- | ----------- |
| `LasciiTextEffect` | `class` | Scramble/reveal text animation. |
| `LasciiImageEffect` | `class` | ASCII canvas reveal for images. |
| `init` | `function` | Scans the DOM and starts effects (`initDom`). |
| `autoInitDom` | `function` | Registers `init` on `DOMContentLoaded`, or runs immediately if the document is ready. |
| `InitDomOptions` | `type` | Options for `init` / `autoInitDom` (`{ lazy?: boolean }`). |
| `LasciiEvent` | `object` | Event name constants: `start`, `progress`, `complete`, `error`. |
| `default` | `object` | `{ LasciiTextEffect, LasciiImageEffect, init, autoInitDom }`. |

### `init()` / `autoInitDom()`

`init()` (alias of `initDom`) runs:

- `LasciiImageEffect.init("[data-lascii-image]")`
- `LasciiTextEffect.init("[data-lascii-text]")`

Pass `{ lazy: true }` to defer creation until each element is near the viewport (Intersection Observer with `rootMargin: "100px"`). This reduces startup work on pages with many effects, especially image sampling. If `IntersectionObserver` is unavailable, init falls back to the eager path.

```js
import { init } from "lascii";

init({ lazy: true });
```

`autoInitDom()` calls `init()` when the document is ready and accepts the same options:

```js
import { autoInitDom } from "lascii";
autoInitDom({ lazy: true });
```

Importing `lascii` does **not** call `autoInitDom()` automatically. For declarative setup:

```js
import "lascii/auto";
```

Or call it yourself:

```js
import { autoInitDom } from "lascii";
autoInitDom();
```

To import a single effect with maximum tree shaking:

```js
import { LasciiTextEffect } from "lascii/core/text";
import { LasciiImageEffect } from "lascii/core/image";
```

---

## Data attributes

| Attribute | Element | Behavior |
| --------- | ------- | -------- |
| `data-lascii-text` | Any text container | Reads `textContent`, runs text scramble effect. |
| `data-lascii-image` | `<img>` | Samples image, ASCII animation, then fades to original. |

### Text: phrase separator

Multiple phrases in one element are separated by `|:|` (configurable via `separator`):

```html
<p data-lascii-text>First|:|Second|:|Third</p>
```

When the separator is present, phrases loop with `phraseDelay` between transitions.

### Image: layout requirements

Place the image inside a **positioned** wrapper with **overflow hidden** and a defined aspect ratio (e.g. `aspect-ratio: 4/5`):

```html
<div style="position: relative; aspect-ratio: 4/5; overflow: hidden;">
  <img data-lascii-image src="photo.jpg" alt="photo" />
</div>
```

Cross-origin images need CORS headers on the image server (`crossOrigin = "anonymous"` is set internally).

---

## `LasciiTextEffect`

```js
import { LasciiTextEffect } from "lascii";
// or
import LasciiTextEffect from "lascii/core/text";
```

### Constructor

```js
new LasciiTextEffect(element, options?)
```

- **element** — DOM node whose `textContent` is the source string.
- **options** — Partial override of `LasciiTextEffect.DEFAULTS`.

On construction, the element’s text is cleared. The first animation is scheduled on a **microtask**, so you can attach lifecycle listeners immediately after `new`.

### Static members

#### `LasciiTextEffect.RevealOrigin`

| Key | Value | Effect |
| --- | ----- | ------ |
| `START` | `"start"` | Reveal progresses from the start of the string. |
| `MIDDLE` | `"middle"` | Reveal radiates from the center outward. |

#### `LasciiTextEffect.DEFAULTS`

| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `introChars` | `string` | `"█▓▒░x92"` | Character sequence shown at the start of each cell’s scramble. |
| `introPhaseFrames` | `number` | `10` | Frames to step through `introChars` before random chars. |
| `chars` | `string` | `"!<>-_\\/[]{}—=+*^?#________"` | Pool of random scramble characters. |
| `frameStartMax` | `number` | `40` | Max frames before a character begins scrambling (spread along string). |
| `frameEndMax` | `number` | `40` | Random extra scramble length cap per character. |
| `randomCharChance` | `number` | `0.28` | Probability of picking a new random char each frame. |
| `phraseDelay` | `number` | `800` | Ms between phrases when looping (`|:|`). |
| `separator` | `string` | `"\|:|"` | Delimiter between phrases in `textContent`. |
| `revealOrigin` | `string` | `"start"` | `"start"` or `"middle"` (`RevealOrigin`). |

`reducedMotion` is a constructor option (not a default): `true` skips animation, `false` forces it, omitted follows `prefers-reduced-motion`. See [Accessibility](#accessibility).

Scramble characters are rendered in `<span class="dud">` — style `.dud` in your CSS if needed.

### Instance methods

| Method | Returns | Description |
| ------ | ------- | ----------- |
| `setText(newText)` | `Promise<void>` | Animates from current text to `newText`. Resolves when complete (3s safety timeout). |

Both effects extend `EventTarget` and emit lifecycle events (see [Lifecycle events](#lifecycle-events)).

### Static methods

```js
LasciiTextEffect.init(selector = "[data-lascii-text]")
```

Creates one `LasciiTextEffect` per matching element.

### Example

```js
const effect = new LasciiTextEffect(document.querySelector(".headline"), {
  phraseDelay: 1200,
  revealOrigin: LasciiTextEffect.RevealOrigin.MIDDLE,
});

effect.addEventListener("complete", (event) => {
  console.log("Effect completed:", event.detail.text);
});

await effect.setText("Updated copy");
```

---

## `LasciiImageEffect`

```js
import { LasciiImageEffect } from "lascii";
// or
import LasciiImageEffect from "lascii/core/image";
```

### Constructor

```js
new LasciiImageEffect(img, index = 0, options?)
```

- **img** — `<img>` element. Opacity is set to `0` until reveal; a canvas is appended to the parent.
- **index** — Stagger index: delay = `index * IMAGE_STAGGER_MS`.
- **options** — Partial override of `LasciiImageEffect.DEFAULTS`.

### `LasciiImageEffect.DEFAULTS`

| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `ASCII_CHARS` | `string` | `" . . . . . . :::=+xX#0369"` | Light-to-dark character ramp for luminance mapping. |
| `FONT_SIZE` | `number` | `40` | Monospace font size used for measurement and drawing. |
| `ASPECT_WIDTH` | `number` | `4` | Target crop aspect (width). |
| `ASPECT_HEIGHT` | `number` | `5` | Target crop aspect (height). |
| `ASCII_COLUMNS` | `number` | `25` | Minimum column count (may increase with container width). |
| `MAX_ASCII_COLUMNS` | `number` | `96` | Upper cap when scaling to container width. |
| `TARGET_CELL_CSS_PX` | `number` | `9` | Target cell size in CSS pixels for column scaling. |
| `IMAGE_STAGGER_MS` | `number` | `100` | Delay multiplier per image `index`. |
| `CELL_APPEAR_MS` | `number` | `0.5` | Delay between starting each cell animation. |
| `SCRAMBLE_COUNT` | `number` | `10` | Scramble frames for “dense” (dark) cells. |
| `SCRAMBLE_SPEED_MS` | `number` | `50` | Interval between scramble frame updates. |
| `REVEAL_DELAY_MS` | `number` | `0` | Delay before fading canvas out and showing the image. |
| `BACKGROUND_COLOR` | `string` | `"transparent"` | Canvas cell background. |
| `TEXT_COLOR` | `string` | `"#c8c8c8"` | ASCII character color. |

`reducedMotion` is a constructor option (not a default): `true` skips the ASCII animation and shows the original image, `false` forces animation, omitted follows `prefers-reduced-motion`.

Column count is recalculated from the image’s displayed width:  
`cols = clamp(ASCII_COLUMNS, round(width / TARGET_CELL_CSS_PX), MAX_ASCII_COLUMNS)`.

### Static methods

```js
LasciiImageEffect.init(selector = "[data-lascii-image]")
```

Creates one `LasciiImageEffect` per matching image, with `index` from `forEach` order.

Both effects extend `EventTarget` and emit lifecycle events (see [Lifecycle events](#lifecycle-events)). For images, `start` / `complete` `detail.text` is `img.alt` or, if empty, `img.src`.

### Example

```js
document.querySelectorAll("[data-lascii-image]").forEach((img, index) => {
  new LasciiImageEffect(img, index, {
    SCRAMBLE_COUNT: 20,
    TEXT_COLOR: "#ffffff",
  });
});
```

---

## Lifecycle events

`LasciiTextEffect` and `LasciiImageEffect` extend `EventTarget`. Attach listeners after construction; the first animation turn waits one microtask so `start` is not missed.

| Event     | When                     | `detail`               |
| --------- | ------------------------ | ---------------------- |
| `start`   | Animation begins         | `{ text: string }`     |
| `progress`| During the animation     | `{ progress: number }` |
| `complete`| Animation finished       | `{ text: string }`     |
| `error`   | Recoverable runtime failure | `{ error: Error }`  |

`progress` is a number from `0` to `1`. Looping text effects emit `start` / `complete` once per phrase.

```js
import { LasciiEvent, LasciiTextEffect } from "lascii";

const effect = new LasciiTextEffect(element);

effect.addEventListener(LasciiEvent.Complete, (event) => {
  console.log("Effect completed:", event.detail.text);
});

effect.addEventListener("error", (event) => {
  console.warn(event.detail.error);
});
```

---

## Accessibility

Effects follow `prefers-reduced-motion: reduce` (WCAG 2.3.3 / motion preferences):

- **Text** — the target phrase is applied immediately; scramble/`requestAnimationFrame` is skipped. Looped phrases still advance after `phraseDelay`, without animation.
- **Image** — the original `<img>` stays visible; canvas sampling and cell animation do not run.

Override with `{ reducedMotion: true }` or `{ reducedMotion: false }`.

### Screen readers

`LasciiTextEffect` treats the host as a live region:

| Attribute | When | Purpose |
| --------- | ---- | ------- |
| `aria-live="polite"` | If the author did not set `aria-live` | Announce phrase changes without interrupting |
| `aria-atomic="true"` | If missing | Read the whole phrase |
| `aria-busy="true"` and `aria-label` | During scramble | Expose the destination text instead of random characters |
| (remove busy/label) | When complete | Accessible name falls back to visible `textContent` |

`LasciiImageEffect` sets `aria-hidden="true"` on the decorative canvas and `aria-busy="true"` on the image while the ASCII overlay is running (cleared on reveal, error, or dispose). Prefer a meaningful `alt` on the `<img>`.

---

## Import patterns

### Declarative (auto-init)

```html
<script type="module">
  import "lascii/auto";
</script>
```

### Manual init (side-effect free)

```js
import { LasciiImageEffect, LasciiTextEffect, init } from "lascii";

init();
```

Or with maximum tree shaking:

```js
import LasciiTextEffect from "lascii/core/text";
import LasciiImageEffect from "lascii/core/image";
import { initDom } from "lascii/dom";

initDom();
```

### Tree-shaking / side effects

Only `./dist/auto.js` and `./dist/auto.cjs` are marked as side-effectful in `package.json`. All other entry points are tree-shakeable:

- `import { LasciiTextEffect } from "lascii"` — can drop unused image code
- `import from "lascii/core/text"` — text effect only
- `import "lascii/auto"` — keeps the auto-init side effect (intentional)