Cybersecurity-Projects/PROJECTS/intermediate/security-news-scraper/internal/tui/theme.go

152 lines
4.1 KiB
Go

// ©AngelaMos | 2026
// theme.go
package tui
import (
"math"
"strings"
"github.com/charmbracelet/lipgloss"
)
const (
colorVoid = "#0B0620"
colorText = "#E7E0FF"
colorMuted = "#9A8AD0"
colorDim = "#574B7D"
colorMagenta = "#FF2E97"
colorAmber = "#FF8A3D"
colorYellow = "#FFE04D"
colorCyan = "#00E5FF"
colorViolet = "#A65CFF"
colorBlue = "#2D6BFF"
)
const (
barFull = "█"
barEmpty = "░"
ruleLead = "▓▒░"
ruleBody = "─"
)
func bandColor(b band) string {
switch b {
case bandCritical:
return colorMagenta
case bandHigh:
return colorAmber
case bandMedium:
return colorYellow
case bandLow:
return colorCyan
default:
return colorDim
}
}
type Theme struct {
Brand lipgloss.Style
BrandMark lipgloss.Style
Meta lipgloss.Style
ColHead lipgloss.Style
Text lipgloss.Style
Muted lipgloss.Style
Dim lipgloss.Style
Rank lipgloss.Style
RankSel lipgloss.Style
Headline lipgloss.Style
HeadlineSel lipgloss.Style
SelBar lipgloss.Style
KeyGlyph lipgloss.Style
KeyDesc lipgloss.Style
Spinner lipgloss.Style
PanelTitle lipgloss.Style
Label lipgloss.Style
Link lipgloss.Style
}
func NewTheme() Theme {
base := lipgloss.NewStyle().Foreground(lipgloss.Color(colorText))
return Theme{
Brand: lipgloss.NewStyle().Foreground(lipgloss.Color(colorViolet)).Bold(true),
BrandMark: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
Meta: lipgloss.NewStyle().Foreground(lipgloss.Color(colorCyan)),
ColHead: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)).Bold(true),
Text: base,
Muted: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)),
Dim: lipgloss.NewStyle().Foreground(lipgloss.Color(colorDim)),
Rank: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)),
RankSel: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
Headline: base,
HeadlineSel: lipgloss.NewStyle().Foreground(lipgloss.Color(colorText)).Bold(true),
SelBar: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
KeyGlyph: lipgloss.NewStyle().Foreground(lipgloss.Color(colorCyan)).Bold(true),
KeyDesc: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMuted)),
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Color(colorMagenta)).Bold(true),
PanelTitle: lipgloss.NewStyle().Foreground(lipgloss.Color(colorViolet)).Bold(true),
Label: lipgloss.NewStyle().Foreground(lipgloss.Color(colorBlue)).Bold(true),
Link: lipgloss.NewStyle().Foreground(lipgloss.Color(colorCyan)).Underline(true),
}
}
func (t Theme) fg(color string) lipgloss.Style {
return lipgloss.NewStyle().Foreground(lipgloss.Color(color))
}
func (t Theme) bandFG(b band) lipgloss.Style {
return t.fg(bandColor(b))
}
func (t Theme) chip(text, color string) string {
return lipgloss.NewStyle().
Foreground(lipgloss.Color(colorVoid)).
Background(lipgloss.Color(color)).
Bold(true).
Render(" " + text + " ")
}
func (t Theme) spectrumBar(frac float64, width int) string {
if width <= 0 {
return ""
}
filled := int(math.Round(clamp01(frac) * float64(width)))
stops := []string{colorCyan, colorBlue, colorViolet, colorMagenta}
var b strings.Builder
for i := 0; i < width; i++ {
if i < filled {
b.WriteString(t.fg(stops[i*len(stops)/width]).Render(barFull))
} else {
b.WriteString(t.Dim.Render(barEmpty))
}
}
return b.String()
}
func (t Theme) bandBar(b band, frac float64, width int) string {
if width <= 0 {
return ""
}
filled := int(math.Round(clamp01(frac) * float64(width)))
color := bandColor(b)
var sb strings.Builder
for i := 0; i < width; i++ {
if i < filled {
sb.WriteString(t.fg(color).Render(barFull))
} else {
sb.WriteString(t.Dim.Render(barEmpty))
}
}
return sb.String()
}
func (t Theme) rule(width int) string {
lead := len([]rune(ruleLead)) + 1
n := width - lead
if n < 0 {
return t.fg(colorViolet).Render(ruleLead)
}
return t.fg(colorViolet).Render(ruleLead) + " " + t.Dim.Render(strings.Repeat(ruleBody, n))
}