commit dc7998b6d7658e8390628ab315e9fcee9dfda795
parent cf0a7411d537972db61fcdfdd82405edc0b12ac0
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:
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">