package main import ( "os" "path/filepath" "strings" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" ) // Theme represents a color theme type Theme struct { Name string // UI colors Base lipgloss.Color Surface lipgloss.Color Overlay lipgloss.Color Text lipgloss.Color Subtext lipgloss.Color Accent lipgloss.Color Blue lipgloss.Color Teal lipgloss.Color Green lipgloss.Color Yellow lipgloss.Color Orange lipgloss.Color Red lipgloss.Color Purple lipgloss.Color // For code highlighting ChromaStyle string GlamourStyle string LineNumColor string } var ( // LightTheme for light terminal backgrounds LightTheme = Theme{ Name: "light", Base: lipgloss.Color("#f5f5f5"), Surface: lipgloss.Color("#e8e8e8"), Overlay: lipgloss.Color("#c0c0c0"), Text: lipgloss.Color("#1a1a1a"), Subtext: lipgloss.Color("#505050"), Accent: lipgloss.Color("#6366f1"), Blue: lipgloss.Color("#2563eb"), Teal: lipgloss.Color("#0d9488"), Green: lipgloss.Color("#16a34a"), Yellow: lipgloss.Color("#ca8a04"), Orange: lipgloss.Color("#ea580c"), Red: lipgloss.Color("#dc2626"), Purple: lipgloss.Color("#9333ea"), ChromaStyle: "github", GlamourStyle: "light", LineNumColor: "#888888", } // DarkTheme for dark terminal backgrounds DarkTheme = Theme{ Name: "dark", Base: lipgloss.Color("#1e1e2e"), Surface: lipgloss.Color("#313244"), Overlay: lipgloss.Color("#45475a"), Text: lipgloss.Color("#cdd6f4"), Subtext: lipgloss.Color("#a6adc8"), Accent: lipgloss.Color("#b4befe"), Blue: lipgloss.Color("#89b4fa"), Teal: lipgloss.Color("#94e2d5"), Green: lipgloss.Color("#a6e3a1"), Yellow: lipgloss.Color("#f9e2af"), Orange: lipgloss.Color("#fab387"), Red: lipgloss.Color("#f38ba8"), Purple: lipgloss.Color("#cba6f7"), ChromaStyle: "catppuccin-mocha", GlamourStyle: "dark", LineNumColor: "#6c7086", } // CurrentTheme is the active theme CurrentTheme = LightTheme ) // DetectTheme tries to detect if terminal has dark or light background func DetectTheme() Theme { // Check for config file override first if theme := loadThemePreference(); theme != "" { if theme == "dark" { return DarkTheme } return LightTheme } // Auto-detect from terminal if termenv.NewOutput(os.Stdout).HasDarkBackground() { return DarkTheme } return LightTheme } // SetTheme sets the current theme and updates all styles func SetTheme(t Theme) { CurrentTheme = t updateStyles() } // loadThemePreference loads theme from config file func loadThemePreference() string { configDir, err := os.UserConfigDir() if err != nil { return "" } configPath := filepath.Join(configDir, "reader", "theme") data, err := os.ReadFile(configPath) if err != nil { return "" } return strings.TrimSpace(string(data)) } // SaveThemePreference saves theme preference to config file func SaveThemePreference(theme string) error { configDir, err := os.UserConfigDir() if err != nil { return err } readerDir := filepath.Join(configDir, "reader") if err := os.MkdirAll(readerDir, 0755); err != nil { return err } configPath := filepath.Join(readerDir, "theme") return os.WriteFile(configPath, []byte(theme), 0644) } // updateStyles updates all the lipgloss styles based on current theme func updateStyles() { t := CurrentTheme activeBorderStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(t.Accent) inactiveBorderStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(t.Overlay) directoryStyle = lipgloss.NewStyle(). Foreground(t.Blue). Bold(true) fileStyle = lipgloss.NewStyle(). Foreground(t.Text) selectedStyle = lipgloss.NewStyle(). Background(t.Surface). Foreground(t.Accent). Bold(true) statusBarStyle = lipgloss.NewStyle(). Background(t.Surface). Foreground(t.Subtext). Padding(0, 1) statusKeyStyle = lipgloss.NewStyle(). Background(t.Overlay). Foreground(t.Text). Padding(0, 1) helpStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(t.Purple). Padding(1, 2) helpTitleStyle = lipgloss.NewStyle(). Foreground(t.Purple). Bold(true). MarginBottom(1) helpKeyStyle = lipgloss.NewStyle(). Foreground(t.Yellow). Width(12) helpDescStyle = lipgloss.NewStyle(). Foreground(t.Subtext) titleStyle = lipgloss.NewStyle(). Foreground(t.Accent). Bold(true). Padding(0, 1) }