# Digital Rust UI Specification

Part of Digital Rust Specification v1.3.1

This document is the canonical specification for Digital Rust as a **user interface
theme**: surfaces, elevation, interaction physics, and component definitions. It is
a sibling to the *Digital Rust Syntax Highlighting Specification*, which owns the
colour palette, token classification, editor chrome, and terminal configuration.
The palette is defined there and is not re-specified here. This document references
colours by name only.

A conforming UI implementation uses the named colours from the syntax
specification and follows the component, elevation, and interaction rules below.

**Two modes.** Digital Rust has two modes. The dark theme is the base. Safe Mode is
the light projection and the accessibility option. Safe Mode uses the same palette,
the same colour names, and the same token roles. Only the value that fills a role
changes. The syntax specification defines the projection rule and the Safe Mode
role values. The specification recommends that every port of Digital Rust provides
both modes, because a reader who cannot use a dark interface needs a way in.

**Semantic tokens.** Component implementations reference the semantic `--dr-*`
tokens defined in `tokens.css`, never raw palette values. A value marked ◆ is the
same in both modes.

| Token | Dark value | Safe Mode value | Role |
| --- | --- | --- | --- |
| `--dr-surface-base` | Event Horizon | Ghost Pixel | Page background, wells, code blocks |
| `--dr-surface-secondary` | Null Pointer | Phosphor Smoke | Elevated chrome: tab bars, gutters, sidebars |
| `--dr-surface-elevated` | Core Meltdown | Phantom Current | Cards, popovers, menus, modals, code block headers |
| `--dr-surface-highlight` | Bad Sector | Corrupted Cache | Hover fills, active list items |
| `--dr-surface-active` | `#5A1810` | Copper Trace | Scrollbar hover, pressed wells |
| `--dr-machined-fill` | Ghost Pixel ◆ | Ghost Pixel ◆ | The light part of a machined control: a toggle thumb, a slider thumb, a check mark |
| `--dr-machined-track` | Bad Sector ◆ | Bad Sector ◆ | The dark rail of a toggle. It holds in both modes, so a Ghost Pixel thumb stays visible |
| `--dr-machined-rail` | Event Horizon ◆ | Event Horizon ◆ | The recessed rail of a slider. It holds in both modes, so a Ghost Pixel thumb stays visible |
| `--dr-surface-hot` | Bricked Board ◆ | Bricked Board ◆ | A hot panel: a toast, a status bar. It keeps a dark fill in both modes |
| `--dr-text-primary` | Ghost Pixel | Event Horizon | Body text |
| `--dr-text-secondary` | Phosphor Smoke | Bad Sector | Supporting text |
| `--dr-text-link` | Phantom Current | Kernel Panic | Links |
| `--dr-text-link-hover` | Fatal Error | Data Rot | Link hover and active |
| `--dr-text-link-visited` | Corrupted Cache | Bricked Board | Visited links |
| `--dr-text-muted` | Phosphor Smoke at 55% | Event Horizon at 60% | Disabled and muted text. At 55% the dark value clears 4.5:1 on every dark surface |
| `--dr-text-quiet` | Phantom Current | `#664C49` | Shell prompts, removed diff lines, footer copy |
| `--dr-heading` | Fatal Error | `#75322A` | The one heading colour, in both modes |
| `--dr-heading-sub` | Copper Trace | `#664C49` | A sub-heading. Copper Trace measures 2.07:1 on a light page |
| `--dr-cursor` | Stack Overglow | `#533900` | The block cursor. Ghost Pixel on it passes AA in both modes |
| `--dr-text-on-accent` | Ghost Pixel ◆ | Ghost Pixel ◆ | Text on a dark fill |
| `--dr-accent` | Digital Rust ◆ | Digital Rust ◆ | Primary brand accent |
| `--dr-accent-hover` | Firewall Breach ◆ | Firewall Breach ◆ | Primary hover fill; Ghost Pixel on it passes AA (4.66:1), raw Amber Alert does not |
| `--dr-success` | Memory Leek | `#455925` | Success borders, text, fills |
| `--dr-warning` | Stack Overglow | `#6C4D06` | Warning borders, text, fills |
| `--dr-danger` | Amber Alert | `#8D362A` | Error borders and fills |
| `--dr-danger-text` | Amber Alert Text | `#8D362A` | Error text on a page or a panel |
| `--dr-info` | Dead Thread | `#2B5A56` | Informational accents |
| `--dr-on-hot-success` | `#9AB867` ◆ | `#9AB867` ◆ | Success text on a hot panel |
| `--dr-on-hot-warning` | `#D9A540` ◆ | `#D9A540` ◆ | Warning text on a hot panel |
| `--dr-on-hot-danger` | `#FF8E7B` ◆ | `#FF8E7B` ◆ | Error text on a hot panel |
| `--dr-on-hot-info` | `#73BAB4` ◆ | `#73BAB4` ◆ | Information text on a hot panel |
| `--dr-on-hot-text` | Ghost Pixel ◆ | Ghost Pixel ◆ | General text on a hot panel |
| `--dr-on-hot-text-muted` | Phantom Current | Phosphor Smoke | A resting or secondary item on a hot panel |
| `--dr-border-color` | Rouge Signal | Dark Packet | The 2px wall on an interactive control. A control identifies itself by this wall, so the wall clears 3:1 |
| `--dr-border-color-container` | Dark Packet ◆ | Dark Packet ◆ | The 2px wall on a container. Content identifies a container, so the wall stays recessed |
| `--dr-border-color-hover` | Firewall Breach | Digital Rust | The control wall on hover, one step further from the surface in both modes |
| `--dr-border-color-invalid` | Amber Alert | `#8D362A` | Invalid controls |
| `--dr-border-color-strong` | Daemon Blood | Copper Trace | The container wall on hover |
| `--dr-border-color-warm` | Stack Overglow | `#533900` | The warm rule on a prose block |
| `--dr-inline-code-bg` | Bad Sector | Phosphor Smoke | Inline code. A fill on dark, a well on light |
| `--dr-scrollbar-thumb` | Rouge Signal | `#9D2917` | Scrollbar thumb. Both clear 3:1 against the page |
| `--dr-scrollbar-thumb-hover` | Firewall Breach | `#8C2414` | Scrollbar thumb on hover |
| `--dr-shadow-glow-sm` | `0 0 10px rgba(175,46,26,0.4)` | none | Radiated heat on primary hover |
| `--dr-shadow-glow-md` | `0 0 20px rgba(175,46,26,0.4)` | none | Hero-level emphasis |
| `--dr-shadow-inner-highlight` | `inset 0 1px 0 rgba(255,255,255,0.1)` | `inset 0 1px 0 rgba(255,255,255,0.5)` | Top-edge bevel |
| `--dr-shadow-inner-lowlight` | `inset 0 -1px 0 rgba(17,4,2,0.6)` | `inset 0 -1px 0 rgba(70,18,10,0.3)` | Bottom-edge shade |
| `--dr-focus-ring` | Stack Overglow | Digital Rust | Focus indicator |
| `--dr-texture-overlay` | The 4px scanline band | A rust scorch at the rim | Surface texture |
| `--dr-vignette` | A warm corner shade | `transparent` | The header shade |
| `--dr-glow-accent` | Digital Rust | `transparent` | Emitted heat on a heading and a status dot |
| `--dr-glow-success` | Memory Leek | `transparent` | Emitted heat on a running indicator |
| `--dr-gutter-fg` | Bad Sector | Phantom Current | A line number is decoration, not text |
| `--dr-gutter-fg-active` | Phantom Current | Copper Trace | The active line number |
| `--dr-term-fg` | `--dr-text-primary` ◆ | `--dr-text-primary` ◆ | Default terminal foreground |
| `--dr-term-bg` | `--dr-surface-base` ◆ | `--dr-surface-base` ◆ | Default terminal background |
| `--dr-term-cursor` | `--dr-cursor` ◆ | `--dr-cursor` ◆ | Terminal cursor |
| `--dr-term-white` | Phosphor Smoke ◆ | Phosphor Smoke ◆ | Terminal slot 7 |
| `--dr-term-red` | Rouge Signal | `#BF3C26` | Terminal slot 1. Digital Rust measures 3.10:1 on the dark page, so it cannot carry terminal text |
| `--dr-term-green` | Memory Leek | `#3B5218` | Terminal slot 2 |
| `--dr-term-yellow` | Stack Overglow | `#533900` | Terminal slot 3 |
| `--dr-term-blue` | Blue Screen | `#495370` | Terminal slot 4 |
| `--dr-term-magenta` | Fractured Shell | `#A84A6D` | Terminal slot 5 |
| `--dr-term-cyan` | Dead Thread | `#1E5451` | Terminal slot 6 |

The badge and diagnostic washes (`--memory-leek-10`, `--amber-alert-15`, and the
rest) hold alpha on the dark theme and a solid value in Safe Mode, composited over
Ghost Pixel. A translucent wash cannot hold its own text contrast on a light
surface, because the panel behind it shows through. See The Light Wash Rule.

One token has no dark value. `--dr-text-quiet` exists because the dark theme paints
a shell prompt, a removed diff line, and footer copy with a pale colour. On a light
page that colour reads at 1.41:1, so Safe Mode needs a separate value.

## Contents

- [Design Principles](#design-principles)
- [Modes](#modes)
- [Surfaces & Texture](#surfaces--texture)
- [Elevation](#elevation)
- [Interaction Physics](#interaction-physics)
- [Typography in UI](#typography-in-ui)
- [Components](#components)
  - [Buttons](#buttons)
  - [Inputs & Text Areas](#inputs--text-areas)
  - [Checkboxes](#checkboxes)
  - [Toggles](#toggles)
  - [Sliders](#sliders)
  - [Tabs](#tabs)
  - [Menus & List Boxes](#menus--list-boxes)
  - [Modals & Dialogs](#modals--dialogs)
  - [Toasts & Notices](#toasts--notices)
  - [Tooltips & Popovers](#tooltips--popovers)
  - [Badges & Tags](#badges--tags)
  - [Callouts & Panels](#callouts--panels)
  - [Scrollbars](#scrollbars)
  - [Links](#links)
  - [Status Bars](#status-bars)
  - [Tables](#tables)
- [States](#states)
- [Accessibility](#accessibility)
- [Do's and Don'ts](#dos-and-donts)

## Design Principles

Digital Rust interfaces are **tactile panels in a failing server rack**. Every
interactive element should feel like a physical piece of chunky old hardware:
a switch you can press, a bezel that catches light, a surface that has weight.

Five rules carry the whole system:

1. **Everything physical has an edge.** Interactive elements carry a 2px wall.
   A control reads `--dr-border-color`, which moves with the mode; a container
   reads `--dr-border-color-container`, which does not. Borders are walls, not
   decoration.
2. **Everything raised casts a hard shadow.** Offset solid shadows, no blur,
   drawn in Bad Sector. Light in this system is directional and warm.
3. **Everything raised catches light.** Elevated and interactive elements carry
   the inner highlight, `inset 0 1px 0 rgba(255, 255, 255, 0.1)`, a faint
   light-line along the top edge. This is the bevel of the panel.
4. **Everything pressable presses.** Rest has offset. Hover grows the offset and
   lifts the element. Active collapses the offset and drives the element into
   the rack. No interactive element is allowed a static hover.
5. **Nothing is flat.** Surfaces carry structural noise and scanlines. The only
   exemptions are interactive text surfaces (inputs, text areas, editable
   regions), which stay clean for legibility, and `prefers-reduced-motion`.

The system rejects: blurred drop shadows, pure black (`#000`) anywhere,
border-radius above 6px, hairline decorative borders, and any cold blue-purple
dark-theme material. The syntax specification defines these as named rules.
They apply equally to UI.

## Modes

A mode changes values, never structure. The two modes share one layout, one set of
component shapes, and one press cycle. A mode layer that changes a selector instead
of a value is a defect, because the next mode needs the same change again.

**The White Is Forbidden Rule.** Pure white (`#FFFFFF`) is prohibited in
backgrounds, borders, shadows, and scrims. Ghost Pixel (`#F7EAE8`) is the lightest
value in the system. The rule mirrors The Black Is Forbidden Rule.

### Surface Direction

The page is the reference surface. Depth moves toward the light in the dark theme
and away from the light in Safe Mode.

| Surface | Dark theme | Safe Mode |
| --- | --- | --- |
| Page (`--dr-surface-base`) | Event Horizon, the darkest stop | Ghost Pixel, the lightest stop |
| Raised panel (`--dr-surface-elevated`) | Core Meltdown, lighter than the page | Phantom Current, darker than the page |
| Well | Event Horizon, darker than its panel | Ghost Pixel, lighter than its panel |

**The Inverted Well Rule.** In Safe Mode a well is the lightest surface in its
panel. The 2px border and the inner lowlight do the recess work, because a well
cannot sit above its panel in a light theme. A well never rises on hover.

### What Each Mode Changes

| Item | Dark theme | Safe Mode |
| --- | --- | --- |
| Texture | Noise and a 4px scanline band | Noise. The scanline band is dropped, because a scanline draws the gap between two lit rows. Texture moves to the rim of the viewport |
| Bevel | Inner highlight at 10% white | Inner highlight at 50% white, and the lowlight carries more of the shade |
| Heat glow | A rust glow on a primary hover | No glow. A glow is emitted light, and it reads as a smudge on a lit page |
| Shadow colour | Bad Sector | Bad Sector, unchanged |
| Hot panels | Toasts and status bars use Bricked Board | No change |
| Header vignette | A warm corner shade | Dropped. The shade reads as soot on a light page, and the page texture does the work |
| Heading shadow | A brand glow and a chromatic split | The chromatic split stays, without the glow |
| Inline code chip | Bad Sector, a fill above the page | Phosphor Smoke, a well |

### Choosing a Mode

A conforming implementation follows this order:

1. Read the mode that the user saved.
2. If the user saved no mode, read `prefers-color-scheme`. Related platform
   indicators of a light or dark preference are also permitted.
3. If the platform gives no preference, use the dark theme.
4. Make sure that a manual switch is available. Some readers need a fixed mode.

Resolve the mode before the first paint. A page that paints the dark theme and then
switches to Safe Mode flashes a bright frame. A page that paints white and then
switches to the dark theme flashes the same way. Set the mode on the root element,
set the `color-scheme` property to match, and set the background of the root
element to the page colour. Without that last step the browser shows a pure white
canvas during a load or an overscroll. The White Is Forbidden Rule bans that value.

## Surfaces & Texture

The rust ramp is the only neutral scale. Semantic surface tokens map onto it:

| Token | Colour | Role |
| --- | --- | --- |
| `--dr-surface-base` | Event Horizon `#110402` | Page background; input wells; code blocks |
| `--dr-surface-secondary` | Null Pointer `#230905` | Elevated chrome: tab bars, gutters, sidebars |
| `--dr-surface-elevated` | Core Meltdown `#340D07` | Cards, popovers, menus, modals, filled hovers |
| `--dr-surface-highlight` | Bad Sector `#46120A` | Hover fills, active list items, shadow colour |
| `--dr-surface-active` | `#5A1810` | Scrollbar hover, pressed wells |

**The Recessed Well Rule.** Inputs and editable regions sit *below* the panel,
not above it. A well uses the darkest surface (Event Horizon), a 2px control wall
border, and no offset shadow. Wells never rise on hover. They respond through
border colour, not elevation. In Safe Mode the well inverts: it uses Ghost Pixel
inside a Phantom Current panel, with the control wall. See
[Modes](#modes). (See [Inputs](#inputs--text-areas).)

**Texture exemption.** The Flat Surface Failure Rule (syntax specification)
applies to all *ambient* surfaces: page backgrounds, cards, panels, modals.
Interactive text surfaces — inputs, text areas, code editors — are exempt.
Texture inside a text field fights the text. Legibility outranks atmosphere.

**Implementation.** The texture is two full-page fixed overlays, stacked above
content with `pointer-events: none`: a faint noise layer (SVG `feTurbulence`
tile at ~2% opacity) and a scanline layer (4px repeating band in
`--dr-scanline` directly under the noise). Content stays below both.

```css
body {
	position: relative;
	z-index: var(--dr-z-content);
}
body::before { /* noise */
	content: "";
	position: fixed;
	inset: 0;
	background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
	opacity: 0.02;
	pointer-events: none;
	z-index: var(--dr-z-noise);
}
body::after { /* scanlines */
	content: "";
	position: fixed;
	inset: 0;
	background: repeating-linear-gradient(0deg,
		transparent,
		transparent 2px,
		var(--dr-scanline) 2px,
		var(--dr-scanline) 4px);
	pointer-events: none;
	z-index: calc(var(--dr-z-noise) - 1);
}
@media (prefers-reduced-motion: reduce) {
	body::before, body::after { content: none; }
}
```

The overlays suppress entirely under `prefers-reduced-motion: reduce`. They never
render inside wells. In Safe Mode the noise layer stays and the scanline layer is
replaced by a rust scorch at the rim of the viewport, so the text plane stays
clean. The syntax specification defines the value.

## Elevation

All structural shadows are offset solids in Bad Sector with zero blur. Light is
always top-left. Shadows fall down-right. The dark theme draws a shadow with the
surface-highlight token, which is Bad Sector. In Safe Mode that token becomes
Corrupted Cache, so the shadow keeps Bad Sector directly.

| Token | Value | Use |
| --- | --- | --- |
| `--dr-shadow-offset-sm` | `2px 2px 0 0` | Resting interactive elements: buttons, checkboxes, toggles |
| `--dr-shadow-offset-md` | `3px 3px 0 0` | Raised containers: menus, toasts, callouts, terminals |
| `--dr-shadow-offset-lg` | `4px 4px 0 0` | Highest layer: modals, lightboxes, drag ghosts |
| `--dr-shadow-hover-md` | `4px 4px 0 0` | Hover state for elements resting at `offset-sm` |
| `--dr-shadow-active` | `0 0 0 0` | Pressed state; the element sits flush |
| `--dr-shadow-glow-sm` | `0 0 10px rgba(175, 46, 26, 0.4)` | Radiated heat on primary hover / focused accents |
| `--dr-shadow-glow-md` | `0 0 20px rgba(175, 46, 26, 0.4)` | Hero-level emphasis only |
| `--dr-shadow-inner-highlight` | `inset 0 1px 0 rgba(255, 255, 255, 0.1)` | Top-edge bevel on raised/interactive elements |
| `--dr-shadow-inner-lowlight` | `inset 0 -1px 0 rgba(17, 4, 2, 0.6)` | Bottom-edge shade for wells and thumbs |

**The No Blur Rule.** Blur radius is zero everywhere except the glow shadows.
The glow shadows simulate radiated heat, not material depth. Safe Mode omits both
glow shadows, because a glow is emitted light and it reads as a smudge on a lit
page. The offset shadow and the bevel remain.

**The Bevel Rule.** The inner highlight accompanies every offset shadow on an
interactive or elevated element. An element with `offset-sm/md/lg` and no inner
highlight is unfinished. Stack the two:

```css
box-shadow:
	var(--dr-shadow-offset-sm),
	var(--dr-shadow-inner-highlight);
```

## Interaction Physics

The press cycle is the system's signature motion. It applies to buttons,
checkboxes, chips, tabs, and any element that behaves like a switch.

| State | Transform | Shadow |
| --- | --- | --- |
| Rest | `translate(0, 0)` | resting offset (`offset-sm` or `offset-md`) + inner highlight |
| Hover | `translate(-2px, -2px)` | offset grows 2px per axis (`hover-md`) + inner highlight |
| Active | `translate(2px, 2px)` | `0 0 0 0`; element drives into the rack |

- Transitions use `--dr-duration-fast` (150ms) with
  `--dr-ease-default` (`cubic-bezier(0.4, 0, 0.2, 1)`).
- The element translates *with* the shadow change: hover lifts toward the light
  (up-left), active presses away from it (down-right). The shadow offset and the
  translation always have the same magnitude and direction.
- **Focus visible**: 2px Stack Overglow outline, 2px offset. Focus never removes
  the resting shadow. Keyboard focus and mouse hover are independent states.
- **Disabled**: no transform, no shadow, muted colours, `cursor: not-allowed`.
  A dead switch does not click.
- `prefers-reduced-motion: reduce` suppresses all of the above: the full state
  *colours* still apply, but transform and transition are removed.

```css
.dr-button {
	transition:
		transform var(--dr-transition-fast),
		box-shadow var(--dr-transition-fast),
		background-color var(--dr-transition-fast),
		border-color var(--dr-transition-fast),
		color var(--dr-transition-fast);
}
.dr-button:hover  { transform: var(--dr-press-hover); }
.dr-button:active { transform: var(--dr-press-active); }
```

## Typography in UI

Digital Rust pairs three typographic roles. The guidance below describes each
role **generically first**. The named faces are examples of the role, not the
only permitted choices.

- **Display, headings, and labels** — a condensed, technical, near-geometric
  sans with a slightly mechanical, engineered feel. Set all-caps with wide
  tracking for short strings (titles, labels, badges). Never set all-caps at
  body size. Example pairing: Rajdhani, with Saira SemiCondensed and Trebuchet
  MS as fallbacks. This is the "embossed server panel" voice.
- **Body and content** — a warm slab serif (or a humanist serif with sturdy,
  bracketed terminals) that stays comfortable over long sessions. Sentence case
  at body size. Example: Zilla Slab, falling back to Bitter, Source Serif 4,
  or Georgia.
- **Monospace readouts** — a monospace with tabular numerals for any value
  where character alignment matters: status bars, hex values, version stamps,
  code. Example: IBM Plex Mono, with Space Mono, Fira Code, Cascadia Code, or
  the platform mono as fallbacks.

These are **general recommendations, not binding rules**. Appropriateness to
the content and the audience is the most important criterion. Pick faces that
read clearly at the required sizes and fit the product's voice. Treat the
guidance above as a tested starting point, not a constraint. If a chosen face
changes the character of the theme, favour warmth and mechanical honesty over
decoration.

Applied to components, the three roles surface as:

- **Button and tab labels**: heading/label sans (for example, Rajdhani) at 600–700,
  uppercase, 0.05em tracking, verb + object ("Save changes", not "OK").
- **Badges and tags**: heading/label sans (for example, Rajdhani) at 700, uppercase,
  0.08em tracking, 0.75rem.
- **Panel and card titles**: heading/label sans (for example, Rajdhani) at 700,
  uppercase, 0.05em tracking, Fatal Error colour (the One Heading Colour Rule).
- **Inputs and text areas**: body serif/sans (for example, Zilla Slab), 1rem, no
  uppercase. Placeholder text is muted and italic.
- **Readouts, status bars, hex values, version stamps**: monospace
  (for example, IBM Plex Mono), 0.875rem or smaller, tabular numerals.

Across all roles, weights, tracking, and case matter more than the exact face.
An equivalent category with matching metrics keeps the system intact.

## Components

A value in a component table is the dark-theme value. Where a table names a
`--dr-*` token, Safe Mode supplies its own value from the token table at the top of
this document, and the component needs no change. A note below a component lists
the steps that Safe Mode adds.

### Buttons

The signature component. A hardware panel switch: thick edge, hard shadow,
light-catching bevel, physical travel.

**Base (all buttons)**

| Property | Value |
| --- | --- |
| Padding | `0.5rem 1.5rem` (`--dr-space-sm --dr-space-lg`) |
| Border | `2px solid` (colour per variant) |
| Radius | `4px` (`--dr-radius`) |
| Font | heading/label sans (for example, Rajdhani) at 600, uppercase, `0.05em` tracking, `--dr-text-sm` |
| Shadow | resting offset + inner highlight |
| Cursor | `pointer` |
| Interaction | full press cycle at 150ms |

**Variants**

| Variant | Rest | Hover | Active |
| --- | --- | --- | --- |
| **Default / Ghost** | Transparent bg, Phosphor Smoke text, the control wall (`--dr-border-color`), `offset-sm` | Core Meltdown fill, Ghost Pixel text, the hover wall (`--dr-border-color-hover`), `hover-md`, lifts | `offset` collapses, presses in |
| **Primary** | Digital Rust bg (`--dr-accent`), Ghost Pixel text, the accent border, `offset-md` | Firewall Breach bg and border (`--dr-accent-hover`), `hover-md` + `glow-sm`, lifts | `offset` collapses, glow removed, presses in |
| **Success** | Memory Leek border and text | Memory Leek bg, Event Horizon text | presses in |
| **Warning** | Stack Overglow border and text | Stack Overglow bg, Event Horizon text | presses in |
| **Error** | Amber Alert border, Amber Alert Text (`#CB6353`) text | Firewall Breach bg and border, Ghost Pixel text (raw Amber Alert fill fails AA with every text colour) | presses in |
| **Disabled** | Bad Sector bg, muted text, no border colour shift | no response | no response; `cursor: not-allowed` |

Reference CSS:

```css
.dr-btn {
	padding: var(--dr-space-sm) var(--dr-space-lg);
	border: 2px solid var(--dr-border-color);
	border-radius: var(--dr-radius);
	background: transparent;
	color: var(--dr-text-secondary);
	font-family: var(--dr-font-heading);
	font-weight: 600;
	font-size: var(--dr-text-sm);
	text-transform: uppercase;
	letter-spacing: 0.05em;
	box-shadow: var(--dr-shadow-offset-sm), var(--dr-shadow-inner-highlight);
	cursor: pointer;
	transition:
		transform var(--dr-transition-fast),
		box-shadow var(--dr-transition-fast),
		background-color var(--dr-transition-fast),
		border-color var(--dr-transition-fast),
		color var(--dr-transition-fast);
}
.dr-btn:hover {
	background: var(--dr-surface-elevated);
	border-color: var(--dr-border-color-hover);
	color: var(--dr-text-primary);
	transform: var(--dr-press-hover);
	box-shadow: var(--dr-shadow-hover-md), var(--dr-shadow-inner-highlight);
}
.dr-btn:active {
	transform: var(--dr-press-active);
	box-shadow: var(--dr-shadow-active);
}
.dr-btn:focus-visible {
	outline: var(--dr-focus-ring);
	outline-offset: var(--dr-focus-ring-offset);
}

.dr-btn-primary {
	background: var(--dr-accent);
	border-color: var(--dr-accent);
	color: var(--dr-text-on-accent);
	box-shadow: var(--dr-shadow-offset-md), var(--dr-shadow-inner-highlight);
}
.dr-btn-primary:hover {
	background: var(--dr-accent-hover);
	border-color: var(--dr-accent-hover);
	transform: var(--dr-press-hover);
	box-shadow:
		var(--dr-shadow-hover-md),
		var(--dr-shadow-glow-sm),
		var(--dr-shadow-inner-highlight);
}
```

**Rules**

- One primary button per view. Primary carries the brand. Two primaries on one
  screen are a visual shouting match.
- Labels are short verbs or verb + object, never "OK" / "Submit" / "Click here".
- Icon + text buttons keep the icon at text size, `0.25rem` gap.
- Full-pill radius is forbidden on buttons. Use `4px` only.

### Inputs & Text Areas

Recessed wells. The darkest surface on the page. The user types into the machine.

| Property | Value |
| --- | --- |
| Background | `--dr-surface-base` |
| Border | `2px solid` `--dr-border-color`, the control wall |
| Radius | `4px` |
| Padding | `0.5rem 0.75rem` |
| Font | body font, `--dr-text-base`, `--dr-text-primary` |
| Shadow | none at rest (wells do not rise) |
| Placeholder | `--dr-text-muted`, italic |
| Hover (unfocused) | the wall shifts to `--dr-border-color-hover` |
| Focus | border shifts to `--dr-border-color-hover`; the focus ring at a 2px offset |
| Disabled | transparent border, `--dr-text-muted`, no shadow |
| Invalid | `--dr-border-color-invalid`; error message in `--dr-danger-text` |

```css
.dr-input {
	background: var(--dr-surface-base);
	border: 2px solid var(--dr-border-color);
	border-radius: var(--dr-radius);
	padding: var(--dr-space-sm) 0.75rem;
	color: var(--dr-text-primary);
	font-family: var(--dr-font-body);
	font-size: var(--dr-text-base);
	transition:
		border-color var(--dr-transition-fast),
		box-shadow var(--dr-transition-fast);
}
.dr-input:hover:not(:focus) { border-color: var(--dr-border-color-hover); }
.dr-input:focus {
	outline: var(--dr-focus-ring);
	outline-offset: var(--dr-focus-ring-offset);
	border-color: var(--dr-border-color-hover);
}
.dr-input::placeholder {
	color: var(--dr-text-muted);
	font-style: italic;
}
```

**Rules**

- Wells are exempt from ambient texture (see The Recessed Well Rule).
- No offset shadows on wells. Depth on inputs is communicated by darkness,
  not elevation.
- Labels sit above the input in the label sans (for example, Rajdhani), uppercase at
  `--dr-text-xs`, in `--dr-text-secondary`.
- **Safe Mode.** The same tokens give a Ghost Pixel well inside a Phantom Current
  panel, so the well is lighter than the panel and the recess reads through the
  control wall and the inner lowlight. The border is the control wall, Dark
  Packet.

### Checkboxes

A small square switch with the full press cycle. 16px box, 2px radius.

| State | Style |
| --- | --- |
| Rest | Event Horizon bg, the control wall, `offset-sm` shadow |
| Hover | the hover wall (`--dr-border-color-hover`), lifts (`-2px, -2px`), `hover-md` |
| Active | presses (`2px, 2px`), shadow collapses |
| Checked | Digital Rust bg and border; Ghost Pixel ✓ mark, 700 weight |
| Focus visible | the focus ring (Stack Overglow on dark, Digital Rust in Safe Mode) at a 2px offset |
| Disabled | no shadow, muted border, `not-allowed` |

The ✓ glyph is drawn centred, 12px at 16px box size. The indeterminate state
uses a Ghost Pixel em-dash block in place of the check.

**Safe Mode.** The box takes `--dr-surface-base`, which is Ghost Pixel, with the
control wall. The checked fill stays Digital Rust with a Ghost Pixel mark. A
disabled box takes `--dr-surface-highlight`, Corrupted Cache, which sits 1.22:1
from its panel. Carry the disabled state with the label as well as the box.

### Toggles

A sliding rack switch. Track: `--dr-machined-track` (Bad Sector) at rest,
Digital Rust when enabled, always with the 2px control wall and `offset-sm`
shadow. Thumb: `--dr-machined-fill` (Ghost Pixel), 2px radius, its own
`1px 1px 0 0` offset in Bad Sector plus inner lowlight. The thumb slides at
150ms. The track does not translate.

**Safe Mode.** The track reads `--dr-machined-track`, so it holds Bad Sector in
both modes. A Ghost Pixel thumb on a light track measures 1.18:1 and disappears.

### Sliders

Track: 4px-high `--dr-machined-rail` (Event Horizon) with the 1px control wall
border. Fill (the "used" portion): Digital Rust. Thumb: 16px square, 2px radius,
`--dr-machined-fill` (Ghost Pixel) with the 2px control wall and `offset-sm`
shadow. The thumb lifts on hover-drag and presses on active-drag. Thumb never
exceeds 2px radius. Sliders are machined parts, not pills.

**Safe Mode.** The rail reads `--dr-machined-rail`, so the Ghost Pixel thumb stays
visible. The used portion is Digital Rust. Keep the progress gradient and do not replace it
with a flat fill, because the fill shows the current value.

### Tabs

Tab bar: Core Meltdown bg with a 2px container wall bottom border. Tabs: Phosphor
Smoke text, 2px radius top corners only. Hover: Ghost Pixel text, Bad Sector bg.
Active tab: Null Pointer bg (surface-secondary), Ghost Pixel text, and a 2px
Digital Rust bottom border that overlaps the bar's border — the active tab is
the one still receiving power. Modified/unsaved indicator: Stack Overglow dot
or `● MOD` label.

### Menus & List Boxes

Container: Core Meltdown bg, the 2px container wall, 4px radius,
`offset-md` + inner highlight, `0.25rem` padding. Items: Phosphor Smoke, 2px
radius, `0.25rem 0.5rem` padding. Hover/selected item: Bad Sector bg, Ghost
Pixel text. Separators: `--dr-border-subtle` (1px), full-bleed. Menus never blur the
content behind them. They are solid panels bolted over it.

### Modals & Dialogs

The highest layer. Core Meltdown bg, the 2px container wall, 4px radius,
`offset-lg` + inner highlight. Title in Fatal Error, label sans (for example, Rajdhani)
at 700 uppercase.
Backdrop: warm-tinted scrim, `rgba(17, 4, 2, 0.7)` — never pure black, never
blurred. The footer action row carries at most one primary button. Modals do
not animate in with scale or bounce. A 150ms fade is the only permitted entry
motion.

### Toasts & Notices

Bricked Board bg (a hotter panel than modals — notices radiate residual heat),
Ghost Pixel text, the 2px container wall, 4px radius, `offset-md` + inner
highlight. Optional 3px left accent border in the semantic colour (Memory Leek
success, Stack Overglow warning, Amber Alert error, Dead Thread info). Auto-
dismiss toasts show a thin Digital Rust progress line along the bottom edge.

**Safe Mode.** The panel stays Bricked Board with Ghost Pixel text. The text takes
the on-hot values, because Memory Leek measures 3.83:1, Dead Thread 3.70:1, and
Amber Alert Text 2.67:1 on Bricked Board. The 3px accent strip keeps the family
base value, because a strip is decoration.

### Tooltips & Popovers

Core Meltdown bg, the 2px container wall, 4px radius, `offset-md` + inner
highlight. Text: Phosphor Smoke at `--dr-text-sm`. No arrow stems. The offset
shadow alone establishes which surface the tooltip floats above. Hover-preview
popovers (rich content) use `offset-lg` and can contain panels. Plain text
tooltips use `offset-md`.

### Badges & Tags

Semantic status chips: small, dense, tracked. 4px radius,
`0.2rem 0.6rem` padding, label sans (for example, Rajdhani) at 700 uppercase, 0.08em
tracking, 0.75rem.

| Kind | Text | Background | Border |
| --- | --- | --- | --- |
| Add / Success | Memory Leek | 15% Memory Leek (`--memory-leek-15`) | 1px Memory Leek 30% (`--memory-leek-30`) |
| Modified / Info | Dead Thread | 15% Dead Thread (`--dead-thread-15`) | 1px Dead Thread 30% (`--dead-thread-30`) |
| Delete / Error | Amber Alert Text | 10% Amber Alert (`--amber-alert-10`) | 1px Amber Alert 25% (`--amber-alert-25`) |
| Warning | Stack Overglow | 15% Stack Overglow (`--stack-overglow-15`) | 1px Stack Overglow 30% (`--stack-overglow-30`) |

Badges are static indicators: no press cycle, no shadow. Full-pill radius is
permitted on badges only. Badges are rivets, not panels.

**The Light Wash Rule.** In Safe Mode a badge layers its tint over Ghost Pixel and
not over its panel. A tint over Phantom Current drops the text below 4.5:1. The
text takes the deep feedback values: `#455925` for success, `#2B5A56` for
information, `#8D362A` for error, and `#6C4D06` for warning.

### Callouts & Panels

Core Meltdown bg, the 2px container wall, 4px radius, `offset-md` + inner
highlight, `1.5rem` internal padding. Semantic variants tint the title and add a
3px left border in Dead Thread (note/info), Stack Overglow (warning), or Amber
Alert (error/danger). The 3px left accent on callouts is a deliberate exception
to the no-accent-stripe rule. It exists so that semantic blocks are
identifiable at scroll speed. Panels that respond to hover (cards, palette
chips) use the press cycle with `offset-sm` rest.

### Scrollbars

Thin (8-10px). Track: transparent. Thumb: `--dr-scrollbar-thumb` (Rouge Signal)
at rest and `--dr-scrollbar-thumb-hover` (Firewall Breach) on hover, so both clear
3:1 against the page; `--dr-surface-active` on drag. 2px radius. Scrollbars are machine
rails: they never glow, never widen past 12px, and never disappear on idle.
Visibility of scroll position is a readability concern.

### Links

Default: Phantom Current, 1px underline, 3px offset. Hover: Fatal Error, 2px
underline. Visited: Corrupted Cache. Active: Digital Rust. Focus: 2px Stack
Overglow outline, 2px offset. Underline style carries semantics: solid for
links, wavy for diagnostics. Never style a diagnostic squiggle as a link or a
link as a diagnostic squiggle.

**Safe Mode.** A link is Kernel Panic, a hover is Data Rot, and a visited link is
Bricked Board. Phantom Current measures 1.41:1 on a light page, so it cannot carry
a link there.

### Status Bars

Bricked Board bg, Ghost Pixel text, the 2px container wall as a top border, monospace
(for example, IBM Plex Mono) at `--dr-text-xs`, `0.25rem 0.5rem` item padding. Items respond
to hover with Phantom Current text only — status bar items do not lift.
Semantic items: Memory Leek (success/OK), Stack Overglow (warning), Amber Alert
Text (error).

**Safe Mode.** The bar stays dark. Semantic items take the on-hot values:
`#9AB867` for success, `#D9A540` for warning, `#FF8E7B` for error, and `#73BAB4`
for information.

### Tables

Header row: label sans (for example, Rajdhani) at 700 uppercase `--dr-text-xs`,
`--dr-heading` text, the 2px container wall as a bottom border. Body: body serif/sans
(for example, Zilla Slab), Ghost Pixel. Row separators: `--dr-border-subtle`.
Row hover: `--dr-surface-highlight`.
Zebra striping is forbidden. The scanline overlay already bands the page.
Double banding reads as interference.

## States

| State | Universal rule |
| --- | --- |
| Hover | Lift toward the light (`-2px, -2px`) + offset grows. Non-pressable surfaces (rows, list items) respond with Bad Sector fill instead of lift. |
| Active (press) | Drive into the rack (`2px, 2px`) + offset collapses to zero. |
| Focus visible | 2px Stack Overglow outline, 2px offset, *in addition to* the resting shadow. Never use `outline: none` without a replacement. |
| Selected | Bad Sector bg + Ghost Pixel text (lists, menus; tabs use the tab rules). |
| Disabled | Bad Sector or transparent bg, muted text, no shadow, no transform, `cursor: not-allowed`. |
| Loading / Processing | Stack Overglow with motion (spinner, pulse). Under reduced motion, a static Stack Overglow text label. |
| Invalid / Error | Amber Alert border on the control + Amber Alert Text message. Errors never rely on colour alone: an icon or text accompanies them. |

### Loading & skeletons

Busy indicators are readouts, not pressables: Stack Overglow text or spinner,
monospace, never a press cycle. Skeleton placeholders are recessed wells, not
raised panels: Event Horizon fill with the control wall, plus a 2px-high
Stack Overglow progress line along the bottom edge when determinate progress
is known. Indeterminate work pulses the line at 400ms
(`--dr-duration-slow`). Under `prefers-reduced-motion`, the pulse is removed
and the label alone carries the state.

## Accessibility

- WCAG 2.1 AA contrast is required on all text in both modes, as defined in the
  syntax specification. Ghost Pixel on Event Horizon (17.17:1) is the dark
  baseline. Event Horizon on Ghost Pixel (17.17:1) is the Safe Mode baseline.
- Amber Alert Text (`#CB6353`) is the AA-compliant text variant of Amber Alert on
a dark page. Use it for error text on the dark base and dark elevated surfaces,
never the raw `#BF5747`.
- A hot panel is the exception. On Bricked Board, Amber Alert Text measures 2.67:1,
  because the panel is too light for that value. A toast and a status bar use the
  on-hot text values instead: `#9AB867`, `#D9A540`, `#FF8E7B`, and `#73BAB4`.
- Every interactive element is reachable and operable by keyboard, with a visible
  focus indicator: the `--dr-focus-ring` token, a 2px outline at a 2px offset.
  The ring is Stack Overglow on dark and Digital Rust on light. Both clear 3:1
  against every surface in their mode.
- State is never communicated by colour alone: errors carry icons or text,
  diffs carry +/− glyphs, focus carries an outline.
- `prefers-reduced-motion: reduce` suppresses all transforms, transitions, the
  cursor blink, and the texture overlays. All state *styling* remains.
- Touch targets are at least 24×24px. The press cycle's 2px travel does not
  count toward target size.
- Both modes are available to every reader. Do not force one mode. A reader who
  needs a light interface must be able to reach Safe Mode, and a reader who needs
  a dark interface must be able to keep the dark theme.

## Do's and Don'ts

### Do

- **Do** stack the inner highlight under every offset shadow on interactive and
  elevated elements. The bevel is half the material.
- **Do** run the full press cycle on every pressable element: rest offset,
  hover lift, active collapse.
- **Do** keep wells (inputs) dark and recessed. Depth through darkness, not
  elevation.
- **Do** give every modal exactly one primary action.
- **Do** use Bricked Board for toasts and status bars. It reads as a hotter,
  more urgent panel than Core Meltdown.
- **Do** keep every wall at 2px: a control reads `--dr-border-color`, and a
  container reads `--dr-border-color-container`.
- **Do** provide both modes. Resolve the mode before the first paint, and offer a
  manual switch.
- **Do** keep hot panels dark in Safe Mode. Toasts and status bars keep their fills
  and take the on-hot text values.
- **Do** keep machined parts dark in Safe Mode. Toggle tracks and slider tracks keep
  a dark fill, because a light thumb on a light track disappears.

### Don't

- **Don't** blur any shadow. Glows simulate heat. Nothing else blurs.
- **Don't** use pure black (`#000`) anywhere, including scrims and backdrops.
  Warm near-blacks only.
- **Don't** give inputs offset shadows or lift them on hover. Wells stay in
  the rack.
- **Don't** use radius above 6px on any component. The default is 4px, 2px for
  small machined parts (checkboxes, toggle thumbs, slider thumbs, menu items).
- **Don't** animate modals or toasts with scale, spring, or bounce. A 150ms
  fade is the maximum drama permitted.
- **Don't** remove focus outlines. The `--dr-focus-ring` ring is the system's
  one non-negotiable courtesy.
- **Don't** force a mode on a reader. Read the saved preference, then
  `prefers-color-scheme`, then the dark theme.
- **Don't** use pure white (`#FFFFFF`) in Safe Mode. Ghost Pixel is the white point.
- **Don't** paint a mode by changing a selector. Change a value. A mode layer that
  changes structure makes the next mode repeat the work.
- **Don't** render a syntax role on a raised panel in Safe Mode. The quiet roles
  fall below 4.5:1 there.
- **Don't** place two primary buttons in one view.
- **Don't** apply the press cycle to passive content (headings, body text,
  badges). Static things stay bolted down.

---

_Digital Rust © 2025–2026 by [Kat Suricata](https://KatSuricata.com/) is dual-licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) or [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)._
