Shared client-side preference in localStorage 'palette-wrap', default off. Toggle button on /new editor head and paste title bar, plus a Line wrap checkbox in the settings menu; all read/write the same preference. Active state uses the accent treatment like other toggles. No em dashes in UI text.
147 lines
7.7 KiB
HTML
147 lines
7.7 KiB
HTML
{{define "head"}}
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="/static/app.css">
|
|
<script>
|
|
// preset hook (#16): ?theme= wins; else persisted choice (#100).
|
|
// #127: theme pairs - each base theme has a light and dark variant;
|
|
// palette-theme stores the base id, palette-dark the dark flag.
|
|
(function () {
|
|
var PAIRS = {
|
|
'midnight': { light: 'midnight-light', dark: 'midnight' },
|
|
'smooth': { light: 'smooth', dark: 'smooth-dark' },
|
|
'pastel-lavender': { light: 'pastel-lavender', dark: 'pastel-lavender-dark' },
|
|
'pastel-peach': { light: 'pastel-peach', dark: 'pastel-peach-dark' },
|
|
'pastel-cloud': { light: 'pastel-cloud', dark: 'pastel-cloud-dark' }
|
|
};
|
|
function resolve(t, dark) {
|
|
for (var base in PAIRS) {
|
|
if (t === PAIRS[base].light || t === PAIRS[base].dark) return t;
|
|
if (t === base) return dark ? PAIRS[base].dark : PAIRS[base].light;
|
|
}
|
|
return null;
|
|
}
|
|
var t = new URLSearchParams(location.search).get('theme');
|
|
// precedence: URL ?theme= > stored pair+dark prefs > server default dark > dark on
|
|
var dark = {{ defaultDark }};
|
|
try {
|
|
var stored = localStorage.getItem('palette-dark');
|
|
if (stored !== null) dark = stored !== 'false';
|
|
} catch (e) {}
|
|
if (t) {
|
|
// URL ?theme= wins and accepts both base and variant ids; a variant id
|
|
// also sets the dark flag and normalizes t back to its base pair id.
|
|
var found = false;
|
|
for (var base in PAIRS) {
|
|
if (t === PAIRS[base].dark) { dark = true; t = base; found = true; break; }
|
|
if (t === PAIRS[base].light) { dark = false; t = base; found = true; break; }
|
|
if (t === base) { found = true; break; }
|
|
}
|
|
if (!found) t = null;
|
|
}
|
|
if (!t) {
|
|
try { t = localStorage.getItem('palette-theme'); } catch (e) {}
|
|
}
|
|
if (!PAIRS[t]) t = 'midnight';
|
|
// do not persist anything here: URL theme is a one-off override
|
|
document.documentElement.dataset.preset = dark ? PAIRS[t].dark : PAIRS[t].light;
|
|
})();
|
|
// #130: shared line wrap preference (localStorage 'palette-wrap', default off)
|
|
(function () {
|
|
function wrapOn() {
|
|
try { return localStorage.getItem('palette-wrap') === '1'; } catch (e) { return false; }
|
|
}
|
|
function wrapApply(on) {
|
|
if (on) document.documentElement.setAttribute('data-wrap', '1');
|
|
else document.documentElement.removeAttribute('data-wrap');
|
|
document.querySelectorAll('.wrap-toggle').forEach(function (b) {
|
|
b.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
});
|
|
var s = document.getElementById('wrap-setting');
|
|
if (s) s.checked = on;
|
|
}
|
|
window.paletteWrapToggle = function () {
|
|
var on = !wrapOn();
|
|
try { localStorage.setItem('palette-wrap', on ? '1' : '0'); } catch (e) {}
|
|
wrapApply(on);
|
|
return on;
|
|
};
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
wrapApply(wrapOn());
|
|
document.querySelectorAll('.wrap-toggle').forEach(function (b) {
|
|
b.addEventListener('click', function () { window.paletteWrapToggle(); });
|
|
});
|
|
var s = document.getElementById('wrap-setting');
|
|
if (s) s.addEventListener('change', function () {
|
|
if (s.checked !== wrapOn()) window.paletteWrapToggle();
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{{end}}
|
|
|
|
{{define "topbar"}}
|
|
<div class="topbar">
|
|
<a class="logo" href="/history">Palette <em>/ {{ version }}</em></a>
|
|
<nav>
|
|
<a href="/new" {{if eq .Page "new"}}class="on"{{end}}>New</a>
|
|
<a href="/history" {{if eq .Page "history"}}class="on"{{end}}>Public</a>
|
|
<a href="/mine" {{if eq .Page "mine"}}class="on"{{end}}>Saved</a>
|
|
<a href="https://git.archfox.org/poslop/palette" target="_blank" rel="noopener">Git<svg class="ext" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>
|
|
</nav>
|
|
<div class="spacer"></div>
|
|
<button class="iconbtn dark-toggle" id="dark-toggle" type="button" title="Toggle dark mode" aria-label="Toggle dark mode">
|
|
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
</button>
|
|
<a class="iconbtn gear{{if eq .Page "settings"}} on{{end}}" href="/settings" title="Settings" aria-label="Settings">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
</a>
|
|
</div>
|
|
<script>
|
|
// #127: dark mode toggle - flips to the other variant of the active pair.
|
|
(function () {
|
|
var PAIRS = {
|
|
'midnight': { light: 'midnight-light', dark: 'midnight' },
|
|
'smooth': { light: 'smooth', dark: 'smooth-dark' },
|
|
'pastel-lavender': { light: 'pastel-lavender', dark: 'pastel-lavender-dark' },
|
|
'pastel-peach': { light: 'pastel-peach', dark: 'pastel-peach-dark' },
|
|
'pastel-cloud': { light: 'pastel-cloud', dark: 'pastel-cloud-dark' }
|
|
};
|
|
var root = document.documentElement;
|
|
function state() {
|
|
var p = root.dataset.preset || 'midnight';
|
|
for (var base in PAIRS) {
|
|
if (p === PAIRS[base].dark) return { base: base, dark: true };
|
|
if (p === PAIRS[base].light) return { base: base, dark: false };
|
|
}
|
|
return { base: 'midnight', dark: true };
|
|
}
|
|
function apply() {
|
|
var s = state();
|
|
document.body.classList.toggle('dark', s.dark);
|
|
root.classList.toggle('dark', s.dark);
|
|
}
|
|
function sync(btns) {
|
|
btns.forEach(function (b) {
|
|
b.setAttribute('aria-pressed', state().dark ? 'true' : 'false');
|
|
});
|
|
}
|
|
var btns = document.querySelectorAll('.dark-toggle');
|
|
apply();
|
|
sync(Array.prototype.slice.call(btns));
|
|
btns.forEach(function (b) {
|
|
b.addEventListener('click', function () {
|
|
var s = state();
|
|
var dark = !s.dark;
|
|
root.dataset.preset = dark ? PAIRS[s.base].dark : PAIRS[s.base].light;
|
|
try { localStorage.setItem('palette-dark', dark ? 'true' : 'false'); } catch (e) {}
|
|
apply();
|
|
sync(Array.prototype.slice.call(btns));
|
|
document.dispatchEvent(new CustomEvent('palette-darkchange'));
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{{end}}
|