mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
named color themes
This commit is contained in:
parent
9eee3ea019
commit
9e40a0f56b
12 changed files with 859 additions and 126 deletions
|
|
@ -100,16 +100,15 @@ Backlog:
|
||||||
|
|
||||||
# Appearance settings
|
# Appearance settings
|
||||||
appearance:
|
appearance:
|
||||||
theme: auto # Theme: "auto" (detect from terminal), "dark", "light"
|
theme: auto # Theme: "auto" (detect from terminal), "dark", "light",
|
||||||
|
# or a named theme: "dracula", "tokyo-night", "gruvbox-dark",
|
||||||
|
# "catppuccin-mocha", "solarized-dark", "nord", "monokai",
|
||||||
|
# "one-dark", "catppuccin-latte", "solarized-light",
|
||||||
|
# "gruvbox-light", "github-light"
|
||||||
gradientThreshold: 256 # Minimum terminal colors for gradient rendering
|
gradientThreshold: 256 # Minimum terminal colors for gradient rendering
|
||||||
# Options: 16, 256, 16777216 (truecolor)
|
# Options: 16, 256, 16777216 (truecolor)
|
||||||
# Gradients disabled if terminal has fewer colors
|
# Gradients disabled if terminal has fewer colors
|
||||||
# Default: 256 (works well on most terminals)
|
# Default: 256 (works well on most terminals)
|
||||||
codeBlock:
|
|
||||||
theme: dracula # Chroma syntax theme for code blocks
|
|
||||||
# Examples: "dracula", "monokai", "catppuccin-macchiato"
|
|
||||||
background: "#282a36" # Code block background color (hex or ANSI e.g. "236")
|
|
||||||
border: "#6272a4" # Code block border color (hex or ANSI e.g. "244")
|
|
||||||
|
|
||||||
# AI agent integration
|
# AI agent integration
|
||||||
ai:
|
ai:
|
||||||
|
|
|
||||||
BIN
assets/light.png
Normal file
BIN
assets/light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
127
config/colors.go
127
config/colors.go
|
|
@ -2,10 +2,6 @@ package config
|
||||||
|
|
||||||
// Color and style definitions for the UI: gradients, unified Color values.
|
// Color and style definitions for the UI: gradients, unified Color values.
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Gradient defines a start and end RGB color for a gradient transition
|
// Gradient defines a start and end RGB color for a gradient transition
|
||||||
type Gradient struct {
|
type Gradient struct {
|
||||||
Start [3]int // R, G, B (0-255)
|
Start [3]int // R, G, B (0-255)
|
||||||
|
|
@ -149,98 +145,13 @@ type Palette struct {
|
||||||
// Content area
|
// Content area
|
||||||
ContentBackgroundColor Color // canvas background (transparent/default — inherits terminal bg)
|
ContentBackgroundColor Color // canvas background (transparent/default — inherits terminal bg)
|
||||||
|
|
||||||
// Statusline (Nord palette)
|
// Statusline
|
||||||
NordPolarNight1 Color // #2e3440
|
StatuslineDarkBg Color // darkest statusline background (accent foreground)
|
||||||
NordPolarNight2 Color // #3b4252
|
StatuslineMidBg Color // mid statusline background (info/error/fill)
|
||||||
NordPolarNight3 Color // #434c5e
|
StatuslineBorderBg Color // statusline main background + deps editor background
|
||||||
NordSnowStorm1 Color // #d8dee9
|
StatuslineText Color // statusline primary text
|
||||||
NordFrostBlue Color // #5e81ac
|
StatuslineAccent Color // statusline accent background
|
||||||
NordAuroraGreen Color // #a3be8c
|
StatuslineOk Color // statusline info/success foreground
|
||||||
}
|
|
||||||
|
|
||||||
// DarkPalette returns the color palette for dark backgrounds.
|
|
||||||
func DarkPalette() Palette {
|
|
||||||
return Palette{
|
|
||||||
HighlightColor: NewColorHex("#ffff00"),
|
|
||||||
TextColor: NewColorHex("#ffffff"),
|
|
||||||
TransparentColor: DefaultColor(),
|
|
||||||
MutedColor: NewColorHex("#686868"),
|
|
||||||
SoftBorderColor: NewColorHex("#686868"),
|
|
||||||
SoftTextColor: NewColorHex("#b4b4b4"),
|
|
||||||
AccentColor: NewColor(tcell.ColorGreen),
|
|
||||||
ValueColor: NewColorHex("#8c92ac"),
|
|
||||||
InfoLabelColor: NewColorHex("#ffa500"),
|
|
||||||
|
|
||||||
SelectionBgColor: NewColorHex("#3a5f8a"),
|
|
||||||
|
|
||||||
AccentBlue: NewColorHex("#5fafff"),
|
|
||||||
SlateColor: NewColorHex("#5f6982"),
|
|
||||||
|
|
||||||
LogoDotColor: NewColorHex("#40e0d0"),
|
|
||||||
LogoShadeColor: NewColorHex("#4682b4"),
|
|
||||||
LogoBorderColor: NewColorHex("#324664"),
|
|
||||||
|
|
||||||
CaptionFallbackGradient: Gradient{
|
|
||||||
Start: [3]int{25, 25, 112},
|
|
||||||
End: [3]int{65, 105, 225},
|
|
||||||
},
|
|
||||||
DeepSkyBlue: NewColorRGB(0, 191, 255),
|
|
||||||
DeepPurple: NewColorRGB(134, 90, 214),
|
|
||||||
|
|
||||||
ContentBackgroundColor: DefaultColor(), // transparent — inherit terminal background
|
|
||||||
|
|
||||||
NordPolarNight1: NewColorHex("#2e3440"),
|
|
||||||
NordPolarNight2: NewColorHex("#3b4252"),
|
|
||||||
NordPolarNight3: NewColorHex("#434c5e"),
|
|
||||||
NordSnowStorm1: NewColorHex("#d8dee9"),
|
|
||||||
NordFrostBlue: NewColorHex("#5e81ac"),
|
|
||||||
NordAuroraGreen: NewColorHex("#a3be8c"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LightPalette returns the color palette for light backgrounds.
|
|
||||||
func LightPalette() Palette {
|
|
||||||
return Palette{
|
|
||||||
HighlightColor: NewColorHex("#0055dd"), // vivid blue — accents, focus markers, key bindings
|
|
||||||
TextColor: NewColor(tcell.ColorBlack),
|
|
||||||
TransparentColor: DefaultColor(),
|
|
||||||
MutedColor: NewColorHex("#808080"), // medium gray — de-emphasized text, placeholders
|
|
||||||
SoftBorderColor: NewColorHex("#d8dee9"), // light blue-gray — unselected box borders recede on light bg
|
|
||||||
SoftTextColor: NewColorHex("#404040"), // dark gray — secondary readable text
|
|
||||||
AccentColor: NewColorHex("#006400"), // dark green — labels
|
|
||||||
ValueColor: NewColorHex("#4a4e6a"), // dark cool gray — field values
|
|
||||||
InfoLabelColor: NewColorHex("#b85c00"), // darker orange — header view name
|
|
||||||
|
|
||||||
SelectionBgColor: NewColorHex("#b8d4f0"), // light blue — selection background
|
|
||||||
|
|
||||||
AccentBlue: NewColorHex("#0060c0"), // darker blue — action keys, points bar
|
|
||||||
SlateColor: NewColorHex("#7080a0"), // blue-gray — tag values, unfilled bar segments
|
|
||||||
|
|
||||||
LogoDotColor: NewColorHex("#20a090"), // darker turquoise
|
|
||||||
LogoShadeColor: NewColorHex("#3060a0"), // medium blue
|
|
||||||
LogoBorderColor: NewColorHex("#6080a0"), // lighter blue-gray (visible on light bg)
|
|
||||||
|
|
||||||
CaptionFallbackGradient: Gradient{
|
|
||||||
Start: [3]int{100, 140, 200},
|
|
||||||
End: [3]int{60, 100, 180},
|
|
||||||
},
|
|
||||||
DeepSkyBlue: NewColorRGB(0, 100, 180),
|
|
||||||
DeepPurple: NewColorRGB(90, 50, 160),
|
|
||||||
|
|
||||||
ContentBackgroundColor: DefaultColor(), // transparent — inherit terminal background
|
|
||||||
|
|
||||||
NordPolarNight1: NewColorHex("#eceff4"), // inverted: light background
|
|
||||||
NordPolarNight2: NewColorHex("#e5e9f0"),
|
|
||||||
NordPolarNight3: NewColorHex("#d8dee9"),
|
|
||||||
NordSnowStorm1: NewColorHex("#2e3440"), // inverted: dark text
|
|
||||||
NordFrostBlue: NewColorHex("#5e81ac"), // stays — good contrast on light
|
|
||||||
NordAuroraGreen: NewColorHex("#4c7a5a"), // darker green for light bg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultColors returns the default color configuration built from the dark palette.
|
|
||||||
func DefaultColors() *ColorConfig {
|
|
||||||
return ColorsFromPalette(DarkPalette())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// darkenRGB returns a darkened version of an RGB triple. ratio 0 = no change, 1 = black.
|
// darkenRGB returns a darkened version of an RGB triple. ratio 0 = no change, 1 = black.
|
||||||
|
|
@ -345,7 +256,7 @@ func ColorsFromPalette(p Palette) *ColorConfig {
|
||||||
HeaderActionViewLabelColor: p.MutedColor,
|
HeaderActionViewLabelColor: p.MutedColor,
|
||||||
|
|
||||||
// Plugin-specific
|
// Plugin-specific
|
||||||
DepsEditorBackground: p.NordPolarNight3,
|
DepsEditorBackground: p.StatuslineBorderBg,
|
||||||
|
|
||||||
// Fallback solid colors
|
// Fallback solid colors
|
||||||
FallbackTaskIDColor: p.DeepSkyBlue,
|
FallbackTaskIDColor: p.DeepSkyBlue,
|
||||||
|
|
@ -357,15 +268,15 @@ func ColorsFromPalette(p Palette) *ColorConfig {
|
||||||
LogoBorderColor: p.LogoBorderColor,
|
LogoBorderColor: p.LogoBorderColor,
|
||||||
|
|
||||||
// Statusline
|
// Statusline
|
||||||
StatuslineBg: p.NordPolarNight3,
|
StatuslineBg: p.StatuslineBorderBg,
|
||||||
StatuslineFg: p.NordSnowStorm1,
|
StatuslineFg: p.StatuslineText,
|
||||||
StatuslineAccentBg: p.NordFrostBlue,
|
StatuslineAccentBg: p.StatuslineAccent,
|
||||||
StatuslineAccentFg: p.NordPolarNight1,
|
StatuslineAccentFg: p.StatuslineDarkBg,
|
||||||
StatuslineInfoFg: p.NordAuroraGreen,
|
StatuslineInfoFg: p.StatuslineOk,
|
||||||
StatuslineInfoBg: p.NordPolarNight2,
|
StatuslineInfoBg: p.StatuslineMidBg,
|
||||||
StatuslineErrorFg: p.HighlightColor,
|
StatuslineErrorFg: p.HighlightColor,
|
||||||
StatuslineErrorBg: p.NordPolarNight2,
|
StatuslineErrorBg: p.StatuslineMidBg,
|
||||||
StatuslineFillBg: p.NordPolarNight2,
|
StatuslineFillBg: p.StatuslineMidBg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,11 +295,7 @@ var UseWideGradients bool
|
||||||
// GetColors returns the global color configuration for the effective theme
|
// GetColors returns the global color configuration for the effective theme
|
||||||
func GetColors() *ColorConfig {
|
func GetColors() *ColorConfig {
|
||||||
if !colorsInitialized {
|
if !colorsInitialized {
|
||||||
if GetEffectiveTheme() == "light" {
|
globalColors = ColorsFromPalette(PaletteForTheme())
|
||||||
globalColors = ColorsFromPalette(LightPalette())
|
|
||||||
} else {
|
|
||||||
globalColors = ColorsFromPalette(DarkPalette())
|
|
||||||
}
|
|
||||||
colorsInitialized = true
|
colorsInitialized = true
|
||||||
}
|
}
|
||||||
return globalColors
|
return globalColors
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ type Config struct {
|
||||||
|
|
||||||
// Appearance configuration
|
// Appearance configuration
|
||||||
Appearance struct {
|
Appearance struct {
|
||||||
Theme string `mapstructure:"theme"` // "dark", "light", "auto"
|
Theme string `mapstructure:"theme"` // "auto", "dark", "light", or a named theme (see ThemeNames())
|
||||||
GradientThreshold int `mapstructure:"gradientThreshold"` // Minimum color count for gradients (16, 256, 16777216)
|
GradientThreshold int `mapstructure:"gradientThreshold"` // Minimum color count for gradients (16, 256, 16777216)
|
||||||
CodeBlock struct {
|
CodeBlock struct {
|
||||||
Theme string `mapstructure:"theme"` // chroma syntax theme (e.g. "dracula", "monokai")
|
Theme string `mapstructure:"theme"` // chroma syntax theme (e.g. "dracula", "monokai")
|
||||||
|
|
@ -390,15 +390,12 @@ func GetGradientThreshold() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCodeBlockTheme returns the chroma syntax highlighting theme for code blocks.
|
// GetCodeBlockTheme returns the chroma syntax highlighting theme for code blocks.
|
||||||
// defaults to "nord" (dark) or "github" (light) when not explicitly configured.
|
// Defaults to the theme registry's chroma mapping when not explicitly configured.
|
||||||
func GetCodeBlockTheme() string {
|
func GetCodeBlockTheme() string {
|
||||||
if t := viper.GetString("appearance.codeBlock.theme"); t != "" {
|
if t := viper.GetString("appearance.codeBlock.theme"); t != "" {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
if GetEffectiveTheme() == "light" {
|
return ChromaThemeForEffective()
|
||||||
return "github"
|
|
||||||
}
|
|
||||||
return "nord"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCodeBlockBackground returns the background color for code blocks
|
// GetCodeBlockBackground returns the background color for code blocks
|
||||||
|
|
|
||||||
582
config/palettes.go
Normal file
582
config/palettes.go
Normal file
|
|
@ -0,0 +1,582 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
// Palette constructors for all built-in and named themes.
|
||||||
|
// Each function returns a Palette with canonical hex values from the theme's specification.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DarkPalette returns the color palette for dark backgrounds.
|
||||||
|
func DarkPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#ffff00"),
|
||||||
|
TextColor: NewColorHex("#ffffff"),
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#686868"),
|
||||||
|
SoftBorderColor: NewColorHex("#686868"),
|
||||||
|
SoftTextColor: NewColorHex("#b4b4b4"),
|
||||||
|
AccentColor: NewColor(tcell.ColorGreen),
|
||||||
|
ValueColor: NewColorHex("#8c92ac"),
|
||||||
|
InfoLabelColor: NewColorHex("#ffa500"),
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#3a5f8a"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#5fafff"),
|
||||||
|
SlateColor: NewColorHex("#5f6982"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#40e0d0"),
|
||||||
|
LogoShadeColor: NewColorHex("#4682b4"),
|
||||||
|
LogoBorderColor: NewColorHex("#324664"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{25, 25, 112},
|
||||||
|
End: [3]int{65, 105, 225},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorRGB(0, 191, 255),
|
||||||
|
DeepPurple: NewColorRGB(134, 90, 214),
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#2e3440"),
|
||||||
|
StatuslineMidBg: NewColorHex("#3b4252"),
|
||||||
|
StatuslineBorderBg: NewColorHex("#434c5e"),
|
||||||
|
StatuslineText: NewColorHex("#d8dee9"),
|
||||||
|
StatuslineAccent: NewColorHex("#5e81ac"),
|
||||||
|
StatuslineOk: NewColorHex("#a3be8c"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LightPalette returns the color palette for light backgrounds.
|
||||||
|
func LightPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#0055dd"),
|
||||||
|
TextColor: NewColor(tcell.ColorBlack),
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#808080"),
|
||||||
|
SoftBorderColor: NewColorHex("#b0b8c8"),
|
||||||
|
SoftTextColor: NewColorHex("#404040"),
|
||||||
|
AccentColor: NewColorHex("#006400"),
|
||||||
|
ValueColor: NewColorHex("#4a4e6a"),
|
||||||
|
InfoLabelColor: NewColorHex("#b85c00"),
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#b8d4f0"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#0060c0"),
|
||||||
|
SlateColor: NewColorHex("#7080a0"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#20a090"),
|
||||||
|
LogoShadeColor: NewColorHex("#3060a0"),
|
||||||
|
LogoBorderColor: NewColorHex("#6080a0"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{100, 140, 200},
|
||||||
|
End: [3]int{60, 100, 180},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorRGB(0, 100, 180),
|
||||||
|
DeepPurple: NewColorRGB(90, 50, 160),
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#eceff4"),
|
||||||
|
StatuslineMidBg: NewColorHex("#e5e9f0"),
|
||||||
|
StatuslineBorderBg: NewColorHex("#d8dee9"),
|
||||||
|
StatuslineText: NewColorHex("#2e3440"),
|
||||||
|
StatuslineAccent: NewColorHex("#5e81ac"),
|
||||||
|
StatuslineOk: NewColorHex("#4c7a5a"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DraculaPalette returns the Dracula theme palette.
|
||||||
|
// Ref: https://draculatheme.com/contribute
|
||||||
|
func DraculaPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#ff79c6"), // pink
|
||||||
|
TextColor: NewColorHex("#f8f8f2"), // foreground
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#6272a4"), // comment
|
||||||
|
SoftBorderColor: NewColorHex("#44475a"), // current line
|
||||||
|
SoftTextColor: NewColorHex("#bfbfbf"),
|
||||||
|
AccentColor: NewColorHex("#50fa7b"), // green
|
||||||
|
ValueColor: NewColorHex("#bd93f9"), // purple
|
||||||
|
InfoLabelColor: NewColorHex("#ffb86c"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#44475a"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#8be9fd"), // cyan
|
||||||
|
SlateColor: NewColorHex("#6272a4"), // comment
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#8be9fd"),
|
||||||
|
LogoShadeColor: NewColorHex("#bd93f9"),
|
||||||
|
LogoBorderColor: NewColorHex("#44475a"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{40, 42, 54},
|
||||||
|
End: [3]int{68, 71, 90},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#8be9fd"),
|
||||||
|
DeepPurple: NewColorHex("#bd93f9"),
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#21222c"),
|
||||||
|
StatuslineMidBg: NewColorHex("#282a36"),
|
||||||
|
StatuslineBorderBg: NewColorHex("#44475a"),
|
||||||
|
StatuslineText: NewColorHex("#f8f8f2"),
|
||||||
|
StatuslineAccent: NewColorHex("#bd93f9"),
|
||||||
|
StatuslineOk: NewColorHex("#50fa7b"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokyoNightPalette returns the Tokyo Night theme palette.
|
||||||
|
// Ref: https://github.com/folke/tokyonight.nvim
|
||||||
|
func TokyoNightPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#e0af68"), // yellow
|
||||||
|
TextColor: NewColorHex("#c0caf5"), // foreground
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#565f89"), // comment
|
||||||
|
SoftBorderColor: NewColorHex("#3b4261"),
|
||||||
|
SoftTextColor: NewColorHex("#a9b1d6"),
|
||||||
|
AccentColor: NewColorHex("#9ece6a"), // green
|
||||||
|
ValueColor: NewColorHex("#7aa2f7"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#ff9e64"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#283457"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#7aa2f7"),
|
||||||
|
SlateColor: NewColorHex("#565f89"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#7dcfff"),
|
||||||
|
LogoShadeColor: NewColorHex("#7aa2f7"),
|
||||||
|
LogoBorderColor: NewColorHex("#3b4261"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{26, 27, 38},
|
||||||
|
End: [3]int{59, 66, 97},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#7dcfff"),
|
||||||
|
DeepPurple: NewColorHex("#bb9af7"),
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#16161e"),
|
||||||
|
StatuslineMidBg: NewColorHex("#1a1b26"),
|
||||||
|
StatuslineBorderBg: NewColorHex("#24283b"),
|
||||||
|
StatuslineText: NewColorHex("#c0caf5"),
|
||||||
|
StatuslineAccent: NewColorHex("#7aa2f7"),
|
||||||
|
StatuslineOk: NewColorHex("#9ece6a"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GruvboxDarkPalette returns the Gruvbox Dark theme palette.
|
||||||
|
// Ref: https://github.com/morhetz/gruvbox
|
||||||
|
func GruvboxDarkPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#fabd2f"), // yellow
|
||||||
|
TextColor: NewColorHex("#ebdbb2"), // fg
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#928374"), // gray
|
||||||
|
SoftBorderColor: NewColorHex("#504945"), // bg2
|
||||||
|
SoftTextColor: NewColorHex("#bdae93"), // fg3
|
||||||
|
AccentColor: NewColorHex("#b8bb26"), // green
|
||||||
|
ValueColor: NewColorHex("#83a598"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#fe8019"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#504945"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#83a598"),
|
||||||
|
SlateColor: NewColorHex("#665c54"), // bg3
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#8ec07c"), // aqua
|
||||||
|
LogoShadeColor: NewColorHex("#83a598"),
|
||||||
|
LogoBorderColor: NewColorHex("#3c3836"), // bg1
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{40, 40, 40},
|
||||||
|
End: [3]int{80, 73, 69},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#83a598"),
|
||||||
|
DeepPurple: NewColorHex("#d3869b"), // purple
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#1d2021"), // bg0_h
|
||||||
|
StatuslineMidBg: NewColorHex("#282828"), // bg0
|
||||||
|
StatuslineBorderBg: NewColorHex("#3c3836"), // bg1
|
||||||
|
StatuslineText: NewColorHex("#ebdbb2"),
|
||||||
|
StatuslineAccent: NewColorHex("#689d6a"), // dark aqua
|
||||||
|
StatuslineOk: NewColorHex("#b8bb26"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CatppuccinMochaPalette returns the Catppuccin Mocha theme palette.
|
||||||
|
// Ref: https://catppuccin.com/palette
|
||||||
|
func CatppuccinMochaPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#f9e2af"), // yellow
|
||||||
|
TextColor: NewColorHex("#cdd6f4"), // text
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#6c7086"), // overlay0
|
||||||
|
SoftBorderColor: NewColorHex("#45475a"), // surface0
|
||||||
|
SoftTextColor: NewColorHex("#bac2de"), // subtext1
|
||||||
|
AccentColor: NewColorHex("#a6e3a1"), // green
|
||||||
|
ValueColor: NewColorHex("#89b4fa"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#fab387"), // peach
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#45475a"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#89b4fa"),
|
||||||
|
SlateColor: NewColorHex("#585b70"), // surface2
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#94e2d5"), // teal
|
||||||
|
LogoShadeColor: NewColorHex("#89b4fa"),
|
||||||
|
LogoBorderColor: NewColorHex("#313244"), // surface0
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{30, 30, 46},
|
||||||
|
End: [3]int{69, 71, 90},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#89dceb"), // sky
|
||||||
|
DeepPurple: NewColorHex("#cba6f7"), // mauve
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#11111b"), // crust
|
||||||
|
StatuslineMidBg: NewColorHex("#1e1e2e"), // base
|
||||||
|
StatuslineBorderBg: NewColorHex("#313244"), // surface0
|
||||||
|
StatuslineText: NewColorHex("#cdd6f4"),
|
||||||
|
StatuslineAccent: NewColorHex("#89b4fa"),
|
||||||
|
StatuslineOk: NewColorHex("#a6e3a1"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SolarizedDarkPalette returns the Solarized Dark theme palette.
|
||||||
|
// Ref: https://ethanschoonover.com/solarized/
|
||||||
|
func SolarizedDarkPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#b58900"), // yellow
|
||||||
|
TextColor: NewColorHex("#839496"), // base0
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#586e75"), // base01
|
||||||
|
SoftBorderColor: NewColorHex("#073642"), // base02
|
||||||
|
SoftTextColor: NewColorHex("#93a1a1"), // base1
|
||||||
|
AccentColor: NewColorHex("#859900"), // green
|
||||||
|
ValueColor: NewColorHex("#268bd2"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#cb4b16"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#073642"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#268bd2"),
|
||||||
|
SlateColor: NewColorHex("#586e75"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#2aa198"), // cyan
|
||||||
|
LogoShadeColor: NewColorHex("#268bd2"),
|
||||||
|
LogoBorderColor: NewColorHex("#073642"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{0, 43, 54},
|
||||||
|
End: [3]int{7, 54, 66},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#268bd2"),
|
||||||
|
DeepPurple: NewColorHex("#6c71c4"), // violet
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#002b36"), // base03
|
||||||
|
StatuslineMidBg: NewColorHex("#073642"), // base02
|
||||||
|
StatuslineBorderBg: NewColorHex("#073642"),
|
||||||
|
StatuslineText: NewColorHex("#839496"),
|
||||||
|
StatuslineAccent: NewColorHex("#268bd2"),
|
||||||
|
StatuslineOk: NewColorHex("#859900"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NordPalette returns the Nord theme palette.
|
||||||
|
// Ref: https://www.nordtheme.com/docs/colors-and-palettes
|
||||||
|
func NordPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#ebcb8b"), // nord13 — yellow
|
||||||
|
TextColor: NewColorHex("#eceff4"), // nord6 — snow storm
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#4c566a"), // nord3
|
||||||
|
SoftBorderColor: NewColorHex("#434c5e"), // nord2
|
||||||
|
SoftTextColor: NewColorHex("#d8dee9"), // nord4
|
||||||
|
AccentColor: NewColorHex("#a3be8c"), // nord14 — green
|
||||||
|
ValueColor: NewColorHex("#81a1c1"), // nord9 — blue
|
||||||
|
InfoLabelColor: NewColorHex("#d08770"), // nord12 — orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#434c5e"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#88c0d0"), // nord8 — frost cyan
|
||||||
|
SlateColor: NewColorHex("#4c566a"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#8fbcbb"), // nord7 — frost teal
|
||||||
|
LogoShadeColor: NewColorHex("#81a1c1"),
|
||||||
|
LogoBorderColor: NewColorHex("#3b4252"), // nord1
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{46, 52, 64},
|
||||||
|
End: [3]int{59, 66, 82},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#88c0d0"),
|
||||||
|
DeepPurple: NewColorHex("#b48ead"), // nord15 — purple
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#2e3440"), // nord0
|
||||||
|
StatuslineMidBg: NewColorHex("#3b4252"), // nord1
|
||||||
|
StatuslineBorderBg: NewColorHex("#434c5e"), // nord2
|
||||||
|
StatuslineText: NewColorHex("#d8dee9"), // nord4
|
||||||
|
StatuslineAccent: NewColorHex("#5e81ac"), // nord10
|
||||||
|
StatuslineOk: NewColorHex("#a3be8c"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonokaiPalette returns the Monokai theme palette.
|
||||||
|
// Ref: https://monokai.pro/
|
||||||
|
func MonokaiPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#e6db74"), // yellow
|
||||||
|
TextColor: NewColorHex("#f8f8f2"), // foreground
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#75715e"), // comment
|
||||||
|
SoftBorderColor: NewColorHex("#49483e"),
|
||||||
|
SoftTextColor: NewColorHex("#cfcfc2"),
|
||||||
|
AccentColor: NewColorHex("#a6e22e"), // green
|
||||||
|
ValueColor: NewColorHex("#66d9ef"), // cyan
|
||||||
|
InfoLabelColor: NewColorHex("#fd971f"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#49483e"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#66d9ef"),
|
||||||
|
SlateColor: NewColorHex("#75715e"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#a6e22e"),
|
||||||
|
LogoShadeColor: NewColorHex("#66d9ef"),
|
||||||
|
LogoBorderColor: NewColorHex("#3e3d32"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{39, 40, 34},
|
||||||
|
End: [3]int{73, 72, 62},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#66d9ef"),
|
||||||
|
DeepPurple: NewColorHex("#ae81ff"), // purple
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#1e1f1c"),
|
||||||
|
StatuslineMidBg: NewColorHex("#272822"), // bg
|
||||||
|
StatuslineBorderBg: NewColorHex("#3e3d32"),
|
||||||
|
StatuslineText: NewColorHex("#f8f8f2"),
|
||||||
|
StatuslineAccent: NewColorHex("#66d9ef"),
|
||||||
|
StatuslineOk: NewColorHex("#a6e22e"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneDarkPalette returns the Atom One Dark theme palette.
|
||||||
|
// Ref: https://github.com/Binaryify/OneDark-Pro
|
||||||
|
func OneDarkPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#e5c07b"), // yellow
|
||||||
|
TextColor: NewColorHex("#abb2bf"), // foreground
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#5c6370"), // comment
|
||||||
|
SoftBorderColor: NewColorHex("#3e4452"),
|
||||||
|
SoftTextColor: NewColorHex("#9da5b4"),
|
||||||
|
AccentColor: NewColorHex("#98c379"), // green
|
||||||
|
ValueColor: NewColorHex("#61afef"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#d19a66"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#3e4452"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#61afef"),
|
||||||
|
SlateColor: NewColorHex("#5c6370"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#56b6c2"), // cyan
|
||||||
|
LogoShadeColor: NewColorHex("#61afef"),
|
||||||
|
LogoBorderColor: NewColorHex("#3b4048"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{40, 44, 52},
|
||||||
|
End: [3]int{62, 68, 82},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#61afef"),
|
||||||
|
DeepPurple: NewColorHex("#c678dd"), // purple
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#21252b"),
|
||||||
|
StatuslineMidBg: NewColorHex("#282c34"), // bg
|
||||||
|
StatuslineBorderBg: NewColorHex("#3b4048"),
|
||||||
|
StatuslineText: NewColorHex("#abb2bf"),
|
||||||
|
StatuslineAccent: NewColorHex("#61afef"),
|
||||||
|
StatuslineOk: NewColorHex("#98c379"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Light themes ---
|
||||||
|
|
||||||
|
// CatppuccinLattePalette returns the Catppuccin Latte (light) theme palette.
|
||||||
|
// Ref: https://catppuccin.com/palette
|
||||||
|
func CatppuccinLattePalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#df8e1d"), // yellow
|
||||||
|
TextColor: NewColorHex("#4c4f69"), // text
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#9ca0b0"), // overlay0
|
||||||
|
SoftBorderColor: NewColorHex("#ccd0da"), // surface0
|
||||||
|
SoftTextColor: NewColorHex("#5c5f77"), // subtext1
|
||||||
|
AccentColor: NewColorHex("#40a02b"), // green
|
||||||
|
ValueColor: NewColorHex("#1e66f5"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#fe640b"), // peach
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#ccd0da"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#1e66f5"),
|
||||||
|
SlateColor: NewColorHex("#acb0be"), // surface2
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#179299"), // teal
|
||||||
|
LogoShadeColor: NewColorHex("#1e66f5"),
|
||||||
|
LogoBorderColor: NewColorHex("#bcc0cc"), // surface1
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{239, 241, 245},
|
||||||
|
End: [3]int{204, 208, 218},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#04a5e5"), // sky
|
||||||
|
DeepPurple: NewColorHex("#8839ef"), // mauve
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#eff1f5"), // base
|
||||||
|
StatuslineMidBg: NewColorHex("#e6e9ef"), // mantle
|
||||||
|
StatuslineBorderBg: NewColorHex("#dce0e8"), // crust
|
||||||
|
StatuslineText: NewColorHex("#4c4f69"),
|
||||||
|
StatuslineAccent: NewColorHex("#1e66f5"),
|
||||||
|
StatuslineOk: NewColorHex("#40a02b"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SolarizedLightPalette returns the Solarized Light theme palette.
|
||||||
|
// Ref: https://ethanschoonover.com/solarized/
|
||||||
|
func SolarizedLightPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#b58900"), // yellow (same accent colors as dark)
|
||||||
|
TextColor: NewColorHex("#657b83"), // base00
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#93a1a1"), // base1
|
||||||
|
SoftBorderColor: NewColorHex("#eee8d5"), // base2
|
||||||
|
SoftTextColor: NewColorHex("#586e75"), // base01
|
||||||
|
AccentColor: NewColorHex("#859900"), // green
|
||||||
|
ValueColor: NewColorHex("#268bd2"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#cb4b16"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#eee8d5"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#268bd2"),
|
||||||
|
SlateColor: NewColorHex("#93a1a1"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#2aa198"), // cyan
|
||||||
|
LogoShadeColor: NewColorHex("#268bd2"),
|
||||||
|
LogoBorderColor: NewColorHex("#eee8d5"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{253, 246, 227},
|
||||||
|
End: [3]int{238, 232, 213},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#268bd2"),
|
||||||
|
DeepPurple: NewColorHex("#6c71c4"), // violet
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#fdf6e3"), // base3
|
||||||
|
StatuslineMidBg: NewColorHex("#eee8d5"), // base2
|
||||||
|
StatuslineBorderBg: NewColorHex("#eee8d5"),
|
||||||
|
StatuslineText: NewColorHex("#657b83"),
|
||||||
|
StatuslineAccent: NewColorHex("#268bd2"),
|
||||||
|
StatuslineOk: NewColorHex("#859900"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GruvboxLightPalette returns the Gruvbox Light theme palette.
|
||||||
|
// Ref: https://github.com/morhetz/gruvbox
|
||||||
|
func GruvboxLightPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#b57614"), // dark yellow
|
||||||
|
TextColor: NewColorHex("#3c3836"), // fg (dark0_hard)
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#928374"), // gray
|
||||||
|
SoftBorderColor: NewColorHex("#d5c4a1"), // bg2
|
||||||
|
SoftTextColor: NewColorHex("#504945"), // fg3 (dark2)
|
||||||
|
AccentColor: NewColorHex("#79740e"), // dark green
|
||||||
|
ValueColor: NewColorHex("#076678"), // dark blue
|
||||||
|
InfoLabelColor: NewColorHex("#af3a03"), // dark orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#d5c4a1"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#076678"),
|
||||||
|
SlateColor: NewColorHex("#bdae93"), // bg3
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#427b58"), // dark aqua
|
||||||
|
LogoShadeColor: NewColorHex("#076678"),
|
||||||
|
LogoBorderColor: NewColorHex("#ebdbb2"), // bg1
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{251, 241, 199},
|
||||||
|
End: [3]int{235, 219, 178},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#076678"),
|
||||||
|
DeepPurple: NewColorHex("#8f3f71"), // dark purple
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#fbf1c7"), // bg0
|
||||||
|
StatuslineMidBg: NewColorHex("#ebdbb2"), // bg1
|
||||||
|
StatuslineBorderBg: NewColorHex("#d5c4a1"), // bg2
|
||||||
|
StatuslineText: NewColorHex("#3c3836"),
|
||||||
|
StatuslineAccent: NewColorHex("#427b58"),
|
||||||
|
StatuslineOk: NewColorHex("#79740e"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GithubLightPalette returns the GitHub Light theme palette.
|
||||||
|
// Ref: https://github.com/primer/github-vscode-theme
|
||||||
|
func GithubLightPalette() Palette {
|
||||||
|
return Palette{
|
||||||
|
HighlightColor: NewColorHex("#0550ae"), // blue accent
|
||||||
|
TextColor: NewColorHex("#1f2328"), // fg.default
|
||||||
|
TransparentColor: DefaultColor(),
|
||||||
|
MutedColor: NewColorHex("#656d76"), // fg.muted
|
||||||
|
SoftBorderColor: NewColorHex("#d0d7de"), // border.default
|
||||||
|
SoftTextColor: NewColorHex("#424a53"),
|
||||||
|
AccentColor: NewColorHex("#116329"), // green
|
||||||
|
ValueColor: NewColorHex("#0969da"), // blue
|
||||||
|
InfoLabelColor: NewColorHex("#953800"), // orange
|
||||||
|
|
||||||
|
SelectionBgColor: NewColorHex("#ddf4ff"),
|
||||||
|
|
||||||
|
AccentBlue: NewColorHex("#0969da"),
|
||||||
|
SlateColor: NewColorHex("#8c959f"),
|
||||||
|
|
||||||
|
LogoDotColor: NewColorHex("#0969da"),
|
||||||
|
LogoShadeColor: NewColorHex("#0550ae"),
|
||||||
|
LogoBorderColor: NewColorHex("#d0d7de"),
|
||||||
|
|
||||||
|
CaptionFallbackGradient: Gradient{
|
||||||
|
Start: [3]int{255, 255, 255},
|
||||||
|
End: [3]int{246, 248, 250},
|
||||||
|
},
|
||||||
|
DeepSkyBlue: NewColorHex("#0969da"),
|
||||||
|
DeepPurple: NewColorHex("#8250df"), // purple
|
||||||
|
|
||||||
|
ContentBackgroundColor: DefaultColor(),
|
||||||
|
|
||||||
|
StatuslineDarkBg: NewColorHex("#ffffff"),
|
||||||
|
StatuslineMidBg: NewColorHex("#f6f8fa"), // canvas.subtle
|
||||||
|
StatuslineBorderBg: NewColorHex("#eaeef2"),
|
||||||
|
StatuslineText: NewColorHex("#1f2328"),
|
||||||
|
StatuslineAccent: NewColorHex("#0969da"),
|
||||||
|
StatuslineOk: NewColorHex("#116329"),
|
||||||
|
}
|
||||||
|
}
|
||||||
50
config/palettes_test.go
Normal file
50
config/palettes_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAllPalettesHaveNonDefaultCriticalFields(t *testing.T) {
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
p := info.Palette()
|
||||||
|
critical := map[string]Color{
|
||||||
|
"TextColor": p.TextColor,
|
||||||
|
"HighlightColor": p.HighlightColor,
|
||||||
|
"AccentColor": p.AccentColor,
|
||||||
|
"MutedColor": p.MutedColor,
|
||||||
|
"AccentBlue": p.AccentBlue,
|
||||||
|
"InfoLabelColor": p.InfoLabelColor,
|
||||||
|
}
|
||||||
|
for field, c := range critical {
|
||||||
|
if c.IsDefault() {
|
||||||
|
t.Errorf("theme %q: %s is default/transparent", name, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLightPalettesHaveDarkText(t *testing.T) {
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
if !info.Light {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := info.Palette()
|
||||||
|
r, g, b := p.TextColor.RGB()
|
||||||
|
luminance := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
|
||||||
|
if luminance > 160 {
|
||||||
|
t.Errorf("light theme %q: TextColor luminance %.0f is too bright (expected dark text)", name, luminance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDarkPalettesHaveLightText(t *testing.T) {
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
if info.Light {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := info.Palette()
|
||||||
|
r, g, b := p.TextColor.RGB()
|
||||||
|
luminance := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
|
||||||
|
if luminance < 128 {
|
||||||
|
t.Errorf("dark theme %q: TextColor luminance %.0f is too dark (expected light text)", name, luminance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
config/themes.go
Normal file
84
config/themes.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
// Theme registry: maps theme names to palette constructors, dark/light classification,
|
||||||
|
// chroma syntax theme, and navidown markdown renderer style.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ThemeInfo holds all metadata for a named theme.
|
||||||
|
type ThemeInfo struct {
|
||||||
|
Light bool // true = light base, false = dark base
|
||||||
|
ChromaTheme string // chroma syntax theme for code blocks
|
||||||
|
NavidownStyle string // navidown markdown renderer style name
|
||||||
|
Palette func() Palette // palette constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
// themeRegistry maps theme names to their ThemeInfo.
|
||||||
|
// "dark" and "light" are the built-in base themes; named themes extend this.
|
||||||
|
var themeRegistry = map[string]ThemeInfo{
|
||||||
|
// built-in base themes
|
||||||
|
"dark": {Light: false, ChromaTheme: "nord", NavidownStyle: "dark", Palette: DarkPalette},
|
||||||
|
"light": {Light: true, ChromaTheme: "github", NavidownStyle: "light", Palette: LightPalette},
|
||||||
|
|
||||||
|
// named dark themes
|
||||||
|
"dracula": {Light: false, ChromaTheme: "dracula", NavidownStyle: "dracula", Palette: DraculaPalette},
|
||||||
|
"tokyo-night": {Light: false, ChromaTheme: "tokyonight-night", NavidownStyle: "tokyo-night", Palette: TokyoNightPalette},
|
||||||
|
"gruvbox-dark": {Light: false, ChromaTheme: "gruvbox", NavidownStyle: "dark", Palette: GruvboxDarkPalette},
|
||||||
|
"catppuccin-mocha": {Light: false, ChromaTheme: "catppuccin-mocha", NavidownStyle: "dark", Palette: CatppuccinMochaPalette},
|
||||||
|
"solarized-dark": {Light: false, ChromaTheme: "solarized-dark256", NavidownStyle: "dark", Palette: SolarizedDarkPalette},
|
||||||
|
"nord": {Light: false, ChromaTheme: "nord", NavidownStyle: "dark", Palette: NordPalette},
|
||||||
|
"monokai": {Light: false, ChromaTheme: "monokai", NavidownStyle: "dark", Palette: MonokaiPalette},
|
||||||
|
"one-dark": {Light: false, ChromaTheme: "onedark", NavidownStyle: "dark", Palette: OneDarkPalette},
|
||||||
|
|
||||||
|
// named light themes
|
||||||
|
"catppuccin-latte": {Light: true, ChromaTheme: "catppuccin-latte", NavidownStyle: "light", Palette: CatppuccinLattePalette},
|
||||||
|
"solarized-light": {Light: true, ChromaTheme: "solarized-light", NavidownStyle: "light", Palette: SolarizedLightPalette},
|
||||||
|
"gruvbox-light": {Light: true, ChromaTheme: "gruvbox-light", NavidownStyle: "light", Palette: GruvboxLightPalette},
|
||||||
|
"github-light": {Light: true, ChromaTheme: "github", NavidownStyle: "light", Palette: GithubLightPalette},
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultTheme = themeRegistry["dark"]
|
||||||
|
|
||||||
|
// lookupTheme returns the ThemeInfo for the effective theme.
|
||||||
|
// Logs a warning and returns the dark theme for unrecognized names.
|
||||||
|
func lookupTheme() ThemeInfo {
|
||||||
|
name := GetEffectiveTheme()
|
||||||
|
if info, ok := themeRegistry[name]; ok {
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
slog.Warn("unknown theme, falling back to dark", "theme", name)
|
||||||
|
return defaultTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLightTheme returns true if the effective theme has a light background.
|
||||||
|
func IsLightTheme() bool {
|
||||||
|
return lookupTheme().Light
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNavidownStyle returns the navidown markdown renderer style for the effective theme.
|
||||||
|
func GetNavidownStyle() string {
|
||||||
|
return lookupTheme().NavidownStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaletteForTheme returns the Palette for the effective theme.
|
||||||
|
func PaletteForTheme() Palette {
|
||||||
|
return lookupTheme().Palette()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChromaThemeForEffective returns the chroma syntax theme name for the effective theme.
|
||||||
|
func ChromaThemeForEffective() string {
|
||||||
|
return lookupTheme().ChromaTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThemeNames returns a sorted list of all registered theme names.
|
||||||
|
func ThemeNames() []string {
|
||||||
|
names := make([]string, 0, len(themeRegistry))
|
||||||
|
for name := range themeRegistry {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names
|
||||||
|
}
|
||||||
114
config/themes_test.go
Normal file
114
config/themes_test.go
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
chromaStyles "github.com/alecthomas/chroma/v2/styles"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestThemeRegistryComplete(t *testing.T) {
|
||||||
|
names := ThemeNames()
|
||||||
|
if len(names) != 14 {
|
||||||
|
t.Fatalf("expected 14 themes, got %d: %v", len(names), names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestThemeNamesAreSorted(t *testing.T) {
|
||||||
|
names := ThemeNames()
|
||||||
|
for i := 1; i < len(names); i++ {
|
||||||
|
if names[i] < names[i-1] {
|
||||||
|
t.Errorf("ThemeNames() not sorted: %q before %q", names[i-1], names[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllPalettesResolve(t *testing.T) {
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
// calling Palette() must not panic
|
||||||
|
p := info.Palette()
|
||||||
|
if p.TextColor.IsDefault() {
|
||||||
|
t.Errorf("theme %q: TextColor is default/transparent", name)
|
||||||
|
}
|
||||||
|
if p.HighlightColor.IsDefault() {
|
||||||
|
t.Errorf("theme %q: HighlightColor is default/transparent", name)
|
||||||
|
}
|
||||||
|
if p.AccentColor.IsDefault() {
|
||||||
|
t.Errorf("theme %q: AccentColor is default/transparent", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsLightThemeClassification(t *testing.T) {
|
||||||
|
expectedLight := map[string]bool{
|
||||||
|
"dark": false,
|
||||||
|
"light": true,
|
||||||
|
"dracula": false,
|
||||||
|
"tokyo-night": false,
|
||||||
|
"gruvbox-dark": false,
|
||||||
|
"catppuccin-mocha": false,
|
||||||
|
"solarized-dark": false,
|
||||||
|
"nord": false,
|
||||||
|
"monokai": false,
|
||||||
|
"one-dark": false,
|
||||||
|
"catppuccin-latte": true,
|
||||||
|
"solarized-light": true,
|
||||||
|
"gruvbox-light": true,
|
||||||
|
"github-light": true,
|
||||||
|
}
|
||||||
|
for name, wantLight := range expectedLight {
|
||||||
|
info, ok := themeRegistry[name]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("theme %q not in registry", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info.Light != wantLight {
|
||||||
|
t.Errorf("theme %q: Light = %v, want %v", name, info.Light, wantLight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownThemeFallsToDark(t *testing.T) {
|
||||||
|
// simulate unknown theme by looking up directly in registry
|
||||||
|
_, ok := themeRegistry["nonexistent-theme"]
|
||||||
|
if ok {
|
||||||
|
t.Error("expected nonexistent-theme to not be in registry")
|
||||||
|
}
|
||||||
|
// lookupTheme() falls back to dark — verify via default
|
||||||
|
if defaultTheme.Light {
|
||||||
|
t.Error("default theme should be dark (Light=false)")
|
||||||
|
}
|
||||||
|
if defaultTheme.ChromaTheme != "nord" {
|
||||||
|
t.Errorf("default chroma theme = %q, want nord", defaultTheme.ChromaTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChromaThemesExist(t *testing.T) {
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
style := chromaStyles.Get(info.ChromaTheme)
|
||||||
|
if style == nil {
|
||||||
|
t.Errorf("theme %q: chroma theme %q not found in chroma registry", name, info.ChromaTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNavidownStylesValid(t *testing.T) {
|
||||||
|
// navidown supports these style names; unknown names fall back to "dark"
|
||||||
|
validNavidown := map[string]bool{
|
||||||
|
"dark": true, "light": true,
|
||||||
|
"dracula": true, "tokyo-night": true,
|
||||||
|
"pink": true, "ascii": true, "notty": true,
|
||||||
|
}
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
if !validNavidown[info.NavidownStyle] {
|
||||||
|
t.Errorf("theme %q: navidown style %q is not a known navidown style", name, info.NavidownStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChromaThemeForEffectiveNonEmpty(t *testing.T) {
|
||||||
|
for name, info := range themeRegistry {
|
||||||
|
if info.ChromaTheme == "" {
|
||||||
|
t.Errorf("theme %q: ChromaTheme is empty", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -302,7 +302,7 @@ func InitColorAndGradientSupport(cfg *config.Config) *sysinfo.SystemInfo {
|
||||||
// which is invisible on light backgrounds.
|
// which is invisible on light backgrounds.
|
||||||
colors := config.GetColors()
|
colors := config.GetColors()
|
||||||
tview.Styles.PrimitiveBackgroundColor = colors.ContentBackgroundColor.TCell()
|
tview.Styles.PrimitiveBackgroundColor = colors.ContentBackgroundColor.TCell()
|
||||||
if config.GetEffectiveTheme() == "light" {
|
if config.IsLightTheme() {
|
||||||
tview.Styles.PrimaryTextColor = colors.ContentTextColor.TCell()
|
tview.Styles.PrimaryTextColor = colors.ContentTextColor.TCell()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func Run(input InputSpec) error {
|
||||||
|
|
||||||
// Set up image rendering for Kitty-compatible terminals
|
// Set up image rendering for Kitty-compatible terminals
|
||||||
resolver := nav.NewImageResolver(input.SearchRoots)
|
resolver := nav.NewImageResolver(input.SearchRoots)
|
||||||
resolver.SetDarkMode(config.GetEffectiveTheme() == "dark")
|
resolver.SetDarkMode(!config.IsLightTheme())
|
||||||
imgMgr := navtview.NewImageManager(resolver, 8, 16)
|
imgMgr := navtview.NewImageManager(resolver, 8, 16)
|
||||||
imgMgr.SetMaxRows(config.GetMaxImageRows())
|
imgMgr.SetMaxRows(config.GetMaxImageRows())
|
||||||
imgMgr.SetSupported(util.SupportsKittyGraphics())
|
imgMgr.SetSupported(util.SupportsKittyGraphics())
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func NewViewFactory(taskStore store.Store) *ViewFactory {
|
||||||
// Configure image resolver with task directory as search root for relative image paths
|
// Configure image resolver with task directory as search root for relative image paths
|
||||||
searchRoots := []string{config.GetTaskDir()}
|
searchRoots := []string{config.GetTaskDir()}
|
||||||
resolver := nav.NewImageResolver(searchRoots)
|
resolver := nav.NewImageResolver(searchRoots)
|
||||||
resolver.SetDarkMode(config.GetEffectiveTheme() == "dark")
|
resolver.SetDarkMode(!config.IsLightTheme())
|
||||||
imgMgr := navtview.NewImageManager(resolver, 8, 16)
|
imgMgr := navtview.NewImageManager(resolver, 8, 16)
|
||||||
imgMgr.SetMaxRows(config.GetMaxImageRows())
|
imgMgr.SetMaxRows(config.GetMaxImageRows())
|
||||||
imgMgr.SetSupported(util.SupportsKittyGraphics())
|
imgMgr.SetSupported(util.SupportsKittyGraphics())
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func NewNavigableMarkdown(cfg NavigableMarkdownConfig) *NavigableMarkdown {
|
||||||
onStateChange: cfg.OnStateChange,
|
onStateChange: cfg.OnStateChange,
|
||||||
}
|
}
|
||||||
nm.viewer.SetAnsiConverter(navutil.NewAnsiConverter(true))
|
nm.viewer.SetAnsiConverter(navutil.NewAnsiConverter(true))
|
||||||
renderer := nav.NewANSIRendererWithStyle(config.GetEffectiveTheme())
|
renderer := nav.NewANSIRendererWithStyle(config.GetNavidownStyle())
|
||||||
if t := config.GetCodeBlockTheme(); t != "" {
|
if t := config.GetCodeBlockTheme(); t != "" {
|
||||||
renderer = renderer.WithCodeTheme(t)
|
renderer = renderer.WithCodeTheme(t)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue