# Introduction A dither engine your coding agent uses to build any dither for your landing page or dashboard. > 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). dithercn is a dither engine with an agent skill. Ask your coding agent for a dither, say "a dithered topographic background behind the hero" or "a radial usage meter for the billing card", and it builds that component on the engine, in your shadcn theme, with motion, light and dark mode and reduced motion handled. The components in these docs are examples of what the engine can make. Install one when it fits, read its source when you want to see how it was done, and ask your agent for something new when it doesn't. ## What you get [#what-you-get] * **The engine.** `dither-core` installs into `lib/dither` as plain source. It turns a tone you compute into dither dots: a WebGL shader for anything that moves or varies per pixel, SVG patterns and CSS masks for static fills, image dithering, and export to PNG, SVG and CSS. * **The agent skill.** A `SKILL.md` and references that teach Claude Code, Codex, Cursor and other agents the engine: how to pick a technique, write a field, place it on a page, colour it from your theme and check it in a browser. [Installation](/docs/installation) adds it to your project. * **The examples.** More than forty backgrounds, charts, interface pieces, text effects and image treatments, each installable with `npx shadcn add`. Browse them in the [catalog](/docs/examples). ## What a dither is [#what-a-dither-is] A dither draws a smooth tone using only a few colours. It places dots of ink at a density that matches the tone, so your eye blends them into a gradient. Dithering began as a workaround for limited screens and printers. It now reads as a deliberate texture, a little like risograph print, and it suits both a hero that should feel crafted and a dashboard that should feel calm. ## Ask your agent for a dither [#ask-your-agent-for-a-dither] Once the skill is installed, describe what you want and where it goes. Name the page, the mood and anything that must stay readable: * "Add a dithered topographic contour background behind the hero on the home page. Slow, quiet, readable headline." * "On the billing page, add a dithered radial meter for storage used, 72%, with the number in the middle." * "Make the pricing section's top edge dissolve into the page with a dithered fade in our accent colour." * "Dither the team photos in the about page in our primary colour, and make them sharpen on hover." * "Replace the empty state on the inbox with a small dithered illustration of a paper plane." You don't need to know which component or API fits. The skill sends the agent through four steps: 1. **Set up the engine.** It checks for `lib/dither` and installs `dither-core` if it's missing. 2. **Pick a technique.** A WebGL field for anything that moves or varies per pixel, SVG patterns for chart and meter fills, CSS masks for blocks and bars, image dithering for photos, and export for files. If an example in the [catalog](/docs/examples) is close, it installs that and edits it. 3. **Build the component.** It writes a typed component with props you can tweak, places it behind or inside your content, and colours it from your theme. 4. **Check it in a browser.** It looks at the result in light and dark mode, at desktop and phone widths and with reduced motion, and fixes what doesn't read. ## How the docs are organised [#how-the-docs-are-organised] * **Get started:** this page, [Installation](/docs/installation) and [Theming](/docs/theming). * **Examples:** the [catalog](/docs/examples) of every component, then one page per component, grouped into Backgrounds, Charts, Interface, Text and Images. * **Engine:** how `dither-core` works, for when you want to build a dither yourself. Start with the [dither engine](/docs/engine/dither-engine) and [Write your own dither](/docs/engine/write-your-own). ## Principles [#principles] * **Your theme, not ours.** Dithers read your `--primary` and `--background` tokens through the [`--dither-*` variables](/docs/theming), so they change when your theme changes. * **Quiet by default.** Motion is slow, and every animated dither holds still when the visitor prefers reduced motion. * **Cheap on dashboards.** Static fills are SVG patterns and CSS masks, and shaders draw one pixel per cell and stop when they scroll out of view. * **Real text stays text.** Dithered headings and labels are still HTML, so they can be selected, read aloud and indexed. * **Source you own.** Everything installs as files in your project. You and your agent can read and change all of it. ## Credits [#credits] [DitherKit](https://www.tripwire.sh/dither-kit) by Boring Software set the bar for this project. The fill variants, bloom, named colours, scrubbing chart tooltip, legend spotlight and lock, docs knobs, generative avatars and the button that thickens its dither on hover all started as ideas we saw there. The code and the look are our own. DitherKit credits [EvilCharts](https://evilcharts.com) for the dithered chart style it grew from, so thanks to both. # Installation Add the dithercn engine and agent skill to a shadcn project, or install a single example. > 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). dithercn installs with the shadcn CLI, like any other registry. One command adds the engine and the agent skill; after that you ask your agent for dithers, or install examples one by one. ## Quick start with your agent [#quick-start-with-your-agent] Paste this prompt into Claude Code, Codex, Cursor or any other coding agent. It installs the engine and the skill, reads the docs and builds the dither you ask for. It's the same prompt the home page copies. The rest of this page does the same steps by hand. ## Before you start [#before-you-start] You need a project that already uses shadcn/ui, with a `components.json` file, Tailwind CSS v4 and React 19. If you don't have one yet, follow the [shadcn installation guide](https://ui.shadcn.com/docs/installation) first. ## Add the skill with the skills CLI [#add-the-skill-with-the-skills-cli] The [skills CLI](https://skills.sh) installs the skill into Claude Code, Codex, Cursor, Gemini CLI and other agents that read skills. Run it in your project: It asks which agents to install for, then writes the skill and its references into each agent's skills folder, such as `.claude/skills/dithercn/` and `.agents/skills/dithercn/`. Add `-g` to install it for every project on your machine. The skill adds the engine to your project the first time your agent builds a dither. ## Or add the engine and the skill with shadcn [#or-add-the-engine-and-the-skill-with-shadcn] ### Run the add command [#run-the-add-command] ```bash npx shadcn@latest add https://dithercn.dev/r/dithercn-skill.json ``` This writes: * `lib/dither/`, the engine (`dither-core`) and its export functions (`dither-export`). * `.agents/skills/dithercn/` and `.claude/skills/dithercn/`, the agent skill. Claude Code reads `.claude/skills`; Codex, Cursor and other agents that follow the [Agent Skills](https://agentskills.io) layout read `.agents/skills`. * The `--dither-*` CSS variables, in the CSS file named in your `components.json`. Commit the skill folders with the rest of your code, so everyone on the project, and every agent, gets the same skill. ### Check your CSS [#check-your-css] The CLI adds the variables for light and dark mode, and colour utilities to the `@theme inline` block: ```css @theme inline { --color-dither-ink: var(--dither-ink); --color-dither-paper: var(--dither-paper); /* and one per named colour: --color-dither-blue, --color-dither-green, … */ } @layer base { :root { --dither-ink: var(--primary); --dither-paper: var(--background); --dither-cell: 3px; } .dark { --dither-ink: var(--primary); --dither-paper: var(--background); --dither-cell: 3px; } } ``` Because the variables point at your theme tokens, there is nothing extra to set for dark mode. [Theming](/docs/theming) lists every variable. Then ask your agent for a dither. The [introduction](/docs#ask-your-agent-for-a-dither) has example requests and explains what the agent does with them. ## What's in the skill [#whats-in-the-skill] | File | What it covers | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SKILL.md` | The four steps the agent follows, and the rules for every dither | | `references/engine-api.md` | Every engine export: `DitherShader`, `FillPattern`, `fillStyle`, colours, motion hooks, `ditherImage` and export | | `references/fields.md` | How to write a GLSL field, with building blocks for gradients, noise, contour lines, rings, arcs and shading | | `references/recipes.md` | Complete components to adapt: a shader background, a hero that clears its headline, an SVG chart fill, a progress bar, a dithered photo, a CSS-only band | | `references/landing-pages.md` | Designing the page around the dither: start from the product, one memorable hero, true copy, the tells to avoid | | `references/placement.md` | Putting a dither on a landing page or dashboard: layering, sizing, server and client components | | `references/theme.md` | Tokens, named colours, palettes, light and dark mode | | `references/quality.md` | The checks: looks, contrast, reduced motion, accessibility, performance | | `references/examples.md` | Every example component, with its install command and docs link | The skill matches the engine it installs with and points your agent at the files in your project, so it never has to guess at an API. ## Install the skill for every project [#install-the-skill-for-every-project] `shadcn add` writes the skill into the current project only. To make it available in every project on your machine, run the skills CLI with `-g`. Without the CLI, download the files into your user skills folder: ```bash SKILL=https://dithercn.dev/.well-known/agent-skills/dithercn for dir in ~/.claude/skills/dithercn ~/.agents/skills/dithercn; do mkdir -p "$dir/references" curl -fsSL "$SKILL/SKILL.md" -o "$dir/SKILL.md" for ref in engine-api examples fields landing-pages placement quality recipes theme; do curl -fsSL "$SKILL/references/$ref.md" -o "$dir/references/$ref.md" done done ``` The skill tells the agent to install the engine into a project the first time it needs it. ## Install the engine or one example [#install-the-engine-or-one-example] Without the skill, install the engine on its own: ```bash npx shadcn@latest add https://dithercn.dev/r/dither-core.json ``` Or install any example. Each one brings the engine with it on its first install: ```bash npx shadcn@latest add https://dithercn.dev/r/dither-gradient.json ``` ## Use a namespace [#use-a-namespace] To install by name instead of by URL, add dithercn to the `registries` field of your `components.json`: ```json title="components.json" { "registries": { "@dithercn": "https://dithercn.dev/r/{name}.json" } } ``` Then install with the `@dithercn` prefix: ```bash npx shadcn@latest add @dithercn/dithercn-skill ``` See the shadcn docs on [namespaced registries](https://ui.shadcn.com/docs/registry/namespace) for more options, including authentication. ## Other ways in for agents [#other-ways-in-for-agents] The skill is the main way in. The site also publishes plain-text surfaces for agents that browse: * [`/llms.txt`](/llms.txt) lists every docs page with a one-line description and a Markdown link, and [`/llms-full.txt`](/llms-full.txt) is the whole documentation as one file. * Add `.md` to any docs URL for its Markdown, for example [`/docs/installation.md`](/docs/installation.md), or send an `Accept: text/markdown` header. Every page also has a **Copy page** menu. * The registry index is at [`/r/registry.json`](/r/registry.json), and each item at `/r/.json`. The [shadcn MCP server](https://ui.shadcn.com/docs/mcp) can browse and install from it once you have [added the `@dithercn` namespace](#use-a-namespace). * [`/.well-known/api-catalog`](/.well-known/api-catalog) links every machine-readable surface (RFC 9727), [`/openapi.json`](/openapi.json) describes the read-only endpoints, and [`/api/status`](/api/status) returns `{"status":"ok"}`. The home page sends these as `Link` headers too. ## Next steps [#next-steps] * [Ask your agent for a dither](/docs#ask-your-agent-for-a-dither) with the skill installed. * Browse the [catalog](/docs/examples) for a component to install as it is. * [Write your own dither](/docs/engine/write-your-own) by hand, step by step. # Theming How dithercn reads your shadcn theme, and how to override colours and cell size. > 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). Every dither reads a few CSS variables, and the main two point at your existing shadcn tokens by default. Change them and every dither on the page follows, including the ones your agent writes. ## The variables [#the-variables] | Variable | Default | What it controls | | ----------------------- | --------------------- | --------------------------------------------------------------- | | `--dither-ink` | `var(--primary)` | The colour of the dots | | `--dither-paper` | `var(--background)` | The colour behind the dots | | `--dither-cell` | `3px` | The size of one dither cell in CSS pixels | | `--dither-bloom` | `1` dark, `0.6` light | How strong glows are | | `--dither-color-` | tuned per theme | The named colours: blue, purple, green, pink, orange, red, grey | [`dither-tokens`](/docs/installation) writes these into both `:root` and `.dark`. Components and the engine accept the names `"ink"`, `"blue"`, `"green"` and so on wherever they take a colour, and Tailwind gets matching utilities such as `bg-dither-blue`. When the visitor switches to dark mode, `--primary` and `--background` change, and every dither changes with them. ## Override for one section [#override-for-one-section] Variables cascade, so you can retint a single part of the page by setting them on a wrapper: ```css .pricing-hero { --dither-ink: var(--chart-2); --dither-cell: 4px; } ``` Anything inside `.pricing-hero` uses the new ink and a coarser grain. The rest of the page is unchanged. ## Pick colours that still read [#pick-colours-that-still-read] A dither only reads when ink and paper differ enough. As a rule, keep the same contrast you would want for body text, about 4.5:1, between `--dither-ink` and `--dither-paper`. Low-contrast pairs look fine as a quiet background, but they lose detail in charts and images. ## Multi-step palettes [#multi-step-palettes] Two-tone dithers cover most backgrounds and interface pieces. Components that need more steps take a `palette` prop: a list of colours from paper to ink, such as your `--chart-1` to `--chart-5` tokens. [Palettes](/docs/engine/palettes) shows how a palette ramp is dithered. # Aurora Curtains of light that fold, sway and shimmer, drawn as a dither, with knobs for colour, rays, tilt and drift. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Aurora } from "@/components/dithercn/aurora"; export function Hero() { return (

Your headline

); } ``` Each curtain has a bright lower edge that folds slowly, with rays that fade as they rise. Further curtains hang higher and dimmer. `height` sets where the lowest edge hangs, so you can keep the area under it clear for text. The props group into three jobs: * Shape: `curtains`, `height`, `spread`, `sway`, `turbulence`, `rays`, `length`. * Light and colour: `color` for the edges, `tipColor` for the tops of the rays, `background`, `glow`, `intensity`, `shimmer`. Pass `palette` to set every tone yourself. * Placement and motion: `tilt`, `scale`, `drift`, `direction`, `mask`, plus the engine's `speed`, `cell` and `matrix`. ## Examples [#examples] ### Above a headline [#above-a-headline] Raising `height` lifts the curtains off the lower third, where the headline sits on clean paper. A `tipColor` of `"blue"` shifts the tops of the rays to a second, cooler hue, and `mask="edges"` fades the sides. ### Northern lights [#northern-lights] Your own colours turn it into the real thing: green edges, violet tips and a night-sky `background`. Four curtains, longer rays, a slight `tilt` and a little `drift` give it depth. ### Hanging over a call to action [#hanging-over-a-call-to-action] `direction="down"` flips the curtains so the rays fall from the top and the button sits on clear paper below. Few broad `rays` and 4px cells give it a coarser, poster-like grain. ### Still, in a banner [#still-in-a-banner] With `animate={false}` it draws one frame. `stillTime` picks which moment, so you can choose a shape you like. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"--dither-ink"` | The bright lower edge of each curtain. | | `tipColor` | `DitherColor` | `color` | The faint tops of the rays. Set a second colour for a two-tone sky. | | `background` | `DitherColor` | `"--dither-paper"` | The sky behind. | | `curtains` | `number` | `3` | How many curtains, from 1 to 6. Each one hangs higher and dimmer. | | `height` | `number` | `0.42` | Where the lowest bright edge hangs, from 0 (bottom) to 1 (top). | | `spread` | `number` | `0.11` | Vertical gap between curtains, from 0 to 0.3. | | `sway` | `number` | `1` | How far the edges fold. 0 is straight, 3 is deep folds. | | `turbulence` | `number` | `0.6` | Fine ripples along each edge, from 0 to 2. | | `rays` | `number` | `8` | How many rays fit across. 2 gives broad bands, 20 fine needles. | | `length` | `number` | `0.45` | How far the rays reach up, from 0 to 1. | | `glow` | `number` | `0.9` | Brightness of the line along each edge, from 0 to 2. | | `intensity` | `number` | `0.95` | Brightness of the curtains, from 0 to 1. | | `shimmer` | `number` | `0.2` | A slow brightness ripple, from 0 to 1. | | `scale` | `number` | `1` | Horizontal zoom. Higher values fit more folds across. | | `tilt` | `number` | `0` | Tilt of the curtains in degrees, from -45 to 45. | | `drift` | `number` | `0` | Sideways travel, from -2 (left) to 2 (right). | | `direction` | `"up" \| "down"` | `"up"` | "down" flips it so the rays hang from a bright top edge. | | `mask` | `"none" \| "top" \| "bottom" \| "edges"` | `"none"` | Fades it to paper where your text sits. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Built from the colour props` | Takes over the colours: paper first, brightest last. Palette names, CSS variable names or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the field snaps to before dithering. Low values give hard steps. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it moves. | | `animate` | `boolean` | `true` | false draws one still frame. | | `stillTime` | `number` | `0` | The moment drawn when motion is off or reduced, in seconds. | | `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 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 a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. More `curtains` cost a little more per pixel. See [Performance](/docs/engine/performance). # Beams God rays: shafts of light from a point or an edge that sweep slowly and fade with distance. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Beams } from "@/components/dithercn/beams"; export function Hero() { return (

Your headline

); } ``` Light comes from `origin` and spreads in a cone around `angle`. The rays are noise around the origin, so they widen with distance like real shafts of light, and dust drifting through them breaks them up along their length. The whole cone sways back and forth by `sweep` degrees over about a minute. ## Examples [#examples] ### From the top edge [#from-the-top-edge] An origin far above the box gives nearly parallel rays, like light through a high window. Brightness fades from where the light enters the box, not from the far origin. ### Spotlight over a headline [#spotlight-over-a-headline] A narrow cone from just above the centre, through a four-colour palette, with a short `reach` so the text at the bottom sits in the dark. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `origin` | `[x: number, y: number]` | `[0.18, -0.12]` | Where the light comes from, as fractions of the box from the top left. Values outside 0 to 1 put it out of frame. | | `angle` | `number` | `160` | Direction the light points, in degrees as in CSS: 180 is straight down. | | `spread` | `number` | `80` | Width of the cone in degrees. | | `rays` | `number` | `9` | Roughly how many separate rays. | | `sweep` | `number` | `6` | How far the cone sways each way, in degrees. 0 holds it still. | | `reach` | `number` | `1` | How far the light reaches from where it enters the box, as a share of the box diagonal. | | `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. | | `levels` | `number` | `All the matrix can draw` | Tones before dithering. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the rays drift and sway. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The light fills this box. | ## Notes [#notes] * The light 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 a soft CSS cone. # Contours A topographic map drawn as a dither: contour lines over slowly drifting hills, with heavier index lines and a light hillshade. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Contours } from "@/components/dithercn/contours"; export function Section() { return (

Your heading

); } ``` Contour lines trace even steps of height across a field of low hills. Every fifth line is drawn heavier, as on a survey map, and slopes facing away from a light in the top left pick up a light stipple. The hills drift and slowly change shape. They keep their size in pixels, so a narrow box shows a smaller piece of the same map rather than a squashed one. ## Examples [#examples] ### Survey map [#survey-map] More `lines`, a smaller `scale` and a little more `relief` in green and brown give an old ordnance survey sheet. `cell={2}` keeps the lines fine. ### Behind a headline [#behind-a-headline] Set `index` and `relief` to 0 for plain lines, and pass two soft tints of the ink so the lines never reach full contrast. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `lines` | `number` | `14` | How many contour lines from the lowest ground to the highest, from 4 to 32. | | `scale` | `number` | `1` | Size of the hills. Higher values pack more of them in. | | `index` | `number` | `5` | Every how many lines one is drawn heavier. 0 draws them all the same. | | `relief` | `number` | `0.5` | How strongly slopes facing away from the light are shaded, from 0 to 1. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, a tint of --dither-ink, then --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 the hills drift. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The map 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 faint CSS rings. # Dither fade A dithered edge that fades a section's content into a solid colour, for the seam between two sections or the end of a clipped list. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherFade } from "@/components/dithercn/dither-fade"; export function Changelog() { return (
); } ``` Put the fade inside a `relative` parent. It covers `size` along `side` and paints `color` over whatever is underneath: sparse cells on the content side, solid at the edge. The default colour is `--dither-paper`, your page background. It is still and drawn with CSS masks, so it renders on the server and uses no WebGL. Use as many as you like on one page. ## Examples [#examples] ### Between two sections [#between-two-sections] Give the fade the colour of the section below it, or leave the default when that section uses the page background. The upper section dissolves into the next one with no hard line. ### Both ends of a row [#both-ends-of-a-row] `side="left"` and `side="right"` fade a scrolling row at its ends. Different sizes on each side hint at which way there is more to see. ### Drifting edge [#drifting-edge] With `animate` the edge wanders slowly, drawn with WebGL. Keep it for one hero-sized seam; the still fade is the better default. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | The edge of the parent the fade sits on. The colour is solid there. | | `size` | `number \| string` | `160` | Depth of the fade. A number is CSS pixels; a string is any CSS length, such as "30%". | | `color` | `DitherColor` | `"--dither-paper"` | The colour the content fades into: a palette name such as "grey", a CSS variable name, 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. | | `animate` | `boolean` | `false` | true lets the edge drift slowly, drawn with WebGL. | | `className` | `string` | `–` | Extra classes for the fade's box. | ## Notes [#notes] * The fade is decorative and hidden from assistive technology. Content under it stays in the page and is still read out, so do not hide anything important beneath it. * The still fade has no motion. With `animate`, reduced motion holds one still frame, and it pauses offscreen like the other backgrounds. * The still fade is 16 masked layers and no script. The animated one costs one small draw per frame and one WebGL context. # Dither globe A rotating dithered globe with a lit terminator, pulsing markers and arcs, for global sections of a landing page. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherGlobe } from "@/components/dithercn/dither-globe"; const LONDON = [51.51, -0.13] as const; const TOKYO = [35.68, 139.69] as const; export function Global() { return ( ); } ``` The globe is drawn on a 2D canvas at dither-cell resolution: every cell on the sphere gets a tone from the land mask and the light, then an ordered threshold decides whether it's inked. Land is bright on the day side and fades to a sparse grid past the terminator; the sea is a faint dither that thickens towards the rim. It uses no WebGL context, so it doesn't count against the page's shader budget. The globe stays round and centred in its box and takes 84% of the shorter side. Make the box larger than its section and offset it to show only the top of the globe, as in the first example. Visitors can turn the globe. Dragging sideways spins it and dragging up or down tilts it, with the land staying under the pointer. Let go mid-drag and it coasts to a stop, then picks its idle spin back up. On phones, a horizontal swipe turns it and a vertical swipe still scrolls the page. When it has keyboard focus, the arrow keys turn it. Set `draggable={false}` for a globe that should only spin on its own. Land comes from the shared `world-land` mask (Natural Earth, public domain), installed with the component. Nothing is fetched at runtime. ## Examples [#examples] ### Horizon behind a headline [#horizon-behind-a-headline] A globe 150% of the section's width, pushed below the bottom edge so only its lit top shows. ### Beside copy [#beside-copy] A square globe in a two-column section. Drag it to spin. ### Turned to one place, in a card [#turned-to-one-place-in-a-card] `speed={0}` and `start` at the marker's longitude hold one region in view while the marker keeps pulsing. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"ink"` | Colour of the land, sea and atmosphere. | | `accent` | `DitherColor` | `"orange"` | Colour of markers and arcs that don't set their own. | | `markers` | `{ at: [lat, lon]; color?: DitherColor }[]` | `[]` | Pulsing locations. Markers on the far side are hidden. | | `arcs` | `{ from: [lat, lon]; to: [lat, lon]; color?: DitherColor }[]` | `[]` | Great-circle arcs lifted off the surface, with a stretch of light running along them. | | `speed` | `number` | `6` | Degrees of rotation per second. Negative spins the other way. | | `start` | `number` | `10` | Longitude facing the viewer at the start, in degrees. | | `tilt` | `number` | `18` | How far the north pole leans towards the viewer, in degrees. | | `light` | `number` | `300` | Where the light comes from, in degrees clockwise from the top. | | `ocean` | `number` | `0.18` | Brightness of the sea on the lit side, 0 to 1. | | `atmosphere` | `boolean` | `true` | A thin dithered glow around the lit rim. | | `draggable` | `boolean` | `true` | Drag or use the arrow keys to turn and tilt the globe. It coasts after a drag, then eases back into its idle spin. | | `cell` | `number` | `2` | Size of one dither cell in CSS pixels. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold matrix. | | `animate` | `boolean` | `true` | Spin and pulse. false draws one still frame. | ## Notes [#notes] * A draggable globe is focusable and announced as an image labelled "Globe. Drag it or use the arrow keys to turn it." Pass your own `aria-label` to say what it shows. With `draggable={false}` it's decorative and hidden from assistive technology, so say what it shows in the copy next to it. * With reduced motion turned on, it draws one still frame. Dragging and the arrow keys still turn it, without the coasting. * It pauses when scrolled out of view or when the tab is hidden. A 600px globe at 2px cells is about 70,000 cells per frame at 30 fps, well within budget on a phone; raise `cell` for very large globes. # Dither glow A soft dithered spotlight from an edge, a corner or any point, for heroes, pricing cards and quiet section backgrounds. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherGlow } from "@/components/dithercn/dither-glow"; export function Hero() { return (

Your headline

); } ``` The glow fills its box and shines from `position`. It uses `bayer8`, whose 64 tones keep the falloff smooth. Its centre is brightest and it falls off softly, with a slow breath and a little noise at the edge so it never looks like a flat circle. ## Examples [#examples] ### Rising behind a headline [#rising-behind-a-headline] With `position="bottom"` the light sits just below the edge, so only its top shows. The headline sits near the top, on clean paper. ### Corner of a card [#corner-of-a-card] A corner glow is the quietest way to lift one card out of a row. Setting `--dither-paper` to the card's background blends the glow into it, and `bayer8` gives a finer grain at small sizes. ### Any point, any palette [#any-point-any-palette] `position` also takes `[x, y]` as fractions of the box. With a longer palette the glow steps through each colour from its edge to its core. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `position` | `"top" \| "bottom" \| "left" \| "right" \| "center" \| "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" \| [x: number, y: number]` | `"top"` | Where the light comes from. Edges sit just outside the box; a point is measured from the top left. | | `radius` | `number` | `0.7` | How far the light reaches. 1 reaches the farthest corner. | | `intensity` | `number` | `0.75` | Brightness at the centre, from 0 (none) to 1 (full ink). | | `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"` | `"bayer8"` | 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 draws one still frame. | | `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 a CSS approximation 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). # Dither gradient A linear, radial or conic gradient drawn as a dither, for heroes, section breaks and cards. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherGradient } from "@/components/dithercn/dither-gradient"; export function Hero() { return (

Your headline

); } ``` The gradient fills its box, so give it a size with `className`. It runs from paper at the start to ink at the end, in your theme's `--dither-paper` and `--dither-ink` colours. ## Examples [#examples] ### Glow behind a headline [#glow-behind-a-headline] A radial gradient that starts as ink and fades to paper. With the centre below the bottom edge and a small radius, only a low arc of light shows, and the headline sits on clean paper. ### Conic sweep through a palette [#conic-sweep-through-a-palette] `palette` takes any number of colours from paper to ink. A conic sweep jumps from its last colour back to its first, so this palette starts and ends on the background to hide the seam. Blue noise gives it an even, grainy texture. ### Still, inside a card [#still-inside-a-card] With `animate={false}` the gradient draws one frame and stops. `to={0.55}` keeps the densest band light enough for text to sit on, and setting `--dither-paper` to the card's own background blends the gradient into it. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `mode` | `"linear" \| "radial" \| "conic"` | `"linear"` | The gradient's shape. | | `angle` | `number` | `180 (linear), 0 (conic)` | Linear: the direction in degrees, as in CSS. Conic: where the sweep starts. | | `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Radial and conic only. The centre as fractions of the box, measured from the top left. | | `radius` | `number` | `1` | Radial only. 1 reaches the farthest corner. | | `from` | `number` | `0` | Tone at the start, from 0 (paper) to 1 (ink). | | `to` | `number` | `1` | Tone at the end. | | `noise` | `number` | `0.12` | How far a slow noise drift bends the bands. 0 gives straight, even bands. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "green", CSS variable names or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the gradient snaps to before dithering. Lower values give visible bands. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the noise drifts. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The gradient fills this box. | ## Notes [#notes] * The gradient 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 a smooth CSS gradient in the same colours. * One gradient costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Dither orb A lit, dithered orb that breathes slowly and leans towards the pointer, for AI and assistant surfaces. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherOrb } from "@/components/dithercn/dither-orb"; export function Hero() { return (

Your headline

); } ``` The orb is a sphere shaded by one light, with a soft terminator, a thin rim light on the shadow side and a dithered glow that falls off around it. Its outline breathes with two slow harmonics and a little noise (`morph`). When the pointer moves anywhere on the page, the orb and its light ease towards it, then drift back to the centre when the pointer leaves. Pass `highlight` for a third, brighter tone on the specular spot, such as `"#fff"` or a pale mix of the orb colour. Pick a colour that is light in both themes: `"--foreground"` turns black in light mode. ## Examples [#examples] ### Assistant hero [#assistant-hero] A purple orb with a pale highlight above a prompt. ### Voice status [#voice-status] A 64px orb on a transparent background, with more `morph` and no lean, as a listening indicator. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"ink"` | Colour of the orb and its glow. | | `highlight` | `DitherColor` | `–` | A third, brighter tone for the specular highlight. | | `background` | `DitherColor` | `"--dither-paper"` | Colour behind the orb. "transparent" floats it over the page. | | `size` | `number` | `0.26` | Radius as a share of the box's shorter side, 0.1 to 0.5. | | `morph` | `number` | `1` | How much the outline wobbles. 0 is a perfect sphere. | | `lean` | `number` | `1` | How far the orb and its light lean towards the pointer. 0 turns it off. | | `glow` | `number` | `0.5` | Strength of the dithered glow around the orb, 0 to 1. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold matrix. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the outline breathes. | | `animate` | `boolean` | `true` | false draws one still frame and stops following the pointer. | ## Notes [#notes] * The orb is decorative and hidden from assistive technology. * With reduced motion turned on, it draws one still, centred frame and ignores the pointer. * One WebGL context per orb. It pauses offscreen and in hidden tabs, and the pointer listener is passive and eased in one animation frame loop that stops once the orb settles. # Dot map A flat world map in dithered dots with pulsing markers and dotted arcs, for customers-worldwide sections. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DotMap } from "@/components/dithercn/dot-map"; const OFFICES = [ { at: [51.51, -0.13], label: "London" }, { at: [35.68, 139.69], label: "Tokyo" }, ] as const; export function Offices() { return ( ); } ``` The map is an SVG that scales to its container's width and keeps its aspect ratio. Land comes from a 3.8 KB mask of Natural Earth's public-domain land polygons that installs with it as `world-land`, so nothing is fetched at runtime. Each marker pulses and casts a dithered halo on the land around it: dense at the marker, thinning through the Bayer levels. Arcs are dotted curves with a stretch of light running along them. ## Examples [#examples] ### Customers worldwide [#customers-worldwide] Many small markers, round dots and a tight halo, between a headline and a row of stats. ### Region map in a card [#region-map-in-a-card] `bounds` crops the map to a region. Here, Europe, with labelled edge regions in a dashboard card. ### Routes, held still [#routes-held-still] Arcs only, with faint sea dots and `animate={false}`. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `markers` | `{ at: [lat, lon]; label?: string; color?: DitherColor }[]` | `[]` | Locations to mark. Labels show beside the marker and are read by screen readers. | | `arcs` | `{ from: [lat, lon]; to: [lat, lon]; color?: DitherColor }[]` | `[]` | Dotted curves between places. | | `color` | `DitherColor` | `"grey"` | Colour of the land dots. | | `accent` | `DitherColor` | `"ink"` | Colour of markers, halos and arcs that don't set their own. | | `columns` | `number` | `160` | Dots across the full width. | | `shape` | `"square" \| "round"` | `"square"` | Dot shape. | | `halo` | `number` | `14` | Radius in degrees of each marker's dithered glow on the land. 0 turns it off. | | `ocean` | `number` | `0` | Share of sea cells with a faint dot, 0 to 1. | | `bounds` | `{ north; south; west; east }` | `78°N to 56°S, all longitudes` | The region to draw, in degrees. | | `animate` | `boolean` | `true` | Pulse the markers and run light along the arcs. | | `title` | `string` | `"World map"` | Accessible name. Marker labels are appended to it. | ## Notes [#notes] * The map is one image to assistive technology, named by `title` plus the marker labels. * Pulses and running arcs are CSS animations. With reduced motion they are hidden and the map is still. * Pass `markers` and `arcs` as constants or memoised arrays: the dot paths are rebuilt when they change. Land cells are cached per grid size. * Land data: [Natural Earth](https://www.naturalearthdata.com/) 1:50m land, public domain, rasterised to 0.75° cells. # Eclipse A dark disc with a glowing rim and a soft corona that breathes slowly, for launch pages and event heroes. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Eclipse } from "@/components/dithercn/eclipse"; export function Hero() { return (

Your headline

); } ``` A disc in `skyColor` sits in front of a light in `rimColor`. A thin rim hugs the edge and is brightest on the `light` side, like the crescent of a real eclipse. Around it, a halo fades outwards with slow variations in strength, and the whole glow breathes on a slow ten-second cycle. ## Examples [#examples] ### Beside a headline [#beside-a-headline] The disc sits in the top right with a short corona, lit from the lower left, and the text sits in the clear space below it. This layout holds from phone to desktop widths. ### Amber on warm black [#amber-on-warm-black] Explicit colours instead of theme tokens, a smaller disc, a wider corona and the full `pulse`. The 8×8 matrix gives a finer texture. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Centre of the disc, as fractions of the box from the top left. | | `size` | `number` | `0.24` | Radius of the disc as a share of the box height. | | `corona` | `number` | `0.5` | How far the halo reaches past the rim, from 0 to 1. | | `light` | `number` | `315` | Where the rim is brightest, in degrees as in CSS: 0 is the top. | | `pulse` | `number` | `0.5` | How much the glow breathes, from 0 (steady) to 1. | | `rimColor` | `DitherColor` | `"--dither-ink"` | Colour of the rim and halo: a palette name such as "orange", a CSS variable name, or any CSS colour. | | `skyColor` | `DitherColor` | `"--dither-paper"` | Colour of the sky and the disc. | | `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 the halo shifts and breathes. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The eclipse fills this box. | ## Notes [#notes] * The eclipse 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 a CSS disc and ring. * The disc is the sky colour, so on a light theme it reads as a pale disc with a coloured ring. Pass a dark `skyColor` for a dark disc on any theme. # Halftone A print halftone: round dots on a rotated screen that swell and shrink with a source, in one ink or up to three overlapping like CMYK. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Halftone } from "@/components/dithercn/halftone"; export function Hero() { return (

Your headline

); } ``` Each square of a rotated screen holds one round dot, sized by the source at its centre, the way a printing screen works. Dot edges are dithered at the screen's own device pixels, so dots stay round and grow smoothly from a speck to touching their neighbours. The screen slides slowly along its angle (`drift`), so dots swell and shrink as they cross the source. Pass `channels` instead of `color` for two or three inks. Each gets its own screen, turned 30° from the last, and overlapping dots mix their colours. With blue, pink and orange you get the rosettes of a CMYK print. ## Examples [#examples] ### Spotlight behind a headline [#spotlight-behind-a-headline] `source="radial"` grows the dots towards `center`. With the centre below the bottom edge, a `radius` under 1 and a transparent background, the dots swell up from the bottom of the section and run out before the text. ### Duotone ramp in a card [#duotone-ramp-in-a-card] `source="linear"` grows the dots towards `direction`. Two channels at a shallow 15° screen, held still, and faded in from the left with a CSS mask. ### Three-ink print [#three-ink-print] A coarse 16px screen in three inks behind an editorial block. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"ink"` | Colour of the dots: a palette name such as "blue", a CSS variable name, or any CSS colour. | | `channels` | `[DitherColor, DitherColor, DitherColor?]` | `–` | Two or three inks on separate screens, 30° apart. Overlaps mix. Use instead of color. | | `background` | `DitherColor` | `"--dither-paper"` | Colour behind the dots. "transparent" lays the screen over the content underneath. | | `source` | `"noise" \| "radial" \| "linear"` | `"noise"` | What sizes the dots: soft drifting clouds, distance from a point, or a ramp. | | `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Radial only. Where the dots are largest, as fractions of the box from the top left. | | `radius` | `number` | `1` | Radial only. Where the dots run out. 1 reaches the farthest corner. | | `direction` | `number` | `0` | Linear only. Direction the dots grow towards, in degrees as in CSS. | | `size` | `number` | `10` | Distance between dot centres in CSS pixels. | | `angle` | `number` | `45` | Screen angle in degrees. Each extra channel adds 30°. | | `intensity` | `number` | `0.9` | Size of the largest dot, 0 to 1. At 1 the biggest dots just touch. | | `scale` | `number` | `1` | Noise only. Size of the clouds. | | `drift` | `number` | `1` | How fast the screen slides along its angle. 0 holds the screen still. | | `speed` | `number` | `1` | How fast the source moves. | | `animate` | `boolean` | `true` | false draws one still frame. | | `cell` | `number` | `one device pixel` | Size of one rendered pixel in CSS pixels. Larger cells give blockier dots. | | `className` | `string` | `–` | Size and position. The halftone fills this box. | ## Notes [#notes] * The halftone 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 a CSS dot screen in the same inks. * Overlap colours are mixed with CSS `color-mix` in oklch, so they follow the theme along with the inks. # Horizon A planet's horizon seen from space, with a lit rim, a drifting aurora glow and fixed stars. The landing page hero, with props. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Horizon } from "@/components/dithercn/horizon"; export function Hero() { return (

Your headline

); } ``` This is the scene behind the dithercn landing page. The planet is a circle whose top touches `horizon`; `curve` sets how small and round it looks. Above the rim, domain-warped noise forms aurora curtains that slide slowly along the horizon. The sky and planet use `skyColor`, and the glow, rim and stars use `glowColor`. ## Examples [#examples] ### Sunrise [#sunrise] A flatter planet, higher in the frame, with a smooth band instead of curtains and an explicit amber glow. ### Small planet under a headline [#small-planet-under-a-headline] A low, strongly curved horizon leaves most of the frame as sky, with more stars. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `horizon` | `number` | `0.28` | Height of the horizon's highest point from the bottom, from 0 to 1. | | `curve` | `number` | `0.6` | How round the planet is, from 0 (nearly flat) to 1 (a small globe). | | `glow` | `number` | `1` | Strength of the glow above the horizon, from 0 to 1. | | `aurora` | `number` | `1` | How much the glow breaks into curtains, from 0 (a smooth band) to 1. | | `stars` | `number` | `1` | How many stars, from 0 to 3. They never move. | | `glowColor` | `DitherColor` | `"--dither-ink"` | Colour of the glow, rim and stars: a palette name such as "orange", a CSS variable name, or any CSS colour. | | `skyColor` | `DitherColor` | `"--dither-paper"` | Colour of the sky and the planet. | | `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 the aurora drifts. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The scene fills this box. | ## Notes [#notes] * The scene 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 a soft CSS glow over a dark arc. * Stars thin out near the horizon, so the glow always has a clean edge. # Mesh gradient Three to five colour blobs that drift and melt into each other, drawn as a dither, for heroes and sign-up screens. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { MeshGradient } from "@/components/dithercn/mesh-gradient"; export function Hero() { return (

Your headline

); } ``` Each blob drifts on its own slow orbit. Where two meet, their colours blend through the palette before the dither, so the seams read as soft gradients instead of edges. The default palette uses tints of your theme's `--dither-ink` with a touch of violet. ## Examples [#examples] ### Five blobs, warm palette [#five-blobs-warm-palette] `palette` starts with paper and then takes one colour per blob. Pass `blobs + 1` colours to give each blob its own; the order sets which colours sit next to each other where blobs meet. Blue noise, the default, keeps the grain even across so many tones. ### Behind a form [#behind-a-form] Three large blobs, bigger cells and a slower `speed` make a calm backdrop. The form sits on a translucent panel so the colour shows around it without hurting the text. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `blobs` | `3 \| 4 \| 5` | `4` | How many colour blobs drift across the box. | | `size` | `number` | `0.6` | Size of each blob as a share of the box height. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, then four tints of --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"` | `"blue-noise"` | 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 draws one still frame. | | `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 a CSS approximation 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). # Metaballs Soft blobs that drift, merge and split like a lava lamp, 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Metaballs } from "@/components/dithercn/metaballs"; export function Hero() { return (

Your headline

); } ``` Each blob rises and sinks on its own slow loop. Where two come close, their edges reach for each other and join, then pull apart again. Every blob is densest at its core and has a soft halo around its edge. ## Examples [#examples] ### Contour rings [#contour-rings] `variant="rings"` draws contour lines around the blobs instead of filling them. The rings merge and split with the blobs. ### Lava lamp [#lava-lamp] Five blobs in warm colours, sped up a little, in a tall rounded box. In a narrow box the blobs are sized by its width. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `"solid" \| "rings"` | `"solid"` | Shaded blobs with a soft halo, or contour lines around them. | | `count` | `number` | `5` | How many blobs, from 2 to 8. | | `size` | `number` | `1` | Size of the blobs. In a tall box they are sized by the width. | | `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 draws one still frame. | | `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 soft CSS blobs 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). # Noise field A slow, folding noise texture drawn as a dither, in four styles, with a mask that clears space for the text on top. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { NoiseField } from "@/components/dithercn/noise-field"; export function Section() { return (

Your headline

); } ``` The field is fractal noise folded back on itself, so it flows like smoke rather than scrolling. `variant` changes what the noise draws: * `smoke`, the default: soft folds that dither into smooth gradients. * `ridges`: sharp creases, like veins or lightning. * `contours`: topographic lines, spaced by `bands`. * `marble`: warped stripes, also set by `bands`. `warp`, `detail` and `scale` shape the noise itself. `contrast`, `coverage` and `intensity` decide how much of it turns to ink. `angle` and `flow` set where it drifts, and `mask` with `maskSize` clears room for your text. ## Examples [#examples] ### Cleared under a headline [#cleared-under-a-headline] `mask="center"` fades the field to paper in the middle of the box. Use `"top"` or `"bottom"` when the text sits at one end, and `maskSize` to clear more or less. ### Fine grain in a dashboard card [#fine-grain-in-a-dashboard-card] A high `scale`, a low `intensity` and 2px cells turn it into a texture you notice only when it moves. ### Topographic lines [#topographic-lines] `variant="contours"` draws the noise as height lines, a good fit for maps and region stats. A bottom `mask` keeps the numbers on clean paper, and a low `flow` lets the terrain shift slowly. ### Marble with a highlight [#marble-with-a-highlight] `variant="marble"` with more `warp` bends the stripes into veins. `highlight` adds a third colour for the brightest peaks, here the foreground, so the stripes catch the light. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"--dither-ink"` | The ink of the texture. | | `highlight` | `DitherColor` | `u2013` | A third colour for the brightest peaks. Leave it out for two tones. | | `background` | `DitherColor` | `"--dither-paper"` | The paper behind. | | `variant` | `"smoke" \| "ridges" \| "contours" \| "marble"` | `"smoke"` | The shape of the texture: soft folds, sharp creases, topographic lines or warped stripes. | | `scale` | `number` | `1.3` | Size of the features. Higher values give smaller, busier noise. | | `warp` | `number` | `1.7` | How far the noise folds back on itself, from 0 (plain noise) to 4. | | `detail` | `number` | `4` | Octaves of noise, from 1 (smooth blobs) to 6 (fine grain on top). | | `contrast` | `number` | `1.2` | 1 is soft. 2 and up give hard-edged islands. | | `coverage` | `number` | `0` | How much of the box is inked, from -0.5 (sparse) to 0.5 (dense). | | `intensity` | `number` | `0.9` | Brightest tone the field reaches, from 0 to 1. | | `bands` | `number` | `8` | Lines for "contours" and stripes for "marble", from 2 to 24. | | `angle` | `number` | `90` | Direction it drifts in, in degrees. 0 is right, 90 is up. | | `flow` | `number` | `1` | How fast it drifts along angle, from 0 to 4. | | `invert` | `boolean` | `false` | Swaps paper and ink. | | `mask` | `"none" \| "center" \| "top" \| "bottom" \| "edges"` | `"none"` | Fades the field out where your text sits. "edges" keeps only the middle. | | `maskSize` | `number` | `0.5` | How much of the box the mask clears, from 0 to 1. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Built from the colour props` | Takes over the colours: paper first, brightest last. Palette names, CSS variable names or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the field snaps to before dithering. Low values give hard steps. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it moves. | | `animate` | `boolean` | `true` | false draws one still frame. | | `stillTime` | `number` | `0` | The moment drawn when motion is off or reduced, in seconds. | | `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 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 a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. Each step of `detail` adds a layer of noise, so lower it on large, busy pages. See [Performance](/docs/engine/performance). # Plasma Classic plasma: overlapping sine waves that flow into each other, dithered in two colours or a whole palette. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Plasma } from "@/components/dithercn/plasma"; export function Hero() { return (

Your headline

); } ``` Plasma fills its box, so give it a size with `className`. Four sine waves, one of them circling the centre, add up to a pattern that never repeats. A slow noise warp bends it so it doesn't look mechanical. By default it runs from `--dither-paper` through `--chart-5` to `--dither-ink`. ## Examples [#examples] ### Through a palette [#through-a-palette] Pass any number of colours from paper to ink. More `bands` cycle through the palette more often, and blue noise swaps the Bayer grid for an even grain. ### Behind a headline [#behind-a-headline] `intensity` caps the brightest tone, so the crests never reach full ink and the text stays readable. A smaller `scale` and more `warp` give fewer, softer shapes. ### Coarse and still [#coarse-and-still] Large cells, the 2×2 matrix and only four `levels` give hard contour bands like an old demo-scene effect. `animate={false}` holds one frame. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `number` | `1` | Size of the pattern. Higher values pack more waves in. | | `warp` | `number` | `0.3` | How far slow noise bends the waves, from 0 (pure sines) to 1. | | `bands` | `number` | `1.5` | How many times the colours cycle across one wave. | | `intensity` | `number` | `1` | The brightest tone, from 0 to 1. Lower it to put text on top. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--chart-5", "--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. | | `levels` | `number` | `All the matrix can draw` | Tones the plasma snaps to before dithering. Lower values give hard contour bands. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the waves move. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The plasma fills this box. | ## Notes [#notes] * The plasma 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 soft CSS blobs in the same colours. * One draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Retro grid A perspective grid floor scrolling towards a horizon under a striped, glowing sun, 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { RetroGrid } from "@/components/dithercn/retro-grid"; export function Hero() { return (

Your headline

); } ``` The floor is a grid seen from a low camera, scrolling slowly towards you. Lines stay about one cell wide at every depth and fade out where they would crowd closer than a few cells, so the horizon doesn't break into moiré. The sun sits half sunk behind the horizon, with slits through its lower half that thicken towards the ground and drift down. Grid squares keep their size against the box height, so a phone shows fewer columns, not thinner ones. ## Examples [#examples] ### Sunset [#sunset] Four warm colours, from a deep violet paper through magenta to a pale gold sun. A bigger `sun` and `cell={4}` give it the chunky look of an old game. ### Without the sun [#without-the-sun] `sun={0}` leaves only the glow along the horizon, which clears the sky for a headline. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `horizon` | `number` | `0.42` | Height of the horizon, from 0 (bottom) to 1 (top). | | `sun` | `number` | `0.4` | Size of the sun as a share of the box height. 0 hides it. | | `columns` | `number` | `12` | How many squares across the front of the floor in a 16:10 box. Narrower boxes show fewer. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, two tints of --dither-ink, then --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 the floor scrolls and the sun's slits drift. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The scene 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 a horizon line, a soft sun and a fade in CSS. # Ridgelines Stacked ridge lines like a pulsar plot, each hiding the ones behind it, 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Ridgelines } from "@/components/dithercn/ridgelines"; export function Section() { return (
); } ``` Each line is flat at the ends and peaked in the middle, and each one hides the lines behind it. The peaks shift slowly. A faint shade under the tallest peaks makes them read as solid ridges. Lines stay one cell wide and at least four cells apart, so a short box draws fewer of them. ## Examples [#examples] ### In a card [#in-a-card] Fewer `lines`, a narrower `spread` and small cells fit a pulsar plot in a card. ### Mountain range [#mountain-range] `spread={1}` raises peaks across the whole width. A lower `amplitude` keeps them low, and a three-colour palette shades the slopes. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `lines` | `number` | `32` | How many lines, from 6 to 64. A short box draws fewer. | | `amplitude` | `number` | `1` | Height of the peaks. 2 is twice as tall. | | `spread` | `number` | `0.5` | Width of the peaked band, as a share of the box, from 0.1 to 1. | | `shade` | `number` | `0.25` | Faint tone under the raised peaks, from 0 to 0.5. 0 keeps only the lines. | | `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 draws one still frame. | | `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 thin CSS lines 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). # Ripples Concentric rings spreading from one to four points and crossing like drops on still water, 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Ripples } from "@/components/dithercn/ripples"; export function Hero() { return (

Your headline

); } ``` Each point in `sources` sends out rings that fade with distance. A lone ring stops at the middle colour; where rings from two points cross, they add up to full ink, so the interference pattern shows up as bright knots. A slow swell bends the rings slightly so they don't look drawn with a compass. ## Examples [#examples] ### One drop [#one-drop] A single source just below the frame sends rings up the box. They fade out before they reach the text at the top, and a little less `intensity` keeps the nearest ones soft. ### Rain [#rain] Four sources, tighter rings and a faster `decay` through the chart colours. The 8×8 matrix gives the fading rings a finer grain. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `sources` | `[x: number, y: number][]` | `[[0.28, 0.38], [0.74, 0.66]]` | Where the rings start, as fractions of the box from the top left. One to four points; values outside 0 to 1 put a point out of frame. | | `rings` | `number` | `12` | How many rings fit in the height of the box. | | `decay` | `number` | `1` | How quickly the rings fade away from their source. 0 keeps them strong across the box. | | `intensity` | `number` | `1` | The brightest tone, from 0 to 1. Lower it to put text on top. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, a tint of --dither-ink, then --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 the rings travel outwards. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The rings fill 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 rings drawn with CSS gradients. # Silk Folds of silk catching the light: smooth sheets that ripple and shift, with a bright highlight along each crest. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Silk } from "@/components/dithercn/silk"; export function Hero() { return (

Your headline

); } ``` The cloth is a height field of folds bent by slow waves. It is lit from the top left: faces turned to the light are bright, the far sides of each fold fall smoothly into shadow, and a narrow highlight runs along every crest. The folds slide and change width over time. It defaults to the 8×8 matrix, which suits its long soft gradients. ## Examples [#examples] ### Behind a headline [#behind-a-headline] Fewer `folds`, a sharper `sheen` and an `intensity` of 0.45 keep the brightest crest below full ink. ### Champagne [#champagne] Eight folds on a diagonal through four warm golds, from near-black to a pale cream highlight. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `folds` | `number` | `6` | How many folds cross the box. | | `angle` | `number` | `110` | Direction the folds run, in degrees as in CSS: 90 runs them left to right. | | `sheen` | `number` | `0.6` | How narrow and bright the highlight on each fold is, from 0 to 1. | | `intensity` | `number` | `1` | The brightest tone, from 0 to 1. Lower it to put text on top. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, two tints of --dither-ink, then --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"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the folds move. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The cloth 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 soft diagonal stripes in CSS. # Smoke Soft smoke that billows upward and thins out, lit from above, 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Smoke } from "@/components/dithercn/smoke"; export function Footer() { return (
); } ``` The smoke is thickest at the bottom and thins out as it climbs. Light from the upper left catches one side of each plume and leaves the other in shadow. The thickest parts stop short of solid ink, so the dither always shows. ## Examples [#examples] ### Behind a headline [#behind-a-headline] A lower `height` keeps the smoke in the bottom half, and a lower `density` makes it lighter. Negative `wind` blows it to the left. ### Embers [#embers] Four warm colours turn the smoke into the glow above a fire. Blue noise gives an even grain instead of the Bayer grid. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `number` | `1` | Size of the plumes. Higher values make more, smaller ones. | | `height` | `number` | `0.8` | How high the smoke climbs before it thins out, from 0 to 1. | | `wind` | `number` | `0.2` | Sideways drift. Negative blows left, positive right. | | `density` | `number` | `1` | How thick the smoke is, from 0 to 1.5. | | `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 draws one still frame. | | `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 a soft CSS haze 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). # Starfield Dithered stars in three parallax layers that twinkle and drift slowly, for space themes, sign-in pages and launch pages. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Starfield } from "@/components/dithercn/starfield"; export function Hero() { return (

Your headline

); } ``` Stars sit on three grids of different sizes. Far stars are small, dim and slow; near stars are brighter, move faster and the brightest get a small cross. A faint haze of dithered dust drifts behind them. Each star snaps to one palette colour, so it never flickers between dither thresholds as it moves. The last palette colour is the brightest star, the middle ones are dimmer stars and the haze. ## Examples [#examples] ### Behind a sign-in form [#behind-a-sign-in-form] A slower drift to the lower left and a stronger haze. The form has a translucent background so stars show through its edges. ### A quiet sky [#a-quiet-sky] `drift={0}` holds the stars in place, and `twinkle={1}` lets them pulse fully. No haze, fewer stars, in neutral foreground colours. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `density` | `number` | `1` | How many stars, from 0 to 2. | | `twinkle` | `number` | `0.6` | How much each star pulses, from 0 (steady) to 1. | | `angle` | `number` | `270` | Direction the stars drift, in degrees as in CSS: 0 is up, 90 right. | | `drift` | `number` | `1.6` | Drift speed of the nearest layer, in cells per second. The others move at 0.6 and 0.3 of it. 0 holds them still. | | `nebula` | `number` | `0.5` | Strength of the dust haze behind the stars, from 0 to 1. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--chart-2", "--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"` | `"blue-noise"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `16 per palette step` | Tones before dithering. The default keeps every palette colour exact so stars stay solid. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast time runs, for both drift and twinkle. | | `animate` | `boolean` | `true` | false draws one still frame. | | `className` | `string` | `–` | Size and position. The starfield fills this box. | ## Notes [#notes] * The starfield 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 tiled CSS dots. * Twinkle rates are random per star and slow, between about 3 and 12 seconds a pulse, so nothing flashes. # 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Tunnel } from "@/components/dithercn/tunnel"; export function Hero() { return (

Your headline

); } ``` 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. ### 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. ## 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 draws one still frame. | | `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). # Waves Layered swells under a glowing horizon, or thin lines like a sound wave, 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Waves } from "@/components/dithercn/waves"; export function Footer() { return (
); } ``` Rows of swells roll towards you below the horizon. Distant rows are small and bright, near ones large and dark, and every crest has a one-cell highlight. ## Examples [#examples] ### Dusk [#dusk] A lower `horizon`, eight `layers` and a warm palette give a sea at sunset. A smaller `amplitude` calms the water. ### Sound wave [#sound-wave] `variant="lines"` draws thin lines that swell in the middle and taper at both ends. It suits audio players, voice input and anything that listens. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `"ocean" \| "lines"` | `"ocean"` | Swells under a horizon, or thin lines like a sound wave. | | `horizon` | `number` | `0.62` | Ocean only. Height of the horizon, from 0 (bottom) to 1 (top). | | `layers` | `number` | `6` | How many waves, from 2 to 8. | | `amplitude` | `number` | `1` | Height of the waves. 1 is calm, 2 is rough. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, then tints of --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 draws one still frame. | | `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 a CSS approximation 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). # Dither engine How dither-core, the engine behind every dithercn dither, turns a tone into dots: threshold matrices, levels, error diffusion, and the three ways to draw. > 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). `dither-core` is the dither engine. It installs into `lib/dither` as plain source, with the [agent skill](/docs/installation) or the first time you add an example, and every dither on this site is built on it. Import it to build your own: ```tsx import { DitherShader, ditherMaskStyle, ditherImage } from "@/lib/dither"; ``` It draws dithers three ways. Pick by what you're making: | You want | Use | Drawn with | | ---------------------------------- | ---------------------------------- | ----------------- | | An animated or full-bleed surface | `DitherShader` | WebGL | | A static fill for a shape or block | `DitherPattern`, `ditherMaskStyle` | SVG or a CSS mask | | A dithered photo or logo | `ditherImage` | The CPU, once | ## From tone to dots [#from-tone-to-dots] A dither starts from a tone between 0 and 1 for each cell: 0 is paper, 1 is ink. A threshold matrix gives every cell in a small repeating tile its own threshold. Where the tone is above the cell's threshold, the cell is inked. A tone of 0.25 inks a quarter of the cells, spread evenly across the tile. The matrix decides how those cells are spread. Each one below draws the same left-to-right ramp: * `bayer2`, `bayer4` and `bayer8` are ordered Bayer matrices of 2, 4 and 8 cells square. Bigger tiles draw more distinct tones, with a regular crosshatch that reads as deliberate and printed. * `blue-noise` is a 64×64 tile with no visible grid. It looks closest to film grain, and suits large soft surfaces where a crosshatch would distract. `bayer4` is the default everywhere. Every matrix is ordered, so the same tone always gives the same dots. That keeps animated dithers calm: a cell only changes when its tone crosses its threshold. ## Levels [#levels] Before dithering, the tone snaps to a fixed number of `levels`. By default that's every tone the matrix can draw, 17 for `bayer4`. Fewer levels give visible bands, a posterised look that works well for bold backgrounds: ## Error diffusion for images [#error-diffusion-for-images] Photos and logos look best with error diffusion. Each pixel is rounded to the nearest colour, and the rounding error is passed on to the pixels not yet visited. `ditherImage` supports `floyd-steinberg`, `atkinson` and `sierra-lite` alongside the ordered matrices: `ditherImage` never changes its input. It returns a new `ImageData` you can draw with `putImageData`, and it has no DOM dependency beyond `ImageData`, so it runs in a Web Worker too. Error diffusion depends on every earlier pixel, so a small change to the input can move dots everywhere. Keep it for still images and use a matrix for anything that moves. ```ts const dithered = ditherImage(context.getImageData(0, 0, width, height), { method: "atkinson", palette: [ [0.05, 0.05, 0.1], [0.55, 0.6, 1], ], }); context.putImageData(dithered, 0, 0); ``` ## Writing a field [#writing-a-field] `DitherShader` runs a GLSL function you write, called the field. [Write your own dither](/docs/engine/write-your-own) builds one step by step; this is the short version. It receives the cell's position and returns its tone; the core does the rest: resizing, quantising, the palette, motion and cleanup. ```tsx const FIELD = ` float field(vec2 uv, vec2 res, float t) { // uv: cell centre in [0, 1], y up. res: size in cells. t: seconds. float glow = 1.0 - distance(uv, glowCentre); return clamp(glow + 0.2 * (fbm(uv * 3.0 + t * 0.05) - 0.5), 0.0, 1.0); } `; ; ``` Values in `uniforms` are declared for you: a number becomes a `float`, and a tuple of 2, 3 or 4 numbers a `vec2`, `vec3` or `vec4`. Changing a value only updates the uniform; changing the field's source recompiles it. The core gives every field three helpers: `hash21(vec2)` for stable per-cell randomness in \[0, 1), `snoise(vec2)` for simplex noise in \[-1, 1], and `fbm(vec2)` for four octaves of it in \[0, 1]. Names starting with `u_` belong to the core's own uniforms, so your uniforms can't use them. ## Static fills [#static-fills] Charts and small surfaces don't need a canvas. `DitherPattern` is an SVG `` you reference from any shape, and `ditherMaskStyle` masks any element to one tone: ```tsx
``` Both render on the server, follow the theme through CSS variables, and cost nothing after the first paint. # Export Turn any dither into a PNG, a compact SVG or a CSS rule from your own code, with dither-export. > 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). `dither-export` turns a dither into files. It takes the `ImageData` from `ditherImage`, one pixel per cell, or a grid of palette indices you worked out yourself, and returns a PNG, an SVG or a CSS rule. Use it to bake a dither into a static asset, to ship a background with no JavaScript, or to give your users a download button. ## Which format [#which-format] | Format | Use it for | | ------ | -------------------------------------------------------- | | PNG | Photos and diffusion dithers. Pixel-exact at any scale. | | SVG | Ordered dithers and flat art. Sharp at any size. | | CSS | Backgrounds in a stylesheet: the whole image, or a tile. | ## Installation [#installation] ## Usage [#usage] ```tsx import { ditherImage } from "@/lib/dither"; import { download, toCSS, toPNG, toSVG } from "@/lib/dither/export"; const dither = ditherImage(pixels, { method: "atkinson", palette: [ [0.04, 0.04, 0.05], [0.95, 0.94, 0.91], ], }); // A full-size PNG: every cell becomes a crisp 4 × 4 block. download({ data: await toPNG(dither, { scale: 4 }), filename: "dither.png" }); // An SVG drawn in cells, 4px each. const svg = toSVG(dither, { cell: 4 }); // A CSS rule with the dither as a covering background. const css = toCSS({ kind: "image", image: dither, selector: ".hero" }); ``` `toSVG` and `toCSS` with a tile return strings and run anywhere, including on the server. `toPNG`, `download` and the PNG form of `toCSS` need a browser, because they draw to a canvas. ## Examples [#examples] ### A fade in pure CSS [#a-fade-in-pure-css] The default kind of `toCSS` repeats one matrix tile at a coverage `level` from 0 to 1. In `mask` mode it paints the element with `ink` and masks it to the dither, so the ink can be a CSS variable that follows your theme. Four bands at falling levels make the stepped fade at the bottom of this card. The rules are built on the server; the page runs no script for them. ```ts toCSS({ level: 0.25, cell: 4, ink: "--dither-ink", mode: "mask" }); ``` ```css .dither { background-color: var(--dither-ink); -webkit-mask: url("data:image/svg+xml,…") 0 0 / 16px 16px repeat; mask: url("data:image/svg+xml,…") 0 0 / 16px 16px repeat; } ``` In the default `background` mode the ink is baked into the tile and `paper` fills the gaps. An SVG inside a data URI can't read CSS variables, so pass a literal colour for `ink` in that mode. ### Indexed bitmaps [#indexed-bitmaps] If you work out the dither yourself, pass `toSVG` one palette index per cell instead of pixels. Any index without a colour is left transparent. ```ts const svg = toSVG( { width: 64, height: 32, indices }, { cell: 3, palette: ["#0a0a0b", "#3d6bff", "#dfe7ff"] } ); ``` ## API [#api] ### toPNG [#topng] ```ts toPNG( source: HTMLCanvasElement | PixelBuffer, options?: { scale?: number } ): Promise ``` Encodes a canvas or pixels as a PNG. `scale` is a whole-number upscale with no smoothing, so pass the cell size to turn one pixel per cell into a full-size image. ### toSVG [#tosvg] ```ts toSVG( source: PixelBuffer | IndexedBitmap, options?: { cell?: number; palette?: DitherColor[]; background?: boolean; } ): string ``` An SVG in cell units, scaled to `cell` pixels per cell (default 1). The most used colour becomes one background rectangle unless `background` is `false`. Rows of same-coloured cells become short relative strokes and taller blocks become rectangles, at most two ``s per colour, so a 400 × 225 ordered dither is about 150 KB before gzip. `palette` colours an `IndexedBitmap` by index. ### toCSS [#tocss] ```ts toCSS(options: CSSOptions): string ``` Returns one CSS rule. The default `tile` kind repeats one matrix tile; the `image` kind embeds a whole dither. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `level` (required) | `number` | | Tile kind. Share of cells inked, 0 to 1. | | `matrix` | `DitherMatrix` | `"bayer4"` | Tile kind. The threshold pattern. | | `cell` | `number` | `3` | Tile kind. Cell size in CSS pixels. | | `ink` | `DitherColor` | `"#3b82f6"` | Tile kind. Use a literal colour in background mode; any colour in mask mode. | | `paper` | `DitherColor` | `"transparent"` | Tile kind, background mode. The colour behind the ink. | | `mode` | `"background" \| "mask"` | `"background"` | Tile kind. Bake the ink into the tile, or mask the element. | | `kind` | `"tile" \| "image"` | `"tile"` | "image" embeds a whole dither as a covering background. | | `image` (required) | `PixelBuffer` | | Image kind. The dither, one pixel per cell. | | `format` | `"png" \| "svg"` | `"png"` | Image kind. PNG needs a browser; SVG is a pure string. | | `selector` | `string` | `".dither"` | The rule's selector. | ### download [#download] ```ts download(options: { data: Blob | string; filename: string; type?: string; }): void ``` Saves a Blob or a string through the browser's download flow. Strings are saved with `type`, default `text/plain`. `svgDataUrl(svg)` is exported too: it wraps an SVG string in a CSS `url()`. ## Notes [#notes] * PNG and SVG exports match pixel for pixel at their own size. Shown smaller than one pixel per cell, an SVG drops cells in stripes, as any cell-based vector does; use PNG for thumbnails. * PNG exports are capped at 16,384 pixels on the longest side, the most every current browser can draw. A larger `scale` is reduced to fit. * The functions never change their input. # Motion How animated dithers run, pause and hold still, including under reduced motion. > 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). Animated dithers in dithercn are slow on purpose. A background should drift, not perform. Every animated component follows the same rules, and they come from `dither-core`, so you get them for free when you build your own. ## When a dither moves [#when-a-dither-moves] A `DitherShader` animates only while someone can see it. Its `data-state` attribute says what it is doing: | `data-state` | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `running` | Drawing up to 30 frames a second. | | `paused` | Scrolled out of view or in a hidden tab. Time stops, so it resumes where it left off. | | `static` | Motion is off. Reduced motion draws `stillTime`. `animate={false}` holds the current frame, or draws `stillTime` if the dither never moved. | | `fallback` | Nothing drawn yet, WebGL is unavailable, or the shader is far offscreen and has released its context. The `fallback` content shows instead. | Time only advances while running, and a long frame (a tab switch, a debugger) counts as at most a tenth of a second, so the motion never jumps. ## Reduced motion [#reduced-motion] When the visitor turns on reduced motion in their system settings, every dither draws one still frame and stops. `stillTime` picks which moment of the animation that frame shows, so you can choose a good one: ```tsx ``` The switch is live: turning the setting on or off while the page is open takes effect at once. Interface pieces follow the same rule. Their transitions become instant, and anything that loops holds still. ## Speed and frame rate [#speed-and-frame-rate] `speed` scales time, and changing it never jumps the motion, because each frame adds its own slice of time. `fps` caps the frame rate. The default of 30 keeps slow drifts smooth at half the cost of 60. ## Your own loops [#your-own-loops] `useFrameLoop` is the loop `DitherShader` uses. Give it a callback and an element, and it calls you once per frame while that element is on screen: ```tsx const ref = useRef(null); const state = useFrameLoop( (time, delta) => { // draw the frame for `time` seconds }, { target: ref, fps: 30, stillTime: 0 } ); ``` Under reduced motion or with `enabled: false`, it calls the callback once with `stillTime` and a `delta` of 0. It returns `"running"`, `"paused"` or `"static"`, and only re-renders when that changes. `usePrefersReducedMotion`, `useInView` and `useDocumentVisible` are exported too. ## Easing and timing [#easing-and-timing] For transitions in interface pieces, `MOTION` holds the shared values: ```ts MOTION.ease; // "cubic-bezier(0.23, 1, 0.32, 1)", a strong ease-out MOTION.fast; // 150ms, for presses and colour changes MOTION.base; // 240ms, for small reveals MOTION.slow; // 600ms, for large surfaces fading in ``` Hover effects use Tailwind's `hover:` variant, which only applies on devices that can hover, so a tap on a phone never leaves an element stuck in its hover state. # Palettes How dithers take their colours from your theme, and how to use more than two. > 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). A palette is the list of colours a dither can use, ordered from paper (tone 0) to ink (tone 1). By default it's two colours, `--dither-paper` and `--dither-ink`, which [`dither-tokens`](/docs/theming) points at your `--background` and `--primary`. ## More than two colours [#more-than-two-colours] Components that draw with `DitherShader` take a `palette` prop with two or more colours. The tone is spread across the list, and each cell is dithered between the two neighbouring colours. With four colours, a tone of 0.5 sits halfway between the second and third: ```tsx ``` Each entry is a CSS variable name such as `"--chart-1"`, or any CSS colour: `"var(--chart-1)"`, `"oklch(0.72 0.15 272)"`, `"#7c8cff"`, even `"transparent"` to let the page show through the paper. The type is `DitherPalette`, a tuple of at least two `DitherColor` strings, so a one-colour palette fails to compile. Order matters more than hue. Put colours in order of how much they stand out against the paper, or the ramp will jump back and forth. ## Following the theme [#following-the-theme] Colours are resolved in the browser, in the context of the component, so a variable you override on a wrapper applies to everything inside it. Formats the GPU can't read, such as `oklch()`, are converted by painting them once into a 1×1 canvas. When the class or style on `` changes, as it does when next-themes switches between light and dark, every dither re-reads its colours and redraws. Nothing else needs to happen. For your own canvas work, `useDitherColors` does the same and returns the colours as `[r, g, b, a]` in \[0, 1]: ```tsx const ref = useRef(null); const colors = useDitherColors(ref); // [ink, paper], or null before mount ``` Pass a list of names as the second argument to read other variables, such as `useDitherColors(ref, ["--chart-1", "--chart-2"])`. ## Static fills [#static-fills] `DitherPattern` takes a `color` and an optional `background`, and `ditherMaskStyle` masks whatever background colour the element already has. Both accept CSS variables, so they follow the theme without JavaScript. ## Contrast [#contrast] A dither reads only when ink and paper differ enough. For charts and anything that carries information, aim for the contrast you would want for body text, about 4.5:1. Quiet backgrounds can go lower, since nobody needs to read them. The defaults, your primary on your background, usually clear that bar in both modes. # Performance What a dither costs to draw, and how dither-core keeps a page of them cheap. > 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). Dithers look heavy and draw light. Each technique has a predictable cost, and the expensive parts happen once. ## What each technique costs [#what-each-technique-costs] | Technique | Cost | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `DitherShader` | One draw call per frame, at one pixel per cell, capped at 30 fps. No allocations per frame. | | `DitherPattern`, `ditherMaskStyle` | One small SVG, built once per level and matrix and cached. No JavaScript after the first paint. | | `ditherImage` | One pass over the image, with no DOM. Error diffusion visits every pixel in order, so run large images in a Web Worker. | A full-width hero at 3px cells shades about 110,000 pixels a frame. A 1080p video frame is two million. ## Pausing [#pausing] A shader stops drawing when it scrolls out of view or the tab is hidden, and holds a still frame under reduced motion. Its uniforms can still change while paused, and it redraws once when they do. ## The WebGL context budget [#the-webgl-context-budget] Browsers cap how many WebGL contexts a page can hold. Chrome drops the oldest past 16, and phones can run out of GPU memory sooner. `dither-core` shares a budget between every `DitherShader` on the page: * A shader holds a context only while it is within 200px of the viewport. Further away, it releases the context and shows its `fallback`. * At most 8 contexts are live at once (`MAX_LIVE_CONTEXTS`). When a ninth shader comes on screen, it takes the context of one just outside the viewport. It never takes one from a shader that is still on screen, so scrolling doesn't make visible dithers flicker; it waits for the next free context instead. * When a shader scrolls back, it gets a fresh context and fades its canvas in over the fallback. So a long docs page with a dozen backgrounds keeps at most 8 contexts, and normally only the two or three on screen. Give each shader a `fallback` that resembles it, such as the CSS gradient `DitherGradient` uses, and a released one is hard to spot. ## Galleries and thumbnails [#galleries-and-thumbnails] A grid of 30 live backgrounds would spend its budget on the first 8. For catalog pages and galleries, show a static image or a `ditherMaskStyle` composition instead, and mount the live shader on the detail page. The docs on this site do the same: each preview mounts its demo only when it nears the viewport. ## Images [#images] `ditherImage` is synchronous. A 512×512 image takes 10 to 30 ms; a 4K photo, 32 times the pixels, can block the main thread for half a second or more. Dither at the size you display, after scaling the image down to one pixel per cell, and move larger jobs to a Web Worker. The function is pure and has no DOM dependency beyond `ImageData`, so the same import works there. ## Blue noise in masks [#blue-noise-in-masks] The blue-noise tile is 64×64 cells, against 4×4 for `bayer4`. Its SVG mask is correspondingly larger, 15 to 20 KB per level against about 100 bytes. Use it in shaders freely, where it is a single texture. For CSS masks, prefer a Bayer matrix unless you need only a level or two. # Pixel scale How big one dither cell is, how it stays crisp on every screen, and how to change it. > 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). Every dither is drawn on a grid of square cells, and each cell is either ink or paper. The size of one cell sets the grain: small cells read as texture, large ones as pixel art. ## Setting the size [#setting-the-size] The size comes from the `--dither-cell` CSS variable, 3px by default. Change it for the whole site in your CSS, or for one section by setting it on a wrapper: ```css .pricing-hero { --dither-cell: 5px; } ``` To size one component directly, pass `cell` in CSS pixels. It wins over the variable: ```tsx ``` ## Crisp on every screen [#crisp-on-every-screen] `DitherShader` renders one canvas pixel per cell and scales the canvas up with `image-rendering: pixelated`, so a large hero costs about as much as a thumbnail. A 1440×700 hero at 3px cells is 480×234 pixels of shading. To keep every cell the same size, the cell is rounded to whole device pixels. On a 2× screen a 3px cell is exactly 6 device pixels. At 1.5× it would be 4.5, so it becomes 5 device pixels, 3.33 CSS pixels. Without this rounding, neighbouring cells would alternate between 4 and 5 pixels and the pattern would shimmer. The canvas covers its box to the next whole cell and the overflow is clipped, so the grid always starts at the top left corner. ## Static fills [#static-fills] `ditherMaskStyle` tiles its mask at `--dither-cell` too. Its `cell` option takes CSS pixels or any CSS length: ```tsx
``` `DitherPattern` lives inside an SVG, so its `cell` is in SVG user units: CSS pixels when the SVG is drawn at its natural size. If the SVG scales with a `viewBox`, the cells scale with it. ## Choosing a size [#choosing-a-size] * 2 to 3px reads as fine grain. It's the right default for large backgrounds behind text. * 4 to 6px is clearly a dither from across the room, good for heroes that are the main visual. * 8px and up is pixel art. Pair it with fewer `levels` for a bold, posterised look. # Write your own dither Build a dithered background from scratch on DitherShader: a tone, a shape, then motion and props. > 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 guide builds a dithered ripple background in three steps. You write one GLSL function, the field, and `DitherShader` does everything else: the dots, the colours, resizing, motion and reduced motion. The same steps make any background you can describe with a little math. Your agent follows them too, through the [dithercn skill](/docs/installation). You need the engine installed (see [Installation](/docs/installation)) and a section with a size to put the dither in. ## Have your agent write it [#have-your-agent-write-it] Your agent can follow this page. Fill in the two blanks and paste this in: If the engine isn't installed yet, start with the [quick start prompt](/docs/installation#quick-start-with-your-agent) instead. The rest of this page is the same build, step by step. ## 1. Return a tone [#1-return-a-tone] The field runs once for every cell and returns a tone from 0 to 1: 0 is paper, 1 is ink. The engine turns that tone into dots, so a field that returns the height draws a gradient from bottom to top: ```tsx "use client"; import { DitherShader } from "@/lib/dither"; const FIELD = ` float field(vec2 uv, vec2 res, float t) { return uv.y; } `; export function Ripples() { return ( ); } ``` `uv` is the cell's position from 0 to 1 on both axes, with y pointing up. `res` is the size of the canvas in cells, and `t` is time in seconds. Return smooth values and let the engine do the dithering. ## 2. Draw a shape [#2-draw-a-shape] `uv` stretches with the box, so a circle measured in `uv` comes out as an oval. Multiply x by the aspect ratio first, and distances are true. Then compute anything that has a distance in it, here rings around a point that fade as they spread: ```glsl float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); vec2 drop = vec2(0.5 * aspect, 0.45); float d = length(p - drop); float rings = 0.5 + 0.5 * cos(d * 48.0); float fade = exp(-d * 3.0); return rings * fade; } ``` `cos` turns distance into repeating bands, and `exp` fades them out. Every tone between 0 and 1 becomes a different dot density, which is why the rings look soft rather than drawn. ## 3. Add motion and props [#3-add-motion-and-props] Three changes finish it. Noise bends the rings so they look like water, `t` moves them outwards, and uniforms turn the fixed numbers into props: ```tsx "use client"; import { DitherShader } from "@/lib/dither"; import type { DitherShaderProps } from "@/lib/dither"; const FIELD = ` float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); vec2 drop = vec2(rippleDrop.x * aspect, 1.0 - rippleDrop.y); float wobble = fbm(p * 2.2 + vec2(t * 0.03, 0.0)) - 0.5; float d = length(p - drop) + wobble * 0.08; float rings = 0.5 + 0.5 * cos(d * rippleRings - t * 0.9); float fade = exp(-d * 2.6); return rippleStrength * rings * fade; } `; export type RipplesProps = Omit & { /** Where the drop falls, from the top left, 0 to 1. */ drop?: readonly [number, number]; /** How tightly the rings are packed. */ rings?: number; /** Peak tone, 0 to 1. */ strength?: number; }; export const Ripples = ({ drop = [0.5, 0.42], rings = 46, strength = 0.85, ...props }: RipplesProps) => ( ); ``` * `fbm` is one of three helpers every field gets, with `snoise` and `hash21`. It returns soft, cloudy noise from 0 to 1. * Each key in `uniforms` is declared for you: a number becomes a `float`, a pair a `vec2`. Changing a prop updates the uniform without recompiling. Prefix the names (`ripple…`) so they never clash with another field. * `stillTime={4}` is the moment shown when the visitor prefers reduced motion. Pick one that looks finished. * The rest of `DitherShaderProps` passes through, so `palette`, `matrix`, `cell` and `speed` work on `Ripples` as they do on every example. ## Put it on the page [#put-it-on-the-page] ```tsx
…
``` The shader fills its positioned parent and ignores the pointer, so your content sits in a sibling on top. Keep the tone low where text sits; here `strength` caps it at 0.85, and the rings fade before they reach the edges. ## Where to go next [#where-to-go-next] * [Dither engine](/docs/engine/dither-engine) covers matrices and levels, and the static fills for charts and blocks. * [Palettes](/docs/engine/palettes), [Pixel scale](/docs/engine/pixel-scale) and [Motion](/docs/engine/motion) cover the shared props. * The examples in the [catalog](/docs/examples) are all built this way. Open any background's source for a field to start from. # Catalog Every example component built on the dithercn engine, grouped by what it's for. > 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). Each component installs as plain source into `components/dithercn/`. Drop one into your page as it is, read its source to see how it's made, or point your agent at one and ask for a variation. For anything not here, [ask your agent](/docs#ask-your-agent-for-a-dither) or [write your own](/docs/engine/write-your-own). ## Using an example [#using-an-example] Install any example by its registry URL. It brings the engine with it the first time: ```bash npx shadcn@latest add https://dithercn.dev/r/dither-gradient.json ``` Backgrounds fill the box you give them. Put one behind your content with `absolute inset-0`: ```tsx import { DitherGradient } from "@/components/dithercn/dither-gradient"; export function Hero() { return (

Your headline

); } ``` Backgrounds built on `DitherShader` share these props: | Prop | What it controls | Default | | --------- | --------------------------------------------------------------------------- | -------------------------------- | | `matrix` | The threshold pattern: `"bayer2"`, `"bayer4"`, `"bayer8"` or `"blue-noise"` | Varies by component | | `levels` | How many tones to snap to before dithering | Every tone the matrix can draw | | `cell` | Cell size in CSS pixels | `--dither-cell` | | `palette` | Colours from paper to ink, two or more | `--dither-paper`, `--dither-ink` | | `animate` | Whether it moves | `true` | | `speed` | How fast it moves | `1` | Charts and interface pieces take a `color` from the [named palette](/docs/theming) and a `fill` of `"gradient"`, `"dotted"`, `"hatched"` or `"solid"`. Each page lists every prop. # Dither image Any photo dithered to a preset or your own palette, with crisp pixels and a coarse-to-fine reveal as it loads. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherImage } from "@/components/dithercn/dither-image"; export function Cover() { return ( ); } ``` The default `duotone` preset suits most photos. Pick another `preset`, then override any part of it. The image fills its box like `object-fit: cover`; give the box a width and a height or aspect ratio, or set `ratio` so the space is reserved before the image loads. Every cell is a whole number of device pixels and the canvas is scaled up with `image-rendering: pixelated`, so edges stay sharp at any zoom. Before dithering, the tones are stretched to fill the palette and edges get a light sharpen, which keeps faces and horizons readable at a 2px cell. Turn these off with `normalize={false}` and `sharpen={0}`. While the file loads the box shows a faint dotted field. When it arrives the image resolves from 16× blocks to full detail in about a third of a second. ## Examples [#examples] ### Presets [#presets] | Preset | Look | | ----------- | ----------------------------------------------------------------------- | | `ink` | Atkinson diffusion, one ink on the page colour. Follows the theme. | | `duotone` | Blue noise over the page colour, a tint and the ink. Follows the theme. | | `newsprint` | An ordered 8×8 screen, near black on warm paper, in both themes. | | `gameboy` | Four greens on a 3px Bayer grid. | | `riso` | Navy, blue and pink on cream, like a two-drum print. | ### Palettes from colour names [#palettes-from-colour-names] `color` takes any palette name: `blue`, `purple`, `green`, `pink`, `orange`, `red`, `grey` or `ink`. Each is tuned for light and dark. Here the duotone preset gives each mission its own hue. For full control, pass `palette` with two to four colours. Names, CSS variables and CSS colours all work: `palette={["--dither-paper", "purple", "oklch(0.9 0.05 300)"]}`. ### Newsprint article [#newsprint-article] A fixed palette ignores the theme, so the newsprint card looks the same on a black page and a white one. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `src` (required) | `string` | | Image URL. Images from another origin need CORS headers, because the pixels are read from a canvas. | | `alt` (required) | `string` | | Describes the image for screen readers. Pass "" when it is decorative. | | `preset` | `"ink" \| "duotone" \| "newsprint" \| "gameboy" \| "riso"` | `"duotone"` | A starting look: method, cell, contrast and palette. | | `color` | `DitherColor` | `"ink"` | The ink for the ink and duotone presets. A palette name or any CSS colour. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `From the preset` | Two to four colours that replace the preset palette. Sorted dark to light. Use "transparent" to let the background show through. | | `method` | `"bayer4" \| "bayer8" \| "blue-noise" \| "floyd-steinberg" \| "atkinson" \| "sierra-lite"` | `From the preset` | The dither algorithm. | | `cell` | `number` | `From the preset (2 or 3)` | Size of one cell in CSS pixels, rounded to whole device pixels. | | `contrast` | `number` | `From the preset` | 1 leaves contrast alone. Higher values push tones apart. | | `brightness` | `number` | `0` | Added to every tone, from -1 to 1. | | `normalize` | `boolean` | `true` | Stretch the image's darkest and lightest tones to the ends of the palette. | | `sharpen` | `number` | `0.3` | Edge sharpening before dithering, 0 to 1. | | `invert` | `boolean` | `false` | Swap light and dark. | | `fit` | `"cover" \| "contain"` | `"cover"` | How the image fills its box, as in object-fit. | | `ratio` | `number` | `The image's ratio` | Width over height. Reserves space before the image loads. A height in className wins. | | `resolve` | `boolean` | `true` | Resolve from coarse blocks to full detail the first time the image appears. | ## Notes [#notes] * The root has `role="img"` and your `alt` text as its label, and `aria-busy` while it loads. With `alt=""` it is hidden from assistive technology. * `data-state` on the root is `idle`, `loading`, `resolving`, `ready` or `error`. * The image loads when it comes within about 300px of the viewport. It redraws when the box resizes, the page zooms or the theme changes, without resolving again. * Under reduced motion the image appears at full detail with no resolve. * Dithering runs on the main thread at one pixel per cell. A 1200 by 800 pixel box at a 2px cell is 240,000 pixels, a few milliseconds of work. Coarse frames are much cheaper. # Dithered logo An SVG or PNG logo redrawn on a pixel grid: exact edges, a dithered fill inside, and an optional shimmer or scan line. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitheredLogo } from "@/components/dithercn/dithered-logo"; export function Brand() { return ; } ``` Set a height with `className`; the width follows the logo's own ratio. The logo is drawn on a grid of whole device pixels and cut at half coverage, so every edge lands on a cell boundary instead of fraying into noise. A solid outline one cell wide keeps the shapes exact, and the inside takes the `fill`: a gradient that thins towards the bottom, dots, hatching, or solid ink. By default every part of the logo becomes one ink, `color`. With `tone="original"` it keeps the file's own colours. ## Examples [#examples] ### One treatment for many logos [#one-treatment-for-many-logos] Logos from different brands, drawn with the same fill and colour, read as one set. Here the hatched fill in grey. ### Colour [#colour] The same three-colour mark with `tone="original"`, and in one palette colour. ### Scan line [#scan-line] A 3px cell turns the wordmark into pixel art. `effect="scan"` runs a bright line down it every `period` seconds and leaves solid ink behind for a moment. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `src` (required) | `string` | | SVG or PNG URL. Files from another origin need CORS headers. | | `alt` (required) | `string` | | The brand name for screen readers. "" hides the logo. | | `color` | `DitherColor` | `"ink"` | The ink. A palette name or any CSS colour. | | `tone` | `"mono" \| "original"` | `"mono"` | One ink, or the file's own colours. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture inside the shapes. | | `outline` | `boolean` | `true` | A solid outline one cell wide, so shapes stay exact with an open fill. | | `floor` | `number` | `0.3` | Gradient fill: the share of ink left at the bottom of the logo. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold pattern for the gradient fill and the effects. | | `cell` | `number` | `1` | Size of one cell in CSS pixels. Larger cells give a chunkier, pixel-art mark. | | `effect` | `"none" \| "shimmer" \| "scan"` | `"none"` | A diagonal band of solid ink, or a bright scan line, sweeping across the logo. | | `period` | `number` | `6` | Seconds per sweep. | | `phase` | `number` | `0` | Where in the sweep this logo starts, 0 to 1. Offsets logos in a row. | | `source` | `"alpha" \| "dark"` | `"alpha"` | "alpha" for transparent backgrounds, "dark" for a dark logo on an opaque light background. | | `ratio` | `number` | `The logo's ratio` | Width over height. Set it to reserve space before the logo loads. | ## Notes [#notes] * The root has `role="img"` with the brand name as its label. * SVGs need a `width` and `height` (or a `viewBox` the browser can size) so the logo has a ratio. SVGs are rasterised by the browser at the exact cell size, so curves stay smooth. * Effects run at 30 fps only while the logo is on screen, and stop under reduced motion. Without one, the logo is drawn once and redrawn only on resize or theme change. # Image compare A draggable before and after slider: the dithered image on one side, the original or another dither on the other. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { ImageCompare } from "@/components/dithercn/image-compare"; export function Showcase() { return ( ); } ``` Drag the divider, click anywhere on the image to send it there, or tab to the handle and use the arrow keys. By default the left side is dithered with the `duotone` preset and the right side is the original photo. When the image first comes into view, the divider nudges left and right once so readers see that it moves. Each side is either `{ kind: "original" }` or `{ kind: "dither", ...look }`, where the look takes the same props as [Dither image](/docs/images/dither-image): `preset`, `color`, `palette`, `method`, `cell` and the rest. ## Examples [#examples] ### Two dither looks [#two-dither-looks] Both sides can be dithered. Here newsprint meets Game Boy greens. ### Feature section [#feature-section] A landing page block that shows what the brand treatment does to a photo. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `src` (required) | `string` | | Image URL. Cross-origin images need CORS headers. | | `alt` (required) | `string` | | Describes the image and the comparison. Used as the group's label. | | `before` | `{ kind: "original" } \| { kind: "dither", ...DitherLook }` | `{ kind: "dither" }` | The left side. | | `after` | `{ kind: "original" } \| { kind: "dither", ...DitherLook }` | `{ kind: "original" }` | The right side. | | `beforeLabel` | `string` | `"Dithered" or "Original"` | Label on the left side, also used in the slider's name. | | `afterLabel` | `string` | `"Dithered" or "Original"` | Label on the right side. | | `labels` | `boolean` | `true` | Show the corner labels. They fade out when the divider covers them. | | `value` | `number` | | Divider position from the left, 0 to 100. Makes the slider controlled. | | `defaultValue` | `number` | `50` | Starting position when uncontrolled. | | `onValueChange` | `(value: number) => void` | | Called on every move, from pointer or keyboard. | | `hint` | `boolean` | `true` | Nudge the divider once when the image first comes into view. | | `ratio` | `number` | `The image's ratio` | Width over height. | | `fit` | `"cover" \| "contain"` | `"cover"` | How the image fills its box, on both sides. | ## Notes [#notes] * The handle is a `role="slider"` with a value from 0 to 100. Arrow keys move it by 1, Shift and arrow or Page Up and Page Down by 10, Home and End to the ends. * Clicks glide the divider over 200ms; drags and key presses follow exactly. * On touch screens a sideways drag moves the divider and a vertical swipe still scrolls the page. * Under reduced motion there is no hint and no glide. # Logo marquee A looping row, or a still wall, of logos in one dithered treatment, with soft or dithered edges. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { LogoMarquee } from "@/components/dithercn/logo-marquee"; const logos = [ { src: "/logos/acme.svg", alt: "Acme", ratio: 4 }, { src: "/logos/globex.svg", alt: "Globex", ratio: 3.5, href: "/customers/globex", }, ]; export function Customers() { return ; } ``` Every logo is a [Dithered logo](/docs/images/dithered-logo) with the same fill, cell and colour, so logos from different brands read as one set. The row repeats as many times as it needs to fill the width and loops without a seam. Hover or focus the row and it coasts to a stop instead of halting; let go and it picks up speed again. A small pause button appears at the right end on hover and keyboard focus. The ends fade out through a mask, so the fade works on any background: `soft` is a smooth ramp, `dither` steps through Bayer levels. `ratio` on each logo reserves its width before it loads, so the row does not shift. Give a logo an `href` to make it a link. ## Examples [#examples] ### Wall [#wall] `variant="wall"` lays the logos out in a still grid with hairline dividers, two columns, or four once its container is 672px wide. Each logo links to a case study, and the shimmer passes through them in turn. ### Two rows [#two-rows] Two marquees moving in opposite directions with dithered edges, the second hatched in grey. ## Props [#props] Also takes the drawing props of [Dithered logo](/docs/images/dithered-logo): `color`, `tone`, `fill`, `outline`, `floor`, `matrix`, `cell`, `effect` and `period`. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `logos` (required) | `{ src: string; alt: string; ratio?: number; href?: string; color?: DitherColor }[]` | | The logos, in order. | | `label` | `string` | `"Logos"` | Names the list for screen readers. | | `variant` | `"marquee" \| "wall"` | `"marquee"` | A scrolling row or a still grid. | | `speed` | `number` | `32` | Marquee only: scroll speed in CSS pixels per second. | | `direction` | `"left" \| "right"` | `"left"` | Marquee only: which way the row moves. | | `fade` | `"soft" \| "dither" \| "none"` | `"soft"` | Marquee only: how the ends fade out. | | `fadeWidth` | `number` | `96` | Marquee only: width of each end's fade in CSS pixels. | | `pauseButton` | `boolean` | `true` | Marquee only: show a pause button on hover and focus. | | `logoHeight` | `number` | `28` | Logo height in CSS pixels. | | `gap` | `number` | `64` | Space between logos in the row, in CSS pixels. | ## Notes [#notes] * The first copy of the row is a list labelled by `label`; the copies that fill the loop are hidden from assistive technology and their links are out of the tab order. * The row stops while it is offscreen. Under reduced motion it becomes a still, centred row that wraps. * The motion is a single Web Animation on a transform, so it runs off the main thread. # Scroll reveal image An image that sharpens from coarse dithered blocks to the full photo as you scroll it into view. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { ScrollRevealImage } from "@/components/dithercn/scroll-reveal-image"; export function Chapter() { return ( ); } ``` Progress follows the scroll position, not a timer. When the top of the image enters at the bottom of the viewport it is a few large blocks. It sharpens in nine steps to full dither detail, then the photo dissolves in cell by cell. It is finished when its top edge is 30% down the viewport. Scroll back and it runs in reverse. `start` and `finish` move those two points, as a share of the viewport height from the top. Inside a scrolling panel, pass the panel's ref as `scrollRoot`, as the demo above does. ## Examples [#examples] ### In a long read [#in-a-long-read] The image resolves as the reader reaches it. `finish={0.4}` completes it a little lower on the screen. ### Ending on dither [#ending-on-dither] `end="dither"` stops at full dither detail and never shows the photo. A later `start` on the second image staggers the pair. ## Props [#props] Takes every look prop of [Dither image](/docs/images/dither-image) (`preset`, `color`, `palette`, `method`, `cell`, `contrast`, `brightness`, `normalize`, `sharpen`, `invert`, `fit`), plus: | Prop | Type | Default | Description | | --- | --- | --- | --- | | `src` (required) | `string` | | Image URL. Cross-origin images need CORS headers. | | `alt` (required) | `string` | | Describes the image. Pass "" when it is decorative. | | `end` | `"photo" \| "dither"` | `"photo"` | Finish on the original photo, or on the finest dither. | | `coarsest` | `number` | `24` | How many cells wide the first blocks are. | | `start` | `number` | `1` | Where the image's top edge is when the reveal starts, 0 (top) to 1 (bottom of the viewport). | | `finish` | `number` | `0.3` | Where the top edge is when the reveal finishes. | | `scrollRoot` | `RefObject` | | The scrolling element, when it is not the page. | | `progress` | `number` | | Set the progress yourself, 0 to 1, instead of following the scroll. | | `onProgressChange` | `(progress: number) => void` | | Called when the progress changes. | | `ratio` | `number` | `The image's ratio` | Width over height. | ## Notes [#notes] * Under reduced motion the image is shown finished: the photo, or the fine dither with `end="dither"`. * The root has `role="img"` and your `alt` text as its label, so screen readers hear the image once, whatever stage it is at. `data-stage` is `coarse`, `fine` or `photo`. * Each coarseness is dithered once and cached, so scrolling back and forth costs nothing after the first pass. Scroll handling only runs while the image is near the viewport, at most once per frame. # Avatar A generative avatar: a mirrored dither face drawn from a name, with a group that overlaps and counts the rest. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { Avatar, AvatarGroup } from "@/components/dithercn/avatar"; export function Owner() { return ; } export function Viewers({ names }: { names: string[] }) { return ( {names.map((name) => ( ))} ); } ``` `name` seeds a small random generator that fills half of a 5 by 5 grid of blocks, and the other half mirrors it. A block is more likely to be on near the centre, so every face is a compact mark rather than noise, and the middle column always has at least two blocks. Each block is full or half tone, and the fill decides how half tone is drawn. Behind the face sits a disc of sparse dither in the same colour, densest at the top left as if lit, and kept one cell clear of the face so the mark stays crisp. The same string always gives the same face and colour, on the server and in the browser, so you can use an email or a user id and get a stable avatar for free. Without `color`, the avatar picks one of six palette hues from the string. Pass a palette name or any CSS colour to set it yourself. ## Examples [#examples] ### Group with a count [#group-with-a-count] `AvatarGroup` overlaps its avatars, cuts a gap around each in the surface colour, and replaces the rest with a "+N" chip. Set `outline` to the colour behind the group so the gaps disappear into it. ### Members with presence [#members-with-presence] A member list seeded by email, labelled by name. `status` adds a dot and appends the state to the accessible name, as in "Alan Turing, busy". The owner gets a `ring`; the bot gets a grey hatched face. ### Fills [#fills] The same six names in every fill. Gradient draws half tone as a Bayer checker; dotted and hatched texture it instead; solid uses a lighter opacity. Full blocks are solid in every fill. ## Props [#props] ### Avatar [#avatar] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `name` (required) | `string` | | The string the face and colour come from, such as a name, email or id. Also the accessible name. | | `label` | `string` | | Accessible name to use instead of name, when name is an id. | | `size` | `number` | `40` | Diameter in pixels. Inside a group, the group's size. | | `color` | `DitherColor` | | A palette name or any CSS colour. Without it, one of six hues picked from name. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | How each block's tone is drawn. | | `shape` | `"circle" \| "square"` | `"circle"` | A circle or a rounded square. | | `ring` | `boolean` | `false` | A ring in the avatar's colour, for the current user or a selection. | | `status` | `"online" \| "away" \| "busy" \| "offline"` | | A presence dot in the corner, added to the accessible name. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in the avatar's colour. | ### AvatarGroup [#avatargroup] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `max` | `number` | | Show at most this many avatars, then a +N chip. | | `size` | `number` | `32` | Diameter of every avatar in pixels. | | `outline` | `string` | `"var(--background)"` | Colour of the gap around each avatar. Match the surface behind the group. | | `moreLabel` | `(count: number) => string` | `"N more"` | What the +N chip announces to screen readers. | `avatarFace(name)` and `avatarColor(name)` are exported too, if you want the same face elsewhere, such as an Open Graph image. ## Notes [#notes] * The avatar is a `role="img"` element named by `label` or `name`, plus the status when there is one. The group is a `role="group"`; give it an `aria-label` such as "Viewers" when the context does not say. * The face is one or a few SVG paths, cached per name and fill. No canvas, no client JavaScript. * Avatars in a group lift by 2 pixels on hover on devices that can hover. With reduced motion they stay put. # Dither button A button with a lit, dithered surface: a highlight dithered down from the top, a shade dithered up from the bottom, brighter on hover and pressed in on click. > 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). ## Installation [#installation] ## Usage [#usage] ```tsx import { DitherButton } from "@/components/dithercn/dither-button"; export function Actions() { return ( <> Deploy View logs ); } ``` `DitherButton` renders a real `