# Tunnel

A slow flight down a tunnel of round or square rings, drawn as a dither.

> 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 dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the dithercn 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 `DitherShader` ([Write your own dither](/docs/engine/write-your-own.md)), and read this example's source for technique. Its full source is in [https://dithercn.dev/r/tunnel.json](https://dithercn.dev/r/tunnel.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/tunnel.json
```

## Usage [#usage]

```tsx
import { Tunnel } from "@/components/dithercn/tunnel";

export function Hero() {
  return (
    <section className="relative isolate min-h-[480px]">
      <Tunnel className="absolute inset-0 -z-10" />
      <h1>Your headline</h1>
    </section>
  );
}
```

Rings come out of the fog at the vanishing point and grow as they pass you. Spokes run along the walls, and every other panel between them is lit, like tiles. The middle stays empty, so a headline fits there.

## Examples [#examples]

### Behind a headline [#behind-a-headline]

`spokes={0}` leaves only the rings, and fewer `rings` spread them out. A larger `fog` widens the empty middle so the text sits clear of the rings.

```tsx title="examples/tunnel-headline.tsx"
import { Tunnel } from "@/components/dithercn/tunnel";

export default function TunnelHeadline() {
  return (
    <section className="relative isolate flex size-full flex-col items-center justify-center gap-3 px-6 text-center">
      <Tunnel
        spokes={0}
        rings={0.8}
        fog={2.6}
        className="absolute inset-0 -z-10"
      />
      <h3 className="max-w-xs text-3xl font-medium tracking-[-0.03em] text-balance">
        Further in
      </h3>
      <p className="text-muted-foreground max-w-[16rem] text-sm text-pretty">
        The rings fade out before they reach the middle, which leaves a clear
        space for a few words.
      </p>
    </section>
  );
}
```

### Square corridor [#square-corridor]

`shape="square"` turns the rings into frames. Moving the `center` up a little and lowering the `sway` gives a steadier corridor, and a three-colour palette lights the near walls.

```tsx title="examples/tunnel-square.tsx"
import { Tunnel } from "@/components/dithercn/tunnel";

export default function TunnelSquare() {
  return (
    <Tunnel
      shape="square"
      spokes={8}
      rings={1.3}
      center={[0.5, 0.42]}
      sway={0.4}
      matrix="bayer8"
      palette={["--dither-paper", "--chart-5", "--chart-1"]}
      className="absolute inset-0"
    />
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `shape` | `"circle" \| "square"` | `"circle"` | The cross-section of the tunnel. |
| `rings` | `number` | `1` | How close together the rings are. 2 packs twice as many in. |
| `spokes` | `number` | `12` | Lines along the walls, from 0 to 32. 0 leaves only the rings and drops the tiles. |
| `center` | `[number, number]` | `[0.5, 0.5]` | The vanishing point, as fractions of the box from the top left. |
| `sway` | `number` | `1` | How far the vanishing point wanders. 0 holds it still. |
| `fog` | `number` | `1` | Size of the empty fog in the middle. 2 doubles it to clear room for text. |
| `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. |
| `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. |
| `speed` | `number` | `1` | How fast it moves. |
| `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. |
| `className` | `string` | `–` | Size and position. The background fills this box. |

## Notes [#notes]

* It is decorative and hidden from assistive technology. Put real text on top of it, not inside it.
* With reduced motion turned on, it draws one still frame and stops.
* It pauses when scrolled out of view or when the tab is hidden, and releases its WebGL context when far offscreen. Without WebGL it shows concentric CSS rings in the same colours.
* It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance).