Files
sass-mohfarawati/lib/site-theme.ts
T

135 lines
3.1 KiB
TypeScript

import {
DEFAULT_SITE_PRIMARY_COLOR,
normalizeSitePrimaryColor,
} from "./site-settings";
type RgbColor = {
r: number;
g: number;
b: number;
};
type HslColor = {
h: number;
s: number;
l: number;
};
function clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max);
}
function hexToRgb(hex: string): RgbColor {
const normalized = normalizeSitePrimaryColor(hex).slice(1);
return {
r: Number.parseInt(normalized.slice(0, 2), 16),
g: Number.parseInt(normalized.slice(2, 4), 16),
b: Number.parseInt(normalized.slice(4, 6), 16),
};
}
function rgbToHsl({ r, g, b }: RgbColor): HslColor {
const red = r / 255;
const green = g / 255;
const blue = b / 255;
const max = Math.max(red, green, blue);
const min = Math.min(red, green, blue);
const delta = max - min;
const lightness = (max + min) / 2;
if (delta === 0) {
return {
h: 0,
s: 0,
l: Math.round(lightness * 100),
};
}
const saturation =
lightness > 0.5
? delta / (2 - max - min)
: delta / (max + min);
let hue = 0;
switch (max) {
case red:
hue = (green - blue) / delta + (green < blue ? 6 : 0);
break;
case green:
hue = (blue - red) / delta + 2;
break;
default:
hue = (red - green) / delta + 4;
break;
}
return {
h: Math.round(hue * 60),
s: Math.round(saturation * 100),
l: Math.round(lightness * 100),
};
}
function toChannels(color: HslColor): string {
return `${Math.round(color.h)} ${Math.round(color.s)}% ${Math.round(color.l)}%`;
}
function deriveSecondaryColor(color: HslColor, dark = false): HslColor {
return {
h: (color.h + 10) % 360,
s: clamp(color.s - (dark ? 2 : 8), 35, 95),
l: clamp(color.l + (dark ? 10 : 8), 20, 80),
};
}
function deriveDarkPrimary(color: HslColor): HslColor {
return {
h: color.h,
s: clamp(color.s, 40, 95),
l: clamp(color.l + 6, 30, 78),
};
}
export function buildSiteThemeTokens(primaryColor: string) {
const safePrimary = normalizeSitePrimaryColor(primaryColor || DEFAULT_SITE_PRIMARY_COLOR);
const basePrimary = rgbToHsl(hexToRgb(safePrimary));
const darkPrimary = deriveDarkPrimary(basePrimary);
return {
light: {
primary: toChannels(basePrimary),
secondary: toChannels(deriveSecondaryColor(basePrimary)),
},
dark: {
primary: toChannels(darkPrimary),
secondary: toChannels(deriveSecondaryColor(darkPrimary, true)),
},
};
}
export function buildSiteThemeStyleText(primaryColor: string) {
const tokens = buildSiteThemeTokens(primaryColor);
return `
:root {
--primary: ${tokens.light.primary};
--ring: ${tokens.light.primary};
--brand-primary: ${tokens.light.primary};
--brand-secondary: ${tokens.light.secondary};
--sidebar-primary: ${tokens.light.primary};
--sidebar-ring: ${tokens.light.primary};
}
.dark {
--primary: ${tokens.dark.primary};
--ring: ${tokens.dark.primary};
--brand-primary: ${tokens.dark.primary};
--brand-secondary: ${tokens.dark.secondary};
--sidebar-primary: ${tokens.dark.primary};
--sidebar-ring: ${tokens.dark.primary};
}
`.trim();
}