feat(appearance): rename default theme, add real brand-accurate Stalwart theme

The color theme previously named "Stalwart" was actually just the
neutral black/white default — it never used Stalwart's own brand
color. Renamed it to "Default" (ColorTheme 'stalwart' -> 'default'
value; label/i18n key updated) and added a genuinely new "Stalwart"
theme in its place.

Colors sourced directly from the official site's own design tokens
(D:\project\stalwart_website/src/styles/tokens.css): brand red/pink
#db2d54 (light primary/ring) and #ff4570 (--brand-hi, dark
primary/ring), converted to oklch via the sRGB->OKLab->OKLCH matrix
transform to match this project's existing token format. Neutrals use
near-true-gray values (also converted from the site's actual light/
dark backgrounds and text colors) rather than being re-hued toward the
brand color like the other theme presets — matching how the real site
treats color (saturated only on the accent, neutral everywhere else).

Verified in the running dev server: Default reverts to the original
neutral look, Stalwart renders the brand red across primary buttons,
active nav state, checkboxes, and badges in both light and dark mode.
No regression (243 tests, typecheck, lint all pass).
This commit is contained in:
Steven RYDELL
2026-08-01 23:02:32 +02:00
parent 410a01700b
commit 637d8bb286
3 changed files with 57 additions and 5 deletions
+10 -4
View File
@@ -8,7 +8,7 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export type Theme = 'light' | 'dark';
export type ColorTheme = 'stalwart' | 'ocean' | 'forest' | 'violet' | 'rose' | 'amber' | 'teal';
export type ColorTheme = 'default' | 'stalwart' | 'ocean' | 'forest' | 'violet' | 'rose' | 'amber' | 'teal';
export type Radius = 'rounded' | 'square';
interface UIState {
@@ -35,8 +35,8 @@ function applyThemeClass(theme: Theme) {
}
function applyColorThemeAttribute(colorTheme: ColorTheme) {
// The default theme is defined directly on :root/.dark, so it needs no attribute.
if (colorTheme === 'stalwart') {
// The neutral "Default" theme is defined directly on :root/.dark, so it needs no attribute.
if (colorTheme === 'default') {
document.documentElement.removeAttribute('data-theme');
} else {
document.documentElement.dataset.theme = colorTheme;
@@ -48,11 +48,17 @@ function applyRadiusAttribute(radius: Radius) {
}
export const COLOR_THEMES: { value: ColorTheme; labelKey: string; fallback: string; swatch: string }[] = [
{
value: 'default',
labelKey: 'appearance.theme.default',
fallback: 'Default',
swatch: 'linear-gradient(135deg, oklch(0.205 0.017 285.823) 50%, oklch(0.92 0.007 285.823) 50%)',
},
{
value: 'stalwart',
labelKey: 'appearance.theme.stalwart',
fallback: 'Stalwart',
swatch: 'linear-gradient(135deg, oklch(0.205 0.017 285.823) 50%, oklch(0.92 0.007 285.823) 50%)',
swatch: 'oklch(0.586 0.207 14.6)',
},
{ value: 'ocean', labelKey: 'appearance.theme.ocean', fallback: 'Ocean', swatch: 'oklch(0.546 0.215 262.9)' },
{ value: 'forest', labelKey: 'appearance.theme.forest', fallback: 'Forest', swatch: 'oklch(0.527 0.137 150.1)' },