Files
devdesk/web/src/theme.js
T
jaredandClaude Opus 5.5 05057f695d
build-deb / Lint, test, package (push) Failing after 1m59s
build-deb / Deploy to CT 126 (push) Skipped
build-deb / Notify on failure (push) Successful in 5s
feat: v0.6.0 light mode, command palette, dialogs, toasts
- Light mode with the house ☀/◐ toggle and lt_theme preference; light
  terminal palette; Claude runs with dark-ansi so its colours follow the
  palette; tmux and the statusLine use ANSI colours.
- Command palette (⌕, Ctrl+Shift+P) with session, terminal, view, link and
  help commands.
- .lt-modal dialogs for new/rename/end and a keyboard shortcuts sheet.
- .lt-toast results for actions, uploads, copy and CI.
- ttyd dropped the reply of fast one-shot commands; replies now wait for the
  browser to hang up, and the client hangs up as soon as it has one.
- Sessions panel re-fetches after an action instead of reusing a stale poll.

Closes #31
Closes #32
Closes #33
Closes #34
Closes #35

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:32:23 -04:00

63 lines
2.0 KiB
JavaScript

// Dark / light theme, interchangeable with web_template's lt.theme (devdesk#31):
// same localStorage key (lt_theme), same data-theme attribute on <html>, the OS
// preference while nothing is saved, and tabs follow each other via `storage`.
// The inline script in index.template.html applies it before first paint.
const KEY = 'lt_theme';
const prefersLight = window.matchMedia('(prefers-color-scheme: light)');
const listeners = new Set();
function saved() {
try {
const value = localStorage.getItem(KEY);
return value === 'light' || value === 'dark' ? value : null;
} catch {
return null;
}
}
export function getTheme() {
return document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
}
function apply(theme) {
const root = document.documentElement;
root.setAttribute('data-theme', theme);
root.style.colorScheme = theme;
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', theme === 'light' ? '#edf0f5' : '#030508');
const next = theme === 'light' ? 'dark' : 'light';
for (const button of document.querySelectorAll('.lt-theme-btn')) {
button.textContent = theme === 'light' ? '◐' : '☀';
button.setAttribute('aria-label', `Switch to ${next} mode`);
button.title = `Switch to ${next} mode`;
}
for (const listener of listeners) listener(theme);
}
export function setTheme(theme) {
try {
localStorage.setItem(KEY, theme);
} catch {
// Private mode: the choice lasts until the tab closes.
}
apply(theme);
}
export function toggleTheme() {
setTheme(getTheme() === 'light' ? 'dark' : 'light');
}
export function onThemeChange(listener) {
listeners.add(listener);
}
export function initTheme() {
apply(saved() ?? (prefersLight.matches ? 'light' : 'dark'));
prefersLight.addEventListener('change', (event) => {
if (!saved()) apply(event.matches ? 'light' : 'dark');
});
window.addEventListener('storage', (event) => {
if (event.key === KEY) apply(saved() ?? (prefersLight.matches ? 'light' : 'dark'));
});
}