▮ Plasma Gases

Gas emitting directly. No phosphor in the stack.

Plasma gases
▯ CRT Phosphors

A beam on a phosphor, which emits and then persists.

CRT phosphors

General purpose · built to be read

Specialized · built for an instrument

Switches

Simulation · never a color

Style & engine · never the hardware

The retrace band is part of the CRT simulation, and there is no sweep across a plasma panel to switch off.

JS Effects need the CRT simulation and a phosphor that holds long enough to see.

State
Technology
PLASMA
Emitter
NEON
Emission
585.2 nm
Mode
PRESET
Engine
CSS + JS
Technology

A gas in a sealed gap, struck by a sustain pulse. It emits directly — no phosphor in the stack — so the panel lights one hue and varies only how hard it is driven.

The gas the plasma panel was actually built on, and what this framework opens to. In a low-pressure glow the 2p→1s array dominates — the lowest excited states that radiate visibly, populated far more heavily than anything above them — so the discharge is one narrow orange-red rather than a mixture. Nixie tubes and the first flat panels ran on it.

A preset sets the display and its simulation. Everything stays adjustable — and Reset puts all of it back, switches, styles and engine alike.
08 AMBER CONSOLE · SYSTEM GUIDE

REFERENCE

Everything the framework exposes, in one place. The seven chapters before this one argue; this one only lists.

HOW TO READ IT

This chapter is the index, not the argument. Every entry links back to the chapter that explains it, and nothing here is a summary of the reasoning — if a row makes you ask why, the chapter it points at is where the answer lives.

It exists because a guide made of specimens answers "how do I do X" very well and "does this thing even have Y" very badly. Both are real questions. Seven chapters of prose could not answer the second one without being read end to end, and a framework whose surface can only be discovered by reading it is a framework people re-implement pieces of.

Tokens are split by who sets them, which is the single most useful distinction in the list and the one the source files do not make on their face. A token you set is part of the API: override it and the framework follows. A token the framework sets is plumbing — it is derived from the palette or from another token, it is rewritten whenever the display changes, and setting it yourself means fighting the code that owns it. Both are documented, because you may well need to read a derived value; only one of them is a place to write.

Nothing here is versioned separately from the framework. If a name appears in this table it is in the build you are looking at, and anything that has been taken away is in DEPRECATED instead.

EVERY CLASS

All of them, grouped the way src/ is. A class marked needs a child will not render on its own — the companion node is listed, and EFFECTS shows the assembled frame.

LAYOUT AND UTILITY · src/base/layout.css

CLASSWHAT IT DOESNOTES
.ac-screenThe full-height frame the simulations paint on.Once per page. 100svh, overflow: clip.
.ac-screen__bodyThe padded content well inside the frame.Override with --ac-screen-pad.
.ac-stackFlex column, gap from --ac-gap.Default gap --ac-space-4.
.ac-rowFlex row, gap from --ac-gap.
.ac-row--wrapLets the row wrap.
.ac-row--centeralign-items: center.
.ac-row--baselinealign-items: baseline.A big number beside its unit.
.ac-row--betweenjustify-content: space-between.Justifies every gap — see .ac-spacer.
.ac-row--endjustify-content: flex-end.
.ac-gridGrid of --ac-cols equal tracks.Default gap --ac-space-6.
.ac-grid--consoleThe canonical split: operate left, instrument right.Stacks below 900px. No --ac-cols.
.ac-col-2 -3 -4 -6Column spans for a grid child.All reset to auto below 900px. No -5.
.ac-growAbsorbs leftover space, with min-width: 0.Needed on a row of .ac-btn--pad.
.ac-spacerEmpty element that eats the slack at one point.Use instead of --between in a row of three.
.ac-pushmargin-top: auto — drop to the bottom.The one margin in the system. Flex column only.
.ac-sr-onlyAvailable to assistive tech, invisible on the panel.For a <legend> the panel title already states.
.ac-rootScoped reset, for embedding in a page you do not own.src/base/reset-scoped.css. Use instead of the global reset.

All of these are demonstrated in TYPE & GEOMETRY · LAYOUT.

CONTROLS · src/components/

CLASSWHAT IT DOESMODIFIERS
.ac-btnThe key. Outline by default.--filled (inverse, one per region) --dim --sm --lg --pad --block
.ac-tabsSoft-key strip. Needs role="tablist".Children: .ac-tab, .ac-tab--active
.ac-toggleTwo-position switch with a mandatory ON/OFF word.--on (state) --input (CSS-only, no JS) · parts __track __thumb __state
.ac-checkStatus bit. A set bit fills solid; no check glyph.--disabled
.ac-radioExclusive mode selection.--disabled
.ac-fieldLabel plus control wrapper.--block --invalid · parts __label
.ac-inputThe recessed well itself.--block
.ac-selectWrapper that draws the well around a <select>.--block
.ac-keypadNumeric pad for gloved hands.--dense (mouse only) · keys .ac-keypad__key, __key--fn (CLR / ENT, read as commands), __key--wide (spans two columns)
.ac-dialogNative <dialog>, flat backdrop, no fade.--wide · parts __title __body __actions
.ac-navThe menu bar. Goes inside the frame.--sticky · parts __mark __links __link __meta · the current link inverts on [aria-current="page"], with __link--active as the class equivalent where you cannot write the aria state
.ac-setupThe permanently-open control board.parts __grid __foot

Live specimens in CONTROLS.

DISPLAY · src/components/

CLASSWHAT IT DOESMODIFIERS
.ac-panelA framed region with a legend chip in its border.--dim (recedes) --bar (inverse title) · parts __title (the chip in the border), __barTitle (the full-width strip --bar draws instead — the one camel-cased name in the framework)
.ac-statusbarInverse strip — the machine's own voice.--line (a data row rather than a statement)
.ac-readoutLabelled instrument value.--lg --inline · parts __label (__label--plain drops the inverse-video strip) __value __unit
.ac-listKeyed log with leader dots. A <dl>.--dense --dim --bright · parts __item __key __fill __value
.ac-tableThe data grid.--zebra --dense · parts __num __row--active
.ac-table-scrollHorizontal scroll container for a wide table.The answer to a table that will not fit a phone.
.ac-meterStepped bargraph, never a smooth fill.--lg --dim --alarm · parts __track __bar __scale
.ac-badgeMicro tag in the pixel face.--filled --dim
.ac-bannerThe loudest element. One per screen.--filled --dim
.ac-spinnerA rotating character, not a circle.--lg --dim
.ac-hrThe only divider, at the 2px stroke.--bright
.ac-cursorBlock cursor after the text.
.ac-sweepThe PPI radar sweep. Pure CSS.part __beam (the rotating wedge; the sweep draws nothing without it) — see PERSISTENCE.

Live specimens in DISPLAY.

SIMULATIONS AND EFFECTS · src/sim/, src/base/blink.css

CLASSWHAT IT DOESNEEDS A CHILD
.ac-bloomThe plasma panel: hotter glow, a breathing bleed, the cell mesh..ac-mesh
.ac-crtThe tube: scanline texture, vignette, 11s drift, 5.5s hum, the retrace band..ac-retrace
.ac-afterglowPersistence. Rides with .ac-crt on the board..ac-persist
.ac-scanlinesThe static, motion-free texture. For cards, thumbnails and print.no
.ac-meshThe cell mesh and its shimmer. Companion to bloom.
.ac-retraceThe sweeping band. Companion to CRT; switchable, and omittable.
.ac-persistThe layer ghosts and the smear are mounted into. Companion to afterglow.
.ac-ghostOne decaying copy of a rewritten value. Mounted and removed by effects.js.
.ac-ghost--fastThe same, on the short tail, where a value rewrites often.
.ac-blinkLaw-2 emphasis. steps(1), 1.1s, never a fade.

All three simulations compose in CSS; the board refuses to run plasma and CRT together, which is a rule of the demo and not of the framework. EFFECTS and PERSISTENCE have the arguments.

CORNER STYLE · src/components/classic.css

CLASSWHAT IT DOESSCOPE
.ac-classicCut corners, extruded key edge, label top-left. The default look.One region — including back inside a rounded one.
.ac-roundedThe way back to the arc.One region.

The page-wide version of the same choice is data-ac-style-classic below. Where corner-shape is unsupported the corners quantize to a hard 2px instead of cutting, so both positions mean something in every browser. CONTROLS shows both at once.

EVERY ATTRIBUTE

Two kinds, and they are not interchangeable. State attributes go on <html> and are read by the CSS — write them yourself and the framework follows, with or without the JavaScript. Hooks are how the optional module finds controls; they do nothing on their own.

STATE · on the root element, read by CSS

ATTRIBUTEVALUESWHAT IT SELECTSDEFAULT
data-ac-techplasma crtWhat is making light at all.plasma
data-ac-emitterneon helium argon krypton · p1 p3 p4 p7 p11 p31 p39Which gas or phosphor inside that technology. The palette.neon
data-ac-enginecss css+jsWhether the effects module may contribute.css+js
data-ac-style-blinkon offLaw-2 blink, everywhere at once.on
data-ac-style-classicon offCut corners and extruded key edges, page-wide.on
data-ac-style-retraceon offThe CRT retrace band.on

The style flags are always written explicitly, both ways, and that is deliberate: CSS selecting on the absence of an attribute cannot tell "the author has not chosen" apart from "the author chose off", and the defaults above are the only place that distinction is allowed to live. An emitter is only meaningful inside its technology, which is why naming a palette takes two attributes and not one — see COLOR.

HOOKS · read by amber-console.js, inert without it

ATTRIBUTEGOES ONWHAT IT WIRES
data-ac="tabs"An .ac-tabs containerThe arrow-key tablist model and roving focus.
data-ac="toggle"An .ac-toggle buttonClick-to-flip, keeping aria-pressed and the ON/OFF word in sync.
data-ac-screenThe frameNames the element the simulations mount on. Falls back to the first .ac-screen.
data-ac-simA toggle"plasma" or "crt" — mounts the classes and the companion child.
data-ac-styleA toggleThe flag name: "blink", "classic", "retrace".
data-ac-style-noteAny elementRevealed while the named style flag can do nothing.
data-ac-engineA buttonThe engine switch. On the root it is state, above; on a button it is the control.
data-ac-engine-note
-off / -on
Any elementThe three states of that switch: cannot / could but off / contributing.
data-ac-displayA radioOne catalog row. Carries -tech, -emitter, -sims, -peak.
data-ac-display-outAny elementA readout: tech emitter peak mode engine label.
data-ac-display-infoAny elementProse for one technology or one exact tech/emitter pair.
data-ac-display-resetA buttonReset to Preset — simulations, styles and engine together.
data-ac-dialog-openA buttonThe id of the <dialog> to show.
data-ac-dialog-closeA button inside the dialogCloses the dialog it is in. Takes no value. Only needed where the button is not in a <form method="dialog">, which closes on its own.
data-ac-scrollingThe frameWritten by effects.js while the page moves. Read it; do not set it.

The catalog is markup, not a table inside the JavaScript. Ship your own [data-ac-display] radios with your own palettes and the module needs no edit — which is the same reason the technology prose is one [data-ac-display-info] node per key rather than strings escaped through a script. SCREEN & BOARD documents the assembled board.

Element hooks are bound once, on load. Both optional modules bind themselves at DOMContentLoaded, so a page that ships its markup in the HTML calls nothing at all. The exception is markup inserted afterwards: a [data-ac="tabs"] strip or a [data-ac="toggle"] your own code rendered is not wired, and the fix is one call — AmberConsole.init(el) over the new subtree, or AmberConsole.init() for the whole document. It keeps a set of what it has already wired, so calling it twice on the same element cannot double-bind a listener.

Two hooks need nothing either way. The dialog pair is delegated from the document, so a <dialog> appended at runtime opens and closes with no call. And the persistence module watches the whole tree with a MutationObserver — ghosting and smear find new nodes on their own, which is why AmberConsoleEffects.init() is called for you and is not something you normally touch.

THE THREE FUNCTIONS · everything either module exports

CALLWHEN YOU NEED IT
AmberConsole.init(el?)Wire markup that arrived after load. Both modules bind themselves at DOMContentLoaded, so a static page never calls this. Idempotent — it remembers what it has already wired.
AmberConsoleEffects.afterglow(el)Leave a decaying copy of an element behind. Call it before you remove the element — a detached node has no rectangle, so there is nowhere to pin the ghost. Text rewrites ghost themselves; this is for a node about to stop existing.
AmberConsoleEffects.transition(fn)Run a change and let the screen it replaces decay per pixel, rather than element by element. Wrap discrete swaps only — a tab switch, a dialog, a panel replacement.
AmberConsoleEffects.transition(() => { // falls back to a plain call panel.replaceChildren(nextScreen); // when the API, the simulation or the }); // engine flag is unavailable

Only discrete changes, and the reason is mechanical rather than aesthetic. A new view transition cancels the one already running, so wrapping a per-second text tick would mean P7's three-second snapshot being thrown away sixty times a second and never completing once — strictly worse than not doing it. Frequent text is what the ghost mechanism is for; ghosts compose rather than cancelling. transition() is always safe to call: it declines and simply runs your function when the API is missing, motion is reduced, the tab is hidden, the persistence simulation is off, or the page asked for the CSS-only engine.

EVERY TOKEN

Every custom property in the framework begins --ac-. That is not tidiness: dist/amber-console.layer.css exists so the framework can be embedded in an app that owns the page, and cascade layers do not protect custom properties — an unlayered :root { --radius: 12px } in the host beats a layered one. A host that happened to use --gap or --cols silently re-cornered every control in the framework. stylelint enforces the prefix now, so the next token cannot go bare.

COLOR · you set these

TOKENWHAT IT IS
--ac-inkBody text. The 7.0:1 stop.
--ac-ink-brightThe live value. 10.5:1.
--ac-ink-dimSecondary drive. 5.2:1 — a cell driven at 70%, not an off state.
--ac-ink-faintFurniture and disabled text. 3.4:1 — below the text floor, which is why it is furniture.
--ac-ink-traceThe faintest mark the panel can make — leader dots, unlit scale ticks. 1.63:1, non-text only.
--ac-screenThe glass. One of three warm blacks.
--ac-screen-wellA recessed surface: inputs, selects.
--ac-screen-raisedA surface standing above the glass.
--ac-fillInverse video: filled keys, status strips.
--ac-fill-brightThe hot fill, for a latched or alarmed surface.
--ac-on-fillText drawn on a fill.
--ac-strokeEvery border in the system.
--ac-stroke-dimThe receded border.
--ac-emit-100 -90 -70 -50 -30The emission ramp the five ink levels are solved from.
--ac-halo-1 -2 -3 -4The scatter ramp — light spread in the glass, as raw r, g, b triples.

Eleven palettes set all of these, and every stop in every one of them is solved to the same contrast ratio — so what changes between displays is the hue, never the brightness. COLOR shows the derivation. The halo tokens are triples rather than colors so they can be given an alpha at the point of use.

TYPE, SPACE AND GEOMETRY · you set these

TOKENWHAT IT IS
--ac-font-terminalThe body face. A bitmap terminal font.
--ac-font-microThe 8px pixel face, for badges and micro labels.
--ac-type-display -title -body -small -microThe five sizes. 18px is the floor for anything anybody reads.
--ac-tracking-display -body -microLetter-spacing per size band.
--ac-leadingLine height. One number for the whole system.
--ac-space-1 -2 -3 -4 -6 -8 -12The scale, in 4px half-cells: 4 8 12 16 24 32 48.
--ac-border-w2px. There are no hairlines on this panel.
--ac-radiusKeys, panels, dialogs. 2px 4px 8px 4px by default — the four corners are cut to different depths, TL TR BR BL. 8px under .ac-rounded.
--ac-radius-smWells and switch housings. 1px 2px 4px 2px — the same diagonal at half depth. 4px under .ac-rounded.
--ac-gapPer element, not on the root: the gap of one row, stack or grid.
--ac-colsPer element: the track count of one .ac-grid.
--ac-screen-padPer element: the padding of one .ac-screen__body.
--ac-meter-valuePer element: 0–100 on an .ac-meter__track.
--ac-panel-title-bgPer element: what the legend chip masks itself against.
--ac-backdropPer element: what an open .ac-dialog paints behind itself. Defaults to flat 72% black — keep it opaque enough to read as a powered-down panel, and do not blur it.
--ac-sweep-sizePer element: the maximum diameter of an .ac-sweep face. Defaults to 260px; the radar demo overrides it.
--ac-sweep-periodPer element: seconds per revolution. Defaults to the phosphor's own tail × 1.5, floored at 2.5s — so a long phosphor comes round about as its trail finishes draining, and neon does not strobe.
--ac-sweep-arcPer element: how many degrees of wake trail the beam. Defaults to 250°, which is what the period expression evaluates to. Lower it for an emitter below the persistence floor, or the wake is a thin spinning line.
--ac-sweep-linePer element: the width of the writing beam itself, 3px by default. A constant width at every radius, because a CRT spot is.

Every row marked per element is set on your element rather than on :root — they are per-instance dials, and there is no global value to override. Two of them, --ac-gap and --ac-cols, are for that reason the only tokens in the 1.x rename that could not be bridged with an alias: there was no framework-level value to alias from, so they have to be renamed where you write them. See DEPRECATED.

GLOW, MOTION AND PERSISTENCE · mostly the framework's

TOKENWHO SETS ITWHAT IT IS
--ac-glow-textyouThe halo on a lit value. Never scales with drive — radius is a property of the glass.
--ac-glow-text-dimyouThe halo that belongs to --ac-ink-dim.
--ac-glow-text-faintyouThe halo that belongs to --ac-ink-faint.
--ac-glow-boxyouThe halo on a filled or focused surface.
--ac-glow-box-dimyouIts receded twin.
--ac-halo-spreadframeworkHow far the glow reaches, per palette. The only one of these three that is a radius — it multiplies the blur distance, and it is the square root of the scatter below.
--ac-halo-scatterframeworkHow much light reaches the halo, per palette. An alpha multiplier, not a distance — neon is 1.00 by definition and every other emitter is stated against it. Radius never scales with drive; only alpha does.
--ac-halo-flashframeworkAn r, g, b triple, not a number: the colour of a two-coating phosphor's fast layer. Declared by P7 alone, and the reason its sweep leads in blue over a yellow-green wake.
--ac-persistframeworkThe working tail, per palette — capped for use in transitions.
--ac-persist-tailframeworkThe uncapped tail. 35µs on P11, 3s on P7. What the engine gate reads.
--ac-persist-fastframeworkDocumentation only — no CSS reads it. The decay of a two-coating phosphor's fast layer (P7, 0.04ms). It records where the hardcoded 4% crossover in ac-ghost-cascade comes from; keyframe offsets cannot read a custom property, so the figure has to be written twice and this is the half that says why. Not the ghost duration — that is .ac-ghost--fast, which is min(--ac-persist-tail, 400ms).
--ac-decay
--ac-decay-fast
--ac-decay-ease
frameworkThe decay durations and curve. Derived from the tail.
--ac-flickerframework0–1, per phosphor. Scales the mains hum and the retrace band, from one number.
--ac-drive-dim
--ac-drive-faint
frameworkThe drive fractions the dim and faint tiers are, arithmetically.
--ac-mesh-pitch
--ac-mesh-wire
frameworkThe cell mesh: spacing, and the darkness of a barrier rib. The rib figure is a contrast budget.
--ac-cornerframeworkbevel or round. What the classic flag actually flips.
--ac-edge
--ac-edge-press
frameworkThe key's extruded edge, and the same key pressed IN.
--ac-edge-3d
--ac-edge-3d-in
frameworkThe classic values those two point at.
--ac-litframework0 or 1 — whether a control is currently lit. A registered @property, which is the whole point: registering it is what makes a custom property animatable, so the afterimage's alpha and radius decay together off this one number instead of needing a box-shadow transition per state.
--ac-blink-anim
--ac-blink-name
--ac-blink-ease
frameworkThe blink, assembled per palette.
--ac-blink-force
--ac-blink-force-ease
frameworkThe root-level override, read first at every blink site. How one rule stops every blink at once.
--ac-ghost-animframeworkThe ghost's decay animation.
--ac-smearframework0–1, how fast the page is currently scrolling. Written onto the frame by effects.js every frame and read by the blur below. Read it; do not set it.
--ac-smear-filterframeworkThe scroll smear. Set to none by reduced-motion and by print.
--ac-afterimage-displayframeworkSwitches the static afterimage halo off wholesale.

The "framework" rows are still worth knowing — several are the single point where an entire class of effect is switched off, which is exactly how prefers-reduced-motion and the print stylesheet do their jobs in a handful of lines rather than a selector list. Read them freely; write them only if you have read the file that owns them, because the palette rewrites most of these whenever the display changes.

EVERY STORED KEY

The optional module remembers what the board was set to. All of it is localStorage, all of it is prefixed ac.sim., and every write is wrapped — localStorage throws in private mode and in sandboxed file:// frames, and a panel that cannot persist must still work.

KEYVALUESWRITTEN BY
ac.sim.techplasma crtPicking a catalog row.
ac.sim.emitterThe emitter namePicking a catalog row.
ac.sim.plasma1 0The PLASMA switch, and any preset that moves it.
ac.sim.crt1 0The CRT switch, and any preset that moves it.
ac.sim.engine1 0The JS Effects switch. Cleared by Reset.
ac.sim.style.blink1 0A blink switch, if the page ships one. Cleared by Reset.
ac.sim.style.classic1 0The Classic Buttons switch. Cleared by Reset.
ac.sim.style.retrace1 0The Retrace Band switch. Cleared by Reset.
ac.sim.mod1 0Whether the board has been moved off its preset.
CLEARED IS NOT THE SAME AS SET TO THE DEFAULT Reset removes the style and engine keys rather than writing the default into them, and the difference is the whole mechanism. An absent key means the user has never said, which is the only state a derived default is allowed to fill in. Writing the default would claim the preference on their behalf, and a flag that derives from the simulation would stop following it forever after. There is no separate "has this been touched" flag, because localStorage already answers that question and a second source of truth for it would be a thing to keep in sync.
A PREFERENCE IS ONLY RESTORED WHILE THE PAGE CAN CHANGE IT BACK Take a switch out of your markup and the flag it wrote falls back to your own attribute, or to the default — the stored value is not read. A preference with no control left on the page is not a preference, it is a setting the visitor can no longer reach, and one earlier click would otherwise have turned blink off on that page forever. Nothing is written back in that case either: the stored value still belongs to whichever page does ship the switch.

EVERY ENVIRONMENT

The framework renders in four environments it does not control, and it has a considered answer in each. You do not switch any of these on. They are in the stylesheet, they respond to the reader's own settings, and the reason they are listed here rather than argued in a chapter is that the correct thing to know about them is that there is nothing to do.

ENVIRONMENTWHAT CHANGESFILE
forced-colors
(high contrast)
The whole design is a colour trick, so this needs a real fallback rather than a broken one. Every simulation overlay is removed; every border is restated in CanvasText, because the borders are the layout; and every inverse-video surface is restated in Highlight / HighlightText, because "the machine is speaking" is information and must not flatten into an outline. Blink survives — it carries the alarm — but returns to a hard edge. src/base/a11y.css
@media print The palette inverts to black on white by re-pointing the semantic tokens, so no component needs a print rule of its own. The 2px rules stay because they are the layout; glow, bloom, scanlines and every animation go, because they describe energized hardware and paper is not. Filled surfaces carry print-color-adjust: exact, without which a browser silently drops them and a set interlock bit prints as an empty square. src/base/print.css
prefers-reduced-motion All three simulations stop. The cell mesh is the one overlay that keeps rendering — it is the shape of the panel rather than an effect playing over it, and only its buzz is motion. Blink stops, the smear stops, ghosts are hidden outright, and every decay transition is blanketed. src/sim/frame.css
No vendored fonts A build swap rather than a media query: replace the tokens/fonts.css import with tokens/fonts-cdn.css and rebuild, and the five vendored woff2 files are dropped in favour of the same two faces from a CDN. Same tokens, same stack, one fewer thing to host. src/tokens/fonts-cdn.css

Two of the four are gated rather than trusted. npm run contrast runs the full pair table against all eleven palettes independently, so a ratio that passes under one emitter and fails under another is a build failure; and the visual suite captures a forced-colors and a print case for every demo page. The one thing the suite cannot see is a missing print-color-adjust — Playwright renders print to a screen surface, which honours backgrounds regardless — so that list is checked by hand against a real print preview.

There is also a scoped embed for putting the framework inside an app that owns the page: swap base/reset.css for base/reset-scoped.css, wrap your markup in .ac-root, and use dist/amber-console.layer.css so the framework loses specificity fights against your own rules. The palette attributes may go on <html> or on the .ac-root wrapper — both resolve correctly — but do not split them across the two.