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:
@@ -403,9 +403,13 @@ const THEME_SWATCH: Record<Exclude<ThemeChoice, 'auto'>, { bg: string; card: str
|
||||
'light-warm': { bg: '#e8dfc9', card: '#faf6ea', accent: '#b8410c' },
|
||||
'light-cool': { bg: '#f4f6f8', card: '#ffffff', accent: '#2563eb' },
|
||||
'light-sage': { bg: '#eef1ec', card: '#f8faf6', accent: '#2f855a' },
|
||||
'light-nordic': { bg: '#eef1f7', card: '#ffffff', accent: '#4f46e5' },
|
||||
'dim-slate': { bg: '#343b47', card: '#3d4552', accent: '#fb923c' },
|
||||
'dark-warm': { bg: '#221d18', card: '#2e2820', accent: '#e07a2e' },
|
||||
'dark-graphite': { bg: '#16181d', card: '#1f232b', accent: '#f97316' },
|
||||
'dark-indigo': { bg: '#0e0f1f', card: '#181a30', accent: '#7c6cff' },
|
||||
'dark-teal': { bg: '#061c21', card: '#0d2c33', accent: '#22d3ee' },
|
||||
'dark-plum': { bg: '#180f1e', card: '#251830', accent: '#f472b6' },
|
||||
'high-contrast': { bg: '#000000', card: '#0d0d0d', accent: '#ff7a1a' },
|
||||
};
|
||||
|
||||
|
||||
@@ -108,8 +108,8 @@ const en: Dict = {
|
||||
'settings.pane.recent': 'Recent QSOs', 'settings.pane.netcontrol': 'Net control',
|
||||
'settings.pane.flex': 'Flex Console', 'settings.pane.icom': 'Icom Console', 'settings.pane.yaesu': 'Yaesu Console', 'yaesu.notConnected': 'Yaesu CAT not connected', 'yaesu.meters': 'Meters', 'yaesu.bandMode': 'Band & mode', 'yaesu.receive': 'Receive', 'yaesu.noiseFilter': 'Noise & filter', 'yaesu.transmit': 'Transmit', 'yaesu.refresh': 'Refresh', 'yaesu.tuneHint': 'Start an antenna-tuner cycle', 'yaesu.sToRst': 'Click to fill the RST sent', 'yaesu.sidebandHint': 'Click to select this mode; click again to switch sideband (U/L)', 'yaesu.splitUpHint': 'Transmit this far above the receive frequency, and turn split on', 'yaesu.txOn': 'TX', 'yaesu.cw': 'CW', 'yaesu.breakInHint': 'Break-in: the rig switches to receive between characters', 'yaesu.zinHint': 'Zero-in: retune so the station you hear lands on your CW pitch',
|
||||
'theme.auto': 'Auto (system)', 'theme.light-warm': 'Warm light', 'theme.light-cool': 'Cool light',
|
||||
'theme.light-sage': 'Sage light', 'theme.dim-slate': 'Dim slate', 'theme.dark-warm': 'Warm dark',
|
||||
'theme.dark-graphite': 'Graphite dark', 'theme.high-contrast': 'High contrast',
|
||||
'theme.light-sage': 'Sage light', 'theme.light-nordic': 'Nordic light', 'theme.dim-slate': 'Dim slate', 'theme.dark-warm': 'Warm dark',
|
||||
'theme.dark-graphite': 'Graphite dark', 'theme.dark-indigo': 'Indigo', 'theme.dark-teal': 'Ocean', 'theme.dark-plum': 'Plum', 'theme.high-contrast': 'High contrast',
|
||||
// Settings navigation (sidebar groups + section names)
|
||||
'nav.user': 'User Configuration', 'nav.software': 'Software Configuration', 'nav.hardware': 'Hardware Configuration', 'nav.lists': 'Lists',
|
||||
'sec.station': 'Station Information', 'sec.profiles': 'Profiles', 'sec.operating': 'Operating conditions',
|
||||
@@ -532,8 +532,8 @@ const fr: Dict = {
|
||||
'settings.pane.recent': 'QSO récents', 'settings.pane.netcontrol': 'Gestion de net',
|
||||
'settings.pane.flex': 'Flex Console', 'settings.pane.icom': 'Icom Console', 'settings.pane.yaesu': 'Yaesu Console', 'yaesu.notConnected': 'CAT Yaesu non connecté', 'yaesu.meters': 'Mesures', 'yaesu.bandMode': 'Bande et mode', 'yaesu.receive': 'Réception', 'yaesu.noiseFilter': 'Bruit et filtre', 'yaesu.transmit': 'Émission', 'yaesu.refresh': 'Actualiser', 'yaesu.tuneHint': "Lancer un cycle d'accord d'antenne", 'yaesu.sToRst': 'Cliquer pour remplir le RST envoyé', 'yaesu.sidebandHint': 'Cliquer pour choisir ce mode ; recliquer pour changer de bande latérale (U/L)', 'yaesu.splitUpHint': "Émettre à cette distance au-dessus de la fréquence de réception, et activer le split", 'yaesu.txOn': 'TX', 'yaesu.cw': 'CW', 'yaesu.breakInHint': "Break-in : la radio repasse en réception entre les caractères", 'yaesu.zinHint': "Zéro-in : réaccorde pour que la station entendue tombe sur votre note CW",
|
||||
'theme.auto': 'Auto (système)', 'theme.light-warm': 'Clair chaud', 'theme.light-cool': 'Clair froid',
|
||||
'theme.light-sage': 'Clair sauge', 'theme.dim-slate': 'Ardoise tamisé', 'theme.dark-warm': 'Sombre chaud',
|
||||
'theme.dark-graphite': 'Sombre graphite', 'theme.high-contrast': 'Contraste élevé',
|
||||
'theme.light-sage': 'Clair sauge', 'theme.light-nordic': 'Clair nordique', 'theme.dim-slate': 'Ardoise tamisé', 'theme.dark-warm': 'Sombre chaud',
|
||||
'theme.dark-graphite': 'Sombre graphite', 'theme.dark-indigo': 'Indigo', 'theme.dark-teal': 'Océan', 'theme.dark-plum': 'Prune', 'theme.high-contrast': 'Contraste élevé',
|
||||
'nav.user': 'Configuration utilisateur', 'nav.software': 'Configuration logicielle', 'nav.hardware': 'Configuration matérielle', 'nav.lists': 'Listes',
|
||||
'sec.station': 'Informations station', 'sec.profiles': 'Profils', 'sec.operating': "Conditions d'opération",
|
||||
'sec.confirmations': 'Confirmations', 'sec.external': 'Services externes',
|
||||
|
||||
@@ -6,11 +6,16 @@ import { GetUIPref } from '../../wailsjs/go/main/App';
|
||||
// CSS variables in style.css key off of. 'auto' follows the OS light/dark
|
||||
// preference. The choice is persisted (localStorage + portable UI pref, so it
|
||||
// travels with the data/ folder like the language).
|
||||
export type ThemeChoice = 'auto' | 'light-warm' | 'light-cool' | 'light-sage' | 'dim-slate' | 'dark-warm' | 'dark-graphite' | 'high-contrast';
|
||||
export type ThemeChoice = 'auto' | 'light-warm' | 'light-cool' | 'light-sage' | 'light-nordic'
|
||||
| 'dim-slate' | 'dark-warm' | 'dark-graphite' | 'dark-indigo' | 'dark-teal' | 'dark-plum' | 'high-contrast';
|
||||
|
||||
// Selectable, concrete themes (excludes 'auto') in display order.
|
||||
// Selectable, concrete themes (excludes 'auto') in display order: lights first,
|
||||
// then darks, with high-contrast last — it is an accessibility choice, not a
|
||||
// taste one, and listing it among the moods buries it.
|
||||
export const CONCRETE_THEMES: Exclude<ThemeChoice, 'auto'>[] = [
|
||||
'light-warm', 'light-cool', 'light-sage', 'dim-slate', 'dark-warm', 'dark-graphite', 'high-contrast',
|
||||
'light-warm', 'light-cool', 'light-sage', 'light-nordic',
|
||||
'dim-slate', 'dark-warm', 'dark-graphite', 'dark-indigo', 'dark-teal', 'dark-plum',
|
||||
'high-contrast',
|
||||
];
|
||||
|
||||
export const LS_KEY = 'opslog.theme';
|
||||
|
||||
+288
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user