From d6fdab2b49e8832b90f0b3f81b418205b0aa8b82 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Sun, 16 Aug 2026 17:54:48 -0400 Subject: [PATCH 1/2] Add: light theme for the settings pages Redeclare the palette, roles and semantic sets at their light values under html[data-theme="light"], and scope the admin page's overlays, elevation and surfaces to the same attribute. A Settings Display Mode row in Appearance selects System Default, Light or Dark. The choice is stored per device in localStorage and applied before the first paint by a classic script. --- CHANGELOG.md | 3 + README.md | 2 +- docs/design-system.md | 31 ++++- tsconfig.frontend.json | 12 ++ ui/admin/index.html | 23 +++- ui/css/admin.css | 210 +++++++++++++++++++++++--------- ui/css/tokens.css | 101 ++++++++++++++- ui/i18n/de.json | 5 + ui/i18n/en.json | 5 + ui/i18n/es.json | 5 + ui/i18n/fa.json | 5 + ui/i18n/fr.json | 5 + ui/i18n/zh-Hans.json | 5 + ui/index.html | 2 +- ui/js/admin-theme.js | 17 +++ ui/js/admin.js | 72 ++++++++--- ui/js/theme.js | 91 ++++++++++++++ ui/test/contrast.test.mjs | 133 ++++++++++++++------ ui/test/palette-values.test.mjs | 127 ++++++++++++++----- ui/test/theme.test.mjs | 114 +++++++++++++++++ 20 files changed, 816 insertions(+), 152 deletions(-) create mode 100644 ui/js/admin-theme.js create mode 100644 ui/js/theme.js create mode 100644 ui/test/theme.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2398ca2c..1c9a3332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Light theme for the Settings pages, with Settings Display Mode in Settings > + Appearance: System Default, Light or Dark. Stored per device. + - Keep Screen Awake in Settings > Appearance, off by default. Needs HTTPS. - Show From on a live activity badge, to badge only counts at or above a number. diff --git a/README.md b/README.md index a45efcb5..dcbe5306 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Then run `stackyard:local` the same way as above. For working on the code withou Everything is configured in the admin UI (`/admin`), split into a few sections: - **General**: title, description, and your server's **Host IP** (used to allow your own server in badge URLs and SSRF checks). Also **Monitoring** (logging level, Docker container health checks, and the socket URL used to reach Docker), optional **password protection**, and config **import / export**. Backup also imports a gethomepage or Dashy YAML config: links and folders are added to the dashboard, existing items are left alone, and anything that cannot be carried across is listed before you confirm. -- **Appearance**: wallpaper and the overall look. Also **Keep Screen Awake**, which stops the screen dimming while the dashboard is open. Browsers allow this over HTTPS only. +- **Appearance**: wallpaper and the overall look. **Settings Display Mode** picks the appearance of the Settings pages: System Default, Light or Dark. It is stored in the browser and applies to that device only. Also **Keep Screen Awake**, which stops the screen dimming while the dashboard is open. Browsers allow this over HTTPS only. - **Dashboard**: add and arrange your apps, folders, and widgets, and configure each one (including live activity badges). - **About**: version and links. diff --git a/docs/design-system.md b/docs/design-system.md index 2a87ce48..b0ef4019 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -4,7 +4,7 @@ The visual system both pages share. It lives in `ui/css/tokens.css`. Values are measured from a reference design kit, not chosen. -Dark values only. No stylesheet declares a `prefers-color-scheme` rule. +Two themes, dark and light. No stylesheet declares a `prefers-color-scheme` rule; see Themes below. ## The three colour layers @@ -16,6 +16,8 @@ Do not name a palette entry in a rule. The layers above point at it. **Roles.** What a colour is for: `--accent`, `--danger`, `--warning`, `--success`. Each points at a hue. +`--on-accent` is the ink on a filled control: an accent button, a selected row, a tile carrying a user-chosen colour. It is white in both themes. It is not a label level, because the surface under it is a colour rather than a background. + **Semantic.** What a colour is applied to. | Set | Tokens | Use | @@ -28,6 +30,22 @@ Do not name a palette entry in a rule. The layers above point at it. Two singletons sit beside them. `--bg-primary-light` is the opt-in light widget card. `--control-knob` is the white of a slider or switch knob, full white in both themes. +## Themes + +The dark theme is the default and is declared on `:root`. The light theme redeclares the palette, the roles and the semantic sets under `html[data-theme="light"]`. + +A page opts in by carrying the attribute. Only the Settings page does. The dashboard never sets it and has one appearance. + +`ui/js/admin-theme.js` writes the attribute before the first paint. It reads the mode from `localStorage` under `sy-theme`: `system`, `light` or `dark`. `system` is resolved in script, not in a media query, so there is one light block rather than two. `ui/js/theme.js` holds the same logic for the running page, and `ui/test/theme.test.mjs` holds the two together. + +The choice is per device. It is never written to the config. + +Light hues are drawn for a fill. Green at `#34C759` behind text is a badge, not a sentence. A role that ends up as text or as the edge of a control points at the `-hi` entry, which is the accessible variant of the same hue. `--accent` is one of them. + +`prefers-contrast: more` raises whichever theme is in force. A light hue needs its `-hi` partner for the same reason a dark one does. + +`ui/test/contrast.test.mjs` measures every required pair in four combinations: each theme, raised and not. + ## Two limits on the semantic layer **Label levels below primary do not clear WCAG on this project's cards.** Use `--sy-a11y-dim` for text that has to be read on a card. Use a label level for hierarchy. `ui/test/contrast.test.mjs` fails on a regression. @@ -172,8 +190,17 @@ It cannot name a token. Most widget colours sit in canvas fills, SVG attributes A surface particular to one page is declared in that page's own `:root`. `--pane` and `--cp` on the admin page, `--glass-bg` and `--dock-bg` on the dashboard. These are not part of the system. +A themed page declares its light values in the same file, under `html[data-theme="light"]`. + +The admin page also scopes two families there rather than writing values into rules. + +| Family | Tokens | Use | +| --- | --- | --- | +| Overlays | `--ov-soft`, `--ov`, `--ov-strong`, `--field-fill`, `--track-off` | A tint over whatever surface a control sits on. White over dark, black over light | +| Elevation | `--shadow-pop`, `--shadow-dlg`, `--shadow-login`, `--shadow-ghost`, `--shadow-bar`, `--shadow-knob`, `--shadow-tog`, `--shadow-sunken` | A shadow over a dark surface is depth; the same shadow over a light one is dirt | + ## Rules A colour written as a literal outside `tokens.css` fails `ui/test/css-tokens.test.mjs`. The same test checks that every `var()` names a token that exists. `ui/test/palette-values.test.mjs` pins each palette entry to its reference value. -`#fff` and `#000` are allowed as ink on an arbitrary coloured fill, where the colour underneath is user-chosen and no token can describe it. +`#fff` and `#000` are allowed as ink on an arbitrary coloured fill, where the colour underneath is user-chosen and no token can describe it. On the admin page `--on-accent` names that ink. diff --git a/tsconfig.frontend.json b/tsconfig.frontend.json index f6502ef3..4c26d695 100644 --- a/tsconfig.frontend.json +++ b/tsconfig.frontend.json @@ -76,6 +76,12 @@ "/js/admin-settings.js?v=*": [ "./ui/js/admin-settings.js" ], + "/js/admin-theme.js": [ + "./ui/js/admin-theme.js" + ], + "/js/admin-theme.js?v=*": [ + "./ui/js/admin-theme.js" + ], "/js/admin-shared.js": [ "./ui/js/admin-shared.js" ], @@ -208,6 +214,12 @@ "/js/spotlight.js?v=*": [ "./ui/js/spotlight.js" ], + "/js/theme.js": [ + "./ui/js/theme.js" + ], + "/js/theme.js?v=*": [ + "./ui/js/theme.js" + ], "/js/ui.js": [ "./ui/js/ui.js" ], diff --git a/ui/admin/index.html b/ui/admin/index.html index 5bec2927..5814434b 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -4,12 +4,14 @@ + not change colour when moving between them. admin-theme.js rewrites it + when this page renders in the light theme. -->
Sets the appearance of the Settings pages. It is stored in this browser, applies to this device only, and takes effect at once.
Stops the screen dimming or locking while the dashboard is open. Browsers only allow this over HTTPS, so it has no effect on a plain HTTP address.