migrate accent colours from HSL to oklch, inject theme into admin
Phase 1: Replace hex_to_hsl with hex_to_oklch in CSSGenerator, output --t-accent-l/c/h instead of --t-accent-h/s/l. All 46 HSL accent references across theme-semantic.css, theme-layer2-attributes.css, and shop/components.css replaced with oklch/color-mix equivalents. Dead style*= attribute selectors for button variants replaced with proper class-based selectors. Added color-scheme: light/dark to mood output. Phase 2: Add LoadTheme plug to admin pipeline, extend AdminLayoutHook with theme_settings and generated_css assigns, add font preloads and generated CSS injection to admin_root.html.heex. No visual changes to admin yet — .themed wrapper added in next phase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -61,7 +61,8 @@ defmodule Berrypod.Theme.CSSGenerator do
|
||||
# Mood colors - surface, text, and border colors
|
||||
defp generate_mood("neutral") do
|
||||
"""
|
||||
--t-surface-base: #ffffff;
|
||||
color-scheme: light;
|
||||
--t-surface-base: #ffffff;
|
||||
--t-surface-raised: #ffffff;
|
||||
--t-surface-sunken: #f5f5f5;
|
||||
--t-surface-overlay: rgba(255, 255, 255, 0.95);
|
||||
@@ -76,7 +77,8 @@ defmodule Berrypod.Theme.CSSGenerator do
|
||||
|
||||
defp generate_mood("warm") do
|
||||
"""
|
||||
--t-surface-base: #fdf8f3;
|
||||
color-scheme: light;
|
||||
--t-surface-base: #fdf8f3;
|
||||
--t-surface-raised: #fffcf8;
|
||||
--t-surface-sunken: #f5ebe0;
|
||||
--t-surface-overlay: rgba(253, 248, 243, 0.95);
|
||||
@@ -91,7 +93,8 @@ defmodule Berrypod.Theme.CSSGenerator do
|
||||
|
||||
defp generate_mood("cool") do
|
||||
"""
|
||||
--t-surface-base: #f4f7fb;
|
||||
color-scheme: light;
|
||||
--t-surface-base: #f4f7fb;
|
||||
--t-surface-raised: #f8fafc;
|
||||
--t-surface-sunken: #e8eff7;
|
||||
--t-surface-overlay: rgba(244, 247, 251, 0.95);
|
||||
@@ -106,7 +109,8 @@ defmodule Berrypod.Theme.CSSGenerator do
|
||||
|
||||
defp generate_mood("dark") do
|
||||
"""
|
||||
--t-surface-base: #0a0a0a;
|
||||
color-scheme: dark;
|
||||
--t-surface-base: #0a0a0a;
|
||||
--t-surface-raised: #171717;
|
||||
--t-surface-sunken: #000000;
|
||||
--t-surface-overlay: rgba(23, 23, 23, 0.95);
|
||||
@@ -239,14 +243,14 @@ defmodule Berrypod.Theme.CSSGenerator do
|
||||
# Fallback for any other density value
|
||||
defp generate_density(_), do: generate_density("balanced")
|
||||
|
||||
# Accent color with HSL breakdown
|
||||
# Accent color with oklch breakdown
|
||||
defp generate_accent(hex_color) do
|
||||
{h, s, l} = hex_to_hsl(hex_color)
|
||||
{l, c, h} = hex_to_oklch(hex_color)
|
||||
|
||||
"""
|
||||
--t-accent-h: #{h};
|
||||
--t-accent-s: #{s}%;
|
||||
--t-accent-l: #{l}%;
|
||||
--t-accent-c: #{c};
|
||||
--t-accent-h: #{h};
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -334,46 +338,55 @@ defmodule Berrypod.Theme.CSSGenerator do
|
||||
"--t-image-aspect-ratio: 4 / 3;"
|
||||
end
|
||||
|
||||
# Convert hex color to HSL
|
||||
defp hex_to_hsl("#" <> hex), do: hex_to_hsl(hex)
|
||||
# Convert hex colour to oklch (lightness%, chroma, hue°)
|
||||
# Pipeline: hex → sRGB → linear RGB → XYZ (D65) → oklab → oklch
|
||||
defp hex_to_oklch("#" <> hex), do: hex_to_oklch(hex)
|
||||
|
||||
defp hex_to_hsl(hex) when byte_size(hex) == 6 do
|
||||
defp hex_to_oklch(hex) when byte_size(hex) == 6 do
|
||||
{r, ""} = Integer.parse(String.slice(hex, 0..1), 16)
|
||||
{g, ""} = Integer.parse(String.slice(hex, 2..3), 16)
|
||||
{b, ""} = Integer.parse(String.slice(hex, 4..5), 16)
|
||||
|
||||
# Normalize RGB values to 0-1
|
||||
r = r / 255
|
||||
g = g / 255
|
||||
b = b / 255
|
||||
# sRGB to linear RGB (inverse gamma)
|
||||
lr = srgb_to_linear(r / 255)
|
||||
lg = srgb_to_linear(g / 255)
|
||||
lb = srgb_to_linear(b / 255)
|
||||
|
||||
max = Enum.max([r, g, b])
|
||||
min = Enum.min([r, g, b])
|
||||
delta = max - min
|
||||
# Linear RGB to oklab via the Björn Ottosson method
|
||||
# First: linear sRGB → LMS (cube root of cone response)
|
||||
l_ = :math.pow(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb, 1 / 3)
|
||||
m_ = :math.pow(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb, 1 / 3)
|
||||
s_ = :math.pow(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb, 1 / 3)
|
||||
|
||||
# Calculate lightness
|
||||
l = (max + min) / 2
|
||||
# LMS to oklab
|
||||
ok_l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_
|
||||
ok_a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_
|
||||
ok_b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
|
||||
|
||||
# Calculate saturation and hue
|
||||
{h, s} =
|
||||
if delta == 0 do
|
||||
{0, 0}
|
||||
# oklab to oklch
|
||||
c = :math.sqrt(ok_a * ok_a + ok_b * ok_b)
|
||||
|
||||
h =
|
||||
if c < 0.0001 do
|
||||
0.0
|
||||
else
|
||||
s = if l > 0.5, do: delta / (2 - max - min), else: delta / (max + min)
|
||||
|
||||
h =
|
||||
cond do
|
||||
max == r -> (g - b) / delta + if(g < b, do: 6, else: 0)
|
||||
max == g -> (b - r) / delta + 2
|
||||
max == b -> (r - g) / delta + 4
|
||||
end
|
||||
|
||||
{h * 60, s}
|
||||
h_rad = :math.atan2(ok_b, ok_a)
|
||||
h_deg = h_rad * 180 / :math.pi()
|
||||
if h_deg < 0, do: h_deg + 360, else: h_deg
|
||||
end
|
||||
|
||||
{round(h), round(s * 100), round(l * 100)}
|
||||
# Return as {lightness%, chroma, hue°} rounded for CSS
|
||||
{
|
||||
Float.round(ok_l * 100, 2),
|
||||
Float.round(c, 4),
|
||||
Float.round(h, 2)
|
||||
}
|
||||
end
|
||||
|
||||
# Handle invalid hex values
|
||||
defp hex_to_hsl(_), do: {0, 0, 50}
|
||||
# Handle invalid hex values (neutral grey fallback)
|
||||
defp hex_to_oklch(_), do: {50.0, 0.0, 0.0}
|
||||
|
||||
# sRGB gamma decompression (IEC 61966-2-1)
|
||||
defp srgb_to_linear(v) when v <= 0.04045, do: v / 12.92
|
||||
defp srgb_to_linear(v), do: :math.pow((v + 0.055) / 1.055, 2.4)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user