rainbow quox canvas stuff
This commit is contained in:
parent
d16ea49d62
commit
d0099fbf19
80 changed files with 497 additions and 415 deletions
2
Makefile
2
Makefile
|
@ -5,7 +5,7 @@ MEDIA = \
|
||||||
$(wildcard media/*.png) $(wildcard media/*.gif) $(wildcard media/*.webp) \
|
$(wildcard media/*.png) $(wildcard media/*.gif) $(wildcard media/*.webp) \
|
||||||
$(wildcard media/flags/*) $(wildcard media/buttons/*) \
|
$(wildcard media/flags/*) $(wildcard media/buttons/*) \
|
||||||
$(wildcard media/icons/*) $(wildcard media/bg/*) 8831.png 8831-quox.png \
|
$(wildcard media/icons/*) $(wildcard media/bg/*) 8831.png 8831-quox.png \
|
||||||
$(wildcard rainbow-quox/front/*.png) $(wildcard rainbow-quox/back/*.png)
|
$(wildcard rainbow-quox/front/*) $(wildcard rainbow-quox/back/*)
|
||||||
SCRIPTS = $(patsubst %.ts,%.js,$(wildcard script/*.ts rainbow-quox/*.ts))
|
SCRIPTS = $(patsubst %.ts,%.js,$(wildcard script/*.ts rainbow-quox/*.ts))
|
||||||
MISC = $(shell find .well-known -type f)
|
MISC = $(shell find .well-known -type f)
|
||||||
ALL = $(CSS) $(PAGES) $(MEDIA) $(SCRIPTS) $(MISC)
|
ALL = $(CSS) $(PAGES) $(MEDIA) $(SCRIPTS) $(MISC)
|
||||||
|
|
BIN
rainbow-quox/back/belly1.png
(Stored with Git LFS)
BIN
rainbow-quox/back/belly1.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/belly1.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/belly1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/belly2.png
(Stored with Git LFS)
BIN
rainbow-quox/back/belly2.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/belly2.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/belly2.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/claws.png
(Stored with Git LFS)
BIN
rainbow-quox/back/claws.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/claws.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/claws.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/cuffs.png
(Stored with Git LFS)
BIN
rainbow-quox/back/cuffs.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/cuffs.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/cuffs.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/eyes.png
(Stored with Git LFS)
BIN
rainbow-quox/back/eyes.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/eyes.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/eyes.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/fins1.png
(Stored with Git LFS)
BIN
rainbow-quox/back/fins1.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/fins1.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/fins1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/fins2.png
(Stored with Git LFS)
BIN
rainbow-quox/back/fins2.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/fins2.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/fins2.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/fins3.png
(Stored with Git LFS)
BIN
rainbow-quox/back/fins3.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/fins3.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/fins3.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/lines.png
(Stored with Git LFS)
BIN
rainbow-quox/back/lines.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/lines.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/lines.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/masks.png
(Stored with Git LFS)
BIN
rainbow-quox/back/masks.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/masks.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/masks.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/outer.png
(Stored with Git LFS)
BIN
rainbow-quox/back/outer.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/outer.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/outer.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
21
rainbow-quox/back/pos.json
Normal file
21
rainbow-quox/back/pos.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"belly1": [39, 67],
|
||||||
|
"belly2": [92, 95],
|
||||||
|
"claws": [191, 334],
|
||||||
|
"cuffs": [221, 215],
|
||||||
|
"eyes": [685, 42],
|
||||||
|
"eyeshine": [685, 42],
|
||||||
|
"fins1": [227, 60],
|
||||||
|
"fins2": [226, 61],
|
||||||
|
"fins3": [229, 195],
|
||||||
|
"lines": [0, 0],
|
||||||
|
"masks": [643, 1],
|
||||||
|
"outer": [2, 22],
|
||||||
|
"spines": [337, 50],
|
||||||
|
"static": [219, 41],
|
||||||
|
"stripes": [219, 221],
|
||||||
|
"vitiligo1": [4, 22],
|
||||||
|
"vitiligo2": [46, 48],
|
||||||
|
"vitiligo3": [101, 134],
|
||||||
|
"vitiligo4": [221, 56]
|
||||||
|
}
|
BIN
rainbow-quox/back/spines.png
(Stored with Git LFS)
BIN
rainbow-quox/back/spines.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/spines.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/spines.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/static.png
(Stored with Git LFS)
BIN
rainbow-quox/back/static.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/static.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/static.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/stripes.png
(Stored with Git LFS)
BIN
rainbow-quox/back/stripes.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/stripes.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/stripes.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/vitiligo1.png
(Stored with Git LFS)
BIN
rainbow-quox/back/vitiligo1.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/vitiligo1.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/vitiligo1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/vitiligo2.png
(Stored with Git LFS)
BIN
rainbow-quox/back/vitiligo2.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/vitiligo2.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/vitiligo2.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/vitiligo3.png
(Stored with Git LFS)
BIN
rainbow-quox/back/vitiligo3.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/vitiligo3.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/vitiligo3.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/back/vitiligo4.png
(Stored with Git LFS)
BIN
rainbow-quox/back/vitiligo4.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/back/vitiligo4.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/back/vitiligo4.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -12,83 +12,36 @@ export const allLayers: Layer[] =
|
||||||
'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', 'vitiligo3',
|
'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', 'vitiligo3',
|
||||||
'vitiligo4', 'eyes', 'eyeshine', 'lines'];
|
'vitiligo4', 'eyes', 'eyeshine', 'lines'];
|
||||||
|
|
||||||
export type Image = ImageData;
|
export function makeLayerInfo<A>(f: (l: Layer) => A): Record<Layer, A> {
|
||||||
export type Images = Record<Layer, Image>;
|
return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record<Layer, A>;
|
||||||
|
|
||||||
export type ComposedImages = Images & {comp?: ImageData};
|
|
||||||
|
|
||||||
export type Side = 'front' | 'back';
|
|
||||||
|
|
||||||
export function flip(s: Side): Side {
|
|
||||||
return s == 'front' ? 'back' : 'front';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Position = [x: number, y: number];
|
||||||
|
export type Positions = Record<Layer, Position>;
|
||||||
|
|
||||||
let buffer = new OffscreenCanvas(WIDTH, HEIGHT);
|
export async function loadPos(side: SideName): Promise<Positions> {
|
||||||
let bufferCtx = buffer.getContext('2d')!;
|
let req = new Request(`./${side}/pos.json`);
|
||||||
|
return fetch(req).then(resp => resp.json());
|
||||||
|
}
|
||||||
|
|
||||||
type Positions = Record<Layer, [number, number]>;
|
export type LayerInfo1 = {data: ImageData, pos: Position};
|
||||||
|
|
||||||
const FRONT_POS: Positions = {
|
export type LayerInfo = {
|
||||||
belly1: [187, 105],
|
layers: Record<Layer, LayerInfo1>,
|
||||||
belly2: [186, 91],
|
comp?: ImageData,
|
||||||
claws: [3, 168],
|
|
||||||
cuffs: [42, 160],
|
|
||||||
eyes: [223, 52],
|
|
||||||
eyeshine: [223, 52],
|
|
||||||
fins1: [381, 31],
|
|
||||||
fins2: [387, 35],
|
|
||||||
fins3: [495, 140],
|
|
||||||
lines: [1, 0],
|
|
||||||
masks: [173, 3],
|
|
||||||
outer: [28, 43],
|
|
||||||
spines: [372, 23],
|
|
||||||
static: [50, 52],
|
|
||||||
stripes: [50, 168],
|
|
||||||
vitiligo1: [34, 23],
|
|
||||||
vitiligo2: [198, 92],
|
|
||||||
vitiligo3: [214, 312],
|
|
||||||
vitiligo4: [647, 71],
|
|
||||||
};
|
|
||||||
|
|
||||||
const BACK_POS: Positions = {
|
|
||||||
belly1: [39, 67],
|
|
||||||
belly2: [92, 95],
|
|
||||||
claws: [191, 334],
|
|
||||||
cuffs: [221, 215],
|
|
||||||
eyes: [685, 42],
|
|
||||||
eyeshine: [685, 42],
|
|
||||||
fins1: [227, 60],
|
|
||||||
fins2: [226, 61],
|
|
||||||
fins3: [229, 195],
|
|
||||||
lines: [0, 0],
|
|
||||||
masks: [643, 1],
|
|
||||||
outer: [2, 22],
|
|
||||||
spines: [337, 50],
|
|
||||||
static: [219, 41],
|
|
||||||
stripes: [219, 221],
|
|
||||||
vitiligo1: [4, 22],
|
|
||||||
vitiligo2: [46, 48],
|
|
||||||
vitiligo3: [101, 134],
|
|
||||||
vitiligo4: [221, 56],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
type Rgb = [number, number, number];
|
type Rgb = [number, number, number];
|
||||||
type Rgbs = Record<Color.Layer, Rgb>;
|
type Rgbs = Record<Color.Layer, Rgb>;
|
||||||
|
|
||||||
|
let rgbBuf = new OffscreenCanvas(1, 1).getContext('2d')!;
|
||||||
|
|
||||||
function toRgb(col: Color.Oklch): Rgb {
|
function toRgb(col: Color.Oklch): Rgb {
|
||||||
// :)
|
// :)
|
||||||
const prev = bufferCtx.getImageData(0, 0, 1, 1);
|
rgbBuf.fillStyle = col.css();
|
||||||
|
rgbBuf.fillRect(0, 0, 1, 1);
|
||||||
bufferCtx.save();
|
const rgb = rgbBuf.getImageData(0, 0, 1, 1).data;
|
||||||
bufferCtx.fillStyle = Color.oklch(col);
|
|
||||||
bufferCtx.fillRect(0, 0, 1, 1);
|
|
||||||
bufferCtx.restore();
|
|
||||||
|
|
||||||
const rgb = bufferCtx.getImageData(0, 0, 1, 1).data;
|
|
||||||
bufferCtx.putImageData(prev, 0, 0);
|
|
||||||
|
|
||||||
return [rgb[0]!, rgb[1]!, rgb[2]!];
|
return [rgb[0]!, rgb[1]!, rgb[2]!];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,98 +50,92 @@ function toRgbs(col: Color.Colors): Rgbs {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setImageDataRgb([r, g, b]: Rgb, img: Image): void {
|
function setImageDataRgb({ data }: ImageData, [r, g, b]: Rgb, a: number = -1) {
|
||||||
let data = img.data;
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
data[i] = r; data[i+1] = g; data[i+2] = b;
|
data[i] = r; data[i+1] = g; data[i+2] = b;
|
||||||
|
if (a >= 0) data[i+3]! *= a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function wait(img: HTMLImageElement): Promise<HTMLImageElement> {
|
async function loadImage(url: string): Promise<ImageBitmap> {
|
||||||
if (img.complete) {
|
const img0 = new Image; img0.src = url;
|
||||||
return new Promise((r, _) => r(img));
|
const img: ImageBitmapSource = img0.complete ? img0 :
|
||||||
} else {
|
await new Promise(r => img0.addEventListener('load', () => r(img0)));
|
||||||
return new Promise((r, _) => img.addEventListener('load', () => r(img)));
|
return createImageBitmap(img);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(side: Side, layer: Layer): Promise<Image> {
|
async function load(buf: OffscreenCanvasRenderingContext2D,
|
||||||
|
side: SideName, layer: Layer): Promise<ImageData> {
|
||||||
if (layer == 'eyeshine') layer = 'eyes';
|
if (layer == 'eyeshine') layer = 'eyes';
|
||||||
let img = new Image; img.src = `./${side}/${layer}.png`;
|
let bmp = await loadImage(`./${side}/${layer}.webp`);
|
||||||
let bmp = await createImageBitmap(await wait(img));
|
return navigator.locks.request('buf', async () => {
|
||||||
bufferCtx.clearRect(0, 0, WIDTH, HEIGHT); // ?
|
buf.clearRect(0, 0, WIDTH, HEIGHT); // ?
|
||||||
bufferCtx.drawImage(bmp, 0, 0);
|
buf.drawImage(bmp, 0, 0);
|
||||||
return bufferCtx.getImageData(0, 0, WIDTH, HEIGHT);
|
return buf.getImageData(0, 0, WIDTH, HEIGHT)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSide(side: Side): Promise<ComposedImages> {
|
async function loadSide(buf: OffscreenCanvasRenderingContext2D,
|
||||||
const res: Partial<ComposedImages> = { };
|
side: SideName): Promise<LayerInfo> {
|
||||||
for (const l of allLayers) { res[l] = await load(side, l); }
|
const layers: Partial<Record<Layer, LayerInfo1>> = { };
|
||||||
return res as ComposedImages;
|
const pos = await loadPos(side);
|
||||||
|
const images = Object.fromEntries(
|
||||||
|
await Promise.all(
|
||||||
|
allLayers.map(l => load(buf, side, l).then(res => [l, res])))
|
||||||
|
) as Record<Layer, ImageData>;
|
||||||
|
console.log(images);
|
||||||
|
for (const l of allLayers) {
|
||||||
|
layers[l] = { data: images[l], pos: pos[l] };
|
||||||
|
}
|
||||||
|
return { layers: layers as Record<Layer, LayerInfo1> };
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColors(cols: Rgbs, layers: ComposedImages): void {
|
function setColors(cols: Rgbs, info: LayerInfo): void {
|
||||||
bufferCtx.save();
|
|
||||||
|
|
||||||
for (const l of allLayers) {
|
for (const l of allLayers) {
|
||||||
if (l == 'static' || l == 'eyeshine') continue;
|
if (l == 'static' || l == 'eyeshine') continue;
|
||||||
setImageDataRgb(cols[l], layers[l]);
|
setImageDataRgb(info.layers[l].data, cols[l]);
|
||||||
}
|
}
|
||||||
|
delete info.comp;
|
||||||
delete layers.comp;
|
|
||||||
|
|
||||||
bufferCtx.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compose(layers: Images, pos: Positions): Promise<ImageData> {
|
async function compose(buf: OffscreenCanvasRenderingContext2D,
|
||||||
bufferCtx.save();
|
{ layers }: LayerInfo): Promise<ImageData> {
|
||||||
bufferCtx.clearRect(0, 0, WIDTH, HEIGHT);
|
return navigator.locks.request('buf', async () => {
|
||||||
|
buf.save();
|
||||||
|
buf.clearRect(0, 0, WIDTH, HEIGHT);
|
||||||
|
|
||||||
for (const l of allLayers) {
|
for (const l of allLayers) {
|
||||||
const [x, y] = pos[l];
|
const [x, y] = layers[l].pos;
|
||||||
bufferCtx.globalCompositeOperation =
|
buf.globalCompositeOperation =
|
||||||
l == 'eyeshine' ? 'luminosity' : 'source-over';
|
l == 'eyeshine' ? 'luminosity' : 'source-over';
|
||||||
bufferCtx.drawImage(await createImageBitmap(layers[l]), x, y);
|
buf.drawImage(await createImageBitmap(layers[l].data), x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = bufferCtx.getImageData(0, 0, WIDTH, HEIGHT);
|
buf.restore();
|
||||||
bufferCtx.restore();
|
return buf.getImageData(0, 0, WIDTH, HEIGHT);
|
||||||
|
});
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function redraw(ctx: CanvasRenderingContext2D,
|
async function redraw(ctx: CanvasRenderingContext2D,
|
||||||
layers: ComposedImages, pos: Positions) {
|
buf: OffscreenCanvasRenderingContext2D,
|
||||||
let data = layers.comp ??= await compose(layers, pos);
|
info: LayerInfo) {
|
||||||
|
let data = info.comp ??= await compose(buf, info);
|
||||||
ctx.putImageData(data, 0, 0);
|
ctx.putImageData(data, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function loadingMessage(): Promise<ImageBitmap> {
|
function message(msg: string, ctx: CanvasRenderingContext2D) {
|
||||||
bufferCtx.save();
|
ctx.save();
|
||||||
bufferCtx.clearRect(0, 0, WIDTH, HEIGHT);
|
ctx.clearRect(0, 0, WIDTH, HEIGHT);
|
||||||
bufferCtx.font = 'bold 100px Muller, sans-serif';
|
ctx.font = 'bold 100px Muller, sans-serif';
|
||||||
bufferCtx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
bufferCtx.fillText('loading layers…', WIDTH/2, HEIGHT/2);
|
ctx.fillText(msg, WIDTH/2, HEIGHT/2);
|
||||||
let res = createImageBitmap(buffer);
|
ctx.restore();
|
||||||
bufferCtx.restore();
|
|
||||||
return await res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let pic: HTMLCanvasElement;
|
|
||||||
let picCtx: CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
let pic2: HTMLCanvasElement;
|
|
||||||
let pic2Ctx: CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
let fronts: Images;
|
|
||||||
let backs: Images;
|
|
||||||
|
|
||||||
let side: Side = 'front';
|
|
||||||
|
|
||||||
function startAnim(name: string) {
|
function startAnim(name: string) {
|
||||||
document.documentElement.dataset.running = name;
|
document.documentElement.dataset.running = name;
|
||||||
}
|
}
|
||||||
|
@ -201,17 +148,38 @@ function finishAnim() {
|
||||||
delete document.documentElement.dataset.running;
|
delete document.documentElement.dataset.running;
|
||||||
}
|
}
|
||||||
|
|
||||||
function layers(): Images { return side == 'front' ? fronts : backs; }
|
type SideName = 'front' | 'back';
|
||||||
function pos(): Positions { return side == 'front' ? FRONT_POS : BACK_POS; }
|
|
||||||
|
|
||||||
|
class Side {
|
||||||
|
cur: SideName = 'front';
|
||||||
|
fronts: LayerInfo;
|
||||||
|
backs: LayerInfo;
|
||||||
|
|
||||||
async function recolorOn(ctx: CanvasRenderingContext2D) {
|
private constructor(fronts: LayerInfo, backs: LayerInfo) {
|
||||||
const cols = Color.colors();
|
this.fronts = fronts; this.backs = backs;
|
||||||
const rgbs = toRgbs(cols);
|
}
|
||||||
setColors(rgbs, fronts);
|
|
||||||
setColors(rgbs, backs);
|
static async init(buf: OffscreenCanvasRenderingContext2D) {
|
||||||
await redraw(ctx, layers(), pos());
|
return new Side(await loadSide(buf, 'front'), await loadSide(buf, 'back'));
|
||||||
return cols.outer.h;
|
}
|
||||||
|
|
||||||
|
flip() { this.cur = (this.cur == 'front' ? 'back' : 'front'); }
|
||||||
|
layers() { return this.cur == 'front' ? this.fronts : this.backs; }
|
||||||
|
|
||||||
|
async recolorOn(ctx: CanvasRenderingContext2D,
|
||||||
|
buf: OffscreenCanvasRenderingContext2D) {
|
||||||
|
const cols = Color.colors();
|
||||||
|
const rgbs = toRgbs(cols);
|
||||||
|
setColors(rgbs, this.fronts);
|
||||||
|
setColors(rgbs, this.backs);
|
||||||
|
await redraw(ctx, buf, this.layers());
|
||||||
|
return cols.outer.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureComposed(buf: OffscreenCanvasRenderingContext2D) {
|
||||||
|
this.fronts.comp ??= await compose(buf, this.fronts);
|
||||||
|
this.backs.comp ??= await compose(buf, this.backs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBg(hue: number) {
|
function setBg(hue: number) {
|
||||||
|
@ -219,56 +187,77 @@ function setBg(hue: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function animateReroll(_e: Event, done: () => void) {
|
async function animateReroll(ctx1: CanvasRenderingContext2D,
|
||||||
const duration = 400;
|
ctx2: CanvasRenderingContext2D,
|
||||||
const hue = await recolorOn(pic2Ctx);
|
buf: OffscreenCanvasRenderingContext2D,
|
||||||
pic2.style.animation = `${duration}ms ease fade-in`;
|
side: Side,
|
||||||
|
done: () => void) {
|
||||||
|
const duration = 200;
|
||||||
|
const hue = await side.recolorOn(ctx2, buf);
|
||||||
|
ctx2.canvas.style.animation = `${duration}ms ease fade-in`;
|
||||||
setBg(hue);
|
setBg(hue);
|
||||||
setTimeout(finish, duration);
|
setTimeout(finish, duration);
|
||||||
|
|
||||||
async function finish() {
|
async function finish() {
|
||||||
await redraw(picCtx, layers(), pos()).then(() => {
|
await redraw(ctx1, buf, side.layers()).then(() => {
|
||||||
pic2.style.removeProperty('animation');
|
ctx2.canvas.style.removeProperty('animation');
|
||||||
pic2.style.removeProperty('opacity');
|
ctx2.canvas.style.removeProperty('opacity');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function animateSwap(_e: Event, done: () => void) {
|
async function animateSwap(picCtx: CanvasRenderingContext2D,
|
||||||
const duration = 1000;
|
pic2Ctx: CanvasRenderingContext2D,
|
||||||
pic.style.animation = `${duration}ms ease swap`;
|
side: Side,
|
||||||
setTimeout(swapImage, duration/2);
|
buf: OffscreenCanvasRenderingContext2D,
|
||||||
|
done: () => void) {
|
||||||
|
const duration = 400;
|
||||||
|
|
||||||
|
side.flip();
|
||||||
|
await Promise.all([
|
||||||
|
side.ensureComposed(buf),
|
||||||
|
redraw(pic2Ctx, buf, side.layers()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
picCtx.canvas.style.animation = `${duration}ms ease swap1`;
|
||||||
|
pic2Ctx.canvas.style.animation = `${duration}ms ease swap2`;
|
||||||
|
setTimeout(swap, duration/2);
|
||||||
setTimeout(finish, duration);
|
setTimeout(finish, duration);
|
||||||
|
|
||||||
async function swapImage() {
|
function swap() {
|
||||||
side = flip(side);
|
let data = pic2Ctx.getImageData(0, 0, WIDTH, HEIGHT);
|
||||||
await redraw(picCtx, layers(), pos());
|
picCtx.putImageData(data, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function finish() {
|
function finish() {
|
||||||
pic.style.removeProperty('animation');
|
picCtx.canvas.style.removeProperty('animation');
|
||||||
|
pic2Ctx.canvas.style.removeProperty('animation');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
pic = document.getElementById('pic') as HTMLCanvasElement;
|
let pic = document.getElementById('pic') as HTMLCanvasElement;
|
||||||
picCtx = pic.getContext('2d')!;
|
let picCtx = pic.getContext('2d')!;
|
||||||
|
|
||||||
pic2 = document.getElementById('pic2') as HTMLCanvasElement;
|
let pic2 = document.getElementById('pic2') as HTMLCanvasElement;
|
||||||
pic2Ctx = pic2.getContext('2d')!;
|
let pic2Ctx = pic2.getContext('2d')!;
|
||||||
|
|
||||||
picCtx.drawImage(await loadingMessage(), 0, 0);
|
let buf = new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!;
|
||||||
|
|
||||||
fronts = await loadSide('front');
|
message('loading layers…', picCtx);
|
||||||
backs = await loadSide('back');
|
|
||||||
side = 'front';
|
|
||||||
|
|
||||||
let hue = await recolorOn(picCtx);
|
let side: Side = await Side.init(buf).catch(err => {
|
||||||
|
message(err, picCtx);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
let hue = await side.recolorOn(picCtx, buf);
|
||||||
setBg(hue);
|
setBg(hue);
|
||||||
await redraw(picCtx, layers(), pos());
|
await redraw(picCtx, buf, side.layers());
|
||||||
|
|
||||||
const reroll = document.getElementById('reroll')!;
|
const reroll = document.getElementById('reroll')!;
|
||||||
const swap = document.getElementById('swap')!;
|
const swap = document.getElementById('swap')!;
|
||||||
|
@ -285,19 +274,25 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||||
swap.removeEventListener('click', handleSwap);
|
swap.removeEventListener('click', handleSwap);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleReroll(e: Event) { wrap(reroll, animateReroll, 'reroll', e); }
|
function handleReroll(e: Event) {
|
||||||
function handleSwap(e: Event) { wrap(swap, animateSwap, 'swap', e); }
|
wrap(reroll, done => animateReroll(picCtx, pic2Ctx, buf, side, done),
|
||||||
|
'reroll', e);
|
||||||
|
}
|
||||||
|
|
||||||
type HandlerWithFinish = (e: Event, finish: () => void) => void;
|
function handleSwap(e: Event) {
|
||||||
|
wrap(swap, done => animateSwap(picCtx, pic2Ctx, side, buf, done),
|
||||||
|
'swap', e);
|
||||||
|
}
|
||||||
|
|
||||||
function wrap(elem: Element, f: HandlerWithFinish,
|
type Handler = (finish: () => void) => void;
|
||||||
name: string, e: Event) {
|
|
||||||
|
function wrap(elem: Element, f: Handler, name: string, e: Event) {
|
||||||
if (elem != e.target) return;
|
if (elem != e.target) return;
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (isRunning()) return;
|
if (isRunning()) return;
|
||||||
removeListeners();
|
removeListeners();
|
||||||
startAnim(name);
|
startAnim(name);
|
||||||
f(e, () => { finishAnim(); addListeners(); });
|
f(() => { finishAnim(); addListeners(); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,131 @@
|
||||||
const rand: () => number = Math.random; // [todo]
|
const rand: () => number = Math.random; // [todo]
|
||||||
|
|
||||||
|
function randBetween(x: number, y: number): number {
|
||||||
|
const lo = min(x, y), hi = max(x, y);
|
||||||
|
return lo + rand() * (hi - lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function oneOf<A>(...xs: A[]): A {
|
||||||
|
return xs[Math.floor(rand() * xs.length)]!;
|
||||||
|
}
|
||||||
|
|
||||||
const max = Math.max;
|
const max = Math.max;
|
||||||
const min = Math.min;
|
const min = Math.min;
|
||||||
|
|
||||||
export type Oklch = { l: number, c: number, h: number };
|
|
||||||
|
|
||||||
export function oklch(col: Oklch, alpha: number = 1): string {
|
|
||||||
return `oklch(${col.l} ${col.c} ${col.h} / ${alpha})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
type LD = 'light' | 'dark';
|
type LD = 'light' | 'dark';
|
||||||
|
|
||||||
|
namespace Oklch {
|
||||||
|
export type Channel = 'l' | 'c' | 'h';
|
||||||
|
export type Channels = Record<Channel, number>;
|
||||||
|
export type ChannelMap = (x: number) => number;
|
||||||
|
export type ChannelMaps = Record<Channel, ChannelMap>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Oklch {
|
||||||
|
static lightFor(baseL: number): number { return randBetween(baseL, MAXL); }
|
||||||
|
|
||||||
|
static darkFor(baseL: number): number { return randBetween(MINL, baseL); }
|
||||||
|
|
||||||
|
static isLight(l: number): boolean { return l >= MINL_LIGHT; }
|
||||||
|
|
||||||
|
static brightFor(l: number, baseC: number): number {
|
||||||
|
if (Oklch.isLight(l)) { return randBetween(baseC, MAXC_LIGHT); }
|
||||||
|
else { return randBetween(baseC, MAXC_DARK); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static dullFor(l: number, baseC: number): number {
|
||||||
|
if (Oklch.isLight(l)) { return randBetween(baseC, MINC_LIGHT); }
|
||||||
|
else { return randBetween(baseC, MINC_DARK); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static analogous1(baseH: number): number {
|
||||||
|
const size = randBetween(MINH_SEP, 2 * MINH_SEP);
|
||||||
|
return rand() > 0.5 ? baseH + size : baseH - size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static analogous(baseH: number, count: number): number[] {
|
||||||
|
const minWidth = min(count * MINH_SEP, MAXH_WIDTH * 0.8);
|
||||||
|
const width = randBetween(minWidth, MAXH_WIDTH);
|
||||||
|
const sep = width / (count - 1);
|
||||||
|
const start = baseH - (width / 2);
|
||||||
|
const numbers = Array.from({length: count}, (_u, i) => start + i * sep);
|
||||||
|
return rand() > 0.5 ? numbers : numbers.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
static complementary1(baseH: number): number {
|
||||||
|
return Oklch.analogous1((baseH + 180) % 360);
|
||||||
|
}
|
||||||
|
|
||||||
|
static complementary(baseH: number, count: number): number[] {
|
||||||
|
const angle = randBetween(180 - MAXH_COMPL/2, 180 + MAXH_COMPL/2);
|
||||||
|
return Oklch.analogous(baseH + angle, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static triad(baseH: number): [number, number] {
|
||||||
|
const angle = randBetween(120 - MAXH_TRIAD/2, 120 + MAXH_TRIAD/2);
|
||||||
|
return [baseH - angle, baseH + angle];
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly l: number; readonly c: number; readonly h: number;
|
||||||
|
|
||||||
|
static baseLuma(ld?: LD): number {
|
||||||
|
if (ld == 'light') {
|
||||||
|
return randBetween(MINL_LIGHT, MAXL);
|
||||||
|
} else if (ld == 'dark') {
|
||||||
|
return randBetween(MINL, MAXL_DARK);
|
||||||
|
} else {
|
||||||
|
return randBetween(MINL, MAXL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static baseChroma(l: number): number {
|
||||||
|
if (l >= MINL_LIGHT) {
|
||||||
|
return randBetween(MINC_LIGHT, MAXC_LIGHT);
|
||||||
|
} else {
|
||||||
|
return randBetween(MINC_DARK, MAXC_DARK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static baseHue(): number { return rand() * 360; }
|
||||||
|
|
||||||
|
constructor();
|
||||||
|
constructor(ld: LD);
|
||||||
|
constructor(cs: Oklch.Channels);
|
||||||
|
constructor(l: number, c: number, h: number);
|
||||||
|
constructor(lcsld?: number | Oklch.Channels | LD,
|
||||||
|
cc?: number, hh?: number) {
|
||||||
|
if (typeof lcsld == 'string' || lcsld == undefined) {
|
||||||
|
this.l = Oklch.baseLuma(lcsld as LD | undefined);
|
||||||
|
this.c = Oklch.baseChroma(this.l);
|
||||||
|
this.h = Oklch.baseHue();
|
||||||
|
} else if (cc == undefined && hh == undefined) {
|
||||||
|
const {l, c, h} = lcsld as Oklch.Channels;
|
||||||
|
this.l = l; this.c = c; this.h = h;
|
||||||
|
} else {
|
||||||
|
this.l = lcsld as number; this.c = cc!; this.h = hh!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
css(alpha: number = 1): string {
|
||||||
|
return `oklch(${this.l} ${this.c} ${this.h} / ${alpha})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
with(lch: Partial<Record<Oklch.Channel, number | Oklch.ChannelMap>>): Oklch {
|
||||||
|
function call(comp: undefined | number | Oklch.ChannelMap, x: number) {
|
||||||
|
if (comp == undefined) { return x; }
|
||||||
|
else if (typeof comp == 'function') { return comp(x); }
|
||||||
|
else { return comp as number; }
|
||||||
|
}
|
||||||
|
return new Oklch({
|
||||||
|
l: call(lch.l, this.l),
|
||||||
|
c: call(lch.c, this.c),
|
||||||
|
h: call(lch.h, this.h),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const MAXL = 0.9;
|
const MAXL = 0.9;
|
||||||
const MINL = 0.4;
|
const MINL = 0.4;
|
||||||
const MINL_LIGHT = 0.7;
|
const MINL_LIGHT = 0.7;
|
||||||
|
@ -34,89 +149,6 @@ const MAXH_COMPL = 40;
|
||||||
// size of the wedge a "triadic" color can be in
|
// size of the wedge a "triadic" color can be in
|
||||||
const MAXH_TRIAD = 25;
|
const MAXH_TRIAD = 25;
|
||||||
|
|
||||||
function randBetween(x: number, y: number): number {
|
|
||||||
const lo = min(x, y), hi = max(x, y);
|
|
||||||
return lo + rand() * (hi - lo);
|
|
||||||
}
|
|
||||||
|
|
||||||
function oneOf<A>(...xs: A[]): A {
|
|
||||||
return xs[Math.floor(rand() * xs.length)]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function baseLuma(ld?: LD): number {
|
|
||||||
if (ld == 'light') {
|
|
||||||
return randBetween(MINL_LIGHT, MAXL);
|
|
||||||
} else if (ld == 'dark') {
|
|
||||||
return randBetween(MINL, MAXL_DARK);
|
|
||||||
} else {
|
|
||||||
return randBetween(MINL, MAXL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function baseChroma(l: number): number {
|
|
||||||
if (l >= MINL_LIGHT) {
|
|
||||||
return randBetween(MINC_LIGHT, MAXC_LIGHT);
|
|
||||||
} else {
|
|
||||||
return randBetween(MINC_DARK, MAXC_DARK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function baseHue(): number { return rand() * 360; }
|
|
||||||
|
|
||||||
function baseOklch(ld?: LD): Oklch {
|
|
||||||
const l = baseLuma(ld);
|
|
||||||
return { l, c: baseChroma(l), h: baseHue() };
|
|
||||||
}
|
|
||||||
|
|
||||||
function lightFor(baseL: number): number {
|
|
||||||
return randBetween(baseL, MAXL);
|
|
||||||
}
|
|
||||||
function darkFor(baseL: number): number {
|
|
||||||
return randBetween(MINL, baseL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLight(l: number): boolean { return l >= MINL_LIGHT; }
|
|
||||||
|
|
||||||
function brightFor(l: number, baseC: number): number {
|
|
||||||
if (isLight(l)) { return randBetween(baseC, MAXC_LIGHT); }
|
|
||||||
else { return randBetween(baseC, MAXC_DARK); }
|
|
||||||
}
|
|
||||||
|
|
||||||
function dullFor(l: number, baseC: number): number {
|
|
||||||
if (isLight(l)) { return randBetween(baseC, MINC_LIGHT); }
|
|
||||||
else { return randBetween(baseC, MINC_DARK); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function analogous1(baseH: number): number {
|
|
||||||
const size = randBetween(MINH_SEP, 2 * MINH_SEP);
|
|
||||||
return rand() > 0.5 ? baseH + size : baseH - size;
|
|
||||||
}
|
|
||||||
|
|
||||||
function analogous(baseH: number, count: number): number[] {
|
|
||||||
const minWidth = min(count * MINH_SEP, MAXH_WIDTH * 0.8);
|
|
||||||
const width = randBetween(minWidth, MAXH_WIDTH);
|
|
||||||
const sep = width / (count - 1);
|
|
||||||
const start = baseH - (width / 2);
|
|
||||||
const numbers = Array.from({length: count}, (_u, i) => start + i * sep);
|
|
||||||
return rand() > 0.5 ? numbers : numbers.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
function complementary1(baseH: number): number {
|
|
||||||
return analogous1((baseH + 180) % 360);
|
|
||||||
}
|
|
||||||
|
|
||||||
function complementary(baseH: number, count: number): number[] {
|
|
||||||
const angle = randBetween(180 - MAXH_COMPL/2, 180 + MAXH_COMPL/2);
|
|
||||||
return analogous(baseH + angle, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
function triad(baseH: number): [number, number] {
|
|
||||||
const angle = randBetween(120 - MAXH_TRIAD/2, 120 + MAXH_TRIAD/2);
|
|
||||||
return [baseH - angle, baseH + angle];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SchemeType = 'triad' | 'fin-belly' | 'fin-body';
|
export type SchemeType = 'triad' | 'fin-belly' | 'fin-body';
|
||||||
|
|
||||||
export type OuterLayer = 'outer' | 'spines' | 'vitiligo1';
|
export type OuterLayer = 'outer' | 'spines' | 'vitiligo1';
|
||||||
|
@ -148,7 +180,7 @@ export function makeColorInfo<A>(f: (l: Layer) => A): Record<Layer, A> {
|
||||||
|
|
||||||
|
|
||||||
export function colors(): Scheme {
|
export function colors(): Scheme {
|
||||||
const outer = baseOklch('dark');
|
const outer = new Oklch('dark');
|
||||||
let outerCols: OuterCols =
|
let outerCols: OuterCols =
|
||||||
{ outer, spines: mkSpines(outer), vitiligo1: mkVitiligo(outer) };
|
{ outer, spines: mkSpines(outer), vitiligo1: mkVitiligo(outer) };
|
||||||
|
|
||||||
|
@ -159,16 +191,16 @@ export function colors(): Scheme {
|
||||||
const whichBody = rand();
|
const whichBody = rand();
|
||||||
if (whichBody > 2/3) {
|
if (whichBody > 2/3) {
|
||||||
type = 'triad';
|
type = 'triad';
|
||||||
const [f, b] = triad(outer.h);
|
const [f, b] = Oklch.triad(outer.h);
|
||||||
finCols = mkFins(f, outer); bellyCols = mkBelly(b);
|
finCols = mkFins(f, outer); bellyCols = mkBelly(b);
|
||||||
} else if (whichBody > 1/3) {
|
} else if (whichBody > 1/3) {
|
||||||
type = 'fin-belly';
|
type = 'fin-belly';
|
||||||
const [f, b] = complementary(outer.h, 2);
|
const [f, b] = Oklch.complementary(outer.h, 2);
|
||||||
finCols = mkFins(f!, outer); bellyCols = mkBelly(b!);
|
finCols = mkFins(f!, outer); bellyCols = mkBelly(b!);
|
||||||
} else {
|
} else {
|
||||||
type = 'fin-body';
|
type = 'fin-body';
|
||||||
finCols = mkFins(analogous1(outer.h), outer);
|
finCols = mkFins(Oklch.analogous1(outer.h), outer);
|
||||||
bellyCols = mkBelly(complementary1(outer.h));
|
bellyCols = mkBelly(Oklch.complementary1(outer.h));
|
||||||
}
|
}
|
||||||
|
|
||||||
let miscCols = mkMisc(outerCols, finCols, bellyCols);
|
let miscCols = mkMisc(outerCols, finCols, bellyCols);
|
||||||
|
@ -178,80 +210,88 @@ export function colors(): Scheme {
|
||||||
|
|
||||||
|
|
||||||
function mkSpines(outer: Oklch): Oklch {
|
function mkSpines(outer: Oklch): Oklch {
|
||||||
return {
|
return outer.with({
|
||||||
l: outer.l * 0.8, c: outer.c * 1.1,
|
l: x => x * 0.8,
|
||||||
h: randBetween(outer.h + 12, outer.h - 12)
|
c: x => x * 1.1,
|
||||||
};
|
h: x => randBetween(x + 12, x - 12),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkVitiligo(outer: Oklch): Oklch {
|
function mkVitiligo(outer: Oklch): Oklch {
|
||||||
return {
|
return outer.with({
|
||||||
l: randBetween(max(outer.l, 0.94), 0.985), // exception to MAXL
|
l: x => randBetween(max(x, 0.94), 0.985), // exception to MAXL
|
||||||
c: randBetween(min(outer.c, 0.1), MINC_LIGHT),
|
c: x => randBetween(min(x, 0.1), MINC_LIGHT),
|
||||||
h: outer.h
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkStripes(): Oklch {
|
function mkStripes(): Oklch {
|
||||||
return {
|
return new Oklch({
|
||||||
l: randBetween(0.8, MAXL),
|
l: randBetween(0.8, MAXL),
|
||||||
c: randBetween(MINC_LIGHT, MAXC_LIGHT),
|
c: randBetween(MINC_LIGHT, MAXC_LIGHT),
|
||||||
h: rand() * 360
|
h: rand() * 360
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkCuffs(sock: Oklch): Oklch {
|
function mkCuffs(sock: Oklch): Oklch {
|
||||||
return {
|
return sock.with({
|
||||||
l: randBetween(sock.l * 0.85, sock.l * 0.65),
|
l: x => randBetween(x * 0.85, x * 0.65),
|
||||||
c: randBetween(sock.c, MAXC_LIGHT),
|
c: x => randBetween(x, MAXC_LIGHT),
|
||||||
h: randBetween(sock.h + 8, sock.h - 8)
|
h: x => randBetween(x + 8, x - 8),
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkFins(h: number, outer: Oklch): FinCols {
|
function mkFins(h: number, outer: Oklch): FinCols {
|
||||||
const [fin1Hue, fin2Hue, fin3Hue] = analogous(h, 3);
|
const [fin1Hue, fin2Hue, fin3Hue] = Oklch.analogous(h, 3);
|
||||||
const [ll, cc] = oneOf([lightFor, dullFor], [darkFor, brightFor]);
|
const [ll, cc] = oneOf(
|
||||||
const fins1 = { l: ll(outer.l), c: cc(outer.l, outer.c), h: fin1Hue! };
|
[Oklch.lightFor, Oklch.dullFor], [Oklch.darkFor, Oklch.brightFor]);
|
||||||
const fins2 = { l: ll(fins1.l), c: cc(fins1.l, fins1.c), h: fin2Hue! };
|
|
||||||
const fins3 = { l: ll(fins2.l), c: cc(fins2.l, fins2.c), h: fin3Hue! };
|
const fins1 = new Oklch(ll(outer.l), cc(outer.l, outer.c), fin1Hue!);
|
||||||
|
const fins2 = new Oklch(ll(fins1.l), cc(fins1.l, fins1.c), fin2Hue!);
|
||||||
|
const fins3 = new Oklch(ll(fins2.l), cc(fins2.l, fins2.c), fin3Hue!);
|
||||||
const vitiligo4 = mkVitiligo(fins1);
|
const vitiligo4 = mkVitiligo(fins1);
|
||||||
return { fins1, fins2, fins3, vitiligo4 };
|
return { fins1, fins2, fins3, vitiligo4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkBelly(h: number): BellyCols {
|
function mkBelly(h: number): BellyCols {
|
||||||
const [belly1Hue, belly2Hue] = analogous(h, 2);
|
const [belly1Hue, belly2Hue] = Oklch.analogous(h, 2);
|
||||||
const belly1 =
|
const belly1 = new Oklch({
|
||||||
{ l: randBetween(0.7, MAXL), c: baseChroma(1), h: belly1Hue! };
|
l: randBetween(0.7, MAXL),
|
||||||
const belly2 =
|
c: Oklch.baseChroma(1),
|
||||||
{ l: min(MAXL, belly1.l * 1.1), c: belly1.c * 0.9, h: belly2Hue! };
|
h: belly1Hue!
|
||||||
|
});
|
||||||
|
const belly2 = belly1.with({
|
||||||
|
l: x => min(MAXL, x * 1.1),
|
||||||
|
c: x => x * 0.9,
|
||||||
|
h: belly2Hue!,
|
||||||
|
});
|
||||||
const vitiligo3 = mkVitiligo(belly1);
|
const vitiligo3 = mkVitiligo(belly1);
|
||||||
const vitiligo2 = mkVitiligo(belly2);
|
const vitiligo2 = mkVitiligo(belly2);
|
||||||
return { belly1, belly2, vitiligo2, vitiligo3 };
|
return { belly1, belly2, vitiligo2, vitiligo3 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkMisc(o: OuterCols, f: FinCols, b: BellyCols): MiscCols {
|
function mkMisc(o: OuterCols, f: FinCols, b: BellyCols): MiscCols {
|
||||||
const masks = {
|
const masks = new Oklch({
|
||||||
l: randBetween(0.8, MAXL),
|
l: randBetween(0.8, MAXL),
|
||||||
c: randBetween(0.01, 0.06),
|
c: randBetween(0.01, 0.06),
|
||||||
h: analogous1(oneOf(o.outer, b.belly1, f.fins1).h)
|
h: Oklch.analogous1(oneOf(o.outer, b.belly1, f.fins1).h)
|
||||||
};
|
});
|
||||||
return {
|
return {
|
||||||
masks,
|
masks,
|
||||||
eyes: {
|
eyes: new Oklch({
|
||||||
l: baseLuma('light'),
|
l: Oklch.baseLuma('light'),
|
||||||
c: randBetween(0.28, MAXC_LIGHT),
|
c: randBetween(0.28, MAXC_LIGHT),
|
||||||
h: oneOf(analogous1, complementary1)(o.outer.h)
|
h: oneOf(Oklch.analogous1, Oklch.complementary1)(o.outer.h)
|
||||||
},
|
}),
|
||||||
claws: {
|
claws: masks.with({
|
||||||
l: min(MAXL, masks.l + randBetween(0, 0.1)),
|
l: x => min(MAXL, x + randBetween(0, 0.1)),
|
||||||
c: randBetween(0.01, 0.06),
|
c: randBetween(0.01, 0.06),
|
||||||
h: analogous1(masks.h)
|
h: Oklch.analogous1,
|
||||||
},
|
}),
|
||||||
lines: {
|
lines: new Oklch({
|
||||||
l: randBetween(0.01, 0.06),
|
l: randBetween(0.01, 0.06),
|
||||||
c: baseChroma(0),
|
c: Oklch.baseChroma(0),
|
||||||
h: analogous1(o.outer.h)
|
h: Oklch.analogous1(o.outer.h)
|
||||||
}
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
rainbow-quox/front/belly1.png
(Stored with Git LFS)
BIN
rainbow-quox/front/belly1.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/belly1.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/belly1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/belly2.png
(Stored with Git LFS)
BIN
rainbow-quox/front/belly2.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/belly2.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/belly2.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/claws.png
(Stored with Git LFS)
BIN
rainbow-quox/front/claws.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/claws.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/claws.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/cuffs.png
(Stored with Git LFS)
BIN
rainbow-quox/front/cuffs.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/cuffs.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/cuffs.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/eyes.png
(Stored with Git LFS)
BIN
rainbow-quox/front/eyes.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/eyes.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/eyes.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/fins1.png
(Stored with Git LFS)
BIN
rainbow-quox/front/fins1.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/fins1.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/fins1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/fins2.png
(Stored with Git LFS)
BIN
rainbow-quox/front/fins2.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/fins2.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/fins2.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/fins3.png
(Stored with Git LFS)
BIN
rainbow-quox/front/fins3.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/fins3.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/fins3.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/lines.png
(Stored with Git LFS)
BIN
rainbow-quox/front/lines.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/lines.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/lines.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/masks.png
(Stored with Git LFS)
BIN
rainbow-quox/front/masks.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/masks.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/masks.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/outer.png
(Stored with Git LFS)
BIN
rainbow-quox/front/outer.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/outer.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/outer.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
21
rainbow-quox/front/pos.json
Normal file
21
rainbow-quox/front/pos.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"belly1": [186, 92],
|
||||||
|
"belly2": [186, 91],
|
||||||
|
"claws": [3, 168],
|
||||||
|
"cuffs": [42, 160],
|
||||||
|
"eyes": [223, 52],
|
||||||
|
"eyeshine": [223, 52],
|
||||||
|
"fins1": [381, 31],
|
||||||
|
"fins2": [387, 35],
|
||||||
|
"fins3": [495, 140],
|
||||||
|
"lines": [1, 0],
|
||||||
|
"masks": [173, 3],
|
||||||
|
"outer": [28, 43],
|
||||||
|
"spines": [372, 23],
|
||||||
|
"static": [50, 52],
|
||||||
|
"stripes": [50, 168],
|
||||||
|
"vitiligo1": [34, 23],
|
||||||
|
"vitiligo2": [198, 92],
|
||||||
|
"vitiligo3": [214, 312],
|
||||||
|
"vitiligo4": [647, 71]
|
||||||
|
}
|
BIN
rainbow-quox/front/spines.png
(Stored with Git LFS)
BIN
rainbow-quox/front/spines.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/spines.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/spines.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/static.png
(Stored with Git LFS)
BIN
rainbow-quox/front/static.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/static.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/static.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/stripes.png
(Stored with Git LFS)
BIN
rainbow-quox/front/stripes.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/stripes.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/stripes.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/vitiligo1.png
(Stored with Git LFS)
BIN
rainbow-quox/front/vitiligo1.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/vitiligo1.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/vitiligo1.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/vitiligo2.png
(Stored with Git LFS)
BIN
rainbow-quox/front/vitiligo2.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/vitiligo2.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/vitiligo2.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/vitiligo3.png
(Stored with Git LFS)
BIN
rainbow-quox/front/vitiligo3.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/vitiligo3.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/vitiligo3.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/front/vitiligo4.png
(Stored with Git LFS)
BIN
rainbow-quox/front/vitiligo4.png
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/front/vitiligo4.webp
(Stored with Git LFS)
Normal file
BIN
rainbow-quox/front/vitiligo4.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
rainbow-quox/kra/back.kra
(Stored with Git LFS)
BIN
rainbow-quox/kra/back.kra
(Stored with Git LFS)
Binary file not shown.
BIN
rainbow-quox/kra/front.kra
(Stored with Git LFS)
BIN
rainbow-quox/kra/front.kra
(Stored with Git LFS)
Binary file not shown.
|
@ -12,12 +12,17 @@
|
||||||
* { transition: none; }
|
* { transition: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes swap {
|
@keyframes swap1 {
|
||||||
0% { transform: none; }
|
0% { transform: none; }
|
||||||
49% { transform: translateX(-150vw); }
|
49% { transform: translateX(-150vw); }
|
||||||
50% { content-visibility: hidden; }
|
50% { content-visibility: hidden; }
|
||||||
51% { transform: translateX(150vw); content-visibility: visible; }
|
100% { content-visibility: hidden; transform: none; }
|
||||||
100% { transform: none; }
|
}
|
||||||
|
|
||||||
|
@keyframes swap2 {
|
||||||
|
0% { content-visibility: hidden; transform: translateX(150vw); }
|
||||||
|
49% { content-visibility: visible; opacity: 1; }
|
||||||
|
100% { transform: none; opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
|
|
Loading…
Add table
Reference in a new issue