From 3c5eeeae8ea06ea610ac7c9be91b7970f5cfacc9 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Tue, 3 Dec 2024 14:50:37 +0100 Subject: [PATCH] refactor rainbow quox --- rainbow-quox/colour.ts | 276 ++++++++++++++++++++--------------------- rainbow-quox/style.css | 11 +- 2 files changed, 142 insertions(+), 145 deletions(-) diff --git a/rainbow-quox/colour.ts b/rainbow-quox/colour.ts index 6d489ce..82b21c0 100644 --- a/rainbow-quox/colour.ts +++ b/rainbow-quox/colour.ts @@ -1,18 +1,4 @@ -type Rand = () => number; - - -/* -let randomData = new Uint32Array(100); -let next: number = 0; -function reset() { self.crypto.getRandomValues(randomData); next = 0; } -function rand() { - if (next >= 100) { reset(); } - return randomData[next++] / 4_294_967_295; // u32 max -} -reset(); -*/ - -const rand: Rand = Math.random; // [todo] +const rand: () => number = Math.random; // [todo] const max = Math.max; const min = Math.min; @@ -38,11 +24,11 @@ const MAXH_WIDTH = 80; // minimum distance between adjacent analogous colours const MINH_SEP = 5; -// how far away from 180° a "complementary" colour can be +// size of the wedge a "complementary" colour can be in const MAXH_COMPL = 40; -// how far away from 120° a "triad" colour can be -const MAXH_TRIAD = 20; +// size of the wedge a "triadic" colour can be in +const MAXH_TRIAD = 25; function randBetween(x: number, y: number): number { const lo = min(x, y), hi = max(x, y); @@ -54,8 +40,6 @@ function oneOf(...xs: A[]): A { } -function isLight(l: number): boolean { return l >= MINL_LIGHT; } - function baseLuma(ld?: LD): number { if (ld == 'light') { return randBetween(MINL_LIGHT, MAXL); @@ -78,7 +62,7 @@ function baseHue(): number { return rand() * 360; } function baseOklch(ld?: LD): Oklch { const l = baseLuma(ld); - return {l, c: baseChroma(l), h: baseHue()}; + return { l, c: baseChroma(l), h: baseHue() }; } function lightFor(baseL: number): number { @@ -88,6 +72,8 @@ function darkFor(baseL: number): number { return randBetween(MINL, baseL); } +function isLight(l: number): boolean { return l >= MINL_LIGHT; } + function brightFor(l: number, baseC: number): number { if (isLight(l)) { return randBetween(baseC, MAXC_LIGHT); } else { return randBetween(baseC, MAXC_DARK); } @@ -99,138 +85,80 @@ function dullFor(l: number, baseC: number): number { } -type Numbers = number[]; - -function upto(end: number): Numbers { - function* go(next: number): Iterable { - if (next < end) { yield next; yield* go(next+1) } - } - return Array.from(go(0)); +function analogous1(baseH: number): number { + const size = randBetween(MINH_SEP, 2 * MINH_SEP); + return rand() > 0.5 ? baseH + size : baseH - size; } -function analogous(baseH: number, count: number): Numbers { - const minWidth = count * MINH_SEP; - const width = - MAXH_WIDTH < minWidth ? minWidth : randBetween(minWidth, MAXH_WIDTH); +function analogous(baseH: number, count: number): number[] { + const minWidth = min(count * MINH_SEP, MAXH_WIDTH * 0.8); + const width = randBetween(minWidth, MAXH_WIDTH); const sep = width / (count - 1); const start = baseH - (width / 2); - let numbers = Array.from(upto(count).map(i => start + i * sep)); + const numbers = Array.from({length: count}, (_u, i) => start + i * sep); return rand() > 0.5 ? numbers : numbers.reverse(); } -function complementary(baseH: number, count: number): Numbers { - const angle = randBetween(180 - MAXH_COMPL, 180 + MAXH_COMPL); +function complementary1(baseH: number): number { + return analogous1((baseH + 180) % 360); +} + +function complementary(baseH: number, count: number): number[] { + const angle = randBetween(180 - MAXH_COMPL/2, 180 + MAXH_COMPL/2); return analogous(baseH + angle, count); } function triad(baseH: number): [number, number] { - const angle = randBetween(120 - MAXH_TRIAD, 120 + MAXH_TRIAD); + const angle = randBetween(120 - MAXH_TRIAD/2, 120 + MAXH_TRIAD/2); return [baseH - angle, baseH + angle]; } -type Layer = - 'outer' | 'spines' | 'vitiligo1' | - 'stripes' | 'cuffs' | - 'fins1' | 'fins2' | 'fins3' | 'vitiligo4' | - 'belly1' | 'vitiligo3' | 'belly2' | 'vitiligo2' | - 'eyes' | 'masks' | 'claws' | 'lines'; +type SchemeType = 'triad' | 'fin-belly' | 'fin-body'; -type Colours = { [l in Layer]: Oklch }; +type OuterLayer = 'outer' | 'spines' | 'vitiligo1'; +type SockLayer = 'stripes' | 'cuffs'; +type FinLayer = 'fins1' | 'fins2' | 'fins3' | 'vitiligo4'; +type BellyLayer = 'belly1' | 'vitiligo3' | 'belly2' | 'vitiligo2'; +type MiscLayer = 'eyes' | 'masks' | 'claws' | 'lines'; +type Layer = OuterLayer | SockLayer | FinLayer | BellyLayer | MiscLayer; + +type ColsOf = Record; +type OuterCols = ColsOf; +type SockCols = ColsOf; +type FinCols = ColsOf; +type BellyCols = ColsOf; +type MiscCols = ColsOf; +type Colours = ColsOf & {type: SchemeType}; function colours(): Colours { - let cols: Partial = {}; + const outer = baseOklch('dark'); + let outerCols: OuterCols = + { outer, spines: mkSpines(outer), vitiligo1: mkVitiligo(outer) }; - cols.outer = baseOklch('dark'); // [todo] - cols.spines = mkSpines(cols.outer); - cols.vitiligo1 = mkVitiligo(cols.outer); + const stripes = mkStripes(); + let sockCols: SockCols = { stripes, cuffs: mkCuffs(stripes) }; - cols.stripes = mkStripes(); - cols.cuffs = mkCuffs(cols.stripes); - - // fins, belly + let finCols: FinCols, bellyCols: BellyCols, type: SchemeType; const whichBody = rand(); - if (whichBody > 0.85) { - // triad - const hs = triad(cols.outer.h); - fins(hs[0]); belly(hs[1]); - } else if (whichBody > 0.4) { - // fins like belly - const [f, b] = complementary(cols.outer.h, 2); - fins(f!); belly(b!); + if (whichBody > 2/3) { + type = 'triad'; + const [f, b] = triad(outer.h); + finCols = mkFins(f, outer); bellyCols = mkBelly(b); + } else if (whichBody > 1/3) { + type = 'fin-belly'; + const [f, b] = complementary(outer.h, 2); + finCols = mkFins(f!, outer); bellyCols = mkBelly(b!); } else { - // fins like outer - fins(analogous(cols.outer.h, 3)[2]!); - belly(complementary(cols.outer.h, 3)[2]!); + type = 'fin-body'; + finCols = mkFins(analogous1(outer.h), outer); + bellyCols = mkBelly(complementary1(outer.h)); } - cols.eyes = { - l: baseLuma('light'), - c: randBetween(0.28, MAXC_LIGHT), - h: oneOf(analogous, complementary)(cols.outer.h, 3)[2]! - }; + let miscCols = mkMisc(outerCols, finCols, bellyCols); - cols.masks = { - l: randBetween(0.8, MAXL), - c: randBetween(0.01, 0.06), - h: analogous(oneOf(cols.outer, cols.belly1, cols.fins1)!.h, 3)[2]! - }; - cols.claws = { - l: min(MAXL, cols.masks!.l + randBetween(0.1, -0.1)), - c: randBetween(0.01, 0.06), - h: analogous(cols.masks!.h, 3)[2]! - }; - - cols.lines = { - l: randBetween(0.01, 0.06), - c: baseChroma(0), - h: analogous(cols.outer!.h, 3)[2]! - } - - return cols as Colours; - - - function fins(h: number) { - const [fin1Hue, fin2Hue, fin3Hue] = analogous(h, 3); - const d = direction(); - cols.fins1 = doDirection(cols.outer!, fin1Hue!, d); - cols.fins2 = doDirection(cols.fins1, fin2Hue!, d); - cols.fins3 = doDirection(cols.fins2, fin3Hue!, d); - cols.vitiligo4 = mkVitiligo(cols.fins1); - } - - function belly(h: number) { - const [belly1Hue, belly2Hue] = analogous(h, 2); - cols.belly1 = { - l: randBetween(0.7, MAXL), - c: baseChroma(1), - h: belly1Hue! - }; - cols.belly2 = { - l: min(MAXL, cols.belly1!.l * 1.1), - c: cols.belly1!.c * 0.9, - h: belly2Hue! - }; - cols.vitiligo3 = mkVitiligo(cols.belly1); // oops sorry - cols.vitiligo2 = mkVitiligo(cols.belly2); - } - - type LFun = (l: number) => number; - type CFun = (l: number, c: number) => number; - function direction(): [LFun, CFun] { - return oneOf([lightFor, dullFor], [darkFor, brightFor]); - } - function doDirection(col: Oklch, h: number, [ll, cc]: [LFun, CFun]) { - return { l: ll(col.l), c: cc(col.l, col.c), h }; - } + return merge(outerCols, sockCols, finCols, bellyCols, miscCols, type); } -function mkVitiligo(outer: Oklch): Oklch { - return { - l: randBetween(max(outer.l, 0.8), MAXL), - c: randBetween(min(outer.c, 0.1), MINC_LIGHT), - h: randBetween(outer.h + 20, outer.h - 20) - }; -} function mkSpines(outer: Oklch): Oklch { return { @@ -239,6 +167,14 @@ function mkSpines(outer: Oklch): Oklch { }; } +function mkVitiligo(outer: Oklch): Oklch { + return { + l: randBetween(max(outer.l, 0.85), MAXL), + c: randBetween(min(outer.c, 0.1), MINC_LIGHT), + h: randBetween(outer.h + 20, outer.h - 20) + }; +} + function mkStripes(): Oklch { return { l: randBetween(0.8, MAXL), @@ -249,42 +185,102 @@ function mkStripes(): Oklch { function mkCuffs(sock: Oklch): Oklch { return { - l: sock.l * 0.7, + l: randBetween(sock.l * 0.85, sock.l * 0.65), c: randBetween(sock.c, MAXC_LIGHT), h: randBetween(sock.h + 8, sock.h - 8) }; } +function mkFins(h: number, outer: Oklch): FinCols { + const [fin1Hue, fin2Hue, fin3Hue] = analogous(h, 3); + const [ll, cc] = oneOf([lightFor, dullFor], [darkFor, brightFor]); + const fins1 = { l: ll(outer.l), c: cc(outer.l, outer.c), h: fin1Hue! }; + const fins2 = { l: ll(fins1.l), c: cc(fins1.l, fins1.c), h: fin2Hue! }; + const fins3 = { l: ll(fins2.l), c: cc(fins2.l, fins2.c), h: fin3Hue! }; + const vitiligo4 = mkVitiligo(fins1); + return { fins1, fins2, fins3, vitiligo4 }; +} + +function mkBelly(h: number): BellyCols { + const [belly1Hue, belly2Hue] = analogous(h, 2); + const belly1 = + { l: randBetween(0.7, MAXL), c: baseChroma(1), h: belly1Hue! }; + const belly2 = + { l: min(MAXL, belly1.l * 1.1), c: belly1.c * 0.9, h: belly2Hue! }; + const vitiligo3 = mkVitiligo(belly1); + const vitiligo2 = mkVitiligo(belly2); + return { belly1, belly2, vitiligo2, vitiligo3 }; +} + +function mkMisc(o: OuterCols, f: FinCols, b: BellyCols): MiscCols { + const masks = { + l: randBetween(0.8, MAXL), + c: randBetween(0.01, 0.06), + h: analogous1(oneOf(o.outer, b.belly1, f.fins1).h) + }; + return { + masks, + eyes: { + l: baseLuma('light'), + c: randBetween(0.28, MAXC_LIGHT), + h: oneOf(analogous1, complementary1)(o.outer.h) + }, + claws: { + l: min(MAXL, masks.l + randBetween(0, 0.1)), + c: randBetween(0.01, 0.06), + h: analogous1(masks.h) + }, + lines: { + l: randBetween(0.01, 0.06), + c: baseChroma(0), + h: analogous1(o.outer.h) + } + }; +} + +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): Colours { + return { + outer, spines, vitiligo1, stripes, cuffs, fins1, fins2, fins3, vitiligo4, + belly1, vitiligo3, belly2, vitiligo2, eyes, masks, claws, lines, type + }; +} + function setColours(cols: Colours) { for (const k in cols) { - const c = cols[k as keyof Colours]; + if (k == 'type') continue; + const c = cols[k as Exclude]; for (const elem of Array.from(document.getElementsByClassName(k))) { (elem as HTMLElement).style.background = `oklch(${c.l} ${c.c} ${c.h})`; } } - document.documentElement.style.setProperty('--hue', `${cols.outer.h}deg`); + document.documentElement.style.setProperty('--hue', `${cols.outer.h}`); } document.addEventListener('DOMContentLoaded', function() { - const reroll = document.getElementById('reroll')!; - const swap = document.getElementById('swap')!; - const pic = document.getElementById('pic')!; - - reroll.addEventListener('click', doReroll); - swap.addEventListener('click', doSwap); + document.getElementById('reroll')?.addEventListener('click', doReroll); + document.getElementById('swap')?.addEventListener('click', doSwap); doReroll(); setTimeout(setTransition); function doReroll() { setColours(colours()); } - function doSwap() { pic.classList.toggle('back'); } + function doSwap() { + document.getElementById('pic')?.classList.toggle('back'); + } function setTransition() { - document.documentElement.style.setProperty('--transition', - 'background 0.4s ease-in-out, color 0.4s ease-in-out'); + document.documentElement.style.setProperty( + '--transition', + 'background 0.4s ease-in-out, color 0.4s ease-in-out' + ); } }); -export {} +export { }; diff --git a/rainbow-quox/style.css b/rainbow-quox/style.css index e05ef37..58e3c9d 100644 --- a/rainbow-quox/style.css +++ b/rainbow-quox/style.css @@ -16,7 +16,8 @@ } :root { - --hue: 300deg; + --hue: 300; + --c-hue: calc(180 + var(--hue)); min-height: 100vh; display: flex; align-items: center; justify-content: center; @@ -144,7 +145,7 @@ button { font: 700 25pt var(--font); flex: 30%; background: oklch(0.5 0.2 var(--hue)); - color: oklch(0.95 0.075 calc(180deg + var(--hue))); + color: oklch(0.95 0.075 var(--c-hue)); border: 3px solid oklch(0.2 0.05 var(--hue)); padding: 0.2em 0.5em; filter: drop-shadow(0 0 10px oklch(0.4 0.2 var(--hue) / 0.45)); @@ -156,9 +157,9 @@ nav { } nav a { - color: light-dark(oklch(0.4 0.15 calc(180deg + var(--hue))), - oklch(0.9 0.19 calc(180deg + var(--hue)))); + color: light-dark(oklch(0.4 0.15 var(--c-hue)), + oklch(0.9 0.19 var(--c-hue))); text-decoration: 3px solid underline; text-decoration-color: - oklch(0.6 0.1 calc(180deg + var(--hue))); + oklch(0.6 0.1 var(--c-hue)); }