Skip to content

Omadia UI — Visual Specification

Archived snapshot — v0.2

Lume material adoption. Reconstructed from commit 57282cc (2026-05-19, Christian Wendler). View latest → · Version history →

Material: Lume — light-as-material. Three user-bindable palettes (Petrol · Atelier · Lagoon, Lagoon = default). Codex-review-ready in the CONCEPT.md cadence.

Version 0.2 — Lume material adoption. Light-as-material thesis introduced; surface luminosity, accent-as-illumination, directional borders and soft corners formalised as the four Lume forces. Three curated palettes replace v0.1's single-accent slot; the choice is user-bindable per contextKey, set conversationally (no Settings screen). Radius scale shifted one stop softer (editor surfaces stay at 0). Two-stop glow primitive replaces v0.1's single accent-subtle tint. Patch-apply changes from fade-in to condensation. Token model gains accent-glow-core for the inner light source. Companion previews at visual-spec-preview.html (flat v0.1 baseline) and visual-spec-preview-lume.html (this material).

Earlier — v0.1: first draft, written against CONCEPT.md v0.7 and walkthroughs.md; flat tokens, single-accent choice, restraint baseline. Superseded by this document; preview retained for material-comparison purposes.


0. How to read this document

  • All values are semantic tokens. Implementers consume them through a tokens module; raw values appear in exactly one place — the token definitions in §2.
  • Tokens are expressed in OKLCH as the source of truth; the token-build step generates sRGB hex for renderers that can't consume OKLCH. Both forms appear in this document so reviewers can validate visually.
  • Lume-specific implementation recipes (the CSS for two-stop glow, donut glow, patch-condensation animation) live in §3 as a single normative block. Per-primitive sections in §4 reference §3 rather than redefining them.
  • ASCII wireframes and tables are the primary illustration formats. Pixel-level visuals live in the Lume preview HTML, not in this Markdown.
  • Rationale — blocks document alternatives that were weighed. Reviewers should attack the rationale, not just the choice.

Non-negotiable constraints inherited from CONCEPT.md

  1. Single material identity. Lume is the material. No era-skinning. Era references resolve to layout idioms (Norton Commander, Photoshop workspace, …), never to visual mimicry.
  2. macOS-first. Windows next, Linux power-user subset. The macOS rendering quality is the bar.
  3. Data-dominant typography. Data carries weight; chrome recedes. Hierarchy is typographic plus light-driven, never via heavy chrome.
  4. One accent slot. Three curated palettes bind to the same slot; the slot itself is single. No status-pill salad.
  5. Skeletons, no spinners for loading (one documented exception in §7.3).
  6. Keyboard-first. Visible focus, ⌘K palette, full arrow-key reach.
  7. Editor-class first-class. canvas-region, timeline, media, vector-path render credibly in the same material that renders a table — while keeping a sharp, opaque boundary marking where Lume stops.

Reference apps (orientation, never mimicry)

  • Apple's design lineage — Aqua, iOS 7 frosted glass, visionOS spatial glass, Liquid Glass (2025). Lume is influenced by, not derived from, this lineage. We adopt light-as-material; we reject refraction, blur-everywhere, and specular-highlights-on-every-chrome (the Linear "ProKit" lesson for productivity-grade UIs).
  • Linear — typography rigour, density, command palette UX, ProKit philosophy.
  • Things 3 — restraint, generous whitespace.
  • Raycast — Spotlight idiom, compact result lists.
  • Tremor — chart restraint.
  • shadcn-ui — token discipline and composability.

Explicitly NOT references: Confluence, Microsoft Teams, JIRA Cloud, Figma sidebars, Slack.


1. Material — Lume

1.1 Thesis

Apple has never shipped "colors and shadows"; each generation has shipped a named material with physical properties (Aqua → frosted glass → spatial glass → Liquid Glass). The current Omadia v0.1 spec adopted tokens in the Linear/Notion lineage — correct in spirit, but materially flat: it had no material story.

Lume is the proposed Omadia material: UI is not drawn, it is condensed out of light. The agent's attention is visible as accent-tinted illumination on the surface it touches. Backgrounds carry a subtle directional luminosity that suggests they are generated, not printed. The single accent slot has two visual forms — fill (the hard form, on buttons and indicators) and glow (the soft form, as halos at selection and focus).

This expresses the Omadia thesis directly. CONCEPT.md says the agent materialises UI per turn; the material identity must say so too.

1.2 The four forces

Everything else in this spec is composition of these four.

ForceWhat it doesWhere it shows up
Surface luminosityEvery surface is a 180° linear-gradient from a slightly lit top to a slightly settled bottom (~1.5% L delta). Imperceptible per-surface; cumulative effect: surfaces feel illuminated, not printed.bg.canvas, bg.surface, bg.surface.raised, bg.surface.sunken, modal surface — all carry a .top / .btm pair.
Accent as illuminationThe single accent token splits into two visual modes. accent is the fill (buttons, indicators). accent-glow + accent-glow-strong are the accent-tinted corona of a light source. accent-glow-core is the bright inner core that reads as emitted light rather than as accent-tinted shadow.Selection halos, focus rings, button-in-flight, active-tool indicators, modal-pane glow.
Directional bordersBorders use a lighter top color and a slightly stronger btm color, so an edge reads as catching light from above. Combined with a 1px inner highlight on raised surfaces, every container gains perceived thickness.All container, input, button, card, modal edges.
Soft corners with editor exceptionLume shifts the v0.1 radius scale up by one stop. Light has no edges; the material's softness expresses the metaphor. The exception is the editor boundary — canvas-region, timeline and similar surfaces stay at radius 0. That visual contrast doubles as a Tier-1 boundary marker.Radius scale §2.9.

1.3 What Lume is NOT

OutReason
RefractionLinear's ProKit team rejected refraction explicitly — it makes dense data-driven UIs harder to read. The boundaries between cells in a table need to be stable; refraction wiggles them.
Real-time blur as primary chromeWe're not in spatial computing. Blur costs performance and competes with text legibility on data surfaces.
Specular highlights on every surfaceApple uses them everywhere in Liquid Glass; we use them only at semantic-meaning moments (active tool, focused input).
GlassmorphismFrosted-everything is a 2020 aesthetic dead end. Lume is solid light, not see-through plastic.
Multiple accent slotsStill exactly one. Three palettes bind to it. Not three accents at once.
Settings / Preferences UI for palettePalette is set conversationally, per CONCEPT.md prefs model. The user says "make it warmer"; Tier 2 writes to ui-prefs.

1.4 Apple-lineage credits

Lume is influenced by Apple's progression from Aqua through Liquid Glass and by Linear's ProKit adaptation. We take light shapes hierarchy as the load- bearing idea. We discard the optical effects (refraction, specular sheen on chrome, glass-everywhere translucency) that don't translate to a productivity-grade canvas. The result is light-as-material at productivity- tool throttle: subtle enough to recede, present enough to feel.


2. Design tokens

2.1 Token model

Token classv0.1 shapev0.2 / Lume shape
Surface backgroundsSingle value (bg.canvas, bg.surface, …)Pair: <class>.top + <class>.btm, consumed as a linear-gradient
BordersSingle value (border.subtle, border.default)Pair: <class>.top + <class>.btm, applied as border-top-color + border-color
AccentOne value + hover/active/subtleOne value + hover/active/subtle + glow + glow-strong + glow-core
TextUnchangedUnchanged
Semantic statesUnchangedUnchanged

OKLCH is the source of truth. The token-build step generates sRGB hex for legacy renderers; the conversion is mechanical and lives in the build pipeline, not in product code.

2.2 Surface tokens

Light mode

TokenOKLCH topOKLCH btmsRGB topsRGB btmUse
bg.canvas0.992 0.002 2500.975 0.004 250#FDFDFE#F7F8FBWorkspace background
bg.surface1.00 0 00.985 0.003 250#FFFFFF#FAFAFDPrimary content surface
bg.surface.raised1.00 0 00.99 0.002 250#FFFFFF#FCFCFECards, popovers, inputs
bg.surface.sunken0.965 0.004 2500.945 0.005 250#F2F3F7#ECEDF2Code blocks, hover, secondary
bg.modal.surface1.00 0 00.99 0.003 250#FFFFFF#FBFBFEModal pane interior
bg.modal.overlay0.30 0.02 250 / 0.40rgba(20,30,50,0.40)Modal scrim — minimally accent-tinted, never neutral black

Dark mode

TokenOKLCH topOKLCH btmsRGB topsRGB btmUse
bg.canvas0.20 0.01 2500.175 0.012 250#232631#1B1D24Workspace background
bg.surface0.22 0.012 2500.20 0.012 250#2A2D38#23262FPrimary content surface
bg.surface.raised0.245 0.013 2500.215 0.012 250#303440#292C37Cards, popovers, inputs
bg.surface.sunken0.18 0.011 2500.15 0.01 250#1D1F26#16181ESunken
bg.modal.surface0.245 0.013 2500.215 0.012 250#303440#292C37Modal pane interior
bg.modal.overlay0 0 0 / 0.60rgba(0,0,0,0.60)Modal scrim

Renderer rule. Every surface is rendered as background: linear-gradient(180deg, <token>.top 0%, <token>.btm 100%). Renderers that can't gradient (extreme legacy) fall back to <token>.btm. The visible delta is small per-surface and cumulative across the screen.

2.3 Text tokens

TokenOKLCH (light)sRGB (light)OKLCH (dark)sRGB (dark)Use
text.primary0.22 0.01 250#1B1D240.96 0.005 250#EEEFF3Headings, body, data
text.secondary0.45 0.01 250#5B5F6B0.75 0.008 250#B6B9C3Labels, captions
text.tertiary0.62 0.01 250#8D90990.58 0.008 250#888B95Hints, placeholders
text.disabled0.78 0.005 250#BFC1C60.38 0.008 250#525561Disabled controls
text.inverse0.99 0.002 250#FCFCFD0.20 0.01 250#1F2127Text on accent or inverse
text.accentper paletteper paletteper paletteper paletteLinks, accent-emphasised values — resolves to the active palette's accent

2.4 Border tokens — directional

Borders are not single colors. They render as 1px solid <class>.btm with an explicit border-top-color: <class>.top override, so the top edge of the border catches more light than the bottom. Combined with a 1px white-tinted box-shadow inset on raised surfaces, this gives every container a perceived thickness.

Light mode

Tokentop colorbtm colorUse
border.subtlergba(20, 24, 36, 0.05)rgba(20, 24, 36, 0.09)Default container, table cell, divider
border.defaultrgba(20, 24, 36, 0.08)rgba(20, 24, 36, 0.14)Inputs, buttons, outlines
border.strongrgba(20, 24, 36, 0.16)rgba(20, 24, 36, 0.26)Pressed, prominent edges
border.focusresolves to accentresolves to accentFocus ring (override, not tinted)

Dark mode

Tokentop colorbtm colorUse
border.subtlergba(255, 255, 255, 0.06)rgba(0, 0, 0, 0.40)Default
border.defaultrgba(255, 255, 255, 0.10)rgba(0, 0, 0, 0.50)Inputs, buttons
border.strongrgba(255, 255, 255, 0.18)rgba(0, 0, 0, 0.60)Pressed
border.focusresolves to accentresolves to accentFocus ring

Dark mode's top color is white-tinted and btm color is shadow-tinted — the same physics rule as light mode, expressed in inverted lightness. Light comes from above, regardless of theme.

2.5 Accent tokens — three palettes

The accent token shape

Every palette defines exactly the same seven sub-tokens for each of light and dark mode. The palette binding (§2.5.4) chooses which palette's values fill this shape; the rest of the spec only references the abstract token names.

Sub-tokenTypeRole
accenthexThe fill — buttons, focus rings, selection bars, indicator dots
accent.hoverhexHover state of accent fills
accent.activehexPressed state of accent fills
accent.subtlergba ~10%Selected-row fill tint, accent-background wash
accent.glowrgba ~22%Soft accent-tinted corona at selection, hover, halo
accent.glow-strongrgba ~38%Stronger corona at focus, active tool, hover-emphasis
accent.glow-corergba ~55%The bright inner light source — white-shifted, not accent-tinted. Closes the light-mode-vs-dark-mode asymmetry the v0.1 single-tint approach had

2.5.1 Palette: Petrolcomputational ambient

Cool steel-blue, hue 235°. Story: the agent as steady daylight, quiet ambient presence. Most-restrained of the three; doesn't impose a strong identity.

Light mode — OKLCH 0.55 0.16 235 base · sRGB #0F7AB8

Sub-tokenOKLCHsRGB / rgba
accent0.55 0.16 235#0F7AB8
accent.hover0.50 0.17 235#0C6CA8
accent.active0.45 0.17 235#0A5E94
accent.subtlergba(15, 122, 184, 0.10)
accent.glowrgba(15, 122, 184, 0.22)
accent.glow-strongrgba(15, 122, 184, 0.36)
accent.glow-corergba(165, 215, 240, 0.55)

Dark mode — OKLCH 0.72 0.13 235 base · sRGB #52B0E2

Sub-tokenOKLCHsRGB / rgba
accent0.72 0.13 235#52B0E2
accent.hover0.78 0.13 235#74C0E8
accent.active0.82 0.12 235#90CFEE
accent.subtlergba(82, 176, 226, 0.16)
accent.glowrgba(82, 176, 226, 0.28)
accent.glow-strongrgba(82, 176, 226, 0.44)
accent.glow-corergba(197, 229, 245, 0.45)

2.5.2 Palette: Atelierstudio warmth

Warm burnt-amber, hue 50°. Story: studio lamp, the agent as craftsman lighting the work. Strongest narrative fit with "agent materialises UI" (workshop metaphor). Separated from semantic states by L+C distance even though hue neighbours error (25°) and warning (80°).

Light mode — OKLCH 0.57 0.13 50 base · sRGB #B36B2E

Sub-tokenOKLCHsRGB / rgba
accent0.57 0.13 50#B36B2E
accent.hover0.52 0.13 50#9F5C26
accent.active0.47 0.13 50#8A4E1F
accent.subtlergba(179, 107, 46, 0.10)
accent.glowrgba(179, 107, 46, 0.24)
accent.glow-strongrgba(179, 107, 46, 0.38)
accent.glow-corergba(245, 215, 175, 0.55)

Dark mode — OKLCH 0.76 0.12 60 base · sRGB #E0A26B

Sub-tokenOKLCHsRGB / rgba
accent0.76 0.12 60#E0A26B
accent.hover0.80 0.12 60#E5B080
accent.active0.84 0.11 60#EBBE93
accent.subtlergba(224, 162, 107, 0.18)
accent.glowrgba(224, 162, 107, 0.30)
accent.glow-strongrgba(224, 162, 107, 0.46)
accent.glow-corergba(250, 228, 200, 0.45)

2.5.3 Palette: Lagoon (default)lit water / bioluminescence

Lit teal-cyan, hue 200°. Story: light passing through shallow water. Strongest light-metaphor coherence of the three — accent.glow-core is bright cyan-white that reads as emitted light, not as accent-tinted shadow. Refined from an earlier "Botanical" draft (hue 195°, L 0.54) which under-played the light metaphor in light mode.

Light mode — OKLCH 0.58 0.12 200 base · sRGB #1F8FA3

Sub-tokenOKLCHsRGB / rgba
accent0.58 0.12 200#1F8FA3
accent.hover0.53 0.12 200#197D90
accent.active0.48 0.12 200#146B7C
accent.subtlergba(31, 143, 163, 0.12)
accent.glowrgba(60, 175, 195, 0.32)
accent.glow-strongrgba(60, 175, 195, 0.48)
accent.glow-corergba(180, 238, 248, 0.60)

Dark mode — OKLCH 0.78 0.10 200 base · sRGB #6FC8D6

Sub-tokenOKLCHsRGB / rgba
accent0.78 0.10 200#6FC8D6
accent.hover0.82 0.10 200#88D2DE
accent.active0.85 0.09 200#A1DCE6
accent.subtlergba(111, 200, 214, 0.20)
accent.glowrgba(111, 200, 214, 0.32)
accent.glow-strongrgba(111, 200, 214, 0.48)
accent.glow-corergba(210, 245, 250, 0.50)

2.5.4 Palette binding — user-controlled, context-aware

The palette is user-bound, not agent-bound. The Skill never picks a palette; it only references the abstract accent token. The user binds the token to a palette via conversational preference:

"Mach das atelier-warm." / "Ich brauche Petrol heute." / "Switch to Lagoon."

Storage: memory://ui-prefs/<tenantId>/<userId>/<contextKey>/accent carries one of "petrol" | "atelier" | "lagoon". Default (no value set): "lagoon".

Per contextKey. The CONCEPT.md identity model already provides context-aware preferences keyed by contextKey. Palette is one such preference. A user can have Lagoon as their default work palette, Petrol on a finance-review canvas, Atelier on a creative-draft canvas. The Tier-2 orchestrator loads the right palette at canvas-activate time.

Switching mid-session. When the palette preference changes during a session, Tier 2 emits a surface_patch that re-tints accent tokens. Tree structure is unchanged; only colors update. The patch increments treeRevision as any other patch does — clients render the change as a short crossfade (see §6.1).

No Settings UI. CONCEPT.md is explicit: user preferences are conversational, never set via a Preferences pane. The palette follows that rule. The UI Skill carries a palette-binding-protocol block (cross-ref CONCEPT.md §"The UI Skill") that lists trigger phrases and the persistence mechanic.

Marketing default. App icon, splash, screenshots, demo videos render in Lagoon. The other two palettes are equal first-class options at runtime but not equal in brand presence.

Rationale. v0.1 forced a single color choice and surfaced eight open questions just on that decision. The 2026 industry trend (per Pantone Cloud Dancer + multiple SaaS-design surveys) is away from a single "Startup Blue" toward palettes that fit context. Apple lets users pick the system accent on macOS and iOS; OS-like surfaces (which Omadia is) follow that convention. The user gets agency without the agent gaining a skinning capability. The single-material constraint is preserved.

2.6 Semantic state tokens

Intentionally text-only — never filled pills, badges or block fills. This rule from v0.1 stands.

Light mode

TokenOKLCHsRGBUse
state.loading0.90 0.008 250#DCDEE3Skeleton base
state.loading.hi0.96 0.004 250#EFEFF2Skeleton pulse highlight
state.error.fg0.45 0.12 25#A8443BError text (never as pill bg)
state.error.edge0.55 0.14 25#C45A501px error border
state.success.fg0.42 0.10 150#3F7A55Success text
state.warning.fg0.50 0.09 80#8C6A1FWarning text

Dark mode

TokenOKLCHsRGBUse
state.loading0.30 0.01 250#3E414CSkeleton base
state.loading.hi0.38 0.012 250#525561Skeleton highlight
state.error.fg0.75 0.12 25#E08577Error text
state.error.edge0.65 0.14 25#C5685AError border
state.success.fg0.78 0.10 150#88C499Success text
state.warning.fg0.80 0.09 80#D6B468Warning text

Hue separation from accent palettes:

PaletteΔ to error 25°Δ to warning 80°Δ to success 150°
Petrol 235°210°155°85°
Atelier 50°25°30°100°
Lagoon 200°175°120°50°

Atelier has the tightest hue separation from error/warning; L+C separation takes over there (error at L 0.45 hue 25 vs Atelier at L 0.57 hue 50 is clearly distinct in both lightness and chroma to daltonism-simulator passes of red-green and blue-yellow types).

2.7 Typography

Unchanged from v0.1. Inter for sans, JetBrains Mono for mono. Type scale, weights and letter-spacing as v0.1. Full rationale in v0.1 §1.3; brief restate here for self-containment.

Families: Inter (UI sans, variable axis), JetBrains Mono (data columns, code blocks, TUI-style layouts). System fallbacks per OS.

Scale (semantic, base 1rem = 16px):

TokenSizeLine heightWeightLetter-spacingRole
type.display1.75rem1.20600-0.01emWelcome surfaces (rare)
type.heading.11.375rem1.25600-0.005emCanvas-level title
type.heading.21.125rem1.306000Section heading
type.heading.30.9375rem1.356000Sub-section
type.body0.875rem1.504000Default UI text
type.body.strong0.875rem1.506000Inline emphasis
type.body.compact0.8125rem1.454000Dense rows
type.caption0.75rem1.404000.005emLabels
type.caption.strong0.75rem1.406000.02emUppercase eyebrows
type.mono.data0.8125rem1.454500Numeric cells, terminal
type.mono.code0.8125rem1.554000Code blocks

Weights used: 400, 450 (mono only), 600. No 300, no 500, no 700+.

2.8 Spacing

Unchanged: 4pt grid.

TokenValueUse
space.00Touching
space.12pxHairline
space.24pxTight group
space.38pxDefault row
space.412pxDefault block / stack gap
space.516pxContainer padding
space.624pxGenerous block
space.732pxCanvas padding
space.848pxMajor layout
space.964pxRare breathing-room

Density variants per primitive: compact shifts one step down, spacious one step up.

2.9 Radii — Lume scale

Lume shifts v0.1's radius scale up by one stop, with the editor exception remaining at 0.

TokenValueUse
radius.00Editor-class surfacescanvas-region, timeline, Photoshop-style tool buttons inside the editor toolbar. Where Lume material stops, sharp corners take over
radius.sm6pxButtons, inputs, list-item hover/selected backgrounds
radius.md8pxContainers, cards, popovers
radius.lg12pxModals, panes, outermost windows — matches macOS window-corner radius (Apple's "concentric corners" rule)
radius.pill999pxSwitches, badge chips, progress bars

Rationale — softer than v0.1. Light has no edges. If the material is condensed luminosity, hard corners fight the metaphor. The shift is one stop (sm 4→6, md 6→8, lg 8→12); not a redesign, a calibration to the material. The editor exception is load-bearing: a Photoshop-like canvas-region with rounded corners reads "consumer photo app", not "professional tool" — and the visual hardness at the edge reinforces the Tier-1 boundary marker.

2.10 Elevation

Three shadow tokens. All include an accent-tinted ambient component for prominent surfaces (modals).

TokenLightDarkUse
elev.0nonenoneFlat content
elev.popover0 1px 2px rgba(20,24,36,0.04), 0 8px 24px rgba(20,24,36,0.06)0 1px 2px rgba(0,0,0,0.30), 0 8px 24px rgba(0,0,0,0.45)Dropdowns, popovers, hover cards
elev.modal0 4px 16px rgba(20,24,36,0.06), 0 24px 48px rgba(20,24,36,0.12), 0 0 32px var(--accent-glow-core), 0 0 96px var(--accent-glow)0 4px 16px rgba(0,0,0,0.55), 0 24px 48px rgba(0,0,0,0.65), 0 0 32px var(--accent-glow-core), 0 0 96px var(--accent-glow)Modal panes — the modal is the lit object, scrim is its shadow
elev.drag0 8px 24px rgba(20,24,36,0.16)0 8px 24px rgba(0,0,0,0.55)Drag-in-flight ghost

Cards do not get a shadow. Cards are differentiated by border + radius + surface luminosity (the gradient pair). This is a deliberate alignment with Linear/Things/Apple Catalyst, against Material Design's everything-is-elevated.

2.11 Motion

TokenValueUse
motion.instant0msTheme switch, scroll jump, focus on tab
motion.quick100msHover, focus fade-in
motion.smooth200msModal open/close, accordion
motion.deliberate320msCanvas-activate (Spaces switch)
motion.condense800msPatch-condensation animation (§3.5)
easing.standardcubic-bezier(0.22, 0.61, 0.36, 1.00)Default decelerate
easing.emphasiscubic-bezier(0.4, 0.0, 0.2, 1.0)Bigger moves (modal, condensation)
easing.linearlinearSkeleton pulse

Reduced-motion: every animation respects prefers-reduced-motion: reduce. Condensation collapses to a single opacity 0→1 fade. Skeleton pulse becomes static fill. Modal open/close becomes instant.

2.12 Icons

Unchanged: Lucide as the icon library, 14/16/20/24 px sizes with 1.5/1.75/2.0 stroke widths. Three documented custom icons allowed (magic-wand, brush-pressure, vector-pen-anchor) for editor-specific glyphs Lucide doesn't cover.


3. Lume implementation primitives

The CSS recipes that every renderer must implement. Per-primitive specs in §4 reference these by name rather than re-defining them. Each recipe is the single source of truth.

3.1 Surface gradient

Every surface is rendered as a 180° linear-gradient between the surface token's .top and .btm colors.

css
background: linear-gradient(180deg, var(--bg-surface-top) 0%, var(--bg-surface-btm) 100%);

Fallback for renderers that cannot gradient: use <token>.btm as a solid fill. The Lume effect degrades to flat; functionality is unaffected.

3.2 Two-stop glow (the default Lume glow)

The standard accent-glow recipe. A bright inner core close to the surface, an accent-tinted corona further out.

css
box-shadow:
  0 0 4px var(--accent-glow-core),       /* tight inner core, white-shifted */
  0 4px 12px var(--accent-glow);         /* wider corona, accent-tinted */

For emphasis states (focused input, hovered primary button), the recipe intensifies:

css
box-shadow:
  0 0 6px var(--accent-glow-core),
  0 6px 18px var(--accent-glow-strong);

For the Spotlight idiom (§5.3), the recipe extends to three stops as its single showcase moment:

css
box-shadow:
  0 0 0 4px var(--accent-glow),          /* hard outline */
  0 0 16px var(--accent-glow-core),      /* tight bright core */
  0 12px 40px var(--accent-glow-strong); /* wide deep corona */

Why two stops, not one (the v0.1 single-tint had problems): with a single rgba(accent, alpha) shadow, a light-mode glow reads as a darker accent-tinted shadow underneath the surface — not as light coming from the surface. The bright glow-core (white-shifted) is what closes that asymmetry. In light mode it adds a luminous halo that the accent-tinted shadow alone could never produce; in dark mode it intensifies the corona's "emitting" quality.

3.3 Donut glow — for surfaces with a glyph at center

When the surface has a centered glyph (Photoshop tool button, status-dot chip, KPI-card delta arrow), the standard recipe places the glow-core at the glyph's pixel position, drowning the glyph in bright white. Bug.

Recipe — donut variant:

css
background: radial-gradient(
  circle at center,
  var(--accent-subtle) 0%,
  var(--accent-subtle) 35%,    /* clear pocket for the glyph */
  var(--accent-glow-core) 75%, /* bright ring radiates outward */
  transparent 100%
);
box-shadow:
  0 0 12px var(--accent-glow-core),
  0 0 22px -4px var(--accent-glow-strong);

Physical analogue: a lit lampshade where the reflector edge glows, not the bulb seen through. The glyph sits in a clean accent-subtle pocket; the bright light radiates around it.

Where the donut applies:

  • ps-tool.active (editor-workspace toolbar)
  • kpi.delta chip when it carries an arrow glyph
  • Status dots with loading: true + icon
  • Any primitive declaring style.center-glyph: true

Open issue (carry-over from preview). The donut still needs visual refinement — early implementations show the ring landing too close to the border, slightly clipped. Spike-phase to refine; the rule stays normative.

3.4 Directional border

css
border: 1px solid var(--border-default-btm);
border-top-color: var(--border-default-top);
box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset; /* top-edge highlight, raised surfaces */

For raised surfaces (cards, modal), the inset highlight is mandatory. For sunken surfaces (bg.surface.sunken), omit the highlight (sunken surfaces shouldn't catch light on their top edge).

3.5 Patch-condensation animation

Replaces v0.1's fade-in + highlight-wash. When a surface_patch arrives, the new content condenses into existence.

Three components, all running concurrently over 800ms (motion.condense):

  1. Content materialisation — the new content starts at opacity: 0, transform: scale(1.03), filter: blur(2px). Over 800ms (easing.emphasis), it animates to opacity: 1, scale: 1, blur: 0. The transition is front-loaded — by 40% of the duration (~320ms) the content is already 90% visible and sharp; the remaining 60% is settle.

  2. Bloom collapse — a radial-gradient pulse appears centered on the new content's bounding box, sized 120% with accent-glow-strong at center fading to transparent at edges. Over 800ms, it collapses to 50% scale and opacity 0. This is the "light converging" effect.

  3. Sweep bar — a 1px horizontal accent-colored bar sweeps across the bottom of the new content over the first 500ms, then fades. This is the "ink-jet pass" reading — the agent's stroke laying down the new content.

Reduced-motion fallback: Components 2 and 3 are dropped entirely. Component 1 collapses to a simple opacity 0→1 fade over 200ms (motion.smooth). No blur, no scale, no bloom, no sweep.

Rapid-stream throttling. When more than 5 patches arrive per second (typing-speed canvas updates), the animation degrades automatically: component 2 is skipped (the bloom would visually noise the stream), component 3 is skipped, only the materialisation (component 1) runs but at 400ms instead of 800ms. Renderer detects rapid-stream by counting patch arrivals in a 1-second sliding window.

3.6 Modal materialization

opacity: 0 → 1 over motion.smooth, easing.emphasis
transform: scale(0.97) → 1.0 concurrently
scrim opacity: 0 → 0.4 (light) / 0.6 (dark) over motion.smooth

The scrim is bg.modal.overlay — minimally accent-tinted, never pure black. The modal pane carries elev.modal (which includes the accent-glow component) so it reads as the lit object emerging into focus.

Modal dismiss: reverse over motion.quick.


4. Per-primitive visual

4.1 General Lume rules (apply to every primitive)

Before primitive-specific notes, the following universal rules apply unless overridden:

RuleWhat it means in CSS
Surfaces are gradient pairsbackground: linear-gradient(180deg, <token>.top, <token>.btm)
Borders are directionalborder: 1px solid <token>.btm; border-top-color: <token>.top
Raised surfaces carry a top-edge highlightbox-shadow: 0 1px 0 rgba(255,255,255,X) inset (X = 0.06 light, 0.06 dark)
Selection / focus emit lightTwo-stop glow recipe §3.2
Loading is a skeleton pulse with linear gradient(unchanged from v0.1)
Hover transitions over motion.quick(unchanged)
Center-glyph surfaces use donut glow§3.3

Each primitive table below lists only the Lume-specific notes. Layout, density, edge cases, states inherited from v0.1 §2 are preserved unless explicitly contradicted here.

4.2 Primitive-by-primitive notes

text · heading

Inherit body and section-title styles from v0.1. No Lume-specific treatment — typography carries hierarchy, light does not.

Exception: an h1-equivalent at the top of a freshly-materialised pane inherits the patch-condensation animation §3.5 like any other new content.

container

Default container (border: true): directional border.subtle, surface gradient bg.surface, padding space.5, radius radius.md (= 8px under Lume). Raised variant: gradient bg.surface.raised, inset top-highlight.

list · tree

Selection rendering uses the two-stop glow §3.2:

css
.list-item.selected, .tree-item.selected {
  background:
    radial-gradient(ellipse at 30% 50%, var(--accent-glow-core) 0%, var(--accent-glow) 20%, transparent 70%),
    linear-gradient(180deg, var(--accent-subtle), transparent);
  box-shadow:
    0 0 12px -2px var(--accent-glow-core),
    0 0 24px -6px var(--accent-glow-strong);
}
.list-item.selected::before {
  content: '';
  position: absolute;
  left: 0; top: 4px; bottom: 4px;
  width: 2px;
  background: var(--accent);
  border-radius: 2px;
  box-shadow: 0 0 6px var(--accent-glow-strong);
}

The accent left-bar gains an accent-glow itself — the bar is the edge of the lit pocket, not a paint stroke.

table

Highlighted row (the agent flagged this row): same recipe as selected list-item, but with background-position: 0 50% so the gradient sits toward the left of the row (where the eye enters). Sort-active column header gets accent.subtle text color, no glow (header text must read clearly).

Skeleton rows: state.loading linear-gradient with skeleton-pulse animation §3.5 — unchanged from v0.1.

button

  • Primary: linear-gradient(180deg, accent, accent.hover) fill, border: 1px solid accent.hover; border-top-color: rgba(255,255,255,0.18), two-stop glow §3.2.
  • Secondary: linear-gradient(180deg, bg.surface.raised.top, bg.surface.raised.btm), directional border.default, inset top-highlight.
  • Ghost: transparent; hover paints accent.subtle. No glow until hover.
  • Danger: transparent fill, border.state.error.edge, text.state.error.fg.
  • Disabled: state.loading fill, text.disabled text, no glow, no border.
  • Focus: layered box-shadow — 0 0 0 2px accent, 0 0 0 6px accent.glow on top of the existing two-stop glow.

input

Default: gradient bg.surface.raised, directional border.default, inset top-highlight (1px). Focus: border-color: accent, layered shadow box-shadow: 0 0 0 1px accent, 0 0 0 4px accent.glow, 0 0 12px accent.glow-core.

Error: border-color: state.error.edge, error-message helper below. No glow on error state — the error is the salient signal, glow would compete.

choice · toggle

Dropdown trigger inherits secondary-button look. Open menu: bg.surface.raised gradient, elev.popover, directional border. Selected item in menu: accent.subtle background, accent checkmark.

Radio + checkbox + switch: same v0.1 shapes; checked state uses accent fill with the two-stop glow recipe applied as box-shadow (smaller-radius glow because the elements are small: 0 0 3px accent.glow-core, 0 0 6px accent.glow).

image · chart

Image: surface-sunken background as placeholder; no Lume chrome — images are content, not material.

Chart: bars/lines use accent fill with a soft accent.glow underglow (box-shadow: 0 0 6px accent.glow). Tooltip: bg.surface.raised, elev.popover, directional border. Multi-series uses chroma-reduction on a single hue (per v0.1).

form

Inspector mode (with context-binding trait): label-left grid layout, inputs use Lume input recipe above, no per-form chrome.

Submit row: primary button + secondary "Cancel".

toolbar

Surface bg.surface gradient, directional border.subtle (top or bottom depending on toolbar position). Separators 1px border.subtle, vertical, 16px tall.

Menu surface: bg.surface.raised, elev.popover, directional border. Item hover: accent.subtle background (no glow — menubars are mode-bridges, glow would noise them).

tabs

Tab labels: type.body, text.secondary inactive, text.primary active. Active tab: 2px accent underline + 1px accent.glow underglow below (makes the underline read as lit, not just colored).

Wizard variant: step dots use a small donut-glow §3.3 around the current step.

pane

Container surface: gradient bg.surface, directional border, radius.md (= 8px). Modal pane variant: radius.lg (= 12px, concentric with window), elev.modal including its accent-glow components.

Drag-in-flight: elev.drag shadow, ghost at 50% opacity. No accent-glow on drag (it's a transient operation, not an active state).

status · progress

Status: text only, optional leading icon. When carrying loading: true trait, icon-area renders an 8×12px skeleton pulse-bar.

Progress: 4px track bg.surface.sunken gradient, accent-fill bar with the two-stop glow shadow underneath (the bar reads as lit, not painted). Knob (for scrubbers): accent-fill circle with surrounding glow.

divider

Single 1px line, border.subtle.btm color. No Lume glow — dividers are quiet by design.

media · canvas-region · timeline · vector-path (editor-class)

canvas-region — the Lume boundary marker.

  • Background: bg.surface.sunken.btm solid (no gradient — opaque is the point).
  • Border: 2px solid accent (active editing target) or border.default.btm (inactive). Border is solid color, not directional — this surface is opaque material, not light.
  • Radius: 0 (radius.0).
  • Outer ambient: 0 0 20px accent.glow shadow (so the active canvas signals "this is the focus" without applying Lume material inside the region).
  • Cursor: per active tool.
  • Selection overlay: 1px dashed marching-ants line, accent color.

The contrast between the lit chrome surrounding the canvas-region and the opaque sharp-cornered region itself is load-bearing: it visually marks the Tier-1 boundary where the agent's chrome ends and the user's raw work begins.

media (audio/video):

  • Frame area: bg.surface.sunken.btm opaque (same logic as canvas-region).
  • Transport bar: full Lume material (gradient surface, directional border, accent-glow on scrubber knob).
  • Waveform (audio): accent.subtle fill, accent for played portion. (Detail mockup follows in spike.)

timeline:

  • Ruler row: surface gradient with type.caption text.secondary ticks.
  • Track row: opaque bg.surface.sunken.btm (the track surface is editor-class).
  • Clip rendering: border.default outline, bg.surface.sunken.btm interior with media thumbnails inside.
  • Selected clip: 2px accent border + outer accent.glow.
  • Playhead: 2px vertical accent line spanning all tracks, with a 12×12 downward triangle at top.

vector-path:

  • Path stroke: 2px accent (active) or 1px text.primary (inactive).
  • Anchor: 8×8 bg.surface.raised square with 1px accent border.
  • Selected anchor: 8×8 accent solid + 4px accent.glow halo (small donut).

5. Composition idioms

The five idioms from CONCEPT.md's Composition-Idiom Library, rendered in Lume material. None of them visually mimic the era they reference; all of them are layouts the agent infers from a request, painted in this material.

5.1 Norton-Commander

Two panes side-by-side, equal width by default. Each pane: directional border, surface gradient, internal list with type.mono.data for the data-grid feel. Resize divider between them carries the standard pane drag treatment. Shared toolbar below.

Focus moves between panes via Tab; arrow keys within active pane. Density typically compact.

What is not taken: blue-on-white box-drawing, function-key labels at bottom, heavy borders. The agent expresses the layout; Lume renders it.

5.2 Wizard

container with step tabs + form per step + toolbar (back/next).

Steps render as small dots connected by lines:

  • Completed: filled accent dot, connecting line is filled accent.
  • Current: ring of accent around bg.surface.raised core, plus accent.glow-strong halo (donut glow — the current step is the lit one).
  • Upcoming: border.subtle ring around bg.surface.raised.

Form renders inspector-mode (label-left grid). Back / Next: secondary / primary buttons at bottom, right-aligned for forward motion.

5.3 Spotlight

Centered input + list of hits. The showcase moment for Lume.

  • Stage: radial accent-glow centered on the input (background of the stage gets radial-gradient(ellipse at 50% 30%, accent.glow, transparent)).
  • Input: 48px tall, type.heading.2 size, leading search icon, the three-stop Spotlight glow recipe §3.2.
  • Results: bg.surface.raised gradient, elev.popover, padding space.2. Focused item uses the two-stop glow §3.2 with radial-gradient(at 25% 50%, glow-core, glow, transparent) background.

The stage itself glows — the user's eye is led by the canvas before they even read the input. This is what makes Spotlight feel less like a search box and more like the agent lighting up in response.

5.4 Dashboard

grid of container with chart, status, KPI-text.

KPI cards: standard raised-surface treatment (gradient + directional border + inset highlight). Value rendered in type.mono.data at display size; delta line below in type.caption. The delta-arrow glyph gets a small text-shadow in accent.glow when positive (text-shadow: 0 0 6px accent.glow) — text-only, no pill, no badge.

Charts: bars use accent fill with accent.glow underglow.

5.5 Photoshop-workspace

The critical Lume test — material around an opaque editor boundary.

  • Left toolbar: 48px wide, surface-sunken gradient (this is editor-class chrome, but still chrome, so it gets Lume material). Tool buttons: style: "compact" ghost buttons.
  • Active tool button: donut glow §3.3 + 1px accent border. The icon glyph sits in a clean accent.subtle pocket; the bright cyan-white ring radiates outward.
  • Center canvas-region: opaque, sharp-cornered, 2px accent border
    • outer accent.glow shadow.
  • Right inspector (form with context-binding): full Lume material, sliders use the standard track + accent-glow knob recipe.
  • Right layer-stack (tree with layer trait): full Lume selection halos on selected layers.

The visual story: lit toolbar holding lit tools, opening into an unlit raw work surface, surrounded by lit inspector + lit layers. The agent's hand is the lit part; the canvas is the user's.


6. Motion language

6.1 Patch arrival — condensation

Per §3.5: 800ms three-component condensation (content materialise + bloom collapse + sweep bar). Reduced-motion: collapses to a 200ms opacity fade. Rapid-stream: degrades automatically when >5 patches/sec.

Snapshot arrival: full-canvas crossfade over motion.smooth with easing.emphasis. No condensation (snapshot is too big to materialise gracefully).

6.2 Modal — materialisation

Per §3.6: 200ms opacity + scale, scrim fades concurrently. Dismiss reverses over motion.quick.

6.3 Selection / focus — light-on

Background tint fades in over motion.quick. Glow recipe applies instantly (focus must be visible the moment the user tabs to it; we don't fade the focus visibility, only the background tint).

6.4 Hover

motion.quick fade on background tint. Cursor change instant. Glow on hover (for primary buttons) intensifies via the emphasis variant of the two-stop recipe §3.2.

6.5 Canvas-activate (Spaces switch)

Outgoing canvas: opacity 1→0, 4px horizontal slide over motion.deliberate. Incoming canvas: opacity 0→1, 4px slide-in over motion.deliberate, starting 60ms after outgoing. Direction follows the direction of the user's switch (next / prev).

Reduced-motion: instant swap.

6.6 Palette switch

When the user changes palette mid-session ("make it warmer"), Tier 2 emits a surface_patch that re-tints accent tokens. Client renders the change as a 200ms crossfade over the affected surfaces (motion.smooth, easing.standard). Tree structure is unchanged; only color values cross-fade.

Reduced-motion: instant token swap, no transition.

6.7 Drag-in-flight

Ghost: 50% opacity, elev.drag. Drop-target: 2px dashed accent border fade-in over motion.quick. No glow during drag — drag is operational, not active-state.


7. Edge cases and anti-patterns

7.1 Empty canvas

Unchanged from v0.1. bg.canvas gradient, no chrome, single status primitive in lower-left: Canvas ready. ⌘K to start. in type.caption text.tertiary.

7.2 Loading > 300ms

Skeleton-pulse animation per §3.5. No spinner. Optional status text appears below skeleton after 3s.

7.3 Button-in-flight (the single spinner exception)

A button that fires an external-effect action (Send, Publish, Delete) cannot show a skeleton — there's no content to skeletonise. Recipe: button stays in primary variant, label replaces with verb + animated dots ("Sending."/"Sending.."/"Sending..."), no spinner glyph, no spinning ring. Period.

7.4 Errors

Three scopes — unchanged from v0.1:

  1. Primitive-scoped: 1px state.error.edge border, inline message state.error.fg.
  2. Field error: state.error.edge border on input, helper-text becomes error.
  3. Canvas-scoped: a status primitive at the top of the affected container, leading alert-triangle icon, inline retry action.

No toasts. The canvas is the surface of record. Errors live in the tree, in context.

7.5 Confirmation modal

Per CONCEPT.md "External-effect action confirmation contract". Visual: modal pane with radius.lg, elev.modal (including the accent-glow components), scrim is bg.modal.overlay. Title heading.2, body, caveat in text.secondary, toolbar right-aligned (Cancel secondary, primary action verb-labelled).

Danger variant: primary button uses button.danger style (transparent + error-border + error-text). Focus opens on Cancel (deliberate friction).

7.6 Anti-pattern list — implementers must NOT

  • Add colored status pills in any palette. Body text + accent row tint is the affordance.
  • Add emoji glyphs as decorative chrome. Agent-content emoji passes through; implementer-chrome emoji is forbidden.
  • Add toasts / floating notifications.
  • Add circular spinners outside the §7.3 exception.
  • Add gradients beyond the documented ones (surface gradients §3.1, button-fill gradients §4.2, skeleton pulse §3.5). No accent-to-purple gradient buttons, no glassmorphism, no neumorphism.
  • Add drop shadows to flat content. Shadows are reserved for temporally elevated surfaces (§2.10).
  • Add a branded splash or empty-state illustration.
  • Place bright accent-glow-core directly under a centered glyph. Use the donut variant §3.3 instead.
  • Skin per era. Single material, three user-bound palettes. Anything else is dynamic skinning, which v1 doesn't ship.

8. Accessibility floor

Unchanged from v0.1.

  • Contrast ratios verified WCAG 2.2 AA at body-text size against canvas: text.primary ≥ 7.0:1, text.secondary ≥ 4.5:1, text.inverse on accent ≥ 4.5:1, accent on canvas ≥ 3.0:1 for non-text uses.
  • Focus rings: always 2px solid + glow halo, never colour-only.
  • Hit targets: 32×32 minimum (24×24 only in dense toolbars with keyboard-accessible parallel paths).
  • Motion respects prefers-reduced-motion: reduce — concretely the fallbacks documented in §3.5 (condensation collapse), §6.5 (Spaces switch) etc.
  • Colour as sole signal: forbidden. Every state communicated through colour also carries a text label, an icon, or both.
  • Keyboard reach: every interactive primitive Tab-reachable, Enter/Space- operable, arrow-key navigable within composite primitives.

Lume-specific accessibility note: the accent.glow-core token at high alpha (≥0.50) approaches bg.surface.raised lightness. Renderers must verify that text inside a glow-core-affected region (e.g. focused input) still meets contrast against the resulting blended background, not against the unblended surface. The token-build step generates a blended-background reference per palette for this check.


9. Out of scope (explicit)

Out of scopeBelongs in
Pixel-level editor-workspace mockupMockup phase + Tier-1 spike
canvas-region / timeline / media pixel detailMockup phase
Brand identity — logo, wordmark, app iconSeparate brand work
Onboarding / first-runSeparate UX phase
Settings / Preferences screenDoes not exist by design — prefs are conversational
Marketing site visualsSeparate track
Email / transactional notification visualsNo such surface
Cross-platform native-control divergenceTier-1 spike
Print stylesheetsNot a v1 workload

10. Implementation contract

  • Token names from §2 are authoritative; renderer code references tokens by semantic name, never raw values.
  • Lume implementation recipes from §3 are normative. Renderers must implement them. Any divergence requires a spec amendment.
  • Per-primitive Lume notes in §4 are normative for default + interactive states. Variants restricted to those listed.
  • Composition idioms (§5) are normative for the layout relationships. The Skill may swap primitive choices within an idiom (e.g. listtable when data is uniform); the layout language is fixed.
  • Motion language (§6) is normative. New transitions require an amendment.
  • Anti-patterns (§7.6) are blockers. Code that reintroduces them must not ship.
  • The default palette is Lagoon. Palette binding is per-contextKey via ui-prefs. Renderers must implement the §6.6 palette-switch crossfade.

11. Open questions for review

The 8 questions in v0.1 §9 are mostly resolved by the Lume decision and the three-palette adoption. Carry-over and new questions:

  1. Donut glow refinement. The current recipe (§3.3) is normative but visually unfinished — the ring lands slightly close to the border in small surfaces (36px Photoshop tool button). Spike-phase to refine the gradient stops without changing the principle. Carry-over to the Tier-1 spike, not blocking spec freeze.

  2. Glow alpha calibration. Per-palette glow / glow-strong / glow-core alphas are tuned to feel "subtle but present". Codex review or first- user feedback may push them up or down. Specifically: Lagoon's glow-core at 0.60 in light mode is the brightest of the three; if sustained-use feedback says "too magical", lower to 0.50.

  3. Patch-condensation duration. 800ms matches v0.1's fade-in. Lume spike-time empirically: with three components running, does it feel slower than 800ms? Rapid-stream throttling (§3.5) helps but doesn't address the single-patch case. Try-it-and-see.

  4. Performance budget under Lume. Multi-layer box-shadows + gradients

    • no blur should stay 60fps in Electron + Skia at typical canvas density (50 rows + 8 panes + active patch animation). Verified by measurement in spike, not by assumption.
  5. Marketing accent for app icon / splash / launch video. Lagoon is the runtime default and the spec recommendation. A reviewer might argue for Petrol on the icon (more "professional, calmer") with Lagoon at runtime. This is a brand decision adjacent to the spec.

  6. Three palettes is the right number. Two might be cleaner; four might be necessary for cultural reach (a green for natural-sciences users, a magenta for design audiences). Three feels like the right trade-off between user agency and brand coherence; revisit at v2 if usage data argues otherwise.

  7. Palette per-canvas vs per-user. Spec says per-contextKey, which means per-canvas when contexts differ. Some users may want a single global palette; CONCEPT.md's pref model allows a fallback chain (canvas-context → user-global → default) — that fallback may need explicit documentation.

  8. Reduced-motion fallback for condensation (§3.5). Currently: components 2+3 dropped, component 1 reduces to opacity fade only. A reviewer might argue we should also retain the sweep bar (it's the smallest of the three, doesn't violate motion-reduction). Open.


12. Changelog

  • v0.2 (this document) — Lume material adoption. Light-as-material thesis introduced; surface luminosity, accent-as-illumination, directional borders, soft corners formalised. Three user-bindable palettes (Petrol, Atelier, Lagoon — Lagoon default) replace the single-accent choice. Radius scale shifted one stop softer (editor surfaces stay 0). Two-stop glow primitive replaces v0.1 single accent-subtle. Patch-condensation replaces fade-in. Donut-glow rule introduced for centered-glyph surfaces. Token model gains accent.glow-core. Open questions 1-7 from v0.1 §9 resolved by material decision; new questions in §11.

  • v0.1 — first draft. Flat tokens, single-accent choice (Petrol proposed default), restraint baseline. Defined the 24-primitive catalogue, the five composition idioms, motion/edge-cases/a11y. Superseded by v0.2; preview retained at ./visual-spec-preview.html for material-comparison.

Lume Visual Specification · reconstructed from git history