## Slice A — explicit dark/light toggle projax now ships with two palettes and a 1y cookie to remember the choice. Dark is the new default; ☀ button in the header nav flips to light and writes projax_theme=light. Server reads the cookie via themeFromRequest(r) and injects Theme + ThemeColor into every template via the centralised render(w, r, …) path, so first paint never flashes the wrong theme. Inline JS in layout.tmpl handles the toggle without a server roundtrip. Every panel colour now lives in a CSS variable under :root[data-theme=dark|light]; the only hardcoded hex values left are inside those two :root blocks. A future palette tweak is one edit, not 30 selectors. Graph node colours, kind-badges, highlights and warn/ok/bad all have parallel dark/light values picked for contrast. Standalone SVG download bakes the light palette inline because the downloaded asset has no parent :root providing vars — m's existing snapshots stay print-friendly regardless of his current cookie. Login page keeps its embedded dark CSS — it's the gateway, intentionally always dark. Tests: TestThemeDefaultIsDark, TestThemeCookieRoundTrips, TestThemeCookieUnknownFallsBackToDark, TestThemeTogglePagesShareSameTheme, TestThemeToggleScriptPresent, TestThemeColorMetaHelper. Full suite green. ## Slice B — file-upload permanently out of scope (m, 2026-05-17) docs/design.md moves "File uploads / in-projax storage" from the §3c parked list to a permanent "Out of scope (decided 2026-05-17)" clause with the rationale: PER is the cross-reference index, not the file system. docs/standards/per.md gains the same explicit clause so future shifts working from the PER standard see the constraint where they look. Memory note filed so future workers don't re-propose multipart uploads, attachments tables, or documents buckets. ## docs/design.md §13 Theming Documents the toggle approach, cookie semantics, palette table, the standalone-SVG carve-out, the login-page exception, and the 4b out-of-scope (prefers-color-scheme detection, per-page overrides, transitions on swap).
73 lines
3.1 KiB
Cheetah
73 lines
3.1 KiB
Cheetah
{{define "layout"}}<!doctype html>
|
|
<html lang="en" data-theme="{{.Theme}}">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="theme-color" content="{{.ThemeColor}}">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="projax">
|
|
<title>{{.Title}} — projax</title>
|
|
<link rel="manifest" href="/static/manifest.webmanifest">
|
|
<link rel="apple-touch-icon" href="/static/icon-192.png">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="/static/icon-192.png">
|
|
<link rel="icon" type="image/png" sizes="512x512" href="/static/icon-512.png">
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
<script>
|
|
// Phase 3j — register the service worker post-load so the install
|
|
// affordance fires and shell assets warm into cache. Failures are silent
|
|
// (older browsers, http context, etc.) — projax must work without SW.
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', function() {
|
|
navigator.serviceWorker.register('/static/sw.js').catch(function(){});
|
|
});
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<nav>
|
|
<a href="/" class="brand">projax</a>
|
|
<a href="/dashboard">dashboard</a>
|
|
<a href="/timeline">timeline</a>
|
|
<a href="/graph">graph</a>
|
|
<a href="/admin">admin</a>
|
|
<button type="button" id="theme-toggle" class="theme-toggle" title="Toggle dark / light"
|
|
aria-label="Toggle theme" data-theme="{{.Theme}}">
|
|
<span class="theme-icon" aria-hidden="true">{{if eq .Theme "light"}}☾{{else}}☀{{end}}</span>
|
|
</button>
|
|
<form method="post" action="/logout" class="logout-form">
|
|
<button type="submit" class="logout-btn">sign out</button>
|
|
</form>
|
|
</nav>
|
|
</header>
|
|
<main>
|
|
{{template "content" .}}
|
|
</main>
|
|
<script>
|
|
// Phase 4b — theme toggle. Writes the projax_theme cookie (1-year Max-Age,
|
|
// Path=/, SameSite=Lax) and flips data-theme + theme-color in place so the
|
|
// user sees the swap without a reload. Server already injected the initial
|
|
// value from the cookie, so first paint never flashes the wrong theme.
|
|
(function() {
|
|
var btn = document.getElementById('theme-toggle');
|
|
if (!btn) return;
|
|
var icon = btn.querySelector('.theme-icon');
|
|
var root = document.documentElement;
|
|
var meta = document.querySelector('meta[name="theme-color"]');
|
|
var themeColors = { dark: '#161616', light: '#f0efe8' };
|
|
btn.addEventListener('click', function() {
|
|
var current = root.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
|
|
var next = current === 'light' ? 'dark' : 'light';
|
|
root.setAttribute('data-theme', next);
|
|
btn.setAttribute('data-theme', next);
|
|
if (icon) icon.textContent = next === 'light' ? '☾' : '☀';
|
|
if (meta) meta.setAttribute('content', themeColors[next]);
|
|
var oneYear = 60 * 60 * 24 * 365;
|
|
document.cookie = 'projax_theme=' + next + '; Max-Age=' + oneYear + '; Path=/; SameSite=Lax';
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>{{end}}
|