SantiagoSchez/tanim

Endless procedural screensaver animations for your terminal: 20+ pixel-art scenes in true color from a single Rust binary, also playable in the browser via WebAssembly.

Rust

0

12 commits

updated Sep 28, 2026

See the code

See what people are saying

SourceMessageScoreDate

tanim: screensaver animations for the terminal (r/commandline)

Hi. I made a few screensaver-style animations for the terminal (Rust + WebAssembly): a roguelike dungeon that explores itself, an aquarium, fire, the After Dark flying toasters and a couple dozen more. They're drawn with half blocks in 24-bit color, and you can zoom in and pan with the arrow keys…

5

Sep 28, 2026

README

tanim logo: northern lights in a terminal window

tanim

Endless, procedural screensaver animations for your terminal. More than twenty of them, from a self-exploring roguelike dungeon to firing neurons, drawn in true color with half-block pixels, in a single native Rust binary with no dependencies beyond libc.

aquarium: pixel-art fish, bubbles and swaying seaweed dungeon: a roguelike level being explored by a hero

tanim            # a random animation
tanim aurora     # a specific one

Try them in your browser → (the same code, compiled to WebAssembly)

  • Procedural and endless. Nothing is pre-recorded: every run is different, and nothing repeats.
  • Two pixels per cell. Everything is drawn with ▀ half blocks, so each terminal cell holds two square pixels in 24-bit color.
  • Light on the terminal. Only the cells that change are sent, invisible color changes are skipped, and frames are wrapped in synchronized output, so even busy scenes stay smooth. It starts in about a millisecond.
  • Interactive. Zoom into any animation, pan around, skip to the next one.
  • Beyond the terminal. Export the animations as standalone web pages (the same code compiled to WebAssembly), as videos, or as looping GIF screensavers for an Elgato Stream Deck.
aurora
aurora: northern lights over mountains, mirrored in a lake
neurons
neurons: spikes racing down axons and crossing synapses
dog
dog: a dog running, napping and chasing butterflies through the day
metaballs
metaballs: glossy drops of gel that bulge and merge
fireworks
fireworks: shells bursting over a city skyline
pipes
pipes: the classic 3D pipes screensaver

Install

You need a Rust toolchain (rustup).

git clone https://github.com/SantiagoSchez/tanim && cd tanim
make install                    # builds and installs ~/.local/bin/tanim
make install PREFIX=/usr/local  # or anywhere else
make uninstall

For the web export, also add the WebAssembly target before building: rustup target add wasm32-unknown-unknown. Without it, tanim builds and runs the same, just without --export-html.

tanim needs a terminal with 24-bit color: Ghostty, iTerm2, WezTerm, kitty, Alacritty, a recent Terminal.app, and most modern ones.

Usage

tanim                   # a random animation from the catalog
tanim aquarium          # a specific one
tanim aq                # any unique prefix works
tanim --list            # the catalog
tanim --seed 42 aurora  # the exact same run every time
tanim --zoom 2 fire     # start zoomed in
tanim --opaque matrix   # paint the background instead of showing the terminal's own
tanim --export-html     # web pages in html/
KeyAction
EnterSwitch to another random animation
↑ / ↓Zoom in and out, up to 4x, without restarting the animation
W A S DPan while zoomed in
SpaceIn dungeon: build a new level
Q, Esc, Ctrl+CQuit, restoring the terminal as it was

Resizing the window restarts the animation at the new size. The animations with a plain dark background (starfield, galaxy, matrix, life, physarum, neurons and pipes) show the terminal's own background instead, so they sit on top of its transparency or blur. If the terminal can't keep up, tanim drops frames rather than slowing down.

Catalog

Animation
aquariumPixel-art fish of seven species, a shark, crabs, bubbles and swaying seaweed
gardenPlants sprouting, branching and blooming through the seasons
dogA dog running across a meadow, napping and chasing butterflies from dawn to night
dungeonA roguelike level that builds itself (rooms and corridors, or caves) and a hero who explores it, fights and loots
rainA thunderstorm with splashes, puddles and branching lightning
snowSnow settling on pines and roofs under shifting wind
ripplesRaindrops rippling across a pond
starfieldWarp-speed flight through the stars
tunnelFlying down an endless tiled tunnel that bends and rolls
fireworksRockets bursting into colored sparks over the city
galaxyA spiral galaxy with dust lanes, slowly turning
auroraNorthern lights over mountains and a lake
boidsA flock of birds moving as one, until a hawk shows up
matrixCascading digital rain
fireA roaring bonfire
plasmaPsychedelic flowing plasma
lavaA lava lamp with blobs that melt and merge
metaballsGlossy blobs merging like drops of gel
lifeConway's Game of Life, colored by age
physarumSlime mould growing a living transport network
neuronsNeurons under a microscope, firing in cascades across their synapses
sandColored sand falling into dunes
mazeA maze carving itself, then being solved
snakeSelf-playing Snake
tetrisSelf-playing Tetris
pipesThe classic 3D pipes screensaver
toastersFlying toasters in chrome and fifties enamel, popping toast, with a winged coffee mug, jam jar, fried egg and croissant tagging along; after the classic After Dark screensaver (Space: how toasted)
cityA night skyline scrolling by, with traffic and planes

On the web

tanim --export-html [dir] [names...]   # default: html/, every animation

The live demo is exactly this, rebuilt and published by GitHub Actions on every push. It writes index.html, with every animation and a picker (deep links like index.html#aurora work), plus one standalone page per animation. The pages run the very same Rust code compiled to WebAssembly, so they are endless, different on every load, adapt to the window and take the same keys, plus +/− for the cell size and F for fullscreen. Each page is self-contained (about 570 KB) and opens straight from disk, no server needed. On the live demo, the ⋯ button next to each animation downloads it as a Stream Deck screensaver for any model.

Videos and Stream Deck screensavers

The script in scripts/ records animations from tanim --dump and encodes them with ffmpeg. It needs Python with Pillow, numpy and fontTools, plus ffmpeg.

make videos                  # a WebM per animation in videos/ (2160x1296, VP9)
make streamdeck MODELS=neo   # looping GIF screensavers in streamdeck/neo/
python3 scripts/video.py --streamdeck all aurora dungeon

The Stream Deck GIFs come at each model's screensaver resolution: neo (480x320), mk2 (480x272), xl (768x384), plus (800x480) and plusxl (1280x800). The virtual terminal is sized so that every half-block pixel maps to whole pixels in the GIF. Since the Stream Deck app keeps only the first 120 frames of a screensaver, each GIF is at most 8 seconds at 15 fps. Simulations never return to an earlier state, so to make them loop the script records extra footage, picks the start and end that look most alike, and cross-fades the last 0.75 s into the frames just before the start: the GIF begins clean and loops without a jump. Set one from Stream Deck → Preferences → Set Screensaver.

The live demo's GIFs are made by GitHub Actions and kept in the gh-pages-assets branch: on every push to main, scripts/video.py --sync renders every model for new animations and for the ones whose module changed since their GIFs were made (recorded in sources.txt there). Run the workflow by hand to regenerate others, say after changing the rasterizer. The pages look for streamdeck/<model>/<name>.gif next to them and only offer the ones they find.

Development

cargo build --release
cargo test --release
./target/release/tanim --check              # run every animation headless at several sizes, down to 1x1, and time each frame
./target/release/tanim --check fire,maze    # just some
./target/release/tanim --snapshot maze 100x30 500   # frame 500 as text
./target/release/tanim --dump fire 80x24 300 30     # 300 raw frames after 30 of warm-up

The animations and the canvas they draw on are a library (src/lib.rs). The terminal binary plays them; the web/ crate exposes them to JavaScript, and build.rs compiles it to WebAssembly and embeds it in tanim for --export-html. Each animation lives in src/anims/<name>.rs and exposes pub fn new(w, h, rng) -> Box<dyn Animation>, drawing into a cell canvas (usually through a buffer of half-block pixels). To add one, create the module and register it in the catalog! macro in src/anims/mod.rs with its frame rate and description.

Contributing

New animations are the best kind of contribution, and ideas are just as welcome.

  • Have an idea? Open an issue describing what it would show: a scene, a simulation, a classic screensaver you miss.
  • Want to build one? Send a pull request. Add the module and register it as described above, add a row to the catalog, and before opening it make sure the build has no warnings, cargo test --release passes and tanim --check <name> runs it down to 1x1. Keep it light on the terminal: check the B/f column of --check, since bytes sent per frame are what really costs. AGENTS.md has the details on how rendering works and the conventions to follow.
  • Found a bug? Issues are welcome too, ideally with your terminal, its size and the animation.

License

MIT

animation
cli
generative-art
pixel-art
procedural-generation
rust
screensaver
stream-deck
terminal
truecolor
tui
webassembly

SantiagoSchez/tanim

Endless procedural screensaver animations for your terminal: 20+ pixel-art scenes in true color from a single Rust binary, also playable in the browser via WebAssembly.

Rust

0

12 commits

updated Sep 28, 2026

See the code

See what people are saying

SourceMessageScoreDate

tanim: screensaver animations for the terminal (r/commandline)

Hi. I made a few screensaver-style animations for the terminal (Rust + WebAssembly): a roguelike dungeon that explores itself, an aquarium, fire, the After Dark flying toasters and a couple dozen more. They're drawn with half blocks in 24-bit color, and you can zoom in and pan with the arrow keys…

5

Sep 28, 2026

README

tanim logo: northern lights in a terminal window

tanim

Endless, procedural screensaver animations for your terminal. More than twenty of them, from a self-exploring roguelike dungeon to firing neurons, drawn in true color with half-block pixels, in a single native Rust binary with no dependencies beyond libc.

aquarium: pixel-art fish, bubbles and swaying seaweed dungeon: a roguelike level being explored by a hero

tanim            # a random animation
tanim aurora     # a specific one

Try them in your browser → (the same code, compiled to WebAssembly)

  • Procedural and endless. Nothing is pre-recorded: every run is different, and nothing repeats.
  • Two pixels per cell. Everything is drawn with ▀ half blocks, so each terminal cell holds two square pixels in 24-bit color.
  • Light on the terminal. Only the cells that change are sent, invisible color changes are skipped, and frames are wrapped in synchronized output, so even busy scenes stay smooth. It starts in about a millisecond.
  • Interactive. Zoom into any animation, pan around, skip to the next one.
  • Beyond the terminal. Export the animations as standalone web pages (the same code compiled to WebAssembly), as videos, or as looping GIF screensavers for an Elgato Stream Deck.
aurora
aurora: northern lights over mountains, mirrored in a lake
neurons
neurons: spikes racing down axons and crossing synapses
dog
dog: a dog running, napping and chasing butterflies through the day
metaballs
metaballs: glossy drops of gel that bulge and merge
fireworks
fireworks: shells bursting over a city skyline
pipes
pipes: the classic 3D pipes screensaver

Install

You need a Rust toolchain (rustup).

git clone https://github.com/SantiagoSchez/tanim && cd tanim
make install                    # builds and installs ~/.local/bin/tanim
make install PREFIX=/usr/local  # or anywhere else
make uninstall

For the web export, also add the WebAssembly target before building: rustup target add wasm32-unknown-unknown. Without it, tanim builds and runs the same, just without --export-html.

tanim needs a terminal with 24-bit color: Ghostty, iTerm2, WezTerm, kitty, Alacritty, a recent Terminal.app, and most modern ones.

Usage

tanim                   # a random animation from the catalog
tanim aquarium          # a specific one
tanim aq                # any unique prefix works
tanim --list            # the catalog
tanim --seed 42 aurora  # the exact same run every time
tanim --zoom 2 fire     # start zoomed in
tanim --opaque matrix   # paint the background instead of showing the terminal's own
tanim --export-html     # web pages in html/
KeyAction
EnterSwitch to another random animation
↑ / ↓Zoom in and out, up to 4x, without restarting the animation
W A S DPan while zoomed in
SpaceIn dungeon: build a new level
Q, Esc, Ctrl+CQuit, restoring the terminal as it was

Resizing the window restarts the animation at the new size. The animations with a plain dark background (starfield, galaxy, matrix, life, physarum, neurons and pipes) show the terminal's own background instead, so they sit on top of its transparency or blur. If the terminal can't keep up, tanim drops frames rather than slowing down.

Catalog

Animation
aquariumPixel-art fish of seven species, a shark, crabs, bubbles and swaying seaweed
gardenPlants sprouting, branching and blooming through the seasons
dogA dog running across a meadow, napping and chasing butterflies from dawn to night
dungeonA roguelike level that builds itself (rooms and corridors, or caves) and a hero who explores it, fights and loots
rainA thunderstorm with splashes, puddles and branching lightning
snowSnow settling on pines and roofs under shifting wind
ripplesRaindrops rippling across a pond
starfieldWarp-speed flight through the stars
tunnelFlying down an endless tiled tunnel that bends and rolls
fireworksRockets bursting into colored sparks over the city
galaxyA spiral galaxy with dust lanes, slowly turning
auroraNorthern lights over mountains and a lake
boidsA flock of birds moving as one, until a hawk shows up
matrixCascading digital rain
fireA roaring bonfire
plasmaPsychedelic flowing plasma
lavaA lava lamp with blobs that melt and merge
metaballsGlossy blobs merging like drops of gel
lifeConway's Game of Life, colored by age
physarumSlime mould growing a living transport network
neuronsNeurons under a microscope, firing in cascades across their synapses
sandColored sand falling into dunes
mazeA maze carving itself, then being solved
snakeSelf-playing Snake
tetrisSelf-playing Tetris
pipesThe classic 3D pipes screensaver
toastersFlying toasters in chrome and fifties enamel, popping toast, with a winged coffee mug, jam jar, fried egg and croissant tagging along; after the classic After Dark screensaver (Space: how toasted)
cityA night skyline scrolling by, with traffic and planes

On the web

tanim --export-html [dir] [names...]   # default: html/, every animation

The live demo is exactly this, rebuilt and published by GitHub Actions on every push. It writes index.html, with every animation and a picker (deep links like index.html#aurora work), plus one standalone page per animation. The pages run the very same Rust code compiled to WebAssembly, so they are endless, different on every load, adapt to the window and take the same keys, plus +/− for the cell size and F for fullscreen. Each page is self-contained (about 570 KB) and opens straight from disk, no server needed. On the live demo, the ⋯ button next to each animation downloads it as a Stream Deck screensaver for any model.

Videos and Stream Deck screensavers

The script in scripts/ records animations from tanim --dump and encodes them with ffmpeg. It needs Python with Pillow, numpy and fontTools, plus ffmpeg.

make videos                  # a WebM per animation in videos/ (2160x1296, VP9)
make streamdeck MODELS=neo   # looping GIF screensavers in streamdeck/neo/
python3 scripts/video.py --streamdeck all aurora dungeon

The Stream Deck GIFs come at each model's screensaver resolution: neo (480x320), mk2 (480x272), xl (768x384), plus (800x480) and plusxl (1280x800). The virtual terminal is sized so that every half-block pixel maps to whole pixels in the GIF. Since the Stream Deck app keeps only the first 120 frames of a screensaver, each GIF is at most 8 seconds at 15 fps. Simulations never return to an earlier state, so to make them loop the script records extra footage, picks the start and end that look most alike, and cross-fades the last 0.75 s into the frames just before the start: the GIF begins clean and loops without a jump. Set one from Stream Deck → Preferences → Set Screensaver.

The live demo's GIFs are made by GitHub Actions and kept in the gh-pages-assets branch: on every push to main, scripts/video.py --sync renders every model for new animations and for the ones whose module changed since their GIFs were made (recorded in sources.txt there). Run the workflow by hand to regenerate others, say after changing the rasterizer. The pages look for streamdeck/<model>/<name>.gif next to them and only offer the ones they find.

Development

cargo build --release
cargo test --release
./target/release/tanim --check              # run every animation headless at several sizes, down to 1x1, and time each frame
./target/release/tanim --check fire,maze    # just some
./target/release/tanim --snapshot maze 100x30 500   # frame 500 as text
./target/release/tanim --dump fire 80x24 300 30     # 300 raw frames after 30 of warm-up

The animations and the canvas they draw on are a library (src/lib.rs). The terminal binary plays them; the web/ crate exposes them to JavaScript, and build.rs compiles it to WebAssembly and embeds it in tanim for --export-html. Each animation lives in src/anims/<name>.rs and exposes pub fn new(w, h, rng) -> Box<dyn Animation>, drawing into a cell canvas (usually through a buffer of half-block pixels). To add one, create the module and register it in the catalog! macro in src/anims/mod.rs with its frame rate and description.

Contributing

New animations are the best kind of contribution, and ideas are just as welcome.

  • Have an idea? Open an issue describing what it would show: a scene, a simulation, a classic screensaver you miss.
  • Want to build one? Send a pull request. Add the module and register it as described above, add a row to the catalog, and before opening it make sure the build has no warnings, cargo test --release passes and tanim --check <name> runs it down to 1x1. Keep it light on the terminal: check the B/f column of --check, since bytes sent per frame are what really costs. AGENTS.md has the details on how rendering works and the conventions to follow.
  • Found a bug? Issues are welcome too, ideally with your terminal, its size and the animation.

License

MIT

animation
cli
generative-art
pixel-art
procedural-generation
rust
screensaver
stream-deck
terminal
truecolor
tui
webassembly

Languages

Rust

93.1%

Python

4.2%

HTML

2.6%