Metadata-Version: 2.4
Name: glowdrift
Version: 0.1.1
Summary: A generative rainbow flow-field terminal screensaver
Author: E. T. Carter
License: MIT
Project-URL: Homepage, https://github.com/dnvsfn/glowdrift
Project-URL: Source, https://github.com/dnvsfn/glowdrift
Project-URL: Issues, https://github.com/dnvsfn/glowdrift/issues
Project-URL: dnvsfn, https://dnvsfn.com
Keywords: terminal,screensaver,ansi,truecolor,generative,art,tui
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Artistic Software
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: render
Requires-Dist: Pillow; extra == "render"
Dynamic: license-file

# glowdrift

An original full-frame terminal screensaver. Rainbow particles drift through
an organic, slowly-evolving flow field, depositing additive glow that decays
into trails on a dark background. Truecolor, half-block rendering (two pixels
per character row), keyboard-controlled, quits on `q`.

![preview](preview.png)

## Install

**Homebrew (macOS / Linux):**

```sh
brew install dnvsfn/tap/glowdrift
```

**pipx (any platform):**

```sh
pipx install glowdrift               # or:  pip install --user glowdrift
pipx install 'glowdrift[render]'     # + Pillow, for PNG output
```

**From source:**

```sh
git clone https://github.com/dnvsfn/glowdrift
cd glowdrift
make install                 # copies to ~/.local/bin/glowdrift
```

Make sure `~/.local/bin` is on your `PATH` (or `sudo make install PREFIX=/usr/local`).

Live playback is pure Python 3 (3.8+) with **no dependencies**; Pillow is only
needed for the PNG-output flags (`--render`, `--gallery`, `--contact`).

## Run

```sh
glowdrift                       # screensaver: drift between presets (any key quits)
glowdrift --still               # hold one fixed default look instead
glowdrift aurora                # a named preset, held (see below)
glowdrift synthwave --speed 2   # a preset with one knob overridden
glowdrift --random --seed 12    # roll a whole random look from a seed
glowdrift --swirl 0.2 --flow-bias 25              # parallel streams flowing down-right
glowdrift --hue-center 205 --hue-range 70         # limit to a blue/cyan palette
glowdrift --colors 3 --edge 0.9                   # 3 crisp colors, hard-edged strokes
```

## Screensaver mode

With no arguments, glowdrift runs as a screensaver: it slowly drifts from one
look to the next forever, crossfading the flow so the trails carry across each
transition (no hard cuts). Naming a preset or setting any knob instead holds
that single look; `--still` forces the fixed default look.

```sh
glowdrift                                          # the default: drift through the presets
glowdrift --cycle --random                         # drift through endless random rolls
glowdrift --cycle-hold 30 --cycle-fade 12          # linger longer, morph slower
```

## Playing live

While it's running, glowdrift is interactive. Press `h` for the on-screen help;
a status bar shows the current look and the selected knob.

| key | action | key | action |
|-----|--------|-----|--------|
| `[` `]` | previous / next preset | `1`-`0` | jump to a preset |
| `m` `M` | step the motion model | `d` `D` | step the direction field |
| `r` | roll a random look | `c` | toggle the auto-cycle drift |
| `←` `→` | select a knob | `↑` `↓` (or `+` `-`) | dial it live |
| `k` | kaleidoscope fold | `x` | pixelate blocks |
| `g` | glitch | `i` / `l` | dither / scanlines |
| `space` | pause | `s` | pin this look to `~/.glowdrift-pins` |
| `h` `?` | toggle help | `q` `Esc` | quit |

Touching any control drops out of the auto-cycle so your edits stick, and the
exact flags to reproduce whatever you were watching print when you quit.

## Pattern & render styles

Opt-in layers that restyle any look (composable, and toggleable live):

```sh
glowdrift aurora --symmetry 6      # kaleidoscope: N-fold mirror into a mandala
glowdrift neon --pixelate 4        # chunky NxN pixel blocks
glowdrift synthwave --glitch 0.6   # row tearing + RGB channel split
glowdrift oceanic --dither         # ordered dithering, retro halftone
glowdrift aurora --scanlines       # dim alternate rows, CRT look
```

(`--cycle` requests cycling explicitly. It's implied by a bare launch, but you
need it to cycle *random* rolls, since `--random` on its own holds one fixed
look. `--cycle-hold` / `--cycle-fade` also imply it.)

## Presets

Twenty-eight hand-tuned looks, each with its own motion model, and many now
baking in a kaleidoscope or render style. Run one by name; any flag you add
still overrides it (`glowdrift synthwave --speed 2`, `glowdrift mandala --symmetry 3`).

<table>
<tr>
<td width="50%"><img src="previews/aurora.png" width="100%"><br><b>aurora</b> &mdash; <code>curl</code> eddies, green/teal/violet, slow</td>
<td width="50%"><img src="previews/nebula.png" width="100%"><br><b>nebula</b> &mdash; <code>orbit</code> swirl, violet/indigo cosmic bloom</td>
</tr>
<tr>
<td><img src="previews/ember.png" width="100%"><br><b>ember</b> &mdash; <code>inertial</code> flames rising, warm reds/orange</td>
<td><img src="previews/oceanic.png" width="100%"><br><b>oceanic</b> &mdash; <code>flow</code> strands drifting sideways, blue/cyan</td>
</tr>
<tr>
<td><img src="previews/neon.png" width="100%"><br><b>neon</b> &mdash; <code>inertial</code> sweeps, scanlined arcade rainbow</td>
<td><img src="previews/whirlpool.png" width="100%"><br><b>whirlpool</b> &mdash; <code>flow</code> vortices, blue/cyan water swirl, fills the frame</td>
</tr>
<tr>
<td><img src="previews/sonar.png" width="100%"><br><b>sonar</b> &mdash; <code>flow</code> ripples, dithered green scope</td>
<td><img src="previews/synthwave.png" width="100%"><br><b>synthwave</b> &mdash; <code>flow</code> streams, magenta + cyan, scanlined</td>
</tr>
<tr>
<td><img src="previews/moss.png" width="100%"><br><b>moss</b> &mdash; <code>curl</code> eddies, dense soft greens</td>
<td><img src="previews/prism.png" width="100%"><br><b>prism</b> &mdash; <code>harmonograph</code> loops as a rainbow gem</td>
</tr>
<tr>
<td><img src="previews/smoke.png" width="100%"><br><b>smoke</b> &mdash; <code>jitter</code> drift, cool tinted haze</td>
<td><img src="previews/bloom.png" width="100%"><br><b>bloom</b> &mdash; <code>spiral</code> petals mirrored into a flower mandala</td>
</tr>
<tr>
<td><img src="previews/pinwheel.png" width="100%"><br><b>pinwheel</b> &mdash; <code>spin</code> field, rainbow rotating vortex</td>
<td><img src="previews/rift.png" width="100%"><br><b>rift</b> &mdash; <code>saddle</code> field, cool hyperbolic streams</td>
</tr>
<tr>
<td><img src="previews/swarm.png" width="100%"><br><b>swarm</b> &mdash; <code>levy</code> bursts, clustered whirlpools</td>
<td><img src="previews/chaos.png" width="100%"><br><b>chaos</b> &mdash; <code>pendulum</code> swings, energetic red/gold fire cloud</td>
</tr>
<tr>
<td><img src="previews/mandala.png" width="100%"><br><b>mandala</b> &mdash; 6-fold <code>curl</code> kaleidoscope, full rainbow</td>
<td><img src="previews/rosette.png" width="100%"><br><b>rosette</b> &mdash; 8-fold <code>harmonograph</code> lace rosette</td>
</tr>
<tr>
<td><img src="previews/snowflake.png" width="100%"><br><b>snowflake</b> &mdash; 6-fold <code>saddle</code>, dithered icy crystal</td>
<td><img src="previews/gyro.png" width="100%"><br><b>gyro</b> &mdash; 4-fold <code>pendulum</code>, chaotic rainbow mandala</td>
</tr>
<tr>
<td><img src="previews/arcade.png" width="100%"><br><b>arcade</b> &mdash; pixelated + scanlined <code>inertial</code> neon</td>
<td><img src="previews/8bit.png" width="100%"><br><b>8bit</b> &mdash; chunky pixelated <code>flow</code> vortices, amber/gold</td>
</tr>
<tr>
<td><img src="previews/datamosh.png" width="100%"><br><b>datamosh</b> &mdash; heavy <code>glitch</code> tearing, magenta/cyan</td>
<td><img src="previews/signal.png" width="100%"><br><b>signal</b> &mdash; <code>levy</code> ripples, glitched broken transmission</td>
</tr>
<tr>
<td><img src="previews/halftone.png" width="100%"><br><b>halftone</b> &mdash; <code>curl</code> swirls, magenta pop-art dither</td>
<td><img src="previews/plasma.png" width="100%"><br><b>plasma</b> &mdash; <code>curl</code> flow, emerald demoscene dithered plasma</td>
</tr>
<tr>
<td><img src="previews/terminal.png" width="100%"><br><b>terminal</b> &mdash; <code>flow</code> waves, dithered green CRT</td>
<td><img src="previews/vhs.png" width="100%"><br><b>vhs</b> &mdash; <code>spin</code> vortex, glitched + scanlined tape</td>
</tr>
</table>

```sh
glowdrift --list              # list presets with their motion + mode
glowdrift --gallery --out /tmp/presets   # render every preset to a labeled grid
```

Previews live in [`previews/`](previews/); regenerate them with
[`make-previews.sh`](make-previews.sh).

Pure Python 3, no dependencies (PIL only needed for `--render`/`--contact`).
Best in a truecolor terminal (iTerm2, Terminal.app, etc.); on terminals without
truecolor it falls back to the xterm-256 palette automatically (force either way
with `--color truecolor|256`).

Every run prints the exact flags to reproduce it, so when `--random` rolls a
look you like you can pin it or tweak one knob:

```
glowdrift mode=vortices (rolled 17 knobs)
  reproduce: glowdrift --seed 12 --mode vortices --flow-scale 1.7 ...
```

## Playing with the flow

Two independent axes drive the movement: the **motion model** (how a particle
moves) and the **direction field** (which way it points). Mixing them is where
the variety comes from, e.g. `glowdrift --motion orbit --mode ripple`.

`--motion` (how particles move):

| motion | movement |
|--------|----------|
| `flow` | smooth advection along the field (drifting strands) |
| `curl` | divergence-free turbulence, coils into eddies, never bunches |
| `inertial` | momentum: comet-like sweeps that bank and overshoot |
| `orbit` | gravity toward drifting attractors, orbiting swirls + slingshots |
| `spiral` | differential rotation around a center (galaxy arms) |
| `harmonograph` | parametric lissajous loops (a cloud of nested curves) |
| `jitter` | brownian drift, a grainy diffuse cloud |
| `levy` | heavy-tailed flight: small drifts with rare long leaps, bursty and clustered |
| `pendulum` | each particle a chaotic double pendulum, swinging looping arcs |

`--mode` (the direction field, used by flow / inertial / jitter / levy):
`waves` (combed streams), `vortices` (whirlpools), `ripple` (concentric petals),
`spin` (a breathing pinwheel), `saddle` (a rotating hyperbolic push/pull).

Other flow knobs:

| knob | what it does |
|------|--------------|
| `--flow-bias DEG` | global flow direction in degrees (0=right, 90=down, 270=up) |
| `--swirl 0..1` | 0 = everything flows toward the bias direction, 1 = full field swirl |
| `--flow-scale` | swirl zoom (cycles across the canvas) |
| `--flow-drift` | how fast the field evolves over time |
| `--flow-spread` | angle variation (higher = more curl / chaos) |

Color / shape knobs:

| knob | what it does |
|------|--------------|
| `--hue-center DEG` / `--hue-range DEG` | limit colors to a slice of the wheel (range 360 = full rainbow) |
| `--colors N` | quantize to N discrete colors (0 = continuous) |
| `--edge 0..1` | shape edge: low = soft wispy strands, high = hard crisp strokes |

Other look knobs: `--speed`, `--decay` (trail length), `--exposure` (bloom),
`--saturation`, `--hue-spread`, `--hue-drift`, `--hue-jitter`, `--glow-radius`,
`--glow-softness`, `--density` (lower = more particles). Run `glowdrift --help`
for every default and random range. `--random` rolls all of them from `--seed`;
any knob you pass explicitly is kept fixed while the rest are randomized.

## Browse a bunch of looks at once

```sh
glowdrift --contact 12 --out /tmp/sheet   # grid of 12 random variants, labeled by seed
```

Eyeball the grid, then run `glowdrift --random --seed N` for the one you like.

## How it looks the way it does

- **Organic motion** comes from a flow field built from summed drifting
  sinusoids, so the swirls evolve continuously and never repeat.
- **Rainbow** is a diagonal hue gradient across the canvas that slowly rotates
  over time, so the whole wheel is on screen at once and drifts.
- **Glow** is additive: each particle stamps a soft radial kernel, the field
  decays ~10% per frame for trails, and a hue-preserving `1 - e^(-x)` tone-map
  gives the bloom rolloff while keeping the background black. Mapping the
  brightest channel and scaling the others by the same factor keeps dense
  overlaps saturated instead of washing them to white.

## Look without a terminal

Render a warmed-up frame to a PNG (handy for tuning):

```sh
glowdrift --render 200 --out /tmp/gd --width 160 --height 48
```

## Options

| flag | default | meaning |
|------|---------|---------|
| `--fps` | 30 | frame-rate cap |
| `--seed` | 1 | changes the composition |
| `--render N` | - | headless: simulate N frames, write a PNG, exit |
| `--out` | /tmp/glowdrift | PNG path prefix for `--render` |
| `--width` / `--height` | 160 / 48 | canvas size (cols/rows) for `--render` |

## License

MIT, see [LICENSE](LICENSE). Copyright (c) 2026 E. T. Carter · [dnvsfn](https://dnvsfn.com).
