# Ink Reveal (Moonarc)

Covers content with a layer that the cursor washes away like ink in water, showing what is underneath. The content is ordinary HTML in the slot, in the page for readers, search and keyboard users from the start. The cover is a canvas above it, drawn by the GPU: a fluid simulation whose dye is the hole, with a ragged, moving edge and a soft grain. The ink fades back at `fade` per second (0 keeps what was revealed). A small swirl opens at the centre on the first frame to say there is something underneath. Focus inside, reduced motion, a touch device without hover, no WebGL2 or no JavaScript all show the content with no cover.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/ink-reveal` (license key required)
- Tier C · category pointer · trigger pointer
- Readout: `<InkReveal fade={0.35}>`
- Browser support: widely (Chrome 56 · Firefox 51 · Safari 15); elsewhere: without WebGL2 or a colour-renderable float texture, on a device without hover, or with reduced motion or focus inside, no cover: the content shows as it is
- Measured cost: 1.6 kB raw JS · 1.0 kB gzip · + runtime + canvas + gl + fluid, 12.5 kB raw in all (with dependencies 12.5 kB raw; CSS 4.7 kB raw)
- Page: https://moonarc.dev/components/ink-reveal/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `color` | `string` | `'oklch(0.22 0.02 260)'` | Cover colour; any CSS colour, theme tokens included. |
| `grain` | `string` | `color lifted 15 % toward white` | A second tone mixed into the cover as a soft grain. |
| `radius` | `number` | `0.5` | Brush radius as a share of the box. |
| `curl` | `number` | `18` | How much the ink swirls. |
| `fade` | `number` | `0.35` | How fast the cover comes back, per second. 0 keeps the reveal. |
| `rough` | `number` | `0.5` | How ragged the ink's edge is, 0–1. |

## Usage

```astro
<InkReveal class="aspect-video">
  <img src="/poster.webp" alt="…" class="h-full w-full object-cover" />
</InkReveal>
```

## Reduced motion

No cover at all: the content shows as it is. The canvas is hidden by the media query, and the page's own motion switch removes it through the runtime.

## With ClientRouter

Bound through the shared runtime: loop, observers and pointer listener are released before the swap and the WebGL context is lost on purpose after it; five round trips leave one context. Under transition:persist the canvas keeps its context and starts again on the next page.

## Craft

- The content is never drawn by the GPU: it is the page. The canvas only covers it, so text stays selectable, indexable and readable to a screen reader under an unrevealed cover.
- The dye of a fluid is the hole: a reveal swirls and spreads after the pointer instead of stamping circles.
- A drifting two-octave value noise pushes the threshold in and out: the edge is ragged and alive, never a blurred disc.
- :focus-within fades the cover: tabbing to a link underneath shows it, which a pointer-only reveal would hide from keyboard users.
- Ink per move has a floor, so a slow pointer still opens the cover, and grows with speed up to a cap.

## Replaces

- Mask reveal / ink cursor (React Bits Pro)
- Scratch card reveal

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/ink-reveal` with a key in components.json (https://moonarc.dev/account/). Related free primitives: spotlight.
