A design system for interfaces that look like they run machinery. Everything here is derived from the physical limits of a late-80s amber plasma display: one lit color, a character grid, 2-pixel strokes, and no time for decoration. The constraints are the design.
Six laws, each inherited from a hardware limitation rather than a style preference. Break one and the illusion collapses.
There is no phosphor in here. A monochrome plasma panel is a grid of gas gaps holding a neon–argon mixture, and the amber is neon emitting directly when the gap strikes — around 590nm, the colour neon simply is. Phosphors arrive only with colour plasma, where xenon UV excites red, green and blue stripes. So the panel can light one colour and vary how hard. Hierarchy comes from brightness, inverse video, and blink — never hue. There is no red, no green, no success color.
A solid amber block with dark text is the machine speaking: live status, section headers, the active mode. Ration it — two or three per screen, no more.
Regions are drawn with rules. Controls are large bordered rectangles with the label parked top-left and empty space below — targets for a gloved finger. Borders do the layout work; elevation and shadow do not exist.
Text sits on terminal rows. Labels are KEY:VALUE pairs, columns align, leading
is tight at 1.15. Space is measured in 4px half-cells, never eyeballed.
System and machine text is ALL CAPS. Operator-facing soft keys — the labels a human presses — are Title Case. That split is information, so preserve it.
Emphasis is built from repeated glyphs, dashes, and colons: ✳✳ REMOTE MODE ✳✳.
No icon libraries, no illustration, no imagery of any kind.
STATUS:AUTO MODE — terse, declarative, no pleasantries.MM:SS, FRM, LAMP V.0000:00, 07/23/26.Five discharge intensities over three warm blacks. Every color decision in the system is a choice of intensity, and intensity means energization: bright is live, faint is inert.
There are two palettes, and they are not a light/dark pair — they are two
different pieces of hardware. Switch with data-ac-gas on the root element, or use the GAS
toggle in the menu bar. Both ramps are shown below regardless of which one is active; both are held to
the same contrast gate.
A monochrome AC plasma panel. The light is neon emitting directly from the gas — no phosphor anywhere in the stack. The hue is derived, not picked: weight the Ne I visible lines by the CIE 1931 observer and the discharge sits at x=0.631 y=0.369, just outside sRGB. That maps to 19°, which is a DC neon tube; a pulsed sustain enriches the 585.2nm line and walks it up. 19–31° is the defensible band and 24° is its middle.
--ink, --fillThe classic amber CRT phosphor (P3): a broad
~590nm band with real green content, where neon is a line spectrum with almost none. Historically this is
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.
--ink, --fillThe --amber-* prefix names the
ramp, not the hue — it is historical, and under NEON those tokens are not amber. Adding a third
gas means adding one block to colors.css: five stops, three surfaces,
--on-fill, and the four --gas-N triples the glow is built from.
| TOKEN | MAPS TO | USE |
|---|---|---|
| --ink / --ink-bright | amber-90 / 100 | Body text; bright for live values and hover. |
| --ink-dim / --ink-faint | amber-70 / 50 | Labels and legends; faint for disabled — and faint never glows. |
| --fill / --on-fill | amber-90 / per palette | Inverse-video surfaces and the dark text on them. |
| --stroke / --stroke-dim | amber-90 / 50 | All borders, always 2px. Dim for secondary frames. |
Two bitmap faces. VT323 — a digitization of the DEC VT320 terminal ROM — carries everything at 18px and above. Silkscreen handles 8px bit labels where VT323 loses its grid. Below 18px, VT323 is unreadable: that is the floor, not a guideline.
ALL CAPS for machine text,
Title Case for operator soft keys. SHOW LOCAL - AUTO MODE is the system reporting;
Change Show is a key a human presses.
The source
hardware used a mask-ROM face with no digital release. VT323 and Silkscreen are era-correct
stand-ins. Supply a licensed bitmap face and only --font-terminal changes.
The only three effects in the system. Glow marks energization, blink marks alarm,
scanlines sit once on the outermost frame. There is no fourth effect, and no component
anywhere in the CSS carries a transition — a control does not ease into being pressed. The one
timing function in the whole framework belongs to the gas, not to the UI: see
.ac-afterglow below.
The switches above default to PLASMA on, CRT off. Plasma is
what this panel is; CRT simulates a different display technology, and its blanking gaps are the
one thing a plasma screen conspicuously does not have. Turn it on to compare — the choice
persists. .ac-afterglow rides the CRT switch, so it is off by default too.
Three screen simulations layer on top, driven from two switches —
.ac-afterglow rides with .ac-crt, because persistence is a property
of the same glass the scanlines are on, and a tube that scans without holding is not a tube
anybody has watched. Each class still works alone if you wire it yourself:
.ac-bloom (the panel — hotter glow tokens for every descendant, a soft
center-weighted bleed breathing on a 9s mains cycle, and the cell mesh with its shimmer),
.ac-crt (the tube —
scanline texture, edge vignette, one-cell line drift per 11s, ±2% flicker, and a retrace band
sweeping down every 13s) and .ac-afterglow (plasma persistence — see below).
All three are tuned near the threshold of perception: if you can point at
the effect, it is too strong. The motion describes energized hardware, not a UI transition —
state changes stay instant — and everything stops under prefers-reduced-motion.
.ac-scanlines is the static, motion-free texture for cards and print.
A screen door is not a scanline, and the difference is the whole
point of .ac-bloom. A cell here lights at the crossing of a row wire and
a column wire, boxed in on four sides by barrier ribs that carry no discharge and emit nothing
— so a lit pixel is a neon dot trapped in a square aperture, and what you see with your eye
close to the glass is a fine mesh in both axes. The dark horizontal blanking gaps
between raster lines belong to .ac-crt and to the tube it simulates. There is no
scan in a plasma panel, so there is nothing to blank. The ribs are drawn in pure black rather
than a tint, which is what makes the grid appear only where the panel is lit: over the unlit
screen a black veil has nothing to take away. On top of it sits the shimmer — the sustain
voltage is AC, so the gas is struck and re-struck rather than held, and a sub-pixel buzz on
two detuned cycles is the visible residue of that. Both stop under
prefers-reduced-motion; the mesh itself does not, because it is the shape of the
hardware rather than an effect playing over it.
All three go on the outermost frame, and the frame is the whole screen —
including the menu bar. A nav that floats above the simulations is a nav that
is not part of the panel, so .ac-nav--sticky sits inside the frame at z-index 20
and every overlay paints above it, from 40 up. That is also why .ac-screen clips
with overflow: clip rather than hidden: hidden would
make the frame a scroll container, and a sticky bar inside one sticks to nothing.
.ac-afterglow is on, which softens the OFF edge only. The ON edge stays instant either way.A gas gap that stops being driven does not go dark on the next refresh. The
discharge leaves excited and metastable neon behind it, and that population keeps emitting
while it relaxes and recombines — the afterglow, which is a property of the gas and not of any
coating. Most of the light is gone in the first fifth of a second, then a short dim tail.
.ac-afterglow is that relaxation, and it is several separate things because
persistence is six separate phenomena — and only four are reachable from CSS alone.
amber-console.js parks a copy at the rect it occupied..ac-blink stays steps(1) by default; with persistence on,
only the ON edge stays hard. Instant up, curve down..ac-blink, .ac-cursor, an invalid
.ac-input and an over-range .ac-meter--alarm bar all keep their own
cycle length and pick up the same curve. The de-energizing rules use the same trick as
decay-out: the transition is declared on the UNLIT half, so going out drains and coming
on is instant.<div class="ac-screen ac-afterglow"><span class="ac-persist"></span> … </div>
Assuming a removed node ghosts itself. The observer only sees text change;
a node that stops existing has no rect left to pin a ghost to, so ghost it
before you remove it — AmberConsole.afterglow(row), then
row.remove(). Retune the decay with --ac-decay (105ms),
--ac-decay-fast (60ms, for rewritten text and de-energizing controls) and
--ac-decay-ease.
Each tile is its own frame. Use the PLASMA and CRT switches in the nav to compare these against the live page — the difference should be almost too small to name. CRT takes the afterglow with it. Note that the switches reach the menu bar too.
<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> … </div>
Nesting a simulation inside another one. Once per screen, on the outermost frame — the tiles above are the exception that proves it, and they are documentation, not UI.
class="ac-cursor".ac-badge, which is a chip rather than structure.Live components from the library. Hover to brighten, press to invert, tab to see the dashed focus ring.
class="ac-btn" ac-btn--filled ac-btn--dim ac-btn--block [disabled]
<!-- or drive it with ARIA: --> <button class="ac-btn" aria-pressed="true">
More than one filled button in a region. Inverse video means "the machine is doing this"; two of them means neither reads.
class="ac-btn ac-btn--sm" ac-btn ac-btn--lg
--sm on a touch panel: it computes to about 30px tall, under the
44px hit-target floor. Use it only in dense, mouse-driven tooling.
class="ac-btn ac-btn--lg ac-btn--pad ac-grow"
A row of pads without .ac-grow (or min-width:0) —
the 130px min-width floor will overflow a narrow viewport.
<div class="ac-tabs" role="tablist" data-ac="tabs">
<button class="ac-tab" role="tab" aria-selected="true" aria-controls="panel-1">Functions</button>
</div>
Navigation between pages — that is .ac-nav. Tabs swap a region,
not a screen.
<label class="ac-field">
<span class="ac-field__label">Title:</span>
<input class="ac-input" type="text">
</label>
<span class="ac-select"><select class="ac-input">…</select></span>
<label class="ac-check"><input type="checkbox"><span>Spare</span></label>
A single yes/no setting the operator flips — that reads better as an
.ac-toggle, which states ON or OFF in words.
<label class="ac-radio"><input type="radio" name="mode"><span>Auto</span></label>
More than about five options — that is an .ac-select.
<button class="ac-toggle" data-ac="toggle" aria-pressed="false">…</button>
<label class="ac-toggle ac-toggle--input">…<input type="checkbox">…</label>
<div class="ac-keypad"><button class="ac-btn ac-keypad__key">7</button>…</div>
Any screen with a keyboard attached. This exists because there is no keyboard in a projection booth.
<dialog>, so Escape and focus trapping come from the browser.
<dialog class="ac-dialog"><form method="dialog">
<h2 class="ac-dialog__title">Confirm</h2>
<div class="ac-dialog__body">…</div>
<div class="ac-dialog__actions">…</div>
</form></dialog>
Anything the operator must see machine state behind. A modal on a control
surface hides the panel; use an .ac-banner instead.
The machine's own voice: frames, strips, readouts, and the single loudest element the system permits.
class="ac-statusbar" ac-statusbar--line role="status"
Prose. A status bar holds KEY:VALUE strings, not sentences.
<div class="ac-panel"><span class="ac-panel__title">Show Information</span>…</div>
A panel over a non---screen background, unless you also set
--ac-panel-title-bg — the chip paints its own background to mask the border.
class="ac-panel ac-panel--bar"
<div class="ac-readout">
<div class="ac-readout__label">Lamp Volts</div>
<div class="ac-readout__value">41.0<span class="ac-readout__unit">V</span></div>
</div>
Text. A readout is for a number that changes.
class="ac-badge" ac-badge--filled ac-badge--dim
Anything the operator must read at a glance — 8px is a label, not a message.
| TIME | CODE | DESCRIPTION | FRAME | STATE |
|---|---|---|---|---|
| 22:28:41 | E-114 | DOUSER NOT CONFIRMED | 255315 | ACTIVE |
| 22:14:02 | W-062 | LAMP CURRENT LOW 148A | 234880 | CLEARED |
| 21:58:37 | W-041 | REEL UNIT SLACK | 212104 | CLEARED |
class="ac-table ac-table--zebra ac-table--dense" <td class="ac-table__num">
<tr class="ac-table__row--active ac-blink">
A fixed set of named readings — that is .ac-list.
<dl class="ac-list"><div class="ac-list__item">
<dt class="ac-list__key">Reel</dt><span class="ac-list__fill"></span>
<dd class="ac-list__value">04 / 06</dd></div></dl>
Sortable or multi-column data. That is a table.
<div class="ac-meter__track" style="--ac-meter-value: 72">
<div class="ac-meter__bar"></div></div>
A single precise reading. A number the operator can write down beats a bar
they have to estimate — use an .ac-readout.
<span class="ac-spinner" role="status" aria-label="Working"></span>
Anything whose duration you know. A machine that can measure progress should
show an .ac-meter; a spinner admits it cannot.
<div class="ac-banner ac-banner--filled ac-blink" role="alert">Lamp Fault</div>
Two banners on one screen. The second one makes the first one furniture.
class="ac-panel ac-panel--dim"
How a full console screen assembles. Title and soft keys on top; the operating column left, instrumentation right; destructive or exiting actions bottom-right, far from RUN.
| ZONE | CONTENTS | RULES |
|---|---|---|
| HEADER | Wordmark, page title (ALL CAPS, underlined), Title Case tabs right. | One display-size line only. |
| LEFT COLUMN | Legend panel with input wells, status strips, motion pads, banner. | Reading order top to bottom is the operator's workflow. |
| RIGHT COLUMN | Bit-field checkboxes in a dim panel, bar panels with readouts. | Read-only instrumentation; dim frames so it recedes. |
| FOOTER ROW | Mode changes left, exit and destructive actions bottom-right. | Never adjacent to a motion control. |
| FRAME | .ac-crt, .ac-bloom and/or .ac-afterglow on the outermost element, plus one companion child per simulation: .ac-mesh for bloom, .ac-retrace for CRT, .ac-persist for afterglow. The nav goes inside it. | Once per screen, never nested. All three should be operator-toggleable — the demos put afterglow on the CRT switch. |
<div class="ac-screen ac-bloom ac-crt ac-afterglow" data-ac-screen>
<span class="ac-mesh"></span>
<span class="ac-retrace"></span>
<span class="ac-persist"></span>
<nav class="ac-nav ac-nav--sticky">…</nav>
<div class="ac-screen__body">
<div class="ac-grid ac-grid--console ac-grow">
<div class="ac-stack">…operate…</div>
<div class="ac-stack">…instrument…</div>
</div>
</div>
</div>
See the assembled reference screen: ORION-70 PROJECTION CONSOLE.