Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/torus-knot.jsonUsage
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
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.
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.
Props
Every ASCII image look prop also applies, with charset defaulting to "blocks", plus fx for a CRT finish.
| Prop | Type | Default |
|---|---|---|
| p Times the tube winds around the central axis. Rounded to a whole number. | Type: number | Default 2 |
| q Times the tube passes through the hole. Rounded to a whole number. | Type: number | Default 5 |
| thickness Tube radius as a share of the knot's radius, 0.04 to 0.3. | Type: number | Default 0.13 |
| size How much of the box's shorter side the knot spans, 0.3 to 1. | Type: number | Default 0.86 |
| spin Turn around the knot's own axis, in radians per second. | Type: number | Default 0.25 |
| speed Multiplies the spin and the slow sway. 0 holds it still. | Type: number | Default 1 |
| colors Truecolor stops, looping back to the first. | Type: AsciiColor[] | Default ["orange", "pink", "purple", "blue"] |
| hue Lay the stops by surface angle, like oil on water, or along the tube. | Type: "film" | "tube" | Default "film" |
| light Where the key light comes from, in degrees clockwise from the top. | Type: number | Default 320 |
| contours Ramp charsets draw - / | \ along the silhouette, following the outline. | Type: boolean | Default true |
| charset Which glyphs draw the tones. | Type: CharsetName | Default "blocks" |
| colorMode How glyphs are coloured. | Type: "mono" | "truecolor" | "ansi16" | "duotone" | "palette" | Default "truecolor" |
| interactive Drag or use the arrow keys to turn it. | Type: boolean | Default true |
| cell Glyph cell height in CSS pixels. | Type: number | Default 10 |
| animate false draws one still frame. | Type: boolean | Default true |
| handle Filled with a handle for export. | Type: RefObject<AsciiHandle | null> | Default – |
Notes
- It is announced as an image whose label names the knot. Pass
aria-labelto 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
cellfor a full-screen hero.