summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16.go651
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16_test.go681
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/terminals.go135
3 files changed, 1467 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16.go
new file mode 100644
index 0000000..fedd783
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16.go
@@ -0,0 +1,651 @@
+package dank16
+
+import (
+ "fmt"
+ "math"
+
+ "github.com/lucasb-eyer/go-colorful"
+)
+
+type RGB struct {
+ R, G, B float64
+}
+
+type HSV struct {
+ H, S, V float64
+}
+
+type ColorInfo struct {
+ Hex string `json:"hex"`
+ HexStripped string `json:"hex_stripped"`
+ R int `json:"r"`
+ G int `json:"g"`
+ B int `json:"b"`
+}
+
+type VariantColorValue struct {
+ Hex string `json:"hex"`
+ HexStripped string `json:"hex_stripped"`
+}
+
+type VariantColorInfo struct {
+ Dark VariantColorValue `json:"dark"`
+ Light VariantColorValue `json:"light"`
+ Default VariantColorValue `json:"default"`
+}
+
+type Palette struct {
+ Color0 ColorInfo `json:"color0"`
+ Color1 ColorInfo `json:"color1"`
+ Color2 ColorInfo `json:"color2"`
+ Color3 ColorInfo `json:"color3"`
+ Color4 ColorInfo `json:"color4"`
+ Color5 ColorInfo `json:"color5"`
+ Color6 ColorInfo `json:"color6"`
+ Color7 ColorInfo `json:"color7"`
+ Color8 ColorInfo `json:"color8"`
+ Color9 ColorInfo `json:"color9"`
+ Color10 ColorInfo `json:"color10"`
+ Color11 ColorInfo `json:"color11"`
+ Color12 ColorInfo `json:"color12"`
+ Color13 ColorInfo `json:"color13"`
+ Color14 ColorInfo `json:"color14"`
+ Color15 ColorInfo `json:"color15"`
+}
+
+type VariantPalette struct {
+ Color0 VariantColorInfo `json:"color0"`
+ Color1 VariantColorInfo `json:"color1"`
+ Color2 VariantColorInfo `json:"color2"`
+ Color3 VariantColorInfo `json:"color3"`
+ Color4 VariantColorInfo `json:"color4"`
+ Color5 VariantColorInfo `json:"color5"`
+ Color6 VariantColorInfo `json:"color6"`
+ Color7 VariantColorInfo `json:"color7"`
+ Color8 VariantColorInfo `json:"color8"`
+ Color9 VariantColorInfo `json:"color9"`
+ Color10 VariantColorInfo `json:"color10"`
+ Color11 VariantColorInfo `json:"color11"`
+ Color12 VariantColorInfo `json:"color12"`
+ Color13 VariantColorInfo `json:"color13"`
+ Color14 VariantColorInfo `json:"color14"`
+ Color15 VariantColorInfo `json:"color15"`
+}
+
+func NewColorInfo(hex string) ColorInfo {
+ rgb := HexToRGB(hex)
+ stripped := hex
+ if len(hex) > 0 && hex[0] == '#' {
+ stripped = hex[1:]
+ }
+ return ColorInfo{
+ Hex: hex,
+ HexStripped: stripped,
+ R: int(math.Round(rgb.R * 255)),
+ G: int(math.Round(rgb.G * 255)),
+ B: int(math.Round(rgb.B * 255)),
+ }
+}
+
+func HexToRGB(hex string) RGB {
+ if hex[0] == '#' {
+ hex = hex[1:]
+ }
+ var r, g, b uint8
+ fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b)
+ return RGB{
+ R: float64(r) / 255.0,
+ G: float64(g) / 255.0,
+ B: float64(b) / 255.0,
+ }
+}
+
+func RGBToHex(rgb RGB) string {
+ r := math.Max(0, math.Min(1, rgb.R))
+ g := math.Max(0, math.Min(1, rgb.G))
+ b := math.Max(0, math.Min(1, rgb.B))
+ return fmt.Sprintf("#%02x%02x%02x", int(r*255), int(g*255), int(b*255))
+}
+
+func RGBToHSV(rgb RGB) HSV {
+ max := math.Max(math.Max(rgb.R, rgb.G), rgb.B)
+ min := math.Min(math.Min(rgb.R, rgb.G), rgb.B)
+ delta := max - min
+
+ var h float64
+ switch {
+ case delta == 0:
+ h = 0
+ case max == rgb.R:
+ h = math.Mod((rgb.G-rgb.B)/delta, 6.0) / 6.0
+ case max == rgb.G:
+ h = ((rgb.B-rgb.R)/delta + 2.0) / 6.0
+ default:
+ h = ((rgb.R-rgb.G)/delta + 4.0) / 6.0
+ }
+
+ if h < 0 {
+ h += 1.0
+ }
+
+ var s float64
+ if max == 0 {
+ s = 0
+ } else {
+ s = delta / max
+ }
+
+ return HSV{H: h, S: s, V: max}
+}
+
+func HSVToRGB(hsv HSV) RGB {
+ h := hsv.H * 6.0
+ c := hsv.V * hsv.S
+ x := c * (1.0 - math.Abs(math.Mod(h, 2.0)-1.0))
+ m := hsv.V - c
+
+ var r, g, b float64
+ switch int(h) {
+ case 0:
+ r, g, b = c, x, 0
+ case 1:
+ r, g, b = x, c, 0
+ case 2:
+ r, g, b = 0, c, x
+ case 3:
+ r, g, b = 0, x, c
+ case 4:
+ r, g, b = x, 0, c
+ case 5:
+ r, g, b = c, 0, x
+ default:
+ r, g, b = c, 0, x
+ }
+
+ return RGB{R: r + m, G: g + m, B: b + m}
+}
+
+func sRGBToLinear(c float64) float64 {
+ if c <= 0.04045 {
+ return c / 12.92
+ }
+ return math.Pow((c+0.055)/1.055, 2.4)
+}
+
+func Luminance(hex string) float64 {
+ rgb := HexToRGB(hex)
+ return 0.2126*sRGBToLinear(rgb.R) + 0.7152*sRGBToLinear(rgb.G) + 0.0722*sRGBToLinear(rgb.B)
+}
+
+func ContrastRatio(hexFg, hexBg string) float64 {
+ lumFg := Luminance(hexFg)
+ lumBg := Luminance(hexBg)
+ lighter := math.Max(lumFg, lumBg)
+ darker := math.Min(lumFg, lumBg)
+ return (lighter + 0.05) / (darker + 0.05)
+}
+
+func getLstar(hex string) float64 {
+ rgb := HexToRGB(hex)
+ col := colorful.Color{R: rgb.R, G: rgb.G, B: rgb.B}
+ L, _, _ := col.Lab()
+ return L * 100.0 // go-colorful uses 0-1, we need 0-100 for DPS
+}
+
+// Lab to hex, clamping if needed
+func labToHex(L, a, b float64) string {
+ c := colorful.Lab(L/100.0, a, b) // back to 0-1 for go-colorful
+ r, g, b2 := c.Clamped().RGB255()
+ return fmt.Sprintf("#%02x%02x%02x", r, g, b2)
+}
+
+func DeltaPhiStar(hexFg, hexBg string, negativePolarity bool) float64 {
+ Lf := getLstar(hexFg)
+ Lb := getLstar(hexBg)
+
+ phi := 1.618
+ inv := 0.618
+ lc := math.Pow(math.Abs(math.Pow(Lb, phi)-math.Pow(Lf, phi)), inv)*1.414 - 40
+
+ if negativePolarity {
+ lc += 5
+ }
+
+ return lc
+}
+
+func DeltaPhiStarContrast(hexFg, hexBg string, isLightMode bool) float64 {
+ negativePolarity := !isLightMode
+ return DeltaPhiStar(hexFg, hexBg, negativePolarity)
+}
+
+func EnsureContrast(hexColor, hexBg string, minRatio float64, isLightMode bool) string {
+ currentRatio := ContrastRatio(hexColor, hexBg)
+ if currentRatio >= minRatio {
+ return hexColor
+ }
+
+ rgb := HexToRGB(hexColor)
+ hsv := RGBToHSV(rgb)
+
+ for step := 1; step < 30; step++ {
+ delta := float64(step) * 0.02
+
+ if isLightMode {
+ newV := math.Max(0, hsv.V-delta)
+ candidate := RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if ContrastRatio(candidate, hexBg) >= minRatio {
+ return candidate
+ }
+
+ newV = math.Min(1, hsv.V+delta)
+ candidate = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if ContrastRatio(candidate, hexBg) >= minRatio {
+ return candidate
+ }
+ } else {
+ newV := math.Min(1, hsv.V+delta)
+ candidate := RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if ContrastRatio(candidate, hexBg) >= minRatio {
+ return candidate
+ }
+
+ newV = math.Max(0, hsv.V-delta)
+ candidate = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if ContrastRatio(candidate, hexBg) >= minRatio {
+ return candidate
+ }
+ }
+ }
+
+ return hexColor
+}
+
+func EnsureContrastDPS(hexColor, hexBg string, minLc float64, isLightMode bool) string {
+ currentLc := DeltaPhiStarContrast(hexColor, hexBg, isLightMode)
+ if currentLc >= minLc {
+ return hexColor
+ }
+
+ rgb := HexToRGB(hexColor)
+ hsv := RGBToHSV(rgb)
+
+ for step := 1; step < 50; step++ {
+ delta := float64(step) * 0.015
+
+ if isLightMode {
+ newV := math.Max(0, hsv.V-delta)
+ candidate := RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if DeltaPhiStarContrast(candidate, hexBg, isLightMode) >= minLc {
+ return candidate
+ }
+
+ newV = math.Min(1, hsv.V+delta)
+ candidate = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if DeltaPhiStarContrast(candidate, hexBg, isLightMode) >= minLc {
+ return candidate
+ }
+ } else {
+ newV := math.Min(1, hsv.V+delta)
+ candidate := RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if DeltaPhiStarContrast(candidate, hexBg, isLightMode) >= minLc {
+ return candidate
+ }
+
+ newV = math.Max(0, hsv.V-delta)
+ candidate = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: hsv.S, V: newV}))
+ if DeltaPhiStarContrast(candidate, hexBg, isLightMode) >= minLc {
+ return candidate
+ }
+ }
+ }
+
+ return hexColor
+}
+
+// Nudge L* until contrast is good enough. Keeps hue intact unlike HSV fiddling.
+func EnsureContrastDPSLstar(hexColor, hexBg string, minLc float64, isLightMode bool) string {
+ current := DeltaPhiStarContrast(hexColor, hexBg, isLightMode)
+ if current >= minLc {
+ return hexColor
+ }
+
+ fg := HexToRGB(hexColor)
+ cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
+ Lf, af, bf := cf.Lab()
+
+ dir := 1.0
+ if isLightMode {
+ dir = -1.0 // light mode = darker text
+ }
+
+ step := 0.5
+ for range 120 {
+ Lf = math.Max(0, math.Min(100, Lf+dir*step))
+ cand := labToHex(Lf, af, bf)
+ if DeltaPhiStarContrast(cand, hexBg, isLightMode) >= minLc {
+ return cand
+ }
+ }
+
+ return hexColor
+}
+
+// Bidirectional contrast - tries both lighter and darker, picks closest to original
+func EnsureContrastDPSBidirectional(hexColor, hexBg string, minLc float64, isLightMode bool) string {
+ current := DeltaPhiStarContrast(hexColor, hexBg, isLightMode)
+ if current >= minLc {
+ return hexColor
+ }
+
+ fg := HexToRGB(hexColor)
+ cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
+ origL, af, bf := cf.Lab()
+
+ var darkerResult, lighterResult string
+ darkerL, lighterL := origL, origL
+ darkerFound, lighterFound := false, false
+
+ step := 0.5
+ for i := range 120 {
+ if !darkerFound {
+ darkerL = math.Max(0, origL-float64(i)*step)
+ cand := labToHex(darkerL, af, bf)
+ if DeltaPhiStarContrast(cand, hexBg, isLightMode) >= minLc {
+ darkerResult = cand
+ darkerFound = true
+ }
+ }
+ if !lighterFound {
+ lighterL = math.Min(100, origL+float64(i)*step)
+ cand := labToHex(lighterL, af, bf)
+ if DeltaPhiStarContrast(cand, hexBg, isLightMode) >= minLc {
+ lighterResult = cand
+ lighterFound = true
+ }
+ }
+ if darkerFound && lighterFound {
+ break
+ }
+ }
+
+ if darkerFound && lighterFound {
+ if math.Abs(darkerL-origL) <= math.Abs(lighterL-origL) {
+ return darkerResult
+ }
+ return lighterResult
+ }
+ if darkerFound {
+ return darkerResult
+ }
+ if lighterFound {
+ return lighterResult
+ }
+ return hexColor
+}
+
+type PaletteOptions struct {
+ IsLight bool
+ Background string
+ UseDPS bool
+}
+
+func ensureContrastAuto(hexColor, hexBg string, target float64, opts PaletteOptions) string {
+ if opts.UseDPS {
+ return EnsureContrastDPSLstar(hexColor, hexBg, target, opts.IsLight)
+ }
+ return EnsureContrast(hexColor, hexBg, target, opts.IsLight)
+}
+
+func ensureContrastBidirectional(hexColor, hexBg string, target float64, opts PaletteOptions) string {
+ if opts.UseDPS {
+ return EnsureContrastDPSBidirectional(hexColor, hexBg, target, opts.IsLight)
+ }
+ return EnsureContrast(hexColor, hexBg, target, opts.IsLight)
+}
+
+func blendHue(base, target, factor float64) float64 {
+ diff := target - base
+ if diff > 0.5 {
+ diff -= 1.0
+ } else if diff < -0.5 {
+ diff += 1.0
+ }
+ result := base + diff*factor
+ if result < 0 {
+ result += 1.0
+ } else if result >= 1.0 {
+ result -= 1.0
+ }
+ return result
+}
+
+func DeriveContainer(primary string, isLight bool) string {
+ rgb := HexToRGB(primary)
+ hsv := RGBToHSV(rgb)
+
+ if isLight {
+ containerV := math.Min(hsv.V*1.77, 1.0)
+ containerS := hsv.S * 0.32
+ return RGBToHex(HSVToRGB(HSV{H: hsv.H, S: containerS, V: containerV}))
+ }
+ containerV := hsv.V * 0.463
+ containerS := math.Min(hsv.S*1.834, 1.0)
+ return RGBToHex(HSVToRGB(HSV{H: hsv.H, S: containerS, V: containerV}))
+}
+
+func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
+ baseColor := DeriveContainer(primaryColor, opts.IsLight)
+
+ rgb := HexToRGB(baseColor)
+ hsv := RGBToHSV(rgb)
+
+ pr := HexToRGB(primaryColor)
+ ph := RGBToHSV(pr)
+
+ var palette Palette
+
+ var normalTextTarget, secondaryTarget float64
+ if opts.UseDPS {
+ normalTextTarget = 40.0
+ secondaryTarget = 35.0
+ } else {
+ normalTextTarget = 4.5
+ secondaryTarget = 3.0
+ }
+
+ var bgColor string
+ if opts.Background != "" {
+ bgColor = opts.Background
+ } else if opts.IsLight {
+ bgColor = "#f8f8f8"
+ } else {
+ bgColor = "#1a1a1a"
+ }
+ palette.Color0 = NewColorInfo(bgColor)
+
+ baseSat := math.Max(ph.S, 0.5)
+ baseVal := math.Max(ph.V, 0.5)
+
+ redH := blendHue(0.0, ph.H, 0.12)
+ greenH := blendHue(0.33, ph.H, 0.10)
+ yellowH := blendHue(0.14, ph.H, 0.04)
+
+ accentTarget := secondaryTarget * 0.7
+
+ if opts.IsLight {
+ redS := math.Min(baseSat*1.2, 1.0)
+ redV := baseVal * 0.95
+ palette.Color1 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: redH, S: redS, V: redV})), bgColor, normalTextTarget, opts))
+
+ greenS := math.Min(baseSat*1.3, 1.0)
+ greenV := baseVal * 0.75
+ palette.Color2 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: greenH, S: greenS, V: greenV})), bgColor, normalTextTarget, opts))
+
+ yellowS := math.Min(baseSat*1.5, 1.0)
+ yellowV := math.Min(baseVal*1.2, 1.0)
+ palette.Color3 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: yellowH, S: yellowS, V: yellowV})), bgColor, accentTarget, opts))
+
+ blueS := math.Min(ph.S*1.05, 1.0)
+ blueV := math.Min(ph.V*1.05, 1.0)
+ palette.Color4 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: ph.H, S: blueS, V: blueV})), bgColor, normalTextTarget, opts))
+
+ // Color5 matches primary_container exactly (light container in light mode)
+ container5 := DeriveContainer(primaryColor, true)
+ palette.Color5 = NewColorInfo(container5)
+
+ palette.Color6 = NewColorInfo(primaryColor)
+
+ gray7S := baseSat * 0.08
+ gray7V := baseVal * 0.28
+ palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
+
+ gray8S := baseSat * 0.05
+ gray8V := baseVal * 0.85
+ dimTarget := secondaryTarget * 0.5
+ palette.Color8 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, dimTarget, opts))
+
+ brightRedS := math.Min(baseSat*1.0, 1.0)
+ brightRedV := math.Min(baseVal*1.2, 1.0)
+ palette.Color9 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: redH, S: brightRedS, V: brightRedV})), bgColor, accentTarget, opts))
+
+ brightGreenS := math.Min(baseSat*1.1, 1.0)
+ brightGreenV := math.Min(baseVal*1.1, 1.0)
+ palette.Color10 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: greenH, S: brightGreenS, V: brightGreenV})), bgColor, accentTarget, opts))
+
+ brightYellowS := math.Min(baseSat*1.4, 1.0)
+ brightYellowV := math.Min(baseVal*1.3, 1.0)
+ palette.Color11 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: yellowH, S: brightYellowS, V: brightYellowV})), bgColor, accentTarget, opts))
+
+ brightBlueS := math.Min(ph.S*1.1, 1.0)
+ brightBlueV := math.Min(ph.V*1.15, 1.0)
+ palette.Color12 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: ph.H, S: brightBlueS, V: brightBlueV})), bgColor, accentTarget, opts))
+
+ lightContainer := DeriveContainer(primaryColor, true)
+ palette.Color13 = NewColorInfo(lightContainer)
+
+ brightCyanS := ph.S * 0.5
+ brightCyanV := math.Min(ph.V*1.3, 1.0)
+ palette.Color14 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: ph.H, S: brightCyanS, V: brightCyanV})))
+
+ white15S := baseSat * 0.04
+ white15V := math.Min(baseVal*1.5, 1.0)
+ palette.Color15 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: white15S, V: white15V})))
+ } else {
+ redS := math.Min(baseSat*1.1, 1.0)
+ redV := math.Min(baseVal*1.15, 1.0)
+ palette.Color1 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: redH, S: redS, V: redV})), bgColor, normalTextTarget, opts))
+
+ greenS := math.Min(baseSat*1.0, 1.0)
+ greenV := math.Min(baseVal*1.0, 1.0)
+ palette.Color2 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: greenH, S: greenS, V: greenV})), bgColor, normalTextTarget, opts))
+
+ yellowS := math.Min(baseSat*1.1, 1.0)
+ yellowV := math.Min(baseVal*1.25, 1.0)
+ palette.Color3 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: yellowH, S: yellowS, V: yellowV})), bgColor, normalTextTarget, opts))
+
+ // Slightly more saturated variant of primary
+ blueS := math.Min(ph.S*1.2, 1.0)
+ blueV := ph.V * 0.95
+ palette.Color4 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: ph.H, S: blueS, V: blueV})), bgColor, normalTextTarget, opts))
+
+ // Color5 matches primary_container exactly (dark container in dark mode)
+ darkContainer := DeriveContainer(primaryColor, false)
+ palette.Color5 = NewColorInfo(darkContainer)
+
+ palette.Color6 = NewColorInfo(primaryColor)
+
+ gray7S := baseSat * 0.12
+ gray7V := math.Min(baseVal*1.05, 1.0)
+ palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
+
+ gray8S := baseSat * 0.15
+ gray8V := baseVal * 0.65
+ palette.Color8 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, secondaryTarget, opts))
+
+ brightRedS := math.Min(baseSat*0.75, 1.0)
+ brightRedV := math.Min(baseVal*1.35, 1.0)
+ palette.Color9 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: redH, S: brightRedS, V: brightRedV})), bgColor, accentTarget, opts))
+
+ brightGreenS := math.Min(baseSat*0.7, 1.0)
+ brightGreenV := math.Min(baseVal*1.2, 1.0)
+ palette.Color10 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: greenH, S: brightGreenS, V: brightGreenV})), bgColor, accentTarget, opts))
+
+ brightYellowS := math.Min(baseSat*0.7, 1.0)
+ brightYellowV := math.Min(baseVal*1.5, 1.0)
+ palette.Color11 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: yellowH, S: brightYellowS, V: brightYellowV})), bgColor, accentTarget, opts))
+
+ // Create a gradient of primary variants: Color12 -> Color13 -> Color14 -> Color15 (near white)
+ // Color12: Start of the lighter gradient - slightly desaturated
+ brightBlueS := ph.S * 0.85
+ brightBlueV := math.Min(ph.V*1.1, 1.0)
+ palette.Color12 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: ph.H, S: brightBlueS, V: brightBlueV})), bgColor, accentTarget, opts))
+
+ // Medium-high saturation pastel primary
+ color13S := ph.S * 0.7
+ color13V := math.Min(ph.V*1.3, 1.0)
+ palette.Color13 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: ph.H, S: color13S, V: color13V})))
+
+ // Lower saturation, lighter variant
+ color14S := ph.S * 0.45
+ color14V := math.Min(ph.V*1.4, 1.0)
+ palette.Color14 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: ph.H, S: color14S, V: color14V})))
+
+ white15S := baseSat * 0.05
+ white15V := math.Min(baseVal*1.45, 1.0)
+ palette.Color15 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: white15S, V: white15V})), bgColor, normalTextTarget, opts))
+ }
+
+ return palette
+}
+
+type VariantOptions struct {
+ PrimaryDark string
+ PrimaryLight string
+ Background string
+ UseDPS bool
+ IsLightMode bool
+}
+
+func mergeColorInfo(dark, light ColorInfo, isLightMode bool) VariantColorInfo {
+ darkVal := VariantColorValue{Hex: dark.Hex, HexStripped: dark.HexStripped}
+ lightVal := VariantColorValue{Hex: light.Hex, HexStripped: light.HexStripped}
+
+ defaultVal := darkVal
+ if isLightMode {
+ defaultVal = lightVal
+ }
+
+ return VariantColorInfo{
+ Dark: darkVal,
+ Light: lightVal,
+ Default: defaultVal,
+ }
+}
+
+func GenerateVariantPalette(opts VariantOptions) VariantPalette {
+ darkOpts := PaletteOptions{IsLight: false, Background: opts.Background, UseDPS: opts.UseDPS}
+ lightOpts := PaletteOptions{IsLight: true, Background: opts.Background, UseDPS: opts.UseDPS}
+
+ dark := GeneratePalette(opts.PrimaryDark, darkOpts)
+ light := GeneratePalette(opts.PrimaryLight, lightOpts)
+
+ return VariantPalette{
+ Color0: mergeColorInfo(dark.Color0, light.Color0, opts.IsLightMode),
+ Color1: mergeColorInfo(dark.Color1, light.Color1, opts.IsLightMode),
+ Color2: mergeColorInfo(dark.Color2, light.Color2, opts.IsLightMode),
+ Color3: mergeColorInfo(dark.Color3, light.Color3, opts.IsLightMode),
+ Color4: mergeColorInfo(dark.Color4, light.Color4, opts.IsLightMode),
+ Color5: mergeColorInfo(dark.Color5, light.Color5, opts.IsLightMode),
+ Color6: mergeColorInfo(dark.Color6, light.Color6, opts.IsLightMode),
+ Color7: mergeColorInfo(dark.Color7, light.Color7, opts.IsLightMode),
+ Color8: mergeColorInfo(dark.Color8, light.Color8, opts.IsLightMode),
+ Color9: mergeColorInfo(dark.Color9, light.Color9, opts.IsLightMode),
+ Color10: mergeColorInfo(dark.Color10, light.Color10, opts.IsLightMode),
+ Color11: mergeColorInfo(dark.Color11, light.Color11, opts.IsLightMode),
+ Color12: mergeColorInfo(dark.Color12, light.Color12, opts.IsLightMode),
+ Color13: mergeColorInfo(dark.Color13, light.Color13, opts.IsLightMode),
+ Color14: mergeColorInfo(dark.Color14, light.Color14, opts.IsLightMode),
+ Color15: mergeColorInfo(dark.Color15, light.Color15, opts.IsLightMode),
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16_test.go
new file mode 100644
index 0000000..9e6b5a8
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/dank16_test.go
@@ -0,0 +1,681 @@
+package dank16
+
+import (
+ "math"
+ "testing"
+)
+
+func TestHexToRGB(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected RGB
+ }{
+ {
+ name: "black with hash",
+ input: "#000000",
+ expected: RGB{R: 0.0, G: 0.0, B: 0.0},
+ },
+ {
+ name: "white with hash",
+ input: "#ffffff",
+ expected: RGB{R: 1.0, G: 1.0, B: 1.0},
+ },
+ {
+ name: "red without hash",
+ input: "ff0000",
+ expected: RGB{R: 1.0, G: 0.0, B: 0.0},
+ },
+ {
+ name: "purple",
+ input: "#625690",
+ expected: RGB{R: 0.3843137254901961, G: 0.33725490196078434, B: 0.5647058823529412},
+ },
+ {
+ name: "mid gray",
+ input: "#808080",
+ expected: RGB{R: 0.5019607843137255, G: 0.5019607843137255, B: 0.5019607843137255},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := HexToRGB(tt.input)
+ if !floatEqual(result.R, tt.expected.R) || !floatEqual(result.G, tt.expected.G) || !floatEqual(result.B, tt.expected.B) {
+ t.Errorf("HexToRGB(%s) = %v, expected %v", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestRGBToHex(t *testing.T) {
+ tests := []struct {
+ name string
+ input RGB
+ expected string
+ }{
+ {
+ name: "black",
+ input: RGB{R: 0.0, G: 0.0, B: 0.0},
+ expected: "#000000",
+ },
+ {
+ name: "white",
+ input: RGB{R: 1.0, G: 1.0, B: 1.0},
+ expected: "#ffffff",
+ },
+ {
+ name: "red",
+ input: RGB{R: 1.0, G: 0.0, B: 0.0},
+ expected: "#ff0000",
+ },
+ {
+ name: "clamping above 1.0",
+ input: RGB{R: 1.5, G: 0.5, B: 0.5},
+ expected: "#ff7f7f",
+ },
+ {
+ name: "clamping below 0.0",
+ input: RGB{R: -0.5, G: 0.5, B: 0.5},
+ expected: "#007f7f",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := RGBToHex(tt.input)
+ if result != tt.expected {
+ t.Errorf("RGBToHex(%v) = %s, expected %s", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestRGBToHSV(t *testing.T) {
+ tests := []struct {
+ name string
+ input RGB
+ expected HSV
+ }{
+ {
+ name: "black",
+ input: RGB{R: 0.0, G: 0.0, B: 0.0},
+ expected: HSV{H: 0.0, S: 0.0, V: 0.0},
+ },
+ {
+ name: "white",
+ input: RGB{R: 1.0, G: 1.0, B: 1.0},
+ expected: HSV{H: 0.0, S: 0.0, V: 1.0},
+ },
+ {
+ name: "red",
+ input: RGB{R: 1.0, G: 0.0, B: 0.0},
+ expected: HSV{H: 0.0, S: 1.0, V: 1.0},
+ },
+ {
+ name: "green",
+ input: RGB{R: 0.0, G: 1.0, B: 0.0},
+ expected: HSV{H: 0.3333333333333333, S: 1.0, V: 1.0},
+ },
+ {
+ name: "blue",
+ input: RGB{R: 0.0, G: 0.0, B: 1.0},
+ expected: HSV{H: 0.6666666666666666, S: 1.0, V: 1.0},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := RGBToHSV(tt.input)
+ if !floatEqual(result.H, tt.expected.H) || !floatEqual(result.S, tt.expected.S) || !floatEqual(result.V, tt.expected.V) {
+ t.Errorf("RGBToHSV(%v) = %v, expected %v", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestHSVToRGB(t *testing.T) {
+ tests := []struct {
+ name string
+ input HSV
+ expected RGB
+ }{
+ {
+ name: "black",
+ input: HSV{H: 0.0, S: 0.0, V: 0.0},
+ expected: RGB{R: 0.0, G: 0.0, B: 0.0},
+ },
+ {
+ name: "white",
+ input: HSV{H: 0.0, S: 0.0, V: 1.0},
+ expected: RGB{R: 1.0, G: 1.0, B: 1.0},
+ },
+ {
+ name: "red",
+ input: HSV{H: 0.0, S: 1.0, V: 1.0},
+ expected: RGB{R: 1.0, G: 0.0, B: 0.0},
+ },
+ {
+ name: "green",
+ input: HSV{H: 0.3333333333333333, S: 1.0, V: 1.0},
+ expected: RGB{R: 0.0, G: 1.0, B: 0.0},
+ },
+ {
+ name: "blue",
+ input: HSV{H: 0.6666666666666666, S: 1.0, V: 1.0},
+ expected: RGB{R: 0.0, G: 0.0, B: 1.0},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := HSVToRGB(tt.input)
+ if !floatEqual(result.R, tt.expected.R) || !floatEqual(result.G, tt.expected.G) || !floatEqual(result.B, tt.expected.B) {
+ t.Errorf("HSVToRGB(%v) = %v, expected %v", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestLuminance(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected float64
+ }{
+ {
+ name: "black",
+ input: "#000000",
+ expected: 0.0,
+ },
+ {
+ name: "white",
+ input: "#ffffff",
+ expected: 1.0,
+ },
+ {
+ name: "red",
+ input: "#ff0000",
+ expected: 0.2126,
+ },
+ {
+ name: "green",
+ input: "#00ff00",
+ expected: 0.7152,
+ },
+ {
+ name: "blue",
+ input: "#0000ff",
+ expected: 0.0722,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := Luminance(tt.input)
+ if !floatEqual(result, tt.expected) {
+ t.Errorf("Luminance(%s) = %f, expected %f", tt.input, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestContrastRatio(t *testing.T) {
+ tests := []struct {
+ name string
+ fg string
+ bg string
+ expected float64
+ }{
+ {
+ name: "black on white",
+ fg: "#000000",
+ bg: "#ffffff",
+ expected: 21.0,
+ },
+ {
+ name: "white on black",
+ fg: "#ffffff",
+ bg: "#000000",
+ expected: 21.0,
+ },
+ {
+ name: "same color",
+ fg: "#808080",
+ bg: "#808080",
+ expected: 1.0,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := ContrastRatio(tt.fg, tt.bg)
+ if !floatEqual(result, tt.expected) {
+ t.Errorf("ContrastRatio(%s, %s) = %f, expected %f", tt.fg, tt.bg, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestEnsureContrast(t *testing.T) {
+ tests := []struct {
+ name string
+ color string
+ bg string
+ minRatio float64
+ isLightMode bool
+ }{
+ {
+ name: "already sufficient contrast dark mode",
+ color: "#ffffff",
+ bg: "#000000",
+ minRatio: 4.5,
+ isLightMode: false,
+ },
+ {
+ name: "already sufficient contrast light mode",
+ color: "#000000",
+ bg: "#ffffff",
+ minRatio: 4.5,
+ isLightMode: true,
+ },
+ {
+ name: "needs adjustment dark mode",
+ color: "#404040",
+ bg: "#1a1a1a",
+ minRatio: 4.5,
+ isLightMode: false,
+ },
+ {
+ name: "needs adjustment light mode",
+ color: "#c0c0c0",
+ bg: "#f8f8f8",
+ minRatio: 4.5,
+ isLightMode: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := EnsureContrast(tt.color, tt.bg, tt.minRatio, tt.isLightMode)
+ actualRatio := ContrastRatio(result, tt.bg)
+ if actualRatio < tt.minRatio {
+ t.Errorf("EnsureContrast(%s, %s, %f, %t) = %s with ratio %f, expected ratio >= %f",
+ tt.color, tt.bg, tt.minRatio, tt.isLightMode, result, actualRatio, tt.minRatio)
+ }
+ })
+ }
+}
+
+func TestGeneratePalette(t *testing.T) {
+ tests := []struct {
+ name string
+ base string
+ opts PaletteOptions
+ }{
+ {
+ name: "dark theme default",
+ base: "#625690",
+ opts: PaletteOptions{IsLight: false},
+ },
+ {
+ name: "light theme default",
+ base: "#625690",
+ opts: PaletteOptions{IsLight: true},
+ },
+ {
+ name: "light theme with custom background",
+ base: "#625690",
+ opts: PaletteOptions{
+ IsLight: true,
+ Background: "#fafafa",
+ },
+ },
+ {
+ name: "dark theme with custom background",
+ base: "#625690",
+ opts: PaletteOptions{
+ IsLight: false,
+ Background: "#0a0a0a",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := GeneratePalette(tt.base, tt.opts)
+
+ colors := []ColorInfo{
+ result.Color0, result.Color1, result.Color2, result.Color3,
+ result.Color4, result.Color5, result.Color6, result.Color7,
+ result.Color8, result.Color9, result.Color10, result.Color11,
+ result.Color12, result.Color13, result.Color14, result.Color15,
+ }
+
+ for i, color := range colors {
+ if len(color.Hex) != 7 || color.Hex[0] != '#' {
+ t.Errorf("Color at index %d (%s) is not a valid hex color", i, color.Hex)
+ }
+ }
+
+ if tt.opts.Background != "" && result.Color0.Hex != tt.opts.Background {
+ t.Errorf("Background color = %s, expected %s", result.Color0.Hex, tt.opts.Background)
+ } else if !tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#1a1a1a" {
+ t.Errorf("Dark mode background = %s, expected #1a1a1a", result.Color0.Hex)
+ } else if tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#f8f8f8" {
+ t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex)
+ }
+
+ // Color15 is now derived from primary, so just verify it's a valid color
+ // and has appropriate luminance for the mode (now theme-tinted, not pure white/black)
+ color15Lum := Luminance(result.Color15.Hex)
+ if tt.opts.IsLight {
+ // Light mode: Color15 should still be relatively light
+ if color15Lum < 0.5 {
+ t.Errorf("Light mode Color15 = %s (lum %.2f) is too dark", result.Color15.Hex, color15Lum)
+ }
+ } else {
+ // Dark mode: Color15 should be light (but may have theme tint, so lower threshold)
+ if color15Lum < 0.5 {
+ t.Errorf("Dark mode Color15 = %s (lum %.2f) is too dark", result.Color15.Hex, color15Lum)
+ }
+ }
+ })
+ }
+}
+
+func TestRoundTripConversion(t *testing.T) {
+ testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}
+
+ for _, hex := range testColors {
+ t.Run(hex, func(t *testing.T) {
+ rgb := HexToRGB(hex)
+ result := RGBToHex(rgb)
+ if result != hex {
+ t.Errorf("Round trip %s -> RGB -> %s failed", hex, result)
+ }
+ })
+ }
+}
+
+func TestRGBHSVRoundTrip(t *testing.T) {
+ testCases := []RGB{
+ {R: 0.0, G: 0.0, B: 0.0},
+ {R: 1.0, G: 1.0, B: 1.0},
+ {R: 1.0, G: 0.0, B: 0.0},
+ {R: 0.0, G: 1.0, B: 0.0},
+ {R: 0.0, G: 0.0, B: 1.0},
+ {R: 0.5, G: 0.5, B: 0.5},
+ {R: 0.3843137254901961, G: 0.33725490196078434, B: 0.5647058823529412},
+ }
+
+ for _, rgb := range testCases {
+ t.Run("", func(t *testing.T) {
+ hsv := RGBToHSV(rgb)
+ result := HSVToRGB(hsv)
+ if !floatEqual(result.R, rgb.R) || !floatEqual(result.G, rgb.G) || !floatEqual(result.B, rgb.B) {
+ t.Errorf("Round trip RGB->HSV->RGB failed: %v -> %v -> %v", rgb, hsv, result)
+ }
+ })
+ }
+}
+
+func floatEqual(a, b float64) bool {
+ return math.Abs(a-b) < 1e-9
+}
+
+func TestDeltaPhiStar(t *testing.T) {
+ tests := []struct {
+ name string
+ fg string
+ bg string
+ negativePolarity bool
+ minExpected float64
+ }{
+ {
+ name: "white on black (negative polarity)",
+ fg: "#ffffff",
+ bg: "#000000",
+ negativePolarity: true,
+ minExpected: 100.0,
+ },
+ {
+ name: "black on white (positive polarity)",
+ fg: "#000000",
+ bg: "#ffffff",
+ negativePolarity: false,
+ minExpected: 100.0,
+ },
+ {
+ name: "low contrast same color",
+ fg: "#808080",
+ bg: "#808080",
+ negativePolarity: false,
+ minExpected: -40.0,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := DeltaPhiStar(tt.fg, tt.bg, tt.negativePolarity)
+ if result < tt.minExpected {
+ t.Errorf("DeltaPhiStar(%s, %s, %v) = %f, expected >= %f",
+ tt.fg, tt.bg, tt.negativePolarity, result, tt.minExpected)
+ }
+ })
+ }
+}
+
+func TestDeltaPhiStarContrast(t *testing.T) {
+ tests := []struct {
+ name string
+ fg string
+ bg string
+ isLightMode bool
+ minExpected float64
+ }{
+ {
+ name: "white on black (dark mode)",
+ fg: "#ffffff",
+ bg: "#000000",
+ isLightMode: false,
+ minExpected: 100.0,
+ },
+ {
+ name: "black on white (light mode)",
+ fg: "#000000",
+ bg: "#ffffff",
+ isLightMode: true,
+ minExpected: 100.0,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := DeltaPhiStarContrast(tt.fg, tt.bg, tt.isLightMode)
+ if result < tt.minExpected {
+ t.Errorf("DeltaPhiStarContrast(%s, %s, %v) = %f, expected >= %f",
+ tt.fg, tt.bg, tt.isLightMode, result, tt.minExpected)
+ }
+ })
+ }
+}
+
+func TestEnsureContrastDPS(t *testing.T) {
+ tests := []struct {
+ name string
+ color string
+ bg string
+ minLc float64
+ isLightMode bool
+ }{
+ {
+ name: "already sufficient contrast dark mode",
+ color: "#ffffff",
+ bg: "#000000",
+ minLc: 60.0,
+ isLightMode: false,
+ },
+ {
+ name: "already sufficient contrast light mode",
+ color: "#000000",
+ bg: "#ffffff",
+ minLc: 60.0,
+ isLightMode: true,
+ },
+ {
+ name: "needs adjustment dark mode",
+ color: "#404040",
+ bg: "#1a1a1a",
+ minLc: 60.0,
+ isLightMode: false,
+ },
+ {
+ name: "needs adjustment light mode",
+ color: "#c0c0c0",
+ bg: "#f8f8f8",
+ minLc: 60.0,
+ isLightMode: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := EnsureContrastDPS(tt.color, tt.bg, tt.minLc, tt.isLightMode)
+ actualLc := DeltaPhiStarContrast(result, tt.bg, tt.isLightMode)
+ if actualLc < tt.minLc {
+ t.Errorf("EnsureContrastDPS(%s, %s, %f, %t) = %s with Lc %f, expected Lc >= %f",
+ tt.color, tt.bg, tt.minLc, tt.isLightMode, result, actualLc, tt.minLc)
+ }
+ })
+ }
+}
+
+func TestGeneratePaletteWithDPS(t *testing.T) {
+ tests := []struct {
+ name string
+ base string
+ opts PaletteOptions
+ }{
+ {
+ name: "dark theme with DPS",
+ base: "#625690",
+ opts: PaletteOptions{IsLight: false, UseDPS: true},
+ },
+ {
+ name: "light theme with DPS",
+ base: "#625690",
+ opts: PaletteOptions{IsLight: true, UseDPS: true},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := GeneratePalette(tt.base, tt.opts)
+
+ colors := []ColorInfo{
+ result.Color0, result.Color1, result.Color2, result.Color3,
+ result.Color4, result.Color5, result.Color6, result.Color7,
+ result.Color8, result.Color9, result.Color10, result.Color11,
+ result.Color12, result.Color13, result.Color14, result.Color15,
+ }
+
+ for i, color := range colors {
+ if len(color.Hex) != 7 || color.Hex[0] != '#' {
+ t.Errorf("Color at index %d (%s) is not a valid hex color", i, color.Hex)
+ }
+ }
+
+ bgColor := result.Color0.Hex
+ for i := 1; i < 8; i++ {
+ // Skip Color5 (container) and Color6 (exact primary) - intentionally not contrast-adjusted
+ if i == 5 || i == 6 {
+ continue
+ }
+ lc := DeltaPhiStarContrast(colors[i].Hex, bgColor, tt.opts.IsLight)
+ minLc := 30.0
+ if lc < minLc && lc > 0 {
+ t.Errorf("Color %d (%s) has insufficient DPS contrast %f with background %s (expected >= %f)",
+ i, colors[i].Hex, lc, bgColor, minLc)
+ }
+ }
+ })
+ }
+}
+
+func TestDeriveContainer(t *testing.T) {
+ tests := []struct {
+ name string
+ primary string
+ isLight bool
+ expected string
+ }{
+ {
+ name: "dark mode",
+ primary: "#ccbdff",
+ isLight: false,
+ expected: "#4a3e76",
+ },
+ {
+ name: "light mode",
+ primary: "#625690",
+ isLight: true,
+ expected: "#e7deff",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := DeriveContainer(tt.primary, tt.isLight)
+
+ resultRGB := HexToRGB(result)
+ expectedRGB := HexToRGB(tt.expected)
+
+ rDiff := math.Abs(resultRGB.R - expectedRGB.R)
+ gDiff := math.Abs(resultRGB.G - expectedRGB.G)
+ bDiff := math.Abs(resultRGB.B - expectedRGB.B)
+
+ tolerance := 0.02
+ if rDiff > tolerance || gDiff > tolerance || bDiff > tolerance {
+ t.Errorf("DeriveContainer(%s, %v) = %s, expected %s (RGB diff: R:%.4f G:%.4f B:%.4f)",
+ tt.primary, tt.isLight, result, tt.expected, rDiff, gDiff, bDiff)
+ }
+ })
+ }
+}
+
+func TestContrastAlgorithmComparison(t *testing.T) {
+ base := "#625690"
+
+ optsWCAG := PaletteOptions{IsLight: false, UseDPS: false}
+ optsDPS := PaletteOptions{IsLight: false, UseDPS: true}
+
+ paletteWCAG := GeneratePalette(base, optsWCAG)
+ paletteDPS := GeneratePalette(base, optsDPS)
+
+ wcagColors := []ColorInfo{
+ paletteWCAG.Color0, paletteWCAG.Color1, paletteWCAG.Color2, paletteWCAG.Color3,
+ paletteWCAG.Color4, paletteWCAG.Color5, paletteWCAG.Color6, paletteWCAG.Color7,
+ paletteWCAG.Color8, paletteWCAG.Color9, paletteWCAG.Color10, paletteWCAG.Color11,
+ paletteWCAG.Color12, paletteWCAG.Color13, paletteWCAG.Color14, paletteWCAG.Color15,
+ }
+ dpsColors := []ColorInfo{
+ paletteDPS.Color0, paletteDPS.Color1, paletteDPS.Color2, paletteDPS.Color3,
+ paletteDPS.Color4, paletteDPS.Color5, paletteDPS.Color6, paletteDPS.Color7,
+ paletteDPS.Color8, paletteDPS.Color9, paletteDPS.Color10, paletteDPS.Color11,
+ paletteDPS.Color12, paletteDPS.Color13, paletteDPS.Color14, paletteDPS.Color15,
+ }
+
+ if paletteWCAG.Color0.Hex != paletteDPS.Color0.Hex {
+ t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG.Color0.Hex, paletteDPS.Color0.Hex)
+ }
+
+ differentCount := 0
+ for i := range 16 {
+ if wcagColors[i].Hex != dpsColors[i].Hex {
+ differentCount++
+ }
+ }
+
+ t.Logf("WCAG and DPS palettes differ in %d/16 colors", differentCount)
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/terminals.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/terminals.go
new file mode 100644
index 0000000..9423311
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/dank16/terminals.go
@@ -0,0 +1,135 @@
+package dank16
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+func GenerateJSON(p Palette) string {
+ marshalled, _ := json.Marshal(p)
+ return string(marshalled)
+}
+
+func GenerateVariantJSON(p VariantPalette) string {
+ marshalled, _ := json.Marshal(p)
+ return string(marshalled)
+}
+
+func GenerateKittyTheme(p Palette) string {
+ var result strings.Builder
+ fmt.Fprintf(&result, "color0 %s\n", p.Color0.Hex)
+ fmt.Fprintf(&result, "color1 %s\n", p.Color1.Hex)
+ fmt.Fprintf(&result, "color2 %s\n", p.Color2.Hex)
+ fmt.Fprintf(&result, "color3 %s\n", p.Color3.Hex)
+ fmt.Fprintf(&result, "color4 %s\n", p.Color4.Hex)
+ fmt.Fprintf(&result, "color5 %s\n", p.Color5.Hex)
+ fmt.Fprintf(&result, "color6 %s\n", p.Color6.Hex)
+ fmt.Fprintf(&result, "color7 %s\n", p.Color7.Hex)
+ fmt.Fprintf(&result, "color8 %s\n", p.Color8.Hex)
+ fmt.Fprintf(&result, "color9 %s\n", p.Color9.Hex)
+ fmt.Fprintf(&result, "color10 %s\n", p.Color10.Hex)
+ fmt.Fprintf(&result, "color11 %s\n", p.Color11.Hex)
+ fmt.Fprintf(&result, "color12 %s\n", p.Color12.Hex)
+ fmt.Fprintf(&result, "color13 %s\n", p.Color13.Hex)
+ fmt.Fprintf(&result, "color14 %s\n", p.Color14.Hex)
+ fmt.Fprintf(&result, "color15 %s\n", p.Color15.Hex)
+ return result.String()
+}
+
+func GenerateFootTheme(p Palette) string {
+ var result strings.Builder
+ fmt.Fprintf(&result, "regular0=%s\n", p.Color0.HexStripped)
+ fmt.Fprintf(&result, "regular1=%s\n", p.Color1.HexStripped)
+ fmt.Fprintf(&result, "regular2=%s\n", p.Color2.HexStripped)
+ fmt.Fprintf(&result, "regular3=%s\n", p.Color3.HexStripped)
+ fmt.Fprintf(&result, "regular4=%s\n", p.Color4.HexStripped)
+ fmt.Fprintf(&result, "regular5=%s\n", p.Color5.HexStripped)
+ fmt.Fprintf(&result, "regular6=%s\n", p.Color6.HexStripped)
+ fmt.Fprintf(&result, "regular7=%s\n", p.Color7.HexStripped)
+ fmt.Fprintf(&result, "bright0=%s\n", p.Color8.HexStripped)
+ fmt.Fprintf(&result, "bright1=%s\n", p.Color9.HexStripped)
+ fmt.Fprintf(&result, "bright2=%s\n", p.Color10.HexStripped)
+ fmt.Fprintf(&result, "bright3=%s\n", p.Color11.HexStripped)
+ fmt.Fprintf(&result, "bright4=%s\n", p.Color12.HexStripped)
+ fmt.Fprintf(&result, "bright5=%s\n", p.Color13.HexStripped)
+ fmt.Fprintf(&result, "bright6=%s\n", p.Color14.HexStripped)
+ fmt.Fprintf(&result, "bright7=%s\n", p.Color15.HexStripped)
+ return result.String()
+}
+
+func GenerateAlacrittyTheme(p Palette) string {
+ var result strings.Builder
+ result.WriteString("[colors.normal]\n")
+ fmt.Fprintf(&result, "black = '%s'\n", p.Color0.Hex)
+ fmt.Fprintf(&result, "red = '%s'\n", p.Color1.Hex)
+ fmt.Fprintf(&result, "green = '%s'\n", p.Color2.Hex)
+ fmt.Fprintf(&result, "yellow = '%s'\n", p.Color3.Hex)
+ fmt.Fprintf(&result, "blue = '%s'\n", p.Color4.Hex)
+ fmt.Fprintf(&result, "magenta = '%s'\n", p.Color5.Hex)
+ fmt.Fprintf(&result, "cyan = '%s'\n", p.Color6.Hex)
+ fmt.Fprintf(&result, "white = '%s'\n", p.Color7.Hex)
+ result.WriteString("\n[colors.bright]\n")
+ fmt.Fprintf(&result, "black = '%s'\n", p.Color8.Hex)
+ fmt.Fprintf(&result, "red = '%s'\n", p.Color9.Hex)
+ fmt.Fprintf(&result, "green = '%s'\n", p.Color10.Hex)
+ fmt.Fprintf(&result, "yellow = '%s'\n", p.Color11.Hex)
+ fmt.Fprintf(&result, "blue = '%s'\n", p.Color12.Hex)
+ fmt.Fprintf(&result, "magenta = '%s'\n", p.Color13.Hex)
+ fmt.Fprintf(&result, "cyan = '%s'\n", p.Color14.Hex)
+ fmt.Fprintf(&result, "white = '%s'\n", p.Color15.Hex)
+ return result.String()
+}
+
+func GenerateGhosttyTheme(p Palette) string {
+ var result strings.Builder
+ fmt.Fprintf(&result, "palette = 0=%s\n", p.Color0.Hex)
+ fmt.Fprintf(&result, "palette = 1=%s\n", p.Color1.Hex)
+ fmt.Fprintf(&result, "palette = 2=%s\n", p.Color2.Hex)
+ fmt.Fprintf(&result, "palette = 3=%s\n", p.Color3.Hex)
+ fmt.Fprintf(&result, "palette = 4=%s\n", p.Color4.Hex)
+ fmt.Fprintf(&result, "palette = 5=%s\n", p.Color5.Hex)
+ fmt.Fprintf(&result, "palette = 6=%s\n", p.Color6.Hex)
+ fmt.Fprintf(&result, "palette = 7=%s\n", p.Color7.Hex)
+ fmt.Fprintf(&result, "palette = 8=%s\n", p.Color8.Hex)
+ fmt.Fprintf(&result, "palette = 9=%s\n", p.Color9.Hex)
+ fmt.Fprintf(&result, "palette = 10=%s\n", p.Color10.Hex)
+ fmt.Fprintf(&result, "palette = 11=%s\n", p.Color11.Hex)
+ fmt.Fprintf(&result, "palette = 12=%s\n", p.Color12.Hex)
+ fmt.Fprintf(&result, "palette = 13=%s\n", p.Color13.Hex)
+ fmt.Fprintf(&result, "palette = 14=%s\n", p.Color14.Hex)
+ fmt.Fprintf(&result, "palette = 15=%s\n", p.Color15.Hex)
+ return result.String()
+}
+
+func GenerateWeztermTheme(p Palette) string {
+ var result strings.Builder
+ fmt.Fprintf(&result, "ansi = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
+ p.Color0.Hex, p.Color1.Hex, p.Color2.Hex, p.Color3.Hex,
+ p.Color4.Hex, p.Color5.Hex, p.Color6.Hex, p.Color7.Hex)
+ fmt.Fprintf(&result, "brights = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
+ p.Color8.Hex, p.Color9.Hex, p.Color10.Hex, p.Color11.Hex,
+ p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
+ return result.String()
+}
+
+func GenerateNeovimTheme(p Palette) string {
+ var result strings.Builder
+ fmt.Fprintf(&result, "vim.g.terminal_color_0 = \"%s\"\n", p.Color0.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_1 = \"%s\"\n", p.Color1.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_2 = \"%s\"\n", p.Color2.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_3 = \"%s\"\n", p.Color3.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_4 = \"%s\"\n", p.Color4.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_5 = \"%s\"\n", p.Color5.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_6 = \"%s\"\n", p.Color6.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_7 = \"%s\"\n", p.Color7.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_8 = \"%s\"\n", p.Color8.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_9 = \"%s\"\n", p.Color9.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_10 = \"%s\"\n", p.Color10.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_11 = \"%s\"\n", p.Color11.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_12 = \"%s\"\n", p.Color12.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_13 = \"%s\"\n", p.Color13.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_14 = \"%s\"\n", p.Color14.Hex)
+ fmt.Fprintf(&result, "vim.g.terminal_color_15 = \"%s\"\n", p.Color15.Hex)
+ return result.String()
+}