dark/light theme toggle
This commit is contained in:
parent
ca59516f26
commit
61e9221b3a
@ -5,18 +5,18 @@
|
|||||||
|
|
||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
// Qualitative palette — muted, matches the notebook aesthetic. Cycles if
|
// Qualitative palette — defined as CSS custom properties on :root (and
|
||||||
// there are more runs than colours.
|
// overridden under [data-theme="dark"]) so the theme toggle automatically
|
||||||
const PALETTE = [
|
// swaps plot colours. Read fresh on every render.
|
||||||
"#1f4e5f", // accent teal
|
function getPalette() {
|
||||||
"#8a3a2a", // rust
|
const cs = getComputedStyle(document.documentElement);
|
||||||
"#a77a2c", // warm amber
|
const out = [];
|
||||||
"#3a6f3f", // olive
|
for (let i = 1; i <= 8; i++) {
|
||||||
"#5d4a7b", // slate purple
|
const v = cs.getPropertyValue(`--plot-${i}`).trim();
|
||||||
"#7a5c4b", // brown
|
if (v) out.push(v);
|
||||||
"#2b5d7a", // deeper blue
|
}
|
||||||
"#6b6b3e", // moss
|
return out.length ? out : ["#1f4e5f"];
|
||||||
];
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
raw: [],
|
raw: [],
|
||||||
@ -50,6 +50,7 @@ async function init() {
|
|||||||
|
|
||||||
populateFilters();
|
populateFilters();
|
||||||
wireEvents();
|
wireEvents();
|
||||||
|
document.addEventListener("themechange", render);
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +164,10 @@ function render() {
|
|||||||
|
|
||||||
// Stable colour assignment per legend row (across the full filtered set),
|
// Stable colour assignment per legend row (across the full filtered set),
|
||||||
// so toggling selection doesn't shuffle colours.
|
// so toggling selection doesn't shuffle colours.
|
||||||
|
const palette = getPalette();
|
||||||
const colored = runs.map((r, i) => ({
|
const colored = runs.map((r, i) => ({
|
||||||
run: r,
|
run: r,
|
||||||
color: PALETTE[i % PALETTE.length],
|
color: palette[i % palette.length],
|
||||||
selected: state.selected.has(runKey(r)),
|
selected: state.selected.has(runKey(r)),
|
||||||
}));
|
}));
|
||||||
const forPlot = state.selected.size === 0 ? colored : colored.filter((c) => c.selected);
|
const forPlot = state.selected.size === 0 ? colored : colored.filter((c) => c.selected);
|
||||||
|
|||||||
@ -25,6 +25,17 @@
|
|||||||
--warm: #a77a2c;
|
--warm: #a77a2c;
|
||||||
--good: #3a6f3f;
|
--good: #3a6f3f;
|
||||||
|
|
||||||
|
/* Qualitative palette for plot lines. JS reads these via getComputedStyle
|
||||||
|
so the palette flips with the theme. */
|
||||||
|
--plot-1: #1f4e5f;
|
||||||
|
--plot-2: #8a3a2a;
|
||||||
|
--plot-3: #a77a2c;
|
||||||
|
--plot-4: #3a6f3f;
|
||||||
|
--plot-5: #5d4a7b;
|
||||||
|
--plot-6: #7a5c4b;
|
||||||
|
--plot-7: #2b5d7a;
|
||||||
|
--plot-8: #6b6b3e;
|
||||||
|
|
||||||
--serif: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
|
--serif: Georgia, "Iowan Old Style", "Palatino Linotype", serif;
|
||||||
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
||||||
"Inter", "Arial", sans-serif;
|
"Inter", "Arial", sans-serif;
|
||||||
@ -36,6 +47,57 @@
|
|||||||
--space: 1rem;
|
--space: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode — warm near-black, preserving the scientific-notebook feel.
|
||||||
|
Toggled by setting [data-theme="dark"] on <html>. */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--page: #1a1917;
|
||||||
|
--panel: #1f1e1c;
|
||||||
|
--ink: #e8e4da;
|
||||||
|
--mute: #9a968c;
|
||||||
|
--faint: #5f5b52;
|
||||||
|
--rule: #2b2925;
|
||||||
|
--rule-2: #3a3834;
|
||||||
|
--accent: #84bcc9;
|
||||||
|
--accent-tint: #1c2930;
|
||||||
|
--alarm: #d18774;
|
||||||
|
--warm: #cba368;
|
||||||
|
--good: #8fb695;
|
||||||
|
|
||||||
|
--plot-1: #84bcc9;
|
||||||
|
--plot-2: #d18774;
|
||||||
|
--plot-3: #cba368;
|
||||||
|
--plot-4: #8fb695;
|
||||||
|
--plot-5: #a695c4;
|
||||||
|
--plot-6: #bc9f8a;
|
||||||
|
--plot-7: #8fa7c5;
|
||||||
|
--plot-8: #adad80;
|
||||||
|
}
|
||||||
|
|
||||||
|
html { color-scheme: light dark; }
|
||||||
|
[data-theme="light"] { color-scheme: light; }
|
||||||
|
[data-theme="dark"] { color-scheme: dark; }
|
||||||
|
|
||||||
|
/* Hardcoded light-mode colour patches. These predate the theme system;
|
||||||
|
overriding them here keeps the original selectors untouched. */
|
||||||
|
[data-theme="dark"] button.submit:hover,
|
||||||
|
[data-theme="dark"] .picker-footer .continue:not(:disabled):hover {
|
||||||
|
background: #a5d0da;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .flash.err,
|
||||||
|
[data-theme="dark"] .badge.cancelled {
|
||||||
|
background: #3a2520;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .badge.completed {
|
||||||
|
background: #1f2e21;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .dataset-picker {
|
||||||
|
--picker-panel: #252420;
|
||||||
|
--picker-hair: #3a3834;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .dataset-picker .card-desc {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
@ -880,6 +942,26 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
|
|||||||
}
|
}
|
||||||
.masthead-link:hover { border-bottom-color: var(--accent); }
|
.masthead-link:hover { border-bottom-color: var(--accent); }
|
||||||
|
|
||||||
|
.theme-toggle {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: var(--mute);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 0 0 0.55rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: color 120ms ease, transform 240ms ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.theme-toggle:hover { color: var(--accent); }
|
||||||
|
.theme-toggle:focus-visible {
|
||||||
|
outline: 1px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .theme-toggle { transform: rotate(180deg); }
|
||||||
|
|
||||||
.masthead .nav-link {
|
.masthead .nav-link {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
|
|||||||
29
app/web/static/theme.js
Normal file
29
app/web/static/theme.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// theme.js — toggle UI wire-up. The initial theme is set by a tiny inline
|
||||||
|
// script in the <head> so there's no flash of the wrong palette on load;
|
||||||
|
// this module only handles click-to-toggle + broadcasting the change so
|
||||||
|
// anything that cached a CSS-var value (e.g. the metrics page's plot
|
||||||
|
// palette) can re-render.
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
function current() {
|
||||||
|
return document.documentElement.getAttribute("data-theme") || "light";
|
||||||
|
}
|
||||||
|
function apply(theme) {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
try { localStorage.setItem("theme", theme); } catch (e) {}
|
||||||
|
document.dispatchEvent(new CustomEvent("themechange", { detail: { theme } }));
|
||||||
|
const btn = document.getElementById("theme-toggle");
|
||||||
|
if (btn) btn.setAttribute("aria-label", theme === "dark" ? "switch to light mode" : "switch to dark mode");
|
||||||
|
}
|
||||||
|
function wire() {
|
||||||
|
const btn = document.getElementById("theme-toggle");
|
||||||
|
if (!btn) return;
|
||||||
|
btn.addEventListener("click", () => apply(current() === "dark" ? "light" : "dark"));
|
||||||
|
apply(current());
|
||||||
|
}
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", wire);
|
||||||
|
} else {
|
||||||
|
wire();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>embedding notebook — web1</title>
|
<title>embedding notebook — web1</title>
|
||||||
<link rel="stylesheet" href="/static/style.css" />
|
<link rel="stylesheet" href="/static/style.css?v=5" />
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
@ -15,6 +15,13 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3' fill='%231f4e5f'/%3E%3C/svg%3E" />
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3' fill='%231f4e5f'/%3E%3C/svg%3E" />
|
||||||
|
<script>
|
||||||
|
(function(){try{
|
||||||
|
var t=localStorage.getItem('theme');
|
||||||
|
if(!t)t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';
|
||||||
|
document.documentElement.setAttribute('data-theme',t);
|
||||||
|
}catch(e){}})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -25,7 +32,8 @@
|
|||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="dot {% if not deployment_id %}bad{% endif %}"></span>
|
<span class="dot {% if not deployment_id %}bad{% endif %}"></span>
|
||||||
{% if deployment_id %}prefect · deployment {{ deployment_id[:8] }}{% else %}prefect · unreachable{% endif %}
|
{% if deployment_id %}prefect · deployment {{ deployment_id[:8] }}{% else %}prefect · unreachable{% endif %}
|
||||||
<a href="/metrics" class="nav-link">metrics →</a><br/>
|
<a href="/metrics" class="nav-link">metrics →</a>
|
||||||
|
<button type="button" class="theme-toggle" id="theme-toggle" aria-label="toggle theme">◐</button><br/>
|
||||||
<span style="color:var(--faint)">{{ prefect_api }}</span>
|
<span style="color:var(--faint)">{{ prefect_api }}</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -185,6 +193,7 @@
|
|||||||
<span><span class="k">web</span> · scientific instrument · port 8001</span>
|
<span><span class="k">web</span> · scientific instrument · port 8001</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script src="/static/theme.js"></script>
|
||||||
<script type="module" src="/static/dataset-picker.js"></script>
|
<script type="module" src="/static/dataset-picker.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -4,8 +4,15 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>metrics — embedding notebook</title>
|
<title>metrics — embedding notebook</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=4" />
|
<link rel="stylesheet" href="/static/style.css?v=5" />
|
||||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3' fill='%231f4e5f'/%3E%3C/svg%3E" />
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3' fill='%231f4e5f'/%3E%3C/svg%3E" />
|
||||||
|
<script>
|
||||||
|
(function(){try{
|
||||||
|
var t=localStorage.getItem('theme');
|
||||||
|
if(!t)t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';
|
||||||
|
document.documentElement.setAttribute('data-theme',t);
|
||||||
|
}catch(e){}})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -14,7 +21,8 @@
|
|||||||
<h1 class="title">embedding notebook <em>— stability metrics</em></h1>
|
<h1 class="title">embedding notebook <em>— stability metrics</em></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<a href="/" class="masthead-link">← runs</a><br/>
|
<a href="/" class="masthead-link">← runs</a>
|
||||||
|
<button type="button" class="theme-toggle" id="theme-toggle" aria-label="toggle theme">◐</button><br/>
|
||||||
<span style="color:var(--faint)">{{ prefect_api }}</span>
|
<span style="color:var(--faint)">{{ prefect_api }}</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -85,7 +93,8 @@
|
|||||||
<span><span class="k">web</span> · metrics · port 8001</span>
|
<span><span class="k">web</span> · metrics · port 8001</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script type="module" src="/static/metrics.js?v=4"></script>
|
<script src="/static/theme.js?v=5"></script>
|
||||||
|
<script type="module" src="/static/metrics.js?v=5"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user