package tui import ( "io" "os" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" "proxyctl/internal/target" ) type semanticTheme struct { title lipgloss.Style section lipgloss.Style selected lipgloss.Style status lipgloss.Style warning lipgloss.Style enabled lipgloss.Style disabled lipgloss.Style external lipgloss.Style drifted lipgloss.Style unavailable lipgloss.Style checking lipgloss.Style checkFailed lipgloss.Style applying lipgloss.Style } func newSemanticTheme(mode themeMode, output io.Writer) semanticTheme { if output == nil { output = os.Stdout } renderer := lipgloss.NewRenderer(output) explicit := mode != themeModeAuto if _, noColor := os.LookupEnv("NO_COLOR"); noColor && mode == themeModeAuto { mode = themeModeNoColor explicit = true } if mode == themeModeAuto { if renderer.ColorProfile() == termenv.Ascii { mode = themeModeNoColor } else if renderer.HasDarkBackground() { mode = themeModeDark } else { mode = themeModeLight } } if mode == themeModeNoColor { renderer.SetColorProfile(termenv.Ascii) } else if explicit { // Explicit modes make fixed-size render tests deterministic. Production // auto mode retains capabilities detected from the injected output. renderer.SetColorProfile(termenv.ANSI256) } foreground, subtle, accent := "252", "245", "75" if mode == themeModeLight { foreground, subtle, accent = "235", "242", "25" } style := renderer.NewStyle return semanticTheme{ title: style().Bold(true).Foreground(lipgloss.Color(accent)), section: style().Bold(true).Foreground(lipgloss.Color(foreground)), selected: style().Bold(true).Underline(true).Foreground(lipgloss.Color(accent)), status: style().Foreground(lipgloss.Color(foreground)), warning: style().Bold(true).Foreground(lipgloss.Color("172")), enabled: style().Foreground(lipgloss.Color("34")), disabled: style().Foreground(lipgloss.Color(subtle)), external: style().Foreground(lipgloss.Color("172")), drifted: style().Bold(true).Foreground(lipgloss.Color("160")), unavailable: style().Foreground(lipgloss.Color(subtle)), checking: style().Foreground(lipgloss.Color(accent)), checkFailed: style().Bold(true).Foreground(lipgloss.Color("160")), applying: style().Foreground(lipgloss.Color(accent)), } } func (t semanticTheme) renderTargetState(state target.State, applying bool) string { if applying { return t.applying.Render("… Applying") } label := targetStateLabel(state) switch state { case target.Enabled: return t.enabled.Render(label) case target.Disabled: return t.disabled.Render(label) case target.External: return t.external.Render(label) case target.Drifted: return t.drifted.Render(label) default: return label } } func targetStateLabel(state target.State) string { switch state { case target.Enabled: return "● Enabled" case target.Disabled: return "○ Disabled" case target.External: return "◇ External" case target.Drifted: return "! Drifted" default: return "? Unknown" } }