/`, where it plays next to the code to use it. You can also [see them all on one page](https://ascii.rest/#pieces).
| category | pieces | what it holds |
| --- | --- | --- |
| scenes | 15 | full-colour places that move: [night coast](https://ascii.rest/night-coast/), [tokyo rain](https://ascii.rest/tokyo-rain/), a misty forest, the Taj Mahal at dawn |
| ui | 12 | parts for a page: [spinners](https://ascii.rest/spinners/), progress bars, a skeleton loader, a 404, a file tree, a clock |
| data | 10 | live charts and meters: a [bar chart](https://ascii.rest/bar-chart/), candlesticks, a gauge, sparklines, a heatmap |
| type | 9 | moving text that takes your own words: a [typewriter](https://ascii.rest/typewriter/), a split-flap board, a marquee, a glitch |
| logos | 29 | logos of programming languages and web tools: [rust](https://ascii.rest/rust/), [python](https://ascii.rest/python/), go, typescript and more |
| companies | 21 | company and product logos, like [vercel](https://ascii.rest/vercel/) and [cloudflare](https://ascii.rest/cloudflare/) |
| distros | 23 | Linux distribution logos: [arch linux](https://ascii.rest/arch-linux/), debian, ubuntu, nixos, and [tux](https://ascii.rest/tux/) the penguin |
| shapes | 12 | 3D shapes that turn: the [donut](https://ascii.rest/donut/), a cube, a tesseract, a DNA helix |
| space | 11 | planets, a [black hole](https://ascii.rest/black-hole/), a galaxy, an eclipse, a rocket launch |
| physics | 14 | simulations: a [double pendulum](https://ascii.rest/double-pendulum/), falling sand, Newton's cradle, smoke |
| nature | 16 | weather, fire and plants: [rain](https://ascii.rest/rain/), snowfall, an aurora, a campfire, a bonsai |
| creatures | 10 | animals: a sleeping [cat](https://ascii.rest/cat/), a fox, an owl, a whale, an aquarium |
| objects | 14 | everyday things: an [analog clock](https://ascii.rest/analog-clock/), a cup of coffee, a lava lamp, a train |
| generative | 13 | maths art: the [mandelbrot](https://ascii.rest/mandelbrot/) set, a maze, a Game of Life glider gun, Voronoi cells |
| effects | 8 | classic demo effects: [doom fire](https://ascii.rest/doom-fire/), matrix rain, fireworks, synthwave |
The pieces come in two kinds, and each kind draws in its own way:
- A **text piece** is plain text in your page's colour and font, drawn in a ``. 129 pieces are text pieces.
- A **coloured piece** has colours of its own and is drawn on a ``. The 88 scenes, logos, companies and distros are coloured pieces.
A banner with no colour is drawn like a text piece. A banner with a colour is drawn like a coloured piece.
On a web page, a piece stops and starts on its own:
- It plays only while it is on screen and its tab is open.
- For readers who prefer reduced motion, it holds one still frame.
Besides the pieces, you can turn any text into a banner. Read [banners](https://ascii.rest/docs/banners/), or make one in the [banner maker](https://ascii.rest/banner/).
## Use it with an AI coding agent
Give your agent one of these plain-text files. Both are built from these docs pages.
- [ascii.rest/llms.txt](https://ascii.rest/llms.txt): an index, with one line and a link for each docs page. It includes the rules below.
- [ascii.rest/llms-full.txt](https://ascii.rest/llms-full.txt): every docs page in one file, to read all at once.
For example, tell it: "Read https://ascii.rest/llms-full.txt, then add the night-coast piece to my home page."
## Rules for AI coding agents
If your agent can't read web pages, paste these rules into its prompt.
- The npm package is `ascii.rest`. It has no dependencies, and only `ascii.rest/react` needs React.
- A piece's name has dashes, like `night-coast`. Its export from `ascii.rest/pieces` is in camelCase, like `nightCoast`.
- `npx ascii.rest list` prints every piece's name.
- React: `import { Ascii, Banner } from "ascii.rest/react"`. Both are client components already (`"use client"`).
- In a React server component, pass a piece's name, ` `, not an imported module.
- Plain HTML: ``, then ` ` or ` `. Always write the closing tag.
- Astro: `import Ascii from "ascii.rest/astro"` and `import Banner from "ascii.rest/astro/banner"`.
- Text pieces draw in a ``. Coloured pieces (scenes, logos, companies, distros) and banners with a colour draw on a ``.
- React's `` adds no CSS to a text piece's ``: set `line-height: 1.2; margin: 0` on it. The HTML tags and the Astro components set both for you.
- Size a text piece with `font-size`. Size a coloured piece with `width` (in React, in `style`, not a class), and never set its height.
- Colours are six-digit hex, like `#f97316`. `#fff` and `orange` don't work.
- `banner()` throws on an option it can't take. `` and `` then draw nothing and `console.warn` why.
- The package has no function that turns an image into ascii. That is the page https://ascii.rest/make/, which runs in a browser.
## Next
- [quick start](https://ascii.rest/docs/quickstart/): from nothing to a piece on your page, one step at a time.
- [examples](https://ascii.rest/docs/examples/): more examples to copy and paste.
- [banners](https://ascii.rest/docs/banners/): any text in block letters, with every option.
---
# quick start
URL: https://ascii.rest/docs/quickstart/
Get an animation on your page in under two minutes, with one script tag, with React or Next.js, or in your terminal.
Pick the way that fits your project and follow its steps. Each one ends with something moving on your screen.
(A video on this page shows this: One script tag on a plain page, then npm install and a banner in React.)
Two words you will see on every page. A **piece** is one animation, like `donut` or `night-coast`. A **banner** is any text you choose, drawn in big block letters.
## On a plain HTML page
Use this for an HTML file with no build step. There is nothing to install.
1. Add the script tag to your page. It can go in the `` or the ``.
```html
```
2. Add a tag where you want the animation. `` plays a piece. `` draws your text as a banner.
```html
```
3. Open the page in your browser. The donut turns. A bright band, the glint, sweeps across the banner every few seconds:
(A live example plays here on the page.)
Here is the whole page. Save it as `index.html` and open it. It works straight from your disk, with no local server.
```html
```
The script loads each piece from ascii.rest the first time your page uses it. To play another piece, change `donut` to any name from [the list of every piece](https://ascii.rest/#pieces). Every attribute is on [html](https://ascii.rest/docs/html/).
## In React or Next.js
Use this in a React or Next.js app. The components come from npm.
1. Install the package:
```sh
npm install ascii.rest
```
2. Add `` and `` to a page. In Next.js, use `app/page.tsx`. In any other React app, use any component.
```tsx
import { Ascii, Banner } from "ascii.rest/react";
export default function Page() {
return (
);
}
```
3. Keep the donut's rows close together. The donut is a text piece, drawn in a ``. `` adds no CSS of its own. So the `` takes your page's line height and the browser's margin. Change the `` line to this:
```tsx
import { Ascii } from "ascii.rest/react";
```
The banner needs no change. It has a colour, so it draws on a ``. To do this with a class instead, see [set the size](https://ascii.rest/docs/react/#set-the-size).
4. Run your dev server, then open the URL it prints. For Next.js, that is `http://localhost:3000`.
```sh
npm run dev
```
You see this:
(A live example plays here on the page.)
`` draws any text you give it. `` plays a piece by its name. It loads that piece in the browser when the component mounts. Both are client components, so a Next.js server component can use them without adding `"use client"` yourself. Their props are on [react](https://ascii.rest/docs/react/). Server components are on [next.js](https://ascii.rest/docs/nextjs/).
## In your terminal
Use this to see a piece without writing any code. You need Node 18.3 or newer. The first time, `npx` asks to download the package: press `y`.
Play a piece. It plays until you press any key:
```sh
npx ascii.rest donut
```
Print a banner. The glint sweeps across it once. Then the banner stays in your terminal with the rest of your output:
```sh
npx ascii.rest banner "hi"
```
```text
▓▓╗ ▓▓╗ ▓▓╗
▓▓║ ▓▓║ ▓▓║
▓▓▓▓▓▓▓▓║ ▓▓║
▓▓╔═══▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║
╚═╝ ╚═╝ ╚═╝
```
Add colour with `--color`. Two or more colours make a fade:
```sh
npx ascii.rest banner "hi" --color f97316,f778ba
```
To see the name of every piece, run `npx ascii.rest list`. Every command and flag is on [cli](https://ascii.rest/docs/cli/).
## Next
- [examples](https://ascii.rest/docs/examples/): more small examples you can copy.
- [banners](https://ascii.rest/docs/banners/): change a banner's font, shadow, colours and motion.
- [github readme](https://ascii.rest/docs/readme/): put a banner or a logo in your README, as an animated SVG.
---
# install
URL: https://ascii.rest/docs/install/
Add ascii.rest to your project from npm, with one script tag, or as source files you own.
There are three ways to add ascii.rest. Pick the one that fits your project:
| way | use it when | how |
| --- | --- | --- |
| npm | your app has a build step, like Next.js, Vite or Astro | [Install from npm](#install-from-npm) |
| one script tag | you have a plain HTML page with no build step | [Add one script tag](#add-one-script-tag) |
| your own copy | you want to own and change the code | [Copy the source into your project](#copy-the-source-into-your-project) |
## Install from npm
Run the command for your package manager:
| package manager | command |
| --- | --- |
| npm | `npm install ascii.rest` |
| pnpm | `pnpm add ascii.rest` |
| yarn | `yarn add ascii.rest` |
| bun | `bun add ascii.rest` |
The package has no dependencies of its own. You need React only if you import `ascii.rest/react`. The package is ES modules, so load it with `import`.
To check that it works, play a piece on your page. A piece is one animation. This one is a spinning donut:
```ts
import { mount } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
const pre = document.createElement("pre");
document.body.append(pre);
mount(pre, donut);
```
`mount()` plays the piece in the element and returns a function that stops it. In React, use ` ` instead: see [react](https://ascii.rest/docs/react/).
## Add one script tag
Use this on a plain HTML page with no build step. The script defines two tags: `` plays any piece, and `` draws any text in big block letters.
```html
```
(A live example plays here on the page.)
The script loads each piece from ascii.rest the first time your page uses it. Every attribute of both tags is on [html](https://ascii.rest/docs/html/).
`https://ascii.rest/ascii.js` always serves the newest version, so it can change without you doing anything. To stay on one version, with an integrity hash, see [Pin a version](https://ascii.rest/docs/html/#pin-a-version).
## Copy the source into your project
Copy the TypeScript into your project when you want to own and change the code. With [shadcn](https://ui.shadcn.com):
```sh
npx shadcn@latest add https://ascii.rest/r/ascii.json https://ascii.rest/r/donut.json
```
Or with the ascii.rest command, no shadcn needed:
```sh
npx ascii.rest add ascii donut
```
Both add the `` React component (the `ascii` item) and the donut piece (the `donut` item). The files go in `components/ascii/`, or `src/components/ascii/` if your project has a `src` folder: see [Where the files go](https://ascii.rest/docs/copy/#where-the-files-go). [Your own copy](https://ascii.rest/docs/copy/) lists every item and shows how to use the files.
## Find the right import
Each part of the library has its own import path. Import only the parts you use.
| import | what it gives you | docs |
| --- | --- | --- |
| `ascii.rest` | `mount()`, which plays a piece in an element, plus `load`, `names`, `isPiece` and `canvas`, to find a piece by its name | [typescript](https://ascii.rest/docs/typescript/) |
| `ascii.rest/pieces` | every piece, each by its export in camelCase: `donut`, `nightCoast` | [typescript](https://ascii.rest/docs/typescript/) |
| `ascii.rest/pieces/` | one piece, by the piece's name, with dashes: `ascii.rest/pieces/night-coast` | [your own pieces](https://ascii.rest/docs/pieces/) |
| `ascii.rest/react` | the `` and `` components for React and Next.js | [react](https://ascii.rest/docs/react/) |
| `ascii.rest/astro` | the `` component for Astro | [astro](https://ascii.rest/docs/astro/) |
| `ascii.rest/astro/banner` | the `` component for Astro | [astro](https://ascii.rest/docs/astro/) |
| `ascii.rest/element` | the `` and `` tags, defined as soon as you import it | [html](https://ascii.rest/docs/html/) |
| `ascii.rest/banner` | `banner()`, which turns any text into a piece in block letters | [banners](https://ascii.rest/docs/banners/) |
| `ascii.rest/svg` | `svg()` and `bannerSvg()`, which turn a piece or a banner into an animated SVG | [svg](https://ascii.rest/docs/svg/) |
| `ascii.rest/terminal` | `play()`, `still()` and `banner()`, which draw in a terminal from Node | [terminal](https://ascii.rest/docs/terminal/) |
The package also has a command, `npx ascii.rest`, which plays pieces and prints banners in your terminal: see [cli](https://ascii.rest/docs/cli/).
Each piece is its own module, so your bundle holds only the pieces you import. To choose a piece by its name while the page runs, use `load`: see [Load a piece by its name](https://ascii.rest/docs/typescript/#load-a-piece-by-its-name).
## Check what it needs
| part | needs |
| --- | --- |
| pieces on a web page | any current browser |
| `ascii.rest/react` | React 18 or newer. No other import needs React. |
| `ascii.rest/terminal` and `npx ascii.rest` | Node 18.3 or newer |
| types | nothing to install: they come with the package. If TypeScript can't find `ascii.rest`, set `moduleResolution` to `bundler`, `node16` or `nodenext` in your `tsconfig.json`. |
## Next
- [quick start](https://ascii.rest/docs/quickstart/): a banner and a piece on your page in a minute
- [react](https://ascii.rest/docs/react/): `` and `` in React and Next.js
- [html](https://ascii.rest/docs/html/): every attribute of the two tags
---
# examples
URL: https://ascii.rest/docs/examples/
Recipes to copy and paste, one for each thing people build most, from a hero scene and a 404 page to a README banner and a CLI splash screen.
Each recipe below does one job. It says when you would use it, gives you code to paste, and shows the result where it runs on a page.
Two words you will see a lot. A **piece** is one animation from the library, like `donut` or `night-coast`. A **banner** is any text you choose, drawn in big block letters. Every piece's name is in [the list of every piece](https://ascii.rest/#pieces).
The web recipes use React, plain HTML or Astro. Each one works in the other two as well: ` ` in React is ` ` in HTML. Every recipe that imports from `ascii.rest` needs the package installed first:
```sh
npm install ascii.rest
```
## Add a scene to the top of your landing page
Use this when you want a big moving picture behind your page's headline.
```tsx
// app/page.tsx
import { Ascii } from "ascii.rest/react";
export default function Home() {
return (
);
}
```
(A live example plays here on the page.)
The scene and the heading share one grid cell, so the heading sits on top of the scene. A scene is a coloured piece: it draws on a `` as wide as its container, and twice as wide as it is tall. You pass its name as a string, so the piece loads as a file of its own when the component mounts. It isn't part of your page's JavaScript. It only plays while it is on screen.
Other scenes to try: `ocean-sunset`, `kyoto-dusk`, `misty-forest`, `aurora-fjord`, `tokyo-rain`.
## Show a spinner while data loads
Use this when part of your page waits on a request.
```tsx
"use client";
import { useEffect, useState } from "react";
import { Ascii } from "ascii.rest/react";
export function Posts() {
const [posts, setPosts] = useState(null);
useEffect(() => {
fetch("/api/posts")
.then((response) => response.json())
.then(setPosts);
}, []);
if (!posts) return ;
return (
{posts.map((post) => (
{post}
))}
);
}
```
The `spinners` piece holds twelve spinners. These are all of them, with their names:
(A live example plays here on the page.)
| option | what it does | default |
| --- | --- | --- |
| `names` | the spinners to show, in order: `line`, `dots`, `pipe`, `arc`, `bounce`, `clock`, `bar`, `loop`, `scan`, `wave`, `slide`, `orbit` | `[]`, which shows all twelve |
| `labels` | shows each spinner's name under it | `true` |
| `speed` | how fast they spin: `2` is twice as fast | `1` |
In plain HTML it is one tag:
```html
```
When your data arrives, remove it with `document.getElementById("spinner").remove()`.
## Show a progress bar
Use this to decorate a page about work going on, like a deploy screen or a "coming soon" page.
```html
```
(A live example plays here on the page.)
The piece draws five bars, each in a different style. Each bar fills at its own uneven pace, holds at 100%, and starts again. The bars move by themselves: they don't follow any real work. To show how far a real task has got, use the HTML `` element.
| option | what it does | default |
| --- | --- | --- |
| `labels` | the name beside each bar, top to bottom; the first nine characters of each show | `["fetching", "unpacking", "indexing", "building", "linking"]` |
## Make a 404 page
Use this for the page people see when they follow a broken link.
```tsx
// app/not-found.tsx
import { Ascii } from "ascii.rest/react";
export default function NotFound() {
return (
go home
);
}
```
(A live example plays here on the page.)
In the Next.js app router, `app/not-found.tsx` is the page for every URL that matches nothing. In Astro, the same piece goes in `src/pages/404.astro`.
| option | what it does | default |
| --- | --- | --- |
| `code` | the big number, up to three digits | `"404"` |
| `title` | the line under the ghost, up to 60 characters | `"page not found"` |
| `message` | the line under the title, up to 60 characters | `"the page you asked for has moved or never existed"` |
## Put your name in your site's header
Use this to show your name, or your project's, in big letters at the top of every page.
```tsx
// components/header.tsx
import { Banner } from "ascii.rest/react";
export function Header() {
return (
);
}
```
(A live example plays here on the page.)
With no colour, a banner is drawn like a text piece: plain text in your page's text colour, so it matches your header. `font="slim"` makes narrower letters, and `fontSize` sets the size. Letters are drawn as capitals. A banner draws letters, digits, spaces and `. , ! ? ' : - + = / _`, and leaves out anything else. Every option is on [banners](https://ascii.rest/docs/banners/#options).
## Change a banner's colours with light and dark mode
Use this when your site has a light and a dark theme, and one colour can't suit both.
```tsx
import { Banner } from "ascii.rest/react";
export function Logo() {
return ;
}
```
(A live example plays here on the page.)
Switch this site's theme with the button in its header to see the demo change.
The banner reads its own CSS `color`, which it inherits from the page. Dark text means a light page, so it uses `light`. Light text means a dark page, so it uses `dark`. It checks each time it draws a frame, so a moving banner follows a theme switch.
A still banner doesn't follow a theme switch straight away. That is a banner with `effect: "still"`, or any banner shown to a reader who prefers reduced motion. It keeps its first colours until its width changes or the page reloads.
Each side takes one colour, or a list of colours for a fade. `shadowColor` takes `{ light, dark }` too:
```tsx
import { Banner } from "ascii.rest/react";
```
In HTML, put `{ light, dark }` in the `options` attribute, as JSON:
```html
```
## Put a small logo next to text
Use this for a "built with" line or a footer credit, where a full-colour logo would be too loud.
```html
```
(A live example plays here on the page.)
`mono` draws a coloured piece as text in one colour: the CSS `color` of its tag, so it matches the words beside it. The rust logo is 64 columns by 32 rows, so at `font-size: 2px` it is under 80 pixels wide. In React it is ` `.
## Make a piece bigger or smaller
Use this to fit a piece into its space on your page. How you size it depends on how it draws.
- **Text pieces** draw in a ``. Set `font-size`. These are every piece except the scenes, logos, companies and distros. A banner with no colour is one too, and so is any piece with `mono`.
- **Coloured pieces** draw on a `` as wide as their tag. Set `width` or `max-width` on the tag, and the height follows. These are the scenes, logos, companies and distros, and a banner with a colour.
```css
/* a text piece: font-size sets its size */
ascii-art.small {
font-size: 6px;
}
/* a text piece that shrinks on small screens */
ascii-art.fluid {
font-size: clamp(6px, 1.6vw, 14px);
}
/* a coloured piece fills its tag's width, so cap the tag */
ascii-art.logo {
max-width: 240px;
}
```
(A live example plays here on the page.)
With the HTML tag, size ``, not the `` inside it. The canvas has `width: 100%` set inline, which beats a `width` in your stylesheet.
In React there is no wrapper, so `style` and `className` go straight on the `` or ``:
```tsx
import { Ascii } from "ascii.rest/react";
```
Give a canvas its width in `style`. A `width` from a class loses to the `width: 100%` that `` sets inline. On a ``, `` adds no CSS at all, so give a text piece `line-height: 1.2; margin: 0` too. See [set the size](https://ascii.rest/docs/react/#set-the-size).
## Respect reduced motion
Use this to know what your readers see when they have asked their system for less motion, and how to give them a play button.
You don't need to do anything for the default:
- On a page, a piece shows one frame and stays still. A banner that types in shows all its letters.
- If the reader changes the setting while the page is open, the piece stops or starts to match.
- An SVG made by ascii.rest holds one frame.
- Every piece on a page also pauses while it is off screen or its tab is hidden.
If your system asks for reduced motion, this donut holds still:
(A live example plays here on the page.)
The `motion` option plays a piece even for a reader who prefers reduced motion. Only use it behind a control the reader chooses, such as a play button:
```tsx
"use client";
import { useState } from "react";
import { Ascii } from "ascii.rest/react";
export function Art() {
const [playing, setPlaying] = useState(false);
return (
setPlaying(true)}>play
);
}
```
For most readers the donut plays at once, and the button only starts it again. For a reader who prefers reduced motion, it holds still until they press the button.
`motion` works in `mount(el, piece, { motion: true })`, in `` (React and Astro), and in ``. `` and `` don't take it. To give a banner a play button, make the banner with `banner()` and pass it to ``:
```tsx
"use client";
import { useState } from "react";
import { Ascii } from "ascii.rest/react";
import { banner } from "ascii.rest/banner";
// Made once, outside the component, so a re-render doesn't start it again.
const hello = banner("hello", { effect: "type" });
export function Hello() {
const [playing, setPlaying] = useState(false);
return (
setPlaying(true)}>play
);
}
```
To test it, turn on reduced motion. On a Mac: System Settings, then Accessibility, then Display, then Reduce motion. In Chrome: open DevTools, open the Rendering panel, and set "Emulate CSS media feature prefers-reduced-motion" to `reduce`.
## Start a plain HTML page from scratch
Use this when you have no build step and nothing to install. You need one `
```
(A live example plays here on the page.)
The script defines `` and ``. It loads each piece from ascii.rest the first time the page uses it. To show a different piece, change `donut` to another piece's name, with dashes: `night-coast`. To add a banner, add one more tag:
```html
```
`https://ascii.rest/ascii.js` is always the latest build. To load one fixed version with an `integrity` hash instead, see [pin a version](https://ascii.rest/docs/html/#pin-a-version). Every attribute is on [html](https://ascii.rest/docs/html/).
## Build an Astro landing page
Use this for an Astro site. A text piece's first frame is drawn on the server, so it shows before any script runs. A coloured piece keeps its space on the page until it draws.
```astro
---
// src/pages/index.astro
import Ascii from "ascii.rest/astro";
import Banner from "ascii.rest/astro/banner";
---
my project
```
(A live example plays here on the page.)
`effect="type"` types the letters in once, and they stay. A scoped `
```
Coloured art fills the element's width, and its height follows from its shape. Set a `width` or `max-width` to size it. Don't set a height.
```astro
---
import Ascii from "ascii.rest/astro";
---
```
A coloured banner reads your text colour to pick its colours. With `color={{ light: "#1f2328", dark: "#f0f6fc" }}`, it uses `light` when your text is dark, and `dark` when your text is light.
### Use a global style
Astro scopes a plain `
```
## Build a landing page
This is a whole page. Paste it into `src/pages/index.astro`. It has a typed banner for the name, a line of text, and a coloured scene.
```astro
---
import Ascii from "ascii.rest/astro";
import Banner from "ascii.rest/astro/banner";
---
my app
A short line about what your app does.
```
(A live example plays here on the page.)
- The banner has no `color`, so its letters are in the HTML as text. The `.logo` rule makes them orange.
- The scene is coloured, so the HTML holds a dark box in its shape. The script paints the scene into it.
- All the CSS is in `
```
The browser downloads that font only when the fonts before it lack a character.
## Change a piece's options
Pass options as the third argument to `mount()`.
```ts
import { mount } from "ascii.rest";
import { archLinux, bigText, donut } from "ascii.rest/pieces";
mount(document.querySelector("#title")!, bigText, { text: "hello" });
mount(document.querySelector("#logo")!, archLinux, { scan: 0 }); // no scan line: the logo holds still
mount(document.querySelector("#art")!, donut, { fps: 12 });
```
(A live example plays here on the page.)
| option | what it does | default |
| --- | --- | --- |
| any option of the piece | Changes the piece. `bigText` takes `text`. `archLinux` takes `scan`: the seconds between the lines that sweep down the logo. | the piece's `meta.options` |
| `fps` | Frames a second. Fewer frames means less work: the piece moves at the same speed, less smoothly. `0` draws one frame and stops. | the piece's `meta.fps`, or 30 |
| `motion` | `true` plays the piece even when the reader has asked for reduced motion. Use it only on a page with its own play button. | `false` |
To see which options a piece takes, and their defaults, read `meta.options`:
```ts
import { archLinux, bigText } from "ascii.rest/pieces";
console.log(bigText.meta.options); // { text: "hello" }
console.log(archLinux.meta.options); // { scan: 5 }
```
### Have TypeScript check the options
`mount()` accepts any options object, so it doesn't check the values you pass. Every piece that takes options exports a type for them, named after the piece: `BigTextOptions`, `SpinnersOptions`. Import it from the piece's own module and type your options with it:
```ts
import { mount } from "ascii.rest";
import { bigText } from "ascii.rest/pieces";
import type { BigTextOptions } from "ascii.rest/pieces/big-text";
const options: Partial = { text: "hello" };
mount(document.querySelector("#title")!, bigText, options);
```
Now `{ text: 42 }` is an error, because `text` takes a string. These types allow extra keys, so a misspelt option name, like `{ txt: "hello" }`, still compiles.
## Stop it
`mount()` returns a function. Call it to stop the piece.
```ts
import { mount } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
const pre = document.querySelector("#art")!;
const stop = mount(pre, donut);
document.querySelector("#stop")!.addEventListener("click", () => stop());
```
Stopping does three things:
- It stops drawing frames.
- It removes the observers and listeners `mount()` added.
- It leaves the last frame in the element. Set `pre.textContent = ""` to clear it.
Call `stop()` before you mount another piece in the same element. If you don't, both keep drawing into it. This swaps one piece for another:
```ts
import { mount, type Piece } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
const pre = document.querySelector("#art")!;
let stop = mount(pre, donut);
function show(piece: Piece) {
stop();
stop = mount(pre, piece);
}
```
Mounting a piece again starts it from the beginning.
## When it plays and when it pauses
`mount()` saves work for you. You don't have to do anything for this.
- It plays only while the element is on screen. When you scroll it out of view, it pauses.
- It pauses while the browser tab is hidden.
- While it is paused, its time stops too. When it plays again, it carries on from the same moment.
- If the reader has asked for reduced motion in their system settings, it shows one still frame and doesn't play. That is the moment the piece names in `meta.still`, or its first frame. A banner that types itself in shows every letter.
- If the reader changes that setting while the page is open, it starts or stops to match.
## Play a banner
`banner()` from `ascii.rest/banner` turns any text into a piece, so `mount()` plays it like any other.
```ts
import { mount } from "ascii.rest";
import { banner } from "ascii.rest/banner";
mount(document.querySelector("#title")!, banner("hello", { shadow: "rounded" }));
```
(A live example plays here on the page.)
A banner with a `color` is a coloured piece, so mount it on a `` to see the colours. The canvas's CSS `color` tells the banner whether your page is light or dark: dark text means a light page.
```ts
import { mount } from "ascii.rest";
import { banner } from "ascii.rest/banner";
const canvas = document.querySelector("#title")!;
canvas.style.width = "24rem";
mount(canvas, banner("hello", { color: ["#f97316", "#f778ba"], shadow: "rounded" }));
```
(A live example plays here on the page.)
`banner()` throws if an option is wrong, such as a font name it doesn't know. The error says what to change. Every banner option, from fonts to colours, is on [banners](https://ascii.rest/docs/banners/#options).
## Load a piece by its name
Use these when the piece is chosen while the page runs, for example from a menu or the URL. They all come from `ascii.rest`.
| export | what it is |
| --- | --- |
| `load` | One function for each piece, by name. `await load["night-coast"]()` imports that piece. |
| `names` | The names of every piece, in alphabetical order. |
| `isPiece(name)` | `true` if a string is a piece's name. In TypeScript, it also narrows the string to the `PieceName` type. |
| `canvas` | A `Set` of the names of the coloured pieces: the ones to draw on a ``. |
These use the piece's name, with dashes: `night-coast`. The export from `ascii.rest/pieces` is the same piece in camelCase: `nightCoast`. So `isPiece("night-coast")` is `true` and `isPiece("nightCoast")` is `false`.
`load` imports a piece only when you call its function. Bundlers such as Vite put each piece in its own file, so your page downloads only the pieces it plays.
This page plays the piece named in its URL, like `?piece=night-coast`:
```ts
import { canvas, isPiece, load, mount } from "ascii.rest";
const name = new URLSearchParams(location.search).get("piece") ?? "donut";
if (isPiece(name)) {
const piece = await load[name]();
const el = document.createElement(canvas.has(name) ? "canvas" : "pre");
document.body.append(el);
mount(el, piece);
}
```
This one makes a menu of every piece and plays the one you pick:
```ts
import { canvas, isPiece, load, mount, names } from "ascii.rest";
const select = document.createElement("select");
for (const name of names) select.add(new Option(name));
const box = document.createElement("div");
document.body.append(select, box);
let stop = () => {};
select.addEventListener("change", async () => {
const name = select.value;
if (!isPiece(name)) return;
const piece = await load[name]();
stop();
const el = document.createElement(canvas.has(name) ? "canvas" : "pre");
box.replaceChildren(el);
stop = mount(el, piece);
});
```
## Draw a frame yourself
You don't need `mount()` to get a frame. A piece is a module with `meta` and a `default` function:
1. Call `default()` with the piece's options. It returns the piece's frame function.
2. Call the frame function with a time in seconds. It returns the frame at that moment, as a string.
```ts
import { banner } from "ascii.rest/banner";
const frame = banner("hi", { shadow: "rounded" }).default();
console.log(frame(0));
```
That prints:
```text
▓▓╮ ▓▓╮ ▓▓╮
▓▓│ ▓▓│ ▓▓│
▓▓▓▓▓▓▓▓│ ▓▓│
▓▓╭───▓▓│ ▓▓│
▓▓│ ▓▓│ ▓▓│
╰─╯ ╰─╯ ╰─╯
```
The same works for every piece in `ascii.rest/pieces`:
```ts
import { bigText, donut } from "ascii.rest/pieces";
const frame = donut.default();
const text = frame(1.5); // the donut at 1.5 seconds
const hello = bigText.default({ text: "hello" })(0); // options go to default()
console.log(text, hello);
```
What to know about frames:
- A frame is `meta.rows` lines of `meta.cols` characters, joined with `\n`. There is no newline at the end.
- Drawing a frame needs no browser. It works in Node, in a web worker and on a server.
- Most pieces give the same frame for the same time, whatever you drew before. A few are simulations, like `falling-sand` and `doom-fire`: each call moves them on from the call before. Call those with a rising time, as `mount()` does.
- A piece with `meta.clock` set, like `digital-clock`, shows the real time or date.
### Tell a frame about the page
The frame function takes a second argument, an `Env` object:
| field | what it does | default |
| --- | --- | --- |
| `paper` | Set it to `true` when you draw dark text on a light background. Pieces drawn in shades, like `donut`, swap their light and dark characters so they still read. | `false` |
| `color` | For a coloured piece: a `Uint8Array` of `cols * rows` numbers. The frame writes each cell's colour into it, as an index into `meta.palette`. | none |
```ts
import { donut } from "ascii.rest/pieces";
const onWhite = donut.default()(1.5, { paper: true });
console.log(onWhite);
```
To get the colours of a coloured piece, pass a `color` array and read it back. Cell `x, y` is at index `y * cols + x`:
```ts
import { typescript } from "ascii.rest/pieces";
const { cols, rows, palette } = typescript.meta;
const color = new Uint8Array(cols * rows);
const lines = typescript.default()(0, { color }).split("\n");
const x = 10, y = 5;
console.log(lines[y][x], palette[color[y * cols + x]]); // 8 #007acc: a cell of the blue square
```
### Run your own loop
This loop is a bare version of `mount()`, with no pausing, no colour and no canvas:
```ts
import { donut } from "ascii.rest/pieces";
const pre = document.querySelector("#art")!;
const frame = donut.default();
const start = performance.now();
function tick(now: number) {
pre.textContent = frame((now - start) / 1000);
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
```
### Play a frame function of your own
`mount()` also takes a plain function instead of a piece. That function must return a frame function. With no `meta`, it plays at 30 frames a second, and a canvas gets 80 columns by 24 rows.
```ts
import { mount } from "ascii.rest";
const dots = () => (t: number) => ".".repeat(1 + (Math.floor(t * 4) % 10));
mount(document.querySelector("#art")!, dots);
```
To give your own picture a size, colours and options, write it as a piece: see [your own pieces](https://ascii.rest/docs/pieces/).
## Use the types
These types come from `ascii.rest`:
| type | what it is |
| --- | --- |
| `Piece` | A piece module: `{ meta, default }`. `Piece` gives its options the type `O`. |
| `Meta` | What a piece says about itself, as `piece.meta`: its name, size, frame rate, options and colours. |
| `Frame` | A frame function: `(t: number, env?: Env) => string`. |
| `Env` | The frame function's second argument: `{ paper?: boolean; color?: Uint8Array }`. |
| `Options` | Any piece's options: `Record`. |
| `Category` | The 15 categories, like `"scenes"`, `"logos"` and `"distros"`. |
| `PieceName` | The name of every piece, as a union of strings. |
| `MountOptions` | The third argument of `mount()`: a piece's options, plus `fps` and `motion`. |
Every field of `Meta`, what it does and its default, is in [fill in its meta](https://ascii.rest/docs/pieces/#fill-in-its-meta).
A helper that takes any piece and plays it in the right element:
```ts
import { mount, type MountOptions, type Piece } from "ascii.rest";
export function addPiece(parent: HTMLElement, piece: Piece, options?: MountOptions): () => void {
const el = document.createElement(piece.meta.palette ? "canvas" : "pre");
el.setAttribute("role", "img");
el.setAttribute("aria-label", piece.meta.name);
parent.append(el);
return mount(el, piece, options);
}
```
`PieceName` catches a misspelt name before the page runs:
```ts
import type { PieceName } from "ascii.rest";
const favourites: PieceName[] = ["donut", "night-coast", "rust"];
console.log(favourites);
```
The other modules export their own types:
```ts
import type { BannerOptions, BannerPiece, Colors, Effect, Font, FontName, ShadowName, Themed } from "ascii.rest/banner";
import type { BannerSvgColor, BannerSvgOptions, Loop, Part, Shot, SvgOptions } from "ascii.rest/svg";
import type { Bannered, Output, Played, PlayOptions, PrintOptions } from "ascii.rest/terminal";
```
Each is described on [api](https://ascii.rest/docs/api/).
## Next
- [your own pieces](https://ascii.rest/docs/pieces/): write a piece and play it with `mount()`.
- [banners](https://ascii.rest/docs/banners/): every option of `banner()`.
- [api](https://ascii.rest/docs/api/): everything each module exports.
---
# your own copy
URL: https://ascii.rest/docs/copy/
Copy the TypeScript of any piece, the banner or the React components into your project, so you own it and can change it.
You can copy ascii.rest's source into your project instead of installing it from npm. The files become yours: change any line, and nothing updates unless you ask.
(A video on this page shows this: Adding a piece with shadcn and with npx ascii.rest add, then using and changing the copied files.)
A **piece** is one animation, like `donut`. A **registry item** is one thing you can copy in: a piece, a React component, or a part of the library. The **registry** is the list of every item and its files, served from `https://ascii.rest/r/`.
You copy items with one of two commands, `npx shadcn add` or `npx ascii.rest add`. Both read the same registry and copy the same files.
Run this in your project's root folder:
```sh
npx ascii.rest add ascii donut
```
It copies the `` component and the `donut` piece into `components/ascii/`, or `src/components/ascii/` if your project has a `src` folder. Use them like any of your own files:
```tsx
"use client";
import { Ascii } from "@/components/ascii/ascii";
import * as donut from "@/components/ascii/pieces/donut";
export default function Page() {
return ;
}
```
The `"use client"` line is for the Next.js app router: it is explained [below](#in-react-and-nextjs).
(A live example plays here on the page.)
## Choose between npm and your own copy
Copy the source when you want to own the code. You can change a piece's characters, colours or speed, or how a component renders. Nothing is added to your `package.json`, and nothing changes when a new version comes out. You only get the files you add.
Install from npm instead when you want updates with `npm update`, or the parts that are not in the registry: the `` tag, the Astro components, and `play()` and `banner()` for the terminal. See [install](https://ascii.rest/docs/install/).
| | npm package | your own copy |
| --- | --- | --- |
| how you get it | `npm install ascii.rest` | `npx ascii.rest add` or `npx shadcn add` |
| change the code | no | yes, any line |
| updates | `npm update` | add the item again, see [update your copy](#update-your-copy) |
| in your `package.json` | `ascii.rest` | nothing new (the React components need `react`, which a React app already has) |
| play a piece by name, `piece="donut"` | yes | no: import the piece's file |
| ``, Astro, terminal | yes | no |
## Add files with shadcn
Use this if your project already uses [shadcn](https://ui.shadcn.com), so it has a `components.json` file. There are two ways: by URL, or by a short name after you register `@ascii` once.
### By URL
Every item has its own URL, `https://ascii.rest/r/- .json`. Pass one or more to `shadcn add`:
```sh
npx shadcn@latest add https://ascii.rest/r/ascii.json https://ascii.rest/r/donut.json
```
shadcn lists the files it created: `types.ts`, `mount.ts`, `ascii.tsx` and `pieces/donut.ts`, all in `components/ascii/`. You asked for two items and got four files. That is because `ascii` needs `core`: the two files every item uses, `types.ts` and `mount.ts`.
### By name, with the @ascii namespace
1. Open `components.json` and add a `registries` key. Keep everything else that is in the file.
```json
{
"registries": {
"@ascii": "https://ascii.rest/r/{name}.json"
}
}
```
2. Add items by name, with `@ascii/` in front:
```sh
npx shadcn@latest add @ascii/ascii-banner @ascii/night-coast
```
shadcn puts the name in place of `{name}`, so `@ascii/night-coast` fetches `https://ascii.rest/r/night-coast.json`.
If a file is already there and the same, shadcn skips it. If yours is different, it asks before replacing it. Add `--overwrite` to replace it without asking, or `--diff` to see what would change without writing anything.
## Add files without shadcn
Use this in any project. You need Node 18.3 or newer, and ascii.rest 0.3.0 or later. You don't need shadcn or a `components.json`.
1. Open a terminal in your project's root folder.
2. Run `npx ascii.rest add` with the items you want, separated by spaces:
```sh
npx ascii.rest add ascii donut
```
It prints every file it wrote, and any npm package those files import:
```text
wrote
components/ascii/types.ts
components/ascii/mount.ts
components/ascii/ascii.tsx
components/ascii/pieces/donut.ts
it needs react: npm install react
The docs for what you added: https://ascii.rest/docs/copy/
```
A React app has `react` already, so you can skip that install.
### What add does, step by step
`add` never asks a question, so a script, a CI job or a coding agent can run it. It works in this order:
1. It downloads each item you named from `https://ascii.rest/r`, and every item those need.
2. It checks every file before it writes any.
3. It writes the files into `src/components/ascii/` if your project has a `src` folder, and into `components/ascii/` if not. `--dir` picks another folder.
4. It keeps any file that is already there, unless you pass `--overwrite`.
5. It prints the files it wrote, the files it kept, and any npm package the files import.
If anything is wrong, it stops with an error and exit code 1, and writes nothing. It stops when:
- a download fails;
- a name you gave is not an item;
- a file would land outside the folder;
- a file's name has a control character in it;
- a file's content isn't text;
- an item asks for an npm package whose name isn't a valid package name.
The last four protect you from a registry someone else runs. A wrong name looks like this:
```sh
npx ascii.rest add rsut
```
```text
ascii.rest: there is no "rsut" to add (https://ascii.rest/r/rsut.json answered 404). npx ascii.rest list shows every piece.
```
### Name the items
Write a piece's name with dashes, as `npx ascii.rest list` prints it: `night-coast`, not `"night coast"`. Separate the names with spaces. Every item is listed in [every item you can add](#every-item-you-can-add).
You can also give an item's full URL, like `https://ascii.rest/r/donut.json`.
### Flags for add
| flag | what it does | default |
| --- | --- | --- |
| `--dir
` | puts the files in this folder, relative to where you run it | `src/components/ascii` if your project has a `src` folder, otherwise `components/ascii` |
| `--overwrite` | replaces files that are already there | off: files that are already there are kept |
| `--registry ` | reads the items, and the items they need, from another copy of the registry | `https://ascii.rest/r` |
`add` takes no other flag: another command's, like `--color` or `--seconds`, stops it with an error before anything is written, and so does a flag the CLI doesn't know. Which command takes which flag is on [cli](https://ascii.rest/docs/cli/#which-flags-each-command-takes).
### Put the files in another folder
The files go straight into the `--dir` folder. This writes `lib/ascii/types.ts`, `lib/ascii/mount.ts`, `lib/ascii/ascii.tsx`, `lib/ascii/banner.ts` and `lib/ascii/ascii-banner.tsx`:
```sh
npx ascii.rest add ascii-banner --dir lib/ascii
```
### Add an item again
Run `npx ascii.rest add donut` a second time, and it keeps the files you already have:
```text
kept, already there (--overwrite replaces them)
components/ascii/types.ts
components/ascii/mount.ts
components/ascii/pieces/donut.ts
The docs for what you added: https://ascii.rest/docs/copy/
```
To replace them, add `--overwrite`. It replaces every file the item brings, `core` included. If you changed any of them, read [update your copy](#update-your-copy) first.
```sh
npx ascii.rest add donut --overwrite
```
## Every item you can add
Each item brings the items it needs. Adding `ascii-banner` also adds `ascii`, `banner` and `core`.
| item | what it adds | it also adds |
| --- | --- | --- |
| `core` | `types.ts`, the shape every piece has, and `mount.ts`, with `mount()` to play a piece in a `` or `` | nothing |
| `ascii` | `ascii.tsx`: the `` component for React and Next.js | `core` |
| `banner` | `banner.ts`: `banner()`, which turns any text into a piece in block letters | `core` |
| `ascii-banner` | `ascii-banner.tsx`: the `` component for React and Next.js | `ascii`, `banner`, `core` |
| `svg` | `svg.ts`: `svg()` and `bannerSvg()`, which turn a piece or a banner into an animated SVG | `banner`, `core` |
| a piece's name, like `donut` | `pieces/donut.ts`: that one piece | `core` |
| `all` | every file above and every piece | everything |
`ascii` and `ascii-banner` import `react`. The other files import nothing outside the folder.
Every piece's name is on [the home page](https://ascii.rest/#pieces), and `npx ascii.rest list` prints them. The registry's index, every item and the files it brings, is at [ascii.rest/r/registry.json](https://ascii.rest/r/registry.json).
## Where the files go
Every file goes into one folder called `ascii`, inside your components folder. With everything added, it looks like this:
```text
components/ascii/
├── types.ts the shape every piece has (core)
├── mount.ts mount(): plays a piece in a or (core)
├── ascii.tsx (ascii)
├── banner.ts banner(): any text in block letters (banner)
├── ascii-banner.tsx (ascii-banner)
├── svg.ts svg() and bannerSvg() (svg)
└── pieces/
├── donut.ts
└── night-coast.ts
```
The files import each other by relative paths, like `./mount` and `../types`. Keep them together in this layout, or fix the imports if you move one. The imports leave off `.ts`, as most bundlers expect.
Which components folder is used depends on how you add the files:
| you add with | the folder |
| --- | --- |
| shadcn | `ascii/` inside the folder your `components` alias in `components.json` points to: `components/ascii` or `src/components/ascii` |
| `npx ascii.rest add` | `src/components/ascii` if there is a `src` folder, otherwise `components/ascii`, or the `--dir` you give |
## Use the copied files
The copied files work like the npm package, imported from your own folder. The examples use the `@/` import alias that Next.js sets up. Without it, import by a relative path, like `../components/ascii/ascii`.
### In React and Next.js
Import a piece with `import * as` and pass the whole module to ``:
```tsx
"use client";
import { Ascii } from "@/components/ascii/ascii";
import { Banner } from "@/components/ascii/ascii-banner";
import * as donut from "@/components/ascii/pieces/donut";
import * as nightCoast from "@/components/ascii/pieces/night-coast";
export default function Page() {
return (
);
}
```
(A live example plays here on the page.)
- The copied `` takes a module, not a name. Write `piece={donut}`, not `piece="donut"`. Playing a piece by name needs the npm package's list of every piece, and your copy does not have it.
- A piece module holds a function, and a Next.js server component can't pass a function to a client component. So the file that imports the pieces needs `"use client"` at its top, as above. Put the art in a component of its own if the rest of the page should stay on the server.
- `` takes only plain values, so a server component can render it on its own.
- Every other prop is the same as in the npm package. They are all on [react](https://ascii.rest/docs/react/).
### Without React
`mount()` plays any piece in an element and returns a function that stops it:
```ts
import { mount } from "@/components/ascii/mount";
import * as donut from "@/components/ascii/pieces/donut";
const el = document.querySelector("#art")!;
const stop = mount(el, donut);
// Later, when you remove the element:
stop();
```
Give a text piece a ``. Give a coloured piece, one with `meta.palette`, a `` to draw it in colour. In a `` it draws as text in one colour. More on `mount()` is on [typescript](https://ascii.rest/docs/typescript/).
### A banner
`banner()` turns text into a piece, so `mount()` and `` play it like any other:
```ts
import { banner } from "@/components/ascii/banner";
import { mount } from "@/components/ascii/mount";
const hello = banner("hello", { shadow: "rounded", effect: "type" });
mount(document.querySelector("#title")!, hello);
```
Every option is on [banners](https://ascii.rest/docs/banners/#options).
### An SVG
`bannerSvg()` returns the markup of an animated SVG, as a string:
```ts
import { bannerSvg } from "@/components/ascii/svg";
// Save it as a .svg file, or send it with Content-Type: image/svg+xml.
const svg = bannerSvg("hello", { color: ["#f97316", "#f778ba"], tagline: "made with ascii.rest" });
```
Every option is on [svg](https://ascii.rest/docs/svg/).
## Change a copied piece
A piece's file has two parts. `meta` holds its name, its size in characters, its frame rate and its colours. The default function draws each **frame**, the picture at one moment, from the time `t` in seconds.
Here is how to give the donut heavier characters and slow it down:
1. Open `components/ascii/pieces/donut.ts`.
2. Change the characters it shades with. They run from the part in shadow to the part in full light:
```diff
-const RAMP = ".,-~:;=!*#$@";
+const RAMP = ".:-=+*#%@";
```
3. Halve the speed it turns at. `t` is the time in seconds, so smaller numbers turn it slower:
```diff
- const A = 1 + t * 0.8;
- const B = 1 + t * 0.35;
+ const A = 1 + t * 0.4;
+ const B = 1 + t * 0.175;
```
4. Save the file. If your dev server is running, the page reloads with your donut.
Its first frame now looks like this, without the blank rows above and below it:
```text
@@@@%%%%%%
%@@@%%%%%##########
@@@%%%####****+**++++++
@@%%%###******++++=++==++++
%%%%###***+++===-------======
%%%###***++==--:::....::----==-
%#####***+==-::...........::----
#####**+++=-::..............:----
####***++=-::.... ....:::----
*****++==-::... ------=----
***++++==-:.... ###++++===-:
**++++==--::...: %@@@%%#*+++=:.
+++++===--:::.:-=*#%%@@%%##*+=-.
=======----:--==+*######**+=-.
=====--------==++*****+*+=:
------------=====+=++=-:.
:::---------=====-:.
...::::::::...
```
Other things you can change:
- **Colours.** A coloured piece lists its colours in `meta.palette`, as `#rrggbb`. Change one to recolour every part drawn in it.
- **Frame rate.** `meta.fps` is how many frames a second it draws, not how fast it moves. Lower it to save work on slow devices.
- **Size.** `meta.cols` and `meta.rows` are the frame's size in characters. Every frame must be exactly that size, so change the drawing with them.
Every `meta` field is explained on [your own pieces](https://ascii.rest/docs/pieces/#fill-in-its-meta).
To keep the original too, copy `donut.ts` to a new file, like `my-donut.ts`, and change `meta.name`. Adding `donut` again later never touches `my-donut.ts`. The rules a piece must follow are on [your own pieces](https://ascii.rest/docs/pieces/).
## Update your copy
Your copy never changes on its own. To take a newer version of an item, add it again and replace the files:
1. Commit your work, so you can see and undo what changes.
2. If you use shadcn, see the changes first. `--diff` prints them and writes nothing:
```sh
npx shadcn@latest add @ascii/donut --diff
```
3. Add the item again with `--overwrite`:
```sh
npx ascii.rest add donut --overwrite
```
Or with shadcn:
```sh
npx shadcn@latest add @ascii/donut --overwrite
```
4. Run `git diff`. Keep the new code you want, and put your own changes back.
`--overwrite` applies to every file the item brings with it, not only the one you named. If you changed `mount.ts`, `add donut --overwrite` replaces your `mount.ts` too.
## Keep the licence header
ascii.rest is MIT licensed. You can use the copied code in any project, free or paid, open or closed, and change it as you like. The MIT licence asks that its copyright and licence notice stay with every copy.
`mount.ts`, `banner.ts`, `svg.ts`, `ascii.tsx` and `ascii-banner.tsx` each have this line in the comment near the top:
```text
Part of ascii.rest by @bas3line (https://github.com/bas3line), MIT licensed.
```
Keep that line when you change the file. The piece files and `types.ts` have no such line, and the same licence covers them. The full text, with the copyright line, is in [LICENSE](https://github.com/bas3line/ascii/blob/main/LICENSE). The simplest way to keep the notice with your copy is to save that file next to the copied files, for example as `components/ascii/LICENSE`.
## Next
- [react](https://ascii.rest/docs/react/): every prop of `` and ``.
- [your own pieces](https://ascii.rest/docs/pieces/): the rules a piece follows, to change one safely or write a new one.
- [cli](https://ascii.rest/docs/cli/): every command and flag of `npx ascii.rest`.
---
# banners
URL: https://ascii.rest/docs/banners/
Draw any text in big block letters, then change its font, shadow, colours, motion and size.
A banner is any text you choose, drawn in big block letters with a drop shadow. By default it has a glint: a bright band that sweeps across the letters every few seconds. Every part of it is an option: the font, the shadow, the colours, the motion and the size.
(A video on this page shows this: A banner's options changed one at a time: font, shadow, fill, colours, effect and speed.)
Pick the example for where you want your banner.
On an HTML page, load the script once, then use the tag:
```html
```
In React or Next.js, run `npm install ascii.rest`, then use the component:
```tsx
import { Banner } from "ascii.rest/react";
export const Hero = () => ;
```
The tag and the component both draw this:
(A live example plays here on the page.)
In a GitHub README, use the banner's URL as an image. In a URL, a colour has no `#`:
```md

```
The URL gives this SVG image:
(A live example plays here on the page.)
## See the raw text
`banner()` from `ascii.rest/banner` returns a piece: one animation. Its `default()` returns a function that draws a frame, the picture at one moment, as text. Give that function a time in seconds. This prints the first frame in Node:
```ts
import { banner } from "ascii.rest/banner";
const frame = banner("hi").default();
console.log(frame(0));
```
It prints:
```text
▓▓╗ ▓▓╗ ▓▓╗
▓▓║ ▓▓║ ▓▓║
▓▓▓▓▓▓▓▓║ ▓▓║
▓▓╔═══▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║
╚═╝ ╚═╝ ╚═╝
```
## Show a banner anywhere
`banner(text, options)` returns a piece, so anything that plays a piece also plays a banner.
| where | how | more |
| --- | --- | --- |
| React, Next.js | ` ` from `ascii.rest/react` | [react](https://ascii.rest/docs/react/) |
| Astro | ` ` from `ascii.rest/astro/banner` | [astro](https://ascii.rest/docs/astro/) |
| HTML | ` ` | [html](https://ascii.rest/docs/html/) |
| TypeScript | `mount(el, banner("hello"))` from `ascii.rest` | [typescript](https://ascii.rest/docs/typescript/) |
| an SVG file | `svg(banner("hello"))` or `bannerSvg("hello")` from `ascii.rest/svg` | [svg](https://ascii.rest/docs/svg/) |
| a GitHub README | `https://ascii.rest/banner/hello.svg` | [github readme](https://ascii.rest/docs/readme/) |
| a terminal | `npx ascii.rest banner hello`, or `banner("hello")` from `ascii.rest/terminal` | [terminal](https://ascii.rest/docs/terminal/) |
Two things here are easy to miss.
The terminal has its own `banner()`. The one in `ascii.rest/terminal` is a different function from the one in `ascii.rest/banner`. It prints the banner, lets the glint cross once (or the letters type in), then leaves it in the output. It takes every option on this page except `size` and `max`, because it fits the banner to the terminal's width. See [terminal](https://ascii.rest/docs/terminal/#print-a-banner-from-your-cli).
In React, make a banner once, outside your component. `` does this for you. But if you call `banner()` yourself and pass the result to ``, call it outside the component. A new banner on every render starts its motion over each time.
```tsx
import { Ascii } from "ascii.rest/react";
import { banner } from "ascii.rest/banner";
const hello = banner("hello", { shadow: "rounded" });
export const Hero = () => ;
```
## Options
Every option is optional. With none, you get the block font, a double-line shadow, and a glint every 3.2 seconds, in your page's text colour.
| option | what it does | default |
| --- | --- | --- |
| `font` | the letters: `"block"`, `"slim"`, `"tall"`, `"bold"`, `"round"`, `"wide"`, `"mixed"`, `"italic"`, or [a font of your own](#use-a-font-of-your-own). See [change the font](#change-the-font) | `"block"` |
| `shadow` | the shadow's line style: `"double"`, `"single"`, `"heavy"`, `"rounded"`, `"ascii"`, `"none"`, or [16 characters of your own](#draw-the-shadow-with-your-own-characters) | `"double"` |
| `fill` | the one character the letters are drawn with, or `{ dark, light }` for each kind of page | `"▓"` on a dark page, `"█"` on a light one |
| `color` | the letters' colour: one `#rrggbb`, a list of them for a fade, or `{ light, dark }` | none: the page's text colour |
| `shadowColor` | the shadow's colour, or `{ light, dark }` | a muted grey, once the banner has a colour |
| `effect` | how it moves: `"glint"`, `"type"` or `"still"` | `"glint"` |
| `speed` | how fast it moves: 2 is twice as fast, 0.5 half as fast | `1` |
| `glint` | the glint's timing and look: `{ first, sweep, every, width, slant, chars }`. See [change the glint](#change-the-glint) | a glint every 3.2 seconds |
| `type` | `{ step }`: seconds to type each letter | `{ step: 2 / 15 }`, about 0.133 |
| `pixel` | columns one pixel of the font takes | `2` |
| `gap` | columns between letters | `2` |
| `pad` | blank cells around it: one number for every side, or `[rows, columns]` | `0` |
| `size` | `{ cols, rows }`: a fixed size to centre the banner in. See [set its size](#set-its-size) | its own size |
| `max` | the most columns it should take, padding included. See [set its size](#set-its-size) | no limit |
| `name` | its name, for screen readers and titles | the text it draws |
`banner()` throws an error for a value it can't take. [Fix an error from banner()](#fix-an-error-from-banner) lists what each option takes.
## Use the same options everywhere
Every option has the same name and value in `banner()` and as a prop of ``, in React and in Astro. Each `` also has a few props of its own, such as `label` and `mono`. See [react](https://ascii.rest/docs/react/#draw-text-as-a-banner) and [astro](https://ascii.rest/docs/astro/#draw-your-own-text-with-banner).
The `` tag takes the common options as attributes, and any other option as JSON in its `options` attribute:
| `banner()` and `` | `` |
| --- | --- |
| `font: "slim"` | `font="slim"` |
| `shadow: "rounded"` | `shadow="rounded"` |
| `fill: "#"` | `fill="#"` |
| `effect: "type"` | `effect="type"` |
| `speed: 2` | `speed="2"` |
| `pixel: 1` | `pixel="1"` |
| `gap: 1` | `gap="1"` |
| `color: ["#f97316", "#f778ba"]` | `color="#f97316,#f778ba"` |
| `shadowColor: "#30363d"` | `shadow-color="#30363d"` |
| any other option, like `pad: 1` | `options='{"pad":1}'` |
Colours for a light and a dark page, `{ light, dark }`, also go in `options`: `options='{"color":{"light":"#c2410c","dark":"#fb923c"}}'`.
An empty attribute counts as no attribute. If an attribute and `options` set the same option, the attribute wins.
A README URL takes a few named choices, not every option. [github readme](https://ascii.rest/docs/readme/#change-how-a-banner-looks) lists them, and the [banner maker](https://ascii.rest/banner/) writes the URL for you.
## Change the font
Eight fonts are built in. `block` is the default.
| font | rows | its letters |
| --- | --- | --- |
| `block` | 5 | four or five pixels wide |
| `slim` | 5 | three pixels wide, so a banner is narrower |
| `tall` | 7 | five pixels wide in thin strokes, like a dot-matrix display |
| `bold` | 7 | six pixels wide on two-pixel stems, the heaviest |
| `round` | 6 | five pixels wide, with their corners rounded off |
| `wide` | 5 | six or seven pixels wide, the widest |
| `mixed` | 9 | capitals and lower case, with room for the tails of g, j, p, q and y |
| `italic` | 5 | capitals that lean right |
Every font draws the letters A to Z, the digits 0 to 9, spaces, and `. , ! ? ' : - + = / _`. `mixed` draws lower case too. The others draw every letter as a capital. A character the font doesn't have is left out.
Pick one with `font`:
```ts
import { banner } from "ascii.rest/banner";
banner("hello", { font: "tall" });
banner("Hello", { font: "mixed" });
```
The same name works everywhere:
- In React and Astro, ` `. In HTML, ` `.
- In a README banner's URL, `?font=tall`: `https://ascii.rest/banner/hello.svg?font=tall`.
- In the [banner maker](https://ascii.rest/banner/), the font row. Each font there shows its name in its own letters.
- In a terminal, `npx ascii.rest banner hello --font tall`.
(A live example plays here on the page.)
Here is each font's name in it, as `banner(name, { font: name }).default()(0)` prints it.
`block`:
```text
▓▓▓▓▓▓╗ ▓▓╗ ▓▓▓▓╗ ▓▓▓▓▓▓╗ ▓▓╗ ▓▓╗
▓▓╔═══▓▓╗ ▓▓║ ▓▓╔═══▓▓╗ ▓▓╔═════╝ ▓▓║ ▓▓╔═╝
▓▓▓▓▓▓╔═╝ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓▓▓╔═╝
▓▓╔═══▓▓╗ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓╔═▓▓╗
▓▓▓▓▓▓╔═╝ ▓▓▓▓▓▓▓▓╗ ╚═▓▓▓▓╔═╝ ╚═▓▓▓▓▓▓╗ ▓▓║ ╚═▓▓╗
╚═════╝ ╚═══════╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝
```
`slim`:
```text
▓▓▓▓╗ ▓▓╗ ▓▓▓▓▓▓╗ ▓▓╗ ▓▓╗
▓▓╔═══╝ ▓▓║ ╚═▓▓╔═╝ ▓▓▓▓▓▓║
╚═▓▓╗ ▓▓║ ▓▓║ ▓▓╔═▓▓║
╚═▓▓╗ ▓▓║ ▓▓║ ▓▓║ ▓▓║
▓▓▓▓╔═╝ ▓▓▓▓▓▓╗ ▓▓▓▓▓▓╗ ▓▓║ ▓▓║
╚═══╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
```
`tall`:
```text
▓▓▓▓▓▓▓▓▓▓╗ ▓▓▓▓▓▓╗ ▓▓╗ ▓▓╗
╚═══▓▓╔═══╝ ▓▓╔═════▓▓╗ ▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║
▓▓║ ▓▓▓▓▓▓▓▓▓▓║ ▓▓║ ▓▓║
▓▓║ ▓▓╔═════▓▓║ ▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║ ▓▓▓▓▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓▓▓╗
╚═╝ ╚═╝ ╚═╝ ╚═════════╝ ╚═════════╝
```
`bold`:
```text
▓▓▓▓▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓╗ ▓▓▓▓╗ ▓▓▓▓▓▓▓▓╗
▓▓▓▓╔═══▓▓▓▓╗ ▓▓▓▓╔═══▓▓▓▓╗ ▓▓▓▓║ ▓▓▓▓╔═▓▓▓▓╗
▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ╚═▓▓▓▓╗
▓▓▓▓▓▓▓▓▓▓╔═╝ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║
▓▓▓▓╔═══▓▓▓▓╗ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║
▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓║ ▓▓▓▓╔═╝
▓▓▓▓▓▓▓▓▓▓╔═╝ ╚═▓▓▓▓▓▓▓▓╔═╝ ▓▓▓▓▓▓▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓╔═╝
╚═════════╝ ╚═══════╝ ╚═══════════╝ ╚═══════╝
```
`round`:
```text
▓▓▓▓▓▓╗ ▓▓▓▓▓▓╗ ▓▓╗ ▓▓╗ ▓▓╗ ▓▓╗ ▓▓▓▓▓▓╗
▓▓╔═════▓▓╗ ▓▓╔═════▓▓╗ ▓▓║ ▓▓║ ▓▓▓▓╗ ▓▓║ ▓▓╔═══▓▓╗
▓▓║ ▓▓╔═╝ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓╔═▓▓╗ ▓▓║ ▓▓║ ╚═▓▓╗
▓▓▓▓▓▓╔═╝ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║
▓▓╔═══▓▓╗ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ╚═▓▓▓▓║ ▓▓║ ▓▓╔═╝
▓▓║ ╚═▓▓╗ ╚═▓▓▓▓▓▓╔═╝ ╚═▓▓▓▓▓▓╔═╝ ▓▓║ ╚═▓▓║ ▓▓▓▓▓▓╔═╝
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝
```
`wide`:
```text
▓▓╗ ▓▓╗ ▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓▓▓▓▓╗
▓▓║ ▓▓╗ ▓▓║ ╚═▓▓╔═╝ ▓▓╔═══════▓▓╗ ▓▓╔═════════╝
▓▓║ ▓▓╔═▓▓╗ ▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓▓▓▓▓▓▓▓▓╗
▓▓▓▓╔═╝ ╚═▓▓▓▓║ ▓▓║ ▓▓║ ▓▓║ ▓▓╔═══════╝
▓▓╔═╝ ╚═▓▓║ ▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓▓▓╔═╝ ▓▓▓▓▓▓▓▓▓▓▓▓╗
╚═╝ ╚═╝ ╚═════╝ ╚═════════╝ ╚═══════════╝
```
`mixed`, with `"Mixed"`. Its last two rows are for tails, so they are blank here:
```text
▓▓╗ ▓▓╗ ▓▓╗ ▓▓╗
▓▓▓▓╗ ▓▓▓▓║ ╚═╝ ▓▓║
▓▓╔═▓▓╔═▓▓║ ▓▓╗ ▓▓╗ ▓▓╗ ▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓║
▓▓║ ▓▓║ ▓▓║ ▓▓║ ╚═▓▓╗ ▓▓╔═╝ ▓▓╔═════▓▓╗ ▓▓╔═════▓▓║
▓▓║ ╚═╝ ▓▓║ ▓▓║ ╚═▓▓╔═╝ ▓▓▓▓▓▓▓▓▓▓║ ▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║ ▓▓╔═▓▓╗ ▓▓╔═══════╝ ▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║ ▓▓╔═╝ ╚═▓▓╗ ╚═▓▓▓▓▓▓▓▓╗ ╚═▓▓▓▓▓▓▓▓║
╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══════╝ ╚═══════╝
```
`italic`:
```text
▓▓▓▓▓▓╗ ▓▓▓▓▓▓▓▓▓▓╗ ▓▓▓▓╗ ▓▓╗ ▓▓▓▓▓▓╗ ▓▓▓▓▓▓╗
╚═▓▓╔═╝ ╚═══▓▓╔═══╝ ▓▓╔═══▓▓╗ ▓▓╔═╝ ╚═▓▓╔═╝ ▓▓╔═════╝
▓▓║ ▓▓║ ▓▓▓▓▓▓▓▓║ ▓▓║ ▓▓║ ▓▓║
▓▓╔═╝ ▓▓╔═╝ ▓▓╔═══▓▓╔═╝ ▓▓╔═╝ ▓▓╔═╝ ▓▓╔═╝
▓▓▓▓▓▓╗ ▓▓║ ▓▓║ ▓▓║ ▓▓▓▓▓▓▓▓╗ ▓▓▓▓▓▓╗ ╚═▓▓▓▓▓▓╗
╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═══════╝ ╚═════╝ ╚═════╝
```
`fonts` holds every built-in font by name, and `FontName` is the type of a name.
### Use a font of your own
A font is an object with two fields. `height` is the number of rows. `glyphs` is a drawing of each character: write each row with `#` for a filled pixel and `.` for an empty one, then join the rows with `|`. Every row must be as wide as the first.
```ts
import { banner, type Font } from "ascii.rest/banner";
const tiny: Font = {
height: 3,
glyphs: {
H: "#.#|###|#.#",
I: "###|.#.|###",
" ": "..",
},
};
banner("hi", { font: tiny });
```
(A live example plays here on the page.)
A few rules for your own font:
- Text is turned into capitals first, so capital letters are enough.
- To draw lower case too, add `cased: true` and lower-case glyphs. Each case then draws its own glyph.
- Add a `" "` glyph if your text has spaces. It sets how wide a space is.
To add one character to a built-in font, copy its glyphs and add yours:
```ts
import { banner, fonts, type Font } from "ascii.rest/banner";
const withStar: Font = {
height: 5,
glyphs: { ...fonts.block.glyphs, "*": "..#..|#.#.#|.###.|#.#.#|..#.." },
};
banner("a*b", { font: withStar });
```
## Change the shadow
The shadow is a line along the bottom and right of each letter. It is the letters' outline, moved half a cell right and down. Pick its style with `shadow`. Here is each style, drawn narrow (`pixel: 1`) so all six fit:
(A live example plays here on the page.)
| `shadow` | the characters it draws with |
| --- | --- |
| `"double"`, the default | `═ ║ ╔ ╗ ╚ ╝` |
| `"single"` | `─ │ ┌ ┐ └ ┘` |
| `"heavy"` | `━ ┃ ┏ ┓ ┗ ┛` |
| `"rounded"` | `─ │ ╭ ╮ ╰ ╯` |
| `"ascii"` | `- \| +` |
| `"none"` | no shadow. The banner is one row shorter and one column narrower. |
```ts
import { banner } from "ascii.rest/banner";
banner("hello", { shadow: "rounded" });
banner("hello", { shadow: "none" });
```
### Draw the shadow with your own characters
Pass a string of 16 characters instead of a name. Each position in the string, counting from 0, is the character for one way the shadow's lines meet in a cell. A position is the sum of the directions the lines leave the cell: up is 1, down is 2, left is 4, right is 8.
A banner only draws seven of the 16 positions:
| position | lines leave the cell | `double` draws |
| --- | --- | --- |
| 0 | none: any empty cell | a space |
| 3 | up and down | `║` |
| 5 | up and left | `╝` |
| 6 | down and left | `╗` |
| 9 | up and right | `╚` |
| 10 | down and right | `╔` |
| 12 | left and right | `═` |
The other nine positions are never drawn, but the string still needs all 16 characters. Keep position 0 a space, or every empty cell shows that character.
The `double` style is `" ║║║═╝╗╣═╚╔╠═╩╦╬"`. Every built-in style is in `shadows`, so you can start from one. For a soft, shaded shadow, use a space and then one character 15 times:
```ts
import { banner, shadows } from "ascii.rest/banner";
banner("hi", { shadow: " " + "░".repeat(15) });
console.log(shadows.rounded); // " │││─╯╮┤─╰╭├─┴┬┼"
```
In ``, pass your own shadow in `options`. The tag trims spaces from the ends of its attributes, which would remove the first character:
```html
```
(A live example plays here on the page.)
## Draw the letters with another character
`fill` sets the one character the letters are drawn with. By default it is `▓` on a dark page, so the glint can look brighter than the letters, and `█` on a light page.
```ts
import { banner } from "ascii.rest/banner";
banner("hi", { fill: "#" });
banner("hi", { fill: { dark: "▒", light: "█" } });
```
(A live example plays here on the page.)
A fill must be exactly one character. The banner decides whether the page is dark or light from its text colour, as [colours](#add-colour) explains.
## Add colour
With no colour, a banner is drawn like a [text piece](https://ascii.rest/docs/#browse-the-pieces): plain text in a ``, in your page's text colour, so your CSS styles it. Give it a colour and it is drawn like a coloured piece: on a ``, in that colour.
`` and `` pick the `` or the `` for you. With `mount()`, you pass the element: on a `` it shows its colours, and in a `` it draws everything in the pre's one colour.
```ts
import { banner } from "ascii.rest/banner";
// one colour
banner("one", { color: "#3fb950" });
// two or more fade from the first to the last, left to right
banner("fade", { color: ["#f97316", "#f778ba", "#ab7df8"] });
// one colour for a light page and one for a dark page
banner("themes", { color: { light: "#c2410c", dark: "#fb923c" } });
// the shadow's colour, one for each kind of page here too
banner("shadow", { color: "#f97316", shadowColor: { light: "#d0d7de", dark: "#30363d" } });
```
(A live example plays here on the page.)
Some details:
- A colour is `#rrggbb`: a `#` and six hex digits. A name like `red` or a short form like `#f00` throws an error.
- A fade changes colour in up to 30 steps across the banner.
- `{ light, dark }` can hold a fade too: `{ light: ["#c2410c", "#be185d"], dark: ["#fb923c", "#f472b6"] }`.
- If you leave out `light` or `dark`, that page gets GitHub's text colour: `#1f2328` on light, `#f0f6fc` on dark.
- Without `shadowColor`, a coloured banner's shadow is a muted grey: `#59636e` on light, `#9198a1` on dark.
- A `shadowColor` on its own also makes a coloured banner. Its letters then take GitHub's text colours.
- `mono` on `` and `` draws a coloured banner as plain text in one colour.
A coloured banner has one set of colours for light pages and one for dark pages. It picks by reading its own CSS `color`: dark text means a light page. A moving banner checks on every frame, so it follows your theme when the text colour changes.
## Change how it moves
`effect` picks the motion:
| `effect` | what it does |
| --- | --- |
| `"glint"`, the default | a glint sweeps across the letters every few seconds |
| `"type"` | the letters type in one at a time, then stay |
| `"still"` | no motion |
```ts
import { banner } from "ascii.rest/banner";
banner("glint");
banner("type", { effect: "type" });
banner("still", { effect: "still" });
```
(A live example plays here on the page.)
On a page, a banner only moves while it is on screen, so a typed banner types in when it first comes into view. If the reader's system asks for reduced motion, the banner holds still: a glint banner shows no glint, and a typed banner shows every letter.
### Speed it up or slow it down
`speed` changes every timing at once. 2 is twice as fast, and 0.5 is half as fast.
```ts
import { banner } from "ascii.rest/banner";
banner("slow", { speed: 0.5 }); // a glint every 6.4 seconds
banner("fast", { speed: 2 }); // a glint every 1.6 seconds
```
(A live example plays here on the page.)
### Change the glint
`glint` sets when the glint crosses and how it looks. Times are in seconds at `speed` 1.
| option | what it does | default |
| --- | --- | --- |
| `first` | when the first glint starts | `0.5` |
| `sweep` | how long one glint takes to cross | `2.4` |
| `every` | the time from the start of one glint to the start of the next. Keep it longer than `sweep`, or each glint starts over before it is across | `3.2` |
| `width` | how wide it is, in columns | `2.4` |
| `slant` | how far it leans, in columns for each row | `1.2` |
| `chars` | one or two characters: the middle, then the edges. Or `{ dark, light }` | `"██"` on a dark page, `"▒▓"` on a light one |
```ts
import { banner } from "ascii.rest/banner";
// a quick glint, twice as often
banner("quick", { glint: { sweep: 1, every: 1.6 } });
// a thin glint in lighter shades
banner("thin", { glint: { width: 1.2, chars: "▒░" } });
```
(A live example plays here on the page.)
### Change the typing speed
`type.step` is how long each letter takes to type in. The default is 2/15 of a second, about 0.133.
```ts
import { banner } from "ascii.rest/banner";
banner("hello", { effect: "type", type: { step: 0.08 } });
```
(A live example plays here on the page.)
## Set its size
A banner is as big as its text needs. These options change that. The numbers in the comments are what each one returns.
```ts
import { banner } from "ascii.rest/banner";
banner("hello").meta.cols; // 49 columns (and 6 rows)
banner("hello", { pixel: 1 }).meta.cols; // 29
banner("hello", { gap: 1 }).meta.cols; // 45
banner("hello", { pad: 1 }).meta.cols; // 51 (and 8 rows)
banner("hello", { pad: [1, 4] }).meta.cols; // 57 (and 8 rows)
banner("hello", { size: { cols: 80, rows: 10 } }).meta.cols; // 80 (and 10 rows)
banner("hello", { size: { cols: 20, rows: 4 } }).meta.cols; // 20, cropped
banner("hello", { max: 40 }).meta.cols; // 29
banner("hello", { max: 10 }).meta.cols; // 29, still wider than 10
```
What each one does:
- `pixel` is how many columns one pixel of the font takes. At 2, the default, pixels look square, because a character is about twice as tall as it is wide. 1 makes narrow letters.
- `gap` is the number of columns between letters.
- `pad` adds blank cells around the banner: one number for every side, or `[rows, columns]`.
- `max` is the most columns the banner should take, padding included. While the banner is wider than `max`, its pixels get narrower, one column at a time. At one column a pixel it can still be wider than `max`. To crop it to a width, use `size`.
- `size` gives the banner a fixed size and centres it inside. `pad` does not apply. If the banner doesn't fit, its pixels get narrower, one column at a time, until it fits with a blank column on each side. If it still doesn't fit at one column a pixel, it is cropped.
`pixel`, `gap`, `pad` and the numbers in `size` take whole numbers. `max` takes any number above 0.
Here is `"hello"` as it comes, with `pixel: 1`, and with `gap: 1`:
(A live example plays here on the page.)
These options set the banner's size in characters. On a page, a coloured banner's canvas fills the width of its container, and a banner with no colour is as big as its font size. Set either with CSS.
The [big text](https://ascii.rest/big-text/) piece is a banner at a fixed 66 by 8. It draws the same frames as `banner(text, { size: { cols: 66, rows: 8 } })`.
## Check what it can draw
`drawable(text, font)` returns the characters of `text` that a font can draw, in the case they came in. `banner()` leaves out the rest.
```ts
import { drawable } from "ascii.rest/banner";
drawable("héllo, wörld!"); // "hllo, wrld!"
drawable("hi", "slim"); // "hi"
```
A banner also tells you its size and what it drew:
```ts
import { banner } from "ascii.rest/banner";
const hi = banner("hi");
hi.meta.cols; // 13
hi.meta.rows; // 6
hi.text; // "hi": the characters it drew
```
It also has a `motion` field, which says how its motion runs. See [api](https://ascii.rest/docs/api/#bannerpiece-motion).
## Fix an error from banner()
`banner()` throws an error when it can't draw what you asked for. The message starts with `ascii.rest:` and says what to change.
```ts
import { banner } from "ascii.rest/banner";
try {
banner("hi", { fill: "##" });
} catch (error) {
console.log((error as Error).message); // ascii.rest: fill takes one character, not "##"
}
```
It throws unless each value is one it takes:
| what you pass | what it takes |
| --- | --- |
| the text | at least one character the font can draw. Spaces alone count as none. |
| `font` | the name of a [built-in font](#change-the-font), or a font of your own |
| `shadow` | `"double"`, `"single"`, `"heavy"`, `"rounded"`, `"ascii"`, `"none"`, or exactly 16 characters |
| `fill` | exactly one character |
| `glint.chars` | one or two characters |
| `effect` | `"glint"`, `"type"` or `"still"` |
| `color` | a colour as `#rrggbb`, or a list of one or more |
| `shadowColor` | a colour as `#rrggbb` |
| `gap` | a whole number, 0 or more |
| `pad` | a whole number, 0 or more, or `[rows, columns]` of two |
| `pixel` | a whole number, 1 or more |
| `size` | `{ cols, rows }`, both of them, each a whole number, 1 or more |
| `speed`, `max`, `type.step`, `glint.sweep`, `glint.every`, `glint.width` | a number above 0 |
| `glint.first`, `glint.slant` | a number |
Where an option also takes `{ light, dark }`, each of the two follows the same rule, and you can leave either one out. The characters of `fill`, `glint.chars` and `shadow` can't be control characters, such as a newline.
`` in React and the `` tag don't throw. They draw nothing, and `console.warn` says why: ` could not draw: ...` or ` could not draw: ...`. Astro's `` calls `banner()` on the server, so it throws when the page renders.
## Next
- [github readme](https://ascii.rest/docs/readme/): put a banner at the top of your README.
- [svg](https://ascii.rest/docs/svg/): a banner with a tagline and a logo, as one SVG file.
- [terminal](https://ascii.rest/docs/terminal/): print a banner when your CLI starts.
---
# image to ascii
URL: https://ascii.rest/docs/images/
Turn a logo or a photo into animated ascii in your browser, then put it on a web page, in a README, or in the library.
[image to ascii](https://ascii.rest/make/) turns any image into animated ascii, in the image's own colours. Drop in a logo or a photo, and you get three things back: a snippet for any web page, two SVGs for a GitHub README, and a piece file. A **piece** is one of the library's animations, and the file is in the library's own format, ready to add to it.
(A video on this page shows this: Image to ascii: a logo dropped in, a JPG's white background left out, a photo shaded, the one-colour view, and the README snippet copied.)
It all happens in your browser. Your image is never uploaded.
The library's own logos were drawn the same way, like this one:
(A live example plays here on the page.)
## Turn an image into ascii
1. Open [image to ascii](https://ascii.rest/make/). Every page of this site links to it, as **[image to ascii]** next to **[make a banner]**.
2. Give it an image, in any of these ways:
- Drop the file anywhere on the page.
- Press **[pick a file]** and choose one.
- Copy an image, such as a screenshot, and paste it anywhere on the page.
- Paste an SVG's markup into the **or paste an svg's markup** box, then press **[draw it]**.
3. The art plays in the box at the top. If the image had a plain background, a message under the art says which colour it left out.
4. Under **settings**, type a name. Change the width, style, motion and category if you want. The art redraws as you change them.
5. Copy what you need from the three boxes at the bottom of the page: **embed it**, **readme**, and the `.ts` file.
The line just under the art sums it up: its size in columns by rows, how many colours it has, how often it moves, and the size of its `.ts` file. For example: `48×32`, `8 colours`, `a glint every 5 s`, `7.4 kB`. **[another image]** on the same line opens a new file.
## Choose a style
The style sets how each cell, one character of the art, is chosen. There are two:
| style | each cell is | use it for |
| --- | --- | --- |
| **logo** | The character whose shape best matches the image's edge through the cell. A solid cell is `8`, and an empty one is a space. | Logos, icons and flat shapes with clear edges. The library's logos are drawn this way. |
| **shade** | A character from `.:-=+*#%@`, denser where the image is brighter. The image's own darkest and brightest parts set the two ends. | Photos, gradients, and anything without clear edges. |
The Go gopher at 32 columns, in the logo style, in one colour. Its white eyes are left out, so they show as gaps:
```text
__ppq8888888qq_, __
_p88p8P"""8888P"''"888888,
:8888P_p, 88Pqp, 88888P
"8888/Y' _d8b/" _8888"
d8888qqpp88888qqp888888,
888888888888Y8888888888|
8888888888qp_8888888888P
d8888888888888888888888P
d8888888888888888888888b
_pp88888888888888888888888q_
YPO88888888888888888888888Y"
d88888888888888888888888
d88888888888888888888888
888888888888888888888888
d8888888888888888888888P
"8888888888888888888888'
"88888888888888888888"
_p888888888888888888q,
O88"'""YP888PP^"" '88"
```
A picture of a sun over hills, in the shade style, as a dark page shows it:
```text
.::::::::-------=======++++++
.::::::::-------=+***==++++++
.::::::::------*@@@@@@*++++++
.::::::::-----=@@@@@@@@++++++
.::::::::------#@@@*-:==+++++
.::::....::-----==......:-+++
............:::............-=
.............................
.............................
```
Each image starts in the style that suits it:
- An SVG starts in **logo**. So does an image with transparency, or with a plain background.
- An image with no transparency and no plain background, such as a photo, starts in **shade**.
Press the other style at any time to switch.
## Keep or remove the background
A JPG, or any image with no transparency, usually sits on a plain background. The page takes that background out, so the logo stands on its own.
Here is how:
- It looks at the pixels round the image's edge. If 6 in 10 of them or more are one colour, that colour is the background. If not, it takes nothing out.
- It removes that colour from the edges inwards. The same colour inside the logo, such as white letters on a shape, stays.
- It says which colour it took out, for example `left out its background, #ffffff`.
To put the background back, press **[keep the background]**. The art is drawn again from the whole image, background and all. Press **[remove the background]** to take it out again. Either button draws the image again in the **logo** style, so press **shade** again if you want it shaded.
The button shows only when a background was found. Nothing is taken out of:
- an SVG;
- an image with transparency of its own, such as a PNG whose edges are clear;
- an image whose border isn't mostly one colour, such as most photos.
## Change the settings
Every setting redraws the art and rewrites the snippets and the file.
| setting | what it does | default |
| --- | --- | --- |
| name | The piece's name. It names the files and sets the text screen readers read. File names are in lower case with dashes between words: `Acme Co!` makes `acme-co.ts`. A name can have up to 32 characters: letters a to z, digits, spaces and `. + # ! _ ' -`. Any other character becomes a space. A name that starts with a digit gets `logo-` in front: `7up` makes `logo-7up.ts`. | The image's file name, without its extension, or `my logo`. Once you type a name, a new image keeps it. Pasted markup keeps the name already there. |
| width | About how many columns wide the art is, from 16 to 80. A tall image comes out narrower, because a piece has at most 32 rows. Below 16 counts as 16, and above 80 as 80. 0 or an empty box counts as 64. | 64 |
| style | **logo** or **shade**. See [choose a style](#choose-a-style). | **logo**, or **shade** for a photo |
| motion | **glint**: a bright band that sweeps across the art. **scan**: a line that runs down it and scrambles the characters it passes. | **glint** |
| every | The seconds from one glint or scan to the next, from 3 to 60. | 5 |
| category | Which of the library's categories the piece is in: **companies**, **logos** (programming languages and web tools) or **distros** (Linux distributions). It is written into the `.ts` file. | **companies** |
| [mono] | Shows the art in one colour, the page's text colour, in a ``. **[colour]** shows it in its own colours again, on a ``. The embed snippet follows your choice, and the site remembers it. | colour |
Motion and category go together, as they do in the library: a distro scans, and a company or a language glints. Picking **distros** picks **scan**, and picking **scan** picks **distros**. Picking **glint** on a distro makes it a company.
In one colour, the art changes in two ways so it still reads:
- In the logo style, a logo with white among its colours leaves the white out. White letters on a coloured shape show as gaps.
- In the shade style, on a light page, the characters swap ends: the densest now mark the darkest parts, because dark ink on a light page reads as dark. Otherwise the picture would show as a negative.
## Embed it on any web page
The **embed it** box holds a snippet for any HTML page. It needs no install and no build step. Press **[copy]**, then paste it where you want the art.
The snippet is an element and a script. The script loads `mount()` from `https://ascii.rest/mount.js` and plays your piece, which is written out in full inside it. Shortened, a piece named `acme` looks like this:
```html
```
What it does on your page:
- In colour, it draws on a ``. With **[mono]** on, the snippet uses a `` in your page's text colour instead.
- It reads your page's text colour to tell a light page from a dark one, and uses the colours made for that page.
- It plays only while it is on screen and its tab is open. For readers who prefer reduced motion, it holds still.
To change it:
- **Make it smaller or bigger:** change `max-width` on the ``. On the ``, change the `10px` in its `font`.
- **Put two on one page:** give each its own name. The element's `id` is `ascii-` and the name, so two with the same name clash.
If nothing shows, work through [nothing shows up](https://ascii.rest/docs/faq/#nothing-shows-up-what-should-i-check). If your site sends a `Content-Security-Policy` header, its `script-src` must allow `https://ascii.rest` and this inline script: add the script's hash, a nonce, or `'unsafe-inline'`.
## Add it to a GitHub README
A README can't run scripts, so the page makes two animated SVG files: one for GitHub's light theme and one for its dark theme.
1. Press **[light svg]**. It saves `.svg`.
2. Press **[dark svg]**. It saves `.dark.svg`.
3. Put both files in your repository, next to `README.md`, and commit them.
4. Copy the snippet from the **readme** box into your README. For `acme`, it is:
```html
```
How it works:
- GitHub shows `acme.dark.svg` in its dark theme, and `acme.svg` in its light theme.
- Each SVG plays one loop of the glint or the scan, again and again, with no script. For readers who prefer reduced motion, it holds still.
- `width` is 5 pixels for each column. Change it to show the art bigger or smaller.
- To keep the files in another folder, change both paths, for example to `.github/acme.svg` and `.github/acme.dark.svg`.
[github readme](https://ascii.rest/docs/readme/) has more on images in a README.
## Add it to the library
The last box holds `.ts`: the piece in the library's own format. It has the piece's `meta`, the art, its colours, and the same glint or scan code as the library's logos. Press **[download]** to save it, or **[copy]**.
The library takes the logos of programming languages and web tools, of companies, and of Linux distributions. To add one:
1. Start from the official logo, such as an SVG from its owner's brand page, Simple Icons or devicon.
2. Pick its category, and keep the **logo** style.
3. Give it a name the library doesn't have yet. If the name is taken, the page says so: `the library already has a piece called python: give this one another name to add it`.
4. Download the `.ts` file.
5. In the comment at the top of the file, say where the logo's artwork came from.
6. Save it in the repo as `src/pieces/.ts`, check it, and open a pull request. [Add it to the library](https://ascii.rest/docs/pieces/#add-it-to-the-library) has every step. The file already imports its types from `../types.ts`, as the library's pieces do, so skip step 3 there.
In short, with Node 23.6 or later:
```sh
git clone https://github.com/bas3line/ascii
cd ascii
npm install
# save the file as src/pieces/acme.ts, then:
npm run gen
npm run check -- acme --show
npm run typecheck
```
To play the file in your own app instead, change its import line to `import type { Frame, Meta } from "ascii.rest";`. Then use it as you would any piece you wrote: [play it in React or Next.js](https://ascii.rest/docs/pieces/#play-it-in-react-or-nextjs).
## Formats it takes
Any image your browser can open works. Each format is drawn like this:
| format | what happens |
| --- | --- |
| SVG | Drawn as an image, so no script in it runs and it loads nothing. It needs a `viewBox`, or a `width` and a `height`. Up to 4 MB. |
| PNG, WebP | A clear background stays clear. With no transparency, the plain background is left out. |
| JPG | Its plain background, if it has one, is left out. |
| GIF | Only its first frame is drawn. |
| any other image, such as AVIF or BMP | Drawn the same way as a PNG. |
- Images other than SVGs can be up to 40 MB. The page first draws every image 2400 pixels across, the longer way, so a larger image adds no detail.
- SVG markup copied out of a web page often has no `xmlns`. The page adds it for you.
## Your image stays in your browser
The page does all of its work in your browser:
- Your image is never uploaded, and nothing about it is sent anywhere.
- The SVGs and the `.ts` file are made on your computer and saved from your browser.
- An SVG is drawn as an image, which runs none of its scripts and loads nothing from elsewhere.
Of what the page gives you, only the embed snippet loads anything from elsewhere: on your own page, it loads `mount.js` from ascii.rest.
## Common problems
A message under the art says what went wrong. Each message, and the other things people run into:
| you see | why | fix |
| --- | --- | --- |
| `that is not an image: try an svg, png, jpg, webp or gif` | The file isn't an image. | Pick an image file. |
| `that image could not be read: try an svg, png, jpg, webp or gif` | Your browser can't open the file. It may be damaged, or in a format your browser doesn't support. | Open it in an image editor and save it as a PNG. |
| `that image is over 40 MB: try a smaller one` | The file is too big. | Make it smaller. 2400 pixels across is plenty. |
| `that svg is over 4 MB, which is a lot for a logo` | The SVG is too big. | Simplify it, or save it as a PNG. |
| `that svg has no size: give it a viewBox` | The `` tag has no `viewBox`, and no `width` and `height`. | Add a `viewBox`, such as `viewBox="0 0 24 24"`, with your drawing's own width and height. |
| `that is not an svg` or `that svg could not be read` | The pasted text has no `` tag, or the markup has a mistake in it. | Paste the whole `` element, from ``. |
| `that svg could not be drawn` | Your browser couldn't draw it as an image. | Export it again from your design tool, or save it as a PNG. |
| `that svg draws nothing` or `that image is all background` | Nothing is left to draw. | Check the image shows something. A logo the same colour as its background is all background. |
| a box or a colour stays round the logo | The image's border isn't mostly one colour, so nothing was taken out. A gradient, a shadow or a photo behind the logo does this. | Crop the image closer to the logo, or use a PNG with a clear background, or an SVG. |
| part of the logo is missing | That part is the background's colour and touches it, so it was taken out too. | Press **[keep the background]**, or use a PNG with a clear background. |
| the art is narrower than the width you set | The image is tall, and a piece has at most 32 rows. | Nothing is wrong. |
| `the library already has a piece called ...` | A library piece has that name. It matters only if you add yours to the library. | Type another name. |
| the art doesn't move | Your system asks for reduced motion. | Press **[play anyway]** at the top of the page. |
## Next
- [github readme](https://ascii.rest/docs/readme/): more ways to put art in a README, and banners with your own text.
- [your own pieces](https://ascii.rest/docs/pieces/): what a piece is made of, to change the `.ts` file by hand.
- [html](https://ascii.rest/docs/html/): the library's tags, for pieces on any web page.
---
# svg
URL: https://ascii.rest/docs/svg/
Turn any piece, or a banner of your own text, into an animated SVG that plays without JavaScript.
`ascii.rest/svg` turns any piece, or a banner of your own text, into an animated SVG. The SVG plays where JavaScript can't run, like a GitHub README or an ` ` tag.
(A video on this page shows this: bannerSvg() in a Node script writes a file, and the README shows it.)
Three words you will see on this page. A **piece** is one animation, like `donut` or the `rust` logo. A **banner** is any text you choose, drawn in big block letters. A **frame** is the picture at one moment.
To make your first SVG:
1. Install the package:
```sh
npm install ascii.rest
```
2. Save this script as `make-svg.ts`:
```ts
import { writeFileSync } from "node:fs";
import { bannerSvg } from "ascii.rest/svg";
writeFileSync("hello.svg", bannerSvg("hello", { color: ["#f97316", "#f778ba"] }));
```
3. Run it. Node runs a `.ts` file as it is from Node 22.18, or 23.6 in Node 23. If yours prints an error, name the file `make-svg.mjs` instead. The script has no types, so it is plain JavaScript too.
```sh
node make-svg.ts
```
4. Open `hello.svg` in your browser, or show it on a page with ` `. It looks like this:
(A live example plays here on the page.)
## When to use an SVG
Use an SVG wherever the page can't run JavaScript. ``, `` and `mount()` draw each frame with JavaScript, so they can't play in these places:
- **A GitHub README.** GitHub runs no scripts in a README, but it shows SVG images and plays their CSS animation.
- **An ` ` tag.** A browser never runs scripts inside an image, but it does play an SVG's CSS animation.
- **Email.** This depends on the email app. Apple Mail shows SVG images. Gmail turns them into a still PNG ([caniemail](https://www.caniemail.com/features/image-svg/)). Test in the apps your readers use.
On a page that can run JavaScript, use [react](https://ascii.rest/docs/react/) or [html](https://ascii.rest/docs/html/) instead. They play the piece live, so it never has to loop.
The SVG holds each frame as text, and CSS shows the frames in turn, so it needs no script. The details are in [how the SVG works](#how-the-svg-works).
`svg()` and `bannerSvg()` take values and return a string. They never touch a page, so they run anywhere JavaScript does: in Node, in a browser, in a Cloudflare Worker, or in a build script.
## Turn a piece into an SVG
`svg(piece, options)` returns one piece as an SVG, in a string. Import the piece from `ascii.rest/pieces` by its export, in camelCase: `nightCoast` for the piece named `night-coast`.
```ts
import { svg } from "ascii.rest/svg";
import { donut, rust } from "ascii.rest/pieces";
const logo = svg(rust); // the rust logo, for a light page
const logoDark = svg(rust, { dark: true }); // the same logo, for a dark page
const spin = svg(donut, { ink: "#3fb950", seconds: 2 }); // a green donut that loops every 2 seconds
```
This is `svg(rust)`. ascii.rest serves the same file at `https://ascii.rest/svg/rust.svg`:
(A live example plays here on the page.)
| option | what it does | default |
| --- | --- | --- |
| `dark` | Draws it for a dark page: light text, and a coloured piece's dark colours. | `false` |
| `ink` | The colour of a text piece, as `#rrggbb`. A text piece is drawn in one colour, like `donut`. A coloured piece, like a logo, keeps its own colours. | GitHub's text colour: `#1f2328`, or `#f0f6fc` with `dark` |
| `seconds` | How long one loop lasts, in seconds. | the piece's own loop, or `4`: see [make the loop seamless](#make-the-loop-seamless) |
| `from` | The moment in the piece where the loop starts, in seconds. | the piece's own loop, or `0` |
| `fps` | Frames it takes each second, from 1 to 60. More is smoother, and makes a bigger file. | `15` |
| `once` | Plays the loop once, then stays on its last frame. | `false`, or `true` for a banner with `effect: "type"` |
| `scale` | Pixels per column, when nothing else sets the image's size. | `10` |
| `label` | What screen readers say. | `", in ascii, from ascii.rest"` |
| `options` | Options for the piece itself, like `{ scan: 3 }` for a distro that scans every 3 seconds. | the piece's own |
A scene has a background colour of its own, its `meta.ground`. Its SVG draws that colour behind it, so it looks the same on a light page and a dark one. The same goes for a scene beside a banner in `bannerSvg()`.
`svg()` throws an error when:
- `ink` isn't `#rrggbb`.
- `fps` isn't between 1 and 60.
- `scale` isn't a number of 0 or more.
### Make the loop seamless
An SVG plays one loop over and over. If the piece looks different at the end of the loop than at the start, you see a jump. `svg()` picks a loop with no jump when it knows one:
- A banner: the time between two glints, or one play of its typing.
- A piece that sets `meta.loop`: that many seconds.
- A logo, or a company's logo: the time between two glints. That is 5 seconds, unless you change the option for it listed in the logo's `meta.options`.
- A Linux distro: the time between two scans, set by its `scan` option. A scan is a line that runs down the logo.
- Anything else: 4 seconds. If the piece doesn't repeat every 4 seconds, it jumps once a loop. Pass `seconds` to change the length.
`loopOf(piece)` tells you which loop `svg()` will play:
```ts
import { loopOf } from "ascii.rest/svg";
import { donut, rust } from "ascii.rest/pieces";
loopOf(rust); // { every: 5, from: 4.5 }: 5 seconds, starting between two glints
loopOf(donut); // { every: 4, from: 0 }
```
A piece that doesn't move, like [box-frames](https://ascii.rest/box-frames/), becomes a still SVG with no animation in it.
## Make a banner SVG
`bannerSvg(text, options)` draws your text as a banner and returns it as an SVG string. It can also add a tagline under the letters, a piece beside them, and a coloured card behind them.
```ts
import { bannerSvg } from "ascii.rest/svg";
import { rust } from "ascii.rest/pieces";
const light = bannerSvg("ferris", { art: rust, color: "art", tagline: "fast, safe, fun" });
const dark = bannerSvg("ferris", { art: rust, color: "art", tagline: "fast, safe, fun", dark: true });
```
(A live example plays here on the page.)
`art: rust` puts the rust logo beside the letters. `color: "art"` gives the letters the logo's own colour. With no `color`, the letters are in GitHub's text colour and the shadow is in GitHub's muted grey, so the banner looks at home in a README.
`bannerSvg()` takes every option of `banner()`, such as `font`, `shadow`, `fill`, `effect` and `speed`. They are all on [banners](https://ascii.rest/docs/banners/#options). These options are only for the SVG:
| option | what it does | default |
| --- | --- | --- |
| `dark` | Draws it for a dark page. | `false` |
| `color` | The letters' colour, as `banner()` takes it: one `#rrggbb`, a list of them for a fade, or `{ light, dark }`. Or `"art"`, for the art's own colour, which is GitHub's text colour when there is no art. | GitHub's text colour: `#1f2328`, or `#f0f6fc` with `dark` |
| `tagline` | A line of plain text under the letters. It types out once the letters are in, then a cursor blinks after it. | none |
| `taglineColor` | The tagline's colour, as `#rrggbb` or `{ light, dark }`. | the shadow's colour |
| `taglineSize` | The tagline's font size, in SVG units. | `28` |
| `art` | A piece to show with the letters, like a logo. Or `{ svg }`, an SVG that `svg()` made. | none |
| `place` | Where the art goes: `"left"`, `"right"`, `"above"` or `"below"`. | `"left"` |
| `artSize` | The art's height, as a multiple of the letters and tagline together. | `1` beside them, `2.4` above or below |
| `spacing` | The gap between the art and the letters, in SVG units. | `32` |
| `align` | Lines up the letters, the tagline and the art across the width: `"start"`, `"center"` or `"end"`. | `"center"` when the art is above or below, else `"start"` |
| `background` | The colour of a card behind everything, as `#rrggbb` or `{ light, dark }`. | none |
| `padding` | The space between the card's edge and what is on it, in SVG units. | `24` |
| `radius` | How round the card's corners are, in SVG units. | `12` |
| `scale` | Pixels per column, when nothing else sets the image's size. | `5` |
| `fps` | Frames it takes each second, from 1 to 60. | `15` |
| `label` | What screen readers say. | `", in ascii, from ascii.rest"`, with `: ` after the text when there is one |
`bannerSvg()` throws an error when:
- The font can draw none of the text, like `"é"` or `"***"`. Text of only spaces counts as none.
- `background` or `taglineColor` isn't `#rrggbb`, for either theme, even one it doesn't use.
- `taglineSize`, `artSize`, `spacing`, `padding`, `radius` or `scale` isn't a number of 0 or more.
- `place` isn't `"left"`, `"right"`, `"above"` or `"below"`, or `align` isn't `"start"`, `"center"` or `"end"`.
- `fps` isn't between 1 and 60.
- `art` is `{ svg }` with an SVG that ascii.rest/svg didn't write.
- An option of `banner()` is one it can't take, like an unknown font. See [banners](https://ascii.rest/docs/banners/#options).
### Add a tagline
A tagline is a line of plain text under the letters. It types out after the letters appear, then a cursor blinks at its end. With `effect: "still"`, it shows all at once, with no cursor.
```ts
import { bannerSvg } from "ascii.rest/svg";
const banner = bannerSvg("my project", { tagline: "a line about it", color: "#f97316" });
```
(A live example plays here on the page.)
Change its colour with `taglineColor` and its size with `taglineSize`.
### Put a piece beside the letters
`art` takes any piece. A logo works best, because its loop has no jump. `place` puts it on any side:
```ts
import { bannerSvg } from "ascii.rest/svg";
import { rust } from "ascii.rest/pieces";
const banner = bannerSvg("ferris", { art: rust, color: "art", place: "above" });
```
(A live example plays here on the page.)
Beside the letters, the art is as tall as the letters and tagline together. Above or below them, it is 2.4 times as tall. Change that with `artSize`. `spacing` sets the gap between the art and the letters, and `align` lines them up across the width.
### Put a card behind it
`background` draws a card with rounded corners behind everything. On a card, the colours follow the card, not the page: a dark card gets light letters, even without `dark`. So one SVG looks right on both light and dark pages.
```ts
import { bannerSvg } from "ascii.rest/svg";
import { rust } from "ascii.rest/pieces";
const banner = bannerSvg("ferris", { art: rust, color: "art", background: "#0d1117" });
```
(A live example plays here on the page.)
`padding` sets the space inside the card's edge, and `radius` rounds its corners.
### Reuse a logo SVG you already have
`art` also takes `{ svg }`: an SVG that `svg()` made, such as a logo from `https://ascii.rest/svg/.svg`. `bannerSvg()` reuses that SVG's frames instead of drawing the piece again. The result is the same SVG, made several times faster: see the times in [keep the file small](#keep-the-file-small).
```ts
import { bannerSvg } from "ascii.rest/svg";
const rustSvg = await (await fetch("https://ascii.rest/svg/rust.svg")).text();
const banner = bannerSvg("ferris", { art: { svg: rustSvg }, color: "art" });
```
For a dark banner, use the dark logo, `https://ascii.rest/svg/rust.dark.svg`. An SVG that ascii.rest/svg didn't write throws an error.
## Set the size
An SVG stays sharp at any size. You can set its size in two places.
On the page, give the ` ` a `width`. The height follows:
```html
```
In the file, `scale` sets how many pixels wide each column is, when nothing else sets the size. `svg()` uses 10 and `bannerSvg()` uses 5:
```ts
import { bannerSvg, svg } from "ascii.rest/svg";
import { rust } from "ascii.rest/pieces";
svg(rust); // 64 columns: 640 by 640 pixels
svg(rust, { scale: 5 }); // 320 by 320 pixels
bannerSvg("hello"); // 49 columns: 245 by 60 pixels
bannerSvg("hello", { scale: 8 }); // 392 by 96 pixels
```
`taglineSize`, `spacing`, `padding` and `radius` are in SVG units. One cell, the space of one character, is 10 units wide and 20 units tall. A letter of the `block` font is 5 cells tall, so 100 units. A `taglineSize` of 28 is a little over a quarter of that.
## Serve an SVG from your server
Both functions return a string, so any server can send one. Send it with the header `Content-Type: image/svg+xml`, so browsers treat it as an image.
To make a banner from the text in the URL, see the route handler on [next.js](https://ascii.rest/docs/nextjs/#serve-a-banner-as-an-svg-from-a-route-handler). The handler uses only `Request` and `Response`, so the same code works in a Cloudflare Worker's `fetch`.
When the SVG never changes, make it once, when the server starts, not on every request. This Cloudflare Worker makes the rust logo once and sends the same string to every request:
```ts
import { svg } from "ascii.rest/svg";
import * as rust from "ascii.rest/pieces/rust";
const light = svg(rust);
const dark = svg(rust, { dark: true });
export default {
fetch(request: Request) {
const body = new URL(request.url).searchParams.has("dark") ? dark : light;
return new Response(body, { headers: { "Content-Type": "image/svg+xml" } });
},
};
```
A few things help:
- **Cache it.** The same options always make the same SVG, so let browsers and CDNs keep it with a `Cache-Control` header.
- **Compress it.** Most of an SVG is frames that look like each other, so gzip makes it far smaller (see [keep the file small](#keep-the-file-small)). Check that your server compresses it: the reply should have a `content-encoding` header.
```sh
curl -sI -H "Accept-Encoding: gzip" "https://example.com/banner?text=hello"
```
- **Make slow ones ahead of time.** Most pieces take under 25 ms to make, but a scene takes 0.2 to 0.5 seconds. The times are in [keep the file small](#keep-the-file-small).
You may not need a server at all: [github readme](https://ascii.rest/docs/readme/) lists URLs on ascii.rest that make banners and logos for you.
## Make SVG files with a script
When the SVG never changes, make it once in a script and commit the file, so you need no server. The script is on [github readme](https://ascii.rest/docs/readme/#make-your-own-svg-and-commit-it), with the `` tag that shows a dark file on GitHub's dark theme.
## Keep the file small
An SVG holds every different frame of its loop as text, so it can get big. This table shows how big, and how long each one took to make.
- The sizes were measured from the package as it is built, as text and after `gzip -9`. 1 KB is 1,024 bytes.
- The times are the middle value of 31 runs, in Node 26 on an Apple M4 laptop. `night-coast` had 3 runs. Your times will differ.
| what | as text | gzipped | time to make |
| --- | --- | --- | --- |
| `svg(rust)` | 156.3 KB | 6.3 KB | 5 ms |
| `svg(archLinux)` | 87.2 KB | 3.1 KB | 3 ms |
| `svg(spinners)` | 58.4 KB | 3.4 KB | 2 ms |
| `svg(donut)` | 110.9 KB | 12.5 KB | 25 ms |
| `svg(donut, { seconds: 2 })` | 56.1 KB | 6.8 KB | 15 ms |
| `svg(donut, { fps: 8 })` | 59.4 KB | 7.6 KB | 15 ms |
| `svg(nightCoast)` | 9,563.8 KB | 356.3 KB | 290 ms |
| `bannerSvg("hello")` | 90.0 KB | 2.4 KB | 1 ms |
| `bannerSvg("hello", { effect: "type" })` | 9.3 KB | 0.9 KB | under 1 ms |
| `bannerSvg("hello", { effect: "still" })` | 3.0 KB | 0.5 KB | under 1 ms |
| `bannerSvg("ferris", { art: rust, color: "art", tagline: "fast, safe, fun" })` | 261.2 KB | 8.8 KB | 6 ms |
| the same ferris banner, with `art: { svg: rustSvg }` from [reuse a logo SVG](#reuse-a-logo-svg-you-already-have) | 261.2 KB | 8.8 KB | 1 ms |
What that means for you:
- **Serve it compressed.** gzip made these files between 5 and 38 times smaller. ascii.rest sends `rust.svg` as 6,484 bytes gzipped, down from 160,026.
- **Use a shorter loop or fewer frames.** Halving `seconds`, or taking 8 frames a second instead of 15, about halves the file.
- **Let a banner stop.** `effect: "type"` plays once and stays, so it has few frames. `effect: "still"` has one.
- **Keep to small pieces.** A scene's SVG is 5 to 20 MB. Every other piece makes an SVG under 400 KB. SVGs suit logos, banners and small pieces best.
## Put two SVGs straight into one HTML page
Usually you show an SVG with ` `, and then you need none of this: each image is its own document.
Every SVG from `svg()` uses the same CSS class names. Paste two into one HTML page and their styles clash. Give one a prefix with `namespaced()`, then rebuild it with `wrap()`:
```ts
import { namespaced, svg, wrap } from "ascii.rest/svg";
import { donut, rust } from "ascii.rest/pieces";
const first = svg(donut);
const second = wrap(namespaced(svg(rust), "r")!, "rust");
```
- `namespaced()` returns `null` for an SVG that ascii.rest/svg didn't write. That is why the code has `!`.
- For three or more SVGs, give each one but the first its own prefix.
- `wrap()` takes a label for screen readers, then the pixels per column: 10 if you leave it out. Pass 5 for a banner, to keep its size.
- This works for any SVG from `svg()`, and for a banner with no tagline and no art. A banner with a tagline or art keeps some shared class names, so show it with ` `.
## How the SVG works
You don't need this to use `svg()`. It explains what is in the file.
- `svg()` plays one loop of the piece and takes a frame 15 times a second, unless you set `fps`.
- It keeps each different frame once, as text. CSS shows the frames in turn.
- There is no script and no font file. The rows use the reader's own monospace font, stretched so they line up.
- For a reader who has turned on reduced motion, the SVG holds one still frame.
## Next
- [github readme](https://ascii.rest/docs/readme/): ready-made SVG URLs for your README, with no code.
- [banners](https://ascii.rest/docs/banners/): every banner option, from fonts and shadows to colours and motion.
- [next.js](https://ascii.rest/docs/nextjs/): serve a banner SVG from a route handler, or make SVG files when you build.
---
# github readme
URL: https://ascii.rest/docs/readme/
Put a moving logo, or your name in big block letters, at the top of a GitHub README.
A GitHub README can't run scripts, so ascii.rest serves animated SVG images that you link to. You can add two kinds:
- A logo, company or Linux distro from the library, by its name. Each of these is a **piece**: one animation.
- A **banner**: any text you like, in big block letters.
(A video on this page shows this: The banner maker: typing a name, picking colours and a logo, copying the snippet, and the banner on GitHub.)
Paste this into your `README.md`:
```html
```
GitHub shows this:
(A live example plays here on the page.)
Here is how it works:
- Every image comes as two files. The `.svg` file is for GitHub's light theme. The `.dark.svg` file is for its dark theme.
- The `` tag shows the file that matches the reader's theme.
- The animation is CSS inside the SVG, so it needs no script.
- If the reader has asked their system for reduced motion, the image holds still.
## Add a logo
Every logo, company and Linux distro in the library has a README image. There are two URLs for each one:
| URL | for |
| --- | --- |
| `https://ascii.rest/svg/.svg` | GitHub's light theme |
| `https://ascii.rest/svg/.dark.svg` | GitHub's dark theme |
`` is the piece's name, with dashes: `rust`, `arch-linux`, `vercel`. It is also the last part of the piece's URL on ascii.rest. For example:
```html
```
(A live example plays here on the page.)
Set `width` (or `height`) on the ` `. Without it, a logo shows at its full size. That is 290 to 790 pixels wide, depending on the logo. Rust's is 640, so `width="320"` above shows it at half size.
You don't have to write this yourself. On every logo's page, the **readme** tab has the snippet, and **[copy readme snippet]** copies it. The snippet sets `width` to half the logo's full size.
The library's other pieces, such as [donut](https://ascii.rest/donut/), have no hosted image: `https://ascii.rest/svg/donut.svg` returns 404. To put one of those in a README, [make the SVG yourself](#make-your-own-svg-and-commit-it).
For your own logo, which isn't in the library, make the two SVGs on [image to ascii](https://ascii.rest/make/), then commit them next to your README. The [image to ascii docs](https://ascii.rest/docs/images/#add-it-to-a-github-readme) have the steps.
## Add a banner with any text
A banner is your text in big block letters. Put the text in the URL:
| URL | for |
| --- | --- |
| `https://ascii.rest/banner/.svg` | GitHub's light theme |
| `https://ascii.rest/banner/.dark.svg` | GitHub's dark theme |
```html
```
What the text can hold:
- Up to 20 characters.
- Letters, digits, spaces, and these marks: `. , ! ? ' : - + = / _`
- Letters are drawn as capitals, except with `font=mixed`, which keeps lower case.
- Any other character is left out. If nothing is left to draw, the URL returns 404.
- Write the text the way a URL needs it: a space is `%20` and a comma is `%2C`. JavaScript's `encodeURIComponent()` does this for you.
With no colour set, the letters are in GitHub's own text colour: dark in the light theme, light in the dark theme. That is why you need both files.
## Change how a banner looks
Add your choices after a `?` at the end of the URL, joined with `&`. Put the same choices on the light URL and the dark URL.
```html
```
(A live example plays here on the page.)
Every key a banner URL takes:
| key | what it does | values | default |
| --- | --- | --- | --- |
| `color` | The letters' colour. Two or more colours make a fade from the first to the last. `art` uses the main colour of the logo you set with `art`. | Six hex digits, without `#`: `f97316`. Up to eight, separated by commas: `f97316,f778ba`. Or `art`. | GitHub's text colour |
| `effect` | How it moves. | `glint`: a bright band, the glint, sweeps across the letters every few seconds. `type`: the letters type in once, then stay. `still`: no motion. | `glint` |
| `speed` | How fast it moves. | `slow`, `normal`, `fast`. `slow` is 0.6 times as fast as `normal`, and `fast` is 1.8 times. | `normal` |
| `font` | The shape of the letters. Each one is shown on [banners](https://ascii.rest/docs/banners/#change-the-font). | `block`: most letters 8 or 10 columns, 5 rows. `slim`: narrow letters, every one 6 columns. `tall`: 10 columns, 7 rows, thin strokes. `bold`: most 12 columns, 7 rows, heavy strokes. `round`: 10 columns, 6 rows, rounded corners. `wide`: 12 or 14 columns. `mixed`: capitals and lower case, 9 rows. `italic`: letters that lean right. | `block` |
| `shadow` | The lines of the drop shadow. | `double` (║ ═), `single` (│ ─), `heavy` (┃ ━), `rounded` (╭ ╯), `ascii` (\| - +), `none` | `double` |
| `fill` | The character the letters are made of. | `shade`: ▓ in the dark theme, █ in the light theme. `block`: █. `light`: ▒. `hash`: #. `at`: @. | `shade` |
| `tagline` | A line of plain text under the letters. It types out one character at a time, then a cursor blinks after it. With `effect=still`, it shows at once, with no cursor. | Any text, up to 60 characters. | none |
| `art` | A logo, company or distro next to the letters. | The name of any [logo image](#add-a-logo): `rust`, `arch-linux`, `vercel`. | none |
| `place` | Where the art goes. Only used with `art`. | `left`, `right`, `above`, `below` | `left` |
| `size` | How big GitHub shows it. | `s`, `m`, `l`: 3, 5 or 8 pixels per column. `hello` is 147, 245 or 392 pixels wide. | `m` |
| `bg` | A background card behind it all. The letters and the art switch to the colours that read on the card, whatever GitHub's theme. | Six hex digits, without `#`: `0d1117`. | none |
A few rules:
- Leave a key out to keep its default.
- Write a space in the tagline as `%20` and a comma as `%2C`. The commas between colours stay as plain commas.
- A value the URL can't take returns 400 with one line that says what it takes, for example `effect takes glint, type, still`.
- A key the URL doesn't know is ignored. A misspelt key gives you the default, not an error.
More examples, each with its URL:
```text
https://ascii.rest/banner/bas3line.svg?color=f97316,f778ba&tagline=making%20ascii.rest
```
(A live example plays here on the page.)
```text
https://ascii.rest/banner/i%20use%20arch.svg?color=art&effect=type&art=arch-linux&place=above
```
(A live example plays here on the page.)
```text
https://ascii.rest/banner/hello%20world.svg?color=22d3ee,4493f8&font=slim&shadow=rounded&tagline=a%20card%20of%20its%20own&bg=0d1117
```
(A live example plays here on the page.)
```text
https://ascii.rest/banner/old%20school.svg?color=3fb950&effect=still&shadow=ascii&fill=hash
```
(A live example plays here on the page.)
## Make a banner by clicking
The [banner maker](https://ascii.rest/banner/) writes the snippet for you. It shows the banner in both themes as you click.
1. Open [ascii.rest/banner](https://ascii.rest/banner/).
2. Type your text, and a tagline if you want one.
3. Pick a colour, font, shadow, letters, motion, speed and size. Each one sets a key from the table above: **letters** sets `fill`, and **motion** sets `effect`.
4. To add a logo, pick one under **art**, then pick where it goes.
5. To add a background card, pick one under **card**.
6. Under **use it**, open the **html** tab. Press **centred** if you want it centred.
7. Press **[copy]** and paste the snippet into your README.
The snippet from the maker also wraps the banner in a link to the maker. You can keep the link or remove it.
The other tabs give you the same banner in other forms:
| tab | what you get |
| --- | --- |
| **markdown** | One Markdown image. See [Markdown or HTML](#choose-markdown-or-html). |
| **url** | The light banner's URL on its own. |
| **react** | A `` component for a web page. See [react](https://ascii.rest/docs/react/). |
| **svg** | The `bannerSvg()` code that makes the same SVG in your own code. |
| **terminal** | The `npx ascii.rest banner` command that prints it in a terminal. |
| **cli** | The code that prints it when your own CLI starts. See [terminal](https://ascii.rest/docs/terminal/). |
Under each preview, **[download svg]** saves that SVG file, so you can commit it to your repository.
The maker's own URL keeps every choice you made. Bookmark it, or send it to someone, to come back to the same banner. If a link to the maker has a value it can't take, the maker keeps the rest of the link and says what it left out.
Every logo's page also has a **[make a banner with it]** link. It opens the maker with the logo's name as the text, the logo as the art, and the logo's colour.
## Choose Markdown or HTML
GitHub READMEs take both Markdown and HTML. Use the HTML `` snippet when you can.
| | HTML `` | Markdown image |
| --- | --- | --- |
| light and dark themes | a different image for each | the same image for both |
| set the size | `width` or `height` on the ` ` | not possible |
| centre it | wrap it in `` | not possible |
The same banner in Markdown, linking to the maker:
```md
[](https://ascii.rest/banner/)
```
Markdown uses the light image in both themes. Its default letters are dark, so they are hard to read in GitHub's dark theme. Set a `color` that reads on both, or set `bg` for a card. With `bg`, the light and dark images are the same anyway.
## Make a profile README
Your GitHub profile shows the README of a public repository named after your username. For the user `sam`, that is the repository `sam/sam`.
1. Create a public repository with the same name as your username.
2. Add a `README.md` file at the top of it.
3. Paste this at the top of the file, with your own text:
```html
```
The banner types in once each time the page loads, then the tagline types out under it:
(A live example plays here on the page.)
The row under it is three logos, each 80 pixels tall. Swap in the languages and tools you use.
## Make your own SVG and commit it
The URLs cover the common choices. For anything else, make the SVG in your own code with `bannerSvg()` from `ascii.rest/svg`. Then commit the files to your repository, so your README loads nothing from ascii.rest.
Things `bannerSvg()` can do that a URL can't:
- Time the glint: `glint: { every: 6 }` makes it cross every 6 seconds.
- Make the letters from any character: `fill: "*"`.
- Draw the shadow with 16 characters of your own, or the letters in a font of your own.
- Set the tagline's colour (`taglineColor`), the art's size (`artSize`), and the card's `padding` and `radius`.
- Line everything up at the start, centre or end (`align`).
Every option is on the [svg](https://ascii.rest/docs/svg/) page.
1. Install the package:
```sh
npm install --save-dev ascii.rest
```
2. Save this as `scripts/banner.ts`:
```ts
import { mkdirSync, writeFileSync } from "node:fs";
import { bannerSvg } from "ascii.rest/svg";
import { rust } from "ascii.rest/pieces";
const options = {
font: "slim",
color: ["#f97316", "#f778ba"],
tagline: "fast, safe, fun",
art: rust,
glint: { every: 6 },
} as const;
mkdirSync(".github", { recursive: true });
writeFileSync(".github/banner.svg", bannerSvg("my project", options));
writeFileSync(".github/banner.dark.svg", bannerSvg("my project", { ...options, dark: true }));
```
In code, colours are written with `#`. `dark: true` makes the dark theme's file.
3. Run it. Node runs a `.ts` file as it is from Node 22.18, or 23.6 in Node 23.
```sh
node scripts/banner.ts
```
It writes `.github/banner.svg` and `.github/banner.dark.svg`. If Node warns that the module type isn't specified, add `"type": "module"` to your `package.json`. On an older Node, save the script as `scripts/banner.mjs`, delete `as const`, and run `node scripts/banner.mjs`.
4. Commit both files.
5. Point your README at them with relative paths. GitHub turns these into links to the files in your repository:
```html
```
When you change the script, run it again and commit the new files.
The same works for any piece, not only banners. `svg()` turns a piece into an SVG, so you can put [donut](https://ascii.rest/donut/), which has no hosted image, in a README:
```ts
import { mkdirSync, writeFileSync } from "node:fs";
import { svg } from "ascii.rest/svg";
import { donut } from "ascii.rest/pieces";
mkdirSync(".github", { recursive: true });
writeFileSync(".github/donut.svg", svg(donut, { scale: 5 }));
writeFileSync(".github/donut.dark.svg", svg(donut, { dark: true, scale: 5 }));
```
`scale: 5` makes it 5 pixels per column, so the donut is 200 pixels wide.
## If an image doesn't show
Open the image's URL in a browser tab. If a banner URL is wrong, it answers with one line of plain text that says what.
| you see | why | fix |
| --- | --- | --- |
| 400 `effect takes glint, type, still` (or another key) | A value the URL can't take. | Use one of the values in [the table](#change-how-a-banner-looks). |
| 400 `a banner takes up to 20 characters` | The text is too long. | Shorten it. |
| 400 `a tagline takes up to 60 characters` | The tagline is too long. | Shorten it. |
| 400 `there is no logo, company or distro called ...` or `art takes the name of a logo ...` | `art` isn't the name of a logo image. Use the piece's name, with dashes: `arch-linux`. | Use a name from [the logo images](#add-a-logo). |
| 400 `color=art takes its colour from the art, so it needs art too` | `color=art` without `art`. | Add `art`, or pick a hex colour. |
| 404 `nothing to draw` | The text has none of the characters a banner can draw. | Use letters, digits or the marks listed [above](#add-a-banner-with-any-text). |
| 404 on `https://ascii.rest/svg/.svg` | That piece has no logo image, or the name is misspelt. | Check the name on the piece's page, or [make the SVG yourself](#make-your-own-svg-and-commit-it). |
| a plain banner, not the one you asked for | A key is misspelt, so it is ignored. | Check each key against [the table](#change-how-a-banner-looks). |
| it doesn't move | Reduced motion is on, `effect` is `still`, or `effect` is `type`, which plays once and stays. | Nothing is broken. To see it move, turn reduced motion off, or use `effect=glint`. |
## Next
- [banners](https://ascii.rest/docs/banners/): every option of `banner()`, for web pages and terminals.
- [svg](https://ascii.rest/docs/svg/): `svg()` and `bannerSvg()`, with every option.
- [terminal](https://ascii.rest/docs/terminal/): print the same banner in a terminal.
---
# terminal
URL: https://ascii.rest/docs/terminal/
Play any piece in your terminal, print your text as a banner, or add a splash screen to your own command-line tool.
ascii.rest works in a terminal as well as on a web page. You can play any piece with one command, print your text in big block letters, or show an animation when your own command-line tool starts. You need Node 18.3 or newer, and nothing else.
(A video on this page shows this: npx ascii.rest donut, the banner command, and a splash screen for a CLI made with play().)
Try it now. The donut turns until you press any key:
```sh
npx ascii.rest donut
```
(A live example plays here on the page.)
A **piece** is one animation, like `donut` or `rust`. A **frame** is the picture at one moment. A **banner** is your own text drawn in block letters. A banner's [glint](https://ascii.rest/docs/banners/#change-the-glint) is a bright band that sweeps across the letters.
## Choose what to use
You can use it in two ways: the `npx ascii.rest` command, or three functions you import from `ascii.rest/terminal` into your own Node program.
| you want | use |
| --- | --- |
| to see a piece or a banner, with no code | `npx ascii.rest` |
| a piece on the whole screen for a moment, then gone, like a splash screen | `play()` |
| your text in block letters that stays in the output, like a CLI's header | `banner()` |
| one frame as a plain string, for a log, a `--help` screen or a file | `still()` |
`play()` and `banner()` behave differently. `play()` takes over the screen and gives it back when it stops, so nothing is left behind. `banner()` prints where the cursor is, moves once, and stays in the scrollback with the rest of your output.
## Play a piece with npx
Use these to see any piece without writing code.
```sh
npx ascii.rest donut # plays until you press a key
npx ascii.rest night-coast --seconds 10 # stops after 10 seconds
npx ascii.rest list # the name of every piece, by category
```
Coloured pieces (scenes, logos, companies and distros) play in their own colours. Every other piece is a text piece, and plays in your terminal's own text colour.
Every flag, like `--mono` for no colour and `--light` for a light terminal, is on [cli](https://ascii.rest/docs/cli/#play-a-piece). So is what happens in a small window.
## Print a banner with npx
Use `npx ascii.rest banner` to print any text in block letters. A glint crosses it once, then it stays in your terminal.
```sh
npx ascii.rest banner hi
```
```text
▓▓╗ ▓▓╗ ▓▓╗
▓▓║ ▓▓║ ▓▓║
▓▓▓▓▓▓▓▓║ ▓▓║
▓▓╔═══▓▓║ ▓▓║
▓▓║ ▓▓║ ▓▓║
╚═╝ ╚═╝ ╚═╝
```
Add colours, a line under it, and another shadow:
```sh
npx ascii.rest banner 'my cli' --color ff6a00,f778ba --shadow rounded --tagline 'v1.0'
```
Put the text in single quotes, so your shell leaves `!` and `$` alone. Every flag, and the characters a banner can draw, are on [cli](https://ascii.rest/docs/cli/#print-a-banner).
## Add a splash screen to your CLI
Use `play()` to show a piece for a moment when your tool starts.
1. Install the package in your CLI's project:
```sh
npm install ascii.rest
```
2. Make your entry file an ES module: name it with `.mjs`, or add `"type": "module"` to your `package.json`. ascii.rest is an ES module, and the examples on this page use `await` at the top level.
3. At the top of your CLI's entry file, play a piece:
```ts
import { play } from "ascii.rest/terminal";
const { interrupted } = await play("donut", { seconds: 2 });
if (interrupted) process.exit(130);
console.log("ready");
```
4. Run your CLI. The donut plays on a clear screen for two seconds, or until you press a key. Then your terminal shows what it showed before, and your program goes on.
Here is what `play()` does:
- It draws on the alternate screen, the separate screen that programs like `less` and `vim` use, with the cursor hidden.
- It stops after `seconds`, when someone presses any key, or on Ctrl+C. Without `seconds`, it plays until a key.
- Ctrl+C does not end your program. `play()` resolves with `interrupted: true`, and you decide what to do. Exit code 130 is the usual one for Ctrl+C.
- It puts the terminal back however it stops: on an error, if your program exits while it plays, or if the process gets SIGTERM.
- Keys are read from `process.stdin`, and only when it is a terminal. If it isn't, only `seconds` or Ctrl+C can stop it, so always pass `seconds` for a splash screen.
### Options for play()
`play(piece, options)` takes the piece's name, with dashes, like `"night-coast"`. It also takes a piece you imported, like `donut` from `ascii.rest/pieces`, or a banner.
| option | what it does | default |
| --- | --- | --- |
| `seconds` | how long it plays, in seconds | until a key is pressed |
| `mono` | draws a coloured piece in the terminal's own text colour, with no colour codes | `false` |
| `light` | for a light terminal: a coloured piece takes its light colours, and a shaded piece flips its shading | `false` |
| `fps` | frames a second, instead of the piece's own | the piece's own |
| `options` | the piece's own options, like `{ text: "hello" }` for `marquee` | the piece's defaults |
| `out` | the stream it draws on | `process.stdout` |
It resolves with an object:
| field | what it is |
| --- | --- |
| `interrupted` | `true` when Ctrl+C stopped it |
| `cropped` | `true` when the terminal was smaller than the piece, so only its middle showed |
| `piece` | the piece's size in terminal cells, `{ cols, rows }` |
| `terminal` | the terminal's size when it stopped, `{ cols, rows }`. 0 by 0 when it drew nothing. |
### Give a piece its own options
Some pieces take options. `marquee` scrolls a line of text you choose:
```ts
import { play } from "ascii.rest/terminal";
await play("marquee", { seconds: 3, options: { text: "my-cli v1.0 is ready" } });
```
### Play your own banner as the splash
A banner from `ascii.rest/banner` is a piece too, so `play()` can play it on the whole screen:
```ts
import { banner } from "ascii.rest/banner";
import { play } from "ascii.rest/terminal";
await play(banner("my-cli", { color: ["#ff6a00", "#f778ba"] }), { seconds: 2 });
```
## Print a banner from your CLI
Use `banner()` from `ascii.rest/terminal` to print your tool's name in block letters where the cursor is. A glint crosses it once, or its letters type in, and then it stays with the rest of your output.
```ts
import { banner } from "ascii.rest/terminal";
await banner("my-cli", { color: ["#ff6a00", "#f778ba"], shadow: "rounded", tagline: "v1.0, fast" });
console.log("ready");
```
It fits itself to your terminal, and keeps one column free at the right edge. It draws wide letters when there is room, and narrower letters in a narrow terminal. If even those don't fit, it prints your text as one plain line.
With no `color` and no `shadowColor`, the letters are in your terminal's own text colour and the shadow is dimmed. With a `color`, the shadow stays dimmed unless you also set `shadowColor`.
### Options for banner()
`banner(text, options)` takes every option of [banner()](https://ascii.rest/docs/banners/#options) except `size` and `max`, because it fits the banner to the terminal's width itself. A `color` of `{ light, dark }` uses the one that matches the `light` option below.
These four options are only for the terminal:
| option | what it does | default |
| --- | --- | --- |
| `seconds` | how long the glint takes to cross once, or the letters to type in. `0` prints it still. | `1` |
| `tagline` | a line printed under it, dimmed, once it has moved | none |
| `light` | for a light terminal: the light colours, and solid `█` letters instead of `▓` | `false` |
| `out` | the stream it prints to | `process.stdout` |
In a terminal it moves once, over `seconds`, so `speed` makes no difference. Change `seconds` instead.
It resolves with `{ cols, rows, interrupted }`. `cols` and `rows` are the banner's size in terminal cells, or 0 if it printed plain text. `interrupted` is `true` if Ctrl+C stopped it moving. Like `play()`, Ctrl+C does not end your program:
```ts
import { banner } from "ascii.rest/terminal";
const { interrupted } = await banner("my-cli", { effect: "type", seconds: 2 });
if (interrupted) process.exit(130);
```
It throws an error when:
- an option is wrong, such as an unknown font or shadow, or a colour that isn't `#rrggbb`. See [fix an error from banner()](https://ascii.rest/docs/banners/#fix-an-error-from-banner).
- the font can draw none of the text. Spaces alone count as none.
- `seconds` is below 0, `NaN` or `Infinity`.
The message says what to change.
### Start it at the beginning of a line
`banner()` prints from where the cursor is, so end anything you print before it with a newline. `console.log()` adds the newline for you. `process.stdout.write()` does not.
```ts
import { banner } from "ascii.rest/terminal";
console.log("starting my-cli"); // ends the line, so the banner starts on a new one
await banner("my-cli");
```
If the cursor is in the middle of a line, the banner's first row starts after that text, out of line with the rest. When the banner moves, it draws over that line, and the text is gone.
### Keep your real output clean
Pass `out: process.stderr` to print the banner on stderr. Your program's own output on stdout then stays clean for pipes and files:
```ts
import { banner } from "ascii.rest/terminal";
await banner("my-cli", { out: process.stderr });
process.stdout.write(JSON.stringify({ ok: true }) + "\n");
```
`play()` takes `out` the same way.
## Know what happens in pipes, CI and small terminals
ascii.rest checks where it is drawing before it draws. A splash screen or a banner never fills a log or a file with escape codes, the hidden codes that move the cursor and set colours.
| when | `play()` | `banner()` |
| --- | --- | --- |
| the output is piped or redirected, as in most CI logs | draws nothing and resolves at once | prints the banner still, with no colour, at once |
| `NO_COLOR` is set | still draws in colour. Pass `mono: true` for none. | prints with no colour, and still moves |
| the terminal is too narrow | a scene shrinks to fit. Any other piece shows its middle, and `cropped` is `true`. | narrower letters, then the plain text |
| the terminal is too short | a scene shrinks to fit. Any other piece shows its middle. | prints the banner still. It needs two rows more than its height to move: 8 rows for a default banner, which is 6 tall. |
| the terminal is resized | draws the piece again in the middle | stops moving where it is |
| Ctrl+C is pressed | stops, with `interrupted: true` | jumps to the end, with `interrupted: true` |
| any other key is pressed | stops | carries on: it doesn't read keys |
To follow `NO_COLOR` in a splash screen too, pass it to `mono`:
```ts
import { play } from "ascii.rest/terminal";
await play("rust", { seconds: 2, mono: Boolean(process.env.NO_COLOR) });
```
The `npx ascii.rest` command checks the same way. Piped or redirected, it doesn't play a piece. It prints one frame as text instead, so this saves the donut to a file:
```sh
npx ascii.rest donut > donut.txt
```
## Get a frame as text with still()
Use `still()` to get one frame of a piece as a plain string, with no colour codes. Print it in a log, a `--help` screen or a file.
```ts
import { still } from "ascii.rest/terminal";
console.log(await still("donut"));
```
The frame is the one the piece shows when it can't move, set by its `meta.still`. For most pieces that is the first frame. A typed banner shows every letter.
A scene comes out at its full size, two rows of its picture on each line of text, so it can be wider than your terminal.
| option | what it does | default |
| --- | --- | --- |
| `light` | for a light background: a shaded piece flips its shading | `false` |
| `options` | the piece's own options | the piece's defaults |
For example, a `--help` screen with a picture at the top:
```ts
import { still } from "ascii.rest/terminal";
if (process.argv.includes("--help")) {
console.log(await still("rust"));
console.log("usage: my-cli ");
}
```
## Next
- [cli](https://ascii.rest/docs/cli/): every command and flag of `npx ascii.rest`.
- [banners](https://ascii.rest/docs/banners/): every banner option, with examples of fonts, shadows, colours and effects.
- [api](https://ascii.rest/docs/api/): every export of the package, with its types.
---
# your own pieces
URL: https://ascii.rest/docs/pieces/
Write your own animation as a piece, then play it in React, on any web page, as an SVG or in a terminal.
A **piece** is one animation, like `donut` or `rust`. You can write your own in a few lines of TypeScript. It then plays in React, on any web page, as an SVG and in a terminal, the same way the library's pieces do.
(A video on this page shows this: Writing a new piece, orbit, and playing it.)
Here is a whole piece: a line that turns, then the word "loading". Save it as `pieces/spinner.ts`:
```ts
// pieces/spinner.ts
import type { Frame, Meta } from "ascii.rest";
export const meta = {
name: "spinner",
category: "ui",
note: "a line turning while something loads",
cols: 10,
rows: 1,
fps: 6,
} satisfies Meta;
export default function spinner(): Frame {
return (t) => `${"|/-\\"[Math.floor(t * 3) % 4]} loading `;
}
```
It draws these four frames in turn, a third of a second each, then starts again:
```text
| loading
/ loading
- loading
\ loading
```
Play it in React:
```tsx
"use client";
import { Ascii } from "ascii.rest/react";
import * as spinner from "./pieces/spinner";
export function Spinner() {
return ;
}
```
The rest of this page explains each part, then shows how to play your piece everywhere else.
## A fuller example: orbit
`orbit` draws a planet going round its sun. It takes one option, `speed`. It draws its track once, when it starts, and then only moves the planet. Save it as `pieces/orbit.ts`:
```ts
// pieces/orbit.ts
import type { Frame, Meta } from "ascii.rest";
export const meta = {
name: "orbit",
category: "space",
note: "a planet going round its sun",
cols: 21,
rows: 9,
fps: 30,
loop: 4,
options: { speed: 1 },
} satisfies Meta<{ speed: number }>;
export default function orbit({ speed = meta.options.speed } = {}): Frame {
const { cols, rows } = meta;
// The cell at an angle on the orbit: up to 9 columns either side of the sun, and 4 rows.
const at = (angle: number) => [Math.round(10 + 9 * Math.cos(angle)), Math.round(4 + 4 * Math.sin(angle))];
// Done once, out here: the sun and the dotted track never move.
const track = Array.from({ length: rows }, () => new Array(cols).fill(" "));
for (let a = 0; a < 2 * Math.PI; a += 0.01) {
const [x, y] = at(a);
track[y][x] = "·";
}
track[4][10] = "*";
// The frame at t seconds. At speed 1 the planet goes round once every 4 seconds.
return (t) => {
const grid = track.map((row) => [...row]);
const [x, y] = at((t / 4) * speed * 2 * Math.PI);
grid[y][x] = "O";
return grid.map((row) => row.join("")).join("\n");
};
}
```
At 0 seconds it draws this. One second later the planet is at the bottom, and after 4 seconds it is back here.
```text
·········
···· ····
·· ··
·· ··
· * O
·· ··
·· ··
···· ····
·········
```
## What a piece is made of
A piece is a module with two exports:
| export | what it is |
| --- | --- |
| `meta` | Facts about the piece: its name, its size in characters and how many frames a second it plays. |
| `default` | A function that takes the piece's options and returns `frame`. |
`frame(t)` returns the picture at `t` seconds. A **frame** is that picture: one string of `meta.rows` lines, joined with `"\n"`. Every line is exactly `meta.cols` characters long. Pad short lines with spaces.
There are two functions for a reason:
- The outer function runs once, when the piece starts. Do slow work there. `orbit` draws its track there. `spinner` has nothing to set up, so its outer function only returns `frame`.
- The inner `frame` runs for every frame, up to `fps` times a second. Draw only what moves.
Use characters that are one cell wide: ASCII, `·`, and the box and block characters like `─ │ ┌ █ ░`. Emoji and other wide characters break the grid.
A **player** is anything that plays a piece: ``, ``, `mount()`, `svg()` or `play()`. They all call your two functions the same way, so a piece written as this page says plays in all of them.
`import * as orbit` gives you an object with `meta` and `default`. Any object of that shape is a piece, so you can also write one inline, typed as `Piece` from `ascii.rest`.
## Fill in its meta
`meta` tells every player how to show your piece. The first six fields are required.
| field | what it does | default |
| --- | --- | --- |
| `name` | The name people see, in lower case: `"newton's cradle"`. In the library, the piece's name, with dashes, comes from its file name instead: `newtons-cradle`. | required |
| `category` | Its group. One of `scenes`, `shapes`, `space`, `physics`, `nature`, `creatures`, `objects`, `generative`, `effects`, `ui`, `data`, `type`, `logos`, `companies`, `distros`. | required |
| `note` | One lower-case line, up to 72 characters, saying what you see. | required |
| `cols` | Its width in characters. Every line of every frame is this long. | required |
| `rows` | Its height in lines. Every frame has this many lines. | required |
| `fps` | Frames a second. `0` makes a still picture, drawn once. | required |
| `options` | The default value of each option your function takes. See [give it options](#give-it-options). | none |
| `loop` | For a piece that repeats exactly: how many seconds one repeat takes. An SVG of it plays that long, then starts again. | none |
| `still` | The moment, in seconds, to show when it can't move: for readers who prefer reduced motion, and for `still()` in a terminal. | `0` |
| `clock` | Set it to `true` if the picture shows the real time or date. | `false` |
| `palette` | 2 to 64 colours, each as `#rrggbb`. A piece with a palette is a coloured piece: it draws in colour on a ``. See [draw it in colour](#draw-it-in-colour). | none |
| `ground` | The colour behind a coloured piece, as `#rrggbb`, on a canvas and in a terminal. Without it, the page shows through. | none |
| `cell` | How tall a cell is compared to its width, on a canvas and in an SVG. `2` is a character's shape. `1` makes square cells. | `2` |
## Give it options
Options let people change your piece without editing it, like `orbit`'s `speed`. Set them up in two places:
1. List each option's default in `meta.options`: `options: { speed: 1 }`.
2. Take each option in the default function, with the same default: `function orbit({ speed = meta.options.speed } = {})`.
Every player starts from the defaults in `meta.options`, puts the options it was given on top, and calls your function with the result. The default in the function covers a direct call, like `orbit.default()`.
People pass options like this:
| where | how |
| --- | --- |
| React | ` ` |
| `mount()` | `mount(el, orbit, { speed: 2 })` |
| `` | the `options` attribute, as JSON: `options='{"speed":2}'` |
| `svg()` | `svg(orbit, { options: { speed: 2 } })` |
| `play()` | `play(orbit, { options: { speed: 2 } })` |
Don't name an option `fps` or `motion`. `mount()`, `` and `` use those two names for themselves.
## Draw it in colour
A piece can colour each cell on its own. Give it a `palette`, then write a palette index for each cell into `env.color`. `env` is the second argument of `frame(t, env)`.
| `env` field | what it is |
| --- | --- |
| `color` | A `Uint8Array` with one number for each cell, `cols * rows` in all, row by row. Write each cell's palette index into it: the cell at column `x`, row `y` is `color[y * cols + x]`. It is `undefined` when the piece is drawn in one colour. |
| `paper` | `true` when the piece is drawn dark on a light background. Use it to pick colours that show up there. |
This is `orbit` in colour. Its palette holds three colours for a light page, then the same three for a dark page. `paper` picks the half.
```ts
// pieces/orbit-color.ts
import type { Frame, Meta } from "ascii.rest";
export const meta = {
name: "orbit in colour",
category: "space",
note: "a blue planet going round a yellow sun",
cols: 21,
rows: 9,
fps: 30,
loop: 4,
// The track, the sun and the planet: three colours for a light page, then three for a dark one.
palette: ["#6e7781", "#9a6700", "#0969da", "#8b949e", "#e3b341", "#58a6ff"],
} satisfies Meta;
export default function orbitColor(): Frame {
const { cols, rows } = meta;
const at = (angle: number) => [Math.round(10 + 9 * Math.cos(angle)), Math.round(4 + 4 * Math.sin(angle))];
const track = Array.from({ length: rows }, () => new Array(cols).fill(" "));
for (let a = 0; a < 2 * Math.PI; a += 0.01) {
const [x, y] = at(a);
track[y][x] = "·";
}
track[4][10] = "*";
return (t, { paper = false, color } = {}) => {
const grid = track.map((row) => [...row]);
const [px, py] = at((t / 4) * 2 * Math.PI);
grid[py][px] = "O";
// Only when it is drawn in colour: a palette index for every cell, row by row.
if (color) {
const half = paper ? 0 : 3; // paper is true on a light page: use the first three colours
for (let y = 0; y < rows; y++)
for (let x = 0; x < cols; x++) color[y * cols + x] = half + Math.max(0, "·*O".indexOf(grid[y][x]));
}
return grid.map((row) => row.join("")).join("\n");
};
}
```
Where colour shows:
- `` and `` see the `palette` and draw on a ``. With `mount()`, pass it a `` yourself.
- In a ``, or with `mono`, the piece is text in one colour and `env.color` is `undefined`. So draw the same text either way, and only write colours when `color` is there.
- `svg()` draws in colour. So does `play()`, unless you pass `mono: true`.
Where `paper` comes from:
| where it plays | `paper` is `true` when |
| --- | --- |
| a `` or `` on a page | the element's text colour is dark. On a canvas, a piece with a `ground` goes by that instead: `paper` is true when the ground is light. |
| `svg()` | you don't pass `dark: true` |
| `play()` in a terminal | you pass `light: true`. A coloured piece with a `ground`, played in colour, goes by its ground instead. |
`paper` matters for text pieces too. On a dark page a dense character like `@` looks bright. On a light page it looks dark. So a piece that shades with characters, from a light `.` to a dense `@`, reverses that order when `paper` is true. `donut` does it like this: `RAMP[paper ? RAMP.length - 1 - i : i]`.
## Keep it a function of time
Played twice from the start, a piece must draw the same frames both times. A frame that only reads `t`, like `orbit`'s, does this on its own.
This matters because players call `frame(t)` when they need to. `mount()` pauses when the piece is off screen. It holds one frame for readers who prefer reduced motion. `svg()` samples 15 frames a second to make its loop.
Three rules keep it true:
1. Don't call `Math.random()`. Make random numbers from a seed instead, once, in the outer function.
2. Don't read the date or time, unless the piece shows it. Then set `clock: true` in `meta`.
3. A simulation may keep state between frames and step it forward as `t` grows, as `doom-fire` does. Seed its randomness too.
This piece puts stars in random places, from a seed:
```ts
// pieces/twinkle.ts
import type { Frame, Meta } from "ascii.rest";
export const meta = {
name: "twinkle",
category: "space",
note: "stars that twinkle",
cols: 30,
rows: 6,
fps: 10,
} satisfies Meta;
// Random numbers from a seed: the same seed gives the same numbers, every time.
const mulberry32 = (a: number) => () => {
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
export default function twinkle(): Frame {
const { cols, rows } = meta;
// Chosen once, from the seed: where each star is, and when it twinkles.
const random = mulberry32(7);
const stars = Array.from({ length: 24 }, () => ({
x: Math.floor(random() * cols),
y: Math.floor(random() * rows),
phase: random() * 2 * Math.PI,
}));
// The frame only reads t, so the same t always gives the same picture.
return (t) => {
const grid = Array.from({ length: rows }, () => new Array(cols).fill(" "));
for (const s of stars) grid[s.y][s.x] = Math.sin(3 * t + s.phase) > 0.5 ? "*" : "·";
return grid.map((row) => row.join("")).join("\n");
};
}
```
## TypeScript notes
Two things to know when you type a piece.
Write `satisfies Meta` after `meta`, as the examples on this page do, rather than `const meta: Meta = ...`. It checks every field and keeps the exact values, so `meta.options.speed` is a `number`. With `: Meta`, TypeScript says `meta.options` is possibly `undefined`.
To describe the options with an `interface`, add `[key: string]: unknown;` to it, as the library's pieces do. Without that line, `Meta` doesn't compile. A `type`, or an inline type like `orbit`'s, works as it is.
## Play it in React or Next.js
`` takes your piece module like any other. Its props are on [react](https://ascii.rest/docs/react/).
```tsx
"use client";
import { Ascii } from "ascii.rest/react";
import * as orbit from "./pieces/orbit";
import * as orbitColor from "./pieces/orbit-color";
export function Orbits() {
return (
<>
>
);
}
```
In the Next.js app router, keep the `"use client"` line. A server component can't pass a piece module to ``, because the module holds a function. Put the component in its own file, like this one, then use ` ` from any page. Other React apps ignore the line. More on [next.js](https://ascii.rest/docs/nextjs/).
## Play it on any web page
There are three ways, depending on how your page is built.
### With mount()
`mount()` plays a piece in an element you choose. Use a `` for text and a `` for colour.
```ts
import { mount } from "ascii.rest";
import * as orbit from "./pieces/orbit";
import * as orbitColor from "./pieces/orbit-color";
// As text, in the pre's own colour.
const stop = mount(document.querySelector("pre")!, orbit, { speed: 2 });
// In colour.
mount(document.querySelector("canvas")!, orbitColor);
// Later, to stop the first one:
stop();
```
It plays only while the element is on screen and the tab is open. For readers who prefer reduced motion, it holds one frame. More on [typescript](https://ascii.rest/docs/typescript/).
### In Astro
The Astro `` component only takes the name of a library piece. For your own, call `mount()` in a script:
```astro
---
// src/pages/index.astro
---
```
### With the ascii-art tag
`` loads a piece from a URL with `src`:
```html
```
- The file must be JavaScript, because the browser loads it as it is. Serve your build's output, or write the piece in plain JavaScript: the same code without `import type`, `satisfies` and the type annotations. [html](https://ascii.rest/docs/html/#play-your-own-piece) has an example.
- `src` works like a link: a relative URL starts from the page's own URL.
- A file on another site must allow cross-origin requests (CORS).
- `piece="orbit"` doesn't work: `piece` only takes the names of library pieces.
## Make an SVG of it
`svg()` turns your piece into an animated SVG file. An SVG plays where scripts can't run, like a GitHub README.
```ts
// make-svg.ts
import { writeFileSync } from "node:fs";
import { svg } from "ascii.rest/svg";
import * as orbit from "./pieces/orbit";
writeFileSync("orbit.svg", svg(orbit)); // for a light page
writeFileSync("orbit.dark.svg", svg(orbit, { dark: true })); // for a dark page
```
To run it with `node make-svg.ts`, write the import as `"./pieces/orbit.ts"`. Node runs a `.ts` file as it is from Node 22.18, or 23.6 in Node 23. It needs the file extension in the import. A bundler finds the file without it.
The SVG plays one loop, then starts again. A piece with `fps: 0` makes a still SVG instead. `svg()` picks the loop's length from the first of these that applies:
1. `meta.loop`, in seconds.
2. For a piece in the `logos` or `companies` category, the time between its glints. For `distros`, the time between its scans.
3. 4 seconds.
Make `loop` the time your piece takes to come back to where it started, so the SVG doesn't jump. If you change an option that changes that time, pass `seconds` too: `svg(orbit, { options: { speed: 0.5 }, seconds: 8 })`.
Every option of `svg()` is on [svg](https://ascii.rest/docs/svg/). To show the two files in a README, see [github readme](https://ascii.rest/docs/readme/).
## Play it in a terminal
`play()` plays your piece in a terminal, for a CLI's splash screen:
```ts
// splash.ts
import { play } from "ascii.rest/terminal";
import * as orbit from "./pieces/orbit";
await play(orbit, { seconds: 3 });
```
It plays for 3 seconds, or until a key is pressed, then puts the terminal back. A coloured piece plays in its dark colours, or its light ones with `light: true`. As with the SVG script, use `"./pieces/orbit.ts"` to run it with Node directly. More on [terminal](https://ascii.rest/docs/terminal/).
## Start from one of ours
Every piece's page shows its source, for example [donut](https://ascii.rest/donut/). To get a copy you can edit, run:
```sh
npx ascii.rest add donut
```
Then change its characters, its colours or its speed. Keep the rules on this page and it still plays everywhere. See [your own copy](https://ascii.rest/docs/copy/).
## Add it to the library
To share your piece with everyone, add it to [the repo](https://github.com/bas3line/ascii) in a pull request.
You need Node 23.6 or later. The repo's scripts are `.ts` files that Node runs with no build step, and [CONTRIBUTING](https://github.com/bas3line/ascii/blob/main/CONTRIBUTING.md) sets 23.6 as the lowest version for that. The repo's CI runs Node 24.
1. Clone the repo and install it:
```sh
git clone https://github.com/bas3line/ascii
cd ascii
npm install
```
2. Save your piece as `src/pieces/orbit.ts`. The file name, without `.ts`, becomes the piece's name, with dashes: `orbit`, or `night-coast`. Use only lower-case letters and digits, with single dashes between words.
3. Change its import to `import type { Frame, Meta } from "../types.ts";`. The library's pieces import their types from there.
4. Add it to the library's lists:
```sh
npm run gen
```
5. Check it, and print its frames at 0, 1, 2.5 and 5 seconds:
```sh
npm run check -- orbit --show
```
6. Check the types:
```sh
npm run typecheck
```
7. See it on a page. Start the site, then open `http://localhost:4321/orbit/`:
```sh
cd site
npm install
npm run dev
```
8. Open a pull request with one piece in it. Paste the frames from step 5.
### What npm run check fails
`npm run check` reads the file, then plays the piece twice from 0 to 6 seconds at its `fps` (every half second when `fps` is 0). It looks at the frames at 0, 1, 2, 3, 4 and 6 seconds. It also plays every piece with `paper: true`. A piece with a `palette` is played once more with no `env.color`, as in a ``, and those frames must pass the size and character rules too.
It fails a piece when any of these is true:
| rule | it fails when |
| --- | --- |
| file name | The file name isn't lower-case letters and digits with single dashes between words, like `orbit` or `night-coast`. |
| no imports | The file has an `import` that isn't `import type`. |
| no browser | The file has the word `document`, `window`, `globalThis`, `navigator`, `localStorage` or `requestAnimationFrame` anywhere, even in a comment. |
| no `Math.random` | The file has `Math.random` anywhere. |
| it loads | The file throws when it is imported. |
| exports | There is no `meta` object, or no default function. |
| `name` | It is empty, or not a string. |
| `category` | It isn't one of the 15 in [fill in its meta](#fill-in-its-meta). |
| `note` | It is empty, or over 72 characters. |
| size | `cols` isn't a whole number from 1 to 80, or `rows` from 1 to 32. A scene (`category: "scenes"`) can be up to 320 by 120. |
| `fps` | It isn't a whole number from 0 to 60. |
| `options` | It is set, but isn't an object. |
| `palette` | It is set, but isn't 2 to 64 colours, each as `#rrggbb`. |
| `ground` | It is set, but isn't `#rrggbb`. |
| `cell` | It is set, but isn't `1` or `2`. |
| scene colours | A scene has no `palette`, or no `ground`. |
| it runs | The default function or a frame throws, or a frame isn't a string. |
| exact size | A frame isn't exactly `rows` lines of `cols` characters. |
| characters | A frame has a character that isn't printable ASCII, `·`, `°`, or a box drawing or block character (U+2500 to U+259F). A scene may also use `•` and `●`. |
| first frame | The frame at 0 seconds is nearly blank: under 3 characters that aren't spaces, and under 2% of its cells. |
| moves | `fps` is above 0, but there are fewer than 3 different frames among the 6 it looks at. |
| holds still | `fps` is 0, but the frame changes. A piece with `clock: true` is let off. |
| same frames | The two plays give different frames, or different colours. A piece with `clock: true` is let off. |
| palette index | A frame writes a colour index into `env.color` that is past the end of the `palette`. |
| a scene's colours | A scene's first frame uses fewer than 4 palette colours on cells that aren't spaces. |
| fast | A frame takes over 4 ms on average, or the slowest takes over 30 ms. A scene gets 10 ms and 40 ms. |
The checker checks only these rules. A reviewer also looks at the frames: the piece should be easy to recognise, move the way its subject would, and loop without a visible jump. [CONTRIBUTING](https://github.com/bas3line/ascii/blob/main/CONTRIBUTING.md) has the rest. Your piece is released under the MIT licence.
## Next
- [your own copy](https://ascii.rest/docs/copy/): copy a library piece into your project and change it.
- [react](https://ascii.rest/docs/react/): every prop of ``.
- [svg](https://ascii.rest/docs/svg/): every option of `svg()`, and serving SVGs.
---
# api
URL: https://ascii.rest/docs/api/
Every function, component and type the ascii.rest package exports, with its signature and the page that shows it in use.
This page lists everything you can import from the `ascii.rest` npm package, grouped by import path. Each entry gives its signature, one line on what it does, and the page that shows it in use.
A few words come up everywhere:
- A **piece** is one animation, like `donut`: a module with `meta` and a `default` function.
- A **frame** is the picture at one moment, as a string of text.
- A **text piece** is plain text in your page's colour, drawn in a ``.
- A **coloured piece** (the scenes, logos, companies and distros) has colours of its own and is drawn on a ``.
The shortest working example plays a piece in a ``:
```ts
import { mount } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
const stop = mount(document.querySelector("#art")!, donut);
```
`stop()` stops it. If you use an AI coding agent, this page is also in [llms-full.txt](https://ascii.rest/llms-full.txt), with every other docs page.
## Pick an import path
Each import path is one part of the package. Import only the ones you use.
| import | what it gives you | runs in | guide |
| --- | --- | --- | --- |
| `ascii.rest` | `mount()`, plus `load`, `names`, `isPiece` and `canvas` to find a piece by name | a browser, for `mount()`; the rest anywhere | [typescript](https://ascii.rest/docs/typescript/) |
| `ascii.rest/pieces` | every piece, as named exports | anywhere | [your own pieces](https://ascii.rest/docs/pieces/) |
| `ascii.rest/pieces/` | one piece, and the type of its options | anywhere | [typescript](https://ascii.rest/docs/typescript/) |
| `ascii.rest/banner` | `banner()`: any text in block letters, as a piece | anywhere | [banners](https://ascii.rest/docs/banners/) |
| `ascii.rest/react` | the `` and `` components | React 18 or later | [react](https://ascii.rest/docs/react/) |
| `ascii.rest/astro` | the `Ascii` Astro component | Astro | [astro](https://ascii.rest/docs/astro/) |
| `ascii.rest/astro/banner` | the `Banner` Astro component | Astro | [astro](https://ascii.rest/docs/astro/) |
| `ascii.rest/element` | the `` and `` tags | a browser | [html](https://ascii.rest/docs/html/) |
| `ascii.rest/svg` | any piece or banner as an animated SVG | anywhere | [svg](https://ascii.rest/docs/svg/) |
| `ascii.rest/terminal` | a piece or a banner in a terminal | Node | [terminal](https://ascii.rest/docs/terminal/) |
The package has no dependencies. React is an optional peer dependency, needed only for `ascii.rest/react`. Everything is typed.
## ascii.rest
The main import: play a piece in an element, and find any piece by its name. Guide: [typescript](https://ascii.rest/docs/typescript/).
```ts
import { canvas, isPiece, load, mount, names } from "ascii.rest";
console.log(names.length); // 217
const name = new URLSearchParams(location.search).get("piece") ?? "donut";
if (isPiece(name)) {
const el = document.createElement(canvas.has(name) ? "canvas" : "pre");
document.body.append(el);
mount(el, await load[name]());
}
```
### mount()
`mount(el: HTMLElement, piece: Piece | Piece["default"], options?: MountOptions): () => void`
Plays a piece in `el` and returns a function that stops it. In a ``, any piece is text in the ``'s colour. On a ``, a coloured piece is drawn in its own colours.
It plays only while `el` is on screen and the tab is visible. For a reader who prefers reduced motion it holds one still frame, unless you pass `motion: true`. `piece` can also be a bare function that returns a frame function. Guide: [typescript](https://ascii.rest/docs/typescript/).
### load
`const load: Record Promise>`
One function for each piece, by name. `await load["night-coast"]()` imports that piece's module. A bundler puts each piece in its own file, so a page downloads only the pieces it plays.
### names
`const names: PieceName[]`
The names of every piece, like `"night-coast"`, in alphabetical order.
### isPiece()
`isPiece(name: string): name is PieceName`
Returns `true` when a string is a piece's name. In TypeScript it also narrows the string to `PieceName`. A piece's name has dashes: `isPiece("night-coast")` is `true` and `isPiece("nightCoast")` is `false`.
### canvas
`const canvas: ReadonlySet`
The names of the coloured pieces: the scenes, logos, companies and distros. Draw these on a `` to see their colours.
### Types from ascii.rest
| type | what it is |
| --- | --- |
| `Piece` | A piece module: `{ meta: Meta; default(options?: Partial): Frame }`. |
| `Meta` | What a piece says about itself: `name`, `category`, `note`, `cols`, `rows`, `fps`, and optionally `options`, `palette`, `ground`, `cell`, `clock`, `loop` and `still`. Each field is explained on [your own pieces](https://ascii.rest/docs/pieces/#fill-in-its-meta). |
| `Frame` | A frame function: `(t: number, env?: Env) => string`. It returns the frame at `t` seconds. |
| `Env` | The frame function's second argument: `{ paper?: boolean; color?: Uint8Array }`. Set `paper` for dark text on a light page. Pass `color` to get each cell's index into `meta.palette`. |
| `Options` | Any piece's options: `Record`. |
| `Category` | One of the 15 categories: `"scenes"`, `"shapes"`, `"space"`, `"physics"`, `"nature"`, `"creatures"`, `"objects"`, `"generative"`, `"effects"`, `"ui"`, `"data"`, `"type"`, `"logos"`, `"companies"` or `"distros"`. |
| `PieceName` | Every piece's name, as a union of strings: `"a0"`, `"agentmail"` and so on. |
| `MountOptions` | The third argument of `mount()`: the piece's own options, plus `fps?: number` and `motion?: boolean`. |
## ascii.rest/pieces
Every piece, as a named export. Its export is its name in camelCase: `night-coast` is `nightCoast`, and `rule-30` is `rule30`. Every piece is in [the list on the home page](https://ascii.rest/#pieces). Guide: [your own pieces](https://ascii.rest/docs/pieces/), which explains what a piece module holds.
```ts
import { bigText, donut } from "ascii.rest/pieces";
console.log(donut.meta.cols, donut.meta.rows); // 40 22
console.log(bigText.default({ text: "hi" })(0)); // the frame at 0 seconds
```
Each piece is a module with two exports:
| export | what it is |
| --- | --- |
| `meta` | The piece's `Meta`: its size, frame rate, options and colours. |
| `default(options?)` | Takes the piece's options and returns its `Frame` function. |
### One piece on its own
`ascii.rest/pieces/` is one piece's module, by the piece's name, with dashes: `ascii.rest/pieces/night-coast`. The 113 pieces that take options also export a type for them: the piece's name in PascalCase, plus `Options`. So `big-text` exports `BigTextOptions`, and `spinners` exports `SpinnersOptions`.
```ts
import * as spinners from "ascii.rest/pieces/spinners";
import type { BigTextOptions } from "ascii.rest/pieces/big-text";
const options: Partial = { text: "hello" };
console.log(spinners.meta.options, options);
```
## ascii.rest/banner
Turn any text into a piece in block letters with a drop shadow. The result plays anywhere a piece does: `mount()`, ``, `svg()` and `play()`. Guide: [banners](https://ascii.rest/docs/banners/).
```ts
import { banner, drawable } from "ascii.rest/banner";
const hi = banner("hi", { shadow: "rounded" });
console.log(hi.default()(0));
console.log(drawable("héllo!")); // "hllo!": the font has no "é"
```
The first `console.log` prints:
```text
▓▓╮ ▓▓╮ ▓▓╮
▓▓│ ▓▓│ ▓▓│
▓▓▓▓▓▓▓▓│ ▓▓│
▓▓╭───▓▓│ ▓▓│
▓▓│ ▓▓│ ▓▓│
╰─╯ ╰─╯ ╰─╯
```
### banner()
`banner(text: string, options?: BannerOptions): BannerPiece`
Returns a piece that draws `text` in block letters. Every option is on [banners](https://ascii.rest/docs/banners/#options).
It throws an error, with a message that says what to change, when:
- the font can draw none of `text`, apart from spaces;
- `font` or `effect` is a name it doesn't know, or `shadow` is neither a name it knows nor 16 characters;
- a colour isn't `#rrggbb`, or `color` is an empty list;
- `fill` isn't exactly one character, or `glint.chars` isn't one or two;
- `gap`, `pad`, `pixel`, `size.cols` or `size.rows` isn't a whole number (`gap` and `pad` 0 or more, the others 1 or more);
- `speed`, `max`, `glint.sweep`, `glint.every`, `glint.width` or `type.step` isn't above 0;
- `glint.first` or `glint.slant` isn't a number.
### drawable()
`drawable(text: string, font?: FontName | Font): string`
Returns the characters of `text` that the font can draw, in the case you gave them. `banner()` leaves the rest out. The font is `"block"` by default.
### fonts
`const fonts: { block: Font; slim: Font; tall: Font; bold: Font; round: Font; wide: Font; mixed: Font; italic: Font }`
The eight built-in fonts. `block`, `slim`, `wide` and `italic` are five rows tall, `round` six, `tall` and `bold` seven, and `mixed` nine. `mixed` is `cased`, so it draws lower case too. Each one is shown on [banners](https://ascii.rest/docs/banners/#change-the-font).
### shadows
`const shadows: Record`
The built-in shadow styles, `double`, `single`, `heavy`, `rounded` and `ascii`. Each is a string of 16 line-drawing characters: `shadows.rounded` is `" │││─╯╮┤─╰╭├─┴┬┼"`.
### Types from ascii.rest/banner
| type | what it is |
| --- | --- |
| `BannerOptions` | Every option of `banner()`: `font`, `pixel`, `gap`, `shadow`, `fill`, `effect`, `speed`, `glint`, `type`, `color`, `shadowColor`, `pad`, `size`, `max` and `name`. Each is explained on [banners](https://ascii.rest/docs/banners/#options). |
| `BannerPiece` | A `Piece` with two more fields: `text`, the characters it drew, and `motion`, `{ seconds, from, once, pass? }`. See [BannerPiece motion](#bannerpiece-motion). |
| `Font` | A pixel font of your own: `{ height: number; glyphs: Record; cased?: boolean }`. Each glyph is its rows joined by a pipe character, with `#` for a pixel. Without `cased`, text is drawn in capitals. |
| `FontName` | `"block"`, `"slim"`, `"tall"`, `"bold"`, `"round"`, `"wide"`, `"mixed"` or `"italic"`. |
| `ShadowName` | `"double"`, `"single"`, `"heavy"`, `"rounded"` or `"ascii"`. |
| `Effect` | How a banner moves: `"glint"`, `"type"` or `"still"`. |
| `Colors` | One colour as `#rrggbb`, or an array of two or more for a fade: `string` or `readonly string[]`. |
| `Themed` | `T`, or `{ light?: T; dark?: T }` for one value for each theme. `color` and `shadowColor` take it. |
### BannerPiece motion
A banner's `motion` field says how it moves. `svg()` reads it to play exactly one loop. The terminal `banner()` reads it to play the first glint, or the typing, once.
| `effect` | what `motion` holds |
| --- | --- |
| `"glint"` | `seconds` is the time from one glint to the next. The first glint crosses between the two times in `pass`. `from` is a moment when the glint is out of sight: an SVG's loop starts there, and a banner held still shows it. `once` is `false`. |
| `"type"` | `seconds` is how long the letters take to type in, plus a short pause. `from` is 0 and `once` is `true`. |
| `"still"` | `seconds` and `from` are 0, and `once` is `false`. |
```ts
import { banner } from "ascii.rest/banner";
banner("hi").motion; // { seconds: 3.2, from: 0, once: false, pass: [0.5, 2.9] }
banner("hi", { glint: { first: 0 } }).motion.from; // 2.8: at 0 the glint is in sight
banner("hi", { effect: "type" }).motion; // { seconds: 0.533..., from: 0, once: true }
```
## ascii.rest/react
Two React components. Both are client components (`"use client"`), so they also work in the Next.js app router. Guides: [react](https://ascii.rest/docs/react/) and [next.js](https://ascii.rest/docs/nextjs/).
```tsx
import { Ascii, Banner } from "ascii.rest/react";
export default function Page() {
return (
<>
>
);
}
```
### Ascii
`function Ascii(props: AsciiProps): JSX.Element`
Plays a piece. It renders a ``, or a `` for a coloured piece, with `role="img"`. Guide: [react](https://ascii.rest/docs/react/).
| prop | type | what it does |
| --- | --- | --- |
| `piece` | `Piece` or `PieceName` | The piece to play: a module, or a name to load when it mounts. Required. |
| `options` | `MountOptions` | The piece's own options, plus `fps` and `motion`. |
| `label` | `string` | What the picture shows, for screen readers. The piece's name by default. |
| `mono` | `boolean` | Draws a coloured piece as text in one colour, in a ``. |
| `className` | `string` | A class for the `` or ``. |
| `style` | `CSSProperties` | Inline styles for the `` or ``. |
### Banner
`function Banner(props: BannerProps): JSX.Element | null`
Draws any text as a banner. Every option of `banner()` is a prop. If `banner()` throws, it renders nothing, and the console says why with `console.warn`: ` could not draw: ...`. Guide: [react](https://ascii.rest/docs/react/).
| prop | type | what it does |
| --- | --- | --- |
| `text` | `string` | The text. Required. |
| every `BannerOptions` field | see [banners](https://ascii.rest/docs/banners/#options) | `color`, `shadow`, `font`, `effect` and the rest. |
| `label` | `string` | What the picture shows, for screen readers. The text by default. |
| `mono` | `boolean` | Draws a coloured banner as text in one colour, in a ``. |
| `fps` | `number` | Overrides its frame rate. `0` is ignored: for a banner that doesn't move, use `effect="still"`. |
| `className` | `string` | A class for the `` or ``. |
| `style` | `CSSProperties` | Inline styles for the `` or ``. |
### Types from ascii.rest/react
`AsciiProps` and `BannerProps` are the props above. `BannerOptions`, `MountOptions`, `Piece` and `PieceName` are exported here too, so one import is enough.
## ascii.rest/astro
Two Astro components, each the default export of its own path. For a text piece or banner, a still frame is rendered on the server, so the page shows it before any script runs. A coloured one keeps its space until its canvas draws. Guide: [astro](https://ascii.rest/docs/astro/).
```astro
---
import Ascii from "ascii.rest/astro";
import Banner from "ascii.rest/astro/banner";
---
```
### Ascii
`import Ascii from "ascii.rest/astro"`
Plays a piece. It renders an `` tag.
| prop | type | what it does |
| --- | --- | --- |
| `piece` | `PieceName` | The piece's name. It takes a name only, not a module. An unknown name throws when the page renders. Required. |
| `options` | `MountOptions` | The piece's own options, plus `fps` and `motion`. |
| `fps` | `number` | Overrides its frame rate. |
| `label` | `string` | What the picture shows, for screen readers. The piece's name by default. |
| `mono` | `boolean` | Draws a coloured piece as text in one colour. |
| `class` | `string` | A class for the `` tag. |
### Banner
`import Banner from "ascii.rest/astro/banner"`
Draws any text as a banner. It renders an `` tag. If `banner()` throws, the error is thrown when the page renders.
| prop | type | what it does |
| --- | --- | --- |
| `text` | `string` | The text. Required. |
| every `BannerOptions` field | see [banners](https://ascii.rest/docs/banners/#options) | `color`, `shadow`, `font`, `effect` and the rest. |
| `label` | `string` | What the picture shows, for screen readers. The text by default. |
| `mono` | `boolean` | Draws a coloured banner as text in one colour. |
| `class` | `string` | A class for the `` tag. |
This `Banner` has no `fps` prop.
## ascii.rest/element
Importing this module defines two HTML tags, `` and ``. On a server, where there is no DOM, it does nothing. The script `https://ascii.rest/ascii.js` is the same module, for a page with no build step. Guide: [html](https://ascii.rest/docs/html/).
```ts
import "ascii.rest/element";
const art = document.createElement("ascii-art"); // typed as AsciiArt
art.setAttribute("piece", "donut");
document.body.append(art);
```
### AsciiArt
`class AsciiArt extends HTMLElement`
The `` tag. Its attributes are `piece`, `src`, `fps`, `options`, `label` and `mono`. An unknown `piece` draws nothing and logs nothing. Guide: [html](https://ascii.rest/docs/html/).
### AsciiBanner
`class AsciiBanner extends HTMLElement`
The `` tag. Its attributes are `text`, `font`, `shadow`, `fill`, `effect`, `speed`, `color`, `shadow-color`, `pixel`, `gap`, `options`, `label` and `mono`. An empty attribute counts as no attribute, except `mono`, which is on whenever it is there. If `banner()` throws, the tag draws nothing, and the console says why with `console.warn`: ` could not draw: ...`. Guide: [html](https://ascii.rest/docs/html/).
### define()
`define(tag?: string): void`
Defines `` under the name `tag`, and ``, if they aren't defined yet. `tag` is `"ascii-art"` by default. Importing the module already calls `define()`, so call it yourself only to add another name, like `define("my-art")`.
The module also tells TypeScript about both tags, so `document.querySelector("ascii-art")` has the type `AsciiArt | null`.
## ascii.rest/svg
Turn any piece, or a banner, into an animated SVG string. An SVG plays where scripts can't run, like a GitHub README or an ` `. It works in a browser, in Node, in a Worker or at build time. Guides: [svg](https://ascii.rest/docs/svg/) and [github readme](https://ascii.rest/docs/readme/).
Most people only need `svg()` and `bannerSvg()`. The rest, from `part()` on, is for building your own SVG.
```ts
import { writeFileSync } from "node:fs";
import { bannerSvg, svg } from "ascii.rest/svg";
import { rust } from "ascii.rest/pieces";
writeFileSync("rust.svg", svg(rust));
writeFileSync("rust.dark.svg", svg(rust, { dark: true }));
writeFileSync("banner.svg", bannerSvg("my-project", { art: rust, color: "art", tagline: "fast, safe, fun" }));
```
### svg()
`svg(piece: Piece, options?: SvgOptions): string`
Returns one loop of a piece as an animated SVG, on the piece's `meta.ground` when it has one, as a scene does. It throws for an `ink` that isn't `#rrggbb`, a `scale` that isn't a number of 0 or more, or an `fps` outside 1 to 60. Guide: [svg](https://ascii.rest/docs/svg/).
### bannerSvg()
`bannerSvg(text: string, options?: Omit & { color?: BannerSvgColor } & BannerSvgOptions): string`
Returns a banner as an animated SVG, with an optional tagline under it, a piece beside or above it, and a background. Its options are every option of `banner()`, plus `BannerSvgOptions`. Its `color` also takes `"art"`, the art's own colour. Guide: [svg](https://ascii.rest/docs/svg/).
It throws when `banner()` would, and also when:
- `background` or `taglineColor` isn't `#rrggbb`, for either theme, whether or not it is used;
- `taglineSize`, `spacing`, `padding`, `radius`, `artSize` or `scale` isn't a number of 0 or more;
- `place` isn't `"left"`, `"right"`, `"above"` or `"below"`, or `align` isn't `"start"`, `"center"` or `"end"`;
- `fps` is outside 1 to 60;
- `art` is `{ svg }` with an SVG that `ascii.rest/svg` didn't write.
### loopOf()
`loopOf(piece: Piece, options?: Options): { every: number; from: number; once?: boolean }`
The loop `svg()` plays: `every`, its length in seconds, and `from`, the time it starts. It is the first of these that applies:
1. a banner's own `motion` (see [BannerPiece motion](#bannerpiece-motion));
2. `every: 0`, for a still piece, one whose `meta.fps` is 0;
3. the piece's `meta.loop`;
4. for a logo, company or distro, the time between its glints or scans;
5. 4 seconds.
### part()
`part(loop: Loop & { cell?: number }, prefix?: string): Part`
Samples a `Loop` into frames and returns them as a `Part`, with its CSS class names starting with `prefix`. It throws for an `fps` outside 1 to 60. Use it to put frames of your own in an SVG.
### wrap()
`wrap(part: Part, label: string, scale?: number): string`
Puts a `Part` in a complete ``, titled `label` for screen readers. `scale` is the pixels per cell width, 10 by default.
### namespaced()
`namespaced(svg: string, prefix: string): Part | null`
Turns an SVG that this module wrote back into a `Part`, with its class names, and its fade's gradient, under `prefix`. Use it to put two in one SVG, or two inline in one HTML page. It returns `null` for any other SVG.
### inkOf()
`inkOf(part: Part): string | null`
The colour that inks the most cells of a part's first frame, such as a logo's main colour. `bannerSvg()` uses it for `color: "art"`.
### darkColor()
`darkColor(hex: string): boolean`
Returns `true` when a `#rrggbb` colour is dark. `bannerSvg()` uses it to pick light or dark colours for a `background`.
### MONO and FACES
`const MONO: string` and `const FACES: string`
`MONO` is the CSS rule that every part's text rows share. `part()` leaves it out of a part's `css`, and `wrap()` adds it. Add it to your SVG's `