import { Rand } from './color/rand.js'; import { Color, Luma, Chroma, Hue, oklch, oklab, rgb } from './color/def.js'; export { Rand, Color, Luma, Chroma, Hue, oklch, oklab, rgb }; const max = Math.max; const min = Math.min; export type SchemeType = 'triad' | 'fin-belly' | 'fin-body'; export type OuterLayer = 'outer' | 'spines' | 'vitiligo1'; export type SockLayer = 'stripes' | 'cuffs'; export type FinLayer = 'fins1' | 'fins2' | 'fins3' | 'vitiligo4'; export type BellyLayer = 'belly1' | 'vitiligo3' | 'belly2' | 'vitiligo2'; export type MiscLayer = 'eyes' | 'masks' | 'claws' | 'lines'; export type Layer = OuterLayer | SockLayer | FinLayer | BellyLayer | MiscLayer; export type ColsOf = Record; export type OuterCols = ColsOf; export type SockCols = ColsOf; export type FinCols = ColsOf; export type BellyCols = ColsOf; export type MiscCols = ColsOf; export type Colors = ColsOf; export type Scheme = Colors & {type: SchemeType}; export const allLayers: Layer[] = ['outer', 'spines', 'stripes', 'cuffs', 'fins1', 'fins2', 'fins3', 'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', 'vitiligo3', 'vitiligo4', 'eyes', 'lines']; export function makeColorInfo(f: (l: Layer) => A): Record { return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record; } type KnownPalette = Partial>; export function colors(r: Rand = new Rand(), base?: KnownPalette): Scheme { const outer = base?.outer ?? r.color('dark'); const spines = base?.spines ?? mkSpines(r, outer); const vitiligo1 = base?.vitiligo1 ?? mkVitiligo(r, outer); const outerCols: OuterCols = { outer, spines, vitiligo1 }; const stripes = base?.stripes ?? mkStripes(r); const cuffs = base?.cuffs ?? mkCuffs(r, stripes); const sockCols: SockCols = { stripes, cuffs }; let finCols: FinCols, bellyCols: BellyCols, type: SchemeType; const whichBody = r.float(); if (whichBody > 2/3) { type = 'triad'; const [f, b] = r.triad(outer.hue); finCols = mkFins(r, f, outer, base); bellyCols = mkBelly(r, b, base); } else if (whichBody > 1/3) { type = 'fin-belly'; const [f, b] = r.complementary(outer.hue, 2); finCols = mkFins(r, f!, outer, base); bellyCols = mkBelly(r, b!, base); } else { type = 'fin-body'; finCols = mkFins(r, r.analogous1(outer.hue), outer, base); bellyCols = mkBelly(r, r.complementary1(outer.hue), base); } const miscCols = mkMisc(r, outerCols, finCols, bellyCols, base); return merge(outerCols, sockCols, finCols, bellyCols, miscCols, type); } function mkSpines(r: Rand, outer: Color): Color { return outer.with({ type: 'oklch', l: l => r.darkFor(l), c: c => r.brightFor(outer.luma, c), h: h => r.float(h + 12, h - 12), }) } function mkVitiligo(r: Rand, outer: Color): Color { return outer.with({ type: 'oklch', l: x => r.float(max(x, 0.94), 0.985), // exception to MAXL c: x => r.float(min(x, 0.1), r.mincLight), }); } function mkStripes(r: Rand): Color { return oklch(r.float(0.8, r.maxl), r.float(r.mincLight, r.maxcLight), r.baseHue()); } function mkCuffs(r: Rand, sock: Color): Color { return sock.with({ type: 'oklch', l: l => r.float(l * 0.85, l * 0.65), c: c => r.float(c, r.maxcLight), h: h => r.float(h + 8, h - 8), }); } function mkFins(r: Rand, h: Hue, outer: Color, base?: KnownPalette): FinCols { const baseFin1 = base?.fins1; const [fin1Hue, fin2Hue, fin3Hue] = r.analogous(baseFin1?.hue ?? h, 3); const direction: 'lighter' | 'darker' = r.choice(['lighter', 'darker']); function ll(l: Luma): Luma { return direction == 'lighter' ? r.lightFor(l) : r.darkFor(l); } function cc(l: Luma, c: Chroma): Chroma { return direction == 'lighter' ? r.dullFor(l, c) : r.brightFor(l, c); } const fins1 = baseFin1 ?? oklch(ll(outer.luma), cc(outer.luma, outer.chroma), fin1Hue!); const fins2 = base?.fins2 ?? oklch(ll(fins1.luma), cc(fins1.luma, fins1.chroma), fin2Hue!); const fins3 = base?.fins3 ?? oklch(ll(fins2.luma), cc(fins2.luma, fins2.chroma), fin3Hue!); const lighter = fins1.luma >= fins3.luma ? fins1 : fins3; const vitiligo4 = base?.vitiligo4 ?? mkVitiligo(r, lighter); return { fins1, fins2, fins3, vitiligo4 }; } function mkBelly(r: Rand, h: Hue, base?: KnownPalette): BellyCols { const baseBelly1 = base?.belly1; const [belly1Hue, belly2Hue] = r.analogous(baseBelly1?.hue ?? h, 2); const belly1 = baseBelly1 ?? oklch(r.float(0.7, r.maxl), r.baseChroma(1), belly1Hue!); const belly2 = base?.belly2 ?? belly1.with({ type: 'oklch', l: x => min(r.maxl, x * 1.1), c: x => x * 0.9, h: [belly2Hue!], }); const vitiligo3 = base?.vitiligo3 ?? mkVitiligo(r, belly1); const vitiligo2 = base?.vitiligo2 ?? mkVitiligo(r, belly2); return { belly1, belly2, vitiligo2, vitiligo3 }; } function mkMisc(r: Rand, o: OuterCols, f: FinCols, b: BellyCols, base?: KnownPalette): MiscCols { const masks = base?.masks ?? oklch(r.float(0.8, r.maxl), r.float(0.01, 0.06), r.analogous1(r.choice([o.outer, b.belly1, f.fins1]).hue)); return { masks, eyes: base?.eyes ?? oklch( r.baseLuma('light'), r.float(0.28, r.maxcLight), r.boolean() ? r.analogous1(o.outer.hue) : r.complementary1(o.outer.hue) ), claws: base?.claws ?? masks.with({ type: 'oklch', l: x => min(r.maxl, x + r.float(0, 0.1)), c: [r.float(0.01, 0.06)], h: h => r.analogous1(h), }), lines: base?.lines ?? oklch(r.float(0.01, 0.06), r.baseChroma(0), r.analogous1(o.outer.hue)), }; } function merge({ outer, spines, vitiligo1 }: OuterCols, { stripes, cuffs }: SockCols, { fins1, fins2, fins3, vitiligo4 }: FinCols, { belly1, vitiligo3, belly2, vitiligo2 }: BellyCols, { eyes, masks, claws, lines }: MiscCols, type: SchemeType): Scheme { return { outer, spines, vitiligo1, stripes, cuffs, fins1, fins2, fins3, vitiligo4, belly1, vitiligo3, belly2, vitiligo2, eyes, masks, claws, lines, type }; } export const KNOWN: Record = { niss: { outer: oklch(0.83, 0.201, 151), belly1: oklch(0.87, 0.082, 99), fins1: oklch(0.68, 0.178, 16), eyes: oklch(0.73, 0.135, 242), }, kesi: { outer: oklch(0.86, 0.147, 147), belly1: oklch(0.96, 0.04, 108), fins1: oklch(0.94, 0.142, 102), eyes: oklch(0.76, 0.115, 300), }, 60309: { outer: oklch(0.84, 0.068, 212), belly1: oklch(0.56, 0.035, 233), fins1: oklch(0.55, 0.101, 268), eyes: oklch(0.86, 0.146, 154), }, 'prickly pear': { outer: oklch(0.64, 0.087, 316), belly1: oklch(0.88, 0.03, 88), fins1: oklch(0.6, 0.071, 142), eyes: oklch(0.66, 0.091, 134), }, 'the goo': { outer: oklch(0.92, 0.046, 354), belly1: oklch(0.83, 0.099, 354), fins1: oklch(0.74, 0.115, 354), eyes: oklch(0.73, 0.149, 0), }, lambda: { outer: oklch(0.71, 0.154, 58), belly1: oklch(0.9, 0.05, 80), fins1: oklch(0.76, 0.16, 140), eyes: oklch(0.82, 0.178, 141), }, flussence: { outer: oklch(0.77, 0.118, 133), belly1: oklch(0.71, 0.086, 253), fins1: oklch(0.58, 0.102, 254), eyes: oklch(0.37, 0.107, 278), }, serena: { outer: oklch(0.69, 0.176, 349), belly1: oklch(0.92, 0.04, 350), fins1: oklch(0.74, 0.138, 319), eyes: oklch(0.65, 0.206, 4), }, pippin: { outer: oklch(0.74, 0.08, 61), belly1: oklch(0.82, 0.062, 70), fins1: oklch(0.52, 0.09, 45), eyes: oklch(0.74, 0.167, 136), }, su: { outer: oklch(0.29, 0.012, 219), belly1: oklch(0.89, 0.01, 256), fins1: oklch(0.53, 0.093, 20), }, trans: { outer: oklch(0.83, 0.065, 228), belly1: oklch(0.95, 0.021, 137), fins1: oklch(0.86, 0.069, 352), }, };