diff options
| author | Fuwn <[email protected]> | 2026-01-20 05:04:23 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-20 05:04:23 -0800 |
| commit | 44a6f0c5acb1984b9565b0c093898a99b9330d81 (patch) | |
| tree | 1d20916424ec88209604a171f60e3d73070be39b | |
| parent | chore: Add justfile (diff) | |
| download | kaze-44a6f0c5acb1984b9565b0c093898a99b9330d81.tar.xz kaze-44a6f0c5acb1984b9565b0c093898a99b9330d81.zip | |
refactor: Use CSS prefers-color-scheme instead of JS-based theme toggle
| -rw-r--r-- | config.example.yaml | 4 | ||||
| -rw-r--r-- | internal/config/config.go | 6 | ||||
| -rw-r--r-- | internal/server/server.go | 2 | ||||
| -rw-r--r-- | internal/server/static/style.css | 123 | ||||
| -rw-r--r-- | internal/server/templates/index.html | 30 | ||||
| -rw-r--r-- | internal/theme/theme.go | 58 |
6 files changed, 89 insertions, 134 deletions
diff --git a/config.example.yaml b/config.example.yaml index bf3ff0f..9dab8b2 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -48,10 +48,6 @@ display: # "UTC" - Use UTC timezone # "America/New_York" - Use specific IANA timezone (e.g., "Europe/London", "Asia/Tokyo") timezone: "Browser" - - # Show the theme toggle button (true by default) - # Set to false to hide the light/dark mode toggle - show_theme_toggle: true # Monitor groups groups: diff --git a/internal/config/config.go b/internal/config/config.go index 68a88f7..dfd403a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -30,8 +30,6 @@ type DisplayConfig struct { PingFixedSlots bool `yaml:"ping_fixed_slots"` // Timezone for display (e.g., "UTC", "America/New_York", "Local") Timezone string `yaml:"timezone"` - // ShowThemeToggle controls whether to show the theme toggle button (defaults to true) - ShowThemeToggle *bool `yaml:"show_theme_toggle"` } // SiteConfig contains site metadata @@ -204,10 +202,6 @@ func (c *Config) applyDefaults() { if c.Display.Timezone == "" { c.Display.Timezone = "Local" } - if c.Display.ShowThemeToggle == nil { - defaultShow := true - c.Display.ShowThemeToggle = &defaultShow - } // Apply group defaults for i := range c.Groups { diff --git a/internal/server/server.go b/internal/server/server.go index 8d491dc..6b5ded3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -126,7 +126,6 @@ type PageData struct { LastUpdatedTooltip string // JSON data for last updated tooltip TickMode string // ping, minute, hour, day TickCount int - ShowThemeToggle bool Timezone string // Timezone for display UseBrowserTimezone bool // Use client-side timezone conversion ThemeCSS template.CSS // OpenCode theme CSS (safe CSS) @@ -210,7 +209,6 @@ func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) { Site: s.config.Site, TickMode: s.config.Display.TickMode, TickCount: s.config.Display.TickCount, - ShowThemeToggle: s.config.Display.ShowThemeToggle != nil && *s.config.Display.ShowThemeToggle, Timezone: s.config.Display.Timezone, UseBrowserTimezone: s.config.Display.Timezone == "Browser", ThemeCSS: themeCSS, diff --git a/internal/server/static/style.css b/internal/server/static/style.css index 3f197a7..94a7e11 100644 --- a/internal/server/static/style.css +++ b/internal/server/static/style.css @@ -33,35 +33,22 @@ html { --status-unknown: #555555; } -.dark { - --bg-primary: #070707; - --bg-secondary: #0f0f0f; - --bg-tertiary: #1a1a1a; - --border-color: #2a2a2a; - --text-primary: #ffffff; - --text-secondary: #999999; - --text-tertiary: #666666; - --text-dim: #444444; - --status-ok: #d0d0d0; - --status-warn: #a0a0a0; - --status-error: #808080; - --status-unknown: #555555; -} - /* Light mode - inverse terminal */ -:root:not(.dark) { - --bg-primary: #ffffff; - --bg-secondary: #f8f8f8; - --bg-tertiary: #f0f0f0; - --border-color: #e0e0e0; - --text-primary: #000000; - --text-secondary: #666666; - --text-tertiary: #999999; - --text-dim: #bbbbbb; - --status-ok: #333333; - --status-warn: #555555; - --status-error: #777777; - --status-unknown: #aaaaaa; +@media (prefers-color-scheme: light) { + :root { + --bg-primary: #ffffff; + --bg-secondary: #f8f8f8; + --bg-tertiary: #f0f0f0; + --border-color: #e0e0e0; + --text-primary: #000000; + --text-secondary: #666666; + --text-tertiary: #999999; + --text-dim: #bbbbbb; + --status-ok: #333333; + --status-warn: #555555; + --status-error: #777777; + --status-unknown: #aaaaaa; + } } /* Font */ @@ -152,49 +139,53 @@ body { .border-red-900 { border-color: var(--border-color); } /* Dark mode overrides */ -.dark .dark\:bg-neutral-800 { background-color: var(--bg-tertiary); } -.dark .dark\:bg-neutral-900 { background-color: var(--bg-secondary); } -.dark .dark\:bg-neutral-900\/50 { background-color: rgba(15, 15, 15, 0.5); } -.dark .dark\:bg-neutral-950 { background-color: var(--bg-primary); } -.dark .dark\:bg-emerald-900\/50 { background-color: var(--bg-secondary); } -.dark .dark\:bg-emerald-950\/30 { background-color: var(--bg-secondary); } -.dark .dark\:bg-yellow-900\/50 { background-color: var(--bg-secondary); } -.dark .dark\:bg-yellow-950\/20 { background-color: var(--bg-secondary); } -.dark .dark\:bg-yellow-950\/30 { background-color: var(--bg-secondary); } -.dark .dark\:bg-red-900\/50 { background-color: var(--bg-secondary); } -.dark .dark\:bg-red-950\/30 { background-color: var(--bg-secondary); } -.dark .dark\:bg-blue-900\/50 { background-color: var(--bg-secondary); } -.dark .dark\:bg-orange-900\/50 { background-color: var(--bg-secondary); } - -.dark .dark\:text-neutral-100 { color: var(--text-primary); } -.dark .dark\:text-neutral-300 { color: var(--text-secondary); } -.dark .dark\:text-neutral-400 { color: var(--text-secondary); } -.dark .dark\:text-neutral-500 { color: var(--text-tertiary); } -.dark .dark\:text-emerald-300 { color: var(--status-ok); } -.dark .dark\:text-emerald-400 { color: var(--status-ok); } -.dark .dark\:text-yellow-300 { color: var(--status-warn); } -.dark .dark\:text-yellow-400 { color: var(--status-warn); } -.dark .dark\:text-red-400 { color: var(--status-error); } -.dark .dark\:text-blue-300 { color: var(--status-ok); } -.dark .dark\:text-orange-300 { color: var(--status-warn); } - -.dark .dark\:border-neutral-800 { border-color: var(--border-color); } -.dark .dark\:border-emerald-900 { border-color: var(--border-color); } -.dark .dark\:border-yellow-900 { border-color: var(--border-color); } -.dark .dark\:border-red-900 { border-color: var(--border-color); } - -.dark .dark\:divide-neutral-800 > :not([hidden]) ~ :not([hidden]) { border-color: var(--border-color); } - -.dark .dark\:hover\:bg-neutral-800:hover { background-color: #1f1f1f; } -.dark .dark\:hover\:bg-neutral-900\/50:hover { background-color: rgba(15, 15, 15, 0.5); } -.dark .dark\:hover\:text-neutral-100:hover { color: var(--text-primary); } +@media (prefers-color-scheme: dark) { + .dark\:bg-neutral-800 { background-color: var(--bg-tertiary); } + .dark\:bg-neutral-900 { background-color: var(--bg-secondary); } + .dark\:bg-neutral-900\/50 { background-color: rgba(15, 15, 15, 0.5); } + .dark\:bg-neutral-950 { background-color: var(--bg-primary); } + .dark\:bg-emerald-900\/50 { background-color: var(--bg-secondary); } + .dark\:bg-emerald-950\/30 { background-color: var(--bg-secondary); } + .dark\:bg-yellow-900\/50 { background-color: var(--bg-secondary); } + .dark\:bg-yellow-950\/20 { background-color: var(--bg-secondary); } + .dark\:bg-yellow-950\/30 { background-color: var(--bg-secondary); } + .dark\:bg-red-900\/50 { background-color: var(--bg-secondary); } + .dark\:bg-red-950\/30 { background-color: var(--bg-secondary); } + .dark\:bg-blue-900\/50 { background-color: var(--bg-secondary); } + .dark\:bg-orange-900\/50 { background-color: var(--bg-secondary); } + + .dark\:text-neutral-100 { color: var(--text-primary); } + .dark\:text-neutral-300 { color: var(--text-secondary); } + .dark\:text-neutral-400 { color: var(--text-secondary); } + .dark\:text-neutral-500 { color: var(--text-tertiary); } + .dark\:text-emerald-300 { color: var(--status-ok); } + .dark\:text-emerald-400 { color: var(--status-ok); } + .dark\:text-yellow-300 { color: var(--status-warn); } + .dark\:text-yellow-400 { color: var(--status-warn); } + .dark\:text-red-400 { color: var(--status-error); } + .dark\:text-blue-300 { color: var(--status-ok); } + .dark\:text-orange-300 { color: var(--status-warn); } + + .dark\:border-neutral-800 { border-color: var(--border-color); } + .dark\:border-emerald-900 { border-color: var(--border-color); } + .dark\:border-yellow-900 { border-color: var(--border-color); } + .dark\:border-red-900 { border-color: var(--border-color); } + + .dark\:divide-neutral-800 > :not([hidden]) ~ :not([hidden]) { border-color: var(--border-color); } + + .dark\:hover\:bg-neutral-800:hover { background-color: #1f1f1f; } + .dark\:hover\:bg-neutral-900\/50:hover { background-color: rgba(15, 15, 15, 0.5); } + .dark\:hover\:text-neutral-100:hover { color: var(--text-primary); } +} /* Display */ .block { display: block; } .hidden { display: none; } .flex { display: flex; } -.dark .dark\:block { display: block; } -.dark .dark\:hidden { display: none; } +@media (prefers-color-scheme: dark) { + .dark\:block { display: block; } + .dark\:hidden { display: none; } +} /* Flexbox */ .flex-1 { flex: 1 1 0%; } diff --git a/internal/server/templates/index.html b/internal/server/templates/index.html index 1ba3177..9d98b44 100644 --- a/internal/server/templates/index.html +++ b/internal/server/templates/index.html @@ -1,13 +1,6 @@ <!DOCTYPE html> -<html lang="en" class="dark"> +<html lang="en"> <head> - <script> - // Theme detection - runs immediately before any rendering - // Default is dark (set on html element above), switch to light only if explicitly set - if (localStorage.theme === 'light' || (localStorage.theme !== 'dark' && window.matchMedia('(prefers-color-scheme: light)').matches)) { - document.documentElement.classList.remove('dark'); - } - </script> {{if .ThemeCSS}} <style> /* OpenCode Theme - Loaded before external CSS to prevent flash */ @@ -39,16 +32,7 @@ <p class="text-sm text-neutral-500 dark:text-neutral-400">{{.Site.Description}}</p> </div> </div> - {{if .ShowThemeToggle}} - <button onclick="toggleTheme()" class="p-2 rounded-md hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors" aria-label="Toggle theme"> - <svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24"> - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/> - </svg> - <svg class="w-5 h-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"> - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/> - </svg> - </button> - {{end}} + </div> </header> @@ -210,16 +194,6 @@ <div id="tooltip" class="tooltip"></div> <script> - function toggleTheme() { - if (document.documentElement.classList.contains('dark')) { - document.documentElement.classList.remove('dark'); - localStorage.theme = 'light'; - } else { - document.documentElement.classList.add('dark'); - localStorage.theme = 'dark'; - } - } - // Group collapse/expand functionality function toggleGroup(groupName) { const content = document.querySelector('[data-group-content="' + groupName + '"]'); diff --git a/internal/theme/theme.go b/internal/theme/theme.go index 691afa0..1fc6600 100644 --- a/internal/theme/theme.go +++ b/internal/theme/theme.go @@ -117,8 +117,8 @@ func (t *ResolvedTheme) GenerateCSS() string { var css strings.Builder - // Generate CSS variables for dark mode - css.WriteString(":root.dark {\n") + // Generate CSS variables for dark mode (default) + css.WriteString(":root {\n") for key, color := range t.Dark { cssVar := toCSSVariableName(key) css.WriteString(fmt.Sprintf(" %s: %s;\n", cssVar, color)) @@ -126,12 +126,12 @@ func (t *ResolvedTheme) GenerateCSS() string { css.WriteString("}\n\n") // Generate CSS variables for light mode - css.WriteString(":root, :root.light {\n") + css.WriteString("@media (prefers-color-scheme: light) {\n :root {\n") for key, color := range t.Light { cssVar := toCSSVariableName(key) - css.WriteString(fmt.Sprintf(" %s: %s;\n", cssVar, color)) + css.WriteString(fmt.Sprintf(" %s: %s;\n", cssVar, color)) } - css.WriteString("}\n") + css.WriteString(" }\n}\n") return css.String() } @@ -195,8 +195,8 @@ func (t *ResolvedTheme) GenerateVariableOverrides() string { /* OpenCode Theme - Override Kaze CSS Variables */ /* Uses !important to prevent flash when external CSS loads */ -/* Light mode - uses theme's light colors */ -:root, :root.light { +/* Dark mode (default) - uses theme's dark colors */ +:root { /* Background colors */ --bg-primary: var(--theme-background) !important; --bg-secondary: var(--theme-background-panel) !important; @@ -218,27 +218,29 @@ func (t *ResolvedTheme) GenerateVariableOverrides() string { --status-unknown: var(--theme-text-muted) !important; } -/* Dark mode - uses theme's dark colors */ -:root.dark { - /* Background colors */ - --bg-primary: var(--theme-background) !important; - --bg-secondary: var(--theme-background-panel) !important; - --bg-tertiary: var(--theme-background-element) !important; - - /* Border color */ - --border-color: var(--theme-border) !important; - - /* Text colors */ - --text-primary: var(--theme-text) !important; - --text-secondary: var(--theme-text-muted) !important; - --text-tertiary: var(--theme-text-muted) !important; - --text-dim: var(--theme-border) !important; - - /* Status colors */ - --status-ok: var(--theme-success) !important; - --status-warn: var(--theme-warning) !important; - --status-error: var(--theme-error) !important; - --status-unknown: var(--theme-text-muted) !important; +/* Light mode - uses theme's light colors */ +@media (prefers-color-scheme: light) { + :root { + /* Background colors */ + --bg-primary: var(--theme-background) !important; + --bg-secondary: var(--theme-background-panel) !important; + --bg-tertiary: var(--theme-background-element) !important; + + /* Border color */ + --border-color: var(--theme-border) !important; + + /* Text colors */ + --text-primary: var(--theme-text) !important; + --text-secondary: var(--theme-text-muted) !important; + --text-tertiary: var(--theme-text-muted) !important; + --text-dim: var(--theme-border) !important; + + /* Status colors */ + --status-ok: var(--theme-success) !important; + --status-warn: var(--theme-warning) !important; + --status-error: var(--theme-error) !important; + --status-unknown: var(--theme-text-muted) !important; + } } ` } |