refactor rainbow quox
This commit is contained in:
parent
da06033eed
commit
3c5eeeae8e
2 changed files with 142 additions and 145 deletions
|
@ -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<A>(...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<number> {
|
||||
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<A extends string> = Record<A, Oklch>;
|
||||
type OuterCols = ColsOf<OuterLayer>;
|
||||
type SockCols = ColsOf<SockLayer>;
|
||||
type FinCols = ColsOf<FinLayer>;
|
||||
type BellyCols = ColsOf<BellyLayer>;
|
||||
type MiscCols = ColsOf<MiscLayer>;
|
||||
type Colours = ColsOf<Layer> & {type: SchemeType};
|
||||
|
||||
function colours(): Colours {
|
||||
let cols: Partial<Colours> = {};
|
||||
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<keyof Colours, 'type'>];
|
||||
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 { };
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue