# Torus knot

A lit 3D torus knot tumbling in glyphs, with p and q windings, a breaks-at-crossings outline and drag to turn.

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

## Installation [#installation]

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

## Usage [#usage]

```tsx
import { TorusKnot } from "@/components/asciicn/torus-knot";

export function Hero() {
  return (
    <section className="relative h-[520px]">
      <TorusKnot p={3} q={7} className="absolute inset-0" />
    </section>
  );
}
```

A torus knot is a closed curve drawn on the surface of a ring. It winds `p` times around the ring's axis and passes `q` times through its hole. When `p` and `q` share no common factor you get one knot, such as the trefoil at `p={2} q={3}`. When they do, you get a link of several loops.

The tube is a real 3D mesh. It is swept along the curve, projected in perspective and rasterised into a depth buffer at the charset's own sample grid, so Braille gets 2×4 samples per cell and quadrants 2×2. Each sample is lit from its surface normal: a key light sets the tone, a tight highlight reaches the densest glyphs, and a rim brightens the edges. Parts farther from the camera fade, which puts the near strands in front.

In truecolor the colour comes from `colors`, the site's orange, pink, purple and blue by default. With `hue="film"` it shifts with the surface angle like oil on water, so the colour slides over the tube as it turns. With `hue="tube"` it runs along the knot instead.

Where one strand crosses over another, the strand underneath breaks just short of the one on top, the way knots are drawn by hand. That keeps every crossing readable even in a coarse charset. With ramp charsets, `contours` traces the silhouette with `- / | \` glyphs that follow the outline.

It turns slowly on its own axis and sways a little, so you see the knot near face on with its depth showing. It does not react to hover. Press and drag to tilt it. Let go mid-drag and it coasts, then stays where you left it. With keyboard focus, the arrow keys turn it.

## Examples [#examples]

### Braille, coloured along the tube [#braille-coloured-along-the-tube]

`hue="tube"` lays the `colors` stops along the tube and loops back to the first, so you can follow one strand round the whole knot. Braille samples 2×4 dots per cell, which gives a (3, 4) knot a fine outline and an even dot shading.

```tsx title="examples/torus-knot-braille.tsx"
import { TorusKnot } from "@/components/asciicn/torus-knot";

export default function TorusKnotBraille() {
  return (
    <TorusKnot
      p={3}
      q={4}
      thickness={0.17}
      charset="braille"
      hue="tube"
      colors={["orange", "pink", "purple"]}
      spin={0.35}
      cell={12}
      className="aspect-square w-full max-w-md"
    />
  );
}
```

### A trefoil beside text [#a-trefoil-beside-text]

Mono with a transparent background takes your theme's ink, so the knot sits on the page like an illustration. `interactive={false}` makes it decoration only.

```tsx title="examples/torus-knot-trefoil.tsx"
import { TorusKnot } from "@/components/asciicn/torus-knot";

export default function TorusKnotTrefoil() {
  return (
    <div className="grid w-full max-w-xl grid-cols-[1fr_auto] items-center gap-6">
      <div className="space-y-2 font-mono text-sm">
        <p className="text-muted-foreground">$ knot --p 2 --q 3</p>
        <p>trefoil, crossing number 3</p>
        <p className="text-muted-foreground">
          The simplest knot that can't be untied.
        </p>
      </div>
      <TorusKnot
        p={2}
        q={3}
        thickness={0.24}
        charset="dense"
        colorMode="mono"
        background="transparent"
        interactive={false}
        cell={9}
        className="size-48"
      />
    </div>
  );
}
```

## Props [#props]

Every [ASCII image](/docs/images/ascii-image#props) look prop also applies, with `charset` defaulting to `"blocks"`, plus `fx` for a CRT finish.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `p` | `number` | `2` | Times the tube winds around the central axis. Rounded to a whole number. |
| `q` | `number` | `5` | Times the tube passes through the hole. Rounded to a whole number. |
| `thickness` | `number` | `0.13` | Tube radius as a share of the knot's radius, 0.04 to 0.3. |
| `size` | `number` | `0.86` | How much of the box's shorter side the knot spans, 0.3 to 1. |
| `spin` | `number` | `0.25` | Turn around the knot's own axis, in radians per second. |
| `speed` | `number` | `1` | Multiplies the spin and the slow sway. 0 holds it still. |
| `colors` | `AsciiColor[]` | `["orange", "pink", "purple", "blue"]` | Truecolor stops, looping back to the first. |
| `hue` | `"film" \| "tube"` | `"film"` | Lay the stops by surface angle, like oil on water, or along the tube. |
| `light` | `number` | `320` | Where the key light comes from, in degrees clockwise from the top. |
| `contours` | `boolean` | `true` | Ramp charsets draw - / \| \ along the silhouette, following the outline. |
| `charset` | `CharsetName` | `"blocks"` | Which glyphs draw the tones. |
| `colorMode` | `"mono" \| "truecolor" \| "ansi16" \| "duotone" \| "palette"` | `"truecolor"` | How glyphs are coloured. |
| `interactive` | `boolean` | `true` | Drag or use the arrow keys to turn it. |
| `cell` | `number` | `10` | Glyph cell height in CSS pixels. |
| `animate` | `boolean` | `true` | false draws one still frame. |
| `handle` | `RefObject<AsciiHandle \| null>` |  | Filled with a handle for export. |

## Notes [#notes]

* It is announced as an image whose label names the knot. Pass `aria-label` to describe what it stands for on your page.
* Under reduced motion it draws one still frame. Dragging and the arrow keys still turn it.
* It draws at up to 60 fps on the CPU and pauses offscreen and in hidden tabs. The mesh is built once per shape; each frame only transforms it and fills the samples it covers. Large boxes in Braille cost the most, so raise `cell` for a full-screen hero.