export interface OKLCHColor {
l: number // Lightness [0, 1]
c: number // Chroma [0, 0.4]
h: number // Hue [0, 360]
}
export interface TonalScale {
[step: number]: {
oklch: OKLCHColor
css: string
hex: string
}
}
export function generateTonalPalette(base: OKLCHColor): TonalScale {
const steps = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
const lightnessMap: Record<number, number> = {
50: 0.97,
100: 0.93,
200: 0.85,
300: 0.76,
400: 0.67,
500: base.l,
600: 0.52,
700: 0.42,
800: 0.31,
900: 0.22,
950: 0.14,
}
const result: TonalScale = {}
for (const step of steps) {
const targetL = lightnessMap[step]
// Dynamic chroma scaling to prevent out-of-gamut clipping
const chromaFactor = Math.sin((targetL) * Math.PI)
const targetC = Math.min(base.c, base.c * chromaFactor * 1.2)
result[step] = {
oklch: { l: targetL, c: targetC, h: base.h },
css: `oklch(${targetL.toFixed(3)} ${targetC.toFixed(3)} ${base.h.toFixed(1)})`,
hex: "#mockhex",
}
}
return result
}