package main import ( "encoding/json" "fmt" "net/http" "strings" "sync" "github.com/a-h/templ" ) func Assert(ok bool) { if !ok { panic("assertion failure") } } type Theme struct { primary string secondary string accent string extra string text string alt string background string } type FontPack struct { Monospace string Normal string } type Style struct { theme Theme fontPack FontPack } var ( mu sync.RWMutex themes = map[string]Theme{} fontPacks = map[string]FontPack{} ) func SetFontPack(name string, fontPack FontPack) { mu.Lock() defer mu.Unlock() fontPacks[name] = fontPack } func GetFontPack(name string) (FontPack, bool) { mu.RLock() defer mu.RUnlock() fontPack, ok := fontPacks[name] return fontPack, ok } func SetTheme(name string, theme Theme) { mu.Lock() defer mu.Unlock() themes[name] = theme } func GetTheme(name string) (Theme, bool) { mu.RLock() defer mu.RUnlock() theme, ok := themes[name] return theme, ok } func currentStyle(r *http.Request) Style { theme, ok := GetTheme("default") Assert(ok) fontPack, ok := GetFontPack("default") Assert(ok) style := Style{ theme: theme, fontPack: fontPack, } if cookie, err := r.Cookie("theme"); err == nil { if theme, ok := GetTheme(cookie.Value); ok { style.theme = theme } } if cookie, err := r.Cookie("font-pack"); err == nil { if fontPack, ok := GetFontPack(cookie.Value); ok { style.fontPack = fontPack } } return style } func (theme Theme) Vars() map[string]string { return map[string]string{ "primary": theme.primary, "secondary": theme.secondary, "accent": theme.accent, "extra": theme.extra, "text": theme.text, "alt": theme.alt, "background": theme.background, } } func (fontPack FontPack) Vars() map[string]string { return map[string]string{ "font-mono": fontPack.Monospace, "font-normal": fontPack.Normal, } } func (style Style) Vars() map[string]string { vars := style.theme.Vars() for k, v := range style.fontPack.Vars() { vars[k] = v } return vars } func styleToCss(style Style) templ.SafeCSS { var b strings.Builder for k, v := range style.Vars() { fmt.Fprintf(&b, "--%s:%s;", k, v) } return templ.SafeCSS(b.String()) } type Project struct { Name string Type string Desc []string Cmd string Link string } var projects = []Project{ { Name: "CPU vs AI", Type: "Game (made with Zig)", Desc: []string{ "A tower defense game where an AI chip on your motherboard", "generates bugs! Place CPUs to destroy the bugs!", "Don't let them corrupt your RAM!", }, }, { Name: "Eko", Type: "Social Media Platform", Desc: []string{ "A terminal-native alternative", "to Discord for Terminal", "nerds!", }, }, { Name: "Trident", Type: "IntelliJ Plugin", Desc: []string{ "A blazingly fast way to", "Navigate to your mosta used", "files. Minimize keystrokes", "and maximize productivity!", }, }, { Name: "Dotfiles", Type: "Configuration", Desc: []string{ "My Linux configuration,", "includes Neovim, tmux and", "other programs.", }, }, } func styleHandler(cookie string, lookup func(string) (map[string]string, bool)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") vars, ok := lookup(name) if !ok { http.NotFound(w, r) return } http.SetCookie(w, &http.Cookie{ Name: cookie, Value: name, Path: "/", MaxAge: 31536000, SameSite: http.SameSiteLaxMode, }) w.Header().Set("Cache-Control", "no-store") json.NewEncoder(w).Encode(vars) } } func main() { carbonightCat := Theme{ primary: "#94e2d5", secondary: "#b4befe", accent: "#74c7ec", extra: "#a6e3a1", text: "#cdd6f4", alt: "#585b70", background: "#1e1e2e", } darkness := Theme{ primary: "#000000", secondary: "#000000", accent: "#000000", extra: "#000000", text: "#000000", alt: "#000000", background: "#000000", } dark := Theme{ primary: "#54d7a9", secondary: "#9b7eca", accent: "#7ed4fb", extra: "#8fcc75", text: "#ffffff", alt: "#999999", background: "#000000", } light := Theme{ primary: "#009399", secondary: "#440b8e", accent: "#177282", extra: "#40a02b", text: "#000000", alt: "#4d4d4d", background: "#ffffff", } banana := Theme{ primary: "#ffdd44", secondary: "#9b7eca", accent: "#7ed4fb", extra: "#000000", text: "#ffffff", alt: "#999999", background: "#000000", } jetbrains := FontPack{ Monospace: "JetBrains Mono", Normal: `system-ui, sans-serif`, } minecraft := FontPack{ Monospace: `"Monocraft", "JetBrains Mono", monospace`, Normal: `"Monocraft", system-ui, sans-serif`, } SetTheme("carbonight-cat", carbonightCat) SetTheme("banana", banana) SetTheme("black", dark) SetTheme("dark", darkness) SetTheme("light", light) SetTheme("white", light) SetTheme("sun", light) SetTheme("sunlight", light) SetTheme("flashbang", light) SetTheme("default", carbonightCat) SetFontPack("jetbrains", jetbrains) SetFontPack("minecraft", minecraft) SetFontPack("default", jetbrains) mux := http.NewServeMux() fileServer := http.FileServer(http.Dir("./public")) mux.Handle("GET /public/", http.StripPrefix("/public/", fileServer)) mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { HomePage(currentStyle(r)).Render(r.Context(), w) }) mux.HandleFunc("GET /skills", func(w http.ResponseWriter, r *http.Request) { SkillsPage(currentStyle(r)).Render(r.Context(), w) }) mux.HandleFunc("GET /projects", func(w http.ResponseWriter, r *http.Request) { ProjectsPage(currentStyle(r), projects).Render(r.Context(), w) }) mux.HandleFunc("GET /theme/{name}", styleHandler("theme", func(n string) (map[string]string, bool) { theme, ok := GetTheme(n) return theme.Vars(), ok })) mux.HandleFunc("GET /font-pack/{name}", styleHandler("font-pack", func(n string) (map[string]string, bool) { fontPack, ok := GetFontPack(n) return fontPack.Vars(), ok })) fmt.Println("Server running at http://localhost:8080") if err := http.ListenAndServe(":8080", mux); err != nil { panic(err) } }