feat(webui): Mono theme — pure white in light mode, pure black in dark mode

Adds a fourth accent alongside sakura/ocean/violet. Unlike those it is not just a
different hue: the coloured themes are glass surfaces (translucent cards with
backdrop-filter) floating over an animated gradient-mesh background, so setting
--card:#ffffff there still renders as a tinted grey. Mono therefore also switches
off the translucency and hides the blobs, so #ffffff is actually #ffffff and
#000000 is actually #000000, with greys carrying the hierarchy that hue carries
elsewhere. A side effect worth having: no backdrop-filter and no animated blobs
makes it the cheapest theme to render, which helps on weak GPUs and over remote
desktops.

Both light and dark variable blocks are defined, so the existing light/dark
toggle drives it with no extra wiring: light -> white, dark -> black.

Also fixes a latent theme bug found while checking contrast on black: the active
chart's grid baseline assigned the literal string "var(--line)" to
ctx.strokeStyle. Canvas 2D does not resolve CSS custom properties, so that was an
invalid colour the browser ignored, leaving the previous fillStyle (black) — an
invisible baseline on every dark theme. Colours used on a canvas now go through a
cssVar() helper.

Tests: TestUIThemeMatrix asserts every accent defines BOTH a light and a dark
block plus a picker button (a half-defined theme shows up as unreadable text, not
as an error); TestUIMonoThemeIsFlat pins the opaque surfaces and disabled blobs;
TestUICanvasColorsResolveVars fails if any ctx.strokeStyle/fillStyle is handed a
raw var().

Unrelated packaging fix in the same commit: dist:linux only built deb+AppImage
while build.linux.target listed rpm too, so `make gui-dist` silently skipped the
rpm that release builds are expected to produce. Makefile/README wording updated
to match.
This commit is contained in:
JianFeeeee
2026-08-30 10:04:53 +08:00
parent 5b6d6fc90d
commit c309448414
6 changed files with 184 additions and 7 deletions

View File

@ -83,6 +83,56 @@
--blob1:rgba(168,85,247,.32); --blob2:rgba(34,211,238,.26); --blob3:rgba(139,92,246,.22);
--line:rgba(168,85,247,.16); --line-strong:rgba(139,92,246,.18);
}
/* ---- Mono: pure white (light) / pure black (dark) ----
Unlike the coloured accents this one deliberately drops the glass
translucency and the gradient-mesh blobs: "pure white" has to actually be
#fff, not a tinted translucent card floating over a gradient. Greys carry
the hierarchy that hue carries in the other themes, and the flat surfaces
also make this the cheapest theme to render (no backdrop-filter, no
animated blobs) — handy on weak GPUs and remote desktops. */
html[data-accent="mono"]{
--primary:#1f1f1f; --primary-h:#000000; --primary-50:#f4f4f5; --primary-100:#e8e8ea;
--primary-200:#d4d4d8; --primary-300:#a1a1aa; --primary-400:#52525b; --primary-500:#1f1f1f;
--secondary:#71717a; --secondary-h:#3f3f46; --secondary-50:#f4f4f5; --secondary-200:#d4d4d8;
--danger:#b91c1c; --danger-50:#fdeaea; --ok:#15803d; --warn:#a16207; --info:#3f3f46;
--bg-s1:#ffffff; --bg-s2:#ffffff; --bg-s3:#ffffff;
--card:#ffffff; --card-solid:#ffffff; --card2:#f6f6f7;
--line:rgba(0,0,0,.12); --line-strong:rgba(0,0,0,.22);
--fg:#18181b; --muted:#66666f;
--blob1:transparent; --blob2:transparent; --blob3:transparent;
--sh-sm:0 1px 2px rgba(0,0,0,.06);
--sh-md:0 2px 8px rgba(0,0,0,.09);
--sh-lg:0 8px 24px rgba(0,0,0,.13);
--glass:0px;
}
html[data-theme="dark"][data-accent="mono"]{
--primary:#f4f4f5; --primary-h:#ffffff; --primary-50:#161618; --primary-100:#1f1f22;
--primary-200:#2a2a2e; --primary-300:#3f3f46; --primary-400:#a1a1aa; --primary-500:#f4f4f5;
--secondary:#a1a1aa; --secondary-h:#d4d4d8; --secondary-50:#161618; --secondary-200:#2a2a2e;
--danger:#f87171; --danger-50:#2a1414; --ok:#4ade80; --warn:#fbbf24; --info:#d4d4d8;
--bg-s1:#000000; --bg-s2:#000000; --bg-s3:#000000;
--card:#0a0a0b; --card-solid:#0a0a0b; --card2:#151517;
--line:rgba(255,255,255,.14); --line-strong:rgba(255,255,255,.26);
--fg:#f4f4f5; --muted:#8c8c96;
--blob1:transparent; --blob2:transparent; --blob3:transparent;
--sh-sm:0 1px 2px rgba(0,0,0,.6);
--sh-md:0 2px 8px rgba(0,0,0,.7);
--sh-lg:0 8px 24px rgba(0,0,0,.8);
--glass:0px;
}
/* Flat surfaces: kill the translucency/blur that the glass themes rely on,
otherwise white/black would show through as grey. */
html[data-accent="mono"] #bgfx{background:var(--bg-s1)}
html[data-accent="mono"] #bgfx .blob{display:none}
html[data-accent="mono"] .sidebar,
html[data-accent="mono"] .card,
html[data-accent="mono"] .modal,
html[data-accent="mono"] .breadcrumb{backdrop-filter:none;-webkit-backdrop-filter:none}
html[data-accent="mono"] nav button.sb-i.active{background:var(--primary-50)}
html[data-accent="mono"] .sb-logo{background:var(--fg);color:var(--bg-s1);box-shadow:none}
html[data-accent="mono"] ::selection{background:var(--fg);color:var(--bg-s1)}
html[data-accent="mono"] ::-webkit-scrollbar-thumb{background:rgba(128,128,136,.45)}
html[data-accent="mono"] ::-webkit-scrollbar-thumb:hover{background:rgba(128,128,136,.75)}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;color:var(--fg);overflow:hidden;
@ -671,6 +721,22 @@
<path d="M12 2.5v19" /></svg
>Violet
</button>
<button data-accent="mono" title="Mono — 纯白(浅色)/ 纯黑(深色)">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
width="14"
height="14"
>
<circle cx="12" cy="12" r="9" />
<path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" />
</svg
>Mono
</button>
</div>
</div>
</aside>
@ -1836,6 +1902,16 @@
.join("");
}
// sparkline: turn a value array into a compact polyline (area fill under accent)
// cssVar reads a CSS custom property off the document root. Canvas 2D does
// not understand var(), so any theme-driven colour used on a canvas has to
// be resolved here first.
function cssVar(name, fallback) {
const v = getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
return v || fallback;
}
function sparkline(ctx, vals, w, h, color) {
if (!vals.length) {
ctx.strokeStyle = color;
@ -1909,8 +1985,11 @@
qs.data.push(Math.max(rate, 0));
}
if (qs.data.length > 40) qs.data.shift();
// grid baseline
ctx.strokeStyle = "var(--line)";
// grid baseline. Canvas cannot resolve CSS custom properties, so the
// literal "var(--line)" used to be an invalid colour that the browser
// silently ignored, leaving whatever fillStyle was set last (black) —
// invisible on dark themes. Resolve it against the document instead.
ctx.strokeStyle = cssVar("--line", "rgba(128,128,128,.2)");
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, H - 1);