feat(theme): four themes with an actual hue — Indigo, Ocean, Plum, Nordic

The seven existing themes are warm beige, cool grey, sage grey, slate, warm
dark, graphite and black — every one neutral, six of the seven accented orange.
Picking a theme changed the shade of grey and little else.

These colour the SURFACES, not just the accent, and each takes a different
primary so the picker tells them apart at a glance: violet on deep indigo, cyan
on deep teal, magenta on aubergine, indigo on a crisp cool white.

The semantic colours stay recognisable as themselves. A logger is read for
hours and "red means a problem" cannot become a decorative choice, so the hue
budget went on the surfaces and the primary. Where a theme's primary would have
collided with a meaning, the MEANING kept its identity and the ornament moved:
Ocean's info is blue rather than cyan, Plum's danger is red rather than rose,
and the matrix entity ramp shifts to cyan under Indigo and teal under Nordic so
a "confirmed" cell never reads as a button.

Everything else follows for free: the grids resolve var(--…) at runtime, so
they re-skin with no re-render. The three shared registrations that do NOT
follow automatically are done — the chart palette (light vs dark ramp) and the
date-picker icon inversion, which is keyed on an explicit list of dark themes.
This commit is contained in:
2026-08-09 10:06:05 +02:00
parent 79427ccd18
commit 80dac64b56
5 changed files with 306 additions and 10 deletions
+288 -3
View File
@@ -495,6 +495,286 @@
color-scheme: dark;
}
/* ---- Theme 5: Indigo — deep blue-violet, electric accent ----------------
The first seven themes are all neutral (beige, slate, graphite, black) with an
orange accent, so "another dark theme" only ever meant another grey. These
four carry a real hue in the SURFACES, not just in the accent, and each takes
a different primary — violet, cyan, pink, indigo — so they are told apart at a
glance in the picker.
Every one keeps the same contract: the semantic colours (success / warning /
caution / danger / info) must stay recognisable as themselves. A logger is
read for hours and "red = a problem" cannot become a decorative choice, so the
hue budget is spent on the surfaces and the primary, never on the meanings. */
[data-theme="dark-indigo"] {
--background: #0e0f1f;
--foreground: #e4e5f2;
--card: #181a30;
--card-foreground: #e4e5f2;
--popover: #181a30;
--popover-foreground: #e4e5f2;
--primary: #7c6cff; /* electric violet */
--primary-foreground: #0b0a1a;
--secondary: #222542;
--secondary-foreground: #e4e5f2;
--muted: #1e2038;
--muted-foreground: #9d9fbe;
--accent: #2a2d51;
--accent-foreground: #c7c9ee;
--destructive: #f2555a;
--destructive-foreground: #12030a;
--destructive-muted: #34131c;
--destructive-muted-foreground: #fca5a5;
--border: #2a2d4c;
--input: #2a2d4c;
--ring: #9b8dff;
--success: #34d399;
--success-foreground: #052018;
--success-muted: #0e2f27;
--success-muted-foreground: #6ee7b7;
--success-border: #165241;
--warning: #fbbf24;
--warning-foreground: #221803;
--warning-muted: #33280f;
--warning-muted-foreground: #fcd34d;
--warning-border: #4f3e15;
--caution: #facc15;
--caution-foreground: #221e04;
--caution-muted: #322d0e;
--caution-muted-foreground: #fde047;
--caution-border: #4b4315;
--danger: #fb7185;
--danger-foreground: #250912;
--danger-muted: #37151f;
--danger-muted-foreground: #fda4af;
--danger-border: #562533;
--info: #38bdf8;
--info-foreground: #051d29;
--info-muted: #0b2a3a;
--info-muted-foreground: #7dd3fc;
--info-border: #12475c;
/* The entity ramp moves to CYAN here: violet is the primary, and a violet
"entity confirmed" cell would read as a button. */
--mx-call-conf: #22c55e;
--mx-call-work: #2c7a52;
--mx-dx-conf: #22d3ee;
--mx-dx-work: #1b6b7c;
--mx-none: #2c2f4d;
--scrollbar-thumb: #383c63;
--scrollbar-thumb-hover: #4c5182;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(228, 229, 242, 0.05);
color-scheme: dark;
}
/* ---- Theme 6: Ocean — deep teal, cyan accent --------------------------- */
[data-theme="dark-teal"] {
--background: #061c21;
--foreground: #dcecee;
--card: #0d2c33;
--card-foreground: #dcecee;
--popover: #0d2c33;
--popover-foreground: #dcecee;
--primary: #22d3ee; /* cyan */
--primary-foreground: #041d24;
--secondary: #143b44;
--secondary-foreground: #dcecee;
--muted: #10333b;
--muted-foreground: #92b4ba;
--accent: #15424c;
--accent-foreground: #a5d8e0;
--destructive: #f2555a;
--destructive-foreground: #12030a;
--destructive-muted: #35161a;
--destructive-muted-foreground: #fca5a5;
--border: #1a4652;
--input: #1a4652;
--ring: #5fe3f5;
--success: #4ade80;
--success-foreground: #04220f;
--success-muted: #0e3020;
--success-muted-foreground: #86efac;
--success-border: #17593a;
--warning: #fbbf24;
--warning-foreground: #221803;
--warning-muted: #33280f;
--warning-muted-foreground: #fcd34d;
--warning-border: #4f3e15;
--caution: #facc15;
--caution-foreground: #221e04;
--caution-muted: #312d0e;
--caution-muted-foreground: #fde047;
--caution-border: #4a4315;
--danger: #fb7185;
--danger-foreground: #250912;
--danger-muted: #38161f;
--danger-muted-foreground: #fda4af;
--danger-border: #572634;
/* Info moves to a BLUE: a cyan "info" would be the primary colour again. */
--info: #7aa2f7;
--info-foreground: #04122e;
--info-muted: #16233f;
--info-muted-foreground: #a9c2fb;
--info-border: #2b3d66;
--mx-call-conf: #4ade80;
--mx-call-work: #2b7a4d;
--mx-dx-conf: #a78bfa;
--mx-dx-work: #5b4a9c;
--mx-none: #1c4550;
--scrollbar-thumb: #235a68;
--scrollbar-thumb-hover: #31788a;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(220, 236, 238, 0.05);
color-scheme: dark;
}
/* ---- Theme 7: Plum — aubergine, magenta accent ------------------------- */
[data-theme="dark-plum"] {
--background: #180f1e;
--foreground: #eee0f0;
--card: #251830;
--card-foreground: #eee0f0;
--popover: #251830;
--popover-foreground: #eee0f0;
--primary: #f472b6; /* magenta-pink */
--primary-foreground: #250518;
--secondary: #33203f;
--secondary-foreground: #eee0f0;
--muted: #2b1b36;
--muted-foreground: #b39dbd;
--accent: #3b2648;
--accent-foreground: #dcc2e6;
--destructive: #f2555a;
--destructive-foreground: #12030a;
--destructive-muted: #3a1620;
--destructive-muted-foreground: #fca5a5;
--border: #3a2547;
--input: #3a2547;
--ring: #f9a8d4;
--success: #34d399;
--success-foreground: #052018;
--success-muted: #10302a;
--success-muted-foreground: #6ee7b7;
--success-border: #1a5745;
--warning: #fbbf24;
--warning-foreground: #221803;
--warning-muted: #352a12;
--warning-muted-foreground: #fcd34d;
--warning-border: #524019;
--caution: #facc15;
--caution-foreground: #221e04;
--caution-muted: #342e12;
--caution-muted-foreground: #fde047;
--caution-border: #4e4519;
/* Danger goes RED here, not rose: a rose "danger" beside a pink primary is
indistinguishable, and this is the one colour that must never be missed. */
--danger: #f43f3f;
--danger-foreground: #2a0606;
--danger-muted: #3d1418;
--danger-muted-foreground: #fca5a5;
--danger-border: #5c2128;
--info: #38bdf8;
--info-foreground: #051d29;
--info-muted: #0f2b3a;
--info-muted-foreground: #7dd3fc;
--info-border: #16485d;
--mx-call-conf: #22c55e;
--mx-call-work: #2f7d55;
--mx-dx-conf: #60a5fa;
--mx-dx-work: #35538c;
--mx-none: #3a2647;
--scrollbar-thumb: #4a3159;
--scrollbar-thumb-hover: #644176;
--card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(238, 224, 240, 0.05);
color-scheme: dark;
}
/* ---- Theme 8: Nordic light — crisp cool white, indigo accent ----------- */
[data-theme="light-nordic"] {
--background: #eef1f7;
--foreground: #1b2130;
--card: #ffffff;
--card-foreground: #1b2130;
--popover: #ffffff;
--popover-foreground: #1b2130;
--primary: #4f46e5; /* indigo */
--primary-foreground: #ffffff;
--secondary: #dfe4ee;
--secondary-foreground: #1b2130;
--muted: #e2e7f0;
--muted-foreground: #4a5468;
--accent: #dcdcfb;
--accent-foreground: #312a91;
--destructive: #b91c1c;
--destructive-foreground: #ffffff;
--destructive-muted: #fef2f2;
--destructive-muted-foreground: #b91c1c;
--border: #c9d1e0;
--input: #c9d1e0;
--ring: #6366f1;
--success: #15803d;
--success-foreground: #ffffff;
--success-muted: #dcfce7;
--success-muted-foreground: #14532d;
--success-border: #86efac;
--warning: #b45309;
--warning-foreground: #ffffff;
--warning-muted: #fef3c7;
--warning-muted-foreground: #92400e;
--warning-border: #fcd34d;
--caution: #a16207;
--caution-foreground: #ffffff;
--caution-muted: #fef9c3;
--caution-muted-foreground: #854d0e;
--caution-border: #fde047;
--danger: #dc2626;
--danger-foreground: #ffffff;
--danger-muted: #fee2e2;
--danger-muted-foreground: #991b1b;
--danger-border: #fca5a5;
--info: #0369a1;
--info-foreground: #ffffff;
--info-muted: #e0f2fe;
--info-muted-foreground: #075985;
--info-border: #7dd3fc;
/* Entity ramp shifts to TEAL: indigo is the primary here. */
--mx-call-conf: #15803d;
--mx-call-work: #86efac;
--mx-dx-conf: #0f766e;
--mx-dx-work: #99f6e4;
--mx-none: #dde3ec;
--scrollbar-thumb: #b6c0d2;
--scrollbar-thumb-hover: #93a0b8;
--card-shadow: 0 1px 2px rgba(27, 33, 48, 0.06), 0 0 0 1px rgba(27, 33, 48, 0.03);
color-scheme: light;
}
/* ── Data-viz palette (Statistics dashboard) ────────────────────────────────
A VALIDATED categorical palette, not hand-picked: the slot ORDER is what makes
it colour-blind-safe (worst adjacent ΔE 24.2 light / 10.3 dark), so never
@@ -512,7 +792,8 @@
:root,
[data-theme="light-warm"],
[data-theme="light-cool"],
[data-theme="light-sage"] {
[data-theme="light-sage"],
[data-theme="light-nordic"] {
--chart-1: #2a78d6; /* blue — default single-series hue */
--chart-2: #1baf7a; /* aqua */
--chart-3: #eda100; /* yellow */
@@ -533,7 +814,10 @@
[data-theme="dim-slate"],
[data-theme="dark-warm"],
[data-theme="dark-graphite"],
[data-theme="high-contrast"] {
[data-theme="high-contrast"],
[data-theme="dark-indigo"],
[data-theme="dark-teal"],
[data-theme="dark-plum"] {
--chart-1: #3987e5;
--chart-2: #199e70;
--chart-3: #c98500;
@@ -681,7 +965,8 @@
help. */
Every temporal input carries the same glyph, so the rule names them all:
datetime-local was left out at first and its icon stayed invisible. */
:is([data-theme='dim-slate'], [data-theme='dark-warm'], [data-theme='dark-graphite'], [data-theme='high-contrast'])
:is([data-theme='dim-slate'], [data-theme='dark-warm'], [data-theme='dark-graphite'], [data-theme='high-contrast'],
[data-theme='dark-indigo'], [data-theme='dark-teal'], [data-theme='dark-plum'])
:is(input[type='date'], input[type='datetime-local'], input[type='time'], input[type='month'], input[type='week'])::-webkit-calendar-picker-indicator {
filter: invert(1) brightness(1.8);
opacity: 0.9;