A project by Diederik, also on X.
A monochrome amber-terminal CSS framework — the look of a late-1980s industrial control panel, the kind of amber plasma display that drove heavy machinery. One stylesheet, no dependencies, no build step, no JavaScript required for any component's appearance.
Live demo — ORION-70 console · System guide
Every rule in this framework descends from a hardware limitation rather than a taste preference. The panel could only light amber pixels, so hierarchy comes from brightness, inverse video and blink — never hue. Text sat on a character grid, so spacing is measured in 4px half-cells. Regions were drawn with strokes, so borders do the layout work and elevation does not exist. That constraint set is the whole design; see the six laws.
A <link> tag is the entire install.
<link rel="stylesheet" href="amber-console.css">Download dist/amber-console.css and the fonts/ directory,
keeping them as siblings — the @font-face rules use ../fonts/. Or:
npm install amber-consoleimport "amber-console"; // dist/amber-console.css
import "amber-console/layer"; // wrapped in @layer amber-console| File | Use it when |
|---|---|
dist/amber-console.css |
Default. Readable, sourcemapped, custom properties intact. |
dist/amber-console.min.css |
Production. 35kb, 6.6kb gzipped, same behaviour. |
dist/amber-console.layer.css |
Dropping into an existing app — @layer makes the framework lose specificity fights against your own rules. Use instead of, never alongside. |
dist/amber-console.layer.min.css |
The same, minified. Production embedding. |
dist/amber-console.js |
Optional behaviour, ES module. For bundlers. |
dist/amber-console.global.js |
Optional behaviour, classic script. Needed for file:// pages, where type="module" is blocked. |
Custom properties survive into dist/ unresolved — they are the public API, so you can override any
of them at runtime without rebuilding.
<link rel="stylesheet" href="dist/amber-console.css">
<div class="ac-screen ac-bloom ac-crt ac-afterglow">
<span class="ac-mesh"></span>
<span class="ac-retrace"></span>
<span class="ac-persist"></span>
<nav class="ac-nav ac-nav--sticky">…</nav> <!-- inside the frame, so the sims reach it -->
<div class="ac-screen__body">
<div class="ac-panel">
<span class="ac-panel__title">Show Information</span>
<div class="ac-statusbar" role="status"><span>STATUS:AUTO MODE</span></div>
<button class="ac-btn ac-btn--filled">Run</button>
</div>
</div>
</div>That renders a framed, glowing, scanlined control panel. No build step, no server — save it as a
.html file and double-click it.
| Class | What it is | Modifiers |
|---|---|---|
.ac-screen |
Full-height frame that hosts the simulations | — |
.ac-screen__body |
Padded content area inside it | override --ac-screen-pad |
.ac-stack |
Flex column, gap from --gap |
— |
.ac-row |
Flex row, gap from --gap |
--wrap --center --baseline --between --end |
.ac-grid |
Grid, columns from --cols |
--console (operate left / instrument right) |
.ac-col-2/3/4/6 |
Column span | — |
.ac-spacer .ac-grow .ac-push |
Fill, absorb, push-to-end | — |
.ac-sr-only |
Available to assistive tech, invisible | — |
| Class | What it is | Modifiers | Required markup |
|---|---|---|---|
.ac-btn |
Soft key | --filled --dim --lg --sm --pad --block |
<button>, <a> or <input type="submit"> |
.ac-tabs / .ac-tab |
Soft-key tabs, Title Case | .ac-tab--active |
role="tablist" > role="tab" + aria-selected |
.ac-nav |
Screen menu bar | --sticky |
__mark __links __link (--active) __meta; aria-current="page" |
.ac-check |
Status bit — fills solid, no ✓ glyph | --disabled |
<label> > <input type="checkbox"> + <span> |
.ac-radio |
Exclusive mode | --disabled |
<label> > <input type="radio"> + <span> |
.ac-toggle |
Two-position switch with mandatory ON/OFF text | --on --input (CSS-only) |
__track > __thumb, plus __state |
.ac-field / .ac-input |
Labelled recessed well | --block --invalid |
<label> > __label + <input> |
.ac-select |
.ac-input plus a typeset ▼ |
--block |
<span class="ac-select"> > <select class="ac-input"> |
.ac-keypad |
3-column numeric entry grid | __key __key--fn __key--wide --dense |
keys are .ac-btn |
| Class | What it is | Modifiers | Required markup |
|---|---|---|---|
.ac-panel |
Framed region | --dim --bar |
__title (legend chip) or __barTitle (inverse strip) |
.ac-statusbar |
The machine's voice, inverse video | --line |
role="status" |
.ac-readout |
Instrument value | --lg --inline |
__label (--plain) + __value + __unit |
.ac-badge |
Micro tag, Silkscreen at 8px | --filled --dim |
— |
.ac-banner |
Loudest element. One per screen | --filled --dim |
role="alert"; ✳✳ auto-injected |
.ac-table |
Data grid | --zebra --dense, __num, __row--active |
wrap in .ac-table-scroll |
.ac-list |
Keyed log with leader dots | --bright --dim --dense |
<dl> > __item > __key + __fill + __value |
.ac-meter |
Stepped bargraph | --lg --dim --alarm |
__track (set --ac-meter-value) > __bar; __scale |
.ac-dialog |
Modal | --wide |
native <dialog>; __title __body __actions |
.ac-spinner |
Character spinner | / — \ | --lg --dim |
role="status" |
.ac-hr |
2px divider | --bright |
<hr> |
| Class | What it is |
|---|---|
.ac-blink |
Hard steps(1) on/off, 1.1s. Never a fade — .ac-afterglow softens the OFF edge only. |
.ac-cursor |
Trailing block cursor |
.ac-bloom |
PLASMA: amplified glow tokens, a soft bleed layer breathing on a 9s mains cycle, and the cell mesh — a crossed wire grid on a 3px pitch, so a lit pixel is a neon dot at a wire intersection rather than part of a solid stroke. Buzzes sub-pixel on two detuned cycles. Needs an .ac-mesh child. |
.ac-crt |
CRT: scanlines, vignette, one-cell drift per 11s, ±2% flicker. Needs a .ac-retrace child. |
.ac-afterglow |
PLASMA PERSISTENCE: things that disappear decay instead of switching off, rewritten text ghosts its old value, and the glass holds faint uneven patches. Needs an .ac-persist child. |
.ac-scanlines |
Static, motion-free line texture, for print and thumbnails |
Put any of .ac-bloom, .ac-crt and .ac-afterglow on the outermost frame, once per screen, never
nested. All three should be operator-toggleable in a real product — the demos show how, putting
.ac-afterglow on the CRT switch rather than a third one, since persistence belongs to the same
glass the scanlines are on.
The demos default to PLASMA on and CRT off, and the markup they ship matches that state rather
than the everything-on state above. Plasma is what the panel is — a matrix of gas gaps — so it is
on. CRT simulates a different display technology, and its horizontal blanking gaps are the one thing
a plasma panel conspicuously does not have; shipping both meant the first impression was a plasma
screen wearing a tube's scanlines. It stays one click away, and the choice persists to
localStorage under ac.sim.plasma / ac.sim.crt. Defaults live per-simulation in the SIMS map
in amber-console.js. Note that .ac-afterglow rides the CRT switch, so it is off by default too.
Anything off by default must be absent from your static markup, or it paints for one frame on every load before the script removes it. The overlay children are mounted and removed with their switch.
On a full-screen frame (.ac-screen) the bleed, scanline mask, vignette and retrace anchor to the
viewport, not the document. They are properties of the glass, and glass does not scroll — a
document-anchored vignette darkens the top and bottom of the page instead of the edges of the
screen, and a backdrop-filter stretched over a 15,000px document bands into visible tile seams.
Small tiles carrying .ac-bloom or .ac-crt are unaffected and stay local.
The frame is the whole screen, menu bar included. Put .ac-nav inside it. The overlays paint
from z-index 40 up, above .ac-nav--sticky at 20, so a sticky bar is part of the panel rather than
a flat strip floating over it. .ac-screen clips with overflow: clip rather than hidden
specifically so that works: hidden makes the frame a scroll container, and position: sticky
inside a scroll container that never scrolls does not stick.
A cell that stops being driven relaxes rather than switching off. Three parts, tunable:
| Token | Default | What it times |
|---|---|---|
--ac-decay |
105ms |
Elements that disappear, and explicit ghosts |
--ac-decay-fast |
60ms |
Rewritten text, and de-energizing controls — where a full tail would smear |
--ac-decay-ease |
cubic-bezier(0.1, 0.72, 0.22, 1) |
The relaxation curve: fast knee, long tail |
It also covers the two light-off events you actually hit most:
- De-energizing — a lamp going out. A pressed button releasing, a tab deselecting, an interlock unchecking, the pointer leaving a control. These never hide, so decay-out never sees them.
- The blink OFF edge —
.ac-blink,.ac-cursor, an invalid.ac-input, an over-range.ac-meter--alarmbar. One rule swapsanimation-name, so each keeps its own cycle length. - Scroll smear — scrolling hands every cell a new value at once, so the image trails. Scaled by
real scroll speed, drained when you stop, and skipped entirely under
prefers-reduced-motion.
In every case the ON edge stays instant: a cell lights on the next refresh, and it is only the switching off that hardware cannot do sharply. Nothing here fades in.
Everything except ghosting is pure CSS. Ghosting needs the value a readout held one frame
ago, which only JS has, so amber-console.js watches the frame for text changes and parks a copy at
the rect it occupied. A node that is removed has no rect left to pin a ghost to, so ghost it first:
AmberConsole.afterglow(row); // no-op when the simulation is off
row.remove();This is the one place the framework uses a transition. It is hardware, not UI: nothing fades in,
and components stay instant redraws — scripts/check-prohibitions.mjs still enforces that.
State hooks are ARIA attributes first, with class aliases as equivalents. A framework that binds
:aria-selected needs no class juggling:
<button class="ac-btn" aria-pressed="true">Run</button> <!-- same as .ac-btn--filled -->
<button class="ac-tab" aria-selected="true">Functions</button>
<a class="ac-nav__link" aria-current="page">Console</a>
<input class="ac-input" aria-invalid="true">:checked and :disabled do the rest. React, Vue, Svelte, Astro, HTMX, Rails, Django, PHP or a
plain .html file all drive this identically.
amber-console.js has no dependencies and is strictly optional — every component looks and
reads correctly without it. It covers only what CSS genuinely cannot do:
- the
role="tablist"keyboard model (←/→/Home/End, roving tabindex) - flipping an
aria-pressedtoggle — the.ac-toggle--inputvariant needs no JS at all - the PLASMA and CRT simulations, persisted to
localStorage— CRT carries the afterglow — and the afterglow ghosts - opening and closing a
<dialog> - the NEON/AMBER gas toggle, likewise persisted — the palettes themselves are pure
CSS and switch on a
data-ac-gasattribute you can write into your own markup
<script src="dist/amber-console.global.js"></script> <!-- classic; works from file:// -->
<script type="module">import "./dist/amber-console.js";</script> <!-- ES module -->Auto-initialises from [data-ac]. Call AmberConsole.init(scope) again after rendering new markup,
and AmberConsole.afterglow(el) to ghost an element you are about to remove.
The system ships two palettes, and they are not a light/dark pair — they are two different pieces of hardware. Switch with one attribute on the root element:
<html data-ac-gas="neon"> <!-- default; omit and you get this -->
<html data-ac-gas="amber">| hue | what it is | |
|---|---|---|
neon (default) |
24° | A monochrome AC plasma panel. The light is neon emitting directly from the gas — no phosphor anywhere in the stack. This is what the six laws describe and what .ac-bloom simulates. |
amber |
38° | The classic amber CRT phosphor (P3): a broad ~590 nm band with real green content. A different display technology, but it is what most people mean by "amber terminal", it is the ramp the source design system was drawn against, and it pairs naturally with .ac-crt. |
The neon hue is derived rather than picked. Weighting the Ne I visible lines by the CIE 1931 observer puts the discharge at x=0.631 y=0.369 — just outside sRGB, gamut-mapping to 19°. That is a DC neon tube; this panel's sustain is a short high-field pulse, which enriches the 585.2 nm line against the 640 nm red group and walks the hue up. 19–31° is the defensible band; 24° is its middle.
Every stop is solved to a contrast ratio, never re-tinted — rotating hue at constant lightness
drops --amber-50 to 2.79:1 and silently fails the 3:1 non-text gate. npm run contrast runs the
full pair table against both palettes independently; a ratio that passes under
one and fails under the other is a build failure.
There is a toggle wired into both demo pages. It is optional — the palettes are pure CSS, so you can
set data-ac-gas in your own markup and never load the JavaScript. If you do want a runtime switch,
put data-ac-gas-toggle on an .ac-toggle button and the module handles
persistence.
Adding a third gas means adding one block to src/tokens/colors.css and nothing else: the five
discharge stops, three surfaces, --on-fill, and the four --gas-N glow triples. Everything else in
the system is an alias or is built from those.
Override any of these; see what you may safely change.
neon (default) amber
--screen #100600 #0d0700
--screen-raised #1b0c02 #170e02
--screen-well #060200 #060200
--amber-100 #ffa86d #ffd052 hot highlight, focus
--amber-90 #ff6b08 #ffae1e primary discharge
--amber-70 #dd5800 #cd8817 secondary
--amber-50 #ab4500 #8d5b10 dim, disabled
--amber-30 #5b2500 #4a2f08 trace, ghost
--on-fill #1e0c00 #1a0e00
--gas-1 … --gas-4 bare "r, g, b" triples the glow is built from
--ink --ink-bright --ink-dim --ink-faint --ink-trace
--fill --fill-bright
--stroke --stroke-dim
--font-terminal "VT323", "Courier New", ui-monospace, monospace
--font-micro "Silkscreen", "VT323", ui-monospace, monospace
--type-display 44px --type-title 30px --type-body 22px --type-small 18px --type-micro 8px
--tracking-display .1em --tracking-body .04em --tracking-micro .08em --leading 1.15
--space-1 4px … --space-12 48px --border-w 2px
--radius 8px --radius-sm 4px (0–2px on strips and badges)
--glow-text --glow-box
--ac-mesh-pitch 3px --ac-mesh-wire 0.075 (the cell matrix; see the note below)
--ac-mesh-wire is a budget, not a taste setting. Two crossed 1px wires lose a mean
1 - (1 - a/3)² of everything under them, and 0.075 is the value that lands that at 0.050 —
exactly the mean loss of the CRT scanlines it sits beside. scripts/contrast.mjs computes from
colors.css and cannot see an overlay, so nothing will fail if you raise it. Redo the arithmetic
first.
The --amber-* prefix names the ramp, not the hue — it is historical, and under
data-ac-gas="neon" those tokens are not amber. Renaming is a public API break and has not been
made; treat the numbers as intensity stops.
Component-level hooks: --gap, --cols, --ac-screen-pad, --ac-panel-title-bg,
--ac-meter-value, --ac-backdrop.
--ink-faint and --ink-dim never glow. Glow is the signal of energization; a disabled control
that glows is a lie about the hardware.
- One gas, many intensities. There is no phosphor in a monochrome plasma panel — the amber is neon emitting directly from the gas. Hierarchy is brightness, inverse video and blink — never hue. There is no red, no green, no "success" colour.
- Inverse video is importance. A solid amber block with dark text is the machine speaking. Ration it: two or three per screen.
- Everything is a box. 2px rules draw the regions. No elevation, no shadows-as-depth.
- The character grid rules. Labels are
KEY:VALUE, leading is 1.15, space is 4px half-cells. - Casing is semantic. System text is ALL CAPS; operator soft keys are Title Case. That split carries information.
- Ornament is typographic. Emphasis is repeated glyphs:
✳✳ REMOTE MODE ✳✳. Never an SVG icon set, emoji, illustration, or imagery of any kind. The icon system is✳ █ ▮ ▯ ▲ ▼ ◄ ►and box-drawing characters, typeset in the terminal font.
There is no logo and no icon set, and this is intentional — the genre predates GUI icon systems. Render product names in plain letter-spaced type.
npm run check enforces the mechanical parts of this: no second hue, no <svg>, no emoji, no
border-radius above 8px, no transition in components, no third font.
Two bitmap faces, both SIL Open Font License 1.1, vendored in fonts/:
- VT323 — everything at 18px and up (
--font-terminal) - Silkscreen — 8–10px micro labels only (
--font-micro)
Regular weight only. There is no bold in this system — hierarchy is size, intensity and inverse
video, never weight — so no bold face is fetched or shipped. Five @font-face rules, 54kb of woff2
total, and a <strong> inside a component gets the same synthetic bold it would get from VT323,
which has no 700 either.
VT323 must never render below 18px. It is a bitmap face and turns to mud. There is no 12px or 14px in this system; below 18px, use Silkscreen at 8–10px.
These are era-correct substitutes, not the original face. The source hardware used a mask-ROM
bitmap font with no digital release; VT323 is a digitization of the DEC VT320 terminal ROM. If you
have a licensed face closer to the hardware, it is a one-line swap — replace --font-terminal and
nothing else.
Prefer not to vendor the binaries? Swap tokens/fonts.css for tokens/fonts-cdn.css in
src/amber-console.css and rebuild. Note the trade-off: offline and file:// pages then fall back
to Courier New, which loses the bitmap grid the whole effect depends on.
The ornament glyphs (✳ █ ▼ …) fall outside the subsetted webfonts' unicode-range and render from
the system font. This matches the upstream Google Fonts behaviour and is what the design was drawn
against.
Ratios below are computed from the tokens by npm run contrast, not estimated — and the
gate runs against both palettes, so neither ships untested.
| Foreground | Background | Ratio | Needs | Verdict | Use |
|---|---|---|---|---|---|
--ink |
--screen |
7.02:1 | 4.5:1 | AA | Body text on the panel |
--ink-bright |
--screen |
10.57:1 | 4.5:1 | AA | Live values, hover, input text |
--ink-dim |
--screen |
5.22:1 | 4.5:1 | AA | Field labels, legends, secondary text |
--ink-faint |
--screen |
3.42:1 | 4.5:1 | fails — exempt | Disabled text — decorative only |
--ink-trace |
--screen |
1.63:1 | 4.5:1 | fails — exempt | Row separators, leader dots — non-text |
--on-fill |
--fill |
6.64:1 | 4.5:1 | AA | Inverse video: dark text on amber |
--on-fill |
--fill-bright |
10.00:1 | 4.5:1 | AA | Inverse video, hover state |
--ink-bright |
--screen-well |
10.89:1 | 4.5:1 | AA | Input text in a recessed well |
--ink-faint |
--screen-well |
3.52:1 | 4.5:1 | fails — exempt | Placeholder text |
--ink |
--screen-raised |
6.69:1 | 4.5:1 | AA | Body text on a zebra table row |
--stroke |
--screen |
7.02:1 | 3:1 | AA (non-text) | 2px borders — non-text, needs 3:1 |
--stroke-dim |
--screen |
3.42:1 | 3:1 | AA (non-text) | Dim borders — non-text, needs 3:1 |
| Foreground | Background | Ratio | Needs | Verdict | Use |
|---|---|---|---|---|---|
--ink |
--screen |
10.81:1 | 4.5:1 | AA | Body text on the panel |
--ink-bright |
--screen |
13.74:1 | 4.5:1 | AA | Live values, hover, input text |
--ink-dim |
--screen |
6.81:1 | 4.5:1 | AA | Field labels, legends, secondary text |
--ink-faint |
--screen |
3.47:1 | 4.5:1 | fails — exempt | Disabled text — decorative only |
--ink-trace |
--screen |
1.62:1 | 4.5:1 | fails — exempt | Row separators, leader dots — non-text |
--on-fill |
--fill |
10.23:1 | 4.5:1 | AA | Inverse video: dark text on amber |
--on-fill |
--fill-bright |
13.01:1 | 4.5:1 | AA | Inverse video, hover state |
--ink-bright |
--screen-well |
14.16:1 | 4.5:1 | AA | Input text in a recessed well |
--ink-faint |
--screen-well |
3.58:1 | 4.5:1 | fails — exempt | Placeholder text |
--ink |
--screen-raised |
10.29:1 | 4.5:1 | AA | Body text on a zebra table row |
--stroke |
--screen |
10.81:1 | 3:1 | AA (non-text) | 2px borders — non-text, needs 3:1 |
--stroke-dim |
--screen |
3.47:1 | 3:1 | AA (non-text) | Dim borders — non-text, needs 3:1 |
Every required pair passes. --ink-faint is documented as disabled and decorative only — never
body text. The palette was not lightened to hide this.
- Focus: a
2px dashed var(--ink-dim)ring on every interactive class, visible on both--screenand inverse-video backgrounds. - Motion: full
prefers-reduced-motionsupport, including the blink. An alarm that only blinked would vanish under it — which is why blink is always paired with inverse video and ✳✳ glyphs. The one thing that keeps rendering is the cell mesh: its shimmer stops, but the grid itself is the shape of the hardware rather than an effect playing over it, so hiding it would change what the panel is in response to a motion preference. - Forced colors:
forced-colors: activedrops every effect and restates borders inCanvasTextand inverse video inHighlight/HighlightText, so "the machine is speaking" still reads. - Print: black on white, 2px rules kept, all effects off. A console screenshot prints legibly.
- Reflow: survives 200% zoom and a 360px viewport with no horizontal scroll.
.ac-btn--smcomputes to roughly 30px tall, below the 44px hit-target floor. The value is fixed by the source design system, so it is documented rather than changed. Reserve--smfor dense, mouse-driven tooling; never use it for a primary control on a touch panel.- Checkbox, radio and toggle keep their drawn sizes (20px squares, a 64×28 track) but their labels
carry
min-height: 44pxso the clickable area clears the floor. Bit-field rows are therefore taller than in the original design. .ac-bannersteps down from 44px to 30px type below 480px. At display size a two-word banner has a min-content width near 400px and would otherwise be clipped on a phone.
You may override: the five amber intensities (to retune the gas), the three surface blacks, spacing, the two radii, glow strength, and any component-level custom property.
You must not override: the hue (one gas — a second hue breaks law 1), the type sizes (the 18px floor is a property of the bitmap face, not a preference), or the 2px stroke.
Embedding in an existing app? Use dist/amber-console.layer.css and swap base/reset.css for
base/reset-scoped.css, then wrap your markup in .ac-root so the framework never repaints the
host page's body.
This is a genre framework, not a neutral one. It will fight any product that wants a modern, brand-flexible UI. If you need more than one hue, an icon set, a light mode, subtle transitions, small type, or a design your marketing team can re-skin — use something else. Amber Console is for dashboards, status boards, terminal tools, control surfaces, game UIs, and anything that should look like it runs machinery.
Current Chromium, Firefox and Safari. Everything load-bearing is plain CSS — no nesting syntax, no
@container, and :has() only as a progressive-enhancement alias for an explicit class.
backdrop-filter (the plasma bleed layer) degrades gracefully: where it is unsupported the bloom
simply does less rather than breaking, because the amplified glow tokens do most of the work.
No install is needed for the main workflow — the build is pure Node.
npm test # check + contrast + build — the whole zero-dependency gate
npm run build # -> dist/, zero dependencies
npm run dev # build + watch + serve docs/ at :4173 (--port to move it)
npm run check # the prohibitions gate
npm run contrast # recompute the contrast table (--md for the README format)Two gates need the optional dev dependencies (npm i):
npm run lint # stylelint: ac- BEM pattern, no transitions in components
npm run test:visual # playwright: 14 captures across widths, a11y modes and printnpm run fonts re-downloads the webfonts and regenerates src/tokens/fonts.css.
npm run assets re-renders the favicon, the touch icon and the social card. They are not drawn —
they are the letter A and the console demo rendered through dist/amber-console.css itself, so
retuning the gas retunes the icon. The favicon is a PNG rather than the usual inline SVG on purpose:
npm run check bans <svg> in HTML, and law 6 does not make an exception for browser chrome.
See CONTRIBUTING.md.
The design system is the project; the console is only its demo. Amber Console is a set of tokens
and component classes for the industrial-control-panel genre. ORION-70 — the fictional projector
console in docs/index.html — exists to exercise every class in one screen, and nothing in the
framework depends on it.
The genre was studied from period projection-booth hardware, and the debt is acknowledged below. ORION-70 itself is invented: its name, its wordmark and its copy are ours, and no manufacturer's branding, wordmark or logo appears anywhere in this repository or in the published package. Where the demo echoes the layout conventions of real equipment, that is the genre being reproduced rather than any one product — the same way a terminal emulator is not the VT320.
The values in src/tokens/ came from an internal design bundle that is not published with this
repository. The deviations from it, and the discrepancies resolved along the way, are recorded in
CHANGELOG.md.
BSD 3-Clause License — see LICENSE. The bundled webfonts are SIL OFL 1.1; see fonts/OFL-*.txt.
- Free to use, modify, and share this code freely in any way you want in private or commercial projects, as long as you keep my copyright notice and the license text.
- Don’t use my name to promote your project.
- The software comes with no guarantees, so use it at your own risk.
- If this has been proven useful or if this helped you make a bunch of money, feel free to buy me a coffee.
Inspired by the 1970s user interface of an IMAX projector. Thanks to @RealJessePalmer and @uncledoomer for talking about it on X — that sparked this idea.
