From e4af3ded0733cfa8e3be3fc7b7607bb83caf44e3 Mon Sep 17 00:00:00 2001 From: Kostya Farber Date: Wed, 30 Sep 2026 11:21:45 +0100 Subject: [PATCH 1/5] fix: give unstyled text the theme's primary colour Nothing set a base text colour, so any text without its own colour class fell back to the browser's colour-scheme default: pure white in dark themes instead of the palette's foreground. --- apps/desktop/src/renderer/index.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/desktop/src/renderer/index.css b/apps/desktop/src/renderer/index.css index 74dc36f0..3b69fcf0 100644 --- a/apps/desktop/src/renderer/index.css +++ b/apps/desktop/src/renderer/index.css @@ -127,6 +127,11 @@ 0 1px 3px -1px var(--color-shadow-key); } +/* Unstyled text follows the theme instead of the browser's color-scheme default. */ +body { + color: var(--color-primary); +} + /* Hide scrollbar while preserving scroll behavior */ @utility scrollbar-hidden { scrollbar-width: none; From 38f460a45aea0b20a4e8f11efee2e7c279d8dd1c Mon Sep 17 00:00:00 2001 From: Kostya Farber Date: Sat, 3 Oct 2026 18:13:24 +0300 Subject: [PATCH 2/5] fix(themes): lift light-theme toolbars off the selection colour Base16 light palettes put a strong selection colour in base02, which made Solarized Light's toolbar a dark grey-teal with unreadable text and hid toolbar hover entirely. Light chrome now tints the canvas toward the text colour, matching Shift Light's own step, and hover uses a translucent text tint. --- .../src/renderer/src/lib/themes/index.test.ts | 22 +++++++++++++++++++ .../src/renderer/src/lib/themes/index.ts | 21 ++++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/renderer/src/lib/themes/index.test.ts b/apps/desktop/src/renderer/src/lib/themes/index.test.ts index 8e0b88db..7c0813c0 100644 --- a/apps/desktop/src/renderer/src/lib/themes/index.test.ts +++ b/apps/desktop/src/renderer/src/lib/themes/index.test.ts @@ -24,6 +24,28 @@ describe("color themes", () => { expect(resolveThemeSelection("gruvbox-light", "dark").id).toBe("gruvbox-light"); }); + it("keeps the toolbar distinct from the canvas and its hover in every override theme", () => { + for (const theme of colorThemes.filter((theme) => theme.id !== "shift-light")) { + const properties = new Map(); + const root = { + dataset: {}, + style: { + colorScheme: "", + setProperty: (name: string, value: string) => properties.set(name, value), + removeProperty: () => "", + }, + } as unknown as HTMLElement; + + applyResolvedTheme(theme, root); + expect(properties.get("--color-icon-button-hover"), theme.id).not.toBe( + properties.get("--color-chrome"), + ); + expect(properties.get("--color-chrome"), theme.id).not.toBe( + properties.get("--color-surface-muted"), + ); + } + }); + it("applies theme metadata and removes overrides for Shift Light", () => { const properties = new Map(); const root = { diff --git a/apps/desktop/src/renderer/src/lib/themes/index.ts b/apps/desktop/src/renderer/src/lib/themes/index.ts index 3b7637fa..0d4afbc9 100644 --- a/apps/desktop/src/renderer/src/lib/themes/index.ts +++ b/apps/desktop/src/renderer/src/lib/themes/index.ts @@ -281,21 +281,23 @@ function colorThemeVariables(theme: ColorTheme): Record { const muted = dark ? withAlpha(palette.base05, 0.6) : palette.base03; const handleOverlay = dark ? palette.base07 : palette.base00; const surface = dark ? palette.base01 : palette.base00; - const chrome = - palette.base02 === surface ? withAlpha(palette.base05, dark ? 0.08 : 0.12) : palette.base02; + // Light palettes put a strong selection colour in base02, so light chrome tints the canvas (base01) + // toward the text colour instead; on Shift Light's palette this reproduces its #e2e2e2 chrome. + const darkChrome = palette.base02 === surface ? withAlpha(palette.base05, 0.08) : palette.base02; + const chrome = dark ? darkChrome : mix(palette.base01, palette.base05, 0.07); return { "--color-background": palette.base00, "--color-surface": surface, "--color-surface-muted": palette.base01, "--color-chrome": chrome, - "--color-hover": dark ? withAlpha(palette.base05, 0.16) : palette.base02, + "--color-hover": withAlpha(palette.base05, dark ? 0.16 : 0.1), "--color-surface-hover": dark ? withAlpha(palette.base05, 0.12) : palette.base01, "--color-surface-inverse": palette.base07, "--color-on-surface-inverse": palette.base00, "--color-input": dark ? withAlpha(palette.base05, 0.12) : palette.base01, "--color-icon-button": dark ? withAlpha(palette.base05, 0.12) : palette.base01, - "--color-icon-button-hover": dark ? withAlpha(palette.base05, 0.2) : palette.base02, + "--color-icon-button-hover": withAlpha(palette.base05, dark ? 0.2 : 0.14), "--color-line": dark ? palette.base03 : palette.base06, "--color-line-subtle": dark ? withAlpha(palette.base05, 0.2) : palette.base01, "--color-shadow-ambient": dark ? "rgba(0, 0, 0, 0.55)" : "rgba(50, 50, 93, 0.25)", @@ -400,6 +402,17 @@ function relativeLuminance(color: string): number { return 0.2126 * channel(1) + 0.7152 * channel(3) + 0.0722 * channel(5); } +function mix(color: string, tint: string, amount: number): string { + const channel = (offset: number) => { + const base = Number.parseInt(color.slice(offset, offset + 2), 16); + const target = Number.parseInt(tint.slice(offset, offset + 2), 16); + return Math.round(base + (target - base) * amount) + .toString(16) + .padStart(2, "0"); + }; + return `#${channel(1)}${channel(3)}${channel(5)}`; +} + function withAlpha(color: string, alpha: number): string { const red = Number.parseInt(color.slice(1, 3), 16); const green = Number.parseInt(color.slice(3, 5), 16); From 53ad8afb4653c203e9b38572ab5ae87103cdb397 Mon Sep 17 00:00:00 2001 From: Kostya Farber Date: Sat, 3 Oct 2026 18:14:03 +0300 Subject: [PATCH 3/5] fix(desktop): hide scrollbars until their element scrolls Unstyled scrollers such as the glyph catalog and text areas drew the browser's white track. Every scrollbar now stays transparent and shows a theme-coloured thumb only while its element is scrolling. --- apps/desktop/src/renderer/index.css | 20 ++++------- apps/desktop/src/renderer/renderer.ts | 3 ++ .../src/components/editor/LeftSidebar.tsx | 2 +- .../src/components/editor/ObjectsPanel.tsx | 2 +- .../src/components/home/LeftSidebar.tsx | 2 +- .../home/glyph-catalog/LanguagePicker.tsx | 2 +- .../glyph-catalog/MissingGlyphsPopover.tsx | 2 +- .../renderer/src/lib/themes/scrollActivity.ts | 35 +++++++++++++++++++ 8 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 apps/desktop/src/renderer/src/lib/themes/scrollActivity.ts diff --git a/apps/desktop/src/renderer/index.css b/apps/desktop/src/renderer/index.css index 3b69fcf0..b356b5d1 100644 --- a/apps/desktop/src/renderer/index.css +++ b/apps/desktop/src/renderer/index.css @@ -141,22 +141,14 @@ body { } } -/* Keep scrollbars legible without introducing a host-defined track color. */ -@utility scrollbar-themed { - scrollbar-color: var(--color-control-muted) var(--color-surface); - - &::-webkit-scrollbar { - width: 10px; - } - - &::-webkit-scrollbar-track { - background: var(--color-surface); +/* Overlay-style scrollbars everywhere: thumbs only show while their element is scrolling (see trackScrollActivity). */ +@layer base { + * { + scrollbar-color: transparent transparent; } - &::-webkit-scrollbar-thumb { - border: 2px solid var(--color-surface); - border-radius: 9999px; - background: var(--color-control-muted); + [data-scrolling] { + scrollbar-color: var(--color-control-muted) transparent; } } diff --git a/apps/desktop/src/renderer/renderer.ts b/apps/desktop/src/renderer/renderer.ts index bfe356c9..303b8c41 100644 --- a/apps/desktop/src/renderer/renderer.ts +++ b/apps/desktop/src/renderer/renderer.ts @@ -30,6 +30,9 @@ import "./index.css"; import React from "react"; import { createRoot } from "react-dom/client"; import App from "./src/app/App"; +import { trackScrollActivity } from "./src/lib/themes/scrollActivity"; + +trackScrollActivity(); // Mount the React app const container = document.getElementById("root"); diff --git a/apps/desktop/src/renderer/src/components/editor/LeftSidebar.tsx b/apps/desktop/src/renderer/src/components/editor/LeftSidebar.tsx index a4e2e572..836f2950 100644 --- a/apps/desktop/src/renderer/src/components/editor/LeftSidebar.tsx +++ b/apps/desktop/src/renderer/src/components/editor/LeftSidebar.tsx @@ -28,7 +28,7 @@ export const LeftSidebar = () => ( -
+
diff --git a/apps/desktop/src/renderer/src/components/editor/ObjectsPanel.tsx b/apps/desktop/src/renderer/src/components/editor/ObjectsPanel.tsx index 1f7b6fdb..e8092648 100644 --- a/apps/desktop/src/renderer/src/components/editor/ObjectsPanel.tsx +++ b/apps/desktop/src/renderer/src/components/editor/ObjectsPanel.tsx @@ -172,7 +172,7 @@ export const ObjectsPanel = () => { return (
setScrollTop(event.currentTarget.scrollTop)} >