# Nebula

Cosmic gas drawn in glyphs: warped clouds with bright filaments, dark dust lanes and a few stars. Every seed is a different nebula.

> 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/nebula.json](https://asciicn.dev/r/nebula.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/nebula.json
```

## Usage [#usage]

```tsx
import { Nebula } from "@/components/asciicn/nebula";

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

Three layers of noise, each bending the next, give the gas its curls. The cloud has a broad soft edge that runs down through the middle of the glyph ramp, thin bright filaments trace where the middle layer folds, and dark lanes of dust cut through the densest parts. Stars show only where the gas is thin. The whole cloud turns slowly around the centre.

It draws in `quadrants` by default: each glyph splits into a 2×2 grid, which keeps the wisps fine while the cloud stays solid. `braille` gives a dusting of dots, `standard` a typed nebula.

Each seed gets its own palette, built in OKLCH: a first hue, then a turn of up to 120 degrees that never runs through the olive and khaki hues that turn muddy at glyph sizes. The palette comes in two versions, deep to bright on dark paper and pale to deep on light paper, and switches with the theme, so the densest glyphs always have the most contrast.

The parts are in `lib/ascii/seed.ts` (shipped with the `ascii-shader` item) if you want your own preset: `seedParams(seed, kind, options)` rolls the parameters, `seedField(seed, kind)` returns `{ field, colorField, uniforms }` for `AsciiShader`, and the `SeedField` component wraps both.

## Examples [#examples]

### Three seeds [#three-seeds]

The same component with three different `seed` strings.

```tsx title="examples/nebula-seeds.tsx"
import { Nebula } from "@/components/asciicn/nebula";

const SEEDS = ["orion-4", "tidepool", "ashfall"] as const;

export default function NebulaSeeds() {
  return (
    <div className="grid size-full grid-cols-3 gap-px">
      {SEEDS.map((seed) => (
        <figure
          key={seed}
          className="relative isolate min-h-40 overflow-hidden"
        >
          <Nebula seed={seed} className="absolute inset-0 -z-10" />
          <figcaption className="bg-background/80 absolute bottom-2 left-2 rounded-md px-2 py-0.5 font-mono text-xs">
            seed=&quot;{seed}&quot;
          </figcaption>
        </figure>
      ))}
    </div>
  );
}
```

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

An `intensity` of 0.55 keeps the gas in the lighter half of the ramp under the text.

```tsx title="examples/nebula-headline.tsx"
import { Nebula } from "@/components/asciicn/nebula";

export default function NebulaHeadline() {
  return (
    <section className="relative isolate flex size-full flex-col items-center justify-center gap-3 px-6 text-center">
      <Nebula
        seed="lantern"
        intensity={0.55}
        speed={0.6}
        className="absolute inset-0 -z-10"
      />
      <h3 className="max-w-md text-3xl font-medium tracking-[-0.03em] text-balance">
        Mission control for deep space
      </h3>
      <p className="text-muted-foreground max-w-sm text-sm text-pretty">
        An intensity of 0.55 keeps the gas in the lighter half of the ramp.
      </p>
    </section>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `seed` | `string` | `"nebula"` | Any string. The same seed always draws the same field; each new one rolls a different shape, palette and motion in the same family. |
| `intensity` | `number` | `1` | Scales the exposure the seed settled on, from 0 to 1.5. Lower it to put text on top. |
| `ink` | `AsciiColor` | `"--ascii-ink"` | The glyph colour in mono mode. |
| `paper` | `AsciiColor` | `"--ascii-paper"` | The surface behind the glyphs. |
| `palette` | `[AsciiColor, AsciiColor, ...AsciiColor[]]` | `Paper, then the seed's four colours` | Colours for palette mode, paper first. |
| `charset` | `CharsetName` | `"quadrants"` | Which glyphs draw the tones. See Charsets. |
| `chars` | `string` | `u2013` | A custom ramp from empty to dense, such as " .oO@". Wins over charset. |
| `colorMode` | `"mono" \| "truecolor" \| "ansi16" \| "duotone" \| "palette"` | `"truecolor"` | How glyphs are coloured. truecolor uses the seed's own OKLCH palette; palette maps tone to it; mono, duotone and ansi16 work as everywhere else. |
| `phosphor` | `"p1" \| "p3" \| "ibm" \| "blueprint"` | `u2013` | In mono mode, a CRT ink and glass that replace ink and paper. |
| `ramp` | `RampName \| string[]` | `u2013` | In duotone mode, the colour ramp from dark to light. |
| `ansi` | `"vga" \| "xterm"` | `"vga"` | In ansi16 mode, which 16-colour table. |
| `grade` | `Grade` | `u2013` | Brightness, contrast, gamma and saturation applied to tones before glyphs are picked. |
| `invert` | `boolean` | `false` | Dense glyphs for dark tones instead of bright ones. |
| `threshold` | `number` | `0.5` | Braille and quadrants: the tone that lights a dot. |
| `edgeThreshold` | `number` | `0.22` | Edge charset: how strong a change in tone must be to draw - / \| or \. |
| `dither` | `"none" \| "bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"none"` | Ordered dithering between neighbouring glyph levels. |
| `background` | `"paper" \| "transparent"` | `"paper"` | "transparent" draws the glyphs only, over your own surface. |
| `fx` | `PostFx` | `u2013` | CRT finish: scanlines, bloom, curvature, glitch, flicker and vignette, each 0 to 1. |
| `interaction` | `"none" \| "repel" \| "ripple" \| "trail"` | `"none"` | What the pointer does to the glyphs. Off by default, so the field ignores hover; opt in only where a pointer effect suits the page. |
| `interactive` | `boolean` | `true` | false turns pointer interaction off whatever interaction says. |
| `cell` | `number` | `12` | Height of one glyph 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. |
| `fps` | `number` | `30` | Frame rate cap. |
| `stillTime` | `number` | `8` | The moment drawn when motion is off or reduced, in seconds. |
| `handle` | `RefObject<AsciiHandle \| null>` | `u2013` | Filled with a handle that reads back the current frame, for export to text, ANSI, SVG or PNG. |
| `className` | `string` | `u2013` | 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 at `stillTime` and stops.
* By default it does not react to the pointer: moving the mouse over it changes nothing.
* 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 soft CSS glows in the seed's colours.
* A seed is rolled once, on the CPU, in a few milliseconds: a small exposure check samples the field at 40×22 at three moments and re-rolls (up to eight times) until between a quarter and three fifths of the box carries a glyph, then sets the gain so the brightest glyphs reach the top of the ramp. Changing `seed` re-rolls; changing the look props does not. See [Performance](/docs/engine/performance).