# Pixel reveal (goodtabs UI)

An image that resolves from large square pixels to sharp as it enters view.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/pixel-reveal.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/pixel-reveal`

## Usage

```tsx
import { PixelReveal } from "@/components/ui/pixel-reveal"

<PixelReveal trigger="scroll" src="/photo.jpg" alt="What the photo shows" className="rounded-lg" />
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| src | `string` |  | The image. Same origin, or served with CORS headers. |
| alt | `string` |  | Alt text for the real image, which stays in the DOM. |
| trigger | `"scroll" | "view"` | `"view"` | "scroll" ties the reveal to scroll position, "view" plays it once on enter. |
| cell | `number` | `64` | Size of the largest block in CSS pixels. |

## When to use

- One large image below the fold on a landing page
- A case study cover that should arrive with some ceremony
- A section about rendering, loading or image work, where the effect makes the point

## When not to use

- On the first screen: the sharp image paints first, then the blocks would pop in over it
- On a grid of images, where many canvases compete and the effect wears out
- On images people need right away, such as product photos in a checkout

## Notes for agents

- It defaults to a 3:2 box. Pass aspect-auto and a height to fill something else, and the shader crops the same way as object-cover
- A real img with its alt is always in the DOM under the canvas. Under reduced motion no canvas mounts, and without WebGL the img simply shows
- With trigger="view" the canvas is removed when the reveal ends. With trigger="scroll" it stays mounted so the reveal can run backward
- The image must be same origin or send CORS headers. It depends on GSAP and ScrollTrigger, installed from npm under GSAP's own licence

## The maths

- Blocks halve from 64px to 2px, which is log2(64 / 2) + 1 = 6 levels, and a loop of 6 stays well under the 32 step limit.
- A block splits when clock passes level + 0.9 × noise + 1.4 × distance from the centre, so the latest possible split is 5 + 0.9 + 1.4 = 7.3.
- The clock is progress^1.5 × 7.31, which puts level 3, the 8px blocks, at about 55 percent of the travel instead of 41.

Docs: https://ui.goodtabs.co/components/pixel-reveal