# ASCII Image Compare

> Drag a divider between a photo and its ASCII version.

Category: Images. Docs: https://asciicn.sumak.dev/docs/ascii-image-compare

## Installation

```bash
npx shadcn@latest add @asciicn/ascii-image-compare
```

Needs the `@asciicn` registry in components.json: `"registries": { "@asciicn": "https://asciicn.sumak.dev/r/{name}.json" }`.

## Usage

```tsx
import { AsciiImageCompare } from "@/components/ascii/ascii-image-compare"

<AsciiImageCompare
  src="/products/sneaker.jpg"
  alt="A white sneaker"
  dither="floyd"
  fontSize={7}
/>
```

## Examples

### Controlled

Pass `position` and `onPositionChange` to drive the divider from your own buttons, or to sync it with something else on the page.

```tsx
const [position, setPosition] = useState(50)

<AsciiImageCompare
  src="/cabin.jpg"
  alt="A red-roofed cabin by a lake below a snowy peak"
  position={position}
  onPositionChange={setPosition}
  charset="blocks"
  className="text-primary"
/>
<Button onClick={() => setPosition(0)}>ASCII</Button>
<Button onClick={() => setPosition(50)}>Split</Button>
<Button onClick={() => setPosition(100)}>Photo</Button>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string` |  | Image URL. Loaded once and shared by the photo and the canvas. Cross-origin images need CORS headers. |
| `alt` | `string` |  | Describes the photo. The canvas is labelled with it too. |
| `defaultPosition` | `number` | `50` | Starting divider position from the left, 0 to 100. |
| `position` | `number` |  | Controlled position. Pair it with `onPositionChange`. |
| `onPositionChange` | `(position: number) => void` |  | Called while dragging and on every key press. |

Also accepts every `AsciiCanvas` prop:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `charset` | `CharsetName | string` | `"standard"` | Named ramp or your own string, empty to dense. "braille" packs 2×4 dots per cell, "quadrants" 2×2 blocks. |
| `fontSize` | `number` | `12` | Cell size in CSS pixels. Smaller means more detail. |
| `dither` | `"none" | "bayer" | "floyd"` | `"none"` | Quantisation. Floyd–Steinberg for photos, Bayer for a retro look. |
| `color` | `"ink" | "source" | string[]` | `"ink"` | Ink uses the CSS colour (theme with text-*). Source keeps pixel colours. An array maps tone onto gradient stops, e.g. palettes.thermal. |
| `edges` | `number` | `0` | Outline contours with | / - \ where the tone gradient beats this strength. Try 0.3. Ramps only. |
| `opacity` | `boolean` | `false` | Fade each glyph by its tone. |
| `brightness` | `number` | `0` | Added after contrast, -1 to 1. |
| `contrast` | `number` | `1` | Multiplier around mid grey. |
| `gamma` | `number` | `1` | Tone curve exponent. |
| `invert` | `boolean` | `false` | Swap ink and blank. |
| `animate` | `boolean` | `auto` | On for fields, frames and video. Off under reduced motion, except video. |
| `fps` | `number` | `30` | Frame cap. |
| `ref` | `Ref<AsciiHandle>` |  | Exposes text(), svg(), png() and the canvas. |

## Notes

- **Accessibility.** The divider is a `role="slider"` with `aria-valuenow`. Arrow keys move it by 1, Shift or Page Up/Down by 10, Home and End jump to the edges. The photo keeps its `alt`.
- **Pointer.** Press anywhere on the image and drag. The pointer is captured, so dragging past the edge keeps working. Vertical touch scrolling still scrolls the page.
- **Theming.** Glyphs take the CSS `color`, so `text-primary` themes them. The divider line uses `mix-blend-difference` to stay visible over any photo.
- **Performance.** The ASCII canvas draws once per prop change. Dragging only moves a `clip-path` on the photo, so the canvas never redraws.
