The Y3K Lab terminal-UI engine — pure bash, zero dependencies, fluent in A3K.
Built for the year 3000
❯ PHOS4_THEME=y3k PHOS4_ANIMATE=1 ./demo.sh
╔═════════════════════════════════╗
║ ╔═╗ ╦═╗ ╔═╗ ╦ ╔═╗ ╔═╗ ╔╦╗ ╔═╗ ║
║ ╠═╝ ╠╦╝ ║ ║ ║ ╠═ ║ ║ ╚═╗ ║
║ ╩ ╩╚═ ╚═╝ ╚╝ ╚═╝ ╚═╝ ╩ ╚═╝ ║
╚═════════════════════════════════╝✓ phos4_step_ok — the good news
⚠ phos4_step_warn — the heads-up
✗ phos4_step_err — the bad news
· phos4_step_info — the aside
─────────────────────────────────────────────────────
phos4_choose — pick one
─────────────────────────────────────────────────────
❯ 1 banner the wordmark, boxed and swept
2 picker this very menu
3 panel the completion box
↑/↓ navigate · enter to select · type number to jump · esc to cancelHand-made in HTML/CSS from the engine's golden masters — the real thing is bash writing ANSI. The wordmark is the y3k theme's default; ▶ replays its power-on, and PS_MONO=1 re-renders the same markup from the grey band — a theme swap, live.
Phos4 is a pure-bash, themeable
terminal-UI engine — gradient-swept wordmarks, arrow-key pickers,
panels, spinners, progress — shipped as a single file any CLI script can
source. No curses, no Python, no npm — bash 3.2 and ANSI
escapes, nothing else.
It exists because the look outgrew its birthplace. A3K — the Y3K design language — took shape inside the Project System, entangled across three scripts; the installer even carried its own duplicate copies of the animations. Good enough to reuse, too tangled to travel. Phos4 is the extraction: the engine (mechanics) pulled clean of the look (data), so the machinery is shared once and the look is swappable per consumer.
The verbs stay disciplined: a surface wears A3K; Phos4 is fluent in it and renders it. An engine never builds the look — “built” stays reserved for the tagline, Built for the year 3000.
The architecture is one clean cleave, three roles. The engine owns the mechanics — how a gradient sweeps a ramp across characters, how a wordmark gets boxed in rails, how a picker repaints in place. A theme is pure data — colour indices, gradient stops, rail glyphs, literal wordmark rows — no rendering code in a theme, ever; the contract is locked. And the consumer — the script that sourced the file — owns choreography: what happens, in what order, is its business. How it looks is not.
| Theme function | Supplies |
|---|---|
| y3k_palette | the colour indices — accent, ok / warn / err, rails, highlight |
| y3k_ramp | the gradient stops — 201 171 135 99 75 45 51, magenta → cyan |
| y3k_anim_ramp | the longer 16-stop loop the power-on animation flows through |
| y3k_wm_width | the wordmark row width every row is padded to (31) |
| y3k_rail | the box rails — top, bottom, side; every glyph is theme data |
| y3k_wordmark | literal glyph-rows for each wordmark key |
That table is the theme side of the seam — swap the prefix and
you’ve named a new theme; the engine dispatches through
PHOS4_THEME and never knows the difference. The seam is proven,
not promised: a second theme — a different brand with its own logo language,
flat colour, single-line box — renders through the same engine with
zero engine changes. The one gap it exposed (a hardcoded
║ side glyph) was closed by promoting that glyph to theme data;
the pure-data contract held, no escape hatch.
Even greyscale is data: PS_MONO=1 swaps the ramp for a
mid-grey band and the engine renders on, colour-blind — that’s the toggle
under the demo above.
The API is small, and all of it is namespaced: source one file, get every
phos4_* call. Prompts answer the bash way — through environment
variables and exit status, never parsed stdout — and ESC always
means no.
| Call | Renders |
|---|---|
| phos4_render_banner | the rail-boxed, gradient-swept wordmark — with a sub-second power-on entrance when PHOS4_ANIMATE=1 |
| phos4_render_splash | rail-less splash art — install banners, mascots, shapes that aren’t a boxed wordmark |
| phos4_gradient_text | the theme ramp swept per-character across any string |
| phos4_choose · confirm · input | the arrow-key picker, the Yes/No toggle, the line prompt |
| phos4_step_ok · warn · err · info | the ✓ ⚠ ✗ · status lines |
| phos4_header · rule · panel | section chrome and the bordered completion box |
| phos4_spin · loadbar | a spinner (⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏) that wraps a command; the self-clearing fill bar |
| phos4_celebrate · celebrate_banner | the shimmer sign-off; giant block letters for the moment a surface finishes something |
Half of Phos4’s value is invisible: the accumulated rules that keep a pure-bash renderer from corrupting a terminal. They live as comments at their site in the engine — no external doc to consult — and every one of them was paid for.
printf emits plain text. Pipe it, log it, test it — the escapes simply aren’t there. The engine never animates into a pipe.${#s} has to count characters, not bytes, or the per-character gradient miscounts and the box tears.═ — concatenation corrupts in the C locale. Every rail ships as a hand-typed literal string.local s="$1" len=${#s} measures s before it’s assigned — length zero, and the gradient silently vanishes. The engine’s signature bug, immortalized as a rule.trap — on every exit path, Ctrl-C included. The terminal is handed back the way it was found.Extraction invites drift: rewrite the machinery and the look shifts a shade. Phos4’s acceptance bar refused that — byte-identical output, enforced by a golden master: renders captured from the original source before extraction, and every engine render since diffed against them, raw ANSI and all.
❯ ./test/parity.sh
◆ Parity ──────────────────────────────────────────
the built engine vs the frozen golden master
✓ banner-default — byte-identical
✓ choose-nav — byte-identical
✓ celebrate — byte-identical
· 24 more captures — every one green
╭───────────────────────────────────────────────────────╮
│ │
│ ✓ Parity green │
│ 27 of 27 captures byte-identical │
│ │
╰───────────────────────────────────────────────────────╯Certified Y3K ReadyTwenty-seven captures — banners, pickers, panels, both themes, colour and mono — all green. Vendoring the engine back into the Project System deleted ~960 lines of duplicated look from its first consumer, and that consumer’s release script re-copies the built engine every time it runs — a stale copy can’t ship.
Authored split, shipped single. Source is an engine plus
one file per theme; the build is a concatenation — this is bash, and the
build step is a cat with opinions:
the mechanics — rendering + the correctness rules
src/themes/y3k.sh the default theme: A3K as pure data (the canonical values)
build.sh engine + themes → one file
dist/phos4.sh what consumers vendor — every theme aboard
test/ the golden-output parity harnessConsumers copy dist/phos4.sh into
their own repo and source it. Zero dependencies, nothing to
install, no version to negotiate — the file that ships is the whole
engine:
# in any bash script
source phos4.sh # every phos4_* call, every theme
phos4_render_banner default # the boxed, gradient-swept wordmark
phos4_step_ok "did the thing"
phos4_choose "Pick one" "Alpha|first" "Beta|second"
echo "$PHOS4_CHOSEN_TITLE" # prompts answer via environmentThree environment variables drive everything:
PHOS4_THEME picks the theme (default y3k — the
A3K look), PHOS4_ANIMATE=1 opts into the power-on entrance,
and PS_MONO=1 goes greyscale.
Phos4 has no public repo and no standalone download — yet. Today it ships
the way it was always meant to: vendored inside a consumer.
The Project System kit carries
it as bin/phos4.sh, rendering the dashboard, the lifecycle
scripts, and the installer. A standalone kit in the
Blue Steel mold is planned — and
until something actually ships, this page stays a read.