Colour
RisingSwamp uses a dark-first palette driven by clinical argument (Moderate Brain Arousal model), not preference. Colour space is OKLCH for perceptually uniform lightness across hues. Contrast validation uses APCA (Advanced Perceptual Contrast Algorithm), not WCAG 2.1.
The palette is multi-theme: every surface, text, and accent token flips per [data-theme] on <html>. Today there are three themes — Delight (night, cool dark, cyan), Relight (dawn, warm off-white, teal), Gilt (golden hour, warm dark, amber) — with Vesper and Bleach planned.
How to use this page
Section titled “How to use this page”- Day-to-day component code: consume Semantic Tokens (the first three sections below). They flip per theme automatically.
- Adding a new theme or extending the palette: see Per-theme Primitive Palettes further down. Don’t reach into these from feature code — that bypasses the multi-theme contract.
- The full naming rules live in
notes/design/TOKEN-NAMING.md.
Two-Plane Rule
Section titled “Two-Plane Rule”- Plane 1 (UI): Only the active accent (
accent.default) as chromatic accent. No phenomenon colours —phenomena.*(plural) tokens are forbidden in any Plane-1 file. - Plane 2 (Environment): Atmospheric — phenomenon slots live here exclusively (
phenomena.atmospheric-base,phenomena.scatter-points,phenomena.directional-wash,phenomena.cool-edge,phenomena.long-tail-glow, optionalphenomena.warning-edge). Each theme fills the slots with its own phenomenon (foxfire / firefly / ember / etc.).
Phenomenon colours in a UI element permanently destroy the scarcity-based neurological impact. The exception is the singular phenomenon.marker semantic alias used by Tag (variant="phenomenon") and Toast (type="phenomenon") to signal Plane-2 source from Plane-1 chrome — same value as phenomena.long-tail-glow per theme, but namespaced so the Plane-1 lint guard targets only the plural form.
Semantic Tokens — consume these
Section titled “Semantic Tokens — consume these”Each token below resolves through a [data-theme] condition; the hex shown is the Delight resolution. Switch the active theme to see the value flip live.
Surface
Section titled “Surface”Surface · Plane 1
| Semantic token | CSS var | Use |
|---|---|---|
bg.canvas | --colors-bg-canvas | Page background — deepest layer |
bg.default | --colors-bg-default | Default surface — cards, panels |
bg.subtle | --colors-bg-subtle | Subtle lift over canvas |
bg.muted | --colors-bg-muted | Muted panel — inputs, nested elements |
bg.emphasized | --colors-bg-emphasized | Emphasised surface — hover state, active panel |
bg.disabled | --colors-bg-disabled | Disabled control background |
border.default | --colors-border-default | Default border colour |
RisingSwamp aliases (all themes-aware): bg.surface, bg.surface.alt, bg.dim, bg.secondary. See panda.config.ts for the full set.
Text · Plane 1
| Semantic token | CSS var | Min size | Use |
|---|---|---|---|
text.DEFAULT | --colors-text | Any | Primary text |
text.secondary | --colors-text-secondary | --fs-base | Secondary body text |
text.tertiary | --colors-text-tertiary | --fs-sm | Labels, descriptive copy |
text.muted | --colors-text-muted | --fs-xs | Metadata, hints. Never below --fs-xs. |
The Park UI–aligned aliases (fg.default, fg.muted, fg.subtle, fg.disabled, fg.error) resolve to the same values; either namespace is acceptable in component code.
Accent
Section titled “Accent”The accent is the most visible per-theme token: cyan in Delight, teal in Relight, amber in Gilt.
Accent · Saltie's colour (Delight resolution shown)
| Semantic token | CSS var | Use |
|---|---|---|
accent.default | --colors-accent-default | Saltie’s hue; focus rings; interactive affordances |
accent.fg | --colors-accent-fg | Text on top of accent surfaces (e.g. solid primary button) |
accent.dim | --colors-accent-dim | Subtle accent backgrounds; focus ring fill (alpha ≈ 0.12) |
accent.glow | --colors-accent-glow | Glow shadow on primary buttons (alpha ≈ 0.25) |
accent.subtle | --colors-accent-subtle | Lightest accent fill — sidebar nav active state (alpha ≈ 0.08) |
colors.saltie | --colors-saltie | Saltie’s primary presence colour (rare ceremonial state) |
accent.fg design note: picks contrast deliberately per theme so text on accent surfaces never disappears. Delight and Gilt use a deep dark (gray.950 / gray-gilt.950); Relight uses gray-relight.50 (the inverted “extreme” of the Relight scale, #0c1217) — naive gray.950 conditioning would resolve to #f0ebe0 in Relight (the page bg) and render the text invisible.
Per-theme Primitive Palettes — reference
Section titled “Per-theme Primitive Palettes — reference”App code should never reach into these directly — go through a semantic token above. These exist so theme designers can see the concrete values backing each accent and verify contrast / harmony decisions.
Accent palettes across all themes
The same shape (DEFAULT, emphasized, dim, glow, plus alpha40 / alpha08) repeats per palette. Each theme picks values that fit the hour:
- Delight: electric cyan against night-water blacks.
- Relight: desaturated teal — the same hue seen through morning atmosphere.
- Gilt: amber gold — Saltie gilded at golden hour, not bioluminescent.
Per-theme gray scales (gray-delight, gray-relight, gray-gilt) follow the same pattern: 13 steps each (950 → 50), mirrored indices, theme-specific values. The semantic surface and text tokens above route through them.
Plane 2 Phenomena (Plane 2 only)
Section titled “Plane 2 Phenomena (Plane 2 only)”Phenomena are role-named slots, not phenomenon-named tokens. Plane-2 components reach for a slot (phenomena.scatter-points) and get the right concrete phenomenon for the active theme — foxfire in Delight, foxfire-muted in Relight, firefly in Gilt. Adding a future theme (Vesper, Bleach) is a single condition entry per slot in panda.config.ts; no Plane-2 component needs a code change.
Phenomena slots · the Delight resolution shown live
| Slot token | CSS var | Role | Delight | Relight | Gilt |
|---|---|---|---|---|---|
phenomena.atmospheric-base | --colors-phenomena-atmospheric-base-* | Radial gradient defining where the light comes from | bioluminescence (below) | dawn glow (above) | low sun (side) |
phenomena.scatter-points | --colors-phenomena-scatter-points | Small discrete bright points, often animated | foxfire | foxfire-muted | firefly |
phenomena.directional-wash | --colors-phenomena-directional-wash | Diffuse warm or cool wash from one direction | aurora-g | aurora-g-muted | ember |
phenomena.cool-edge | --colors-phenomena-cool-edge | The “periwinkle weird-side” phenomenon | elmo | elmo-muted | copper |
phenomena.long-tail-glow | --colors-phenomena-long-tail-glow | Soft diffuse glow hanging in air | glory | glory-muted | pollen |
phenomena.warning-edge | --colors-phenomena-warning-edge | Burning sky edge (Gilt only — others fall back quietly to long-tail-glow) | (silent) | (silent) | dusk-r |
These slot tokens must never be referenced from a Plane-1 file. A pnpm test guard (src/lib/no-phenomena-in-plane-1.test.ts) fails the build if any Plane-1 source references phenomena.* (plural). Plane-1 components that need a phenomenon-coloured signal use the singular phenomenon.marker alias instead — see the Two-Plane Rule above.
The atmospheric-base slot is split into three sub-tokens (position, inner, outer) consumed by AtmosphericLayout to assemble the full radial gradient inline. The remaining slots resolve to a single colour each.
Elevation
Section titled “Elevation”No shadows. Depth via background tone only:
bg.canvas → bg.default → bg.muted → bg.emphasizedThree to four levels max. No further nesting.
Contrast Requirements
Section titled “Contrast Requirements”All text must pass APCA at its rendered size and weight:
- Body text: Lc 60+
- Large text (≥24px / ≥18.5px bold): Lc 45+
text.mutedat--fs-xsis the floor — do not go smaller
- Consume semantic tokens in component styles, never primitives
- Validate contrast with APCA, not WCAG 2.1 ratio
- Trust the per-theme conditioning — do not hardcode hex fallbacks for a specific theme
- Reach into the per-theme primitive palettes (
gray-relight.X,accent-gilt.Y) from feature code - Use raw hex values in component code (the focus-ring exception in
custom.cssis a docs-site artefact, not a pattern to copy) - Use
phenomena.*(plural) slot tokens in any Plane-1 file (use the singularphenomenon.markeralias for phenomenon-signalling Tag/Toast variants) - Use
text.mutedbelow--fs-xs - Use drop shadows for elevation