Cybersecurity-Projects/PROJECTS/beginner/steganography-multi-tool/internal/tui/theme.go

218 lines
5.3 KiB
Go

/*
©AngelaMos | 2026
theme.go
Spectral cybercore palette, HCL gradient engine, and lipgloss styles for the TUI
*/
package tui
import (
"math"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/lucasb-eyer/go-colorful"
)
const (
hexVioletLight = "#A78BFA"
hexViolet = "#8B5CF6"
hexVioletDeep = "#6D4AFF"
hexBlue = "#4457E8"
hexBlueBright = "#6E86FF"
hexBlueDim = "#5A63A8"
hexRed = "#CE2417"
hexBright = "#E9E6F5"
hexNormal = "#C4C0D6"
hexDim = "#8B86A6"
hexFaint = "#46425F"
hexFrame = "#39346B"
hexInk = "#0C0A16"
)
const (
blockFull = "█"
blockEmpty = "░"
ruleGlyph = "━"
accentBar = "▌"
connector = "─"
)
var (
brandStops = mustStops(hexVioletLight, hexViolet, hexVioletDeep, hexBlue)
capacityStops = mustStops(hexBlue, hexVioletLight, hexViolet, hexVioletDeep)
unboundedStops = mustStops(hexBlue, hexViolet)
)
var (
styleTagline = lipgloss.NewStyle().Foreground(lipgloss.Color(hexDim)).Italic(true)
styleLabel = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBlueDim))
styleValue = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBright))
styleValueBold = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBright)).Bold(true)
stylePanelTitle = lipgloss.NewStyle().Foreground(lipgloss.Color(hexViolet)).Bold(true)
styleCursor = lipgloss.NewStyle().Foreground(lipgloss.Color(hexRed)).Bold(true)
styleStepCurrent = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBright)).Bold(true)
styleStepDone = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBlue))
styleStepFuture = lipgloss.NewStyle().Foreground(lipgloss.Color(hexFaint))
styleSelected = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBright)).Bold(true)
styleUnselected = lipgloss.NewStyle().Foreground(lipgloss.Color(hexNormal))
styleHint = lipgloss.NewStyle().Foreground(lipgloss.Color(hexDim))
styleError = lipgloss.NewStyle().Foreground(lipgloss.Color(hexRed)).Bold(true)
styleSuccess = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBlueBright)).Bold(true)
styleWarn = lipgloss.NewStyle().Foreground(lipgloss.Color(hexRed))
styleHelpKey = lipgloss.NewStyle().Foreground(lipgloss.Color(hexBlue)).Bold(true)
styleHelpDesc = lipgloss.NewStyle().Foreground(lipgloss.Color(hexDim))
styleHelpSep = lipgloss.NewStyle().Foreground(lipgloss.Color(hexFaint))
styleTrack = lipgloss.NewStyle().Foreground(lipgloss.Color(hexFaint))
)
const (
framePadX = 3
framePadY = 1
frameBorder = 1
horizontalGutter = 2
)
func frameStyle(width int) lipgloss.Style {
return lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color(hexFrame)).
Padding(framePadY, framePadX).
Width(width)
}
func badge(text, hex string) string {
return lipgloss.NewStyle().
Foreground(lipgloss.Color(hexInk)).
Background(lipgloss.Color(hex)).
Bold(true).
Padding(0, 1).
Render(text)
}
func mustStops(hexes ...string) []colorful.Color {
stops := make([]colorful.Color, len(hexes))
for i, h := range hexes {
c, err := colorful.Hex(h)
if err != nil {
panic("tui: invalid palette hex " + h)
}
stops[i] = c
}
return stops
}
func ramp(stops []colorful.Color, n int) []lipgloss.Color {
out := make([]lipgloss.Color, n)
if n == 0 {
return out
}
if n == 1 || len(stops) == 1 {
for i := range out {
out[i] = lipgloss.Color(stops[0].Hex())
}
return out
}
segments := len(stops) - 1
for i := range out {
x := float64(i) / float64(n-1) * float64(segments)
idx := int(x)
if idx >= segments {
idx = segments - 1
}
local := x - float64(idx)
out[i] = lipgloss.Color(stops[idx].BlendHcl(stops[idx+1], local).Clamped().Hex())
}
return out
}
func gradientText(text string, stops []colorful.Color, bold bool) string {
runes := []rune(text)
cols := ramp(stops, len(runes))
var b strings.Builder
for i, r := range runes {
s := lipgloss.NewStyle().Foreground(cols[i])
if bold {
s = s.Bold(true)
}
b.WriteString(s.Render(string(r)))
}
return b.String()
}
func gradientBlock(lines []string, stops []colorful.Color, bold bool) string {
width := 0
for _, ln := range lines {
if w := len([]rune(ln)); w > width {
width = w
}
}
cols := ramp(stops, width)
out := make([]string, len(lines))
for li, ln := range lines {
var b strings.Builder
for i, r := range []rune(ln) {
s := lipgloss.NewStyle().Foreground(cols[i])
if bold {
s = s.Bold(true)
}
b.WriteString(s.Render(string(r)))
}
out[li] = b.String()
}
return strings.Join(out, "\n")
}
func gradientRule(width int, stops []colorful.Color) string {
if width <= 0 {
return ""
}
return gradientText(strings.Repeat(ruleGlyph, width), stops, false)
}
func spectralBar(width int, frac float64, stops []colorful.Color) string {
if width <= 0 {
return ""
}
over := frac > 1
if frac < 0 {
frac = 0
}
if frac > 1 {
frac = 1
}
filled := int(math.Round(frac * float64(width)))
if filled > width {
filled = width
}
cols := ramp(capacityStops, width)
if stops != nil {
cols = ramp(stops, width)
}
var b strings.Builder
for i := range width {
if i < filled {
col := cols[i]
if over {
col = lipgloss.Color(hexRed)
}
b.WriteString(lipgloss.NewStyle().Foreground(col).Render(blockFull))
} else {
b.WriteString(styleTrack.Render(blockEmpty))
}
}
return b.String()
}