Themes: share app theme colors between desktop and Android (app-themes.css), add theme picker and accent color to the Android app, and add Windows XP, Windows 11, macOS (Liquid Glass) and Cyberpunk 2077 themes with matching terminal schemes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 14:31:51 +02:00
co-authored by Claude Opus 5.5
parent 27091ab5d1
commit 58709e09f2
10 changed files with 285 additions and 101 deletions
+51 -10
View File
@@ -193,10 +193,12 @@ async function reload() {
S.lock = await api.call('lock:status');
I18N.setLanguage(S.lock.meta.language || 'auto', S.info.locale);
applyStatic();
applyTheme(S.lock.meta);
if (S.lock.locked) return showLock();
S.vault = await api.call('vault:get');
I18N.setLanguage(settings().language || 'auto', S.info.locale);
applyStatic();
applyTheme();
S.sync = await api.call('sync:status').catch(() => null);
$('#main').hidden = false;
render();
@@ -410,7 +412,33 @@ const KEYS = [
];
const mods = { ctrl: 0, alt: 0 }; // 0 aus, 1 einmal, 2 festgestellt
function termTheme() { return TERM_THEMES[settings().terminalTheme] || TERM_THEMES.mrterm; }
function termTheme() {
const id = settings().terminalTheme;
if (!id || id === 'auto') return TERM_THEMES[(APP_THEMES[settings().appTheme] || APP_THEMES.midnight).term] || TERM_THEMES.mrterm;
return TERM_THEMES[id] || TERM_THEMES.mrterm;
}
// App-Design (gemeinsam mit dem Desktop: lib/app-themes.css + APP_THEMES)
function applyTheme(st = settings()) {
const root = document.documentElement;
const t = APP_THEMES[st.appTheme] || APP_THEMES.midnight;
if (st.appTheme && st.appTheme !== 'midnight' && APP_THEMES[st.appTheme]) root.dataset.theme = st.appTheme; else delete root.dataset.theme;
if (st.accent) root.style.setProperty('--accent', st.accent); else root.style.removeProperty('--accent');
root.classList.toggle('light', !!t.light);
S.sessions?.forEach((x) => { x.term.options.theme = termTheme(); });
document.documentElement.style.setProperty('--term-bg', termTheme().background);
syncBars();
}
// Statusleiste passend zur sichtbaren Fläche (Terminal oder App)
function syncBars() {
const t = APP_THEMES[settings().appTheme] || APP_THEMES.midnight;
const inTerm = !$('#termScreen').hidden;
const bg = inTerm ? getComputedStyle(document.querySelector('.term-head')).backgroundColor : getComputedStyle(document.body).backgroundColor;
const rgb = bg.match(/\d+/g)?.map(Number) || [20, 22, 29];
const hex = '#' + rgb.slice(0, 3).map((n) => n.toString(16).padStart(2, '0')).join('');
const light = inTerm ? (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 > 150 : !!t.light;
Native.bars?.({ light, color: hex }).catch(() => {});
}
const fontSize = () => Number(settings().mobileFontSize) || 12;
class Session {
@@ -537,6 +565,7 @@ function showSession(s) {
$('#termScreen').hidden = false;
$('#main').hidden = true;
document.documentElement.style.setProperty('--term-bg', termTheme().background);
syncBars();
updateTermHead();
drawKeybar();
requestAnimationFrame(() => { s.refit(); if (s.alive) s.term.focus(); });
@@ -548,6 +577,7 @@ function hideTerminal() {
S.active?.term.blur();
S.active = null;
render();
syncBars();
}
function updateTermHead() {
@@ -723,20 +753,31 @@ function viewSettings(v) {
v.append(h(`<div class="top"><h1>${esc(T('Settings'))}</h1></div>`));
const st = settings();
// Sprache & Terminal
// Sprache, Design & Terminal
const gen = h(`<div class="card"><h3>${esc(T('General'))}</h3>
${field(T('Language'), 'language', st.language || 'auto', { type: 'select', options: [['auto', T('System language')], ...Object.entries(I18N.LANGUAGES)] })}
${field(T('Terminal color scheme'), 'terminalTheme', st.terminalTheme || 'mrterm', { type: 'select', options: Object.entries(TERM_THEMES).map(([id, t]) => [id, t.name]) })}
<div class="field"><label>Design</label><div class="theme-grid"></div></div>
<div class="field"><label>${esc(T('Accent color'))}</label><div class="colors"></div></div>
${field(T('Terminal color scheme'), 'terminalTheme', st.terminalTheme || 'auto', { type: 'select', options: [['auto', T('Match theme')], ...Object.entries(TERM_THEMES).map(([id, t]) => [id, t.name])] })}
${field(T('Font size'), 'mobileFontSize', fontSize(), { type: 'select', options: [8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20].map((n) => [n, `${n} px`]) })}
</div>`);
$('[name=language]', gen).onchange = async (e) => { await call('vault:settings', { language: e.target.value }); await reload(); };
$('[name=terminalTheme]', gen).onchange = async (e) => {
await call('vault:settings', { terminalTheme: e.target.value }); S.vault.settings.terminalTheme = e.target.value;
S.sessions.forEach((s) => { s.term.options.theme = termTheme(); });
};
const save = async (patch) => { await call('vault:settings', patch); Object.assign(S.vault.settings, patch); };
$('[name=language]', gen).onchange = async (e) => { await save({ language: e.target.value }); await reload(); };
for (const [id, t] of Object.entries(APP_THEMES)) {
const p = h(`<button class="theme-prev ${(st.appTheme || 'midnight') === id ? 'active' : ''}" style="background:${t.bg};color:${t.text}">
<div class="dp"><i style="background:${t.side}"></i><div><b style="background:${t.card}"><s style="background:${t.accent}"></s></b><b style="background:${t.card}"></b></div></div>${esc(T(t.name))}</button>`);
p.onclick = async () => { await save({ appTheme: id }); applyTheme(); render(); };
$('.theme-grid', gen).append(p);
}
['', ...COLORS.slice(0, 6), '#21c7a8', '#cba6f7', '#fcee0a'].forEach((col) => {
const b = h(`<button title="${esc(col || T('Theme default'))}" class="${(st.accent || '') === col ? 'active' : ''}" style="background:${col || 'conic-gradient(#6e7bff,#21c7a8,#cba6f7,#ffb454,#6e7bff)'}"></button>`);
b.onclick = async () => { await save({ accent: col }); applyTheme(); render(); };
$('.colors', gen).append(b);
});
$('[name=terminalTheme]', gen).onchange = async (e) => { await save({ terminalTheme: e.target.value }); applyTheme(); };
$('[name=mobileFontSize]', gen).onchange = async (e) => {
const n = Number(e.target.value); await call('vault:settings', { mobileFontSize: n }); S.vault.settings.mobileFontSize = n;
S.sessions.forEach((s) => { s.term.options.fontSize = n; });
const n = Number(e.target.value); await save({ mobileFontSize: n });
S.sessions.forEach((x) => { x.term.options.fontSize = n; });
};
v.append(gen);