prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

commit 221c4e179fc2f0c89e0cedbda8a58d709ea9b4f0
parent 58890a7d23f3acc57f2b29afed0d1b256d0993ac
Author: Damian Myrda <damian@prime8.dev>
Date:   Wed, 21 Oct 2026 01:54:14 +0000

refactor: centralize tag colors in utils

Tag colors now live in one table in internal/utils. The web stylesheet
is generated from it (TagCSS) and the TUI builds its lipgloss styles
from the same entries, so a color is changed in one place.

Also drop dead tag maps and unused TUI/utils helpers.

Diffstat:
Minternal/config/experience.go | 4----
Minternal/tui/helpers.go | 28----------------------------
Minternal/tui/styles.go | 32--------------------------------
Minternal/tui/tags.go | 87++++++++++++-------------------------------------------------------------------
Minternal/utils/tags.go | 165+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Dinternal/utils/url.go | 7-------
Minternal/web/renderer.go | 2++
Minternal/web/styles/layout.css | 114-------------------------------------------------------------------------------
Minternal/web/templates/layout.gohtml | 3++-
9 files changed, 116 insertions(+), 326 deletions(-)

diff --git a/internal/config/experience.go b/internal/config/experience.go @@ -71,10 +71,6 @@ func GetExperience() []Experience { } } -func GetCurrentJob() *Experience { - return CurrentJob(GetExperience()) -} - func CurrentJob(experience []Experience) *Experience { for i := range experience { if experience[i].Current { diff --git a/internal/tui/helpers.go b/internal/tui/helpers.go @@ -60,12 +60,6 @@ func padRunesRight(s string, width int) string { return s + strings.Repeat(" ", width-len(r)) } -func renderKeyValueFixed(key string, value string, keyWidth int, valueWidth int, keyStyle lipgloss.Style, valueStyle lipgloss.Style) string { - keyText := padRunesRight(fitRunes(key, keyWidth), keyWidth) + ":" - valueText := padRunesRight(fitRunesLeading(value, valueWidth), valueWidth) - return keyStyle.Render(keyText) + " " + valueStyle.Render(valueText) -} - func renderKeyValueLine(key string, value string, totalWidth int, keyStyle lipgloss.Style, valueStyle lipgloss.Style) string { keyLabel := key + ":" valueWidth := max(totalWidth-len([]rune(keyLabel))-1, 1) @@ -94,13 +88,6 @@ func renderPanelWithStyle(style lipgloss.Style, width int, totalHeight int, cont Render(clipped) } -func panelStyleSharedRight(style lipgloss.Style) lipgloss.Style { - b := lipgloss.NormalBorder() - b.TopRight = "┬" - b.BottomRight = "┴" - return style.BorderStyle(b) -} - func panelStyleSharedLeft(style lipgloss.Style) lipgloss.Style { b := lipgloss.NormalBorder() b.TopLeft = "┬" @@ -108,13 +95,6 @@ func panelStyleSharedLeft(style lipgloss.Style) lipgloss.Style { return style.BorderStyle(b) } -func panelStyleSharedBottom(style lipgloss.Style) lipgloss.Style { - b := lipgloss.NormalBorder() - b.BottomLeft = "├" - b.BottomRight = "┤" - return style.BorderStyle(b) -} - func panelStyleSharedTop(style lipgloss.Style) lipgloss.Style { b := lipgloss.NormalBorder() b.TopLeft = "├" @@ -126,18 +106,10 @@ func panelStyleWithoutRight(style lipgloss.Style) lipgloss.Style { return style.Border(lipgloss.NormalBorder(), true, false, true, true) } -func panelStyleWithoutLeft(style lipgloss.Style) lipgloss.Style { - return style.Border(lipgloss.NormalBorder(), true, true, true, false) -} - func panelStyleWithoutBottom(style lipgloss.Style) lipgloss.Style { return style.Border(lipgloss.NormalBorder(), true, true, false, true) } -func panelStyleWithoutTop(style lipgloss.Style) lipgloss.Style { - return style.Border(lipgloss.NormalBorder(), false, true, true, true) -} - func utilsYearLabel(exp config.Experience) string { if exp.Current { return "now" diff --git a/internal/tui/styles.go b/internal/tui/styles.go @@ -59,36 +59,4 @@ var ( footerTimeStyle = lipgloss.NewStyle().Foreground(colorCyan).Background(colorFooterBg) footerWakaStyle = lipgloss.NewStyle().Foreground(colorMagenta).Background(colorFooterBg) footerDimStyle = lipgloss.NewStyle().Foreground(colorDim).Background(colorFooterBg) - - tagDefaultStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("233")).Background(lipgloss.Color("242")).Padding(0, 1) - tagStyles = map[string]lipgloss.Style{ - "lang-zig": lipgloss.NewStyle().Background(lipgloss.Color("#f7a41d")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "lang-rust": lipgloss.NewStyle().Background(lipgloss.Color("#dea584")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "lang-c": lipgloss.NewStyle().Background(lipgloss.Color("#a8b1c2")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "lang-go": lipgloss.NewStyle().Background(lipgloss.Color("#00add8")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "lang-ts": lipgloss.NewStyle().Background(lipgloss.Color("#3178c6")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-astro": lipgloss.NewStyle().Background(lipgloss.Color("#ff5d01")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "lang-python": lipgloss.NewStyle().Background(lipgloss.Color("#f4c542")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tool-zed": lipgloss.NewStyle().Background(lipgloss.Color("#084ccf")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tool-neovim": lipgloss.NewStyle().Background(lipgloss.Color("#57a143")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tool-opencode": lipgloss.NewStyle().Background(lipgloss.Color("#af5faf")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tool-linux": lipgloss.NewStyle().Background(lipgloss.Color("#f7c94c")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tool-fish": lipgloss.NewStyle().Background(lipgloss.Color("#d78700")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tool-ghostty": lipgloss.NewStyle().Background(lipgloss.Color("#8b5cf6")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "interest-lowlevel": lipgloss.NewStyle().Background(lipgloss.Color("#d75f5f")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "interest-tui": lipgloss.NewStyle().Background(lipgloss.Color("#5faf5f")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "interest-embedded": lipgloss.NewStyle().Background(lipgloss.Color("#5fafaf")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "interest-unix": lipgloss.NewStyle().Background(lipgloss.Color("#d7af5f")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "interest-webdev": lipgloss.NewStyle().Background(lipgloss.Color("#2d7d9a")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-temporal": lipgloss.NewStyle().Background(lipgloss.Color("#4c35c9")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-postgres": lipgloss.NewStyle().Background(lipgloss.Color("#336791")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-pytorch": lipgloss.NewStyle().Background(lipgloss.Color("#ee4c2c")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-cuda": lipgloss.NewStyle().Background(lipgloss.Color("#76b900")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tech-docker": lipgloss.NewStyle().Background(lipgloss.Color("#2496ed")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-kafka": lipgloss.NewStyle().Background(lipgloss.Color("#4c35c9")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "tech-rn": lipgloss.NewStyle().Background(lipgloss.Color("#61dafb")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tech-supabase": lipgloss.NewStyle().Background(lipgloss.Color("#3ecf8e")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - "tech-rest": lipgloss.NewStyle().Background(lipgloss.Color("#6b8e23")).Foreground(lipgloss.Color("#ffffff")).Padding(0, 1), - "project-portfolio": lipgloss.NewStyle().Background(lipgloss.Color("#b07a2f")).Foreground(lipgloss.Color("#0d0d0d")).Padding(0, 1), - } ) diff --git a/internal/tui/tags.go b/internal/tui/tags.go @@ -1,89 +1,28 @@ package tui -import "strings" +import ( + "strings" + + "git.prime8.dev/moncheeta/prime8dev/internal/utils" + "github.com/charmbracelet/lipgloss" +) + +var tagDefaultStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("233")).Background(colorDim).Padding(0, 1) func renderTag(label string) string { - style, ok := tagStyles[tagClassKey(label)] - if !ok { - style = tagDefaultStyle + style := tagDefaultStyle + if t, ok := utils.LookupTag(label); ok { + style = lipgloss.NewStyle().Background(lipgloss.Color(t.BG)).Foreground(lipgloss.Color(t.FG)).Padding(0, 1) } return style.Render(label) } func renderTagList(labels []string) string { - if len(labels) == 0 { - return "" - } out := make([]string, 0, len(labels)) for _, label := range labels { - if strings.TrimSpace(label) == "" { - continue + if strings.TrimSpace(label) != "" { + out = append(out, renderTag(label)) } - out = append(out, renderTag(label)) } return strings.Join(out, " ") } - -func tagClassKey(label string) string { - v := strings.ToLower(strings.TrimSpace(label)) - switch v { - case "zig": - return "lang-zig" - case "rust": - return "lang-rust" - case "c": - return "lang-c" - case "go", "golang": - return "lang-go" - case "typescript", "ts": - return "lang-ts" - case "astro": - return "tech-astro" - case "python": - return "lang-python" - case "zed", "zed (vim mode)": - return "tool-zed" - case "neovim": - return "tool-neovim" - case "opencode": - return "tool-opencode" - case "linux": - return "tool-linux" - case "fish": - return "tool-fish" - case "ghostty": - return "tool-ghostty" - case "low-level systems", "low level systems": - return "interest-lowlevel" - case "tui/cli tooling", "tui cli tooling", "tui": - return "interest-tui" - case "embedded", "embedded systems": - return "interest-embedded" - case "unix philosophy", "unix": - return "interest-unix" - case "web development", "webdev", "web dev": - return "interest-webdev" - case "temporal": - return "tech-temporal" - case "postgres", "postgresql": - return "tech-postgres" - case "pytorch": - return "tech-pytorch" - case "cuda": - return "tech-cuda" - case "docker": - return "tech-docker" - case "kafka": - return "tech-kafka" - case "react native", "react-native", "rn": - return "tech-rn" - case "supabase": - return "tech-supabase" - case "rest": - return "tech-rest" - case "portfolio", "portfolio site": - return "project-portfolio" - default: - return "" - } -} diff --git a/internal/utils/tags.go b/internal/utils/tags.go @@ -1,90 +1,123 @@ package utils -import "strings" +import ( + "fmt" + "sort" + "strings" +) -var tagClassMap = map[string]string{ - // Languages - "Zig": "lang-zig", - "Rust": "lang-rust", - "C": "lang-c", - "Go": "lang-go", - "TypeScript": "lang-ts", - "Python": "lang-python", +// Tag is one colored label. Web and TUI both read this table, so a color +// is changed here and nowhere else. +type Tag struct { + Class string + BG, FG string + Aliases []string // lowercase labels that map to this tag +} - // Tools - "Zed (Vim mode)": "tool-zed", - "Neovim": "tool-neovim", - "OpenCode": "tool-opencode", - "Linux": "tool-linux", - "Fish": "tool-fish", - "Ghostty": "tool-ghostty", +const ( + dark = "#0d0d0d" + light = "#ffffff" +) - // Interests - "Low-level systems": "interest-lowlevel", - "TUI/CLI tooling": "interest-tui", - "Embedded": "interest-embedded", - "UNIX philosophy": "interest-unix", +// Brand colors where a brand exists; otherwise picked to stay distinct +// from their neighbors in the same row. +var Tags = []Tag{ + // languages + {"lang-zig", "#f7a41d", dark, []string{"zig"}}, + {"lang-rust", "#dea584", dark, []string{"rust"}}, + {"lang-c", "#a8b9cc", dark, []string{"c"}}, + {"lang-go", "#00add8", dark, []string{"go", "golang"}}, + {"lang-ts", "#3178c6", light, []string{"typescript", "ts"}}, + {"lang-python", "#ffd43b", dark, []string{"python"}}, - // Tech - "Temporal": "tech-temporal", - "PostgreSQL": "tech-postgres", - "PyTorch": "tech-pytorch", - "CUDA": "tech-cuda", - "Docker": "tech-docker", - "Kafka": "tech-kafka", - "React Native": "tech-rn", - "Supabase": "tech-supabase", - "REST": "tech-rest", -} + // tools + {"tool-crux", "#2e5c9a", light, []string{"crux linux", "crux"}}, + {"tool-helix", "#7f5ad6", light, []string{"helix"}}, + {"tool-fish", "#2aa198", dark, []string{"fish"}}, + {"tool-claude", "#d97757", dark, []string{"claude code", "claude"}}, -var techAbbreviations = map[string]string{ - "TypeScript": "TS", + // interests + {"interest-osdev", "#b4469b", light, []string{"osdev"}}, + {"interest-firmware", "#2e8b57", light, []string{"firmware"}}, + {"interest-tui", "#ffb000", dark, []string{"tui/cli tooling", "tui"}}, + {"interest-unix", "#4c78a8", light, []string{"unix philosophy", "unix"}}, + {"interest-webdev", "#2d7d9a", light, []string{"web development", "webdev"}}, + + // hobbies + {"hobby-audi", "#f2c400", dark, []string{"old-school audis"}}, + {"hobby-rally", "#d7263d", light, []string{"rally"}}, + {"hobby-soccer", "#2e9d5b", light, []string{"pickup soccer"}}, + + // project and experience tech + {"tech-cuda", "#76b900", dark, []string{"cuda"}}, + {"tech-docker", "#2496ed", light, []string{"docker"}}, + {"tech-kafka", "#e8e8e8", dark, []string{"kafka"}}, + {"tech-rn", "#61dafb", dark, []string{"react native"}}, + {"tech-supabase", "#3ecf8e", dark, []string{"supabase"}}, + {"tech-rest", "#8b95a5", dark, []string{"rest"}}, + {"tech-temporal", "#7c6cf2", light, []string{"temporal"}}, + {"tech-pytorch", "#ee4c2c", light, []string{"pytorch"}}, + {"tech-postgres", "#336791", light, []string{"postgresql", "postgres"}}, + {"tech-astro", "#ff5d01", light, []string{"astro"}}, + {"project-portfolio", "#b07a2f", dark, []string{"portfolio"}}, } -func TagClass(tag string) string { - if className, ok := tagClassMap[tag]; ok { - return className +var tagByLabel = func() map[string]*Tag { + m := make(map[string]*Tag) + for i := range Tags { + for _, a := range Tags[i].Aliases { + m[a] = &Tags[i] + } } + return m +}() - for knownTag, className := range tagClassMap { - if strings.EqualFold(knownTag, tag) { - return className - } +// LookupTag finds a tag by label, case-insensitively. +func LookupTag(label string) (Tag, bool) { + t, ok := tagByLabel[strings.ToLower(strings.TrimSpace(label))] + if !ok { + return Tag{}, false } + return *t, true +} - return "" +// TagClass returns the CSS class for a label, or "" for the default style. +func TagClass(label string) string { + t, _ := LookupTag(label) + return t.Class } -func TechMeta(tech []string) string { - if len(tech) == 0 { - return "" +// TagColor returns the background color for a label, or "" if untagged. +func TagColor(label string) string { + t, _ := LookupTag(label) + return t.BG +} + +// TagCSS renders one rule per tag for the web stylesheet. +func TagCSS() string { + rules := make([]string, 0, len(Tags)) + for _, t := range Tags { + rules = append(rules, fmt.Sprintf(".tag.%s{background:%s;color:%s}", t.Class, t.BG, t.FG)) } + sort.Strings(rules) + return strings.Join(rules, "\n") +} + +var techAbbreviations = map[string]string{ + "typescript": "TS", +} +// TechMeta is the short "rust, cuda" line shown in the project list. +func TechMeta(tech []string) string { parts := make([]string, 0, 2) - for i, item := range tech { - if i >= 2 { + for _, item := range tech { + if len(parts) == 2 { break } - - if short, ok := techAbbreviations[item]; ok { - parts = append(parts, short) - continue - } - - short := "" - for tech, abbreviation := range techAbbreviations { - if strings.EqualFold(tech, item) { - short = abbreviation - break - } - } - - if short != "" { - parts = append(parts, short) - } else { - parts = append(parts, item) + if short, ok := techAbbreviations[strings.ToLower(item)]; ok { + item = short } + parts = append(parts, item) } - return strings.Join(parts, ", ") } diff --git a/internal/utils/url.go b/internal/utils/url.go @@ -1,7 +0,0 @@ -package utils - -import "strings" - -func EnsureHTTPS(url string) string { - return strings.Replace(url, "http://", "https://", 1) -} diff --git a/internal/web/renderer.go b/internal/web/renderer.go @@ -60,6 +60,8 @@ func New() (*Renderer, error) { "tabPath": utils.TabPath, "tabURL": utils.TabURL, "tagClass": utils.TagClass, + "tagColor": func(label string) template.CSS { return template.CSS(utils.TagColor(label)) }, + "tagCSS": func() template.CSS { return template.CSS(utils.TagCSS()) }, "techMeta": utils.TechMeta, "formatMonthYear": utils.FormatMonthYear, "formatEndDate": utils.FormatEndDate, diff --git a/internal/web/styles/layout.css b/internal/web/styles/layout.css @@ -149,120 +149,6 @@ a:hover { background: var(--fg-dim); } -.tag.lang-zig { - background: #f7a41d; -} - -.tag.lang-rust { - background: #dea584; -} - -.tag.lang-c { - background: #a8b1c2; -} - -.tag.lang-go { - background: #00add8; -} - -.tag.lang-ts { - background: #3178c6; - color: #fff; -} - -.tag.lang-python { - background: #f4c542; - color: #0d0d0d; -} - -.tag.tool-zed { - background: #084ccf; - color: #fff; -} - -.tag.tool-neovim { - background: #57a143; -} - -.tag.tool-opencode { - background: #af5faf; - color: #fff; -} - -.tag.tool-linux { - background: #f7c94c; -} - -.tag.tool-fish { - background: #d78700; -} - -.tag.tool-ghostty { - background: #8b5cf6; - color: #fff; -} - -.tag.interest-lowlevel { - background: #d75f5f; - color: #fff; -} - -.tag.interest-tui { - background: #5faf5f; - color: #0d0d0d; -} - -.tag.interest-embedded { - background: #5fafaf; - color: #0d0d0d; -} - -.tag.interest-unix { - background: #d7af5f; - color: #0d0d0d; -} - -.tag.tech-temporal { - background: #4c35c9; - color: #fff; -} - -.tag.tech-postgres { - background: #336791; - color: #fff; -} - -.tag.tech-pytorch { - background: #ee4c2c; - color: #fff; -} - -.tag.tech-cuda { - background: #76b900; -} - -.tag.tech-docker { - background: #2496ed; - color: #fff; -} - -.tag.tech-kafka { - background: #4c35c9; - color: #fff; -} - -.tag.tech-rn { - background: #61dafb; -} - -.tag.tech-supabase { - background: #3ecf8e; -} - -.tag.tech-rest { - background: #6b8e23; -} - #boot-overlay { position: fixed; top: 0; diff --git a/internal/web/templates/layout.gohtml b/internal/web/templates/layout.gohtml @@ -35,7 +35,8 @@ <script defer src="https://metrics.prime8.dev/script.js" data-website-id="d3032378-7959-4411-92ec-f67e20399fe4"></script> <script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script> <script src="https://unpkg.com/htmx-ext-preload@2.1.0/preload.js" crossorigin="anonymous"></script> - <style>{{inlineCSS "layout.css"}}</style> + <style>{{inlineCSS "layout.css"}} +{{tagCSS}}</style> </head> <body> <div id="app" data-boot-title="{{.About.Name}}" data-boot-subtitle="www.prime8.dev" hx-ext="preload">