# Usage: install and API reference

benday is distributed through the shadcn registry, not npm. The CLI copies the component into your project; there is no runtime dependency and no package to upgrade behind your back.

## Install

```bash
bunx shadcn@latest add @benday/benday
```

`@benday` is a namespace in shadcn's registry directory, so nothing has to be added to `components.json`. Seven files land under your UI directory: `benday.tsx` plus `benday/{bake,renderer,presets,dom,types,use-dot-map}.ts`. Only React is imported by any of them.

## Basic use

```tsx
import { Benday } from "@/components/ui/benday";

export function ThinkingIndicator({ busy }: { busy: boolean }) {
  return (
    <Benday src="/logo.svg" state={busy ? "thinking" : "done"} size={24} />
  );
}
```

`state` is the whole control surface: `thinking` runs the preset, while `idle` and `done` settle the dots back into the crisp mark through a spring. Changing props never restarts the animation, because the renderer is imperative and outlives React's render cycle.

## Bake at build time

Baking goes through a canvas, so it is browser-only and costs a few milliseconds on first paint. To skip it, run the bake once at build time and ship the result:

```ts
import { bake } from "@/components/ui/benday";

const dotMap = await bake("/logo.svg", { grid: 28 });
// write dotMap to JSON, import it, and pass it as `dotMap`
```

```tsx
<Benday dotMap={dotMap} state="thinking" />
```

`dotMap` takes precedence over `src`, and is plain JSON.

## Bake options

Passed as the `bake` prop, or as the second argument to `bake()`.

- `grid` (default `24`): dots across the longest side of the trimmed logo.
- `threshold` (default `0.18`): cell coverage required to emit a dot at all.
- `gamma` (default `1`): gamma on coverage before thresholding; below 1 boosts faint ink.
- `dilate` (default `0`): grow the mask by N working pixels. The rescue knob for hairline strokes.
- `maskMode` (default `'auto'`): `alpha`, `luma`, or `auto`, which picks alpha when the source has soft pixels.
- `invert` (default `false`): treat the luminance mask as inverted, for light ink on a dark background. Luma mode only.
- `trim` (default `true`): trim to the mask's bounding box before gridding.

## Props

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `src` | `BakeSource` | none | URL, data URI, File/Blob, or a loaded HTMLImageElement |
| `dotMap` | `DotMap` | none | A pre-baked map. Takes precedence over src |
| `bake` | `BakeOptions` | none | grid, threshold, gamma, dilate, maskMode, invert, trim |
| `size` | `number` | `64` | CSS pixels |
| `fit` | `'square' \| 'natural'` | `'square'` | 'natural' sizes to the mark, which is what wordmarks need |
| `state` | `BendayState` | `'thinking'` | thinking runs the preset; the others settle to the crisp mark |
| `preset` | `PresetName \| Preset` | `'contour'` | A preset name or your own per-dot function |
| `speed` | `number` | `1` | Multiplier |
| `color` | `string` | `'currentColor'` | Resolved off the canvas, re-resolved on theme change |
| `dotScale` | `number` | `0.62` | Dot size against a full-coverage halftone dot |
| `shape` | `DotShape` | `'circle'` | circle, square or diamond |
| `glow` | `number` | `0` | Halo radius as a fraction of the dot |
| `padding` | `number` | `0.06` | Inset as a fraction of the box |
| `weight` | `number` | `0.5` | How strongly ink coverage drives dot size |
| `paused` | `boolean` | `false` | Freeze on the current frame |
| `reducedMotion` | `boolean \| 'auto'` | `'auto'` | 'auto' follows prefers-reduced-motion |

## Presets

- `contour`, **Contour** (Signature): A wave follows the mark’s thickness from outline to core.
- `shimmer`, **Shimmer** (Signature): A lit band sweeps across the mark on the diagonal.
- `ripple`, **Ripple** (Signature): Concentric rings pulse outward from the center.
- `breathe`, **Breathe** (Signature): The whole mark swells and settles with a soft edge delay.
- `scan`, **Scan** (Sweep): A narrow vertical beam traverses the silhouette.
- `cascade`, **Cascade** (Sweep): One signal follows the lattice in serpentine order.
- `weave`, **Weave** (Sweep): Counter-moving diagonal bands cross through the mark.
- `rain`, **Rain** (Sweep): Offset droplets descend each column with soft tails.
- `swirl`, **Swirl** (Orbit): The mark twists around its center, with outer dots lagging.
- `orbit`, **Orbit** (Orbit): A soft energy point circles the center of the mark.
- `comet`, **Comet** (Orbit): A bright head and tapered tail chase around the mark.
- `radar`, **Radar** (Orbit): A rotating search beam crosses the logo with a soft wake.
- `pinwheel`, **Pinwheel** (Orbit): Three curved blades rotate around a steady core.
- `beacon`, **Beacon** (Orbit): Emphasis hands off between the four cardinal directions.
- `flicker`, **Flicker** (Field): A stable random subset of dots blinks at any moment.
- `wave`, **Wave** (Field): A soft traveling current bends the dot lattice.
- `equalizer`, **Equalizer** (Field): Independent column levels rise and fall like a spectrum.
- `resolve`, **Resolve** (Field): Dots assemble in stable random order, then dissolve.
- `scatter`, **Scatter** (Transform): Dots drift off the lattice, then reconverge into the mark.
- `magnetic`, **Magnetic** (Transform): The field pulls toward its center and releases.
- `glitch`, **Glitch** (Transform): Brief horizontal faults disturb a few rows, then clear.

Pass a name, or your own function, to `preset`:

```tsx
const pulse: Preset = (dot, t, out) => {
  const g = (Math.sin(t * 2 - dot.r * 5) + 1) / 2;
  out.a = 0.2 + 0.8 * g;
  out.s = 0.8 + 0.4 * g;
  out.dx = 0;
  out.dy = 0;
};

<Benday src="/logo.svg" preset={pulse} />;
```

A preset receives one dot's context (normalized position, radius, angle, ink coverage, tone, depth inside the shape, a stable random value) plus the clock, and writes a scale, an alpha and an offset in cell units into `out`. It runs once per dot per frame, so it writes into the reused object rather than allocating.

## Small sizes

A 16 to 20px indicator is the case the pipeline is tuned around. Below 32px the renderer adds optical weight, damps motion, snaps the lattice onto the device pixel grid, and consolidates cells that would fall under two CSS pixels. Check changes at the size you ship, not at 128px.

## Accessibility and cost

The canvas carries `role="img"` and an `aria-label` reflecting the state, which you can override. `prefers-reduced-motion` is followed by default: the mark shows, the motion does not. Painting stops when the element scrolls off-screen or the tab is hidden, so an idle indicator costs nothing.

## Where to go next

- [Playground](/playground.md): tune the bake and the motion on your own logo
- [Home](/index.md): what benday is and how the pipeline works
- Registry payload: <https://benday.kacemmathlouthi.dev/r/registry.json>

---

Canonical HTML: <https://benday.kacemmathlouthi.dev/usage>
Agent index: <https://benday.kacemmathlouthi.dev/llms.txt>
