Theme pairs with dark mode toggle, gear active state, PALETTE_DEFAULT_DARK (#127)
- Collapse the 10 theme cards into 5 pairs: light swatches top row, dark bottom row - Dark mode toggle in settings and topbar (sun/moon inline SVG) - Toggling dark mode switches to the other variant of the selected pair - Persist pair id in localStorage palette-theme, dark flag in palette-dark - Head script resolves base+dark to variant; URL ?theme= accepts both ids and wins - Settings gear highlights like nav tabs on /settings - PALETTE_DEFAULT_DARK env var sets server default dark state (default on) - Server-side resolution helpers + tests
This commit is contained in:
+41
-2
@@ -24,6 +24,44 @@ import (
|
||||
//go:embed templates/*.html
|
||||
var tmplFS embed.FS
|
||||
|
||||
// DefaultDark is the server-configured default dark mode state for visitors
|
||||
// without stored preferences (#127). Set via PALETTE_DEFAULT_DARK.
|
||||
var DefaultDark = true
|
||||
|
||||
// SetDefaultDark applies the PALETTE_DEFAULT_DARK env var: unset = dark on,
|
||||
// "false"/"0"/"off" = dark off.
|
||||
func SetDefaultDark(v string) {
|
||||
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||
case "false", "0", "off":
|
||||
DefaultDark = false
|
||||
default:
|
||||
DefaultDark = true
|
||||
}
|
||||
}
|
||||
|
||||
// ResolvePreset maps a theme pair id (or explicit variant id) plus dark state
|
||||
// to a concrete preset id. Returns the defaults when pair is empty or unknown.
|
||||
// Precedence handled by callers: URL ?theme= > stored pair+dark > defaults.
|
||||
func ResolvePreset(pair string, dark bool) string {
|
||||
variants := map[string][2]string{ // pair -> {light, dark}
|
||||
"midnight": {"midnight-light", "midnight"},
|
||||
"smooth": {"smooth", "smooth-dark"},
|
||||
"pastel-lavender": {"pastel-lavender", "pastel-lavender-dark"},
|
||||
"pastel-peach": {"pastel-peach", "pastel-peach-dark"},
|
||||
"pastel-cloud": {"pastel-cloud", "pastel-cloud-dark"},
|
||||
}
|
||||
if v, ok := variants[pair]; ok {
|
||||
if dark {
|
||||
return v[1]
|
||||
}
|
||||
return v[0]
|
||||
}
|
||||
if dark {
|
||||
return "midnight"
|
||||
}
|
||||
return "midnight-light"
|
||||
}
|
||||
|
||||
//go:embed static
|
||||
var staticFS embed.FS
|
||||
|
||||
@@ -33,8 +71,9 @@ type UI struct {
|
||||
|
||||
func New() (*UI, error) {
|
||||
funcs := template.FuncMap{
|
||||
"humanSize": humanSize,
|
||||
"version": func() string { return Version }, // #93: topbar version label
|
||||
"humanSize": humanSize,
|
||||
"version": func() string { return Version }, // #93: topbar version label
|
||||
"defaultDark": func() bool { return DefaultDark }, // #127: server-configured default
|
||||
}
|
||||
t, err := template.New("").Funcs(funcs).ParseFS(tmplFS, "templates/*.html")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user