dorfteich/apps/web/src/styles/tokens.css
Claude Fable 5 2c571f9f5e #180: dark mode — Light/Dark/System setting with token-based dark palette
The dark palette lives as a single :root[data-theme='dark'] block in
tokens.css; theme.ts and the pre-paint public/theme-init.js (external file
because the prod CSP forbids inline scripts) always resolve the stored
ui.theme.mode to a concrete data-theme, so 'system' needs no @media
duplicate and follows live OS changes via matchMedia. color-scheme flips
per theme (native controls/scrollbars), paired theme-color metas track the
effective theme, and the new Appearance settings section offers the
three-way choice as native radios (device-local, like #170). Label chips
gain a chip-outline ring so arbitrary user colors stay separated on the
dark canvas; useEffectiveTheme() is exported for the later pond-scoped
theming stage (ADR 0018).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
2026-07-28 20:40:47 +02:00

106 lines
4.0 KiB
CSS

/*
* Design tokens. Components consume only these variables — never raw
* values — so pond-level font settings (ADR 0016) and future theming stay
* one-line changes. The look is deliberately plain and professional.
*/
:root {
/* Native controls/scrollbars follow the active theme; the dark block
below flips this together with the palette (issue #180). */
color-scheme: light;
/* Font slots per ADR 0016; the real self-hosted fonts arrive with issue #66. */
--font-heading: 'Roboto', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-body: 'Roboto', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-mono: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
--font-weight-heading: 400;
--font-weight-body: 300;
--font-weight-mono: 400;
/* Color palette: calm neutrals with one pond-green accent. */
--color-text: #1f2933;
--color-text-muted: #616e7c;
--color-bg: #ffffff;
--color-bg-subtle: #f5f7fa;
/* Elevated panels (modals, cards) — several rules already referenced this
* token with a #fff fallback; the fallback-less ones dropped their
* background entirely (#120). */
--color-surface: #ffffff;
/* Muted pill/badge surfaces (member seats, count pills, neutral badges).
Was a fallback-only value scattered through base.css before #180. */
--color-surface-muted: #e2e8f0;
--color-border: #d9e2ec;
/* Boundaries of interactive controls (inputs, selects) need 3:1 against
both backgrounds (WCAG 1.4.11) — #d9e2ec is only ~1.3:1. */
--color-border-input: #7d8a97;
--color-accent: #2f6f4f;
--color-accent-contrast: #ffffff;
--color-danger: #ab091e;
/* Text sitting ON a danger background (buttons, notification badge) —
paired with --color-danger the same way --color-accent-contrast is
paired with --color-accent; the two must flip together per theme. */
--color-danger-contrast: #ffffff;
--color-danger-strong: #8a0718;
--color-ok: #14803c;
/* Favorite stars and tree icons (issue #132) — a readable gold. ICON
use only: 3.25:1 passes the 3:1 non-text minimum but NOT the 4.5:1
required for text (audit A11Y-024c). */
--color-favorite: #b8860b;
/* Status badges (admin system page): pastel surface + strong text, each
pair ≥ 4.5:1 in both themes (asserted by theme-contrast.test.ts). */
--color-badge-ok-bg: #d6f2df;
--color-badge-ok-text: #1d6f42;
--color-badge-error-bg: #fbe3e0;
--color-badge-error-text: #a02818;
--color-badge-warn-bg: #fdf0d4;
--color-badge-warn-text: #8a5a00;
/* Separation ring for user-colored label chips/swatches — invisible on
light, a faint light outline on dark where arbitrary label colors can
melt into the background. */
--color-chip-outline: transparent;
/* Spacing scale (rem-based). */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--radius: 6px;
--topbar-height: 3rem;
--sidebar-width: 16rem;
}
/*
* Dark palette (issue #180). public/theme-init.js and theme/theme.ts always
* resolve the user's Light/Dark/System choice to a CONCRETE data-theme on
* <html> — there is deliberately no @media fallback here, so this single
* selector is the whole contract for later theming stages (ADR 0018).
* Every pair is contrast-asserted by theme-contrast.test.ts; border-input
* and favorite pass ≥ 3:1 against both palettes and stay unchanged.
*/
:root[data-theme='dark'] {
color-scheme: dark;
--color-text: #e2e8f0;
--color-text-muted: #a7b3c0;
--color-bg: #10161d;
--color-bg-subtle: #161d26;
--color-surface: #1b2430;
--color-surface-muted: #26303c;
--color-border: #323e4d;
--color-accent: #5cb88a;
--color-accent-contrast: #10161d;
--color-danger: #f87171;
--color-danger-contrast: #10161d;
--color-danger-strong: #fa8f8f;
--color-ok: #4ade80;
--color-badge-ok-bg: #1b3d2b;
--color-badge-ok-text: #8fdcb0;
--color-badge-error-bg: #43181d;
--color-badge-error-text: #f3a8a8;
--color-badge-warn-bg: #3c3113;
--color-badge-warn-text: #e3c06b;
--color-chip-outline: rgba(255, 255, 255, 0.18);
}