swarm-ui: derive theme override from colors.css instead of duplicating hex values
mara's review: the theme override read as a hand-copied duplicate of colors.css's hex values instead of deriving from it. Restructured colors.css to declare each palette's 16 hex values exactly once (--mocha-baseNN, --latte-baseNN) and have every activation block (the default, the prefers-color-scheme media query, and two new :root[data-theme='light'|'dark'] blocks) just re-point the active --baseNN slot at one of those two raw palettes via var() - never a second copy of a hex value. theme-apply.ts simplifies to match: it now only toggles a data-theme attribute on <html>, same shape motion-apply.ts already had. No palette values live in JS at all anymore. Re-verified the override still genuinely outranks the media query with the new mechanism: same seed-localStorage-while-forcing-the- opposite-OS-preference test as before, both directions still render the stored override correctly.
This commit is contained in:
parent
a08aacfdf6
commit
afe627e0a9
3 changed files with 200 additions and 163 deletions
|
|
@ -7,93 +7,174 @@
|
|||
with nothing else to regenerate.
|
||||
|
||||
Kept as its own standalone stylesheet (not @import-ed into theme.css)
|
||||
so a swap replaces just this ~16-line file — `theme.css` and the page
|
||||
bundles stay untouched. Every page links this BEFORE `theme.css`. The
|
||||
hex defaults are Catppuccin Mocha. Standard base16 slot meanings are
|
||||
in the trailing comments. See docs/web-ui/css-vars.md. */
|
||||
so a swap replaces just this file — `theme.css` and the page bundles
|
||||
stay untouched. Every page links this BEFORE `theme.css`. See
|
||||
docs/web-ui/css-vars.md.
|
||||
|
||||
Two raw palettes, one declaration each — `--mocha-baseNN` (dark,
|
||||
Catppuccin Mocha) and `--latte-baseNN` (light, Catppuccin-Latte-
|
||||
derived; base08-0F are darkened from stock Latte, see the comment
|
||||
above those declarations for why). The four blocks below this one
|
||||
never restate a hex value — each just points the active `--baseNN`
|
||||
slot at one of these two raw palettes via `var()`, so "which theme is
|
||||
active" and "what a theme's colours are" can't drift apart the way a
|
||||
hand-duplicated copy could. */
|
||||
:root {
|
||||
--base00: #1e1e2e; /* default bg */
|
||||
--base01: #181825; /* lighter bg (elevated surfaces) */
|
||||
--base02: #313244; /* selection / surface — borders */
|
||||
--base03: #45475a; /* comments / dim surface */
|
||||
--base04: #585b70; /* dark foreground */
|
||||
--base05: #cdd6f4; /* default foreground */
|
||||
--base06: #f5e0dc; /* light foreground */
|
||||
--base07: #b4befe; /* lightest */
|
||||
--base08: #f38ba8; /* red */
|
||||
--base09: #fab387; /* orange / peach */
|
||||
--base0A: #f9e2af; /* yellow */
|
||||
--base0B: #a6e3a1; /* green */
|
||||
--base0C: #89dceb; /* cyan (our sky — note: Catppuccin's base0C is teal) */
|
||||
--base0D: #89b4fa; /* blue */
|
||||
--base0E: #cba6f7; /* magenta / mauve */
|
||||
--base0F: #f5c2e7; /* extra accent — pink */
|
||||
--mocha-base00: #1e1e2e; /* default bg */
|
||||
--mocha-base01: #181825; /* lighter bg (elevated surfaces) */
|
||||
--mocha-base02: #313244; /* selection / surface — borders */
|
||||
--mocha-base03: #45475a; /* comments / dim surface */
|
||||
--mocha-base04: #585b70; /* dark foreground */
|
||||
--mocha-base05: #cdd6f4; /* default foreground */
|
||||
--mocha-base06: #f5e0dc; /* light foreground */
|
||||
--mocha-base07: #b4befe; /* lightest */
|
||||
--mocha-base08: #f38ba8; /* red */
|
||||
--mocha-base09: #fab387; /* orange / peach */
|
||||
--mocha-base0A: #f9e2af; /* yellow */
|
||||
--mocha-base0B: #a6e3a1; /* green */
|
||||
--mocha-base0C: #89dceb; /* cyan (our sky — note: Catppuccin's base0C is teal) */
|
||||
--mocha-base0D: #89b4fa; /* blue */
|
||||
--mocha-base0E: #cba6f7; /* magenta / mauve */
|
||||
--mocha-base0F: #f5c2e7; /* extra accent — pink */
|
||||
|
||||
--latte-base00: #eff1f5; /* default bg */
|
||||
--latte-base01: #e6e9ef; /* lighter bg (elevated surfaces) */
|
||||
--latte-base02: #ccd0da; /* selection / surface — borders */
|
||||
--latte-base03: #bcc0cc; /* comments / dim surface */
|
||||
--latte-base04: #acb0be; /* dark foreground */
|
||||
--latte-base05: #4c4f69; /* default foreground */
|
||||
--latte-base06: #dc8a78; /* light foreground */
|
||||
--latte-base07: #7287fd; /* lightest */
|
||||
/* base08-0F: darkened from stock Catppuccin Latte's own accent hexes
|
||||
(same hue/saturation, lower HSL lightness), not a literal upstream
|
||||
port like base00-07 above. A review round computed real WCAG
|
||||
contrast ratios and found the literal Latte accents fail badly as
|
||||
`StatusChip` fill-text (green/amber/red/yellow on
|
||||
`--purple-dim`/base03: 1.4:1-3:1, need 4.5:1) and even as plain
|
||||
text on `--bg`/base00 in the agent/dashboard packages that also
|
||||
consume this same file (2.3:1-4.8:1). Root cause: Latte's own
|
||||
accent colors are calibrated against Latte's near-white
|
||||
`base`/`crust`, not against a mid-gray `surface1` — the Mocha
|
||||
palette doesn't have this problem because Mocha's pastel accents
|
||||
are already *light*, so they contrast fine against a *dark*
|
||||
surface1; Latte's saturated-but-mid-brightness accents don't have
|
||||
the equivalent headroom against Latte's own (much lighter)
|
||||
surface1. Each value below was picked by holding the stock Latte
|
||||
hue+saturation fixed and binary-searching HSL lightness down to
|
||||
the point real contrast against base03 clears 4.5:1 with a small
|
||||
margin (also verified against base00: all land above 7:1 there) —
|
||||
same hue identity, same base16 role, just dark/saturated enough to
|
||||
actually be legible as filled-chip or plain text either way.
|
||||
Yellow and peach read closer to olive/brown than a bright
|
||||
yellow/orange once darkened this far — an inherent property of
|
||||
darkening a warm hue in sRGB (Bezold-Brücke shift), not a mapping
|
||||
mistake; boosting saturation further didn't rescue it (verified). */
|
||||
--latte-base08: #9c0b2a; /* red */
|
||||
--latte-base09: #883201; /* orange / peach */
|
||||
--latte-base0A: #6d450e; /* yellow */
|
||||
--latte-base0B: #235818; /* green */
|
||||
--latte-base0C: #025374; /* cyan (our sky — note: Catppuccin's base0C is teal) */
|
||||
--latte-base0D: #0843b8; /* blue */
|
||||
--latte-base0E: #6311ce; /* magenta / mauve */
|
||||
--latte-base0F: #8f166e; /* extra accent — pink */
|
||||
|
||||
/* Default activation: Mocha, unless the media query below or a
|
||||
per-user override (further below) says otherwise. */
|
||||
--base00: var(--mocha-base00);
|
||||
--base01: var(--mocha-base01);
|
||||
--base02: var(--mocha-base02);
|
||||
--base03: var(--mocha-base03);
|
||||
--base04: var(--mocha-base04);
|
||||
--base05: var(--mocha-base05);
|
||||
--base06: var(--mocha-base06);
|
||||
--base07: var(--mocha-base07);
|
||||
--base08: var(--mocha-base08);
|
||||
--base09: var(--mocha-base09);
|
||||
--base0A: var(--mocha-base0A);
|
||||
--base0B: var(--mocha-base0B);
|
||||
--base0C: var(--mocha-base0C);
|
||||
--base0D: var(--mocha-base0D);
|
||||
--base0E: var(--mocha-base0E);
|
||||
--base0F: var(--mocha-base0F);
|
||||
}
|
||||
|
||||
/* Light-mode default. base00-07 (surfaces/foreground) are stock
|
||||
Catppuccin Latte — no contrast issue there, they're the ends of the
|
||||
bg/fg scale. base08-0F (the eight chromatic "tone" slots: red through
|
||||
pink) are deliberately NOT stock Latte hexes — see the comment right
|
||||
above those declarations below for why; don't reuse stock Catppuccin
|
||||
Latte's own accent hexes there if this ever gets regenerated.
|
||||
|
||||
This is a *default*, not a user preference store: it only applies
|
||||
when nothing more specific has already decided the palette. A
|
||||
stylix-generated colors.css (nix/host-modules/hive-c0re/theme.nix)
|
||||
replaces this entire file wholesale — a fixed, unconditional `:root`
|
||||
block with no media query in it at all — so a stylix-themed
|
||||
deployment is naturally unaffected by, and never fights, this block.
|
||||
Some people need light for low-vision/contrast reasons, others need
|
||||
dark for photosensitivity, so respect the OS/browser signal by
|
||||
default the same way `prefers-reduced-motion` already is elsewhere —
|
||||
rather than always forcing the bundled dark palette on a client with
|
||||
no stylix session of its own (e.g. a phone browser hitting swarm-ui
|
||||
directly). A future per-user override takes precedence over this
|
||||
media query by simply setting the vars later in the cascade (e.g.
|
||||
via an inline `style` on `:root`, which always outranks a
|
||||
stylesheet rule). */
|
||||
/* OS/browser-level light-mode default. This is a *default*, not a user
|
||||
preference store: it only applies when nothing more specific has
|
||||
already decided the palette. A stylix-generated colors.css
|
||||
(nix/host-modules/hive-c0re/theme.nix) replaces this entire file
|
||||
wholesale — a fixed, unconditional `:root` block with no media query
|
||||
in it at all — so a stylix-themed deployment is naturally unaffected
|
||||
by, and never fights, this block. Some people need light for
|
||||
low-vision/contrast reasons, others need dark for photosensitivity,
|
||||
so respect the OS/browser signal by default the same way
|
||||
`prefers-reduced-motion` already is elsewhere — rather than always
|
||||
forcing the bundled dark palette on a client with no stylix session
|
||||
of its own (e.g. a phone browser hitting swarm-ui directly). */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--base00: #eff1f5; /* default bg */
|
||||
--base01: #e6e9ef; /* lighter bg (elevated surfaces) */
|
||||
--base02: #ccd0da; /* selection / surface — borders */
|
||||
--base03: #bcc0cc; /* comments / dim surface */
|
||||
--base04: #acb0be; /* dark foreground */
|
||||
--base05: #4c4f69; /* default foreground */
|
||||
--base06: #dc8a78; /* light foreground */
|
||||
--base07: #7287fd; /* lightest */
|
||||
/* base08-0F: darkened from stock Catppuccin Latte's own accent
|
||||
hexes (same hue/saturation, lower HSL lightness), not a literal
|
||||
upstream port like base00-07 above. argus's review on this PR
|
||||
computed real WCAG contrast ratios and found the literal Latte
|
||||
accents fail badly as `StatusChip` fill-text (green/amber/red/
|
||||
yellow on `--purple-dim`/base03: 1.4:1-3:1, need 4.5:1) and even
|
||||
as plain text on `--bg`/base00 in the agent/dashboard packages
|
||||
that also consume this same file (2.3:1-4.8:1). Root cause:
|
||||
Latte's own accent colors are calibrated against Latte's
|
||||
near-white `base`/`crust`, not against a mid-gray `surface1` —
|
||||
the Mocha row doesn't have this problem because Mocha's pastel
|
||||
accents are already *light*, so they contrast fine against a
|
||||
*dark* surface1; Latte's saturated-but-mid-brightness accents
|
||||
don't have the equivalent headroom against Latte's own
|
||||
(much lighter) surface1. Each value below was picked by holding
|
||||
the stock Latte hue+saturation fixed and binary-searching HSL
|
||||
lightness down to the point real contrast against base03 clears
|
||||
4.5:1 with a small margin (also verified against base00: all
|
||||
land above 7:1 there) — same hue identity, same base16 role,
|
||||
just dark/saturated enough to actually be legible as filled-chip
|
||||
or plain text either way. Yellow and peach read closer to
|
||||
olive/brown than a bright yellow/orange once darkened this far —
|
||||
an inherent property of darkening a warm hue in sRGB (Bezold-
|
||||
Brücke shift), not a mapping mistake; boosting saturation
|
||||
further didn't rescue it (verified). */
|
||||
--base08: #9c0b2a; /* red */
|
||||
--base09: #883201; /* orange / peach */
|
||||
--base0A: #6d450e; /* yellow */
|
||||
--base0B: #235818; /* green */
|
||||
--base0C: #025374; /* cyan (our sky — note: Catppuccin's base0C is teal) */
|
||||
--base0D: #0843b8; /* blue */
|
||||
--base0E: #6311ce; /* magenta / mauve */
|
||||
--base0F: #8f166e; /* extra accent — pink */
|
||||
--base00: var(--latte-base00);
|
||||
--base01: var(--latte-base01);
|
||||
--base02: var(--latte-base02);
|
||||
--base03: var(--latte-base03);
|
||||
--base04: var(--latte-base04);
|
||||
--base05: var(--latte-base05);
|
||||
--base06: var(--latte-base06);
|
||||
--base07: var(--latte-base07);
|
||||
--base08: var(--latte-base08);
|
||||
--base09: var(--latte-base09);
|
||||
--base0A: var(--latte-base0A);
|
||||
--base0B: var(--latte-base0B);
|
||||
--base0C: var(--latte-base0C);
|
||||
--base0D: var(--latte-base0D);
|
||||
--base0E: var(--latte-base0E);
|
||||
--base0F: var(--latte-base0F);
|
||||
}
|
||||
}
|
||||
|
||||
/* Per-user override (swarm-ui's `SettingsMenu` / `lib/theme-apply.ts`
|
||||
sets `data-theme` on `<html>`; unset/`"system"` removes the
|
||||
attribute and lets the media query above decide, same as before this
|
||||
existed). `:root[data-theme='light']` has higher specificity than
|
||||
the plain `:root` inside the media query above (an attribute
|
||||
selector always outranks none), so this wins regardless of the
|
||||
actual OS preference or source order — no `!important`, no inline
|
||||
style, and critically no second copy of either palette's hex values:
|
||||
both blocks below just re-point at the same `--mocha-baseNN` /
|
||||
`--latte-baseNN` custom properties declared once at the top of this
|
||||
file. */
|
||||
:root[data-theme='light'] {
|
||||
--base00: var(--latte-base00);
|
||||
--base01: var(--latte-base01);
|
||||
--base02: var(--latte-base02);
|
||||
--base03: var(--latte-base03);
|
||||
--base04: var(--latte-base04);
|
||||
--base05: var(--latte-base05);
|
||||
--base06: var(--latte-base06);
|
||||
--base07: var(--latte-base07);
|
||||
--base08: var(--latte-base08);
|
||||
--base09: var(--latte-base09);
|
||||
--base0A: var(--latte-base0A);
|
||||
--base0B: var(--latte-base0B);
|
||||
--base0C: var(--latte-base0C);
|
||||
--base0D: var(--latte-base0D);
|
||||
--base0E: var(--latte-base0E);
|
||||
--base0F: var(--latte-base0F);
|
||||
}
|
||||
:root[data-theme='dark'] {
|
||||
--base00: var(--mocha-base00);
|
||||
--base01: var(--mocha-base01);
|
||||
--base02: var(--mocha-base02);
|
||||
--base03: var(--mocha-base03);
|
||||
--base04: var(--mocha-base04);
|
||||
--base05: var(--mocha-base05);
|
||||
--base06: var(--mocha-base06);
|
||||
--base07: var(--mocha-base07);
|
||||
--base08: var(--mocha-base08);
|
||||
--base09: var(--mocha-base09);
|
||||
--base0A: var(--mocha-base0A);
|
||||
--base0B: var(--mocha-base0B);
|
||||
--base0C: var(--mocha-base0C);
|
||||
--base0D: var(--mocha-base0D);
|
||||
--base0E: var(--mocha-base0E);
|
||||
--base0F: var(--mocha-base0F);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue