# ASCII reveal

An image that resolves out of glyphs: cells lock in and hand over to the real photo along a sweep, on view, on hover or on cue.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The asciicn agent skill, for building any ASCII effect on the engine, is at [/.well-known/agent-skills/asciicn/SKILL.md](/.well-known/agent-skills/asciicn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the asciicn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `AsciiShader` ([Write your own](/docs/engine/write-your-own.md)), or draw a CPU grid with `AsciiGrid`, and read this example's source for technique. Its full source is in [https://asciicn.dev/r/ascii-reveal.json](https://asciicn.dev/r/ascii-reveal.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/ascii-reveal.json
```

## Usage [#usage]

```tsx
import { AsciiReveal } from "@/components/asciicn/ascii-reveal";

export function Hero() {
  return (
    <AsciiReveal
      src="/photos/launch.jpg"
      alt="The rocket on the pad at dawn"
      direction="down"
      className="w-full max-w-xl"
    />
  );
}
```

`AsciiReveal` starts as the picture drawn in glyphs. It samples the photo with the same area-averaging sampler as `AsciiImage`, so every glyph is the average of the pixels under its cell.

When the reveal runs, an edge sweeps across the picture. Cells at the edge flicker through glyphs in the `accent` colour, then lock in as the photo. Behind the edge the real photo shows. Ahead of it the glyphs hold. The edge bends a little and has some grain, so cells hand over one by one instead of in straight lines.

`trigger` sets what starts it:

* `view` reveals once, when the image scrolls into view.
* `hover` reveals on hover or keyboard focus, and goes back to glyphs on leave. On a touch screen a tap toggles it.
* `manual` follows the `revealed` prop, so you can tie it to your own state.

The box keeps the image's aspect ratio. Pass `ratio` to reserve the space before the image loads, or set your own `aspect-*` class.

## Examples [#examples]

### Hover cards [#hover-cards]

Each card reveals on hover or focus and sweeps back on leave. A short `duration` keeps them quick under the pointer.

```tsx title="examples/ascii-reveal-cards.tsx"
import { AsciiReveal } from "@/components/asciicn/ascii-reveal";

const CARDS = [
  {
    alt: "Abraham Lincoln in 1863, facing the camera, in a dark coat and bow tie",
    direction: "up",
    name: "Abraham Lincoln",
    role: "1863",
    src: "/examples/lincoln.jpg",
  },
  {
    alt: "Astronaut Sally Ride in 1984, smiling, in a navy NASA shirt",
    direction: "radial",
    name: "Sally Ride",
    role: "1984",
    src: "/examples/ride.jpg",
  },
  {
    alt: "Buzz Aldrin standing on the Moon during Apollo 11",
    direction: "random",
    name: "Buzz Aldrin",
    role: "1969",
    src: "/examples/aldrin.jpg",
  },
] as const;

export default function AsciiRevealCards() {
  return (
    <div className="grid w-full max-w-3xl grid-cols-2 gap-4 sm:grid-cols-3">
      {CARDS.map((card) => (
        <figure key={card.src} className="flex flex-col gap-2">
          <AsciiReveal
            src={card.src}
            alt={card.alt}
            trigger="hover"
            direction={card.direction}
            duration={1100}
            cell={8}
            ratio={4 / 5}
            className="w-full rounded-md"
          />
          <figcaption className="flex flex-wrap justify-between gap-x-2 font-mono text-xs">
            <span>{card.name}</span>
            <span className="text-muted-foreground">{card.role}</span>
          </figcaption>
        </figure>
      ))}
    </div>
  );
}
```

### Full-width radial reveal [#full-width-radial-reveal]

`direction="radial"` opens the photo from the centre out. This one is Braille in `truecolor` and reveals once, when it scrolls into view. Press Replay to see it again.

```tsx title="examples/ascii-reveal-radial.tsx"
import { AsciiReveal } from "@/components/asciicn/ascii-reveal";

export default function AsciiRevealRadial() {
  return (
    <AsciiReveal
      src="/examples/pillars.jpg"
      alt="The Pillars of Creation, towers of gas and dust in the Eagle Nebula"
      direction="radial"
      duration={3600}
      colorMode="truecolor"
      grade={{ gamma: 1.25, saturation: 1.25 }}
      cell={9}
      ratio={16 / 9}
      className="w-full"
    />
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string` |  | Image URL. Files on another origin need CORS headers, or the canvas can't read their pixels. |
| `alt` | `string` |  | What the image shows, passed to the <img>. Pass "" for a decorative image. |
| `trigger` | `"view" \| "hover" \| "manual"` | `"view"` | view reveals once on scroll into view. hover reveals on hover or focus. manual follows revealed. |
| `revealed` | `boolean` | `false` | With manual: true shows the photo, false the glyphs. |
| `direction` | `"down" \| "up" \| "left" \| "right" \| "radial" \| "random"` | `"down"` | Which way the edge travels. radial opens from the centre. random dissolves in patches. |
| `duration` | `number` | `2400` | Length of one full sweep in milliseconds. A reveal that turns back halfway takes half as long. |
| `accent` | `AsciiColor` | `"orange"` | Colour of the glyphs at the edge. A palette name, a CSS variable or any CSS colour. |
| `ratio` | `number` | `the image's own, 4/3 before it loads` | Width over height of the box. |
| `charset` | `"standard" \| "dense" \| "blocks" \| "quadrants" \| "braille" \| "cp437" \| "petscii" \| "katakana" \| "dots" \| "edge"` | `"standard"` | Which glyphs draw the picture. |
| `chars` | `string` |  | A custom ramp from empty to dense, such as " .oO@". Wins over charset. |
| `colorMode` | `"mono" \| "truecolor" \| "ansi16" \| "duotone" \| "palette"` | `"mono"` | mono draws in one ink. truecolor takes each cell's colour from the photo. duotone reads tones through ramp. |
| `phosphor` | `"p1" \| "p3" \| "ibm" \| "blueprint"` |  | With mono: a CRT ink and its dark glass. |
| `ramp` | `RampName \| string[]` | `"mainframe"` | With duotone: a named ramp or your own stops, dark to light. |
| `ansi` | `"vga" \| "xterm"` | `"vga"` | With ansi16: which 16-colour table. |
| `grade` | `{ brightness?, contrast?, gamma?, saturation? }` |  | Tone and colour grading before glyphs are chosen. |
| `invert` | `boolean` | `follows the theme` | true: dark pixels draw dense glyphs. false: bright pixels do. Unset, it follows the page. |
| `threshold` | `number` | `0.5` | Braille and quadrants: the tone that lights a dot. |
| `edgeThreshold` | `number` | `0.22` | With the edge charset: how strong a contour must be to draw a stroke. |
| `dither` | `"none" \| "bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"blue-noise" for Braille and quadrants, else "none"` | Ordered dithering between glyph levels and dots. |
| `background` | `"paper" \| "transparent"` | `"paper"` | transparent draws glyphs only, over whatever is behind. |
| `fx` | `PostFx` |  | CRT effects over the glyphs: scanlines, bloom, vignette. |
| `cell` | `number` | `9` | Glyph cell height in CSS pixels. |
| `animate` | `boolean` | `true` | false swaps between glyphs and photo without a sweep, as reduced motion does. |
| `crossOrigin` | `"anonymous" \| "use-credentials"` | `"anonymous"` | Passed to the image requests. |
| `handle` | `RefObject<AsciiHandle \| null>` |  | Filled with a handle for export: the glyph layer as text, ANSI, SVG or PNG. |

## Notes [#notes]

* The real `<img>` is always in the page and carries `alt`, so screen readers read the image in either state. The glyph layers are `aria-hidden`.
* The image loads lazily. The glyphs sample it once the box comes within about 300 px of the viewport, and the `<img>` uses `loading="lazy"`.
* With reduced motion, or `animate={false}`, there is no sweep. A `view` reveal shows the photo straight away. A `hover` reveal shows the glyphs until you hover or focus it, then swaps to the photo. A `manual` reveal shows whatever `revealed` says.
* The sweep is the only motion. Before it starts and after it ends the component draws nothing new until a prop or the box size changes. Offscreen and in a hidden tab the sweep pauses where it is.
* During the sweep the photo is drawn through a mask of one pixel per cell, so the handover lines up with the glyph grid. At rest the `<img>` itself shows.
* With `trigger="hover"` the box is focusable. If it sits inside a link or a button, use `trigger="manual"` and drive `revealed` from that element's hover and focus instead.
* The sample photos are public domain, from NASA, the Hubble Heritage Team and Ansel Adams for the US National Park Service, all from Wikimedia Commons.