From ddc36a9eea1bc2f8ace2721abd6ddec3bef35504 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Wed, 1 Jan 2025 00:40:20 +0100 Subject: [PATCH 01/23] refactor rainbow-quox scripting --- Makefile | 4 +- rainbow-quox/script/color.ts | 377 +++++------------------------- rainbow-quox/script/color/conv.ts | 126 ++++++++++ rainbow-quox/script/color/def.ts | 136 +++++++++++ rainbow-quox/script/color/rand.ts | 120 ++++++++++ rainbow-quox/script/history.ts | 27 +-- rainbow-quox/script/layer.ts | 68 ++---- rainbow-quox/script/palette.ts | 41 ++-- rainbow-quox/script/quox.ts | 114 ++++----- rainbow-quox/script/rand.ts | 4 +- 10 files changed, 556 insertions(+), 461 deletions(-) create mode 100644 rainbow-quox/script/color/conv.ts create mode 100644 rainbow-quox/script/color/def.ts create mode 100644 rainbow-quox/script/color/rand.ts diff --git a/Makefile b/Makefile index 59126da..fcaa51e 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,9 @@ CSS = $(shell find fonts -type f) \ $(patsubst %.scss,%.css, \ $(wildcard rainbow-quox/style/*) $(wildcard style/*)) \ dnd/base.css dnd/bio.css dnd/index.css $(wildcard dnd/*/style.css) -SCRIPTS = $(patsubst %.ts,%.js,$(wildcard script/*.ts rainbow-quox/script/*.ts)) +SCRIPTS = $(patsubst %.ts,%.js, \ + $(wildcard script/*.ts rainbow-quox/script/color/*.ts \ + rainbow-quox/script/*.ts)) MISC = $(shell find .well-known -type f) ALL = $(CSS) $(PAGES) $(MEDIA) $(SCRIPTS) $(MISC) diff --git a/rainbow-quox/script/color.ts b/rainbow-quox/script/color.ts index 1cd1d39..4fdd815 100644 --- a/rainbow-quox/script/color.ts +++ b/rainbow-quox/script/color.ts @@ -1,194 +1,12 @@ -import * as R from './rand.js'; +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 Luma = number; -export type Chroma = number; -export type Hue = number; -export type Alpha = number; - -export type HueDistance = number; - -const MAXL: Luma = 0.9; -const MINL: Luma = 0.4; -const MINL_LIGHT: Luma = 0.7; -const MAXL_DARK: Luma = 0.65; - -const MINC_LIGHT: Chroma = 0.08; -const MAXC_LIGHT: Chroma = 0.1; -const MINC_DARK: Chroma = 0.12; -const MAXC_DARK: Chroma = 0.175; - -// max spread for a sequence of analogous colors. unless that would put them -// too close together -const MAXH_WIDTH: HueDistance = 80; - -// minimum distance between adjacent analogous colors -const MINH_SEP: HueDistance = 5; - -// size of the wedge a "complementary" color can be in -const MAXH_COMPL: HueDistance = 40; - -// size of the wedge a "triadic" color can be in -const MAXH_TRIAD: HueDistance = 25; - - -type LD = 'light' | 'dark'; - -export namespace Oklch { - export type Channel = 'l' | 'c' | 'h'; - export type Channels = Record; - export type ChannelMap = (x: number) => number; - export type ChannelMaps = Record; - - // a function, or constant value, for each channel; - // or nothing, which indicates identity function - export type With = Partial>; - export type With1 = ChannelMap | number | undefined; -} - -function isLight(l: Luma): boolean { return l >= MINL_LIGHT; } - -export namespace Rand { export type State = R.State; } - -type CloseFar = 'close' | 'far'; - -export class Rand extends R.Rand { - constructor(); - constructor([a, b, c, d]: Rand.State); - constructor(str: string); - constructor(st?: Rand.State | string) { - if (st === undefined) super(); - else if (typeof st === 'string') super(st); - else super(st); - } - - lightFor(baseL: Luma, d: CloseFar = 'close'): Luma { - let maxl = d == 'close' ? min(MAXL, baseL * 1.25) : MAXL; - return this.float(baseL, maxl); - } - darkFor(baseL: Luma, d: CloseFar = 'close'): Luma { - let minl = d == 'close' ? max(MINL, baseL * 0.8) : MINL - return this.float(minl, baseL); - } - - brightFor(l: Luma, baseC: Chroma): Chroma { - return this.float(baseC, isLight(l) ? MAXC_LIGHT : MAXC_DARK); - } - - dullFor(l: Luma, baseC: Chroma): Chroma { - return this.float(baseC, isLight(l) ? MINC_LIGHT : MINC_DARK); - } - - analogous1(baseH: Hue): Hue { - const size = this.float(MINH_SEP, 2 * MINH_SEP); - return this.boolean() ? baseH + size : baseH - size; - } - - analogous(baseH: Hue, count: number): Hue[] { - const minWidth = min(count * MINH_SEP, MAXH_WIDTH * 0.8); - const width = this.float(minWidth, MAXH_WIDTH); - const sep = width / (count - 1); - const start = baseH - (width / 2); - const numbers = Array.from({length: count}, (_u, i) => start + i * sep); - return this.boolean() ? numbers : numbers.reverse(); - } - - complementary1(baseH: Hue): Hue { - return this.analogous1((baseH + 180) % 360); - } - - complementary(baseH: Hue, count: number): Hue[] { - const angle = this.float(180 - MAXH_COMPL/2, 180 + MAXH_COMPL/2); - return this.analogous(baseH + angle, count); - } - - triad(baseH: Hue): [Hue, Hue] { - const angle = this.float(120 - MAXH_TRIAD/2, 120 + MAXH_TRIAD/2); - return [baseH - angle, baseH + angle]; - } - - baseLuma(ld?: LD): Luma { - if (ld == 'light') { - return this.float(MINL_LIGHT, MAXL); - } else if (ld == 'dark') { - return this.float(MINL, MAXL_DARK); - } else { - return this.float(MINL, MAXL); - } - } - - baseChroma(l: Luma): Chroma { - if (l >= MINL_LIGHT) { - return this.float(MINC_LIGHT, MAXC_LIGHT); - } else { - return this.float(MINC_DARK, MAXC_DARK); - } - } - - baseHue(): Hue { return this.float(360); } -} - - -export class Oklch { - readonly l: Luma; readonly c: Chroma; readonly h: Hue; - - static normHue(h: Hue) { return (h = h % 360) < 0 ? h + 360 : h; } - - constructor(l: Luma, c: Chroma, h: Hue); - constructor(r: Rand, ld?: LD); - constructor(cs: Oklch.Channels); - constructor(ll: Luma | Oklch.Channels | Rand, cc?: Chroma | LD, hh?: Hue) { - if (hh !== undefined) { - this.l = ll as Luma; - this.c = cc as Chroma; - this.h = hh as Hue; - } else if (typeof ll == 'object' && 'l' in ll) { - const {l, c, h} = ll as Oklch.Channels; - this.l = l; this.c = c; this.h = h; - } else { - const r = ll as Rand; - this.l = r.baseLuma(cc as LD | undefined); - this.c = r.baseChroma(this.l); - this.h = r.baseHue(); - } - } - - with(maps: Oklch.With): Oklch { - function call(comp: Oklch.With1, x: number) { - switch (typeof comp) { - case 'number': return comp; - case 'function': return comp(x); - default: return x; - } - } - return new Oklch({ - l: call(maps.l, this.l), - c: call(maps.c, this.c), - h: call(maps.h, this.h), - }); - } - - css(alpha: number = 1): string { - const l = (this.l * 100).toFixed(0); - const c = (this.c * 250).toFixed(0); - const h = this.h.toFixed(0); - if (alpha != 1) { return `oklch(${l}% ${c}% ${h} / ${alpha})`; } - else { return `oklch(${l}% ${c}% ${h})`; } - } - - rgb(): Rgb { return toRgbViaCanvas(this); } - - static validate(x: unknown): Oklch | undefined { - if (typeof x == 'object' && x != null && 'l' in x && 'c' in x && 'h' in x) { - const { l, c, h } = x; - if (typeof l == 'number' && typeof c == 'number' && typeof h == 'number') - return oklch(l, c, h); - } - } -} - export type SchemeType = 'triad' | 'fin-belly' | 'fin-body'; @@ -200,7 +18,7 @@ export type MiscLayer = 'eyes' | 'masks' | 'claws' | 'lines'; export type Layer = OuterLayer | SockLayer | FinLayer | BellyLayer | MiscLayer; -export type ColsOf = Record; +export type ColsOf = Record; export type OuterCols = ColsOf; export type SockCols = ColsOf; export type FinCols = ColsOf; @@ -223,73 +41,72 @@ export type BaseCol = 'outer' | 'belly' | 'fins'; export type OptionalBaseCol = 'eyes' | 'stripes'; type KnownPalette = - Record & Partial>; + Record & Partial>; export function colors(r: Rand = new Rand(), base?: KnownPalette): Scheme { - const outer = base?.outer ?? new Oklch(r, 'dark'); - let outerCols: OuterCols = + const outer = base?.outer ?? r.color('dark'); + const outerCols: OuterCols = { outer, spines: mkSpines(r, outer), vitiligo1: mkVitiligo(r, outer) }; const stripes = mkStripes(r); - let sockCols: SockCols = { stripes, cuffs: mkCuffs(r, stripes) }; + const sockCols: SockCols = { stripes, cuffs: mkCuffs(r, stripes) }; let finCols: FinCols, bellyCols: BellyCols, type: SchemeType; const whichBody = r.float(); if (whichBody > 2/3) { type = 'triad'; - const [f, b] = r.triad(outer.h); + 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.h, 2); + 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.h), outer, base); - bellyCols = mkBelly(r, r.complementary1(outer.h), base); + finCols = mkFins(r, r.analogous1(outer.hue), outer, base); + bellyCols = mkBelly(r, r.complementary1(outer.hue), base); } - let miscCols = mkMisc(r, outerCols, finCols, bellyCols, base); + const miscCols = mkMisc(r, outerCols, finCols, bellyCols, base); return merge(outerCols, sockCols, finCols, bellyCols, miscCols, type); } -function mkSpines(r: Rand, outer: Oklch): Oklch { - return outer.with({ +function mkSpines(r: Rand, outer: Color): Color { + return outer.with({ type: 'oklch', l: l => r.darkFor(l), - c: c => r.brightFor(outer.l, c), + c: c => r.brightFor(outer.luma, c), h: h => r.float(h + 12, h - 12), }) } -function mkVitiligo(r: Rand, outer: Oklch): Oklch { - return outer.with({ +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), MINC_LIGHT), + c: x => r.float(min(x, 0.1), r.mincLight), }); } -function mkStripes(r: Rand): Oklch { - return new Oklch({ - l: r.float(0.8, MAXL), - c: r.float(MINC_LIGHT, MAXC_LIGHT), - h: r.baseHue(), - }); +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: Oklch): Oklch { - return sock.with({ +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, MAXC_LIGHT), + c: c => r.float(c, r.maxcLight), h: h => r.float(h + 8, h - 8), }); } -function mkFins(r: Rand, h: Hue, outer: Oklch, base?: KnownPalette): FinCols { +function mkFins(r: Rand, h: Hue, outer: Color, + base?: KnownPalette): FinCols { const baseFin1 = base?.fins; - const [fin1Hue, fin2Hue, fin3Hue] = r.analogous(baseFin1?.h ?? h, 3); + const [fin1Hue, fin2Hue, fin3Hue] = r.analogous(baseFin1?.hue ?? h, 3); const direction: 'lighter' | 'darker' = r.choice(['lighter', 'darker']); @@ -300,26 +117,24 @@ function mkFins(r: Rand, h: Hue, outer: Oklch, base?: KnownPalette): FinCols { return direction == 'lighter' ? r.dullFor(l, c) : r.brightFor(l, c); } - const fins1 = baseFin1 ?? oklch(ll(outer.l), cc(outer.l, outer.c), fin1Hue!); - const fins2 = oklch(ll(fins1.l), cc(fins1.l, fins1.c), fin2Hue!); - const fins3 = oklch(ll(fins2.l), cc(fins2.l, fins2.c), fin3Hue!); - const lighter = fins1.l >= fins3.l ? fins1 : fins3; + const fins1 = baseFin1 ?? + oklch(ll(outer.luma), cc(outer.luma, outer.chroma), fin1Hue!); + const fins2 = oklch(ll(fins1.luma), cc(fins1.luma, fins1.chroma), fin2Hue!); + const fins3 = oklch(ll(fins2.luma), cc(fins2.luma, fins2.chroma), fin3Hue!); + const lighter = fins1.luma >= fins3.luma ? fins1 : fins3; const vitiligo4 = mkVitiligo(r, lighter); return { fins1, fins2, fins3, vitiligo4 }; } function mkBelly(r: Rand, h: Hue, base?: KnownPalette): BellyCols { - let baseBelly1 = base?.belly; - const [belly1Hue, belly2Hue] = r.analogous(baseBelly1?.h ?? h, 2); - const belly1 = baseBelly1 ?? new Oklch({ - l: r.float(0.7, MAXL), - c: r.baseChroma(1), - h: belly1Hue! - }); - const belly2 = belly1.with({ - l: x => min(MAXL, x * 1.1), + const baseBelly1 = base?.belly; + 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 = belly1.with({ type: 'oklch', + l: x => min(r.maxl, x * 1.1), c: x => x * 0.9, - h: belly2Hue!, + h: [belly2Hue!], }); const vitiligo3 = mkVitiligo(r, belly1); const vitiligo2 = mkVitiligo(r, belly2); @@ -328,28 +143,22 @@ function mkBelly(r: Rand, h: Hue, base?: KnownPalette): BellyCols { function mkMisc(r: Rand, o: OuterCols, f: FinCols, b: BellyCols, base?: KnownPalette): MiscCols { - const masks = new Oklch({ - l: r.float(0.8, MAXL), - c: r.float(0.01, 0.06), - h: r.analogous1(r.choice([o.outer, b.belly1, f.fins1]).h) - }); + const 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 ?? new Oklch({ - l: r.baseLuma('light'), - c: r.float(0.28, MAXC_LIGHT), - h: r.boolean() ? r.analogous1(o.outer.h) : r.complementary1(o.outer.h) - }), - claws: masks.with({ - l: x => min(MAXL, x + r.float(0, 0.1)), - c: r.float(0.01, 0.06), + 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: 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: new Oklch({ - l: r.float(0.01, 0.06), - c: r.baseChroma(0), - h: r.analogous1(o.outer.h) - }), + lines: oklch(r.float(0.01, 0.06), r.baseChroma(0), + r.analogous1(o.outer.hue)), }; } @@ -366,82 +175,6 @@ function merge({ outer, spines, vitiligo1 }: OuterCols, } -export namespace Rgb { - export type Channel = number; - export type Channels = { r: number, g: number, b: number }; -} - -export class Rgb { - readonly r: Rgb.Channel; - readonly g: Rgb.Channel; - readonly b: Rgb.Channel; - - static clamp(x: Rgb.Channel) { - return min(max(0, Math.floor(x)), 255); - } - - constructor(r: Rgb.Channel, g: Rgb.Channel, b: Rgb.Channel); - constructor({r, g, b}: Rgb.Channels); - constructor(rr: Rgb.Channel | Rgb.Channels, gg?: Rgb.Channel, bb?: Rgb.Channel) { - const C = Rgb.clamp; - if (typeof rr == 'number') { - this.r = C(rr!); this.g = C(gg!); this.b = C(bb!); - } else { - this.r = C(rr.r); this.g = C(rr.g); this.b = C(rr.b); - } - } - - css() { - function h(x: Rgb.Channel) { - let s = x.toString(16); - return s.length == 2 ? s : '0' + s; - } - return `#${h(this.r)}${h(this.g)}${h(this.b)}` - } - - static validate(x: unknown): Rgb | undefined { - if (typeof x == 'object' && x != null && 'r' in x && 'g' in x && 'b' in x) { - const { r, g, b } = x; - if (typeof r == 'number' && typeof g == 'number' && typeof b == 'number') - return rgb(r, g, b); - } - } -} - -export type Rgbs = Record; - -let rgbBuf: OffscreenCanvasRenderingContext2D; - -export function toRgbViaCanvas(col: Oklch): Rgb { - rgbBuf ??= new OffscreenCanvas(1, 1).getContext('2d')!; - rgbBuf.fillStyle = col.css(); - rgbBuf.fillRect(0, 0, 1, 1); - const pix = rgbBuf.getImageData(0, 0, 1, 1).data; - return rgb(pix[0]!, pix[1]!, pix[2]!); -} - -export function toRgbs(col: Colors): Rgbs { - return makeColorInfo(l => col[l].rgb()); -} - -export function toHex({r, g, b}: Rgb): string { - function chan(n: number) { - let a = Math.floor(n).toString(16); - return a.length == 1 ? `0${a}` : a; - } - return `#${chan(r)}${chan(g)}${chan(b)}`; -} - - -export function oklch(l: number, c: number, h: number) { - return new Oklch(l, c, h); -} - -export function rgb(r: number, g: number, b: number) { - return new Rgb(r, g, b); -} - - export const KNOWN: Record = { niss: { outer: oklch(0.83, 0.201, 151), diff --git a/rainbow-quox/script/color/conv.ts b/rainbow-quox/script/color/conv.ts new file mode 100644 index 0000000..312f945 --- /dev/null +++ b/rainbow-quox/script/color/conv.ts @@ -0,0 +1,126 @@ +export type Oklch = { type: 'oklch', l: number, c: number, h: number }; +export type Oklab = { type: 'oklab', l: number, a: number, b: number }; +export type Srgb = { type: 'srgb', r: number, g: number, b: number }; +export type Lrgb = { type: 'lrgb', r: number, g: number, b: number }; + +export type ColorData = Oklch | Oklab | Srgb; + +export type ColorType = 'oklch' | 'oklab' | 'srgb'; + + +type Deg = number; +type Rad = number; + +function deg2rad(θ: Deg): Rad { return θ / 180 * Math.PI; } +function rad2deg(θ: Rad): Deg { return θ * 180 / Math.PI; } +function dcos(θ: Deg): number { return Math.cos(deg2rad(θ)); } +function dsin(θ: Deg): number { return Math.sin(deg2rad(θ)); } +function datan2(b: number, a: number): Deg { return rad2deg(Math.atan2(b, a)); } + +export function oklch2oklab({ l, c, h }: Oklch): Oklab { + return { type: 'oklab', l, a: c * dcos(h), b: c * dsin(h) }; +} + +export function oklab2oklch({ l, a, b }: Oklab): Oklch { + return { type: 'oklch', l, c: Math.sqrt(a*a + b*b), h: datan2(b, a) } +} + +// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab +export function lrgb2oklab({ r, g, b }: Lrgb): Oklab { + const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b; + const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b; + const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b; + + const l_ = Math.cbrt(l); + const m_ = Math.cbrt(m); + const s_ = Math.cbrt(s); + + return { + type: 'oklab', + l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, + a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, + b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_, + }; +} + +export function oklab2lrgb({ l, a, b }: Oklab): Lrgb { + const L_ = l + 0.3963377774 * a + 0.2158037573 * b; + const M_ = l - 0.1055613458 * a - 0.0638541728 * b; + const S_ = l - 0.0894841775 * a - 1.2914855480 * b; + + const L = L_*L_*L_; + const M = M_*M_*M_; + const S = S_*S_*S_; + + return { + type: 'lrgb', + r: clamp(+4.0767416621 * L - 3.3077115913 * M + 0.2309699292 * S), + g: clamp(-1.2684380046 * L + 2.6097574011 * M - 0.3413193965 * S), + b: clamp(-0.0041960863 * L - 0.7034186147 * M + 1.7076147010 * S), + }; +} + +function clamp(x: number): number { + return Math.max(0, Math.min(1, x)); +} + +// https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F +function γ(x: number): number { + return x >= 0.0031308 ? 1.055 * x ** (1/2.4) - 0.055 + : 12.92 * x; +} + +function γ̂(x: number): number { + return x >= 0.04045 ? ((x + 0.055)/1.055) ** 2.4 + : x / 12.92; +} + +export function lrgb2srgb({ r, g, b }: Lrgb): Srgb { + return { type: 'srgb', r: γ(r), g: γ(g), b: γ(b) }; +} + +export function srgb2lrgb({ r, g, b }: Srgb): Lrgb { + return { type: 'lrgb', r: γ̂(r), g: γ̂(g), b: γ̂(b) }; +} + + +export function oklab2srgb(c: Oklab): Srgb { + return lrgb2srgb(oklab2lrgb(c)); +} + +export function oklch2srgb(c: Oklch): Srgb { + return oklab2srgb(oklch2oklab(c)); +} + +export function srgb2oklab(c: Srgb): Oklab { + return lrgb2oklab(srgb2lrgb(c)); +} + +export function srgb2oklch(c: Srgb): Oklch { + return oklab2oklch(srgb2oklab(c)); +} + + +export function toOklch(c: ColorData): Oklch { + switch (c.type) { + case 'oklch': return c; + case 'oklab': return oklab2oklch(c); + case 'srgb': return srgb2oklch(c); + } +} + +export function toOklab(c: ColorData): Oklab { + switch (c.type) { + case 'oklch': return oklch2oklab(c); + case 'oklab': return c; + case 'srgb': return srgb2oklab(c); + } +} + +export function toSrgb(c: ColorData): Srgb { + switch (c.type) { + case 'oklch': return oklch2srgb(c); + case 'oklab': return oklab2srgb(c); + case 'srgb': return c; + } +} diff --git a/rainbow-quox/script/color/def.ts b/rainbow-quox/script/color/def.ts new file mode 100644 index 0000000..cd78966 --- /dev/null +++ b/rainbow-quox/script/color/def.ts @@ -0,0 +1,136 @@ +import { Oklch, Oklab, Srgb, ColorType, ColorData } from './conv.js'; +import * as Conv from './conv.js'; +export { Oklch, Oklab, Srgb, ColorType, ColorData }; + + +export type CSSFormat = 'oklch' | 'oklab' | 'rgb' | 'hex'; + +export type ChannelMapper = [A] | ((x: A) => A); + +export type ColorMapper = + { type: C['type'] } & + { [k in Exclude]?: ChannelMapper }; + +export function apply(f: ChannelMapper | undefined, x: A): A { + if (typeof f == 'undefined') return x; + else if (typeof f == 'function') return f(x); + else return f[0]; +} + +export class Color { + readonly oklch: Oklch; + readonly oklab: Oklab; + readonly srgb: Srgb; + + constructor(c: ColorData) { + this.oklch = Conv.toOklch(c); + this.oklab = Conv.toOklab(c); + this.srgb = Conv.toSrgb(c); + } + + get luma() { return this.oklch.l; } + get chroma() { return this.oklch.c; } + get hue() { return this.oklch.h; } + get labA() { return this.oklab.a; } + get labB() { return this.oklab.b; } + get red() { return Math.floor(255 * this.srgb.r); } + get green() { return Math.floor(255 * this.srgb.g); } + get blue() { return Math.floor(255 * this.srgb.b); } + + css(format: CSSFormat = 'hex', α = 1): string { + switch (format) { + case 'oklch': { + const { l, c, h } = this.oklch; + return `oklch(${pc(l)} ${pc(c, 0.4)} ${deg(h)} / ${pc(α)})`; + } + case 'oklab': { + const { l, a, b } = this.oklab; + return `oklab(${pc(l)} ${pc(a)} ${pc(b)} / ${pc(α)})`; + } + case 'rgb': { + const { r, g, b } = this.srgb; + return `rgb(${pc(r)} ${pc(g)} ${pc(b)} / ${pc(α)})`; + } + case 'hex': { + const { r, g, b } = this.srgb; + return α == 1 ? `#${hex(r)}${hex(g)}${hex(b)}` : + `#${hex(r)}${hex(g)}${hex(b)}${hex(α)}`; + } + } + + function hex(c: number) { + const d = Math.min(255, Math.max(0, Math.floor(255 * c))); + const str = d.toString(16); + return str.length == 1 ? `0${str}` : str; + } + + function pc(c: number, max = 1) { + c *= 100 / max; + return `${c.toFixed(0)}%`; + } + + function deg(θ: number) { + θ %= 360; if (θ < 0) θ += 360; + return `${θ.toFixed(0)}deg`; + } + } + + with(maps: ColorMapper): Color; + with(maps: ColorMapper): Color; + with(maps: ColorMapper): Color { + switch (maps.type) { + case 'oklch': { + const { l, c, h } = this.oklch; + const { l: ll, c: cc, h: hh } = maps as ColorMapper; + return oklch(apply(ll, l), apply(cc, c), apply(hh, h)); + } + case 'srgb': { + const { r, g, b } = this.srgb; + const { r: rr, g: gg, b: bb } = maps as ColorMapper; + return rgb(apply(rr, r), apply(gg, g), apply(bb, b)); + } + } + } + + static validate(x: unknown): Color | undefined { + if (typeof x == 'object' && x != null) { + if ('l' in x && 'c' in x && 'h' in x) { + const { l, c, h } = x; + if (num(l) && num(c) && num(h)) return oklch(l, c, h); + } else if ('l' in x && 'a' in x && 'b' in x) { + const { l, a, b } = x; + if (num(l) && num(a) && num(b)) return oklab(l, a, b); + } else if ('r' in x && 'g' in x && 'b' in x) { + const { r, g, b } = x; + if (num(r) && num(g) && num(b)) return rgb(r, g, b); + } + } + function num(x: unknown): x is number { return typeof x == 'number'; } + } + + toJSON(): unknown { const { l, c, h } = this.oklch; return { l, c, h }; } +} + +export function oklch(l: number, c: number, h: number): Color { + return new Color({ type: 'oklch', l, c, h }); +} + +export function oklab(l: number, a: number, b: number): Color { + return new Color({ type: 'oklab', l, a, b }); +} + +export function rgb(r: number, g: number, b: number, + style: 'int' | 'float' = 'int'): Color { + return style == 'int' ? + new Color({ type: 'srgb', r: r / 255, g: g / 255, b: b / 255 }) : + new Color({ type: 'srgb', r, g, b }); +} + + + +export type Luma = number; +export type Chroma = number; +export type Hue = number; +export type Alpha = number; + +export type HueDistance = number; diff --git a/rainbow-quox/script/color/rand.ts b/rainbow-quox/script/color/rand.ts new file mode 100644 index 0000000..e9f12f3 --- /dev/null +++ b/rainbow-quox/script/color/rand.ts @@ -0,0 +1,120 @@ +import * as R from '../rand.js'; +import * as Color from './def.js'; + +const max = Math.max; +const min = Math.min; + + +export type State = R.State; + + +export type CloseFar = 'close' | 'far'; +export type LightDark = 'light' | 'dark'; + +export class Rand extends R.Rand { + maxl: Color.Luma = 0.9; + minl: Color.Luma = 0.4; + minlLight: Color.Luma = 0.7; + maxlDark: Color.Luma = 0.65; + + mincLight: Color.Chroma = 0.08; + maxcLight: Color.Chroma = 0.1; + mincDark: Color.Chroma = 0.12; + maxcDark: Color.Chroma = 0.175; + + // max spread for a sequence of analogous colors. unless that would put them + // too close together + maxhWidth: Color.HueDistance = 80; + + // minimum distance between adjacent analogous colors + minhSep: Color.HueDistance = 5; + + // size of the wedge a "complementary" color can be in + maxhCompl: Color.HueDistance = 40; + + // size of the wedge a "triadic" color can be in + maxhTriad: Color.HueDistance = 25; + + constructor(); + constructor([a, b, c, d]: State); + constructor(str: string); + constructor(st?: State | string) { + if (st === undefined) super(); + else if (typeof st === 'string') super(st); + else super(st); + } + + isLight(l: Color.Luma): boolean { return l >= this.minlLight; } + + lightFor(baseL: Color.Luma, d: CloseFar = 'close'): Color.Luma { + const maxl = d == 'close' ? min(this.maxl, baseL * 1.25) : this.maxl; + return this.float(baseL, maxl); + } + darkFor(baseL: Color.Luma, d: CloseFar = 'close'): Color.Luma { + const minl = d == 'close' ? max(this.minl, baseL * 0.8) : this.minl + return this.float(minl, baseL); + } + + brightFor(l: Color.Luma, baseC: Color.Chroma): Color.Chroma { + return this.float(baseC, this.isLight(l) ? this.maxcLight : this.maxcDark); + } + + dullFor(l: Color.Luma, baseC: Color.Chroma): Color.Chroma { + return this.float(baseC, this.isLight(l) ? this.mincLight : this.mincDark); + } + + analogous1(baseH: Color.Hue): Color.Hue { + const size = this.float(this.minhSep, 2 * this.minhSep); + return this.boolean() ? baseH + size : baseH - size; + } + + analogous(baseH: Color.Hue, count: number): Color.Hue[] { + const minWidth = min(count * this.minhSep, this.maxhWidth * 0.8); + const width = this.float(minWidth, this.maxhWidth); + const sep = width / (count - 1); + const start = baseH - (width / 2); + const numbers = Array.from({length: count}, (_u, i) => start + i * sep); + return this.boolean() ? numbers : numbers.reverse(); + } + + complementary1(baseH: Color.Hue): Color.Hue { + return this.analogous1((baseH + 180) % 360); + } + + complementary(baseH: Color.Hue, count: number): Color.Hue[] { + const angle = this.float(180 - this.maxhCompl/2, 180 + this.maxhCompl/2); + return this.analogous(baseH + angle, count); + } + + triad(baseH: Color.Hue): [Color.Hue, Color.Hue] { + const angle = this.float(120 - this.maxhTriad/2, 120 + this.maxhTriad/2); + return [baseH - angle, baseH + angle]; + } + + baseLuma(ld?: LightDark): Color.Luma { + if (ld == 'light') { + return this.float(this.minlLight, this.maxl); + } else if (ld == 'dark') { + return this.float(this.minl, this.maxlDark); + } else { + return this.float(this.minl, this.maxl); + } + } + + baseChroma(l: Color.Luma): Color.Chroma { + if (l >= this.minlLight) { + return this.float(this.mincLight, this.maxcLight); + } else { + return this.float(this.mincDark, this.maxcDark); + } + } + + baseHue(): Color.Hue { return this.float(360); } + + color(ld?: LightDark): Color.Color { + const l = this.baseLuma(ld); + const c = this.baseChroma(l); + const h = this.baseHue(); + return Color.oklch(l, c, h); + } +} diff --git a/rainbow-quox/script/history.ts b/rainbow-quox/script/history.ts index 47fe42e..0e7ede6 100644 --- a/rainbow-quox/script/history.ts +++ b/rainbow-quox/script/history.ts @@ -1,19 +1,17 @@ -import { Colors as Oklchs, Rgbs } from './color.js'; +import { Colors } from './color.js'; import * as Color from './color.js'; export class HistoryItem { - name: string; - oklch: Oklchs; - rgb: Rgbs; + name: string; + cols: Colors; - constructor(name: string, oklch: Oklchs, rgb: Rgbs) { - this.oklch = oklch; - this.rgb = rgb; + constructor(name: string, cols: Colors) { + this.cols = cols; this.name = name; } asHtml(): HTMLButtonElement { - const { lines, outer, belly1: belly, fins1: fins } = this.rgb; + const { lines, outer, belly1: belly, fins1: fins } = this.cols; const content = ` @@ -37,7 +35,7 @@ export class HistoryItem { ${this.name} `; - let button = document.createElement('button'); + const button = document.createElement('button'); button.className = 'history-item'; button.dataset.name = this.name; button.innerHTML = content; @@ -54,7 +52,7 @@ export class History { add(name: string): void { this.items.push(name); } *iterNames(maxLength: number = 100): Iterable { - let seen = new Set; + const seen = new Set; let done = 0; for (let i = this.items.length - 1; i >= 0; i--) { @@ -71,10 +69,9 @@ export class History { // pass a negative number to iterate over all *iterItems(maxLength?: number): Iterable { for (const name of this.iterNames(maxLength)) { - const oklch = Color.colors(new Color.Rand(name), Color.KNOWN[name]); - const rgbs = Color.toRgbs(oklch); + const cols = Color.colors(new Color.Rand(name), Color.KNOWN[name]); - yield new HistoryItem(name, oklch, rgbs); + yield new HistoryItem(name, cols); } } @@ -107,8 +104,8 @@ export class History { } prune(maxLength?: number): void { - let keep = []; - for (let name of this.iterNames(maxLength)) keep.push(name); + const keep = []; + for (const name of this.iterNames(maxLength)) keep.push(name); this.items = keep.reverse(); } } diff --git a/rainbow-quox/script/layer.ts b/rainbow-quox/script/layer.ts index 4727b20..49db489 100644 --- a/rainbow-quox/script/layer.ts +++ b/rainbow-quox/script/layer.ts @@ -4,14 +4,18 @@ async function loadBitmap(url: string): Promise { const img0 = new Image; const img: Promise = new Promise((ok, err) => { img0.addEventListener('load', () => ok(img0)); - img0.addEventListener('error', () => err(`couldn't load file: ${url}`)); + img0.addEventListener('error', () => { + err(new Error(`couldn't load file: ${url}`)); + }); }); img0.src = url; return createImageBitmap(await img); } -export type Buffer = OffscreenCanvasRenderingContext2D; +export type Buffer = + CanvasCompositing & CanvasDrawImage & CanvasImageData & + CanvasRect & CanvasState; function dataViaBuffer(bmp: ImageBitmap, buf: Buffer): ImageData { buf.clearRect(0, 0, bmp.width, bmp.height); @@ -19,34 +23,10 @@ function dataViaBuffer(bmp: ImageBitmap, buf: Buffer): ImageData { return buf.getImageData(0, 0, bmp.width, bmp.height); } -async function loadDataLocking(url: string, buf: Buffer): Promise { - return loadBitmap(url).then(i => - navigator.locks.request('imagebuf', () => dataViaBuffer(i, buf))); -} - -async function loadDataFresh(url: string): Promise { - const img = await loadBitmap(url); - let buf = new OffscreenCanvas(img.width, img.height).getContext('2d')!; - return dataViaBuffer(img, buf); -} - -export function loadImageData(url: string, buf?: Buffer): Promise { - if (buf && navigator.locks) return loadDataLocking(url, buf); - else return loadDataFresh(url); -} - export const WIDTH = 1040; export const HEIGHT = 713; -export function makeBuffer(width = WIDTH, height = HEIGHT): Buffer { - return new OffscreenCanvas(width, height).getContext('2d')!; -} - -function makeBufferIfLocks(width?: number, height?: number): Buffer | undefined { - if (navigator.locks) return makeBuffer(width, height); -} - export type Layer = 'stroke' | 'static' | 'eyeshine' | Color.Layer; // in compositing order @@ -61,14 +41,16 @@ export function makeLayerInfo(f: (l: Layer) => A): Record { export async function makeLayerInfoAsync(f: (l: Layer) => Promise): Promise> { - let list = await Promise.all(allLayers.map(l => f(l).then(res => [l, res]))); - return Object.fromEntries(list); + const list = await Promise.all(allLayers.map(l => f(l).then(res => [l, res]))); + return Object.fromEntries(list) as Promise>; } -export function loadLayers(dir: string): Promise> { - let buf = makeBufferIfLocks(WIDTH, HEIGHT); - return makeLayerInfoAsync(l => loadImageData(`./${dir}/${l}.webp`, buf)); +export async function +loadLayers(dir: string, buf: Buffer): Promise> { + const bitmaps = + await makeLayerInfoAsync(l => loadBitmap(`./${dir}/${l}.webp`)); + return makeLayerInfo(l => dataViaBuffer(bitmaps[l], buf)); } @@ -76,7 +58,7 @@ export type Position = [x: number, y: number]; export type Positions = Record; export async function loadPos(dir: string): Promise { - return (await fetch(`./${dir}/pos.json`)).json(); + return (await fetch(`./${dir}/pos.json`)).json() as Promise; } @@ -95,10 +77,10 @@ export type Data = { export type ComposedData = Required; -export async function loadData(): Promise { - let [fl, fp, bl, bp] = await Promise.all([ - loadLayers('front'), loadPos('front'), - loadLayers('back'), loadPos('back') +export async function loadData(buf: Buffer): Promise { + const [fl, fp, bl, bp] = await Promise.all([ + loadLayers('front', buf), loadPos('front'), + loadLayers('back', buf), loadPos('back') ]); return { front: makeLayerInfo(l => [fl[l], fp[l]]), @@ -107,13 +89,13 @@ export async function loadData(): Promise { } -function recolor({ data }: ImageData, { r, g, b }: Color.Rgb) { +function recolor({ data }: ImageData, col: Color.Color) { for (let i = 0; i < data.length; i += 4) { - data[i] = r; data[i+1] = g; data[i+2] = b; + data[i] = col.red; data[i+1] = col.green; data[i+2] = col.blue; } } -export async function recolorAll(layers: Data, cols: Color.Rgbs) { +export async function recolorAll(layers: Data, cols: Color.Colors) { await Promise.all(Color.allLayers.map(l => { recolor(layers.front[l][0], cols[l]); recolor(layers.back[l][0], cols[l]); @@ -139,7 +121,7 @@ async function compose(buf: Buffer, layers: ComposeLayer[], export async function ensureComposed(buf: Buffer, data: Data): Promise { - let { front, back } = data; + const { front, back } = data; data.frontImage ??= await composeLayers(front); data.backImage ??= await composeLayers(back); return data as ComposedData; @@ -148,14 +130,12 @@ ensureComposed(buf: Buffer, data: Data): Promise { return compose(buf, allLayers.map(l => makeLayer(l, sdata)), WIDTH, HEIGHT); } function makeLayer(l: Layer, sdata: SideData): ComposeLayer { - let [i, p] = sdata[l]; + const [i, p] = sdata[l]; return [i, p, l == 'eyeshine' ? 'luminosity' : 'source-over']; } } -export async function redraw(ctx: CanvasRenderingContext2D, - buf: Buffer, data: ComposedData, side: Side) { - await ensureComposed(buf, data); +export function redraw(ctx: CanvasImageData, data: ComposedData, side: Side) { ctx.putImageData(data[`${side}Image`], 0, 0); } diff --git a/rainbow-quox/script/palette.ts b/rainbow-quox/script/palette.ts index 1b4501b..6a4839f 100644 --- a/rainbow-quox/script/palette.ts +++ b/rainbow-quox/script/palette.ts @@ -1,18 +1,19 @@ -import { Rgb, Rgbs, rgb } from './color.js'; +import { Color, Colors, rgb } from './color.js'; import { Layer } from './layer.js'; -export type Color = +export type Swatch = Exclude | 'collars' | 'bells' | 'tongues' | 'socks' | 'sclera'; // in palette order -export const COLORS: Color[] = - ['lines', 'outer', 'vitiligo1', 'spines', 'fins1', 'fins2', 'fins3', - 'vitiligo4', 'belly1', 'vitiligo3', 'belly2', 'vitiligo2', 'sclera', - 'eyes', 'tongues', 'masks', 'claws', 'socks', 'stripes', 'cuffs', - 'collars', 'bells']; +export const SWATCHES: Swatch[] = + ['lines', 'outer', 'vitiligo1', 'spines', + 'socks', 'stripes', 'cuffs', + 'vitiligo4', 'belly1', 'vitiligo3', 'belly2', 'vitiligo2', + 'fins1', 'fins2', 'fins3', 'masks', 'claws', + 'sclera', 'eyes', 'tongues', 'collars', 'bells']; -export const NAMES: Partial> = { +export const NAMES: Partial> = { outer: 'outer body', stripes: 'sock stripes', cuffs: 'sock cuffs', @@ -27,13 +28,13 @@ export const NAMES: Partial> = { vitiligo4: 'fins vitiligo', }; -export function name(l: Color): string { +export function name(l: Swatch): string { return NAMES[l] ?? l; } -export type StaticColor = Exclude; +export type StaticColor = Exclude; -export const STATIC_COLS: Record = { +export const STATIC_COLS: Record = { collars: rgb(206, 75, 101), bells: rgb(235, 178, 79), tongues: rgb(222, 165, 184), @@ -41,23 +42,23 @@ export const STATIC_COLS: Record = { sclera: rgb(238, 239, 228), }; -export function get(col: Color, palette: Rgbs): Rgb { - type PPalette = Partial>; - let p = palette as PPalette; - let s = STATIC_COLS as PPalette; +export function get(col: Swatch, palette: Colors): Color { + type PPalette = Partial>; + const p = palette as PPalette; + const s = STATIC_COLS as PPalette; return (p[col] ?? s[col])!; } -export function make(seed: string, palette: Rgbs): Blob { - let lines = [ +export function make(seed: string, palette: Colors): Blob { + const lines = [ "GIMP Palette\n", `Name: quox ${seed}\n`, "Columns: 6\n\n", ]; - for (const col of COLORS) { - let { r, g, b } = get(col, palette); - lines.push(`${r} ${g} ${b} ${name(col)}\n`); + for (const sw of SWATCHES) { + const col = get(sw, palette); + lines.push(`${col.red} ${col.green} ${col.blue} ${name(sw)}\n`); } return new Blob(lines, { type: 'application/x-gimp-palette' }); diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index ab19ebf..a8bad51 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -16,7 +16,7 @@ function message(msg: string, size = 100) { function urlState(): string | undefined { - let hash = document.location.hash?.substring(1); + const hash = document.location.hash?.substring(1); if (hash != '' && hash !== undefined) return decodeURI(hash); } @@ -28,37 +28,35 @@ type ApplyStateOpts = { seed: string, side?: Layer.Side, firstLoad?: boolean, - buf?: Layer.Buffer, history?: History, done?: Done, }; async function applyState(data: Layer.Data, opts: ApplyStateOpts): Promise { - let { side, seed, firstLoad, buf, history, done } = opts; + const { seed, history } = opts; + let { side, firstLoad, done } = opts; side ??= 'front'; firstLoad ??= false; - buf ??= Layer.makeBuffer(); done ??= () => {}; - let rand = new Color.Rand(seed); + const rand = new Color.Rand(seed); - const oklch = Color.colors(rand, Color.KNOWN[seed]); - const rgb = Color.toRgbs(oklch); + const cols = Color.colors(rand, Color.KNOWN[seed]); const newSeed = rand.alphaNum(); - await Layer.recolorAll(data, rgb); + await Layer.recolorAll(data, cols); - updateBg(oklch); - updateSvgs(oklch, rgb); + updateBg(cols); + updateSvgs(cols); updateLabel(seed); updateUrl(seed); if (firstLoad) { - await instantUpdateImage(side, await Layer.ensureComposed(buf, data)); + await instantUpdateImage(side, data); done(); } else { - await animateUpdateImage(buf, side, data, done); + await animateUpdateImage(side, data, done); } if (history) history.addSave(seed); @@ -74,8 +72,9 @@ function getCanvasCtx(id: CanvasId) { } async function -instantUpdateImage(side: Layer.Side, data: Layer.ComposedData) { - getCanvasCtx('main').putImageData(data[`${side}Image`], 0, 0); +instantUpdateImage(side: Layer.Side, data: Layer.Data) { + const cdata = await Layer.ensureComposed(getCanvasCtx('aux'), data); + getCanvasCtx('main').putImageData(cdata[`${side}Image`], 0, 0); } type Done = () => void; @@ -83,10 +82,9 @@ type Done = () => void; const noAnim = matchMedia('(prefers-reduced-motion: reduce)'); async function -animateUpdateImage(buf: Layer.Buffer, side: Layer.Side, - data: Layer.Data, done: Done) { +animateUpdateImage(side: Layer.Side, data: Layer.Data, done: Done) { if (noAnim.matches) { - instantUpdateImage(side, await Layer.ensureComposed(buf, data)); + await instantUpdateImage(side, data); done(); return; } @@ -97,11 +95,11 @@ animateUpdateImage(buf: Layer.Buffer, side: Layer.Side, const aux = getCanvasCtx('aux'); document.documentElement.dataset.running = 'reroll'; - const cdata = await Layer.ensureComposed(buf, data); - Layer.redraw(aux, buf, cdata, side); + const cdata = await Layer.ensureComposed(aux, data); + Layer.redraw(aux, cdata, side); - aux.canvas.addEventListener('animationend', async () => { - await Layer.redraw(main, buf, cdata, side); + aux.canvas.addEventListener('animationend', () => { + Layer.redraw(main, cdata, side); aux.canvas.style.removeProperty('animation'); delete document.documentElement.dataset.running; done(); @@ -113,10 +111,9 @@ animateUpdateImage(buf: Layer.Buffer, side: Layer.Side, } async function -animateSwapImage(buf: Layer.Buffer, newSide: Layer.Side, - data: Layer.ComposedData, done: Done) { +animateSwapImage(newSide: Layer.Side, data: Layer.ComposedData, done: Done) { if (noAnim.matches) { - instantUpdateImage(newSide, data); + await instantUpdateImage(newSide, data); done(); return; } @@ -127,9 +124,9 @@ animateSwapImage(buf: Layer.Buffer, newSide: Layer.Side, const aux = getCanvasCtx('aux'); document.documentElement.dataset.running = 'swap'; - await Layer.redraw(aux, buf, data, newSide); + Layer.redraw(aux, data, newSide); - aux.canvas.addEventListener('animationend', async () => { + aux.canvas.addEventListener('animationend', () => { const image = aux.getImageData(0, 0, Layer.WIDTH, Layer.HEIGHT); main.putImageData(image, 0, 0); @@ -144,7 +141,7 @@ animateSwapImage(buf: Layer.Buffer, newSide: Layer.Side, } function updateBg(cols: Color.Colors) { - document.documentElement.style.setProperty('--hue', `${cols.outer.h}`); + document.documentElement.style.setProperty('--hue', `${cols.outer.hue}`); } function updateLabel(seed: string) { @@ -152,21 +149,21 @@ function updateLabel(seed: string) { if (stateLabel) stateLabel.innerHTML = seed; } -function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { +function updateSvgs(cols: Color.Colors) { const paletteObj = document.getElementById('palette') as HTMLObjectElement; const palette = paletteObj.contentDocument as XMLDocument | null; if (palette) { - palette.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); + palette.documentElement.style.setProperty('--hue', `${cols.outer.hue}`); const get = (id: string) => palette.getElementById(id); for (const layer of Color.allLayers) { - let col = rgb[layer].css(); + const col = cols[layer].css(); let elem; // main group - if (elem = get(`i-${layer}`)) { - if (oklch[layer].l < 0.6) { + if ((elem = get(`i-${layer}`))) { + if (cols[layer].luma < 0.6) { elem.classList.add('light'); elem.classList.remove('dark'); } else { elem.classList.add('dark'); elem.classList.remove('light'); @@ -175,9 +172,9 @@ function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { } // label - if (elem = get(`c-${layer}`)) elem.innerHTML = col; + if ((elem = get(`c-${layer}`))) elem.innerHTML = col; // minor swatch, if applicable - if (elem = get(`s-${layer}`)) elem.style.setProperty('--col', col); + if ((elem = get(`s-${layer}`))) elem.style.setProperty('--col', col); } } } @@ -188,14 +185,12 @@ function showHistory(history: History, data: Layer.Data, const list = document.getElementById('history-items'); if (!list) return; list.innerHTML = ''; - let { side, firstLoad, buf, done } = opts; + const { side, firstLoad, done } = opts; for (const item of history.iterItems()) { const elem = item.asHtml(); - let allOpts = { side, firstLoad, buf, done, seed: item.name, history }; - elem.addEventListener('click', () => { - applyState(data, allOpts); - }); + const allOpts = { side, firstLoad, done, seed: item.name, history }; + elem.addEventListener('click', () => void applyState(data, allOpts)); list.appendChild(elem); } @@ -211,13 +206,13 @@ function showHistory(history: History, data: Layer.Data, function closeHistory() { document.getElementById('history-items')?. scroll({ top: 0, left: 0, behavior: 'smooth' }); - let field = document.getElementById('history-close-target'); + const field = document.getElementById('history-close-target'); if (field) field.parentElement?.removeChild(field); document.documentElement.dataset.state = 'ready'; } function download(seed: string) { - const colors = Color.toRgbs(Color.colors(new Color.Rand(seed))); + const colors = Color.colors(new Color.Rand(seed)); const blob = Palette.make(seed, colors); // there must be a better way to push out a file than @@ -233,14 +228,15 @@ function download(seed: string) { async function setup() { message('loading layers…'); - let data = await Layer.loadData().catch(e => { message(e, 30); throw e }); - let history = History.load(); + const aux = getCanvasCtx('aux'); - let buf = Layer.makeBuffer(); + const data = await Layer.loadData(aux) + .catch(e => { message(`${e}`, 30); throw e }); + const history = History.load(); let prevSeed = urlState() ?? new Color.Rand().alphaNum(); let seed = - await applyState(data, { seed: prevSeed, buf, history, firstLoad: true }); + await applyState(data, { seed: prevSeed, history, firstLoad: true }); let side: Layer.Side = 'front'; const reroll = document.getElementById('reroll')!; @@ -248,6 +244,10 @@ async function setup() { addListeners(); + function asyncHandler(h: (e: Event) => Promise): (e: Event) => void { + return (e: Event) => void h(e); + } + // these ones don't need to be toggled document.getElementById('hideui')?.addEventListener('click', () => { document.documentElement.dataset.state = 'fullquox'; @@ -258,38 +258,38 @@ async function setup() { document.getElementById('history-button')?.addEventListener('click', () => { // does this need the add/remove listeners dance // actually does anything any more? - showHistory(history, data, { side, buf }); + showHistory(history, data, { side }); }); document.getElementById('close-history')?.addEventListener('click', closeHistory); - document.getElementById('current-name')?.addEventListener('focusout', async e => { + document.getElementById('current-name')?.addEventListener('focusout', asyncHandler(async e => { const space = String.raw`(\n|\s|
| )`; const re = new RegExp(`^${space}+|${space}+$`, 'msgu'); - let elem = e.target as HTMLElement; + const elem = e.target as HTMLElement; let str = elem.innerText.replaceAll(re, ''); if (!str) str = new Color.Rand().alphaNum(); elem.innerText = str; // todo allow images cos it's funny prevSeed = seed; - seed = await applyState(data, { side, seed: str, buf, history }); - }); + seed = await applyState(data, { side, seed: str, history }); + })); document.getElementById('download-button')?.addEventListener('click', () => { download(prevSeed); }); document.documentElement.dataset.state = 'ready'; - async function run(task: (k: Done) => Promise): Promise { + function run(task: (k: Done) => Promise): void { removeListeners(); - await task(addListeners); + void task(addListeners); } function updateFromUrl() { run(async k => { const newSeed = urlState(); if (newSeed) { - const opts = { history, side, seed: newSeed, buf, done: k }; + const opts = { history, side, seed: newSeed, done: k }; prevSeed = seed; seed = await applyState(data, opts); } @@ -298,14 +298,14 @@ async function setup() { function runReroll() { run(async k => { prevSeed = seed; - seed = await applyState(data, { side, seed, buf, history, done: k }); + seed = await applyState(data, { side, seed, history, done: k }); }); } function runSwap() { run(async k => { side = Layer.swapSide(side); - const cdata = await Layer.ensureComposed(buf, data); - await animateSwapImage(buf, side, cdata, k); + const cdata = await Layer.ensureComposed(aux, data); + await animateSwapImage(side, cdata, k); }); } @@ -321,4 +321,4 @@ async function setup() { } } -document.addEventListener('DOMContentLoaded', setup); +document.addEventListener('DOMContentLoaded', () => void setup()); diff --git a/rainbow-quox/script/rand.ts b/rainbow-quox/script/rand.ts index efc8a1c..0be4ac6 100644 --- a/rainbow-quox/script/rand.ts +++ b/rainbow-quox/script/rand.ts @@ -73,7 +73,7 @@ export class Rand implements Randy { h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233); h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213); h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179); - h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1; + h1 ^= (h2 ^ h3 ^ h4); h2 ^= h1; h3 ^= h1; h4 ^= h1; return [h1>>>0, h2>>>0, h3>>>0, h4>>>0]; } @@ -112,7 +112,7 @@ export class Rand implements Randy { #next(): number { this.#a |= 0; this.#b |= 0; this.#c |= 0; this.#d |= 0; - let t = (this.#a + this.#b | 0) + this.#d | 0; + const t = (this.#a + this.#b | 0) + this.#d | 0; this.#d = this.#d + 1 | 0; this.#a = this.#b ^ this.#b >>> 9; this.#b = this.#c + (this.#c << 3) | 0; From a4f3877b7833c0b153e672f9916b8170c1675b05 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Wed, 1 Jan 2025 00:40:43 +0100 Subject: [PATCH 02/23] fix "my name is" font size and download z index --- rainbow-quox/style/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rainbow-quox/style/style.scss b/rainbow-quox/style/style.scss index 4a16007..f02d2b2 100644 --- a/rainbow-quox/style/style.scss +++ b/rainbow-quox/style/style.scss @@ -205,6 +205,7 @@ $button-fg: oklch(0.98 0.1 var(--c-hue)); @include history-box; position: absolute; top: 0; left: 0; + font-size: 150%; display: grid; grid-template: "a c" "b c" / auto min-content; gap: 0 15px; @@ -281,7 +282,7 @@ $button-fg: oklch(0.98 0.1 var(--c-hue)); @layer layering { #main { z-index: 0; } #aux { z-index: 1; } - #buttons, #palette-holder, #back, #showui { z-index: 2; } + #buttons, #palette-holder, #back, #showui, #download-button { z-index: 2; } #history-close-target { z-index: 3; } #history { z-index: 4; } } From 15e6c50641e7a60723c53ef526be59184d1b3bc0 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Wed, 1 Jan 2025 00:41:07 +0100 Subject: [PATCH 03/23] =?UTF-8?q?let=20=E2=86=92=20const?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/run.ts | 4 ++-- script/shuffle.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/script/run.ts b/script/run.ts index 3a0f836..b8c0d0b 100644 --- a/script/run.ts +++ b/script/run.ts @@ -183,7 +183,7 @@ export function applyConfiguration(): void { } export function move(c: Conf, ...ms: Rotation[]): Conf { - let res: Partial = {}; + const res: Partial = {}; for (const pane of allPanes) { res[pane] = applyMoves(c[pane], ms) } return res as Conf; } @@ -290,7 +290,7 @@ export function fadeTo(newPane: Pane): void { -let reducedMotion = +const reducedMotion = matchMedia(`(prefers-reduced-motion: reduce), (max-height: 649px), (max-width: 649px)`); diff --git a/script/shuffle.ts b/script/shuffle.ts index 1ec208f..7df95a7 100644 --- a/script/shuffle.ts +++ b/script/shuffle.ts @@ -1,5 +1,5 @@ function shuffle
(subject: A[]): A[] { - let res = Array.from(subject); + const res = Array.from(subject); for (let i = 0; i < res.length - 1; ++i) { const j = i + Math.floor(Math.random() * (res.length - i)); @@ -16,16 +16,16 @@ function shuffle(subject: A[]): A[] { function group(subject: A[], keepTogether: A[][]): A[][] { type Value = {array: A[], added: boolean}; - let groups: Map = new Map; + const groups: Map = new Map; for (const xs of keepTogether) { - let value = {array: xs, added: false}; + const value = {array: xs, added: false}; for (const x of xs) { groups.set(x, value); } } - let res = []; + const res = []; for (const x of subject) { - let group = groups.get(x); + const group = groups.get(x); if (group?.added) { continue; } else if (group) { group.added = true; @@ -43,10 +43,10 @@ function groupedShuffle(subject: A[], keepTogether: A[][]): A[] { } function shuffleAll() { - let groups = [group('myno', 'abyss'), group('clip', 'cervine')]; + const groups = [group('myno', 'abyss'), group('clip', 'cervine')]; for (const elem of Array.from(document.getElementsByClassName('shuffle'))) { - let shuffled = groupedShuffle(Array.from(elem.children), groups); + const shuffled = groupedShuffle(Array.from(elem.children), groups); elem.innerHTML = ''; for (const child of shuffled) { @@ -55,7 +55,7 @@ function shuffleAll() { } function group(...xs: string[]) { - let elements = xs.map(x => document.getElementById(x)); + const elements = xs.map(x => document.getElementById(x)); return elements.every(x => x) ? elements as HTMLElement[] : []; } } From 9bd5edeeb3cd26c161bc1a794d0248253c6ee2ba Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sat, 15 Feb 2025 23:54:25 +0100 Subject: [PATCH 04/23] remove svg, eot, woff(1) versions of muller. who needs those --- fonts/muller/muller.css | 140 ++++++++++++---------------------------- 1 file changed, 40 insertions(+), 100 deletions(-) diff --git a/fonts/muller/muller.css b/fonts/muller/muller.css index fe3d69a..b63ff7e 100644 --- a/fonts/muller/muller.css +++ b/fonts/muller/muller.css @@ -2,11 +2,8 @@ font-family: Muller; font-weight: 50; src: - url(050.eot) format('embedded-opentype'), - url(050.svg) format('svg'), - url(050.ttf) format('truetype'), - url(050.woff) format('woff'), - url(050.woff2) format('woff2'); + url(050.woff2) format('woff2'), + url(050.ttf) format('truetype'); } @font-face { @@ -14,22 +11,16 @@ font-weight: 50; font-style: italic; src: - url(050i.eot) format('embedded-opentype'), - url(050i.svg) format('svg'), - url(050i.ttf) format('truetype'), - url(050i.woff) format('woff'), - url(050i.woff2) format('woff2'); + url(050i.woff2) format('woff2'), + url(050i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 100; src: - url(100.eot) format('embedded-opentype'), - url(100.svg) format('svg'), - url(100.ttf) format('truetype'), - url(100.woff) format('woff'), - url(100.woff2) format('woff2'); + url(100.woff2) format('woff2'), + url(100.ttf) format('truetype'); } @font-face { @@ -37,22 +28,16 @@ font-weight: 100; font-style: italic; src: - url(100i.eot) format('embedded-opentype'), - url(100i.svg) format('svg'), - url(100i.ttf) format('truetype'), - url(100i.woff) format('woff'), - url(100i.woff2) format('woff2'); + url(100i.woff2) format('woff2'), + url(100i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 200; src: - url(200.eot) format('embedded-opentype'), - url(200.svg) format('svg'), - url(200.ttf) format('truetype'), - url(200.woff) format('woff'), - url(200.woff2) format('woff2'); + url(200.woff2) format('woff2'), + url(200.ttf) format('truetype'); } @font-face { @@ -60,22 +45,16 @@ font-weight: 200; font-style: italic; src: - url(200i.eot) format('embedded-opentype'), - url(200i.svg) format('svg'), - url(200i.ttf) format('truetype'), - url(200i.woff) format('woff'), - url(200i.woff2) format('woff2'); + url(200i.woff2) format('woff2'), + url(200i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 300; src: - url(300.eot) format('embedded-opentype'), - url(300.svg) format('svg'), - url(300.ttf) format('truetype'), - url(300.woff) format('woff'), - url(300.woff2) format('woff2'); + url(300.woff2) format('woff2'), + url(300.ttf) format('truetype'); } @font-face { @@ -83,22 +62,16 @@ font-weight: 300; font-style: italic; src: - url(300i.eot) format('embedded-opentype'), - url(300i.svg) format('svg'), - url(300i.ttf) format('truetype'), - url(300i.woff) format('woff'), - url(300i.woff2) format('woff2'); + url(300i.woff2) format('woff2'), + url(300i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 400; src: - url(400.eot) format('embedded-opentype'), - url(400.svg) format('svg'), - url(400.ttf) format('truetype'), - url(400.woff) format('woff'), - url(400.woff2) format('woff2'); + url(400.woff2) format('woff2'), + url(400.ttf) format('truetype'); } @font-face { @@ -106,22 +79,16 @@ font-weight: 400; font-style: italic; src: - url(400i.eot) format('embedded-opentype'), - url(400i.svg) format('svg'), - url(400i.ttf) format('truetype'), - url(400i.woff) format('woff'), - url(400i.woff2) format('woff2'); + url(400i.woff2) format('woff2'), + url(400i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 500; src: - url(500.eot) format('embedded-opentype'), - url(500.svg) format('svg'), - url(500.ttf) format('truetype'), - url(500.woff) format('woff'), - url(500.woff2) format('woff2'); + url(500.woff2) format('woff2'), + url(500.ttf) format('truetype'); } @font-face { @@ -129,22 +96,16 @@ font-weight: 500; font-style: italic; src: - url(500i.eot) format('embedded-opentype'), - url(500i.svg) format('svg'), - url(500i.ttf) format('truetype'), - url(500i.woff) format('woff'), - url(500i.woff2) format('woff2'); + url(500i.woff2) format('woff2'), + url(500i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 600; src: - url(600.eot) format('embedded-opentype'), - url(600.svg) format('svg'), - url(600.ttf) format('truetype'), - url(600.woff) format('woff'), - url(600.woff2) format('woff2'); + url(600.woff2) format('woff2'), + url(600.ttf) format('truetype'); } @font-face { @@ -152,22 +113,16 @@ font-weight: 600; font-style: italic; src: - url(600i.eot) format('embedded-opentype'), - url(600i.svg) format('svg'), - url(600i.ttf) format('truetype'), - url(600i.woff) format('woff'), - url(600i.woff2) format('woff2'); + url(600i.woff2) format('woff2'), + url(600i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 700; src: - url(700.eot) format('embedded-opentype'), - url(700.svg) format('svg'), - url(700.ttf) format('truetype'), - url(700.woff) format('woff'), - url(700.woff2) format('woff2'); + url(700.woff2) format('woff2'), + url(700.ttf) format('truetype'); } @font-face { @@ -175,22 +130,16 @@ font-weight: 700; font-style: italic; src: - url(700i.eot) format('embedded-opentype'), - url(700i.svg) format('svg'), - url(700i.ttf) format('truetype'), - url(700i.woff) format('woff'), - url(700i.woff2) format('woff2'); + url(700i.woff2) format('woff2'), + url(700i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 800; src: - url(800.eot) format('embedded-opentype'), - url(800.svg) format('svg'), - url(800.ttf) format('truetype'), - url(800.woff) format('woff'), - url(800.woff2) format('woff2'); + url(800.woff2) format('woff2'), + url(800.ttf) format('truetype'); } @font-face { @@ -198,22 +147,16 @@ font-weight: 800; font-style: italic; src: - url(800i.eot) format('embedded-opentype'), - url(800i.svg) format('svg'), - url(800i.ttf) format('truetype'), - url(800i.woff) format('woff'), - url(800i.woff2) format('woff2'); + url(800i.woff2) format('woff2'), + url(800i.ttf) format('truetype'); } @font-face { font-family: Muller; font-weight: 900; src: - url(900.eot) format('embedded-opentype'), - url(900.svg) format('svg'), - url(900.ttf) format('truetype'), - url(900.woff) format('woff'), - url(900.woff2) format('woff2'); + url(900.woff2) format('woff2'), + url(900.ttf) format('truetype'); } @font-face { @@ -221,9 +164,6 @@ font-weight: 900; font-style: italic; src: - url(900i.eot) format('embedded-opentype'), - url(900i.svg) format('svg'), - url(900i.ttf) format('truetype'), - url(900i.woff) format('woff'), - url(900i.woff2) format('woff2'); + url(900i.woff2) format('woff2'), + url(900i.ttf) format('truetype'); } From 8dbb45953ea3421aa25db5a08cf88134c30716a6 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sat, 15 Feb 2025 23:54:58 +0100 Subject: [PATCH 05/23] fix faealchemist button --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 68ab674..d4b0503 100644 --- a/index.html +++ b/index.html @@ -343,9 +343,9 @@
  • - - faealchemist + faealchemist From 0d1a749c3db12c4f566cf12a966d39d4379d0057 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sat, 15 Feb 2025 23:55:11 +0100 Subject: [PATCH 06/23] add clocktower button --- index.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/index.html b/index.html index d4b0503..cbddfeb 100644 --- a/index.html +++ b/index.html @@ -441,6 +441,13 @@ deneb + +
  • + + the clocktower headspace + From 1ef8c3ef91aa2be0b6da6fd772de6c3015e370c0 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 00:08:42 +0100 Subject: [PATCH 07/23] minor tweaks to quox color stuff --- rainbow-quox/script/color/conv.ts | 27 ++++++++++++++------------- rainbow-quox/script/color/def.ts | 20 ++++++++++---------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/rainbow-quox/script/color/conv.ts b/rainbow-quox/script/color/conv.ts index 312f945..3d0ff2c 100644 --- a/rainbow-quox/script/color/conv.ts +++ b/rainbow-quox/script/color/conv.ts @@ -3,9 +3,7 @@ export type Oklab = { type: 'oklab', l: number, a: number, b: number }; export type Srgb = { type: 'srgb', r: number, g: number, b: number }; export type Lrgb = { type: 'lrgb', r: number, g: number, b: number }; -export type ColorData = Oklch | Oklab | Srgb; - -export type ColorType = 'oklch' | 'oklab' | 'srgb'; +export type AnyColor = Oklch | Oklab | Srgb; type Deg = number; @@ -17,6 +15,11 @@ function dcos(θ: Deg): number { return Math.cos(deg2rad(θ)); } function dsin(θ: Deg): number { return Math.sin(deg2rad(θ)); } function datan2(b: number, a: number): Deg { return rad2deg(Math.atan2(b, a)); } +export function normDeg(θ: Deg): Deg { + θ %= 360; + return θ < 0 ? θ + 360 : θ; +} + export function oklch2oklab({ l, c, h }: Oklch): Oklab { return { type: 'oklab', l, a: c * dcos(h), b: c * dsin(h) }; } @@ -35,8 +38,7 @@ export function lrgb2oklab({ r, g, b }: Lrgb): Oklab { const m_ = Math.cbrt(m); const s_ = Math.cbrt(s); - return { - type: 'oklab', + return { type: 'oklab', l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_, @@ -48,12 +50,11 @@ export function oklab2lrgb({ l, a, b }: Oklab): Lrgb { const M_ = l - 0.1055613458 * a - 0.0638541728 * b; const S_ = l - 0.0894841775 * a - 1.2914855480 * b; - const L = L_*L_*L_; - const M = M_*M_*M_; - const S = S_*S_*S_; + const L = L_ * L_ * L_; + const M = M_ * M_ * M_; + const S = S_ * S_ * S_; - return { - type: 'lrgb', + return { type: 'lrgb', r: clamp(+4.0767416621 * L - 3.3077115913 * M + 0.2309699292 * S), g: clamp(-1.2684380046 * L + 2.6097574011 * M - 0.3413193965 * S), b: clamp(-0.0041960863 * L - 0.7034186147 * M + 1.7076147010 * S), @@ -101,7 +102,7 @@ export function srgb2oklch(c: Srgb): Oklch { } -export function toOklch(c: ColorData): Oklch { +export function toOklch(c: AnyColor): Oklch { switch (c.type) { case 'oklch': return c; case 'oklab': return oklab2oklch(c); @@ -109,7 +110,7 @@ export function toOklch(c: ColorData): Oklch { } } -export function toOklab(c: ColorData): Oklab { +export function toOklab(c: AnyColor): Oklab { switch (c.type) { case 'oklch': return oklch2oklab(c); case 'oklab': return c; @@ -117,7 +118,7 @@ export function toOklab(c: ColorData): Oklab { } } -export function toSrgb(c: ColorData): Srgb { +export function toSrgb(c: AnyColor): Srgb { switch (c.type) { case 'oklch': return oklch2srgb(c); case 'oklab': return oklab2srgb(c); diff --git a/rainbow-quox/script/color/def.ts b/rainbow-quox/script/color/def.ts index cd78966..a619727 100644 --- a/rainbow-quox/script/color/def.ts +++ b/rainbow-quox/script/color/def.ts @@ -1,6 +1,6 @@ -import { Oklch, Oklab, Srgb, ColorType, ColorData } from './conv.js'; +import { Oklch, Oklab, Srgb as Rgb, AnyColor, normDeg } from './conv.js'; import * as Conv from './conv.js'; -export { Oklch, Oklab, Srgb, ColorType, ColorData }; +export { Oklch, Oklab, Rgb, AnyColor }; export type CSSFormat = 'oklch' | 'oklab' | 'rgb' | 'hex'; @@ -20,9 +20,9 @@ export function apply(f: ChannelMapper | undefined, x: A): A { export class Color { readonly oklch: Oklch; readonly oklab: Oklab; - readonly srgb: Srgb; + readonly srgb: Rgb; - constructor(c: ColorData) { + constructor(c: AnyColor) { this.oklch = Conv.toOklch(c); this.oklab = Conv.toOklab(c); this.srgb = Conv.toSrgb(c); @@ -31,8 +31,10 @@ export class Color { get luma() { return this.oklch.l; } get chroma() { return this.oklch.c; } get hue() { return this.oklch.h; } + get labA() { return this.oklab.a; } get labB() { return this.oklab.b; } + get red() { return Math.floor(255 * this.srgb.r); } get green() { return Math.floor(255 * this.srgb.g); } get blue() { return Math.floor(255 * this.srgb.b); } @@ -70,14 +72,13 @@ export class Color { } function deg(θ: number) { - θ %= 360; if (θ < 0) θ += 360; - return `${θ.toFixed(0)}deg`; + return `${normDeg(θ).toFixed(0)}deg`; } } with(maps: ColorMapper): Color; - with(maps: ColorMapper): Color; - with(maps: ColorMapper): Color { + with(maps: ColorMapper): Color; + with(maps: ColorMapper): Color { switch (maps.type) { case 'oklch': { const { l, c, h } = this.oklch; @@ -86,7 +87,7 @@ export class Color { } case 'srgb': { const { r, g, b } = this.srgb; - const { r: rr, g: gg, b: bb } = maps as ColorMapper; + const { r: rr, g: gg, b: bb } = maps as ColorMapper; return rgb(apply(rr, r), apply(gg, g), apply(bb, b)); } } @@ -127,7 +128,6 @@ export function rgb(r: number, g: number, b: number, } - export type Luma = number; export type Chroma = number; export type Hue = number; From 994dd3eed37c5544050022335adf52b854db3c2d Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 00:09:08 +0100 Subject: [PATCH 08/23] cc button --- media/buttons/clocktower.png | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 media/buttons/clocktower.png diff --git a/media/buttons/clocktower.png b/media/buttons/clocktower.png new file mode 100644 index 0000000..fba7206 --- /dev/null +++ b/media/buttons/clocktower.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12aa8d16bfbd38a54c70a073ebfa715574bfefedca582bed340d34eed3180e6a +size 890 From 6a48d013b270c1766d5b8183472569747d1cfa6b Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 00:10:48 +0100 Subject: [PATCH 09/23] make yellow cube face more yellow --- style/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style/base.css b/style/base.css index 8711df0..94fc4ac 100644 --- a/style/base.css +++ b/style/base.css @@ -257,7 +257,7 @@ strong { font-weight: 700; } #activities, #activities ::selection { - --hue: 90deg; + --hue: 60deg; --bg-angle: -60deg; } From f6a4270d13fcefed4953affd1ab689e400fc21ac Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 00:13:16 +0100 Subject: [PATCH 10/23] remove the noscript message. who cares --- index.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/index.html b/index.html index cbddfeb..63a4e29 100644 --- a/index.html +++ b/index.html @@ -56,12 +56,6 @@

    i’m niss. what’s up

    -
    From 65db9cdd5af995b65aee61cab5e0235fae4d48cf Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 00:14:25 +0100 Subject: [PATCH 11/23] remove d&d stuff from site till i work on it more --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fcaa51e..7eaf9e4 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ PAGES = index.html pubkey.txt rainbow-quox/index.html \ - dnd/index.html $(wildcard dnd/*/index.html) + # dnd/index.html $(wildcard dnd/*/index.html) MEDIA = \ $(wildcard media/*.png) $(wildcard media/*.gif) $(wildcard media/*.webp) \ $(wildcard media/flags/*) $(wildcard media/buttons/*) \ $(wildcard media/icons/*) $(wildcard media/bg/*) 8831.png 8831-quox.png \ $(wildcard rainbow-quox/front/*) $(wildcard rainbow-quox/back/*) \ $(wildcard rainbow-quox/*.svg) rainbow-quox/palette.svg \ - $(wildcard dnd/*.png) $(wildcard dnd/*.webp) $(wildcard dnd/*/*.webp) + # $(wildcard dnd/*.png) $(wildcard dnd/*.webp) $(wildcard dnd/*/*.webp) CSS = $(shell find fonts -type f) \ $(patsubst %.scss,%.css, \ $(wildcard rainbow-quox/style/*) $(wildcard style/*)) \ - dnd/base.css dnd/bio.css dnd/index.css $(wildcard dnd/*/style.css) + # dnd/base.css dnd/bio.css dnd/index.css $(wildcard dnd/*/style.css) SCRIPTS = $(patsubst %.ts,%.js, \ $(wildcard script/*.ts rainbow-quox/script/color/*.ts \ rainbow-quox/script/*.ts)) From 2514562a868015c704c81120714b00e7e23d4530 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 00:25:15 +0100 Subject: [PATCH 12/23] simplify markup on id face --- index.html | 31 +++++++++++-------------------- style/base.css | 15 +++++++++------ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/index.html b/index.html index 63a4e29..a9356b9 100644 --- a/index.html +++ b/index.html @@ -62,31 +62,22 @@

    so what’s your deal

    -
    name -
    -
      -
    • niss or q.t. -
    • always lowercase -
    +
    name +
    niss or q.t. +
    always lowercase -
    pronouns -
    -
      -
    • she, they, or it in english. pick whichever -
    • sie auf deutsch -
    +
    pronouns +
    she, they, or it in english. pick whichever +
    sie auf deutsch -
    shapes -
    - +
    some others that come and go
    diff --git a/style/base.css b/style/base.css index 94fc4ac..2c8401d 100644 --- a/style/base.css +++ b/style/base.css @@ -230,15 +230,18 @@ strong { font-weight: 700; } #id dl { display: grid; grid-template-columns: min-content auto; - gap: 1em 2em; + gap: 0 2em; } #id dt { font-weight: 600; } -#id dd { margin: 0; } -#id dd a { font-weight: 700; } - -#id dl ul { - padding-left: 1em; +#id dd { + margin: 0 0 0 1em; + display: list-item; list-style: '❧ '; + grid-column: 2 / 3; +} +#id dd a { font-weight: 700; } +#id dt, #id dt + dd { + margin-top: 1em; } #flags { From 186b02a1326594b2f0fbe0a0aba5f35a1dbca629 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 16:23:29 +0100 Subject: [PATCH 13/23] fix cube in webkit --- script/run.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/script/run.ts b/script/run.ts index b8c0d0b..80341db 100644 --- a/script/run.ts +++ b/script/run.ts @@ -24,6 +24,8 @@ function table(m: Record): (x: A) => B { return x => m[x]; } +const NO_TRANS = 'rotateY(0deg)'; + const doCwO = table({up: 'right', right: 'down', down: 'left', left: 'up'}); @@ -133,7 +135,7 @@ const movementToTransform = table({ /** the css `transform` value corresponding to this sequence of movements */ export function movementsToTransform(ms: Rotation[]): string { - return ms.length > 0 ? ms.map(movementToTransform).join(' ') : 'none'; + return ms.length > 0 ? ms.map(movementToTransform).join(' ') : NO_TRANS; } @@ -155,7 +157,7 @@ const orientationToTransform = table({ export function placeToTransform([f, o]: Place): string { const ft = faceToTransform(f); const ot = orientationToTransform(o); - return ft || ot ? `${ft} ${ot}` : 'none'; + return ft || ot ? `${ft} ${ot}` : NO_TRANS; } @@ -193,6 +195,8 @@ export function animateMoveWith(ds: RotateXY[], rs: RotateZ[]): void { const outer = document.getElementById('outer')!; const cube = document.getElementById('cube')!; + console.log('animateMoveWith'); + cube.dataset.moving = 'true'; cube.style.transition = '0.4s cubic-bezier(.4, -0.29, .43, 1.26)'; outer.style.transition = `0.4s 0.25s cubic-bezier(.48, 0, .44, 1.07)`; @@ -207,10 +211,14 @@ export function animateMoveWith(ds: RotateXY[], rs: RotateZ[]): void { let removeOuter = () => {}; let removeCube = () => {}; if (rs.length > 0) { + console.log('xy'); + removeOuter = transitionListener(outer); cube.style.transform = movementsToTransform(ds); outer.style.transform = movementsToTransform(rs); } else if (ds.length > 0) { + console.log('z'); + removeCube = transitionListener(cube); cube.style.transform = movementsToTransform(ds); } else { @@ -218,11 +226,13 @@ export function animateMoveWith(ds: RotateXY[], rs: RotateZ[]): void { } function finish() { + console.log('finish'); + removeOuter(); removeCube(); delete cube.dataset.moving; outer.style.transition = cube.style.transition = 'none'; - outer.style.transform = cube.style.transform = 'none'; + outer.style.transform = cube.style.transform = NO_TRANS; current = move(current, ...ds, ...rs); applyConfiguration(); @@ -237,7 +247,7 @@ export function animateMoveTo(pane: Pane): void { export function squashCube() { for (const pane of allPanes) { const elem = document.getElementById(pane)! - elem.style.setProperty('--base-transform', 'none'); + elem.style.setProperty('--base-transform', NO_TRANS); } } @@ -289,7 +299,6 @@ export function fadeTo(newPane: Pane): void { } - const reducedMotion = matchMedia(`(prefers-reduced-motion: reduce), (max-height: 649px), (max-width: 649px)`); From 732e5a3d4737baecdf496b58bfc51a4ddc670a98 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 16:23:46 +0100 Subject: [PATCH 14/23] fix breathing animation --- style/cube.css | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/style/cube.css b/style/cube.css index daef211..47109a8 100644 --- a/style/cube.css +++ b/style/cube.css @@ -107,7 +107,7 @@ html { --side: min(65vh, 70vw); --half: calc(var(--side) * .5); --nhalf: calc(0em - var(--half)); - --breathe: calc(var(--side) * .1); + --breathe: calc(var(--side) * 0.02); } #outer { @@ -135,14 +135,12 @@ html { overscroll-behavior: contain; } -@supports (scrollbar-color: pink orange) { /* safari detector */ - #cube:not([data-moving]) > section { - animation: breathe 9s infinite ease-in-out; - } +#cube:not([data-moving]) > section:not([data-state=active]) { + animation: breathe 7.5s infinite ease-in-out; +} - @keyframes breathe { - 35% { transform: translateZ(var(--breathe)) var(--base-transform); } - } +@keyframes breathe { + 40% { transform: var(--base-transform) translateZ(var(--breathe)); } } @media (prefers-reduced-motion: no-preference) { From 3ff1fb1aaedb561ed32e6a6316e395deb3371c29 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 17:08:50 +0100 Subject: [PATCH 15/23] refactor cube styles using @layer --- style/base.css | 979 +++++++++++++++++++++---------------------- style/cube.css | 286 ++++++------- style/flat.css | 190 +++++---- style/properties.css | 44 ++ 4 files changed, 764 insertions(+), 735 deletions(-) create mode 100644 style/properties.css diff --git a/style/base.css b/style/base.css index 2c8401d..e52084e 100644 --- a/style/base.css +++ b/style/base.css @@ -1,546 +1,545 @@ -@import url(../fonts/muller/muller.css); +@layer outer, face-menu, cube, face-base, faces; + +@import url(../fonts/muller/muller.css) layer(outer); +@import url(properties.css); + +/* +@import url(../fonts/muller/muller.css) layer(outer) + (not (prefers-reduced-data: reduce)); + +if a browser doesn't understand the media query it DOESN'T do +the import. so this doesn't work until prefers-reduced-data +actually exists +*/ -/* OUTER */ +@layer outer { + * { box-sizing: border-box; } -* { box-sizing: border-box; } - -html, body { - min-height: 100vh; - min-height: 100lvh; - scrollbar-gutter: stable; -} - -html { - font-size: large; - font-family: Muller, sans-serif; - font-weight: 500; - - color: black; - - --gradient: - linear-gradient(120deg in oklch, - oklch(93% 27.1% 96deg), - oklch(84.03% 22% 15deg), - oklch(80% 29.3% 303deg), - oklch(84% 23% 233deg), - oklch(89% 25% 161deg)); - --shadow-hsl: 330deg 40% 40%; - --base-background: var(--gradient) fixed; - background: var(--base-background); - - --menu-bg-hsl: 60deg 100% 96%; -} - -@media (prefers-reduced-data: reduce) { - html { - font-family: sans-serif; - font-weight: normal; + :root, body { + min-height: 100vh; + min-height: 100lvh; + margin: 0; + scrollbar-gutter: stable; } -} -html, body { margin: 0; } + :root { + font-size: large; -@media (prefers-color-scheme: dark) { - html { + color-scheme: light dark; + color: black; --gradient: - linear-gradient(20deg, - hsl(300deg 30% 20%), - hsl(220deg 30% 20%), - hsl(150deg 30% 20%), - hsl(30deg 30% 20%), - hsl(350deg 30% 20%)); - --menu-bg-hsl: 260deg 100% 8%; - color: #ffd; + linear-gradient(120deg in oklch, + oklch(93% 27.1% 96deg), + oklch(84.03% 22% 15deg), + oklch(80% 29.3% 303deg), + oklch(84% 23% 233deg), + oklch(89% 25% 161deg)); + --menu-bg-hsl: 60deg 100% 96%; + + @media (prefers-color-scheme: dark) { + color: #ffd; + --gradient: + linear-gradient(20deg, + hsl(300deg 30% 20%), + hsl(220deg 30% 20%), + hsl(150deg 30% 20%), + hsl(30deg 30% 20%), + hsl(350deg 30% 20%)); + --menu-bg-hsl: 260deg 100% 8%; + } + + --shadow-hsl: 330deg 40% 40%; + --base-background: var(--gradient) fixed; + background: var(--base-background); + + font-family: Muller, sans-serif; + font-weight: 500; + @media (prefers-reduced-data: reduce) { + font-family: sans-serif; + font-weight: normal; + } } } -/* TOP MENU */ +@layer face-menu { + #face-menu { + align-self: center; + margin: 10px; -#face-menu { - align-self: center; - margin: 10px; -} + font-size: 125%; -.menu input, .menu label { - cursor: pointer; -} + menu { + display: flex; + place-content: center; + place-items: center; + margin: auto; + padding: 0; -.menu { - display: flex; - place-content: center; - place-items: center; - margin: auto; - padding: 0; + flex-wrap: wrap; + justify-content: center; - flex-wrap: wrap; - justify-content: center; + background: hsl(var(--menu-bg-hsl) / 65%); + border: 2px solid hsl(var(--menu-bg-hsl)); + box-shadow: 0 0 10px 5px hsl(var(--menu-bg-hsl) / 30%); - font-size: 125%; + @media (prefers-reduced-transparency: reduce) { + background: hsl(var(--menu-bg-hsl)); + } + } - background: hsl(var(--menu-bg-hsl) / 65%); - border: 2px solid hsl(var(--menu-bg-hsl)); - box-shadow: 0 0 10px 5px hsl(var(--menu-bg-hsl) / 30%); -} + input, label { cursor: pointer; } -@media (prefers-reduced-transparency: reduce) { - background: hsl(var(--menu-bg-hsl)); -} + input { + appearance: none; + width: 0; + margin: 0; + padding: 0; + } -.hide-boxes input { - appearance: none; - width: 0; - margin: 0; - padding: 0; -} -.menu li { - display: flex; - list-style: none; - flex: 1 0 4em; -} -.menu label { - padding: .25em 1.25em; - flex: 1 0 auto; - text-align: center; + label { + padding: .25em 1.25em; + flex: 1 0 auto; + text-align: center; + } + + li { + display: flex; + list-style: none; + flex: 1 0 4em; + } + } } -/* BASE FACE STYLES */ - -#cube > section { - --base-background: - repeating-linear-gradient(var(--bg-angle), - transparent, transparent 0.8em, - #fff8 0.8em, #fff8 1em), - linear-gradient(150deg, - hsl(var(--hue) 86% 94%), - hsl(var(--hue) 66% 82%) - ); - background: var(--base-background); - - --border-thickness: 10px; - border: var(--border-thickness) solid white; - padding: 2em; - - color: hsl(var(--hue) 40% 10%); - --text-shadow-color: white; - text-shadow: - 1px 1px 1px var(--text-shadow-color), - -1px 1px 1px var(--text-shadow-color), - -1px -1px 1px var(--text-shadow-color), - 1px -1px 1px var(--text-shadow-color); - - scrollbar-color: - hsl(calc(var(--hue) + 180deg) 90% 60%) - hsl(var(--hue) 50% 95%); -} - -#cube a { - color: hsl(calc(var(--hue) + 180deg) 90% 20%); - font-weight: 600; - text-decoration: none; -} -#cube a:hover { - text-decoration: underline; - text-decoration-style: dotted; - text-decoration-color: currentcolor; - text-decoration-thickness: 2px; -} - -h2 { margin-top: 0; } - -h3 { - text-align: center; - margin-bottom: 0.25em; -} - -h2 + section h3 { margin-top: 0; } - -strong { font-weight: 700; } - -#cube section ::selection { - color: white; - background: hsl(calc(var(--hue) + 180deg), 50%, 30%); - text-shadow: none; -} - -@media (prefers-color-scheme: dark) { +@layer face-base { #cube > section { --base-background: repeating-linear-gradient(var(--bg-angle), transparent, transparent 0.8em, - #0014 0.8em, #0014 1em), + #fff8 0.8em, #fff8 1em), linear-gradient(150deg, - hsl(var(--hue) 40% 30%), - hsl(var(--hue) 40% 15%) + hsl(var(--hue) 86% 94%), + hsl(var(--hue) 66% 82%) ); + background: var(--base-background); - border-color: hsl(var(--hue) 40% 10%); + --border-thickness: 10px; + border: var(--border-thickness) solid white; + padding: 2em; - color: hsl(var(--hue) 40% 90%); - --text-shadow-color: black; + color: hsl(var(--hue) 40% 10%); + --text-shadow-color: white; + text-shadow: + 1px 1px 1px var(--text-shadow-color), + -1px 1px 1px var(--text-shadow-color), + -1px -1px 1px var(--text-shadow-color), + 1px -1px 1px var(--text-shadow-color); + + @media (prefers-color-scheme: dark) { + --base-background: + repeating-linear-gradient(var(--bg-angle), + transparent, transparent 0.8em, + #0014 0.8em, #0014 1em), + linear-gradient(150deg, + hsl(var(--hue) 40% 30%), + hsl(var(--hue) 40% 15%) + ); + + border-color: hsl(var(--hue) 40% 10%); + + color: hsl(var(--hue) 40% 90%); + --text-shadow-color: black; + } + + scrollbar-color: + hsl(calc(var(--hue) + 180deg) 90% 60%) + hsl(var(--hue) 50% 95%); + + ::selection { + color: white; + background: hsl(calc(var(--hue) + 180deg), 50%, 30%); + text-shadow: none; + } } #cube a { - color: hsl(calc(var(--hue) + 180deg) 100% 85%); + color: hsl(calc(var(--hue) + 180deg) 90% 20%); + font-weight: 600; + text-decoration: none; + + &:hover { + text-decoration: underline; + text-decoration-style: dotted; + text-decoration-color: currentcolor; + text-decoration-thickness: 2px; + } + + @media (prefers-color-scheme: dark) { + color: hsl(calc(var(--hue) + 180deg) 100% 85%); + } + } + + h2 { margin-top: 0; } + + h3 { + text-align: center; + margin-bottom: 0.25em; + } + + h2 + section h3 { margin-top: 0; } + + strong { font-weight: 700; } + + .artcredit { + position: absolute; + bottom: 0; + left: 1em; + font-size: smaller; + font-style: italic; + background: hsl(var(--menu-bg-hsl) / 75%); + padding: .1em .5em; } } +@layer faces.hello { + #hello { + --hue: 310deg; + --bg-angle: 135deg; -/* SPECIFIC FACE STYLES */ + /* this one makes more sense to show if there is a paint before + * the script runs */ + z-index: 1; -/* the separate "#whatever ::selection" selector is because in - * chrome, ::selection doesn't inherit variables */ -#hello, #hello ::selection { - --hue: 310deg; - --bg-angle: 135deg; + /* the quick linx */ + h3 { text-align: left; } + + ul { padding-left: 0; } + li { list-style: none; } + + --bg-image: url(../media/wave.webp); + background: + var(--bg-image) bottom right / auto 60% no-repeat, + var(--base-background); + + @media (prefers-color-scheme: dark) { + --bg-image: url(../media/wave-neon.webp); + } + @media (prefers-reduced-data: reduce) { + --bg-image: url(../media/wave.l.webp); + } + @media (prefers-color-scheme: dark) and (prefers-reduced-data: reduce) { + --bg-image: url(../media/wave-neon.l.webp); + } + } } -/* this one makes more sense to show if there is a paint before - * the script runs */ -#hello { - z-index: 1; -} +@layer faces.id { + #id { + --hue: 10deg; + --bg-angle: 45deg; -/* extra #cube selector for specificity */ -#cube #hello { - --bg-image: url(../media/wave.webp); - background: - var(--bg-image) bottom right / auto 60% no-repeat, - var(--base-background); -} - -@media (prefers-color-scheme: dark) { - #cube #hello { --bg-image: url(../media/wave-neon.webp); } -} -@media (prefers-reduced-data: reduce) { - #cube #hello { --bg-image: url(../media/wave.l.webp); } -} -@media (prefers-color-scheme: dark) and (prefers-reduced-data: reduce) { - #cube #hello { --bg-image: url(../media/wave-neon.l.webp); } -} - -#id, #id ::selection, #flags img { - --hue: 10deg; - --bg-angle: 45deg; -} - -#id dl { - display: grid; - grid-template-columns: min-content auto; - gap: 0 2em; -} -#id dt { font-weight: 600; } -#id dd { - margin: 0 0 0 1em; - display: list-item; - list-style: '❧ '; - grid-column: 2 / 3; -} -#id dd a { font-weight: 700; } -#id dt, #id dt + dd { - margin-top: 1em; -} - -#flags { - display: flex; - flex-flow: wrap; - justify-content: center; - gap: 1em; - margin-top: 3em; -} - -#flags img { - height: 2em; - border: 2px solid hsl(var(--hue) 30% 5% / 60%); - rotate: 8deg; -} - - -#activities, #activities ::selection { - --hue: 60deg; - --bg-angle: -60deg; -} - -#cube #activities { - /* height of quobl.webp is 58% of width */ - --bg-image: url(../media/quobl.webp); - background: - var(--bg-image) bottom left 2ex / 50% auto no-repeat local, - var(--base-background); - padding-bottom: 29%; -} - -@media (prefers-color-scheme: dark) { - #cube #activities { --bg-image: url(../media/quobl-neon.webp); } -} -@media (prefers-reduced-data: reduce) { - #cube #activities { --bg-image: url(../media/quobl.l.webp); } -} -@media (prefers-color-scheme: dark) and (prefers-reduced-data: reduce) { - #cube #activities { --bg-image: url(../media/quobl-neon.webp); } -} - -#links, #links ::selection { - --hue: 130deg; - --bg-angle: 210deg; -} - -#links ul { - padding: 0; - width: 100%; - display: flex; - flex-wrap: wrap; - gap: 4px; -} -#links li { - flex: 1 0 10em; - list-style: none; - --icon-bg: var(--fg); -} - -#links a { - height: 3em; - padding-left: calc(3em + 1ex); - display: flex; - align-items: center; - - border: 2px solid black; - background: - linear-gradient(to right, transparent 3em, var(--bg) 3em), - var(--icon) calc(1.5em - 16px) center / 32px auto no-repeat, - var(--icon-bg); - - color: var(--fg); - text-shadow: none; -} - -#links a:hover { text-decoration: none; } - -#links #gallery { - --icon: url(../media/favicon.webp); - --fg: hsl(280deg 38% 43%); - --bg: hsl(100deg 99% 81%); - image-rendering: pixelated; -} - -#links #code { - --icon: url(../media/icons/forgejo.svg); - --fg: white; - --icon-bg: #171e26; - --bg: #c2410c; -} - -#links #blog { - --icon: url(../media/icons/blog.webp); - --fg: #ffeebb; - --bg: #332255; - /* image-rendering: pixelated; */ -} -#links #blog a { - background-position: center, left calc(1.5em - 51px) center, center; - background-size: contain; -} - -#links #itaku { - --icon: url(../media/icons/itaku.svg); - --fg: #ffeb3b; - --bg: #303030; -} - -#links #weasyl { - --icon: url(../media/icons/weasyl.svg); - --fg: white; - --bg: #970000; -} - -#links #fa { - --icon: url(../media/icons/furaffinity.webp); - --icon-bg: #20242a; - --bg: #353b45; - --fg: white; -} - -#links #da { - --icon: url(../media/icons/deviantart.webp); - --icon-bg: #000608; - --bg: #314537; - --fg: #e7ede4; -} - -#links #kofi { - --icon: url(../media/icons/ko-fi.webp); - --icon-bg: #def3ff; - --bg: white; - --fg: #ff5966; -} - -#links #artfight { - --icon: url(../media/icons/artfight.webp); - --icon-bg: #222222; - --bg: #a65178; - --fg: white; -} - -#links #chitter { - --icon: url(../media/icons/chitter.webp); - --bg: #582c58; - --icon-bg: #2c162c; - --fg: white; -} - -#links #cohost { - --icon: url(../media/icons/cohost.svg); - --bg: #ffab5c; - --icon-bg: #83254f; - --fg: black; -} - -#links #bluesky { - --icon: url(../media/icons/bluesky.svg); - --bg: #161e27; - --fg: white; -} - -@media (prefers-color-scheme: dark) { - /* most link colours are fine in both modes. except: */ - - #links #gallery { - --icon-bg: hsl(280deg 42% 30%); - --bg: hsl(280deg 38% 43%); - --fg: hsl(100deg 99% 81%); + dl { + display: grid; + grid-template-columns: min-content auto; + gap: 0 2em; + } + dt { font-weight: 600; } + dd { + margin: 0 0 0 1em; + display: list-item; + list-style: '❧ '; + grid-column: 2 / 3; + a { font-weight: 700; } + } + dt, dt + dd { margin-top: 1em; } } - #links #weasyl { - --icon-bg: #252d32; + #flags { + display: flex; + flex-flow: wrap; + justify-content: center; + gap: 1em; + margin-top: 3em; + + img { + height: 2em; + border: 2px solid hsl(var(--hue) 30% 5% / 60%); + rotate: 8deg; + } + } +} + +@layer faces.activities { + #activities { + --hue: 60deg; + --bg-angle: -60deg; + + /* height of quobl.webp is 58% of width */ + --bg-image: url(../media/quobl.webp); + background: + var(--bg-image) bottom left 2ex / 50% auto no-repeat local, + var(--base-background); + padding-bottom: 29%; + + @media (prefers-color-scheme: dark) { + --bg-image: url(../media/quobl-neon.webp); + } + @media (prefers-reduced-data: reduce) { + --bg-image: url(../media/quobl.l.webp); + } + @media (prefers-color-scheme: dark) and (prefers-reduced-data: reduce) { + --bg-image: url(../media/quobl-neon.webp); + } + } +} + + +@layer faces.links { + #links { + --hue: 130deg; + --bg-angle: 210deg; + + ul { + padding: 0; + width: 100%; + display: flex; + flex-wrap: wrap; + gap: 4px; + } + li { + flex: 1 0 10em; + list-style: none; + --icon-bg: var(--fg); + } + + a { + height: 3em; + padding-left: calc(3em + 1ex); + display: flex; + align-items: center; + + border: 2px solid black; + background: + linear-gradient(to right, transparent 3em, var(--bg) 3em), + var(--icon) calc(1.5em - 16px) center / 32px auto no-repeat, + var(--icon-bg); + + color: var(--fg); + text-shadow: none; + &:hover { text-decoration: none; } + } } - #links #kofi { - --icon-bg: #2b3a44; - --bg: #192025; - --fg: #dce7eb; + #gallery { + --icon: url(../media/favicon.webp); + --fg: hsl(280deg 38% 43%); + --bg: hsl(100deg 99% 81%); + image-rendering: pixelated; } - #links #bluesky { - --bg: #161e27; - --icon-bg: #1e2936; + #code { + --icon: url(../media/icons/forgejo.svg); + --fg: white; + --icon-bg: #171e26; + --bg: #c2410c; + } + + #blog { + --icon: url(../media/icons/blog.webp); + --fg: #ffeebb; + --bg: #332255; + /* image-rendering: pixelated; */ + a { + background-position: center, left calc(1.5em - 51px) center, center; + background-size: contain; + } + } + + #itaku { + --icon: url(../media/icons/itaku.svg); + --fg: #ffeb3b; + --bg: #303030; + } + + #weasyl { + --icon: url(../media/icons/weasyl.svg); + --fg: white; + --bg: #970000; + } + + #fa { + --icon: url(../media/icons/furaffinity.webp); + --icon-bg: #20242a; + --bg: #353b45; + --fg: white; + } + + #da { + --icon: url(../media/icons/deviantart.webp); + --icon-bg: #000608; + --bg: #314537; + --fg: #e7ede4; + } + + #kofi { + --icon: url(../media/icons/ko-fi.webp); + --icon-bg: #def3ff; + --bg: white; + --fg: #ff5966; + } + + #artfight { + --icon: url(../media/icons/artfight.webp); + --icon-bg: #222222; + --bg: #a65178; --fg: white; } + + #chitter { + --icon: url(../media/icons/chitter.webp); + --bg: #582c58; + --icon-bg: #2c162c; + --fg: white; + } + + #bluesky { + --icon: url(../media/icons/bluesky.svg); + --bg: #161e27; + --fg: white; + } + + @media (prefers-color-scheme: dark) { + /* most link colours are fine in both modes. except: */ + #gallery { + --icon-bg: hsl(280deg 42% 30%); + --bg: hsl(280deg 38% 43%); + --fg: hsl(100deg 99% 81%); + } + + #weasyl { + --icon-bg: #252d32; + } + + #kofi { + --icon-bg: #2b3a44; + --bg: #192025; + --fg: #dce7eb; + } + + #bluesky { + --bg: #161e27; + --icon-bg: #1e2936; + --fg: white; + } + } } +@layer faces.friends { + #friends { + --hue: 190deg; + --bg-angle: 300deg; -#friends, #friends ::selection { - --hue: 190deg; - --bg-angle: 300deg; + display: grid; + grid: "hdr" min-content + "links1" auto + "links2" auto + "buttons" auto; + + img { image-rendering: pixelated; } + + > section { + align-self: start; + + ul { + list-style: none; + padding: 0; + + display: flex; + width: 80%; + margin: 0 auto; + + align-items: start; + gap: 0; + justify-content: center; + flex-wrap: wrap; + } + + img { + width: 88px; + height: 31px; + object-fit: none; + display: block; + } + } + + .txt { + display: inline-block; + width: 88px; + height: 31px; + border: 3px outset currentcolor; + text-align: center; + line-height: 28px; + + font-size: 18px; + font-weight: 600; + text-shadow: none; + } + + a:hover { text-decoration: none; } + } + + #khr a { background: #ffab71; color: #71153e; } + #ionchy a { background: #feca2f; color: #1b1505; } + #tenna a { background: #6095da; color: #243224; } + #river a { background: #98d8e7; color: #d67d28; } + #spiral a { background: #ef4d5a; color: #1f1f1f; } + #codl a { background: #87261f; color: #edb970; } + #violet a { background: #8c2bd8; color: #dddddd; } + #brin a { background: #1e1e1e; color: #ff4400; } + #konsti a { background: #060038; color: #ffcccc; } + #lena a { background: #e3ccf7; color: #000000; } + #serena a { background: #e787ad; color: #204; } + #deneb a { background: #540f00; color: #ee6bfa; } + + #nissbuttons { + margin: 2em auto 0; + align-self: end; + display: flex; + align-items: center; + justify-content: center; + gap: 1em; + + h3 { + margin: 0; + font-size: 1rem; + font-weight: 500; + text-align: right; + text-wrap: balance; + } + } } -#cube #friends { - display: grid; - grid: "hdr" min-content - "links1" auto - "links2" auto - "buttons" auto; -} - -#friends img { image-rendering: pixelated; } - -#friendlinks, #otherlinks { align-self: start; } - -#friendlinks ul, #otherlinks ul { - list-style: none; - padding: 0; - - display: flex; - width: 80%; - margin: 0 auto; - - align-items: start; - gap: 4px; - justify-content: center; - flex-wrap: wrap; -} - -#friendlinks img, #otherlinks img { - width: 88px; - height: 31px; - object-fit: none; -} - -#friends .txt { - display: inline-block; - width: 88px; - height: 31px; - border: 3px outset currentcolor; - text-align: center; - line-height: 28px; - - font-size: 18px; - font-weight: 600; - text-shadow: none; -} - -#friends a:hover { text-decoration: none; } - -#friends #khr a { background: #ffab71; color: #71153e; } -#friends #ionchy a { background: #feca2f; color: #1b1505; } -#friends #tenna a { background: #6095da; color: #243224; } -#friends #river a { background: #98d8e7; color: #d67d28; } -#friends #spiral a { background: #ef4d5a; color: #1f1f1f; } -#friends #codl a { background: #87261f; color: #edb970; } -#friends #violet a { background: #8c2bd8; color: #dddddd; } -#friends #brin a { background: #1e1e1e; color: #ff4400; } -#friends #konsti a { background: #060038; color: #ffcccc; } -#friends #lena a { background: #e3ccf7; color: #000000; } -#friends #serena a { background: #e787ad; color: #204; } -#friends #deneb a { background: #540f00; color: #ee6bfa; } - -#nissbuttons { - margin: 2em auto 0; - align-self: end; - display: flex; - align-items: center; - justify-content: center; - gap: 1em; -} - -#nissbuttons h3 { - font-size: 1rem; - font-weight: 500; - text-align: right; - text-wrap: balance; -} -#nissbuttons * { margin: 0; } - -#six, #six ::selection { - --hue: 250deg; - --bg-angle: 130deg; -} - -#cube #six { - --bg-image: url(../media/kesi.webp); - background: - var(--bg-image) bottom right / 100% auto no-repeat, - var(--base-background); -} - -.artcredit { - position: absolute; - bottom: 0; - left: 1em; - font-size: smaller; - font-style: italic; - background: hsl(var(--menu-bg-hsl) / 75%); - padding: .1em .5em; -} - -@media (prefers-color-scheme: dark) { - #cube #six { --bg-image: url(../media/kesi-neon.webp); } -} -@media (prefers-reduced-data: reduce) { - #cube #activities { --bg-image: url(../media/kesi.l.webp); } -} -@media (prefers-color-scheme: dark) and (prefers-reduced-data: reduce) { - #cube #activities { --bg-image: url(../media/kesi-neon.l.webp); } +@layer faces.six { + #six, #six ::selection { + --hue: 250deg; + --bg-angle: 130deg; + + --bg-image: url(../media/kesi.webp); + background: + var(--bg-image) bottom right / 100% auto no-repeat, + var(--base-background); + + @media (prefers-color-scheme: dark) { + --bg-image: url(../media/kesi-neon.webp); + } + @media (prefers-reduced-data: reduce) { + --bg-image: url(../media/kesi.l.webp); + } + @media (prefers-color-scheme: dark) and (prefers-reduced-data: reduce) { + --bg-image: url(../media/kesi-neon.l.webp); + } + } } diff --git a/style/cube.css b/style/cube.css index 47109a8..aa3a350 100644 --- a/style/cube.css +++ b/style/cube.css @@ -1,160 +1,148 @@ @media (prefers-reduced-motion: no-preference) and (min-height: 650px) and (min-width: 650px) { -/* BACKGROUND STUFF */ +@layer outer { + :root { + --bg-60309: url(../media/bg/60309.png); + --bg-kesi: url(../media/bg/kesi.png); + --bg-korai: url(../media/bg/korai.png); + --bg-mirai: url(../media/bg/mirai.png); + --bg-niss: url(../media/bg/niss.png); + --bg-niss2: url(../media/bg/niss2.png); + --bg-nisse: url(../media/bg/nisse.png); + --bg-prickly: url(../media/bg/prickly.png); + --bg-qt: url(../media/bg/qt.png); + --bg-qt2: url(../media/bg/qt2.png); -html { - --bg-60309: url(../media/bg/60309.png); - --bg-kesi: url(../media/bg/kesi.png); - --bg-korai: url(../media/bg/korai.png); - --bg-mirai: url(../media/bg/mirai.png); - --bg-niss: url(../media/bg/niss.png); - --bg-niss2: url(../media/bg/niss2.png); - --bg-nisse: url(../media/bg/nisse.png); - --bg-prickly: url(../media/bg/prickly.png); - --bg-qt: url(../media/bg/qt.png); - --bg-qt2: url(../media/bg/qt2.png); -} + @media (prefers-color-scheme: dark) { + --bg-60309: url(../media/bg/60309_neon.png); + --bg-kesi: url(../media/bg/kesi_neon.png); + --bg-korai: url(../media/bg/korai_neon.png); + --bg-mirai: url(../media/bg/mirai_neon.png); + --bg-niss: url(../media/bg/niss_neon.png); + --bg-niss2: url(../media/bg/niss2_neon.png); + --bg-nisse: url(../media/bg/nisse_neon.png); + --bg-prickly: url(../media/bg/prickly_neon.png); + --bg-qt: url(../media/bg/qt_neon.png); + --bg-qt2: url(../media/bg/qt2_neon.png); + } -@media (prefers-color-scheme: dark) { - html { - --bg-60309: url(../media/bg/60309_neon.png); - --bg-kesi: url(../media/bg/kesi_neon.png); - --bg-korai: url(../media/bg/korai_neon.png); - --bg-mirai: url(../media/bg/mirai_neon.png); - --bg-niss: url(../media/bg/niss_neon.png); - --bg-niss2: url(../media/bg/niss2_neon.png); - --bg-nisse: url(../media/bg/nisse_neon.png); - --bg-prickly: url(../media/bg/prickly_neon.png); - --bg-qt: url(../media/bg/qt_neon.png); - --bg-qt2: url(../media/bg/qt2_neon.png); + background-blend-mode: overlay; + + @media (prefers-color-scheme: dark) { + background-blend-mode: soft-light; + } + + background: + var(--bg-60309) bottom 29% right calc(44% - var(--half)) + / auto 17% no-repeat, + var(--bg-kesi) bottom 23% left calc(44% - var(--half)) + / auto 20% no-repeat, + var(--bg-korai) bottom left 7% / auto 19% no-repeat, + var(--bg-mirai) top calc(5em + 3%) right 6% / 20% auto no-repeat, + var(--bg-niss) top 30% left calc(31% - var(--half)) + / auto 20% no-repeat, + var(--bg-niss2) top 51% right calc(33% - var(--half)) + / auto 20% no-repeat, + var(--bg-nisse) top 29% right calc(36% - var(--half)) + / auto 15% no-repeat, + var(--bg-qt) bottom 41% left calc(24% - var(--half)) + / auto 18% no-repeat, + var(--bg-qt2) top 10% left calc(46% - var(--half)) + / auto 16% no-repeat, + var(--bg-prickly) bottom 9% right calc(28% - var(--half)) + / auto 15% no-repeat, + var(--base-background); + + @media ((prefers-reduced-data: reduce), + (prefers-reduced-transparency: reduce)) { + background: var(--base-background); + background-blend-mode: initial; + } + } + + body { + display: grid; + grid-template-rows: 5em 1fr; + + perspective: 440vw; + perspective-origin: 50% 120%; + } + + menu { + label { + position: relative; + &::after { + content: ' '; + z-index: -1; + position: absolute; + inset: 0 0 100% 0; + background: hsl(var(--menu-bg-hsl)); + + @media not (prefers-reduced-motion: reduce) { + transition: inset .15s linear; + } + } + } + + :checked + label::after { + bottom: 0; + } } } -html { - background: - /* - url(../media/bg/kesi_sprite_front.png) top 100px right no-repeat, - url(../media/bg/kesi_sprite_mid.png) top 150px center repeat-x, - url(../media/bg/kesi_sprite_back.png) top 200px left no-repeat, - */ - var(--bg-60309) bottom 29% right calc(44% - var(--half)) - / auto 17% no-repeat, - var(--bg-kesi) bottom 23% left calc(44% - var(--half)) - / auto 20% no-repeat, - var(--bg-korai) bottom left 7% / auto 19% no-repeat, - var(--bg-mirai) top calc(5em + 3%) right 6% / 20% auto no-repeat, - var(--bg-niss) top 30% left calc(31% - var(--half)) - / auto 20% no-repeat, - var(--bg-niss2) top 51% right calc(33% - var(--half)) - / auto 20% no-repeat, - var(--bg-nisse) top 29% right calc(36% - var(--half)) - / auto 15% no-repeat, - var(--bg-qt) bottom 41% left calc(24% - var(--half)) - / auto 18% no-repeat, - var(--bg-qt2) top 10% left calc(46% - var(--half)) - / auto 16% no-repeat, - var(--bg-prickly) bottom 9% right calc(28% - var(--half)) - / auto 15% no-repeat, - var(--base-background); - background-blend-mode: overlay; -} +@layer cube { + :root { + --side: min(65vh, 70vw); + --half: calc(var(--side) * .5); + --nhalf: calc(0em - var(--half)); + --breathe: calc(var(--side) * 0.02); + } -@media (prefers-color-scheme: dark) { - html { - background-blend-mode: soft-light; + #outer { + --transform-origin: 50% 50% calc(var(--nhalf) - min(6vh, 6vw)); + } + + #outer, #cube { + transform-style: preserve-3d; + transform-origin: var(--transform-origin); + width: calc(var(--side) + 10vw); + height: calc(var(--side) + 10vh); + margin: auto; + } + + #cube { + position: relative; + + > section { + position: absolute; + inset: 5vh 5vw; + overflow: auto; + transform-origin: var(--transform-origin); + transform: var(--base-transform); + overscroll-behavior: contain; + } + + &:not([data-moving]) > section:not([data-state=active]) { + animation: breathe 7.5s infinite ease-in-out; + } + } + + @keyframes breathe { + 40% { transform: var(--base-transform) translateZ(var(--breathe)); } + } + + @media not (prefers-reduced-motion: reduce) { + .zoom * { transition: all 0.25s ease-in; } + .zoom > :hover { + scale: 110%; + filter: drop-shadow(4px 4px 5px rgb(0 0 0 / 60%)); + &:nth-child(5n) { rotate: 4deg; } + &:nth-child(5n+1) { rotate: -2deg; } + &:nth-child(5n+2) { rotate: 1deg; } + &:nth-child(5n+3) { rotate: -3deg; } + &:nth-child(10n+4) { rotate: 4deg; } + &:nth-child(10n+9) { rotate: -1deg; } + } } } - -@media (prefers-reduced-data: reduce) { - html { - background: var(--base-background); - background-blend-mode: unset; - } -} - - -/* LAYOUT BASICS */ - -body { - display: grid; - grid-template-rows: 5em 1fr; - - perspective: 440vw; - perspective-origin: 50% 120%; -} - -/* MENU ANIMATION */ - -.menu label { position: relative; } -.menu label::after { - content: ' '; - z-index: -1; - position: absolute; - inset: 0 0 100% 0; - background: hsl(var(--menu-bg-hsl)); - transition: inset .15s linear; -} -.menu :checked + label::after { - bottom: 0; -} - - -/* CUBE ASSEMBLY */ -/* the rest is in cube.ts */ - -html { - --side: min(65vh, 70vw); - --half: calc(var(--side) * .5); - --nhalf: calc(0em - var(--half)); - --breathe: calc(var(--side) * 0.02); -} - -#outer { - --transform-origin: 50% 50% calc(var(--nhalf) - min(6vh, 6vw)); -} - -#outer, #cube { - transform-style: preserve-3d; - transform-origin: var(--transform-origin); - width: calc(var(--side) + 10vw); - height: calc(var(--side) + 10vh); - margin: auto; -} - -#cube { - position: relative; -} - -#cube > section { - position: absolute; - inset: 5vh 5vw; - overflow: auto; - transform-origin: var(--transform-origin); - transform: var(--base-transform); - overscroll-behavior: contain; -} - -#cube:not([data-moving]) > section:not([data-state=active]) { - animation: breathe 7.5s infinite ease-in-out; -} - -@keyframes breathe { - 40% { transform: var(--base-transform) translateZ(var(--breathe)); } -} - -@media (prefers-reduced-motion: no-preference) { - .zoom * { transition: all 0.25s ease-in; } - .zoom > :hover { - scale: 110%; - filter: drop-shadow(4px 4px 5px rgb(0 0 0 / 60%)); - } - .zoom > :hover:nth-child(5n) { rotate: 4deg; } - .zoom > :hover:nth-child(5n+1) { rotate: -2deg; } - .zoom > :hover:nth-child(5n+2) { rotate: 1deg; } - .zoom > :hover:nth-child(5n+3) { rotate: -3deg; } - .zoom > :hover:nth-child(10n+4) { rotate: 4deg; } - .zoom > :hover:nth-child(10n+9) { rotate: -1deg; } -} - -} diff --git a/style/flat.css b/style/flat.css index 5ddebd7..b359c65 100644 --- a/style/flat.css +++ b/style/flat.css @@ -1,122 +1,120 @@ @media (prefers-reduced-motion: reduce), (max-height: 649px), (max-width: 649px) { -html { +:root { --side: 0px; --half: 0px; --nhalf: 0px; } -/* LAYOUT */ +@layer outer { + body { + display: grid; + grid-template: "menu" 5em "body" 1fr; + } -body { - display: grid; - grid-template: "menu" 5em "body" 1fr; - /* height: 100vh; height: 100dvh; */ - /* width: 100vw; width: 100dvw; */ -} + #face-menu { grid-area: menu; } -#face-menu { grid-area: menu; } -#outer { grid-area: body; } + #outer { + grid-area: body; + position: relative; + height: 90%; + width: 90%; + margin: auto; -#outer { - position: relative; - height: 90%; - width: 90%; - margin: auto; -} - -#outer::after { - content: ' '; - position: absolute; - inset: 0; - box-shadow: 0 0 40px hsl(var(--shadow-hsl) / 40%); - mix-blend-mode: multiply; + &::after { + content: ' '; + position: absolute; + inset: 0; + box-shadow: 0 0 40px hsl(var(--shadow-hsl) / 40%); + mix-blend-mode: multiply; + } + } } /* not really a cube any more. but */ -#cube, #cube > section { - position: absolute; - inset: 0; -} -#cube > section { overflow: auto; } +@layer cube { + #cube, #cube > section { + position: absolute; + inset: 0; + } + #cube > section { overflow: auto; } -@media (prefers-color-scheme: dark) { - html { --shadow-hsl: 60deg 100% 96%; } + @media (prefers-color-scheme: dark) { + html { --shadow-hsl: 60deg 100% 96%; } + } } - /* BACKGROUND FIXES */ -@media (max-width: 649px) { - #cube #hello { - background-size: auto 80%, auto, auto; - background-position: bottom -20% right 60%, center, center; - } -} +@layer faces { + @layer hello { + #hello { + @media (max-width: 649px) { + background-size: auto 80%, auto, auto; + background-position: bottom -20% right 60%, center, center; + } -@media (max-height: 649px) { - #cube #hello { - background-size: auto 80%, auto, auto; - background-position: bottom right, center, center; + @media (max-height: 649px) { + background-size: auto 80%, auto, auto; + background-position: bottom right, center, center; + } + } + } + + @layer activities { + #activities { + @media (max-width: 649px) { + background-size: auto 30%, auto, auto; + background-position: bottom left 70%, center, center; + } + } + } + + @layer six { + #six { + background-position: bottom left 70%, center, center; + + @media (max-width: 649px) { + background-size: auto 100%, auto, auto; + background-position: bottom left 70%, center, center; + } + + @media (max-height: 649px) { + background-size: cover; + background-position: top 15% center; + } + } } } -@media (max-width: 649px) { - #cube #activities { - background-size: auto 30%, auto, auto; - background-position: bottom left 70%, center, center; +@layer cube { + #face-menu { + label { transition: background 0.1s ease-in; } + :checked + label { background: hsl(var(--menu-bg-hsl)); } + } + + #cube > section { + &[data-state=entering] { + z-index: 1; + opacity: 0; + } + + &[data-state=active] { + z-index: 1; + opacity: 1; + transition: opacity 0.1s ease-in; + } + + &[data-state=leaving] { + z-index: -1; + opacity: 0; + transition: opacity 0s 0.1s ease-in; + } + + &[data-state=hidden] { + display: none; + } } } - -#cube #six { - background-position: bottom left 70%, center, center; -} - -@media (max-width: 649px) { - #cube #six { - background-size: auto 100%, auto, auto; - background-position: bottom left 70%, center, center; - } -} - -@media (max-height: 649px) { - #cube #six { - background-size: cover; - background-position: top 15% center; - } -} - - -/* TRANSITIONS */ - -#face-menu :checked + label { - background: hsl(var(--menu-bg-hsl)); -} -#face-menu label { - transition: background 0.1s ease-in; -} - -#cube > :is(section[data-state=entering], #a) { - z-index: 1; - opacity: 0; -} - -#cube > :is(section[data-state=active], #a) { - z-index: 1; - opacity: 1; - transition: opacity 0.1s ease-in; -} - -#cube > :is(section[data-state=leaving], #a) { - z-index: -1; - opacity: 0; - transition: opacity 0s 0.1s ease-in; -} - -#cube > :is(section[data-state=hidden], #a) { - display: none; -} - -} diff --git a/style/properties.css b/style/properties.css new file mode 100644 index 0000000..0a3c261 --- /dev/null +++ b/style/properties.css @@ -0,0 +1,44 @@ +/* face backgrounds */ + +@property --hue { + syntax: ""; + inherits: true; + initial-value: 0deg; +} + +@property --bg-angle { + syntax: ""; + inherits: true; + initial-value: 45deg; +} + + +/* link buttons */ + +@property --icon { + syntax: ""; + inherits: true; + initial-value: url(../media/favicon.webp); +} + +@property --fg { + syntax: ""; + inherits: true; + initial-value: white; +} + +@property --bg { + syntax: ""; + inherits: true; + initial-value: black; +} + +@property --icon-bg { + syntax: ""; + inherits: true; + initial-value: white; +} +/* the initial value can't be `var(--bg)` because only "computationally + * independent" values are allowed + * https://drafts.css-houdini.org/css-properties-values-api/#initial-value-descriptor + */ From 355ea4400332cb6df5920f8eef02a1c3e23bebc1 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 17:09:07 +0100 Subject: [PATCH 16/23] quat button --- index.html | 5 +++++ media/buttons/hsa.gif | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 media/buttons/hsa.gif diff --git a/index.html b/index.html index a9356b9..55a6c38 100644 --- a/index.html +++ b/index.html @@ -433,6 +433,11 @@ the clocktower headspace + +
  • + + highly suspect agency +
  • diff --git a/media/buttons/hsa.gif b/media/buttons/hsa.gif new file mode 100644 index 0000000..a21a1a1 --- /dev/null +++ b/media/buttons/hsa.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:136278c9ddae266704f4ad879c48c48db8dfa2a9e6c43c3fb46b8abf6f574a92 +size 593 From da410a74f2bb59a35fcb907a92e7e126673e1f5b Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 17:09:35 +0100 Subject: [PATCH 17/23] let known quox colour schemes specify any colours --- rainbow-quox/script/color.ts | 140 +++++++++++++++++------------------ 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/rainbow-quox/script/color.ts b/rainbow-quox/script/color.ts index 4fdd815..65b9f54 100644 --- a/rainbow-quox/script/color.ts +++ b/rainbow-quox/script/color.ts @@ -37,20 +37,18 @@ export function makeColorInfo(f: (l: Layer) => A): Record { return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record; } -export type BaseCol = 'outer' | 'belly' | 'fins'; -export type OptionalBaseCol = 'eyes' | 'stripes'; - -type KnownPalette = - Record & Partial>; +type KnownPalette = Partial>; export function colors(r: Rand = new Rand(), base?: KnownPalette): Scheme { const outer = base?.outer ?? r.color('dark'); - const outerCols: OuterCols = - { outer, spines: mkSpines(r, outer), vitiligo1: mkVitiligo(r, outer) }; + const spines = base?.spines ?? mkSpines(r, outer); + const vitiligo1 = base?.vitiligo1 ?? mkVitiligo(r, outer); + const outerCols: OuterCols = { outer, spines, vitiligo1 }; - const stripes = mkStripes(r); - const sockCols: SockCols = { stripes, cuffs: mkCuffs(r, stripes) }; + 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(); @@ -105,7 +103,7 @@ function mkCuffs(r: Rand, sock: Color): Color { function mkFins(r: Rand, h: Hue, outer: Color, base?: KnownPalette): FinCols { - const baseFin1 = base?.fins; + const baseFin1 = base?.fins1; const [fin1Hue, fin2Hue, fin3Hue] = r.analogous(baseFin1?.hue ?? h, 3); const direction: 'lighter' | 'darker' = r.choice(['lighter', 'darker']); @@ -119,32 +117,35 @@ function mkFins(r: Rand, h: Hue, outer: Color, const fins1 = baseFin1 ?? oklch(ll(outer.luma), cc(outer.luma, outer.chroma), fin1Hue!); - const fins2 = oklch(ll(fins1.luma), cc(fins1.luma, fins1.chroma), fin2Hue!); - const fins3 = oklch(ll(fins2.luma), cc(fins2.luma, fins2.chroma), fin3Hue!); + 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 = mkVitiligo(r, lighter); + const vitiligo4 = base?.vitiligo4 ?? mkVitiligo(r, lighter); return { fins1, fins2, fins3, vitiligo4 }; } function mkBelly(r: Rand, h: Hue, base?: KnownPalette): BellyCols { - const baseBelly1 = base?.belly; + 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 = belly1.with({ type: 'oklch', + 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 = mkVitiligo(r, belly1); - const vitiligo2 = mkVitiligo(r, belly2); + 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 = oklch(r.float(0.8, r.maxl), r.float(0.01, 0.06), - r.analogous1(r.choice([o.outer, b.belly1, f.fins1]).hue)); + 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( @@ -152,13 +153,14 @@ function mkMisc(r: Rand, o: OuterCols, f: FinCols, b: BellyCols, r.float(0.28, r.maxcLight), r.boolean() ? r.analogous1(o.outer.hue) : r.complementary1(o.outer.hue) ), - 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: oklch(r.float(0.01, 0.06), r.baseChroma(0), - r.analogous1(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)), }; } @@ -177,69 +179,67 @@ function merge({ outer, spines, vitiligo1 }: OuterCols, export const KNOWN: Record = { niss: { - outer: oklch(0.83, 0.201, 151), - belly: oklch(0.87, 0.082, 99), - fins: oklch(0.68, 0.178, 16), - eyes: oklch(0.73, 0.135, 242), + 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), - belly: oklch(0.96, 0.04, 108), - fins: oklch(0.94, 0.142, 102), - eyes: oklch(0.76, 0.115, 300), + 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), - belly: oklch(0.56, 0.035, 233), - fins: oklch(0.55, 0.101, 268), - eyes: oklch(0.86, 0.146, 154), + 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), - belly: oklch(0.88, 0.03, 88), - fins: oklch(0.6, 0.071, 142), - eyes: oklch(0.66, 0.091, 134), + 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), - belly: oklch(0.83, 0.099, 354), - fins: oklch(0.74, 0.115, 354), - eyes: oklch(0.73, 0.149, 0), + 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), - belly: oklch(0.9, 0.05, 80), - fins: oklch(0.76, 0.16, 140), - eyes: oklch(0.82, 0.178, 141), + 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), - belly: oklch(0.71, 0.086, 253), - fins: oklch(0.58, 0.102, 254), - eyes: oklch(0.37, 0.107, 278), + 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), - belly: oklch(0.92, 0.04, 350), - fins: oklch(0.74, 0.138, 319), - eyes: oklch(0.65, 0.206, 4), + 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), - belly: oklch(0.82, 0.062, 70), - fins: oklch(0.52, 0.09, 45), - eyes: oklch(0.74, 0.167, 136), + 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), - belly: oklch(0.89, 0.01, 256), - fins: oklch(0.53, 0.093, 20), - // eyes: oklch(0.53, 0.109, 254), + 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), - belly: oklch(0.95, 0.021, 137), - fins: oklch(0.86, 0.069, 352), - // eyes: oklch(0.57, 0.158, 273), + outer: oklch(0.83, 0.065, 228), + belly1: oklch(0.95, 0.021, 137), + fins1: oklch(0.86, 0.069, 352), }, }; From 38f3ee0a64ba4d93618a707672f81a350b9ddb81 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 16 Feb 2025 22:35:54 +0100 Subject: [PATCH 18/23] quick linx on front face --- Makefile | 1 + index.html | 20 ++++++++++++++------ media/point_right.svg | 32 ++++++++++++++++++++++++++++++++ style/base.css | 16 +++++++++++----- 4 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 media/point_right.svg diff --git a/Makefile b/Makefile index 7eaf9e4..22ae81d 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ PAGES = index.html pubkey.txt rainbow-quox/index.html \ # dnd/index.html $(wildcard dnd/*/index.html) MEDIA = \ $(wildcard media/*.png) $(wildcard media/*.gif) $(wildcard media/*.webp) \ + $(wildcard media/*.svg) \ $(wildcard media/flags/*) $(wildcard media/buttons/*) \ $(wildcard media/icons/*) $(wildcard media/bg/*) 8831.png 8831-quox.png \ $(wildcard rainbow-quox/front/*) $(wildcard rainbow-quox/back/*) \ diff --git a/index.html b/index.html index 55a6c38..d97793f 100644 --- a/index.html +++ b/index.html @@ -56,12 +56,20 @@

    i’m niss. what’s up

    +

    quick linx

    + +

    so what’s your deal

    -
    +
    name
    niss or q.t.
    always lowercase @@ -116,7 +124,7 @@

    gec sites

    -