Design system
Scope: what a contributor needs to produce on-brand product UI — posture,
tokens, and the rules that get violated. The full token block (exact component
specs: buttons by size, inputs, badges, splash loader) lives in DESIGN.md at
the monorepo root; it moves with the B-022 cutover. Governs product UI only
— the optoapp.link marketing site follows its own editorial voice.
Living style guide: the portal's dev-only /ref-page route — ungated, not
linked from product UI (optolink-portal/src/app/ref-page/). Update it in
place after DESIGN.md edits; never create a parallel version. Its theme toggle
rides the app-wide ThemeProvider (dark is the app default since v2.2.0) and
persists in localStorage["theme"] — toggle back to Dark after light-mode
checks or the next session starts light.
Portal paths source-checked against optolink-portal @ 7d31d45, 2026-10-06 (
src/app/ref-page/exists). The dark-default/theme-persistence facts are recorded — re-verify on next use.
Posture — "The Instrument Panel"
The portal is operational infrastructure UI, not a marketing surface: state
readable at a glance, density and precision over decoration. Dark mode is the
default. Warm-neutral base (cream #F7F5F1 / ink #17242B), not stark
white/black. Nothing here licenses gradients, glassmorphism, motion flourishes,
or marketing copy inside the product.
Color tokens
| Token | Hex | Role |
|---|---|---|
| primary (Deep Teal) | #004F70 | The ONLY "primary action" color — actions, active nav, links; never decorative |
| secondary (Electric Cyan) | #01B8CA | Focus rings, info accents — never small text (~2:1 on cream; use info-text #0E6B77) |
| accent (Precision Orange) | #FF7C2B | One key CTA per screen max, or critical confirmations |
| cream / ink | #F7F5F1 / #17242B | The warm neutral pair (light bg/text ↔ inverted in dark) |
| neutrals 50–900 | cream→ink ramp | Borders, muted text, surfaces — never a gray outside the scale |
| success / warning / error | #2E9E5B / #D9962E / #D9483E | Each with -bg (soft fill) + AA-safe -text variant |
- One Orange Rule: at most one orange element per screen; if two want it, the hierarchy is wrong. Warning amber ≠ orange: amber = "needs attention", orange = "act here".
- Semantic colors split hue (icons/dots/borders) from
-text(the only color for text on the soft-bg). Dark mode lightens the-textvariants. - Charts use the fixed chart-1…5 sequence (teal, cyan, green, amber, taupe); never reassign semantically.
- Dark mode:
dark-bg#14201F,dark-surface#1C2A29, text = cream, primary brightens to#3FA9CC.
Typography
One family: Geist Variable (self-hosted, fontsource) — hierarchy by weight (400/500/600), no second display face; do not style for "General Sans" (the licensed-swap intent) until the swap lands. Data-is-Mono Rule: literal data a user scans or copies (short codes, keys, hashes, tokens) is JetBrains Mono — prose and labels are Geist; there is no third category.
Scale: display 2rem/600 · h1 1.5rem · h2 1.25rem · h3 1.0625rem (all 600) · body 0.9375rem/400 · body-sm + label 0.8125rem · caption 0.75rem. Body text max 65–75ch. Avoid ALL-CAPS tracked labels and accented words in headings.
Shape, space, depth
- Radius tiers by role, never one everywhere: 6px controls (buttons,
inputs, table rows) · 10px containers (cards, panels, modals) · 14px
top-level (page-level panels, empty states).
rounded-fullonly for circles and badge pills — buttons never read as pills. - Spacing: 8px scale (4/8/12/16/24/32/48/64); tighter rhythm in tables, looser in forms; max 720px form/detail width.
- Two Shadows Rule: Level 1 (cards, barely visible) + Level 2 (modals/ dropdowns) only — a surface needing more depth is a layering bug, not a third shadow.
- Every interactive element: hover, focus-visible (cyan ring), disabled. Data surfaces (tables/lists) additionally require loading, empty, and error states with real copy — no generic placeholder text.
Logo & favicon
Three lockups × three treatments, source SVGs in optolink-portal/public/brand/:
wordmark (default, where width allows) · brandmark (icon only:
collapsed sidebar, favicon, avatars) · "by Optomatica" sub-lockup (legal/
about contexts only). Treatments: color on light surfaces, white on dark/solid
fills, black only for single-color print contexts.
The logo's embedded navy/cyan/orange are locked, not tokens — they sit
close to but not exactly on primary/secondary/accent by design. Never
recolor logo paths to match tokens, never pull logo hexes into the UI palette,
never recreate the mark in CSS/text. Minimums: wordmark 96px wide, brandmark
20px; clear space ≥ the brandmark's corner-notch depth. The favicon is the
brandmark (public/favicon.svg).