feat(design): Fjord, a trial fifth scheme — Midnight's navy with a steel-blue accent and steel rules (E28)

This commit is contained in:
2026-09-25 15:39:49 +02:00
parent 62dbf35ea3
commit 905a9f2856
7 changed files with 94 additions and 5 deletions
+2 -1
View File
@@ -150,7 +150,8 @@ function Appearance({ prefs }: { prefs: PrefsController }) {
return (
<>
<Section title={t("settings.appearance.colorScheme")}>
<div className="grid grid-cols-4 gap-2">
{/* One row whatever the count: a fifth scheme wrapped alone onto a second line. */}
<div className="grid auto-cols-fr grid-flow-col gap-2">
{SCHEMES.map((s) => (
<Tooltip key={s.id} hint={s.name}>
<Pressable
+7 -2
View File
@@ -65,7 +65,7 @@ const pal = (
accentFg: string,
): Pal => ({ bg, surface, border, fg, muted, accent, accentFg });
// A literal copy of index.css's scheme tokens, so the four tiles render side by side (the live CSS
// A literal copy of index.css's scheme tokens, so the tiles render side by side (the live CSS
// switches only one scheme at a time). index.css is the source; theme.contract.test.ts holds it.
const SCHEMES: SchemeDef[] = [
{
@@ -88,6 +88,11 @@ const SCHEMES: SchemeDef[] = [
dark: pal("#0b0e0b", "#111510", "#2a3226", "#d9ded6", "#8f9a8a", "#86a05a", "#0c1208"),
light: pal("#f4f5f0", "#ffffff", "#dfe3d8", "#161a14", "#5a6352", "#556b2f", "#ffffff"),
},
{
name: "Fjord · steel (trial)",
dark: pal("#0d1225", "#121931", "#25506e", "#eef1f5", "#9fb0c4", "#5aa1cb", "#0d1225"),
light: pal("#f3f6fa", "#ffffff", "#c9d6e3", "#152232", "#4f6278", "#256b8f", "#ffffff"),
},
];
function Metric({ ok, children }: { ok: boolean; children: ReactNode }) {
@@ -461,7 +466,7 @@ export function BatchB() {
id="b2"
code="B2"
title="The scheme set"
rule="Four schemes, four hue families: Midnight blue, Forest cyan, Slate fuchsia, Matrix moss. YouTube and Starship are gone (a user on them lands on Midnight). Rule: an accent is ≥ 30° of OKLCH hue from danger, warn and success, and ≥ 4.5:1 as text and under its button text, dark and light."
rule="Four schemes, four hue families: Midnight blue, Forest cyan, Slate fuchsia, Matrix moss. YouTube and Starship are gone (a user on them lands on Midnight). Fjord is a trial outside the rule (Midnight's family, Playnite's steel accent): folded into Midnight or removed. Rule: an accent is ≥ 30° of OKLCH hue from danger, warn and success, and ≥ 4.5:1 as text and under its button text, dark and light."
>
<div className="grid gap-2 grid-cols-[repeat(auto-fill,minmax(9.5rem,1fr))]">
{SCHEMES.flatMap((s) =>
+42
View File
@@ -356,6 +356,14 @@ html[data-backdrop="on"][data-theme="dark"][data-scheme="matrix"] body {
),
url("/backdrops/matrix.svg");
}
html[data-backdrop="on"][data-theme="dark"][data-scheme="fjord"] body {
background-image:
linear-gradient(
color-mix(in srgb, var(--bg) var(--bg-fade), transparent),
color-mix(in srgb, var(--bg) var(--bg-fade), transparent)
),
url("/backdrops/fjord.svg");
}
html[data-backdrop="on"][data-theme="light"][data-scheme="midnight"] body {
background-image:
linear-gradient(
@@ -388,6 +396,14 @@ html[data-backdrop="on"][data-theme="light"][data-scheme="matrix"] body {
),
url("/backdrops/light/matrix.svg");
}
html[data-backdrop="on"][data-theme="light"][data-scheme="fjord"] body {
background-image:
linear-gradient(
color-mix(in srgb, var(--bg) var(--bg-fade), transparent),
color-mix(in srgb, var(--bg) var(--bg-fade), transparent)
),
url("/backdrops/light/fjord.svg");
}
html[data-backdrop="on"][data-theme="dark"] body,
html[data-backdrop="on"][data-theme="light"] body {
background-size: cover;
@@ -984,6 +1000,32 @@ html[data-scheme="matrix"][data-theme="light"] {
--accent-fg: #ffffff;
}
/* Fjord — TRIAL, not yet a member of the set (docs/DESIGN.md decision log, B2 exception, 2026-09-25):
Midnight's navy with Playnite's calm — a muted steel-blue accent and steel rules instead of the vivid
periwinkle. It shares Midnight's hue family on purpose; the owner tries it app-wide, then it is folded
into Midnight or removed (normalizeTheme lands a stored "fjord" on the default either way). Playnite's
own accent (#337599) is 3.7:1 as text, so the accent is the same hue lifted to 6.5:1. */
html[data-scheme="fjord"][data-theme="dark"] {
--bg: #0d1225;
--surface: #121931;
--card: #161e38;
--border: #25506e;
--fg: #eef1f5;
--muted: #9fb0c4;
--accent: #5aa1cb;
--accent-fg: #0d1225;
}
html[data-scheme="fjord"][data-theme="light"] {
--bg: #f3f6fa;
--surface: #ffffff;
--card: #ffffff;
--border: #c9d6e3;
--fg: #152232;
--muted: #4f6278;
--accent: #256b8f;
--accent-fg: #ffffff;
}
/* ===== Semantic status colors (danger / warn / success) =====
Scheme-INDEPENDENT, theme-scoped: a status color is recognised by its conventional
hue (red=danger), so it stays constant across schemes and only shifts by theme to
+4 -1
View File
@@ -1,7 +1,7 @@
import { LS, readAccountMerged, writeAccount } from "./storage";
type Mode = "dark" | "light";
export type Scheme = "midnight" | "forest" | "slate" | "matrix";
export type Scheme = "midnight" | "forest" | "slate" | "matrix" | "fjord";
// The scheme set (docs/DESIGN.md, decision B2): each earns its place with its own hue family, and its
// accent stays ≥ 30° of OKLCH hue away from danger / warn / success. theme.contract.test.ts measures
@@ -11,6 +11,9 @@ export const SCHEMES: { id: Scheme; name: string; swatch: string }[] = [
{ id: "forest", name: "Forest", swatch: "#22d3ee" },
{ id: "slate", name: "Slate", swatch: "#e879f9" },
{ id: "matrix", name: "Matrix", swatch: "#86a05a" },
// A TRIAL outside B2 (Midnight's hue family, DESIGN.md decision log 2026-09-25): folded into
// Midnight or removed once the owner has tried it; a stored "fjord" then lands on the default.
{ id: "fjord", name: "Fjord", swatch: "#5aa1cb" },
];
export interface ThemePrefs {