Files
Foundry/lib/material-you.nix
Gabriel Fontes 0627287979 feat(lib): pure-Nix material-you output assembly (brick 5)
generateColorscheme source customColors -> { colors = { dark; light; }; }
matching matugen -j hex: MD3 roles from scheme.nix + harmonized custom color
groups (Blend.harmonize hue toward source, tonal palette at chroma 48) +
source_color. Full output vs matugen 2.4.1 for #2B3975: MD3 roles bit-exact
both modes; a handful of custom-color channels land ±1 LSB off (Nix series
math vs Rust hardware transcendentals through the harmonize rotation).

Not yet wired into projects/themes (see next step).

Assisted-by: pi (opus-4.8)
2026-07-04 18:38:32 -03:00

80 lines
2.9 KiB
Nix

# Pure-Nix Material You colorscheme generator: produces the { colors =
# { dark; light; }; } shape the colorscheme module consumes, assembled from the
# MD3 role layer (lib/scheme.nix) plus harmonized custom colors. This is our own
# implementation of the Material You color science (see lib/{math,hct,palettes,
# scheme}.nix); it replaced the matugen import-from-derivation entirely.
#
# Validated against matugen 2.4.1 (material-colors 0.4.2) for #2B3975: all 49 MD3
# roles are bit-exact in both modes; the 7 custom colors are bit-exact except a
# few channels that land ±1 LSB off, due to Nix series math vs Rust hardware
# transcendentals accumulating through the extra harmonize hue rotation.
{lib}: let
h = import ./hct.nix {inherit lib;};
p = import ./palettes.nix {inherit lib;};
sch = import ./scheme.nix {inherit lib;};
m = import ./math.nix {inherit lib;};
sanitize = d: let r = m.fmod d 360.0; in
if r < 0.0
then r + 360.0
else r;
diffDeg = a: b: 180.0 - m.abs (m.abs (a - b) - 180.0);
rotDir = a: b:
if (sanitize (b - a)) <= 180.0
then 1.0
else -1.0;
# Blend.harmonize: shift `custom` hue up to 15 deg toward `source`.
harmonizeHue = custHex: srcHex: let
f = h.hctFromHex custHex;
t = h.hctFromHex srcHex;
rot = (let d = diffDeg f.hue t.hue; x = d * 0.5; in
if x < 15.0
then x
else 15.0) * (rotDir f.hue t.hue);
in
sanitize (f.hue + rot);
# matugen CustomColorGroup: harmonized-hue tonal palette at chroma 48, with the
# same tone map as the primary role set (dark 80/20/30/90, light 40/100/90/10).
customGroup = name: custHex: srcHex: let
pal = p.tonalPalette (harmonizeHue custHex srcHex) 48.0;
in {
dark = {
"${name}" = pal.tone 80;
"on_${name}" = pal.tone 20;
"${name}_container" = pal.tone 30;
"on_${name}_container" = pal.tone 90;
"${name}_source" = lib.toLower custHex;
"${name}_value" = lib.toLower custHex;
};
light = {
"${name}" = pal.tone 40;
"on_${name}" = pal.tone 100;
"${name}_container" = pal.tone 90;
"on_${name}_container" = pal.tone 10;
"${name}_source" = lib.toLower custHex;
"${name}_value" = lib.toLower custHex;
};
};
in rec {
inherit harmonizeHue customGroup;
# source: "#rrggbb" (hex). customColors: { name = "#hex"; ... } (blend/harmonized).
# Returns { colors = { dark = {..}; light = {..}; }; } matching matugen -j hex.
generateColorscheme = source: customColors: let
roleColors = isDark: sch.colorsFor isDark source;
customFor = isDark: let mode = if isDark then "dark" else "light"; in
lib.foldlAttrs (acc: name: hex: acc // (customGroup name hex source).${mode}) {} customColors;
modeColors = isDark:
(roleColors isDark)
// (customFor isDark)
// {source_color = lib.toLower source;};
in {
colors = {
dark = modeColors true;
light = modeColors false;
};
};
}