detached 6a915d6

feat(web): footer theme switch (auto, light, dark) (!33)

- feat(web): footer theme switch for auto, light and dark modes
- feat(web): announce theme changes, refresh on bfcache restore, match theme-color by data-scheme
commit
6a915d6ce62ef1d4936877d6320427e4b1a51e75
author
brice · · 2026-10-22 09:45 UTC
committer
ermite · · 2026-10-22 11:33 UTC
parent
a7f3fdb
signature
SSH VerifiedSHA256:lY9fILbB/A+hln6Vh/iHoiiog74bbw/4qWrf/lrTU3U · ermite instance key Verified for the committer email
checks
4/4
43 files · +457 -74
M internal/adapters/web/ASSETS.md +2 -2
@@ -8,7 +8,7 @@ content-hashed names. Nothing is fetched at runtime.
88 | `static/js/htmx.min.js` | htmx 4.0.0 (BSD Zero Clause, `static/js/htmx-LICENSE`) | `mockups/assets/htmx/htmx.min.js` of the design | `e484d9171a9db30a39c8f16e3d709d4137f3211c659f8e6125816635033d593f` |
99 | `static/js/hx-live.min.js` | htmx 4 `hx-live` extension | `mockups/assets/htmx/ext/hx-live.min.js` | `adf9b90949d57baf20dc2eaf1d1fd46c81347ac82c090a5e2912ce6b37726734` |
1010 | `static/js/hx-sse.min.js` | htmx 4 `hx-sse` extension | `mockups/assets/htmx/ext/hx-sse.min.js` | `8a834680c4000a9034d79228872372a92e140c810a075cb6d4a76690dfc13085` |
11-| `static/js/store.js` | the localStorage client of the scripts: preferences such as the home layout and the single-key shortcuts (`window.ermiteStore`, tested in Node by `jstest/store.test.mjs`) | written here | |
11+| `static/js/store.js` | the localStorage client of the scripts: preferences such as the home layout and the single-key shortcuts (`window.ermiteStore`, tested in Node by `jstest/store.test.mjs`); applies the wide mode and the theme (`window.ermiteTheme`: auto, light or dark, key `theme`; tested by `jstest/theme.test.mjs`) before the first paint | written here | |
1212 | `static/js/ermite.js` | key sequences to `k-*` events, the functions the keys and inputs call (`window.ermite`), the live fragments (`ermiteLive`), the diff table of contents observer | `mockups/d/js/ermite.js`, extended here | |
1313 | `static/js/log.js` | the live log of a running job (`window.ermiteLog`, tested in Node by `jstest/log.test.mjs`) | written here | |
1414 | `static/js/files.js` | the Files view: a tree whose directories open in place and walk by keyboard (`window.ermiteTree`, tested in Node by `jstest/tree.test.mjs`), the fuzzy file finder and the palette's file hits (`window.ermiteFinder`; its core is tested in Node by `jstest/files.test.mjs`) | written here | |
@@ -19,7 +19,7 @@ content-hashed names. Nothing is fetched at runtime.
1919 | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | |
2020 | `static/fonts/atkinson-next.woff2`, `atkinson-next-italic.woff2` | Atkinson Hyperlegible Next, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-next.txt`) | Google Fonts | `18b2a1a39a2fa298b0ba5390aca68462669826c90925656f1c1f6796e0e1bbaf`, `4a5037bfaf6680f40147407407ec09fa42925774bde809a579283d27f9f08106` |
2121 | `static/fonts/atkinson-mono.woff2` | Atkinson Hyperlegible Mono, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-mono.txt`) | Google Fonts | `2706b1ee4f452e744ea91f7e4908cbde9c5d35521bf5ffffc71a382a2de89613` |
22-| `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is. Only the icons in use are copied, one file each: `branch`, `search`, `tag`, `file`, `chevron`, `user`, `key`, `log-in`, `log-out`, `cards`, `split`, `compact` are Lucide 1.8 (`git-branch`, `search`, `tag`, `file-text`, `chevron-down`, `user-round`, `key-round`, `log-in`, `log-out`, `rows-2`, `columns-2`, `list`; ISC, https://lucide.dev/license) | Lucide; `brand.svg` written here | |
22+| `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is. Only the icons in use are copied, one file each: `branch`, `search`, `tag`, `file`, `chevron`, `user`, `key`, `log-in`, `log-out`, `cards`, `split`, `compact`, `sun-moon`, `sun`, `moon` are Lucide 1.8 (`git-branch`, `search`, `tag`, `file-text`, `chevron-down`, `user-round`, `key-round`, `log-in`, `log-out`, `rows-2`, `columns-2`, `list`, `sun-moon`, `sun`, `moon`; ISC, https://lucide.dev/license) | Lucide; `brand.svg` written here | |
2323 | `static/app/*.png` | icons of the installed web app (manifest, `apple-touch-icon`), rendered from `brand.svg` with resvg; the maskable and Apple icons have the picture in the safe zone on `#181818` | generated here | |
2424 | `static/sw.js` | the service worker of the installed app: a network-only passthrough (no cache), served at `/-/sw.js` with `Service-Worker-Allowed: /` and revalidated on each check (not a hashed asset) | written here | |
2525 | `static/rules.json` | speculation rules: prefetch the light same-origin pages on hover (`Speculation-Rules` header); the heavy ones (tree, blob, commit, commits, compare, refs, raw, MR "since") are excluded, each costs several Git reads on the server | written here | |
@@ -8,7 +8,7 @@ content-hashed names. Nothing is fetched at runtime.
8 | `static/js/htmx.min.js` | htmx 4.0.0 (BSD Zero Clause, `static/js/htmx-LICENSE`) | `mockups/assets/htmx/htmx.min.js` of the design | `e484d9171a9db30a39c8f16e3d709d4137f3211c659f8e6125816635033d593f` |8 | `static/js/htmx.min.js` | htmx 4.0.0 (BSD Zero Clause, `static/js/htmx-LICENSE`) | `mockups/assets/htmx/htmx.min.js` of the design | `e484d9171a9db30a39c8f16e3d709d4137f3211c659f8e6125816635033d593f` |
9 | `static/js/hx-live.min.js` | htmx 4 `hx-live` extension | `mockups/assets/htmx/ext/hx-live.min.js` | `adf9b90949d57baf20dc2eaf1d1fd46c81347ac82c090a5e2912ce6b37726734` |9 | `static/js/hx-live.min.js` | htmx 4 `hx-live` extension | `mockups/assets/htmx/ext/hx-live.min.js` | `adf9b90949d57baf20dc2eaf1d1fd46c81347ac82c090a5e2912ce6b37726734` |
10 | `static/js/hx-sse.min.js` | htmx 4 `hx-sse` extension | `mockups/assets/htmx/ext/hx-sse.min.js` | `8a834680c4000a9034d79228872372a92e140c810a075cb6d4a76690dfc13085` |10 | `static/js/hx-sse.min.js` | htmx 4 `hx-sse` extension | `mockups/assets/htmx/ext/hx-sse.min.js` | `8a834680c4000a9034d79228872372a92e140c810a075cb6d4a76690dfc13085` |
11-| `static/js/store.js` | the localStorage client of the scripts: preferences such as the home layout and the single-key shortcuts (`window.ermiteStore`, tested in Node by `jstest/store.test.mjs`) | written here | |11+| `static/js/store.js` | the localStorage client of the scripts: preferences such as the home layout and the single-key shortcuts (`window.ermiteStore`, tested in Node by `jstest/store.test.mjs`); applies the wide mode and the theme (`window.ermiteTheme`: auto, light or dark, key `theme`; tested by `jstest/theme.test.mjs`) before the first paint | written here | |
12 | `static/js/ermite.js` | key sequences to `k-*` events, the functions the keys and inputs call (`window.ermite`), the live fragments (`ermiteLive`), the diff table of contents observer | `mockups/d/js/ermite.js`, extended here | |12 | `static/js/ermite.js` | key sequences to `k-*` events, the functions the keys and inputs call (`window.ermite`), the live fragments (`ermiteLive`), the diff table of contents observer | `mockups/d/js/ermite.js`, extended here | |
13 | `static/js/log.js` | the live log of a running job (`window.ermiteLog`, tested in Node by `jstest/log.test.mjs`) | written here | |13 | `static/js/log.js` | the live log of a running job (`window.ermiteLog`, tested in Node by `jstest/log.test.mjs`) | written here | |
14 | `static/js/files.js` | the Files view: a tree whose directories open in place and walk by keyboard (`window.ermiteTree`, tested in Node by `jstest/tree.test.mjs`), the fuzzy file finder and the palette's file hits (`window.ermiteFinder`; its core is tested in Node by `jstest/files.test.mjs`) | written here | |14 | `static/js/files.js` | the Files view: a tree whose directories open in place and walk by keyboard (`window.ermiteTree`, tested in Node by `jstest/tree.test.mjs`), the fuzzy file finder and the palette's file hits (`window.ermiteFinder`; its core is tested in Node by `jstest/files.test.mjs`) | written here | |
@@ -19,7 +19,7 @@ content-hashed names. Nothing is fetched at runtime.
19 | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | |19 | `static/css/*.css` | native CSS, served as one stylesheet in name order; relative `url(fonts/…)` and `url(icons/…)` become hashed URLs | written here | |
20 | `static/fonts/atkinson-next.woff2`, `atkinson-next-italic.woff2` | Atkinson Hyperlegible Next, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-next.txt`) | Google Fonts | `18b2a1a39a2fa298b0ba5390aca68462669826c90925656f1c1f6796e0e1bbaf`, `4a5037bfaf6680f40147407407ec09fa42925774bde809a579283d27f9f08106` |20 | `static/fonts/atkinson-next.woff2`, `atkinson-next-italic.woff2` | Atkinson Hyperlegible Next, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-next.txt`) | Google Fonts | `18b2a1a39a2fa298b0ba5390aca68462669826c90925656f1c1f6796e0e1bbaf`, `4a5037bfaf6680f40147407407ec09fa42925774bde809a579283d27f9f08106` |
21 | `static/fonts/atkinson-mono.woff2` | Atkinson Hyperlegible Mono, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-mono.txt`) | Google Fonts | `2706b1ee4f452e744ea91f7e4908cbde9c5d35521bf5ffffc71a382a2de89613` |21 | `static/fonts/atkinson-mono.woff2` | Atkinson Hyperlegible Mono, variable weight, latin subset (SIL OFL 1.1, `static/fonts/OFL-mono.txt`) | Google Fonts | `2706b1ee4f452e744ea91f7e4908cbde9c5d35521bf5ffffc71a382a2de89613` |
22-| `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is. Only the icons in use are copied, one file each: `branch`, `search`, `tag`, `file`, `chevron`, `user`, `key`, `log-in`, `log-out`, `cards`, `split`, `compact` are Lucide 1.8 (`git-branch`, `search`, `tag`, `file-text`, `chevron-down`, `user-round`, `key-round`, `log-in`, `log-out`, `rows-2`, `columns-2`, `list`; ISC, https://lucide.dev/license) | Lucide; `brand.svg` written here | |22+| `static/icons/*.svg` | interface icons, used as CSS masks; `brand.svg` is a grey picture shown as is. Only the icons in use are copied, one file each: `branch`, `search`, `tag`, `file`, `chevron`, `user`, `key`, `log-in`, `log-out`, `cards`, `split`, `compact`, `sun-moon`, `sun`, `moon` are Lucide 1.8 (`git-branch`, `search`, `tag`, `file-text`, `chevron-down`, `user-round`, `key-round`, `log-in`, `log-out`, `rows-2`, `columns-2`, `list`, `sun-moon`, `sun`, `moon`; ISC, https://lucide.dev/license) | Lucide; `brand.svg` written here | |
23 | `static/app/*.png` | icons of the installed web app (manifest, `apple-touch-icon`), rendered from `brand.svg` with resvg; the maskable and Apple icons have the picture in the safe zone on `#181818` | generated here | |23 | `static/app/*.png` | icons of the installed web app (manifest, `apple-touch-icon`), rendered from `brand.svg` with resvg; the maskable and Apple icons have the picture in the safe zone on `#181818` | generated here | |
24 | `static/sw.js` | the service worker of the installed app: a network-only passthrough (no cache), served at `/-/sw.js` with `Service-Worker-Allowed: /` and revalidated on each check (not a hashed asset) | written here | |24 | `static/sw.js` | the service worker of the installed app: a network-only passthrough (no cache), served at `/-/sw.js` with `Service-Worker-Allowed: /` and revalidated on each check (not a hashed asset) | written here | |
25 | `static/rules.json` | speculation rules: prefetch the light same-origin pages on hover (`Speculation-Rules` header); the heavy ones (tree, blob, commit, commits, compare, refs, raw, MR "since") are excluded, each costs several Git reads on the server | written here | |25 | `static/rules.json` | speculation rules: prefetch the light same-origin pages on hover (`Speculation-Rules` header); the heavy ones (tree, blob, commit, commits, compare, refs, raw, MR "since") are excluded, each costs several Git reads on the server | written here | |
M internal/adapters/web/jstest/a11y.test.mjs +1 -1
@@ -44,7 +44,7 @@ El.prototype.focus = function (opts) {
4444 const doc = installDom({ parseHTML: (text) => parsed[text] });
4545 const parsed = {};
4646 Object.defineProperty(doc, 'activeElement', { get: () => active ?? dom.body });
47-doc.documentElement = { classList: { add() {} } };
47+doc.documentElement = { classList: { add() {} }, setAttribute() {}, removeAttribute() {} };
4848 const query = { all: doc.querySelectorAll, one: doc.querySelector };
4949 doc.querySelectorAll = () => []; // the page-wide queries of the top of ermite.js
5050 doc.querySelector = () => null;
@@ -44,7 +44,7 @@ El.prototype.focus = function (opts) {
44 const doc = installDom({ parseHTML: (text) => parsed[text] });44 const doc = installDom({ parseHTML: (text) => parsed[text] });
45 const parsed = {};45 const parsed = {};
46 Object.defineProperty(doc, 'activeElement', { get: () => active ?? dom.body });46 Object.defineProperty(doc, 'activeElement', { get: () => active ?? dom.body });
47-doc.documentElement = { classList: { add() {} } };47+doc.documentElement = { classList: { add() {} }, setAttribute() {}, removeAttribute() {} };
48 const query = { all: doc.querySelectorAll, one: doc.querySelector };48 const query = { all: doc.querySelectorAll, one: doc.querySelector };
49 doc.querySelectorAll = () => []; // the page-wide queries of the top of ermite.js49 doc.querySelectorAll = () => []; // the page-wide queries of the top of ermite.js
50 doc.querySelector = () => null;50 doc.querySelector = () => null;
A internal/adapters/web/jstest/theme.test.mjs +151 -0
@@ -0,0 +1,151 @@
1+// Tests of the theme: window.ermiteTheme of static/js/store.js (applied before
2+// the first paint) and the footer switch of static/js/ermite.js
3+// (window.ermiteThemeSwitch), on the small DOM of dom.mjs. Only the
4+// ermiteThemeSwitch block of ermite.js is evaluated: the rest needs a page.
5+// Run with
6+// node --test 'internal/adapters/web/jstest/*.test.mjs'
7+import assert from 'node:assert/strict';
8+import { readFileSync } from 'node:fs';
9+import { beforeEach, test } from 'node:test';
10+import { el, installDom } from './dom.mjs';
11+
12+const read = (f) => readFileSync(new URL('../static/js/' + f, import.meta.url), 'utf8');
13+const ermite = read('ermite.js');
14+const start = ermite.indexOf(' window.ermiteThemeSwitch = ');
15+const block = ermite.slice(start, ermite.indexOf(' })();', start) + ' })();'.length);
16+const storeSrc = read('store.js');
17+
18+const doc = installDom();
19+let listeners;
20+let storage;
21+let root;
22+let scheme;
23+let colors;
24+let button;
25+let icon;
26+
27+// A page as shell.templ renders it: the metas and the footer button in auto.
28+// Then store.js runs (in <head>) and, unless withSwitch is false, the block of
29+// ermite.js (deferred).
30+function load({ stored, withSwitch = true } = {}) {
31+ listeners = {};
32+ storage = new Map(stored ? Object.entries(stored) : []);
33+ globalThis.localStorage = {
34+ getItem: (k) => (storage.has(k) ? storage.get(k) : null),
35+ setItem: (k, v) => { storage.set(k, String(v)); },
36+ removeItem: (k) => { storage.delete(k); },
37+ };
38+ globalThis.addEventListener = (type, f) => (listeners[type] ||= []).push(f);
39+ globalThis.removeEventListener = (type, f) => { listeners[type] = (listeners[type] || []).filter((g) => g !== f); };
40+ root = el('html');
41+ doc.documentElement = root;
42+ scheme = el('meta', { attrs: { name: 'color-scheme', content: 'light dark' } });
43+ colors = [
44+ el('meta', { attrs: { name: 'theme-color', 'data-scheme': 'light', media: '(prefers-color-scheme: light)', content: '#ffffff' } }),
45+ el('meta', { attrs: { name: 'theme-color', 'data-scheme': 'dark', media: '(prefers-color-scheme: dark)', content: '#0c0c0c' } }),
46+ ];
47+ icon = el('span', { cls: 'ico i-sun-moon', attrs: { 'aria-hidden': 'true' } });
48+ button = el('button', { cls: 'foot-theme', attrs: { type: 'button', 'data-js': '', 'data-theme-switch': '' } }, icon);
49+ doc.body.replaceChildren(scheme, ...colors, el('footer', { cls: 'foot' }, button));
50+ new Function(storeSrc)();
51+ if (withSwitch) new Function(block)();
52+ return window.ermiteTheme;
53+}
54+
55+const state = () => ({
56+ attr: root.getAttribute('data-theme'),
57+ scheme: scheme.getAttribute('content'),
58+ colors: colors.map((m) => m.getAttribute('content')),
59+});
60+const click = (target) => listeners.click.forEach((f) => f({ target }));
61+const pageshow = (persisted) => listeners.pageshow.forEach((f) => f({ persisted }));
62+const storageEvent = (newValue) => listeners.storage.forEach((f) => f({ key: 'theme', newValue, storageArea: localStorage }));
63+
64+beforeEach(() => { delete globalThis.localStorage; });
65+
66+test('nothing stored: auto, the page is left to the system', () => {
67+ const T = load({ withSwitch: false });
68+ assert.equal(T.get(), 'auto');
69+ assert.deepEqual(state(), { attr: null, scheme: 'light dark', colors: ['#ffffff', '#0c0c0c'] });
70+});
71+
72+test('store.js applies a stored theme before ermite.js runs', () => {
73+ load({ stored: { theme: 'dark' }, withSwitch: false });
74+ assert.deepEqual(state(), { attr: 'dark', scheme: 'dark', colors: ['#0c0c0c', '#0c0c0c'] });
75+ load({ stored: { theme: 'light' }, withSwitch: false });
76+ assert.deepEqual(state(), { attr: 'light', scheme: 'light', colors: ['#ffffff', '#ffffff'] });
77+});
78+
79+test('a stored value that is not a theme falls back to auto', () => {
80+ const T = load({ stored: { theme: 'sepia' } });
81+ assert.equal(T.get(), 'auto');
82+ assert.equal(root.getAttribute('data-theme'), null);
83+});
84+
85+test('the switch shows the stored theme on load', () => {
86+ load({ stored: { theme: 'dark' } });
87+ assert.equal(icon.className, 'ico i-moon');
88+ assert.equal(button.getAttribute('aria-label'), 'theme: dark (switch to auto)');
89+});
90+
91+test('a click on the button (or its icon) cycles, stores and shows the theme', () => {
92+ load();
93+ assert.equal(button.getAttribute('aria-label'), 'theme: auto (switch to light)');
94+ click(icon);
95+ assert.equal(storage.get('theme'), 'light');
96+ assert.deepEqual(state(), { attr: 'light', scheme: 'light', colors: ['#ffffff', '#ffffff'] });
97+ assert.equal(icon.className, 'ico i-sun');
98+ assert.equal(button.getAttribute('aria-label'), 'theme: light (switch to dark)');
99+ assert.equal(button.getAttribute('title'), 'theme: light (switch to dark)');
100+ click(button);
101+ assert.equal(storage.get('theme'), 'dark');
102+ assert.deepEqual(state(), { attr: 'dark', scheme: 'dark', colors: ['#0c0c0c', '#0c0c0c'] });
103+ assert.equal(icon.className, 'ico i-moon');
104+ click(button);
105+ assert.equal(storage.get('theme'), 'auto');
106+ assert.deepEqual(state(), { attr: null, scheme: 'light dark', colors: ['#ffffff', '#0c0c0c'] });
107+ assert.equal(icon.className, 'ico i-sun-moon');
108+ assert.equal(button.getAttribute('aria-label'), 'theme: auto (switch to light)');
109+});
110+
111+test('a page back from the back/forward cache shows the stored theme', () => {
112+ load();
113+ storage.set('theme', 'light');
114+ pageshow(false);
115+ assert.equal(root.getAttribute('data-theme'), null, 'a fresh load: store.js already applied it');
116+ pageshow(true);
117+ assert.deepEqual(state(), { attr: 'light', scheme: 'light', colors: ['#ffffff', '#ffffff'] });
118+ assert.equal(icon.className, 'ico i-sun');
119+});
120+
121+test('other clicks do nothing', () => {
122+ const T = load();
123+ click(doc.body);
124+ click({ closest: undefined });
125+ assert.equal(T.get(), 'auto');
126+});
127+
128+test("another tab's change applies live", () => {
129+ const T = load();
130+ storage.set('theme', 'dark');
131+ storageEvent('dark');
132+ assert.equal(T.get(), 'dark');
133+ assert.equal(root.getAttribute('data-theme'), 'dark');
134+ assert.equal(icon.className, 'ico i-moon');
135+ storage.delete('theme');
136+ storageEvent(null);
137+ assert.equal(root.getAttribute('data-theme'), null, 'removed: back to auto');
138+ assert.equal(button.getAttribute('aria-label'), 'theme: auto (switch to light)');
139+});
140+
141+test('without usable storage the choice holds for the page', () => {
142+ load();
143+ globalThis.localStorage = {
144+ getItem: () => { throw new Error('denied'); },
145+ setItem: () => { throw new Error('quota'); },
146+ removeItem: () => { throw new Error('denied'); },
147+ };
148+ click(button);
149+ assert.equal(window.ermiteTheme.get(), 'light');
150+ assert.equal(root.getAttribute('data-theme'), 'light');
151+});
@@ -0,0 +1,151 @@
1+// Tests of the theme: window.ermiteTheme of static/js/store.js (applied before
2+// the first paint) and the footer switch of static/js/ermite.js
3+// (window.ermiteThemeSwitch), on the small DOM of dom.mjs. Only the
4+// ermiteThemeSwitch block of ermite.js is evaluated: the rest needs a page.
5+// Run with
6+// node --test 'internal/adapters/web/jstest/*.test.mjs'
7+import assert from 'node:assert/strict';
8+import { readFileSync } from 'node:fs';
9+import { beforeEach, test } from 'node:test';
10+import { el, installDom } from './dom.mjs';
11+
12+const read = (f) => readFileSync(new URL('../static/js/' + f, import.meta.url), 'utf8');
13+const ermite = read('ermite.js');
14+const start = ermite.indexOf(' window.ermiteThemeSwitch = ');
15+const block = ermite.slice(start, ermite.indexOf(' })();', start) + ' })();'.length);
16+const storeSrc = read('store.js');
17+
18+const doc = installDom();
19+let listeners;
20+let storage;
21+let root;
22+let scheme;
23+let colors;
24+let button;
25+let icon;
26+
27+// A page as shell.templ renders it: the metas and the footer button in auto.
28+// Then store.js runs (in <head>) and, unless withSwitch is false, the block of
29+// ermite.js (deferred).
30+function load({ stored, withSwitch = true } = {}) {
31+ listeners = {};
32+ storage = new Map(stored ? Object.entries(stored) : []);
33+ globalThis.localStorage = {
34+ getItem: (k) => (storage.has(k) ? storage.get(k) : null),
35+ setItem: (k, v) => { storage.set(k, String(v)); },
36+ removeItem: (k) => { storage.delete(k); },
37+ };
38+ globalThis.addEventListener = (type, f) => (listeners[type] ||= []).push(f);
39+ globalThis.removeEventListener = (type, f) => { listeners[type] = (listeners[type] || []).filter((g) => g !== f); };
40+ root = el('html');
41+ doc.documentElement = root;
42+ scheme = el('meta', { attrs: { name: 'color-scheme', content: 'light dark' } });
43+ colors = [
44+ el('meta', { attrs: { name: 'theme-color', 'data-scheme': 'light', media: '(prefers-color-scheme: light)', content: '#ffffff' } }),
45+ el('meta', { attrs: { name: 'theme-color', 'data-scheme': 'dark', media: '(prefers-color-scheme: dark)', content: '#0c0c0c' } }),
46+ ];
47+ icon = el('span', { cls: 'ico i-sun-moon', attrs: { 'aria-hidden': 'true' } });
48+ button = el('button', { cls: 'foot-theme', attrs: { type: 'button', 'data-js': '', 'data-theme-switch': '' } }, icon);
49+ doc.body.replaceChildren(scheme, ...colors, el('footer', { cls: 'foot' }, button));
50+ new Function(storeSrc)();
51+ if (withSwitch) new Function(block)();
52+ return window.ermiteTheme;
53+}
54+
55+const state = () => ({
56+ attr: root.getAttribute('data-theme'),
57+ scheme: scheme.getAttribute('content'),
58+ colors: colors.map((m) => m.getAttribute('content')),
59+});
60+const click = (target) => listeners.click.forEach((f) => f({ target }));
61+const pageshow = (persisted) => listeners.pageshow.forEach((f) => f({ persisted }));
62+const storageEvent = (newValue) => listeners.storage.forEach((f) => f({ key: 'theme', newValue, storageArea: localStorage }));
63+
64+beforeEach(() => { delete globalThis.localStorage; });
65+
66+test('nothing stored: auto, the page is left to the system', () => {
67+ const T = load({ withSwitch: false });
68+ assert.equal(T.get(), 'auto');
69+ assert.deepEqual(state(), { attr: null, scheme: 'light dark', colors: ['#ffffff', '#0c0c0c'] });
70+});
71+
72+test('store.js applies a stored theme before ermite.js runs', () => {
73+ load({ stored: { theme: 'dark' }, withSwitch: false });
74+ assert.deepEqual(state(), { attr: 'dark', scheme: 'dark', colors: ['#0c0c0c', '#0c0c0c'] });
75+ load({ stored: { theme: 'light' }, withSwitch: false });
76+ assert.deepEqual(state(), { attr: 'light', scheme: 'light', colors: ['#ffffff', '#ffffff'] });
77+});
78+
79+test('a stored value that is not a theme falls back to auto', () => {
80+ const T = load({ stored: { theme: 'sepia' } });
81+ assert.equal(T.get(), 'auto');
82+ assert.equal(root.getAttribute('data-theme'), null);
83+});
84+
85+test('the switch shows the stored theme on load', () => {
86+ load({ stored: { theme: 'dark' } });
87+ assert.equal(icon.className, 'ico i-moon');
88+ assert.equal(button.getAttribute('aria-label'), 'theme: dark (switch to auto)');
89+});
90+
91+test('a click on the button (or its icon) cycles, stores and shows the theme', () => {
92+ load();
93+ assert.equal(button.getAttribute('aria-label'), 'theme: auto (switch to light)');
94+ click(icon);
95+ assert.equal(storage.get('theme'), 'light');
96+ assert.deepEqual(state(), { attr: 'light', scheme: 'light', colors: ['#ffffff', '#ffffff'] });
97+ assert.equal(icon.className, 'ico i-sun');
98+ assert.equal(button.getAttribute('aria-label'), 'theme: light (switch to dark)');
99+ assert.equal(button.getAttribute('title'), 'theme: light (switch to dark)');
100+ click(button);
101+ assert.equal(storage.get('theme'), 'dark');
102+ assert.deepEqual(state(), { attr: 'dark', scheme: 'dark', colors: ['#0c0c0c', '#0c0c0c'] });
103+ assert.equal(icon.className, 'ico i-moon');
104+ click(button);
105+ assert.equal(storage.get('theme'), 'auto');
106+ assert.deepEqual(state(), { attr: null, scheme: 'light dark', colors: ['#ffffff', '#0c0c0c'] });
107+ assert.equal(icon.className, 'ico i-sun-moon');
108+ assert.equal(button.getAttribute('aria-label'), 'theme: auto (switch to light)');
109+});
110+
111+test('a page back from the back/forward cache shows the stored theme', () => {
112+ load();
113+ storage.set('theme', 'light');
114+ pageshow(false);
115+ assert.equal(root.getAttribute('data-theme'), null, 'a fresh load: store.js already applied it');
116+ pageshow(true);
117+ assert.deepEqual(state(), { attr: 'light', scheme: 'light', colors: ['#ffffff', '#ffffff'] });
118+ assert.equal(icon.className, 'ico i-sun');
119+});
120+
121+test('other clicks do nothing', () => {
122+ const T = load();
123+ click(doc.body);
124+ click({ closest: undefined });
125+ assert.equal(T.get(), 'auto');
126+});
127+
128+test("another tab's change applies live", () => {
129+ const T = load();
130+ storage.set('theme', 'dark');
131+ storageEvent('dark');
132+ assert.equal(T.get(), 'dark');
133+ assert.equal(root.getAttribute('data-theme'), 'dark');
134+ assert.equal(icon.className, 'ico i-moon');
135+ storage.delete('theme');
136+ storageEvent(null);
137+ assert.equal(root.getAttribute('data-theme'), null, 'removed: back to auto');
138+ assert.equal(button.getAttribute('aria-label'), 'theme: auto (switch to light)');
139+});
140+
141+test('without usable storage the choice holds for the page', () => {
142+ load();
143+ globalThis.localStorage = {
144+ getItem: () => { throw new Error('denied'); },
145+ setItem: () => { throw new Error('quota'); },
146+ removeItem: () => { throw new Error('denied'); },
147+ };
148+ click(button);
149+ assert.equal(window.ermiteTheme.get(), 'light');
150+ assert.equal(root.getAttribute('data-theme'), 'light');
151+});
M internal/adapters/web/shell.templ +6 -3
@@ -13,8 +13,8 @@ templ Shell(l Layout, rail templ.Component) {
1313 <meta name="viewport" content="width=device-width, initial-scale=1"/>
1414 <meta name="color-scheme" content="light dark"/>
1515 <title>{ l.Title }</title>
16- <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"/>
17- <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"/>
16+ <meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff"/>
17+ <meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c"/>
1818 <meta name="apple-mobile-web-app-title" content="ermite"/>
1919 <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/>
2020 <link rel="apple-touch-icon" href={ templ.SafeURL(l.Assets.AppleIcon) }/>
@@ -192,12 +192,15 @@ templ Header(l Layout) {
192192 }
193193
194194 // Footer is the version and the link to the source of the running version,
195-// which the AGPL §13 asks every user of the service to be offered.
195+// which the AGPL §13 asks every user of the service to be offered, then the
196+// keyboard shortcuts and the theme switch (auto, light, dark; ermite.js shows
197+// the stored mode).
196198 templ Footer(l Layout) {
197199 <footer class="foot">
198200 <span>ermite { l.Version }</span>
199201 <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
200202 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"><kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
203+ <button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)"><span class="ico i-sun-moon" aria-hidden="true"></span></button>
201204 </footer>
202205 }
203206
@@ -13,8 +13,8 @@ templ Shell(l Layout, rail templ.Component) {
13 <meta name="viewport" content="width=device-width, initial-scale=1"/>13 <meta name="viewport" content="width=device-width, initial-scale=1"/>
14 <meta name="color-scheme" content="light dark"/>14 <meta name="color-scheme" content="light dark"/>
15 <title>{ l.Title }</title>15 <title>{ l.Title }</title>
16- <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff"/>16+ <meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff"/>
17- <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c"/>17+ <meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c"/>
18 <meta name="apple-mobile-web-app-title" content="ermite"/>18 <meta name="apple-mobile-web-app-title" content="ermite"/>
19 <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/>19 <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/>
20 <link rel="apple-touch-icon" href={ templ.SafeURL(l.Assets.AppleIcon) }/>20 <link rel="apple-touch-icon" href={ templ.SafeURL(l.Assets.AppleIcon) }/>
@@ -192,12 +192,15 @@ templ Header(l Layout) {
192 }192 }
193 193
194 // Footer is the version and the link to the source of the running version,194 // Footer is the version and the link to the source of the running version,
195-// which the AGPL §13 asks every user of the service to be offered.195+// which the AGPL §13 asks every user of the service to be offered, then the
196+// keyboard shortcuts and the theme switch (auto, light, dark; ermite.js shows
197+// the stored mode).
196 templ Footer(l Layout) {198 templ Footer(l Layout) {
197 <footer class="foot">199 <footer class="foot">
198 <span>ermite { l.Version }</span>200 <span>ermite { l.Version }</span>
199 <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>201 <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
200 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"><kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>202 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?"><kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
203+ <button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)"><span class="ico i-sun-moon" aria-hidden="true"></span></button>
201 </footer>204 </footer>
202 }205 }
203 206
M internal/adapters/web/shell_templ.go +7 -5
@@ -47,7 +47,7 @@ func Shell(l Layout, rail templ.Component) templ.Component {
4747 if templ_7745c5c3_Err != nil {
4848 return templ_7745c5c3_Err
4949 }
50- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#ffffff\"><meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#0c0c0c\"><meta name=\"apple-mobile-web-app-title\" content=\"ermite\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"")
50+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><meta name=\"theme-color\" data-scheme=\"light\" media=\"(prefers-color-scheme: light)\" content=\"#ffffff\"><meta name=\"theme-color\" data-scheme=\"dark\" media=\"(prefers-color-scheme: dark)\" content=\"#0c0c0c\"><meta name=\"apple-mobile-web-app-title\" content=\"ermite\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"")
5151 if templ_7745c5c3_Err != nil {
5252 return templ_7745c5c3_Err
5353 }
@@ -842,7 +842,9 @@ func Header(l Layout) templ.Component {
842842 }
843843
844844 // Footer is the version and the link to the source of the running version,
845-// which the AGPL §13 asks every user of the service to be offered.
845+// which the AGPL §13 asks every user of the service to be offered, then the
846+// keyboard shortcuts and the theme switch (auto, light, dark; ermite.js shows
847+// the stored mode).
846848 func Footer(l Layout) templ.Component {
847849 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
848850 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -871,7 +873,7 @@ func Footer(l Layout) templ.Component {
871873 var templ_7745c5c3_Var44 string
872874 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version)
873875 if templ_7745c5c3_Err != nil {
874- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 198, Col: 26}
876+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 200, Col: 26}
875877 }
876878 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
877879 if templ_7745c5c3_Err != nil {
@@ -884,13 +886,13 @@ func Footer(l Layout) templ.Component {
884886 var templ_7745c5c3_Var45 templ.SafeURL
885887 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL))
886888 if templ_7745c5c3_Err != nil {
887- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 199, Col: 38}
889+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 201, Col: 38}
888890 }
889891 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
890892 if templ_7745c5c3_Err != nil {
891893 return templ_7745c5c3_Err
892894 }
893- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <button type=\"button\" class=\"foot-keys\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\"><kbd aria-hidden=\"true\">?</kbd> keyboard shortcuts</button></footer>")
895+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <button type=\"button\" class=\"foot-keys\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\"><kbd aria-hidden=\"true\">?</kbd> keyboard shortcuts</button> <button type=\"button\" class=\"foot-theme\" data-js data-theme-switch aria-label=\"theme: auto (switch to light)\" title=\"theme: auto (switch to light)\"><span class=\"ico i-sun-moon\" aria-hidden=\"true\"></span></button></footer>")
894896 if templ_7745c5c3_Err != nil {
895897 return templ_7745c5c3_Err
896898 }
@@ -47,7 +47,7 @@ func Shell(l Layout, rail templ.Component) templ.Component {
47 if templ_7745c5c3_Err != nil {47 if templ_7745c5c3_Err != nil {
48 return templ_7745c5c3_Err48 return templ_7745c5c3_Err
49 }49 }
50- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#ffffff\"><meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#0c0c0c\"><meta name=\"apple-mobile-web-app-title\" content=\"ermite\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"")50+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><meta name=\"theme-color\" data-scheme=\"light\" media=\"(prefers-color-scheme: light)\" content=\"#ffffff\"><meta name=\"theme-color\" data-scheme=\"dark\" media=\"(prefers-color-scheme: dark)\" content=\"#0c0c0c\"><meta name=\"apple-mobile-web-app-title\" content=\"ermite\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"")
51 if templ_7745c5c3_Err != nil {51 if templ_7745c5c3_Err != nil {
52 return templ_7745c5c3_Err52 return templ_7745c5c3_Err
53 }53 }
@@ -842,7 +842,9 @@ func Header(l Layout) templ.Component {
842 }842 }
843 843
844 // Footer is the version and the link to the source of the running version,844 // Footer is the version and the link to the source of the running version,
845-// which the AGPL §13 asks every user of the service to be offered.845+// which the AGPL §13 asks every user of the service to be offered, then the
846+// keyboard shortcuts and the theme switch (auto, light, dark; ermite.js shows
847+// the stored mode).
846 func Footer(l Layout) templ.Component {848 func Footer(l Layout) templ.Component {
847 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {849 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
848 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context850 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -871,7 +873,7 @@ func Footer(l Layout) templ.Component {
871 var templ_7745c5c3_Var44 string873 var templ_7745c5c3_Var44 string
872 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version)874 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(l.Version)
873 if templ_7745c5c3_Err != nil {875 if templ_7745c5c3_Err != nil {
874- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 198, Col: 26}876+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 200, Col: 26}
875 }877 }
876 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))878 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
877 if templ_7745c5c3_Err != nil {879 if templ_7745c5c3_Err != nil {
@@ -884,13 +886,13 @@ func Footer(l Layout) templ.Component {
884 var templ_7745c5c3_Var45 templ.SafeURL886 var templ_7745c5c3_Var45 templ.SafeURL
885 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL))887 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(l.SourceURL))
886 if templ_7745c5c3_Err != nil {888 if templ_7745c5c3_Err != nil {
887- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 199, Col: 38}889+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/shell.templ`, Line: 201, Col: 38}
888 }890 }
889 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))891 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
890 if templ_7745c5c3_Err != nil {892 if templ_7745c5c3_Err != nil {
891 return templ_7745c5c3_Err893 return templ_7745c5c3_Err
892 }894 }
893- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <button type=\"button\" class=\"foot-keys\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\"><kbd aria-hidden=\"true\">?</kbd> keyboard shortcuts</button></footer>")895+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" rel=\"noopener\" title=\"source of this version (AGPL-3.0-or-later)\">source</a> <button type=\"button\" class=\"foot-keys\" data-js data-fire=\"help\" aria-haspopup=\"dialog\" aria-controls=\"panel-help\" aria-keyshortcuts=\"?\"><kbd aria-hidden=\"true\">?</kbd> keyboard shortcuts</button> <button type=\"button\" class=\"foot-theme\" data-js data-theme-switch aria-label=\"theme: auto (switch to light)\" title=\"theme: auto (switch to light)\"><span class=\"ico i-sun-moon\" aria-hidden=\"true\"></span></button></footer>")
894 if templ_7745c5c3_Err != nil {896 if templ_7745c5c3_Err != nil {
895 return templ_7745c5c3_Err897 return templ_7745c5c3_Err
896 }898 }
M internal/adapters/web/static/css/00-tokens.css +4 -0
@@ -27,7 +27,11 @@
2727 cursor, links in prose, open folders. */
2828 @layer tokens {
2929 :root {
30+ /* the system's scheme, unless the theme switch forces one
31+ (html[data-theme], set by store.js) */
3032 color-scheme: light dark;
33+ &[data-theme="light"] { color-scheme: light; }
34+ &[data-theme="dark"] { color-scheme: dark; }
3135
3236 /* surfaces, bottom to top */
3337 --bg-canvas: light-dark(#f3f3f3, #050505); /* the page */
@@ -27,7 +27,11 @@
27 cursor, links in prose, open folders. */27 cursor, links in prose, open folders. */
28 @layer tokens {28 @layer tokens {
29 :root {29 :root {
30+ /* the system's scheme, unless the theme switch forces one
31+ (html[data-theme], set by store.js) */
30 color-scheme: light dark;32 color-scheme: light dark;
33+ &[data-theme="light"] { color-scheme: light; }
34+ &[data-theme="dark"] { color-scheme: dark; }
31 35
32 /* surfaces, bottom to top */36 /* surfaces, bottom to top */
33 --bg-canvas: light-dark(#f3f3f3, #050505); /* the page */37 --bg-canvas: light-dark(#f3f3f3, #050505); /* the page */
M internal/adapters/web/static/css/20-shell.css +9 -1
@@ -30,6 +30,9 @@
3030 .i-split { --i: url(icons/split.svg); }
3131 .i-compact { --i: url(icons/compact.svg); }
3232 .i-mailbox { --i: url(icons/mailbox.svg); }
33+ .i-sun-moon { --i: url(icons/sun-moon.svg); }
34+ .i-sun { --i: url(icons/sun.svg); }
35+ .i-moon { --i: url(icons/moon.svg); }
3336
3437 .pill {
3538 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -185,7 +188,7 @@
185188 & .l4, & .l5, & .l6 { padding-left: var(--s6); }
186189 }
187190
188- /* footer: version and source (AGPL §13) */
191+ /* footer: version and source (AGPL §13), then shortcuts and theme */
189192 .foot {
190193 display: flex; flex-wrap: wrap; gap: var(--s4); align-items: center;
191194 width: 100%; max-width: var(--page); margin: 0 auto; padding: var(--s4) var(--pad) var(--s5);
@@ -194,6 +197,11 @@
194197 & a { color: var(--ink-dim); }
195198 & .foot-keys { margin-left: auto; color: var(--ink-dim); }
196199 & .foot-keys:hover { color: var(--ink-hi); }
200+ & .foot-theme {
201+ display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
202+ margin: -6px 0; color: var(--ink-dim); border-radius: var(--r-s);
203+ }
204+ & .foot-theme:hover { color: var(--ink-hi); background: var(--bg-raised); }
197205 }
198206
199207 /* hint: bottom right, only while it has something to say */
@@ -30,6 +30,9 @@
30 .i-split { --i: url(icons/split.svg); }30 .i-split { --i: url(icons/split.svg); }
31 .i-compact { --i: url(icons/compact.svg); }31 .i-compact { --i: url(icons/compact.svg); }
32 .i-mailbox { --i: url(icons/mailbox.svg); }32 .i-mailbox { --i: url(icons/mailbox.svg); }
33+ .i-sun-moon { --i: url(icons/sun-moon.svg); }
34+ .i-sun { --i: url(icons/sun.svg); }
35+ .i-moon { --i: url(icons/moon.svg); }
33 36
34 .pill {37 .pill {
35 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);38 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -185,7 +188,7 @@
185 & .l4, & .l5, & .l6 { padding-left: var(--s6); }188 & .l4, & .l5, & .l6 { padding-left: var(--s6); }
186 }189 }
187 190
188- /* footer: version and source (AGPL §13) */191+ /* footer: version and source (AGPL §13), then shortcuts and theme */
189 .foot {192 .foot {
190 display: flex; flex-wrap: wrap; gap: var(--s4); align-items: center;193 display: flex; flex-wrap: wrap; gap: var(--s4); align-items: center;
191 width: 100%; max-width: var(--page); margin: 0 auto; padding: var(--s4) var(--pad) var(--s5);194 width: 100%; max-width: var(--page); margin: 0 auto; padding: var(--s4) var(--pad) var(--s5);
@@ -194,6 +197,11 @@
194 & a { color: var(--ink-dim); }197 & a { color: var(--ink-dim); }
195 & .foot-keys { margin-left: auto; color: var(--ink-dim); }198 & .foot-keys { margin-left: auto; color: var(--ink-dim); }
196 & .foot-keys:hover { color: var(--ink-hi); }199 & .foot-keys:hover { color: var(--ink-hi); }
200+ & .foot-theme {
201+ display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
202+ margin: -6px 0; color: var(--ink-dim); border-radius: var(--r-s);
203+ }
204+ & .foot-theme:hover { color: var(--ink-hi); background: var(--bg-raised); }
197 }205 }
198 206
199 /* hint: bottom right, only while it has something to say */207 /* hint: bottom right, only while it has something to say */
A internal/adapters/web/static/icons/moon.svg +1 -0
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>
A internal/adapters/web/static/icons/sun-moon.svg +1 -0
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v2"/><path d="M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715"/><path d="M16 12a4 4 0 0 0-4-4"/><path d="m19 5-1.256 1.256"/><path d="M20 12h2"/></svg>
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v2"/><path d="M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715"/><path d="M16 12a4 4 0 0 0-4-4"/><path d="m19 5-1.256 1.256"/><path d="M20 12h2"/></svg>
A internal/adapters/web/static/icons/sun.svg +1 -0
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
M internal/adapters/web/static/js/ermite.js +36 -0
@@ -15,6 +15,7 @@
1515 // 8. Chip lists kept on one line, with a … chip (ermiteFit).
1616 // 9. The layout of the home page, remembered in the browser (ermiteLayout).
1717 // 10. The wide mode: every page takes the whole width (ermiteWide).
18+// 11. The theme switch of the footer (ermiteThemeSwitch).
1819 //
1920 // Preferences are kept by ermiteStore (store.js, loaded first).
2021 (() => {
@@ -600,6 +601,41 @@
600601 return { toggle, apply };
601602 })();
602603
604+ // The theme switch: the footer button [data-theme-switch] cycles the theme
605+ // of ermiteTheme (store.js, which applies it before the first paint):
606+ // auto → light → dark → auto. Its icon, label and title say the current
607+ // mode and the next one; a click flashes the new mode in the hint (a live
608+ // region). Another tab's change applies here at once, and on a page back
609+ // from the back/forward cache.
610+ // Self-contained with store.js: jstest/theme.test.mjs evaluates this block.
611+ window.ermiteThemeSwitch = (() => {
612+ const T = window.ermiteTheme;
613+ const icons = { auto: 'i-sun-moon', light: 'i-sun', dark: 'i-moon' };
614+ function apply() {
615+ const mode = T.apply();
616+ const label = `theme: ${mode} (switch to ${T.next(mode)})`;
617+ for (const b of document.querySelectorAll('[data-theme-switch]')) {
618+ b.setAttribute('aria-label', label);
619+ b.setAttribute('title', label);
620+ const ico = b.querySelector('.ico');
621+ if (ico) ico.className = 'ico ' + icons[mode];
622+ }
623+ }
624+ function cycle() {
625+ const mode = T.next(T.get());
626+ T.set(mode);
627+ apply();
628+ window.ermite?.flash('theme: ' + mode);
629+ }
630+ addEventListener('click', (e) => {
631+ if (e.target.closest?.('[data-theme-switch]')) cycle();
632+ });
633+ addEventListener('pageshow', (e) => { if (e.persisted) apply(); });
634+ T.watch(apply);
635+ apply();
636+ return { cycle, apply };
637+ })();
638+
603639 // The menus of the bars (<details class="menu">: the diff view, the merge
604640 // menu): closing one hands the focus back to its button when it was inside,
605641 // so that a keyboard user keeps their place.
@@ -15,6 +15,7 @@
15 // 8. Chip lists kept on one line, with a … chip (ermiteFit).15 // 8. Chip lists kept on one line, with a … chip (ermiteFit).
16 // 9. The layout of the home page, remembered in the browser (ermiteLayout).16 // 9. The layout of the home page, remembered in the browser (ermiteLayout).
17 // 10. The wide mode: every page takes the whole width (ermiteWide).17 // 10. The wide mode: every page takes the whole width (ermiteWide).
18+// 11. The theme switch of the footer (ermiteThemeSwitch).
18 //19 //
19 // Preferences are kept by ermiteStore (store.js, loaded first).20 // Preferences are kept by ermiteStore (store.js, loaded first).
20 (() => {21 (() => {
@@ -600,6 +601,41 @@
600 return { toggle, apply };601 return { toggle, apply };
601 })();602 })();
602 603
604+ // The theme switch: the footer button [data-theme-switch] cycles the theme
605+ // of ermiteTheme (store.js, which applies it before the first paint):
606+ // auto → light → dark → auto. Its icon, label and title say the current
607+ // mode and the next one; a click flashes the new mode in the hint (a live
608+ // region). Another tab's change applies here at once, and on a page back
609+ // from the back/forward cache.
610+ // Self-contained with store.js: jstest/theme.test.mjs evaluates this block.
611+ window.ermiteThemeSwitch = (() => {
612+ const T = window.ermiteTheme;
613+ const icons = { auto: 'i-sun-moon', light: 'i-sun', dark: 'i-moon' };
614+ function apply() {
615+ const mode = T.apply();
616+ const label = `theme: ${mode} (switch to ${T.next(mode)})`;
617+ for (const b of document.querySelectorAll('[data-theme-switch]')) {
618+ b.setAttribute('aria-label', label);
619+ b.setAttribute('title', label);
620+ const ico = b.querySelector('.ico');
621+ if (ico) ico.className = 'ico ' + icons[mode];
622+ }
623+ }
624+ function cycle() {
625+ const mode = T.next(T.get());
626+ T.set(mode);
627+ apply();
628+ window.ermite?.flash('theme: ' + mode);
629+ }
630+ addEventListener('click', (e) => {
631+ if (e.target.closest?.('[data-theme-switch]')) cycle();
632+ });
633+ addEventListener('pageshow', (e) => { if (e.persisted) apply(); });
634+ T.watch(apply);
635+ apply();
636+ return { cycle, apply };
637+ })();
638+
603 // The menus of the bars (<details class="menu">: the diff view, the merge639 // The menus of the bars (<details class="menu">: the diff view, the merge
604 // menu): closing one hands the focus back to its button when it was inside,640 // menu): closing one hands the focus back to its button when it was inside,
605 // so that a keyboard user keeps their place.641 // so that a keyboard user keeps their place.
M internal/adapters/web/static/js/store.js +52 -0
@@ -19,6 +19,9 @@
1919 // set ignores what is not
2020 //
2121 // Self-contained: jstest/store.test.mjs evaluates this file alone.
22+//
23+// It also sets, before the first paint, what the stored preferences change on
24+// <html>: the wide mode and the theme (window.ermiteTheme, below).
2225 window.ermiteStore = (() => {
2326 // What storage could not keep, by key. A key mapped to null is removed for
2427 // this page although storage would not say so.
@@ -79,3 +82,52 @@ window.ermiteStore = (() => {
7982 // shell.templ loads this file without defer, so a wide page never draws narrow
8083 // first.
8184 if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');
85+
86+// window.ermiteTheme: the colour theme, "auto" (the system's, the default),
87+// "light" or "dark", kept by ermiteStore ("theme"). A forced theme is
88+// html[data-theme], which sets color-scheme (00-tokens.css); the
89+// color-scheme and theme-color metas follow so the browser's own UI matches.
90+// Applied here, before the first paint: the page never draws in the wrong
91+// theme first. The footer button (ermiteThemeSwitch in ermite.js) cycles it.
92+//
93+// get() the mode
94+// next(mode) the mode after it: auto → light → dark → auto
95+// set(mode) stores and applies it
96+// apply() shows the stored mode on the page
97+// watch(fn) fn(mode) when another tab changes it
98+//
99+// Tested in Node by jstest/theme.test.mjs.
100+window.ermiteTheme = (() => {
101+ const values = ['auto', 'light', 'dark'];
102+ const pref = window.ermiteStore.pref('theme', { values, fallback: 'auto' });
103+ const next = (mode) => values[(values.indexOf(mode) + 1) % values.length];
104+
105+ function apply() {
106+ const mode = pref.get();
107+ const doc = globalThis.document;
108+ const root = doc?.documentElement;
109+ if (!root) return mode;
110+ if (mode === 'auto') root.removeAttribute('data-theme');
111+ else root.setAttribute('data-theme', mode);
112+ doc.querySelector('meta[name="color-scheme"]')
113+ ?.setAttribute('content', mode === 'auto' ? 'light dark' : mode);
114+ // Each theme-color meta keeps its rendered colour in data-auto. Forced,
115+ // both metas take the colour of the meta of that scheme (data-scheme),
116+ // whatever the system scheme their media matches.
117+ const metas = [...doc.querySelectorAll('meta[name="theme-color"]')];
118+ for (const m of metas) if (!m.hasAttribute('data-auto')) m.setAttribute('data-auto', m.getAttribute('content') ?? '');
119+ const forced = metas.find((m) => m.getAttribute('data-scheme') === mode);
120+ for (const m of metas) {
121+ m.setAttribute('content', mode === 'auto' || !forced ? m.getAttribute('data-auto') : forced.getAttribute('data-auto'));
122+ }
123+ return mode;
124+ }
125+
126+ function set(mode) {
127+ pref.set(mode);
128+ apply();
129+ }
130+
131+ apply();
132+ return { values, get: pref.get, next, set, apply, watch: pref.watch };
133+})();
@@ -19,6 +19,9 @@
19 // set ignores what is not19 // set ignores what is not
20 //20 //
21 // Self-contained: jstest/store.test.mjs evaluates this file alone.21 // Self-contained: jstest/store.test.mjs evaluates this file alone.
22+//
23+// It also sets, before the first paint, what the stored preferences change on
24+// <html>: the wide mode and the theme (window.ermiteTheme, below).
22 window.ermiteStore = (() => {25 window.ermiteStore = (() => {
23 // What storage could not keep, by key. A key mapped to null is removed for26 // What storage could not keep, by key. A key mapped to null is removed for
24 // this page although storage would not say so.27 // this page although storage would not say so.
@@ -79,3 +82,52 @@ window.ermiteStore = (() => {
79 // shell.templ loads this file without defer, so a wide page never draws narrow82 // shell.templ loads this file without defer, so a wide page never draws narrow
80 // first.83 // first.
81 if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');84 if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');
85+
86+// window.ermiteTheme: the colour theme, "auto" (the system's, the default),
87+// "light" or "dark", kept by ermiteStore ("theme"). A forced theme is
88+// html[data-theme], which sets color-scheme (00-tokens.css); the
89+// color-scheme and theme-color metas follow so the browser's own UI matches.
90+// Applied here, before the first paint: the page never draws in the wrong
91+// theme first. The footer button (ermiteThemeSwitch in ermite.js) cycles it.
92+//
93+// get() the mode
94+// next(mode) the mode after it: auto → light → dark → auto
95+// set(mode) stores and applies it
96+// apply() shows the stored mode on the page
97+// watch(fn) fn(mode) when another tab changes it
98+//
99+// Tested in Node by jstest/theme.test.mjs.
100+window.ermiteTheme = (() => {
101+ const values = ['auto', 'light', 'dark'];
102+ const pref = window.ermiteStore.pref('theme', { values, fallback: 'auto' });
103+ const next = (mode) => values[(values.indexOf(mode) + 1) % values.length];
104+
105+ function apply() {
106+ const mode = pref.get();
107+ const doc = globalThis.document;
108+ const root = doc?.documentElement;
109+ if (!root) return mode;
110+ if (mode === 'auto') root.removeAttribute('data-theme');
111+ else root.setAttribute('data-theme', mode);
112+ doc.querySelector('meta[name="color-scheme"]')
113+ ?.setAttribute('content', mode === 'auto' ? 'light dark' : mode);
114+ // Each theme-color meta keeps its rendered colour in data-auto. Forced,
115+ // both metas take the colour of the meta of that scheme (data-scheme),
116+ // whatever the system scheme their media matches.
117+ const metas = [...doc.querySelectorAll('meta[name="theme-color"]')];
118+ for (const m of metas) if (!m.hasAttribute('data-auto')) m.setAttribute('data-auto', m.getAttribute('content') ?? '');
119+ const forced = metas.find((m) => m.getAttribute('data-scheme') === mode);
120+ for (const m of metas) {
121+ m.setAttribute('content', mode === 'auto' || !forced ? m.getAttribute('data-auto') : forced.getAttribute('data-auto'));
122+ }
123+ return mode;
124+ }
125+
126+ function set(mode) {
127+ pref.set(mode);
128+ apply();
129+ }
130+
131+ apply();
132+ return { values, get: pref.get, next, set, apply, watch: pref.watch };
133+})();
M internal/app/testdata/web/blob-binary.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>data.bin · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -140,6 +140,10 @@
140140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
141141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
144+<span class="ico i-sun-moon" aria-hidden="true">
145+</span>
146+</button>
143147 </footer>
144148 <div class="hint">
145149 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>data.bin · files · example.test</title>7 <title>data.bin · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -140,6 +140,10 @@
140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
144+<span class="ico i-sun-moon" aria-hidden="true">
145+</span>
146+</button>
143 </footer>147 </footer>
144 <div class="hint">148 <div class="hint">
145 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">149 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-code.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>main.go · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -210,6 +210,10 @@
210210 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
211211 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
212212 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
213+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
214+<span class="ico i-sun-moon" aria-hidden="true">
215+</span>
216+</button>
213217 </footer>
214218 <div class="hint">
215219 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>main.go · files · example.test</title>7 <title>main.go · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -210,6 +210,10 @@
210 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>210 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
211 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">211 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
212 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>212 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
213+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
214+<span class="ico i-sun-moon" aria-hidden="true">
215+</span>
216+</button>
213 </footer>217 </footer>
214 <div class="hint">218 <div class="hint">
215 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">219 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-empty.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>empty.txt · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -140,6 +140,10 @@
140140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
141141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
144+<span class="ico i-sun-moon" aria-hidden="true">
145+</span>
146+</button>
143147 </footer>
144148 <div class="hint">
145149 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>empty.txt · files · example.test</title>7 <title>empty.txt · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -140,6 +140,10 @@
140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
144+<span class="ico i-sun-moon" aria-hidden="true">
145+</span>
146+</button>
143 </footer>147 </footer>
144 <div class="hint">148 <div class="hint">
145 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">149 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-image.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>pic.png · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -142,6 +142,10 @@
142142 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
143143 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
144144 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
145+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
146+<span class="ico i-sun-moon" aria-hidden="true">
147+</span>
148+</button>
145149 </footer>
146150 <div class="hint">
147151 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>pic.png · files · example.test</title>7 <title>pic.png · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -142,6 +142,10 @@
142 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>142 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
143 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">143 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
144 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>144 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
145+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
146+<span class="ico i-sun-moon" aria-hidden="true">
147+</span>
148+</button>
145 </footer>149 </footer>
146 <div class="hint">150 <div class="hint">
147 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">151 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-markdown.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>docs/guide.md · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -207,6 +207,10 @@
207207 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
208208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
209209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
210+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
211+<span class="ico i-sun-moon" aria-hidden="true">
212+</span>
213+</button>
210214 </footer>
211215 <div class="hint">
212216 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>docs/guide.md · files · example.test</title>7 <title>docs/guide.md · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -207,6 +207,10 @@
207 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>207 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
210+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
211+<span class="ico i-sun-moon" aria-hidden="true">
212+</span>
213+</button>
210 </footer>214 </footer>
211 <div class="hint">215 <div class="hint">
212 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">216 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-svg.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>logo.svg · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -165,6 +165,10 @@
165165 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
166166 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
167167 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
168+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
169+<span class="ico i-sun-moon" aria-hidden="true">
170+</span>
171+</button>
168172 </footer>
169173 <div class="hint">
170174 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>logo.svg · files · example.test</title>7 <title>logo.svg · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -165,6 +165,10 @@
165 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>165 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
166 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">166 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
167 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>167 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
168+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
169+<span class="ico i-sun-moon" aria-hidden="true">
170+</span>
171+</button>
168 </footer>172 </footer>
169 <div class="hint">173 <div class="hint">
170 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">174 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-text.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>notes.txt · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -160,6 +160,10 @@
160160 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
161161 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
162162 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
163+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
164+<span class="ico i-sun-moon" aria-hidden="true">
165+</span>
166+</button>
163167 </footer>
164168 <div class="hint">
165169 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>notes.txt · files · example.test</title>7 <title>notes.txt · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -160,6 +160,10 @@
160 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>160 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
161 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">161 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
162 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>162 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
163+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
164+<span class="ico i-sun-moon" aria-hidden="true">
165+</span>
166+</button>
163 </footer>167 </footer>
164 <div class="hint">168 <div class="hint">
165 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">169 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-too-large.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>big.txt · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -140,6 +140,10 @@
140140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
141141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
144+<span class="ico i-sun-moon" aria-hidden="true">
145+</span>
146+</button>
143147 </footer>
144148 <div class="hint">
145149 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>big.txt · files · example.test</title>7 <title>big.txt · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -140,6 +140,10 @@
140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>140 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">141 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>142 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
143+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
144+<span class="ico i-sun-moon" aria-hidden="true">
145+</span>
146+</button>
143 </footer>147 </footer>
144 <div class="hint">148 <div class="hint">
145 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">149 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/blob-trojan.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>evil.go · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -207,6 +207,10 @@
207207 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
208208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
209209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
210+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
211+<span class="ico i-sun-moon" aria-hidden="true">
212+</span>
213+</button>
210214 </footer>
211215 <div class="hint">
212216 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>evil.go · files · example.test</title>7 <title>evil.go · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -207,6 +207,10 @@
207 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>207 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">208 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>209 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
210+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
211+<span class="ico i-sun-moon" aria-hidden="true">
212+</span>
213+</button>
210 </footer>214 </footer>
211 <div class="hint">215 <div class="hint">
212 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">216 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-big.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 add a very large file · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -192,6 +192,10 @@
192192 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
193193 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
194194 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
195+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
196+<span class="ico i-sun-moon" aria-hidden="true">
197+</span>
198+</button>
195199 </footer>
196200 <div class="hint">
197201 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 add a very large file · diffs · example.test</title>7 <title>SHA7 add a very large file · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -192,6 +192,10 @@
192 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>192 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
193 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">193 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
194 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>194 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
195+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
196+<span class="ico i-sun-moon" aria-hidden="true">
197+</span>
198+</button>
195 </footer>199 </footer>
196 <div class="hint">200 <div class="hint">
197 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">201 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-binary.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 change a binary file · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -183,6 +183,10 @@
183183 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
184184 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
185185 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
186+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
187+<span class="ico i-sun-moon" aria-hidden="true">
188+</span>
189+</button>
186190 </footer>
187191 <div class="hint">
188192 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 change a binary file · diffs · example.test</title>7 <title>SHA7 change a binary file · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -183,6 +183,10 @@
183 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>183 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
184 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">184 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
185 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>185 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
186+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
187+<span class="ico i-sun-moon" aria-hidden="true">
188+</span>
189+</button>
186 </footer>190 </footer>
187 <div class="hint">191 <div class="hint">
188 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">192 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-merge.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 merge the side branch · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -225,6 +225,10 @@
225225 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
226226 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
227227 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
228+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
229+<span class="ico i-sun-moon" aria-hidden="true">
230+</span>
231+</button>
228232 </footer>
229233 <div class="hint">
230234 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 merge the side branch · diffs · example.test</title>7 <title>SHA7 merge the side branch · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -225,6 +225,10 @@
225 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>225 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
226 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">226 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
227 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>227 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
228+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
229+<span class="ico i-sun-moon" aria-hidden="true">
230+</span>
231+</button>
228 </footer>232 </footer>
229 <div class="hint">233 <div class="hint">
230 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">234 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-modify-split.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 modify a file and make a script executable · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -348,6 +348,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
348348 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
349349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
350350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
351+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
352+<span class="ico i-sun-moon" aria-hidden="true">
353+</span>
354+</button>
351355 </footer>
352356 <div class="hint">
353357 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 modify a file and make a script executable · diffs · example.test</title>7 <title>SHA7 modify a file and make a script executable · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -348,6 +348,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
348 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>348 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
351+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
352+<span class="ico i-sun-moon" aria-hidden="true">
353+</span>
354+</button>
351 </footer>355 </footer>
352 <div class="hint">356 <div class="hint">
353 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">357 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-modify.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 modify a file and make a script executable · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -348,6 +348,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
348348 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
349349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
350350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
351+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
352+<span class="ico i-sun-moon" aria-hidden="true">
353+</span>
354+</button>
351355 </footer>
352356 <div class="hint">
353357 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 modify a file and make a script executable · diffs · example.test</title>7 <title>SHA7 modify a file and make a script executable · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -348,6 +348,10 @@ And &lt;b&gt;html&lt;/b&gt; &amp; &#34;quotes&#34;.</pre>
348 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>348 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">349 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>350 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
351+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
352+<span class="ico i-sun-moon" aria-hidden="true">
353+</span>
354+</button>
351 </footer>355 </footer>
352 <div class="hint">356 <div class="hint">
353 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">357 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-rename.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 rename a file and delete another · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -252,6 +252,10 @@
252252 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
253253 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
254254 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
255+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
256+<span class="ico i-sun-moon" aria-hidden="true">
257+</span>
258+</button>
255259 </footer>
256260 <div class="hint">
257261 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 rename a file and delete another · diffs · example.test</title>7 <title>SHA7 rename a file and delete another · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -252,6 +252,10 @@
252 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>252 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
253 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">253 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
254 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>254 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
255+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
256+<span class="ico i-sun-moon" aria-hidden="true">
257+</span>
258+</button>
255 </footer>259 </footer>
256 <div class="hint">260 <div class="hint">
257 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">261 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-root.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 root: the first files · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -571,6 +571,10 @@
571571 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
572572 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
573573 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
574+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
575+<span class="ico i-sun-moon" aria-hidden="true">
576+</span>
577+</button>
574578 </footer>
575579 <div class="hint">
576580 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 root: the first files · diffs · example.test</title>7 <title>SHA7 root: the first files · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -571,6 +571,10 @@
571 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>571 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
572 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">572 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
573 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>573 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
574+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
575+<span class="ico i-sun-moon" aria-hidden="true">
576+</span>
577+</button>
574 </footer>578 </footer>
575 <div class="hint">579 <div class="hint">
576 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">580 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commit-trojan.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>SHA7 add evil.go · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -400,6 +400,10 @@
400400 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
401401 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
402402 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
403+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
404+<span class="ico i-sun-moon" aria-hidden="true">
405+</span>
406+</button>
403407 </footer>
404408 <div class="hint">
405409 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>SHA7 add evil.go · diffs · example.test</title>7 <title>SHA7 add evil.go · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -400,6 +400,10 @@
400 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>400 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
401 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">401 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
402 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>402 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
403+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
404+<span class="ico i-sun-moon" aria-hidden="true">
405+</span>
406+</button>
403 </footer>407 </footer>
404 <div class="hint">408 <div class="hint">
405 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">409 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commits-page2.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>commits · long · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -167,6 +167,10 @@
167167 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
168168 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
169169 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
170+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
171+<span class="ico i-sun-moon" aria-hidden="true">
172+</span>
173+</button>
170174 </footer>
171175 <div class="hint">
172176 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>commits · long · example.test</title>7 <title>commits · long · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -167,6 +167,10 @@
167 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>167 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
168 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">168 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
169 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>169 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
170+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
171+<span class="ico i-sun-moon" aria-hidden="true">
172+</span>
173+</button>
170 </footer>174 </footer>
171 <div class="hint">175 <div class="hint">
172 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">176 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commits-path.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>history of a.txt · long · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -182,6 +182,10 @@
182182 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
183183 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
184184 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
185+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
186+<span class="ico i-sun-moon" aria-hidden="true">
187+</span>
188+</button>
185189 </footer>
186190 <div class="hint">
187191 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>history of a.txt · long · example.test</title>7 <title>history of a.txt · long · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -182,6 +182,10 @@
182 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>182 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
183 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">183 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
184 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>184 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
185+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
186+<span class="ico i-sun-moon" aria-hidden="true">
187+</span>
188+</button>
185 </footer>189 </footer>
186 <div class="hint">190 <div class="hint">
187 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">191 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commits-root.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>commits · long · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -964,6 +964,10 @@
964964 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
965965 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
966966 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
967+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
968+<span class="ico i-sun-moon" aria-hidden="true">
969+</span>
970+</button>
967971 </footer>
968972 <div class="hint">
969973 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>commits · long · example.test</title>7 <title>commits · long · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -964,6 +964,10 @@
964 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>964 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
965 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">965 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
966 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>966 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
967+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
968+<span class="ico i-sun-moon" aria-hidden="true">
969+</span>
970+</button>
967 </footer>971 </footer>
968 <div class="hint">972 <div class="hint">
969 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">973 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/commits-small.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>commits · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -309,6 +309,10 @@
309309 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
310310 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
311311 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
312+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
313+<span class="ico i-sun-moon" aria-hidden="true">
314+</span>
315+</button>
312316 </footer>
313317 <div class="hint">
314318 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>commits · diffs · example.test</title>7 <title>commits · diffs · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -309,6 +309,10 @@
309 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>309 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
310 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">310 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
311 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>311 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
312+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
313+<span class="ico i-sun-moon" aria-hidden="true">
314+</span>
315+</button>
312 </footer>316 </footer>
313 <div class="hint">317 <div class="hint">
314 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">318 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/empty.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>quiet · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -85,6 +85,10 @@
8585 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
8686 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
8787 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
88+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
89+<span class="ico i-sun-moon" aria-hidden="true">
90+</span>
91+</button>
8892 </footer>
8993 <div class="hint">
9094 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>quiet · example.test</title>7 <title>quiet · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -85,6 +85,10 @@
85 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>85 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
86 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">86 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
87 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>87 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
88+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
89+<span class="ico i-sun-moon" aria-hidden="true">
90+</span>
91+</button>
88 </footer>92 </footer>
89 <div class="hint">93 <div class="hint">
90 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">94 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/home.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -181,6 +181,10 @@
181181 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
182182 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
183183 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
184+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
185+<span class="ico i-sun-moon" aria-hidden="true">
186+</span>
187+</button>
184188 </footer>
185189 <div class="hint">
186190 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>example.test</title>7 <title>example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -181,6 +181,10 @@
181 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>181 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
182 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">182 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
183 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>183 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
184+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
185+<span class="ico i-sun-moon" aria-hidden="true">
186+</span>
187+</button>
184 </footer>188 </footer>
185 <div class="hint">189 <div class="hint">
186 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">190 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/not-found-page.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>404 · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -74,6 +74,10 @@
7474 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
7575 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
7676 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
77+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
78+<span class="ico i-sun-moon" aria-hidden="true">
79+</span>
80+</button>
7781 </footer>
7882 <div class="hint">
7983 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>404 · example.test</title>7 <title>404 · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -74,6 +74,10 @@
74 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>74 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
77+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
78+<span class="ico i-sun-moon" aria-hidden="true">
79+</span>
80+</button>
77 </footer>81 </footer>
78 <div class="hint">82 <div class="hint">
79 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">83 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/not-found.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>404 · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -74,6 +74,10 @@
7474 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
7575 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
7676 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
77+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
78+<span class="ico i-sun-moon" aria-hidden="true">
79+</span>
80+</button>
7781 </footer>
7882 <div class="hint">
7983 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>404 · example.test</title>7 <title>404 · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -74,6 +74,10 @@
74 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>74 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">75 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>76 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
77+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
78+<span class="ico i-sun-moon" aria-hidden="true">
79+</span>
80+</button>
77 </footer>81 </footer>
78 <div class="hint">82 <div class="hint">
79 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">83 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/refs.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>refs · demo · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -191,6 +191,10 @@
191191 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
192192 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
193193 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
194+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
195+<span class="ico i-sun-moon" aria-hidden="true">
196+</span>
197+</button>
194198 </footer>
195199 <div class="hint">
196200 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>refs · demo · example.test</title>7 <title>refs · demo · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -191,6 +191,10 @@
191 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>191 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
192 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">192 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
193 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>193 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
194+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
195+<span class="ico i-sun-moon" aria-hidden="true">
196+</span>
197+</button>
194 </footer>198 </footer>
195 <div class="hint">199 <div class="hint">
196 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">200 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/summary.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>demo · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -217,6 +217,10 @@
217217 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
218218 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
219219 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
220+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
221+<span class="ico i-sun-moon" aria-hidden="true">
222+</span>
223+</button>
220224 </footer>
221225 <div class="hint">
222226 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>demo · example.test</title>7 <title>demo · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -217,6 +217,10 @@
217 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>217 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
218 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">218 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
219 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>219 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
220+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
221+<span class="ico i-sun-moon" aria-hidden="true">
222+</span>
223+</button>
220 </footer>224 </footer>
221 <div class="hint">225 <div class="hint">
222 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">226 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/tree-nested.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>src/lib · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -155,6 +155,10 @@
155155 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
156156 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
157157 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
158+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
159+<span class="ico i-sun-moon" aria-hidden="true">
160+</span>
161+</button>
158162 </footer>
159163 <div class="hint">
160164 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>src/lib · files · example.test</title>7 <title>src/lib · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -155,6 +155,10 @@
155 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>155 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
156 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">156 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
157 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>157 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
158+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
159+<span class="ico i-sun-moon" aria-hidden="true">
160+</span>
161+</button>
158 </footer>162 </footer>
159 <div class="hint">163 <div class="hint">
160 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">164 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/tree-root.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>tree · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -376,6 +376,10 @@
376376 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
377377 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
378378 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
379+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
380+<span class="ico i-sun-moon" aria-hidden="true">
381+</span>
382+</button>
379383 </footer>
380384 <div class="hint">
381385 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>tree · files · example.test</title>7 <title>tree · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -376,6 +376,10 @@
376 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>376 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
377 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">377 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
378 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>378 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
379+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
380+<span class="ico i-sun-moon" aria-hidden="true">
381+</span>
382+</button>
379 </footer>383 </footer>
380 <div class="hint">384 <div class="hint">
381 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">385 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/tree-slash-ref.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>x · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -152,6 +152,10 @@
152152 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
153153 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
154154 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
155+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
156+<span class="ico i-sun-moon" aria-hidden="true">
157+</span>
158+</button>
155159 </footer>
156160 <div class="hint">
157161 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>x · files · example.test</title>7 <title>x · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -152,6 +152,10 @@
152 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>152 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
153 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">153 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
154 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>154 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
155+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
156+<span class="ico i-sun-moon" aria-hidden="true">
157+</span>
158+</button>
155 </footer>159 </footer>
156 <div class="hint">160 <div class="hint">
157 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">161 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
M internal/app/testdata/web/tree-subdir.html +6 -2
@@ -5,8 +5,8 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <meta name="color-scheme" content="light dark">
77 <title>docs · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">
8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
1010 <meta name="apple-mobile-web-app-title" content="ermite">
1111 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
1212 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -168,6 +168,10 @@
168168 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
169169 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
170170 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
171+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
172+<span class="ico i-sun-moon" aria-hidden="true">
173+</span>
174+</button>
171175 </footer>
172176 <div class="hint">
173177 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">
@@ -5,8 +5,8 @@
5 <meta name="viewport" content="width=device-width, initial-scale=1">5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="color-scheme" content="light dark">6 <meta name="color-scheme" content="light dark">
7 <title>docs · files · example.test</title>7 <title>docs · files · example.test</title>
8-<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">8+<meta name="theme-color" data-scheme="light" media="(prefers-color-scheme: light)" content="#ffffff">
9-<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0c0c0c">9+<meta name="theme-color" data-scheme="dark" media="(prefers-color-scheme: dark)" content="#0c0c0c">
10 <meta name="apple-mobile-web-app-title" content="ermite">10 <meta name="apple-mobile-web-app-title" content="ermite">
11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">11 <link rel="icon" type="image/svg+xml" href="/-/static/img/icon.HASH.svg">
12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">12 <link rel="apple-touch-icon" href="/-/static/app/apple-touch-icon.HASH.png">
@@ -168,6 +168,10 @@
168 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>168 <a href="http://example.test/ermite" rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
169 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">169 <button type="button" class="foot-keys" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?">
170 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>170 <kbd aria-hidden="true">?</kbd> keyboard shortcuts</button>
171+<button type="button" class="foot-theme" data-js data-theme-switch aria-label="theme: auto (switch to light)" title="theme: auto (switch to light)">
172+<span class="ico i-sun-moon" aria-hidden="true">
173+</span>
174+</button>
171 </footer>175 </footer>
172 <div class="hint">176 <div class="hint">
173 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">177 <span aria-hidden="true" class="hint-nav" :text="document.body.dataset.nav || ''">

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 42

↑↓ moveenter openesc close