Design system
The tokens and components behind the emissions console. Every value on this page comes from tokens/tokens.json, and every component below uses the same markup and classes as the prototype.
Loading tokens…
Principles
One accent, one alarm
Yellow marks what's active, live or selected. Red is reserved for permit breaches and actions that stop production. Nothing else competes for attention.
A drawing, not a render
The plant is drawn as flat outlines with an orthographic camera, so it reads like a schematic from any angle and the data stays the focus.
Humans decide
Automation (capture vents) and the copilot act or recommend, but only an operator can shut a line down, and always through a confirmation step.
The plant is the canvas
The 3D view fills the screen and never leaves it. The overview sits on the left and the copilot on the right; both collapse to rails, and the camera keeps the plant centred in whatever space they leave open.
Numbers you can cite
Figures use the display face with tabular numerals. Every report value traces back to a logged record, and the copilot fetches data before it answers.
Color
Dark only, by design. Swatches are generated from the token file; an arrow shows a token that points at another one, so changing the accent updates everything that uses it.
Emission scale
Used for smoke and fence sensors: normal, elevated, over the limit.
Typography
Chakra Petch, a squared technical face, carries headings, figures and controls. Archivo handles running text. Both use tabular numerals so live figures don't jitter.
Type scale
| Token | Size | Specimen |
|---|
Weights and tracking
| Token | Value | Specimen |
|---|
Shape and frames
Panels get thin borders with yellow corner brackets (.frame). Buttons get a cut corner (--shape-chamfer). Small chips over the 3D view use a left accent rule.
| Token | Value | Use |
|---|
Elevation and layout
One elevation level for containers that float over the 3D view (app bar, side panel, line-card dock, the overview's top and bottom bars), a card level for overview cards that float on their own, and a smaller one for chips and stats. Floating surfaces are slightly translucent and blur the plant behind them.
--elevation-floatApp bar, side panel, dock, overview bars--elevation-cardOverview cards--elevation-raisedFloating stats, scanner chipnoneCards inside a container| Token | Value | Use |
|---|
3D and motion
The 3D scene reads its colours, opacities and timing from tokens at runtime, so the drawing follows the same palette as the interface.
| Token | Value | Use |
|---|
All tokens
The full list, in the order it appears in tokens.json. Edit that file and run npm run tokens to regenerate assets/css/tokens.css.
| CSS variable | Type | Value | Description |
|---|
Floating container
Line-card dock
Sits over the 3D view with a drop shadow and a blurred, translucent fill.
Add .float to any container that sits over the full-screen 3D view, usually with .frame for the corner brackets. It sets --color-surface-float, a --effect-blur-float backdrop blur, --shape-float-radius and the --elevation-float shadow. Containers keep --layout-float-gap from the screen edge and from each other. Below the --layout-breakpoint they stop floating and stack.
App bar
Halden Ridge Cement Works
14:23:05 Live: Claude.bar.float.frame, fixed to the top of the screen at --layout-bar-height, with a diamond-marked title, the .clock and the .mode chip (data-mode="live" turns it yellow). There is no view switcher: the 3D view, the overview and the copilot are all on screen at once.
Overview column
.dash is an invisible container: no background, border or shadow, so the plant shows through every gap. Inside it, .dash-top (title, collapse button and the reporting-period .seg.seg-fill) and .dash-bottom (exports) are .float.frame bars, and every card between them is a .dcard that floats on its own with --elevation-card over --color-surface-card. The cards scroll inside .dash-scroll and fade out over --layout-dash-fade where they meet a bar, but only on a side with more to scroll to (data-more-above, data-more-below), so the list visibly ends at the top and the bottom. Set data-collapsed="true" to shrink it to a --layout-dash-rail rail, with an alarm .dot when a line is over its limit. In the prototype the choice is remembered between visits and [ toggles it. Scroll the cards and try the toggle above.
Side panel
.panel.float.frame holds the copilot and event log. The .icon-btn in its header collapses it to a --layout-panel-rail-wide rail (set data-collapsed="true") and expands it back to --layout-panel-width, animating over --motion-panel. The rail keeps one icon per tab, so the operator can jump straight to either, and carries the unseen-event .badge. Keep aria-expanded on the toggle in sync, and make the hidden body inert. In the prototype the choice is remembered between visits and \ toggles the panel. Try the toggles above.
Buttons
.btn.primary for the main action, .btn for secondary, .btn.danger only for actions that stop production, and only after a confirmation step. .sim is the demo control that sits over the 3D view.
Status pills
Line status uses .pill.running, .stopping, .starting or .stopped. A capture vent uses .pill.vent while working (solid yellow) and .pill.vent.idle otherwise.
Production line card
Line 2
RunningCV-2 capturing 30 t/h.lcard.frame. Set data-alarm="true" for a permit breach (red frame and glow), add .selected when the line is selected in 3D, and .hover when it's hovered there (white outline). The sparkline shows the last 90 seconds of released CO2 against the dashed limit. Shutting down always goes through the inline confirmation shown on the right.
Floating stats
.stats.frame floats over the 3D view on a blurred overlay surface. Units sit in a smaller muted <small> after each figure.
Tabs
.tabs with aria-selected on the active tab. The red .badge counts unseen operational events.
Copilot messages
.suggest buttons start a conversation. .msg-user for the operator, .msg-agent for the copilot (its .who says "Claude" or "Demo script" so it's always clear which is answering), .msg-tool each time it fetches data, .msg-sys for status and .msg-err for failures.
Recommendation card
Recommendation: shut down Line 2
Line 2 is releasing 121.6 t/h, above the 112 t/h permit limit, and CV-2 is already capturing at full capacity.
.proposal.frame. The copilot can recommend a shutdown but never perform one: the operator chooses here, and the choice is logged either way.
Chart
Built by buildChart(spec) in assets/js/lib.js: line or bar, up to four series coloured --color-chart-1 to 4, and an optional dashed reference line in the alarm colour for a limit or target. Line charts fit their axis to the data and leave gaps for missing values.
Event log
Earlier today
Each .ev row takes a data-k kind that colours its label: vent (yellow), alarm (red), upset and air (orange), shutdown and restart (white), maintenance and info (muted).
Segmented control
.seg for picking one option from a short set, such as the reporting period. The chosen option has aria-pressed="true". Add .seg-fill to share the full width equally, as in the overview's top bar.
KPI card
.kpi.frame inside a .kpis grid. Use .good or .bad in the sub-line to state the verdict in words, not only colour. .capbar shows usage (<i>) against a marker (<em>), here the share of the year elapsed.
Live status strip
.live-strip keeps live state visible next to historical figures. Add .alarm during a breach, with a way to get to the controls; in the overview its button selects the line in 3D.
Export bar
.exports.frame for wide layouts. In the overview column the same exports sit in .dash-bottom as an .export-btns grid with short labels, and the full name in each button's title. Name each export by what it contains and its format. .export-status confirms the result in place; add .bad when it fails.
Data table
By line
| Line | Released t | Intensity | Captured t |
|---|---|---|---|
| Line 1 | 71,428 | 0.843 | 182.5 |
| Line 2 | 68,568 | 0.864 | 173.5 |
CO2 by source, before capture
table.data inside .table-wrap (scrolls sideways on small screens). Right-align figures with .num; mark values over a limit or target with .over. Where a table has too many columns for a narrow card, as with the exceedance register in the overview column, use the stacked .reg list instead (shown in the overview column example).
Composer
.composer pinned to the bottom of the side panel. While Claude is answering, the button becomes Stop (.stop).