Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
31 changes: 29 additions & 2 deletions docs/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 |
Expand All @@ -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.
Expand Down Expand Up @@ -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.
12 changes: 12 additions & 0 deletions tsconfig.frontend.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
],
Expand Down Expand Up @@ -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"
],
Expand Down
23 changes: 19 additions & 4 deletions ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<!-- Same value as the dashboard and the manifest, so the browser chrome does
not change colour when moving between them. -->
not change colour when moving between them. admin-theme.js rewrites it
when this page renders in the light theme. -->
<meta name="theme-color" content="#0d1117">
<title>Stackyard Settings</title>
<script src="/js/mobile-class.js?v=fe95da0d"></script>
<link rel="stylesheet" href="/css/tokens.css?v=f8135c6f">
<link rel="stylesheet" href="/css/admin.css?v=2635f968">
<script src="/js/admin-theme.js?v=66565254"></script>
<link rel="stylesheet" href="/css/tokens.css?v=5077138e">
<link rel="stylesheet" href="/css/admin.css?v=3904bb4a">
<link rel="stylesheet" href="/css/widget-config-form.css?v=53b91d5f">
</head>
<body>
Expand Down Expand Up @@ -127,8 +129,21 @@
</div>
<div class="grp-hdr" data-i18n="appearance.display">Display</div>
<div class="grp">
<div class="row">
<span class="rl" data-i18n="appearance.displayMode">Settings Display Mode</span>
<div class="row-dd" id="theme-dd">
<button class="row-dd-btn" type="button" id="theme-btn" aria-haspopup="listbox">System Default<svg class="dd-chev" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 10.5 12 6.5 16 10.5"/><path d="M8 13.5 12 17.5 16 13.5"/></svg></button>
<ul class="row-dd-list" id="theme-list" role="listbox" aria-label="Settings display mode" hidden>
<li role="option" data-val="system" aria-selected="true" data-i18n="appearance.displaySystem">System Default</li>
<li role="option" data-val="light" aria-selected="false" data-i18n="appearance.displayLight">Light</li>
<li role="option" data-val="dark" aria-selected="false" data-i18n="appearance.displayDark">Dark</li>
</ul>
</div>
<input type="hidden" id="theme-sel" value="system">
</div>
<div class="row"><span class="rl" data-i18n="appearance.keepAwake">Keep Screen Awake</span><label class="tog" aria-label="Keep Screen Awake"><input type="checkbox" id="set-awake"><div class="tr"></div></label></div>
</div>
<p class="grp-tip" data-i18n="appearance.displayModeTip">Sets the appearance of the Settings pages. It is stored in this browser, applies to this device only, and takes effect at once.</p>
<p class="grp-tip" data-i18n="appearance.keepAwakeTip">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.</p>
<div class="grp-hdr" data-i18n="appearance.wallpaper">Wallpaper</div>
<div class="grp">
Expand Down Expand Up @@ -288,6 +303,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=4a92bad6"></script>
<script type="module" src="/js/admin.js?v=e2141d76"></script>
</body>
</html>
Loading
Loading