# Glyph trail

A layer where the pointer paints glyphs that burn bright, cool through the charset and drift away like smoke, with a slow wanderer when nobody draws.

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

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/glyph-trail.json
```

## Usage [#usage]

```tsx
import { GlyphTrail } from "@/components/asciicn/glyph-trail";

export function Hero() {
  return (
    <GlyphTrail className="flex min-h-[480px] items-center justify-center">
      <h1>Your headline</h1>
    </GlyphTrail>
  );
}
```

Move the mouse, a pen or a finger across the box. The pointer lays puffs of glyphs along its path. Each one starts hot, in the densest glyphs of the charset and the `hot` colour, then cools: its glyphs thin out toward the sparse end of the charset, its colour fades to `cool`, and it drifts up and aside on a slow current before it vanishes. The tail frays into wisps while the head stays solid.

Speed sets how much is laid down. A slow move leaves a short, thin line. A quick flick lays hotter, wider puffs, throws a few single-glyph sparks and leaves a long comet behind it.

While nobody draws, a wanderer traces a slow loop across the box and leaves the same trail, so the first frame is never empty. It fades out when the pointer starts drawing and comes back after a few seconds of stillness. Set `idle={false}` for a layer that stays blank until someone touches it.

Children render above the trail. The trail clears itself behind their text and controls, so a headline keeps its contrast while the smoke flows around it. Set `clearContent={false}` to let it pass straight behind. With no children it is a plain layer: place it with `className`, behind or over a section.

```tsx
<section className="relative">
  <YourContent />
  <GlyphTrail className="absolute inset-0 z-10" />
</section>
```

## Examples [#examples]

### A hero with a button [#a-hero-with-a-button]

The headline and the button are children, so they sit above the trail, which clears around them. The trail never takes pointer events, so the button clicks as usual.

```tsx title="examples/glyph-trail-hero.tsx"
"use client";

import { useState } from "react";

import { GlyphTrail } from "@/components/asciicn/glyph-trail";
import { Button } from "@/components/ui/button";

export default function GlyphTrailHero() {
  const [count, setCount] = useState(0);
  return (
    <GlyphTrail
      decay={2.2}
      className="flex h-[420px] w-full flex-col items-center justify-center gap-4 px-6 text-center"
    >
      <p className="text-muted-foreground font-mono text-xs tracking-[0.3em] uppercase">
        Move the pointer
      </p>
      <h3 className="max-w-md text-3xl font-semibold tracking-tight text-balance">
        Every move leaves a mark
      </h3>
      <Button className="mt-2" onClick={() => setCount((value) => value + 1)}>
        {count === 0 ? "Click through the trail" : `Clicked ${count}×`}
      </Button>
    </GlyphTrail>
  );
}
```

### A small card [#a-small-card]

Braille draws the trail in fine dots on a card. A wider `spread` keeps the dots in a full plume at a small cell size.

```tsx title="examples/glyph-trail-card.tsx"
import { GlyphTrail } from "@/components/asciicn/glyph-trail";

export default function GlyphTrailCard() {
  return (
    <GlyphTrail
      charset="braille"
      cell={12}
      spread={2.2}
      className="bg-card flex h-48 w-full max-w-sm flex-col justify-end overflow-hidden rounded-lg border p-5"
    >
      <span className="text-muted-foreground font-mono text-xs">~/drafts</span>
      <span className="mt-1 text-lg font-medium">Sketches, 14 files</span>
    </GlyphTrail>
  );
}
```

## Props [#props]

Every other `div` prop goes to the wrapper. `charset`, `chars`, `phosphor`, `grade`, `threshold`, `edgeThreshold`, `fx`, `speed`, `animate` and `handle` work as on the [ASCII image](/docs/images/ascii-image#props). `dither` defaults to `"bayer4"` for Braille and quadrants, so the smoke keeps a dot gradient.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `decay` | `number` | `2.4` | How long a glyph takes to cool and vanish, in seconds. |
| `spread` | `number` | `1.8` | How wide the trail fans out around the path, in cells. |
| `idle` | `boolean` | `true` | A slow wanderer draws the trail while nobody moves the pointer. |
| `charset` | `CharsetName` | `"standard"` | Which glyphs draw the trail, densest at the head. |
| `colorMode` | `"truecolor" \| "duotone" \| "mono"` | `"truecolor"` | "truecolor" cools from hot to cool, "duotone" uses ramp, "mono" draws in the ink. "mono" when a phosphor is set and no mode is. |
| `hot` | `AsciiColor` | `"orange"` | The colour of a fresh glyph in truecolor. |
| `cool` | `AsciiColor` | `"ink"` | The colour a glyph cools to in truecolor, and the ink in mono. |
| `ramp` | `RampName \| string[]` | `"ember"` | Duotone: a ramp name or stops as #rrggbb, cold to hot. |
| `background` | `"paper" \| "transparent"` | `"transparent"` | Fill the box behind the glyphs. "paper" when a phosphor is set. |
| `cell` | `number` | `14` | Glyph cell height in CSS pixels. |
| `fps` | `number` | `60` | Frame rate cap while the pointer draws. The wanderer runs at up to 30. |
| `stillTime` | `number` | `7` | Where the wanderer's path ends in the still frame drawn under reduced motion, in seconds. |
| `clearContent` | `boolean` | `true` | Clear the trail behind the children's text and controls so they stay readable. |
| `children` | `ReactNode` |  | Content drawn above the trail. |

## Notes [#notes]

* The canvas is decorative and hidden from assistive technology. Children stay in the accessibility tree as usual.
* The canvas has `pointer-events: none`. Moves are read from `window` and kept only when they land inside the box, so links and buttons under or over the trail still work. With no children the wrapper ignores pointer events too.
* Touch is read without blocking scroll: a finger dragging down the page scrolls it and leaves a trail.
* Under reduced motion it draws one still frame of the wanderer's path at `stillTime` and ignores the pointer. With `idle={false}` that frame is empty.
* It pauses offscreen and in hidden tabs. With `idle={false}` the loop stops entirely once the last glyph has burned out, and starts again when the pointer enters the box.
* The tail is drawn translucent, so it fades into whatever is behind the box. On a light page the hottest glyphs deepen instead of paling.