package theme import ( "fmt" "math" "strconv" "strings" ) // HSLToHex converts an HSL string like "220 14% 96%" to a hex color like "#f4f5f7". // This is needed for email templates since email clients don't support CSS variables. func HSLToHex(hsl string) string { h, s, l, err := parseHSL(hsl) if err != nil { // Return a fallback color on parse error return "#000000" } r, g, b := hslToRGB(h, s, l) return fmt.Sprintf("#%02x%02x%02x", r, g, b) } // parseHSL parses an HSL string like "220 14% 96%" into h (0-360), s (0-1), l (0-1). func parseHSL(hsl string) (h, s, l float64, err error) { // Trim and normalize spaces hsl = strings.TrimSpace(hsl) parts := strings.Fields(hsl) if len(parts) != 3 { return 0, 0, 0, fmt.Errorf("invalid HSL format: expected 3 parts, got %d", len(parts)) } // Parse hue (0-360 degrees) h, err = strconv.ParseFloat(parts[0], 64) if err != nil { return 0, 0, 0, fmt.Errorf("invalid hue value: %s", parts[0]) } // Parse saturation (0-100%) satStr := strings.TrimSuffix(parts[1], "%") s, err = strconv.ParseFloat(satStr, 64) if err != nil { return 0, 0, 0, fmt.Errorf("invalid saturation value: %s", parts[1]) } s = s / 100.0 // Convert to 0-1 range // Parse lightness (0-100%) lightStr := strings.TrimSuffix(parts[2], "%") l, err = strconv.ParseFloat(lightStr, 64) if err != nil { return 0, 0, 0, fmt.Errorf("invalid lightness value: %s", parts[2]) } l = l / 100.0 // Convert to 0-1 range return h, s, l, nil } // hslToRGB converts HSL values to RGB. // h: 0-360, s: 0-1, l: 0-1 // Returns r, g, b in 0-255 range. func hslToRGB(h, s, l float64) (r, g, b uint8) { // Normalize hue to 0-1 range h = h / 360.0 var rFloat, gFloat, bFloat float64 if s == 0 { // Achromatic (gray) rFloat = l gFloat = l bFloat = l } else { var q float64 if l < 0.5 { q = l * (1 + s) } else { q = l + s - l*s } p := 2*l - q rFloat = hueToRGB(p, q, h+1.0/3.0) gFloat = hueToRGB(p, q, h) bFloat = hueToRGB(p, q, h-1.0/3.0) } r = uint8(math.Round(rFloat * 255)) g = uint8(math.Round(gFloat * 255)) b = uint8(math.Round(bFloat * 255)) return r, g, b } // hueToRGB is a helper function for HSL to RGB conversion. func hueToRGB(p, q, t float64) float64 { if t < 0 { t += 1 } if t > 1 { t -= 1 } if t < 1.0/6.0 { return p + (q-p)*6*t } if t < 1.0/2.0 { return q } if t < 2.0/3.0 { return p + (q-p)*(2.0/3.0-t)*6 } return p } // ThemeColorsToEmail converts theme color settings (stored as HSL) to EmailColors (hex). // This function takes the color map from theme settings and returns hex colors for email inlining. func ThemeColorsToEmail(colors map[string]string) map[string]string { result := make(map[string]string) colorKeys := []string{ "primary", "primary-foreground", "secondary", "secondary-foreground", "background", "foreground", "muted", "muted-foreground", "border", "card", "card-foreground", "accent", "accent-foreground", "destructive", "destructive-foreground", } for _, key := range colorKeys { if hsl, ok := colors[key]; ok && hsl != "" { result[key] = HSLToHex(hsl) } } return result } // ColorSchemeToEmailColors converts a theme ColorScheme (HSL strings) to hex colors for email inlining. // Email clients don't support CSS variables, so colors must be inlined as hex. func ColorSchemeToEmailColors(cs *ColorScheme) EmailColors { if cs == nil { return EmailColors{} } return EmailColors{ Primary: HSLToHex(cs.Primary), PrimaryForeground: HSLToHex(cs.PrimaryForeground), Secondary: HSLToHex(cs.Secondary), SecondaryForeground: HSLToHex(cs.SecondaryForeground), Background: HSLToHex(cs.Background), Foreground: HSLToHex(cs.Foreground), Muted: HSLToHex(cs.Muted), MutedForeground: HSLToHex(cs.MutedForeground), Border: HSLToHex(cs.Border), Card: HSLToHex(cs.Card), CardForeground: HSLToHex(cs.CardForeground), } } // EmailColors contains theme colors converted to hex for email inlining. // Mirrors templates.EmailColors structure but lives in theme package for conversion. type EmailColors struct { Primary string PrimaryForeground string Secondary string SecondaryForeground string Background string Foreground string Muted string MutedForeground string Border string Card string CardForeground string }