Halden Ridge Cement Works

Prototype Design system

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

TokenSizeSpecimen

Weights and tracking

TokenValueSpecimen

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.

.frame
Accent rule chip
TokenValueUse

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 chip
noneCards inside a container
TokenValueUse

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.

TokenValueUse

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 variableTypeValueDescription

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

Running Shutting down Starting Stopped CV-1 capturing 30 t/h CV-2 idle

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 1

RunningCV-1 idle
118.3 t/hClinker output
99.4 t/hCO2 released (limit 112)
99.4 t/hCO2 from kiln

Line 2

RunningCV-2 capturing 30 t/h
112.1 t/hClinker output
121.6 t/hCO2 released (limit 112)
151.6 t/hCO2 from kiln
Above permit limit even with the vent at full capacity. Shutting down would stop it.
Stop clinker production on Line 2? About 112 t/h of output will stop.

.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

Today so farsince 00:00
3,278tClinker produced
2,759tCO2 released
25.5tCO2 captured by vents
5.4m/sWind toward east

.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

How did last night's production go?
Pulled production report: Sun, Oct 4 22:00 to Today 06:00
Read event log: 7 events
ClaudeLast night the plant made 1,739 t of clinker and released 1,469 t of CO2. Line 2 had a permit-limit alarm at 02:33 and was shut down at 03:05.
Claude is thinking…
Claude is rate-limited right now. Try again in a minute.

.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
RestartLine 2 restart started by night shift operator.
ShutdownLine 2 shut down by night shift operator. Reason: kiln feed instability.
AirFence sensor E2 reads 548 ppm, downwind of Line 2.
AlarmLine 2 released CO2 above permit limit (116 t/h vs 112 t/h) with CV-2 at full capacity.
VentCV-2 opened automatically. Line 2 CO2 at 121 t/h (trigger 105 t/h).
UpsetLine 2 kiln feed unstable. Stack CO2 climbing.
MaintenanceLine 2 stopped for planned refractory maintenance.

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

CO2 released to air
139,996t
140,352 t from kilns, 356.0 t captured
Permit-limit exceedances
4
113 min above 112 t/h
Annual cap used
72%
76% of the year gone. On track

.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: within permit limitsLine 1 98.9 t/hLine 2 96.8 t/h
Live: permit limit exceededLine 1 122.1 t/hLine 2 96.8 t/h

.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

Export this periodSaved halden-ridge_last-30-days_emissions.csv

.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

LineReleased tIntensityCaptured t
Line 171,4280.843182.5
Line 268,5680.864173.5

CO2 by source, before capture

Calcination (process)86,962 t, 62%
Fuel combustion53,390 t, 38%

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).