From d0099fbf19b7681f313c95674e4758aa5970fd60 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sat, 7 Dec 2024 21:28:49 +0100 Subject: [PATCH 01/19] rainbow quox canvas stuff --- Makefile | 2 +- rainbow-quox/back/belly1.png | 3 - rainbow-quox/back/belly1.webp | 3 + rainbow-quox/back/belly2.png | 3 - rainbow-quox/back/belly2.webp | 3 + rainbow-quox/back/claws.png | 3 - rainbow-quox/back/claws.webp | 3 + rainbow-quox/back/cuffs.png | 3 - rainbow-quox/back/cuffs.webp | 3 + rainbow-quox/back/eyes.png | 3 - rainbow-quox/back/eyes.webp | 3 + rainbow-quox/back/fins1.png | 3 - rainbow-quox/back/fins1.webp | 3 + rainbow-quox/back/fins2.png | 3 - rainbow-quox/back/fins2.webp | 3 + rainbow-quox/back/fins3.png | 3 - rainbow-quox/back/fins3.webp | 3 + rainbow-quox/back/lines.png | 3 - rainbow-quox/back/lines.webp | 3 + rainbow-quox/back/masks.png | 3 - rainbow-quox/back/masks.webp | 3 + rainbow-quox/back/outer.png | 3 - rainbow-quox/back/outer.webp | 3 + rainbow-quox/back/pos.json | 21 ++ rainbow-quox/back/spines.png | 3 - rainbow-quox/back/spines.webp | 3 + rainbow-quox/back/static.png | 3 - rainbow-quox/back/static.webp | 3 + rainbow-quox/back/stripes.png | 3 - rainbow-quox/back/stripes.webp | 3 + rainbow-quox/back/vitiligo1.png | 3 - rainbow-quox/back/vitiligo1.webp | 3 + rainbow-quox/back/vitiligo2.png | 3 - rainbow-quox/back/vitiligo2.webp | 3 + rainbow-quox/back/vitiligo3.png | 3 - rainbow-quox/back/vitiligo3.webp | 3 + rainbow-quox/back/vitiligo4.png | 3 - rainbow-quox/back/vitiligo4.webp | 3 + rainbow-quox/canvas.ts | 325 +++++++++++++++--------------- rainbow-quox/color.ts | 308 ++++++++++++++++------------ rainbow-quox/front/belly1.png | 3 - rainbow-quox/front/belly1.webp | 3 + rainbow-quox/front/belly2.png | 3 - rainbow-quox/front/belly2.webp | 3 + rainbow-quox/front/claws.png | 3 - rainbow-quox/front/claws.webp | 3 + rainbow-quox/front/cuffs.png | 3 - rainbow-quox/front/cuffs.webp | 3 + rainbow-quox/front/eyes.png | 3 - rainbow-quox/front/eyes.webp | 3 + rainbow-quox/front/fins1.png | 3 - rainbow-quox/front/fins1.webp | 3 + rainbow-quox/front/fins2.png | 3 - rainbow-quox/front/fins2.webp | 3 + rainbow-quox/front/fins3.png | 3 - rainbow-quox/front/fins3.webp | 3 + rainbow-quox/front/lines.png | 3 - rainbow-quox/front/lines.webp | 3 + rainbow-quox/front/masks.png | 3 - rainbow-quox/front/masks.webp | 3 + rainbow-quox/front/outer.png | 3 - rainbow-quox/front/outer.webp | 3 + rainbow-quox/front/pos.json | 21 ++ rainbow-quox/front/spines.png | 3 - rainbow-quox/front/spines.webp | 3 + rainbow-quox/front/static.png | 3 - rainbow-quox/front/static.webp | 3 + rainbow-quox/front/stripes.png | 3 - rainbow-quox/front/stripes.webp | 3 + rainbow-quox/front/vitiligo1.png | 3 - rainbow-quox/front/vitiligo1.webp | 3 + rainbow-quox/front/vitiligo2.png | 3 - rainbow-quox/front/vitiligo2.webp | 3 + rainbow-quox/front/vitiligo3.png | 3 - rainbow-quox/front/vitiligo3.webp | 3 + rainbow-quox/front/vitiligo4.png | 3 - rainbow-quox/front/vitiligo4.webp | 3 + rainbow-quox/kra/back.kra | 4 +- rainbow-quox/kra/front.kra | 4 +- rainbow-quox/style.css | 11 +- 80 files changed, 497 insertions(+), 415 deletions(-) delete mode 100644 rainbow-quox/back/belly1.png create mode 100644 rainbow-quox/back/belly1.webp delete mode 100644 rainbow-quox/back/belly2.png create mode 100644 rainbow-quox/back/belly2.webp delete mode 100644 rainbow-quox/back/claws.png create mode 100644 rainbow-quox/back/claws.webp delete mode 100644 rainbow-quox/back/cuffs.png create mode 100644 rainbow-quox/back/cuffs.webp delete mode 100644 rainbow-quox/back/eyes.png create mode 100644 rainbow-quox/back/eyes.webp delete mode 100644 rainbow-quox/back/fins1.png create mode 100644 rainbow-quox/back/fins1.webp delete mode 100644 rainbow-quox/back/fins2.png create mode 100644 rainbow-quox/back/fins2.webp delete mode 100644 rainbow-quox/back/fins3.png create mode 100644 rainbow-quox/back/fins3.webp delete mode 100644 rainbow-quox/back/lines.png create mode 100644 rainbow-quox/back/lines.webp delete mode 100644 rainbow-quox/back/masks.png create mode 100644 rainbow-quox/back/masks.webp delete mode 100644 rainbow-quox/back/outer.png create mode 100644 rainbow-quox/back/outer.webp create mode 100644 rainbow-quox/back/pos.json delete mode 100644 rainbow-quox/back/spines.png create mode 100644 rainbow-quox/back/spines.webp delete mode 100644 rainbow-quox/back/static.png create mode 100644 rainbow-quox/back/static.webp delete mode 100644 rainbow-quox/back/stripes.png create mode 100644 rainbow-quox/back/stripes.webp delete mode 100644 rainbow-quox/back/vitiligo1.png create mode 100644 rainbow-quox/back/vitiligo1.webp delete mode 100644 rainbow-quox/back/vitiligo2.png create mode 100644 rainbow-quox/back/vitiligo2.webp delete mode 100644 rainbow-quox/back/vitiligo3.png create mode 100644 rainbow-quox/back/vitiligo3.webp delete mode 100644 rainbow-quox/back/vitiligo4.png create mode 100644 rainbow-quox/back/vitiligo4.webp delete mode 100644 rainbow-quox/front/belly1.png create mode 100644 rainbow-quox/front/belly1.webp delete mode 100644 rainbow-quox/front/belly2.png create mode 100644 rainbow-quox/front/belly2.webp delete mode 100644 rainbow-quox/front/claws.png create mode 100644 rainbow-quox/front/claws.webp delete mode 100644 rainbow-quox/front/cuffs.png create mode 100644 rainbow-quox/front/cuffs.webp delete mode 100644 rainbow-quox/front/eyes.png create mode 100644 rainbow-quox/front/eyes.webp delete mode 100644 rainbow-quox/front/fins1.png create mode 100644 rainbow-quox/front/fins1.webp delete mode 100644 rainbow-quox/front/fins2.png create mode 100644 rainbow-quox/front/fins2.webp delete mode 100644 rainbow-quox/front/fins3.png create mode 100644 rainbow-quox/front/fins3.webp delete mode 100644 rainbow-quox/front/lines.png create mode 100644 rainbow-quox/front/lines.webp delete mode 100644 rainbow-quox/front/masks.png create mode 100644 rainbow-quox/front/masks.webp delete mode 100644 rainbow-quox/front/outer.png create mode 100644 rainbow-quox/front/outer.webp create mode 100644 rainbow-quox/front/pos.json delete mode 100644 rainbow-quox/front/spines.png create mode 100644 rainbow-quox/front/spines.webp delete mode 100644 rainbow-quox/front/static.png create mode 100644 rainbow-quox/front/static.webp delete mode 100644 rainbow-quox/front/stripes.png create mode 100644 rainbow-quox/front/stripes.webp delete mode 100644 rainbow-quox/front/vitiligo1.png create mode 100644 rainbow-quox/front/vitiligo1.webp delete mode 100644 rainbow-quox/front/vitiligo2.png create mode 100644 rainbow-quox/front/vitiligo2.webp delete mode 100644 rainbow-quox/front/vitiligo3.png create mode 100644 rainbow-quox/front/vitiligo3.webp delete mode 100644 rainbow-quox/front/vitiligo4.png create mode 100644 rainbow-quox/front/vitiligo4.webp diff --git a/Makefile b/Makefile index 76ee9e2..112913d 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ 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/*.png) $(wildcard rainbow-quox/back/*.png) + $(wildcard rainbow-quox/front/*) $(wildcard rainbow-quox/back/*) SCRIPTS = $(patsubst %.ts,%.js,$(wildcard script/*.ts rainbow-quox/*.ts)) MISC = $(shell find .well-known -type f) ALL = $(CSS) $(PAGES) $(MEDIA) $(SCRIPTS) $(MISC) diff --git a/rainbow-quox/back/belly1.png b/rainbow-quox/back/belly1.png deleted file mode 100644 index 8baa6ee..0000000 --- a/rainbow-quox/back/belly1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:936b3d8ff41e8b56747c9e8fbeb461a8daa9837e8fcc17aadbbfa902dc0c7de5 -size 31823 diff --git a/rainbow-quox/back/belly1.webp b/rainbow-quox/back/belly1.webp new file mode 100644 index 0000000..b180728 --- /dev/null +++ b/rainbow-quox/back/belly1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17435ce4ea276f54be6e355a86a9cdafcaa0f740e79bf7cd23fe2a054bf2942f +size 10186 diff --git a/rainbow-quox/back/belly2.png b/rainbow-quox/back/belly2.png deleted file mode 100644 index c0cc1df..0000000 --- a/rainbow-quox/back/belly2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:95546e967c48402d64da50c2eb5e4cbee109e2d132f11e6e73f18be8efd609ae -size 26262 diff --git a/rainbow-quox/back/belly2.webp b/rainbow-quox/back/belly2.webp new file mode 100644 index 0000000..963ee27 --- /dev/null +++ b/rainbow-quox/back/belly2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e4583e2e8da22d32dd9ef8504cb3f1a9b98506c4ec93072f1405857eb28080c +size 11180 diff --git a/rainbow-quox/back/claws.png b/rainbow-quox/back/claws.png deleted file mode 100644 index 8f0480c..0000000 --- a/rainbow-quox/back/claws.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37767fc980789fffd668903c8f90648076fe1860471455094619d2a197adbe8b -size 15219 diff --git a/rainbow-quox/back/claws.webp b/rainbow-quox/back/claws.webp new file mode 100644 index 0000000..68290cc --- /dev/null +++ b/rainbow-quox/back/claws.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bf7aae31eb9508d901047727e485f9cb0ec570b02a7503d7fd55ec35ee2144d +size 5866 diff --git a/rainbow-quox/back/cuffs.png b/rainbow-quox/back/cuffs.png deleted file mode 100644 index c2c2e41..0000000 --- a/rainbow-quox/back/cuffs.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f00fe3d6df5686c7087effd62a18810d2cdb9e948a5679f3915414e11224954a -size 18248 diff --git a/rainbow-quox/back/cuffs.webp b/rainbow-quox/back/cuffs.webp new file mode 100644 index 0000000..17b507f --- /dev/null +++ b/rainbow-quox/back/cuffs.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea07cae1405ff4d18c18b75190dee86667e898a2d43059402b8fec5cd4162453 +size 7216 diff --git a/rainbow-quox/back/eyes.png b/rainbow-quox/back/eyes.png deleted file mode 100644 index f36026a..0000000 --- a/rainbow-quox/back/eyes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:270e569cd19f8c4aa5ab6cffd613eb50a68b66d8ab83a895a5094dce339ad724 -size 2494 diff --git a/rainbow-quox/back/eyes.webp b/rainbow-quox/back/eyes.webp new file mode 100644 index 0000000..fa1916d --- /dev/null +++ b/rainbow-quox/back/eyes.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eebdbf997f1fe6bbcb95b07904631197a4a8f9da4901dc52f7b3a4281dd966f2 +size 1834 diff --git a/rainbow-quox/back/fins1.png b/rainbow-quox/back/fins1.png deleted file mode 100644 index 5012332..0000000 --- a/rainbow-quox/back/fins1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5927463cd24394137adb4c21eeffda315e4b85bbd427edfee625dc28db74d1b6 -size 19271 diff --git a/rainbow-quox/back/fins1.webp b/rainbow-quox/back/fins1.webp new file mode 100644 index 0000000..051760d --- /dev/null +++ b/rainbow-quox/back/fins1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c43e64361cf12fd1368d47a5b2e60d4ab75b471bbedc0bd2e82c01838d09a1fa +size 8934 diff --git a/rainbow-quox/back/fins2.png b/rainbow-quox/back/fins2.png deleted file mode 100644 index 5dc5ed9..0000000 --- a/rainbow-quox/back/fins2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04da31fc3479fa6aa416bf3d26692241cdc3589a487a308304e120604233620d -size 22619 diff --git a/rainbow-quox/back/fins2.webp b/rainbow-quox/back/fins2.webp new file mode 100644 index 0000000..edfaa31 --- /dev/null +++ b/rainbow-quox/back/fins2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a3b502e3edc6422fc80ace73b2c10f1a863005ef85f7120de32c264952fbb9b +size 9604 diff --git a/rainbow-quox/back/fins3.png b/rainbow-quox/back/fins3.png deleted file mode 100644 index dfb8a92..0000000 --- a/rainbow-quox/back/fins3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e32d31df3b0eaee39b976ee06bab91b7d345a43d247b9539c4780bd15153c88 -size 7710 diff --git a/rainbow-quox/back/fins3.webp b/rainbow-quox/back/fins3.webp new file mode 100644 index 0000000..50ec1c2 --- /dev/null +++ b/rainbow-quox/back/fins3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2378beb39791d7e76ecdc20161d5f399c0e26f8e803e4ce0b1d86bb89f330dac +size 3336 diff --git a/rainbow-quox/back/lines.png b/rainbow-quox/back/lines.png deleted file mode 100644 index 11d32c7..0000000 --- a/rainbow-quox/back/lines.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dccafcdf3bedcf53a3b4d02296b0a32a8d4129ccc8f900ee03ec48a69b412100 -size 175067 diff --git a/rainbow-quox/back/lines.webp b/rainbow-quox/back/lines.webp new file mode 100644 index 0000000..94e6417 --- /dev/null +++ b/rainbow-quox/back/lines.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cb5de6589abac930b8c7d524a60ce0f90f2a544455aacc83faa1b75d491c34d +size 75832 diff --git a/rainbow-quox/back/masks.png b/rainbow-quox/back/masks.png deleted file mode 100644 index a6c2634..0000000 --- a/rainbow-quox/back/masks.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65ef56f98c07512cb61a49dddb878d05745395e0ef3b70a5e2059cadb11dfbcd -size 11064 diff --git a/rainbow-quox/back/masks.webp b/rainbow-quox/back/masks.webp new file mode 100644 index 0000000..43f85cd --- /dev/null +++ b/rainbow-quox/back/masks.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e38d13e93dee8edf287443e107321cdbcd460535ed35708ea770cd9f8b82b7a +size 5054 diff --git a/rainbow-quox/back/outer.png b/rainbow-quox/back/outer.png deleted file mode 100644 index 32050e7..0000000 --- a/rainbow-quox/back/outer.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5f14f71598703bb5cbb8c681c130e1530035c8d880c37c785a031a020efd6f25 -size 53674 diff --git a/rainbow-quox/back/outer.webp b/rainbow-quox/back/outer.webp new file mode 100644 index 0000000..1f494df --- /dev/null +++ b/rainbow-quox/back/outer.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cebc512183191a771b4897727666df3a3ecf74fa473fe250df969870457ca92 +size 16254 diff --git a/rainbow-quox/back/pos.json b/rainbow-quox/back/pos.json new file mode 100644 index 0000000..f93e41b --- /dev/null +++ b/rainbow-quox/back/pos.json @@ -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] +} diff --git a/rainbow-quox/back/spines.png b/rainbow-quox/back/spines.png deleted file mode 100644 index 52d0ca6..0000000 --- a/rainbow-quox/back/spines.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d74050957003e3899004c8feb2e42238711c8f0a36b7d02d71d5da8ce0d31dd4 -size 14773 diff --git a/rainbow-quox/back/spines.webp b/rainbow-quox/back/spines.webp new file mode 100644 index 0000000..f1fc5b6 --- /dev/null +++ b/rainbow-quox/back/spines.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e2928c46e0a82b93f6f4404116ade0f62cc1252f1f8a2c787d348ca265692f2 +size 7770 diff --git a/rainbow-quox/back/static.png b/rainbow-quox/back/static.png deleted file mode 100644 index fc4d743..0000000 --- a/rainbow-quox/back/static.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fcd0a1acdee002be0d27a9f63324dd67726d3d1e1b928984eee8a5218201e4de -size 30714 diff --git a/rainbow-quox/back/static.webp b/rainbow-quox/back/static.webp new file mode 100644 index 0000000..b871a39 --- /dev/null +++ b/rainbow-quox/back/static.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3574730db19b5c7543c486263b1c6ff4ffde294ea6486fc3a32981300ba9741b +size 14654 diff --git a/rainbow-quox/back/stripes.png b/rainbow-quox/back/stripes.png deleted file mode 100644 index e69e71c..0000000 --- a/rainbow-quox/back/stripes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0854a36214a060929bcce3da6fa5c72d44421ea60f85ffc39ba0e99f02eea466 -size 34634 diff --git a/rainbow-quox/back/stripes.webp b/rainbow-quox/back/stripes.webp new file mode 100644 index 0000000..a9158eb --- /dev/null +++ b/rainbow-quox/back/stripes.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff825e8616b2385276f2bd0091b12372770f764d54de2d3f8ea5efd4afe887c2 +size 15888 diff --git a/rainbow-quox/back/vitiligo1.png b/rainbow-quox/back/vitiligo1.png deleted file mode 100644 index 6b90b62..0000000 --- a/rainbow-quox/back/vitiligo1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16080417ad3230a645c5bff273c3370f0db00cde84424fc13e90e27354c5a0f9 -size 33461 diff --git a/rainbow-quox/back/vitiligo1.webp b/rainbow-quox/back/vitiligo1.webp new file mode 100644 index 0000000..32bfed2 --- /dev/null +++ b/rainbow-quox/back/vitiligo1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c037de4c156b9aebc2b221c7167c5ea7ffae49c1480a0f678275c08997dddc5 +size 14106 diff --git a/rainbow-quox/back/vitiligo2.png b/rainbow-quox/back/vitiligo2.png deleted file mode 100644 index 6da0281..0000000 --- a/rainbow-quox/back/vitiligo2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e0c30c5058ebe7febf9a0a1811801ee7f8bf2cab86343669cec22061d82ca08 -size 7283 diff --git a/rainbow-quox/back/vitiligo2.webp b/rainbow-quox/back/vitiligo2.webp new file mode 100644 index 0000000..d17c37b --- /dev/null +++ b/rainbow-quox/back/vitiligo2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1db28390d6407495f8c0a0125db81f8d5e15d363463c04dc77748808fbd97eb7 +size 2632 diff --git a/rainbow-quox/back/vitiligo3.png b/rainbow-quox/back/vitiligo3.png deleted file mode 100644 index c67913c..0000000 --- a/rainbow-quox/back/vitiligo3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a688d2fe7f603d2360ff027b20091a60cb710873204bdf0adfabec73a1f3be65 -size 7040 diff --git a/rainbow-quox/back/vitiligo3.webp b/rainbow-quox/back/vitiligo3.webp new file mode 100644 index 0000000..0899891 --- /dev/null +++ b/rainbow-quox/back/vitiligo3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:195c7aaf80305f17cbee871b59bf3f797628aafb09701567b22549ae68626f27 +size 2578 diff --git a/rainbow-quox/back/vitiligo4.png b/rainbow-quox/back/vitiligo4.png deleted file mode 100644 index 3d34316..0000000 --- a/rainbow-quox/back/vitiligo4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1cf7bd357602ac2ffee7d2b364562ef5b29065e6090023c71fd6631762126771 -size 13109 diff --git a/rainbow-quox/back/vitiligo4.webp b/rainbow-quox/back/vitiligo4.webp new file mode 100644 index 0000000..5ccc13d --- /dev/null +++ b/rainbow-quox/back/vitiligo4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dabdb0e89303ef963a74f864301a9e501377fe2521e7856fcdc569a7ca53dc79 +size 5824 diff --git a/rainbow-quox/canvas.ts b/rainbow-quox/canvas.ts index 8f989b6..86d90d3 100644 --- a/rainbow-quox/canvas.ts +++ b/rainbow-quox/canvas.ts @@ -12,83 +12,36 @@ export const allLayers: Layer[] = 'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', 'vitiligo3', 'vitiligo4', 'eyes', 'eyeshine', 'lines']; -export type Image = ImageData; -export type Images = Record; - -export type ComposedImages = Images & {comp?: ImageData}; - -export type Side = 'front' | 'back'; - -export function flip(s: Side): Side { - return s == 'front' ? 'back' : 'front'; +export function makeLayerInfo(f: (l: Layer) => A): Record { + return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record; } +export type Position = [x: number, y: number]; +export type Positions = Record; -let buffer = new OffscreenCanvas(WIDTH, HEIGHT); -let bufferCtx = buffer.getContext('2d')!; +export async function loadPos(side: SideName): Promise { + let req = new Request(`./${side}/pos.json`); + return fetch(req).then(resp => resp.json()); +} -type Positions = Record; +export type LayerInfo1 = {data: ImageData, pos: Position}; -const FRONT_POS: Positions = { - belly1: [187, 105], - 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], -}; - -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], +export type LayerInfo = { + layers: Record, + comp?: ImageData, }; type Rgb = [number, number, number]; type Rgbs = Record; +let rgbBuf = new OffscreenCanvas(1, 1).getContext('2d')!; + function toRgb(col: Color.Oklch): Rgb { // :) - const prev = bufferCtx.getImageData(0, 0, 1, 1); - - bufferCtx.save(); - 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); - + rgbBuf.fillStyle = col.css(); + rgbBuf.fillRect(0, 0, 1, 1); + const rgb = rgbBuf.getImageData(0, 0, 1, 1).data; 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 { - let data = img.data; +function setImageDataRgb({ data }: ImageData, [r, g, b]: Rgb, a: number = -1) { for (let i = 0; i < data.length; i += 4) { data[i] = r; data[i+1] = g; data[i+2] = b; + if (a >= 0) data[i+3]! *= a; } } -async function wait(img: HTMLImageElement): Promise { - if (img.complete) { - return new Promise((r, _) => r(img)); - } else { - return new Promise((r, _) => img.addEventListener('load', () => r(img))); - } +async function loadImage(url: string): Promise { + const img0 = new Image; img0.src = url; + const img: ImageBitmapSource = img0.complete ? img0 : + await new Promise(r => img0.addEventListener('load', () => r(img0))); + return createImageBitmap(img); } -async function load(side: Side, layer: Layer): Promise { +async function load(buf: OffscreenCanvasRenderingContext2D, + side: SideName, layer: Layer): Promise { if (layer == 'eyeshine') layer = 'eyes'; - let img = new Image; img.src = `./${side}/${layer}.png`; - let bmp = await createImageBitmap(await wait(img)); - bufferCtx.clearRect(0, 0, WIDTH, HEIGHT); // ? - bufferCtx.drawImage(bmp, 0, 0); - return bufferCtx.getImageData(0, 0, WIDTH, HEIGHT); + let bmp = await loadImage(`./${side}/${layer}.webp`); + return navigator.locks.request('buf', async () => { + buf.clearRect(0, 0, WIDTH, HEIGHT); // ? + buf.drawImage(bmp, 0, 0); + return buf.getImageData(0, 0, WIDTH, HEIGHT) + }); } -async function loadSide(side: Side): Promise { - const res: Partial = { }; - for (const l of allLayers) { res[l] = await load(side, l); } - return res as ComposedImages; +async function loadSide(buf: OffscreenCanvasRenderingContext2D, + side: SideName): Promise { + const layers: Partial> = { }; + 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; + console.log(images); + for (const l of allLayers) { + layers[l] = { data: images[l], pos: pos[l] }; + } + return { layers: layers as Record }; } -function setColors(cols: Rgbs, layers: ComposedImages): void { - bufferCtx.save(); - +function setColors(cols: Rgbs, info: LayerInfo): void { for (const l of allLayers) { if (l == 'static' || l == 'eyeshine') continue; - setImageDataRgb(cols[l], layers[l]); + setImageDataRgb(info.layers[l].data, cols[l]); } - - delete layers.comp; - - bufferCtx.restore(); + delete info.comp; } -async function compose(layers: Images, pos: Positions): Promise { - bufferCtx.save(); - bufferCtx.clearRect(0, 0, WIDTH, HEIGHT); +async function compose(buf: OffscreenCanvasRenderingContext2D, + { layers }: LayerInfo): Promise { + return navigator.locks.request('buf', async () => { + buf.save(); + buf.clearRect(0, 0, WIDTH, HEIGHT); - for (const l of allLayers) { - const [x, y] = pos[l]; - bufferCtx.globalCompositeOperation = - l == 'eyeshine' ? 'luminosity' : 'source-over'; - bufferCtx.drawImage(await createImageBitmap(layers[l]), x, y); - } + for (const l of allLayers) { + const [x, y] = layers[l].pos; + buf.globalCompositeOperation = + l == 'eyeshine' ? 'luminosity' : 'source-over'; + buf.drawImage(await createImageBitmap(layers[l].data), x, y); + } - let res = bufferCtx.getImageData(0, 0, WIDTH, HEIGHT); - bufferCtx.restore(); - - return res; + buf.restore(); + return buf.getImageData(0, 0, WIDTH, HEIGHT); + }); } async function redraw(ctx: CanvasRenderingContext2D, - layers: ComposedImages, pos: Positions) { - let data = layers.comp ??= await compose(layers, pos); + buf: OffscreenCanvasRenderingContext2D, + info: LayerInfo) { + let data = info.comp ??= await compose(buf, info); ctx.putImageData(data, 0, 0); } -async function loadingMessage(): Promise { - bufferCtx.save(); - bufferCtx.clearRect(0, 0, WIDTH, HEIGHT); - bufferCtx.font = 'bold 100px Muller, sans-serif'; - bufferCtx.textAlign = 'center'; - bufferCtx.fillText('loading layers…', WIDTH/2, HEIGHT/2); - let res = createImageBitmap(buffer); - bufferCtx.restore(); - return await res; +function message(msg: string, ctx: CanvasRenderingContext2D) { + ctx.save(); + ctx.clearRect(0, 0, WIDTH, HEIGHT); + ctx.font = 'bold 100px Muller, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(msg, WIDTH/2, HEIGHT/2); + ctx.restore(); } -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) { document.documentElement.dataset.running = name; } @@ -201,17 +148,38 @@ function finishAnim() { delete document.documentElement.dataset.running; } -function layers(): Images { return side == 'front' ? fronts : backs; } -function pos(): Positions { return side == 'front' ? FRONT_POS : BACK_POS; } +type SideName = 'front' | 'back'; +class Side { + cur: SideName = 'front'; + fronts: LayerInfo; + backs: LayerInfo; -async function recolorOn(ctx: CanvasRenderingContext2D) { - const cols = Color.colors(); - const rgbs = toRgbs(cols); - setColors(rgbs, fronts); - setColors(rgbs, backs); - await redraw(ctx, layers(), pos()); - return cols.outer.h; + private constructor(fronts: LayerInfo, backs: LayerInfo) { + this.fronts = fronts; this.backs = backs; + } + + static async init(buf: OffscreenCanvasRenderingContext2D) { + return new Side(await loadSide(buf, 'front'), await loadSide(buf, 'back')); + } + + 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) { @@ -219,56 +187,77 @@ function setBg(hue: number) { } -async function animateReroll(_e: Event, done: () => void) { - const duration = 400; - const hue = await recolorOn(pic2Ctx); - pic2.style.animation = `${duration}ms ease fade-in`; +async function animateReroll(ctx1: CanvasRenderingContext2D, + ctx2: CanvasRenderingContext2D, + buf: OffscreenCanvasRenderingContext2D, + 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); setTimeout(finish, duration); async function finish() { - await redraw(picCtx, layers(), pos()).then(() => { - pic2.style.removeProperty('animation'); - pic2.style.removeProperty('opacity'); + await redraw(ctx1, buf, side.layers()).then(() => { + ctx2.canvas.style.removeProperty('animation'); + ctx2.canvas.style.removeProperty('opacity'); done(); }); } } -function animateSwap(_e: Event, done: () => void) { - const duration = 1000; - pic.style.animation = `${duration}ms ease swap`; - setTimeout(swapImage, duration/2); +async function animateSwap(picCtx: CanvasRenderingContext2D, + pic2Ctx: CanvasRenderingContext2D, + side: Side, + 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); - async function swapImage() { - side = flip(side); - await redraw(picCtx, layers(), pos()); + function swap() { + let data = pic2Ctx.getImageData(0, 0, WIDTH, HEIGHT); + picCtx.putImageData(data, 0, 0); } function finish() { - pic.style.removeProperty('animation'); + picCtx.canvas.style.removeProperty('animation'); + pic2Ctx.canvas.style.removeProperty('animation'); done(); } } + document.addEventListener('DOMContentLoaded', async function() { - pic = document.getElementById('pic') as HTMLCanvasElement; - picCtx = pic.getContext('2d')!; + let pic = document.getElementById('pic') as HTMLCanvasElement; + let picCtx = pic.getContext('2d')!; - pic2 = document.getElementById('pic2') as HTMLCanvasElement; - pic2Ctx = pic2.getContext('2d')!; + let pic2 = document.getElementById('pic2') as HTMLCanvasElement; + let pic2Ctx = pic2.getContext('2d')!; - picCtx.drawImage(await loadingMessage(), 0, 0); + let buf = new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; - fronts = await loadSide('front'); - backs = await loadSide('back'); - side = 'front'; + message('loading layers…', picCtx); - 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); - await redraw(picCtx, layers(), pos()); + await redraw(picCtx, buf, side.layers()); const reroll = document.getElementById('reroll')!; const swap = document.getElementById('swap')!; @@ -285,19 +274,25 @@ document.addEventListener('DOMContentLoaded', async function() { swap.removeEventListener('click', handleSwap); } - function handleReroll(e: Event) { wrap(reroll, animateReroll, 'reroll', e); } - function handleSwap(e: Event) { wrap(swap, animateSwap, 'swap', e); } + function handleReroll(e: Event) { + 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, - name: string, e: Event) { + type Handler = (finish: () => void) => void; + + function wrap(elem: Element, f: Handler, name: string, e: Event) { if (elem != e.target) return; e.stopPropagation(); if (isRunning()) return; removeListeners(); startAnim(name); - f(e, () => { finishAnim(); addListeners(); }); + f(() => { finishAnim(); addListeners(); }); } }); diff --git a/rainbow-quox/color.ts b/rainbow-quox/color.ts index 337a33f..9de056b 100644 --- a/rainbow-quox/color.ts +++ b/rainbow-quox/color.ts @@ -1,16 +1,131 @@ 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(...xs: A[]): A { + return xs[Math.floor(rand() * xs.length)]!; +} + const max = Math.max; 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'; +namespace Oklch { + export type Channel = 'l' | 'c' | 'h'; + export type Channels = Record; + export type ChannelMap = (x: number) => number; + export type ChannelMaps = Record; +} + +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>): 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 MINL = 0.4; const MINL_LIGHT = 0.7; @@ -34,89 +149,6 @@ const MAXH_COMPL = 40; // size of the wedge a "triadic" color can be in 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(...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 OuterLayer = 'outer' | 'spines' | 'vitiligo1'; @@ -148,7 +180,7 @@ export function makeColorInfo(f: (l: Layer) => A): Record { export function colors(): Scheme { - const outer = baseOklch('dark'); + const outer = new Oklch('dark'); let outerCols: OuterCols = { outer, spines: mkSpines(outer), vitiligo1: mkVitiligo(outer) }; @@ -159,16 +191,16 @@ export function colors(): Scheme { const whichBody = rand(); if (whichBody > 2/3) { type = 'triad'; - const [f, b] = triad(outer.h); + const [f, b] = Oklch.triad(outer.h); finCols = mkFins(f, outer); bellyCols = mkBelly(b); } else if (whichBody > 1/3) { type = 'fin-belly'; - const [f, b] = complementary(outer.h, 2); + const [f, b] = Oklch.complementary(outer.h, 2); finCols = mkFins(f!, outer); bellyCols = mkBelly(b!); } else { type = 'fin-body'; - finCols = mkFins(analogous1(outer.h), outer); - bellyCols = mkBelly(complementary1(outer.h)); + finCols = mkFins(Oklch.analogous1(outer.h), outer); + bellyCols = mkBelly(Oklch.complementary1(outer.h)); } let miscCols = mkMisc(outerCols, finCols, bellyCols); @@ -178,80 +210,88 @@ export function colors(): Scheme { function mkSpines(outer: Oklch): Oklch { - return { - l: outer.l * 0.8, c: outer.c * 1.1, - h: randBetween(outer.h + 12, outer.h - 12) - }; + return outer.with({ + l: x => x * 0.8, + c: x => x * 1.1, + h: x => randBetween(x + 12, x - 12), + }) } function mkVitiligo(outer: Oklch): Oklch { - return { - l: randBetween(max(outer.l, 0.94), 0.985), // exception to MAXL - c: randBetween(min(outer.c, 0.1), MINC_LIGHT), - h: outer.h - }; + return outer.with({ + l: x => randBetween(max(x, 0.94), 0.985), // exception to MAXL + c: x => randBetween(min(x, 0.1), MINC_LIGHT), + }); } function mkStripes(): Oklch { - return { + return new Oklch({ l: randBetween(0.8, MAXL), c: randBetween(MINC_LIGHT, MAXC_LIGHT), h: rand() * 360 - }; + }); } function mkCuffs(sock: Oklch): Oklch { - return { - l: randBetween(sock.l * 0.85, sock.l * 0.65), - c: randBetween(sock.c, MAXC_LIGHT), - h: randBetween(sock.h + 8, sock.h - 8) - }; + return sock.with({ + l: x => randBetween(x * 0.85, x * 0.65), + c: x => randBetween(x, MAXC_LIGHT), + h: x => randBetween(x + 8, x - 8), + }); } function mkFins(h: number, outer: Oklch): FinCols { - const [fin1Hue, fin2Hue, fin3Hue] = analogous(h, 3); - const [ll, cc] = oneOf([lightFor, dullFor], [darkFor, brightFor]); - const fins1 = { l: ll(outer.l), c: cc(outer.l, outer.c), h: fin1Hue! }; - const fins2 = { l: ll(fins1.l), c: cc(fins1.l, fins1.c), h: fin2Hue! }; - const fins3 = { l: ll(fins2.l), c: cc(fins2.l, fins2.c), h: fin3Hue! }; + const [fin1Hue, fin2Hue, fin3Hue] = Oklch.analogous(h, 3); + const [ll, cc] = oneOf( + [Oklch.lightFor, Oklch.dullFor], [Oklch.darkFor, Oklch.brightFor]); + + 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); return { fins1, fins2, fins3, vitiligo4 }; } function mkBelly(h: number): BellyCols { - const [belly1Hue, belly2Hue] = analogous(h, 2); - const belly1 = - { l: randBetween(0.7, MAXL), c: baseChroma(1), h: belly1Hue! }; - const belly2 = - { l: min(MAXL, belly1.l * 1.1), c: belly1.c * 0.9, h: belly2Hue! }; + const [belly1Hue, belly2Hue] = Oklch.analogous(h, 2); + const belly1 = new Oklch({ + l: randBetween(0.7, MAXL), + c: Oklch.baseChroma(1), + 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 vitiligo2 = mkVitiligo(belly2); return { belly1, belly2, vitiligo2, vitiligo3 }; } function mkMisc(o: OuterCols, f: FinCols, b: BellyCols): MiscCols { - const masks = { + const masks = new Oklch({ l: randBetween(0.8, MAXL), 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 { masks, - eyes: { - l: baseLuma('light'), + eyes: new Oklch({ + l: Oklch.baseLuma('light'), c: randBetween(0.28, MAXC_LIGHT), - h: oneOf(analogous1, complementary1)(o.outer.h) - }, - claws: { - l: min(MAXL, masks.l + randBetween(0, 0.1)), + h: oneOf(Oklch.analogous1, Oklch.complementary1)(o.outer.h) + }), + claws: masks.with({ + l: x => min(MAXL, x + randBetween(0, 0.1)), c: randBetween(0.01, 0.06), - h: analogous1(masks.h) - }, - lines: { + h: Oklch.analogous1, + }), + lines: new Oklch({ l: randBetween(0.01, 0.06), - c: baseChroma(0), - h: analogous1(o.outer.h) - } + c: Oklch.baseChroma(0), + h: Oklch.analogous1(o.outer.h) + }), }; } diff --git a/rainbow-quox/front/belly1.png b/rainbow-quox/front/belly1.png deleted file mode 100644 index 778fc65..0000000 --- a/rainbow-quox/front/belly1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:018fa612600c9819e2d5256e9a3f11d025cee9ee544e7a6e05f2c08cb0d01c64 -size 32398 diff --git a/rainbow-quox/front/belly1.webp b/rainbow-quox/front/belly1.webp new file mode 100644 index 0000000..c04d004 --- /dev/null +++ b/rainbow-quox/front/belly1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7a0574268f1913792486b092a12961b6e5b10fdb21a5e48be527dc1b63a785b +size 9078 diff --git a/rainbow-quox/front/belly2.png b/rainbow-quox/front/belly2.png deleted file mode 100644 index e4a8188..0000000 --- a/rainbow-quox/front/belly2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec027a0197521656c8d4a6a49a31abb4847dbbf9de6e29cdbd9b329bb8c959ce -size 33178 diff --git a/rainbow-quox/front/belly2.webp b/rainbow-quox/front/belly2.webp new file mode 100644 index 0000000..f420bde --- /dev/null +++ b/rainbow-quox/front/belly2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b36476275d883bfe11a6287310166a9c6cbfcbd534f415c46dda598f7c26a16 +size 14686 diff --git a/rainbow-quox/front/claws.png b/rainbow-quox/front/claws.png deleted file mode 100644 index dbb913d..0000000 --- a/rainbow-quox/front/claws.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5ea380fb19422ab0b375b24839ecb70140cc7bb73035c9b3ffbb62d937b61497 -size 21353 diff --git a/rainbow-quox/front/claws.webp b/rainbow-quox/front/claws.webp new file mode 100644 index 0000000..861ddbd --- /dev/null +++ b/rainbow-quox/front/claws.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e220c631d24393db8328b2a5cadf1aed5102fec789dbfa3aab40b7bbc92794c +size 8714 diff --git a/rainbow-quox/front/cuffs.png b/rainbow-quox/front/cuffs.png deleted file mode 100644 index 1871a7b..0000000 --- a/rainbow-quox/front/cuffs.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7c8949d75a69041e6b5375e76901dcd9a023f7aafe9ca287fe63d558837dcc9a -size 23718 diff --git a/rainbow-quox/front/cuffs.webp b/rainbow-quox/front/cuffs.webp new file mode 100644 index 0000000..b8e849b --- /dev/null +++ b/rainbow-quox/front/cuffs.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8f44bf4243d67aea2fc32b905b919afa00e8ecad227425e816b67269260ca61 +size 8304 diff --git a/rainbow-quox/front/eyes.png b/rainbow-quox/front/eyes.png deleted file mode 100644 index 121d181..0000000 --- a/rainbow-quox/front/eyes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:575412468db799eb7dad082cf2c3e5bc1d035da19c99ada2d6a1a4b6d18231e5 -size 2543 diff --git a/rainbow-quox/front/eyes.webp b/rainbow-quox/front/eyes.webp new file mode 100644 index 0000000..db3c69c --- /dev/null +++ b/rainbow-quox/front/eyes.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c407db7a06688de206e8edfff49a6164c4a0ac62dab42c780befa80fb100e3f6 +size 1890 diff --git a/rainbow-quox/front/fins1.png b/rainbow-quox/front/fins1.png deleted file mode 100644 index 427f23a..0000000 --- a/rainbow-quox/front/fins1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f94136fa4f440cd0a7f437aa09ec10ad4a743234364434f3639d50c623fe6740 -size 13888 diff --git a/rainbow-quox/front/fins1.webp b/rainbow-quox/front/fins1.webp new file mode 100644 index 0000000..967f769 --- /dev/null +++ b/rainbow-quox/front/fins1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7259534ef0f8bc9857a89f11bffcbad8b65f6f54e17de4c43542278bf912847 +size 6438 diff --git a/rainbow-quox/front/fins2.png b/rainbow-quox/front/fins2.png deleted file mode 100644 index b31282f..0000000 --- a/rainbow-quox/front/fins2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3b2b0d1fb34210e4306ed6d17282b8e88617114810108e0905dcb29857f8d19 -size 14569 diff --git a/rainbow-quox/front/fins2.webp b/rainbow-quox/front/fins2.webp new file mode 100644 index 0000000..8c337fb --- /dev/null +++ b/rainbow-quox/front/fins2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:142905b5ec5eace81ecbfa9a93482ae8672e55b7962b9c698b9b02fb14543c1e +size 5988 diff --git a/rainbow-quox/front/fins3.png b/rainbow-quox/front/fins3.png deleted file mode 100644 index feac6ff..0000000 --- a/rainbow-quox/front/fins3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81d82d665ca0b355af4166f1cc0966e67212418e334915dad24ada05516b006d -size 4093 diff --git a/rainbow-quox/front/fins3.webp b/rainbow-quox/front/fins3.webp new file mode 100644 index 0000000..c70fded --- /dev/null +++ b/rainbow-quox/front/fins3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50722592a90c4d8d562eefd5376ac1bd7d07edc72592789fe3b20b24bc06f580 +size 2298 diff --git a/rainbow-quox/front/lines.png b/rainbow-quox/front/lines.png deleted file mode 100644 index c53cdf4..0000000 --- a/rainbow-quox/front/lines.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:889efb643e0688f235bf0284b9492efa7ecc079b804c82d808fdb91a12c9d4fe -size 179826 diff --git a/rainbow-quox/front/lines.webp b/rainbow-quox/front/lines.webp new file mode 100644 index 0000000..7e7046f --- /dev/null +++ b/rainbow-quox/front/lines.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37a4f97523109c4b64f528bdf4eb13bce9e0d91da23ac653e449db4e82b0ed40 +size 79804 diff --git a/rainbow-quox/front/masks.png b/rainbow-quox/front/masks.png deleted file mode 100644 index 5a0ee6b..0000000 --- a/rainbow-quox/front/masks.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e37e416635756c263a96913e47913a3686a095a3b01f81a5283787a71e89e4b -size 10878 diff --git a/rainbow-quox/front/masks.webp b/rainbow-quox/front/masks.webp new file mode 100644 index 0000000..07a035d --- /dev/null +++ b/rainbow-quox/front/masks.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:326b64b92fc4ccf34d36c39472eac5a8c0b484eb7450af637d2551b92ac81f9c +size 4848 diff --git a/rainbow-quox/front/outer.png b/rainbow-quox/front/outer.png deleted file mode 100644 index 3323174..0000000 --- a/rainbow-quox/front/outer.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a9e7ab5efdee557acd9bb8bcbeeefbcbeda4c31071185b1b0d6f8a26da879fc4 -size 34875 diff --git a/rainbow-quox/front/outer.webp b/rainbow-quox/front/outer.webp new file mode 100644 index 0000000..e070513 --- /dev/null +++ b/rainbow-quox/front/outer.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2abf9b569e88b52b61cadbcf882ead5c8b06128f31c9495f0e50b13b28778aec +size 13222 diff --git a/rainbow-quox/front/pos.json b/rainbow-quox/front/pos.json new file mode 100644 index 0000000..24c65a6 --- /dev/null +++ b/rainbow-quox/front/pos.json @@ -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] +} diff --git a/rainbow-quox/front/spines.png b/rainbow-quox/front/spines.png deleted file mode 100644 index a0a8a0c..0000000 --- a/rainbow-quox/front/spines.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b43c12e8e6c6b6edbdf6ea35f04151f23b26c67a624e86497063b12c6fa4a4cc -size 11263 diff --git a/rainbow-quox/front/spines.webp b/rainbow-quox/front/spines.webp new file mode 100644 index 0000000..cba0a90 --- /dev/null +++ b/rainbow-quox/front/spines.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d41ce8b4cf1ff2f05abfc2b001028f9db29e9c25442607657373b4008b82233 +size 5240 diff --git a/rainbow-quox/front/static.png b/rainbow-quox/front/static.png deleted file mode 100644 index 3ba9e0a..0000000 --- a/rainbow-quox/front/static.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d31dd489e752b5d9fbfb33c67199ab6fe49778fb9d976c06b3695f2bd91ca37b -size 43641 diff --git a/rainbow-quox/front/static.webp b/rainbow-quox/front/static.webp new file mode 100644 index 0000000..49c8711 --- /dev/null +++ b/rainbow-quox/front/static.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:747a5d5b456c54ae31e2eda3279be1e6782b3700133c58e130cc86cee0c4a565 +size 23672 diff --git a/rainbow-quox/front/stripes.png b/rainbow-quox/front/stripes.png deleted file mode 100644 index 39288a6..0000000 --- a/rainbow-quox/front/stripes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6efe51d45898de27318186ab9a945e434b64c9fccad4d0fe6f3266463916d0f6 -size 43540 diff --git a/rainbow-quox/front/stripes.webp b/rainbow-quox/front/stripes.webp new file mode 100644 index 0000000..cadc60f --- /dev/null +++ b/rainbow-quox/front/stripes.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1327c6808599d2f7a48069cc0171a21213318383a2019f6178a6a092112313b +size 22274 diff --git a/rainbow-quox/front/vitiligo1.png b/rainbow-quox/front/vitiligo1.png deleted file mode 100644 index 494f004..0000000 --- a/rainbow-quox/front/vitiligo1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:363be71bf2164cffb8f91562bf5913591d73293158f922a5ed9a1917fafb7471 -size 21373 diff --git a/rainbow-quox/front/vitiligo1.webp b/rainbow-quox/front/vitiligo1.webp new file mode 100644 index 0000000..d62fe8f --- /dev/null +++ b/rainbow-quox/front/vitiligo1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32f7fe2f08b927c52068039792dd894ff449de8838774b5609a7d62129c2895a +size 8260 diff --git a/rainbow-quox/front/vitiligo2.png b/rainbow-quox/front/vitiligo2.png deleted file mode 100644 index fe6cfc6..0000000 --- a/rainbow-quox/front/vitiligo2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:935543bd4940a2268ba66bc2cb26859fc42368d5e480ed302c14c679a0c3ae1f -size 5993 diff --git a/rainbow-quox/front/vitiligo2.webp b/rainbow-quox/front/vitiligo2.webp new file mode 100644 index 0000000..ffdb985 --- /dev/null +++ b/rainbow-quox/front/vitiligo2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a67f1fe1cfb109e2928efadf95c26fdccc65dad09af2d2f1df60db80374757d5 +size 2108 diff --git a/rainbow-quox/front/vitiligo3.png b/rainbow-quox/front/vitiligo3.png deleted file mode 100644 index 16942ab..0000000 --- a/rainbow-quox/front/vitiligo3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:072c8876a5569801d89db446615f81e3262efd0c6355311b57a4df621b23070f -size 2421 diff --git a/rainbow-quox/front/vitiligo3.webp b/rainbow-quox/front/vitiligo3.webp new file mode 100644 index 0000000..7ca5a11 --- /dev/null +++ b/rainbow-quox/front/vitiligo3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ec9edac7aaa5ba1125929e711a47dcb004c9e4f65d62820cc30e9cb19fff056 +size 1036 diff --git a/rainbow-quox/front/vitiligo4.png b/rainbow-quox/front/vitiligo4.png deleted file mode 100644 index e1246b9..0000000 --- a/rainbow-quox/front/vitiligo4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c07b8c4234de1e9a03ffecd54e3805f925d2f5f110181ed0c9f467051c626220 -size 3465 diff --git a/rainbow-quox/front/vitiligo4.webp b/rainbow-quox/front/vitiligo4.webp new file mode 100644 index 0000000..aaca515 --- /dev/null +++ b/rainbow-quox/front/vitiligo4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb16c83441c0262ae9a37f8b30c849c6a01f403b88419d3a8e0d4e4f3ea54319 +size 1794 diff --git a/rainbow-quox/kra/back.kra b/rainbow-quox/kra/back.kra index a01a414..de27925 100644 --- a/rainbow-quox/kra/back.kra +++ b/rainbow-quox/kra/back.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec8adde036f86be12b8517ff1cdc227ad3dcaaf9e0a0e9e186d060d503fac573 -size 11558980 +oid sha256:30c8f371a2ea5c37b882c14726909a5cdc0626306ad563f19e1d6243f6f95152 +size 10709057 diff --git a/rainbow-quox/kra/front.kra b/rainbow-quox/kra/front.kra index 7fd8554..2dfbb5f 100644 --- a/rainbow-quox/kra/front.kra +++ b/rainbow-quox/kra/front.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:579ca5af9a79a8577f62013418f58c729c646cfda9f2fd2bed54688b59de2ddd -size 11346742 +oid sha256:047b1eb6e703a2c8d67d6ea3a83b942a8636da501b924d8d6a261f3c2f5f1544 +size 11122381 diff --git a/rainbow-quox/style.css b/rainbow-quox/style.css index b5316c5..10c04ba 100644 --- a/rainbow-quox/style.css +++ b/rainbow-quox/style.css @@ -12,12 +12,17 @@ * { transition: none; } } -@keyframes swap { +@keyframes swap1 { 0% { transform: none; } 49% { transform: translateX(-150vw); } 50% { content-visibility: hidden; } - 51% { transform: translateX(150vw); content-visibility: visible; } - 100% { transform: none; } + 100% { content-visibility: hidden; 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 { From 815ef5c23f7564ef5d9f6f6e5d156f54b8c9dfb7 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 8 Dec 2024 03:32:47 +0100 Subject: [PATCH 02/19] palette --- Makefile | 6 +- rainbow-quox/back/pos.json | 4 +- rainbow-quox/back/vitiligo2.webp | 4 +- rainbow-quox/back/vitiligo3.webp | 4 +- rainbow-quox/canvas.ts | 95 +++++++++++++++++------ rainbow-quox/color.ts | 6 +- rainbow-quox/index.html | 15 +++- rainbow-quox/kra/back.kra | 4 +- rainbow-quox/palette.svg.raku | 125 +++++++++++++++++++++++++++++++ rainbow-quox/style.css | 124 ++++++++---------------------- 10 files changed, 256 insertions(+), 131 deletions(-) create mode 100644 rainbow-quox/palette.svg.raku diff --git a/Makefile b/Makefile index 112913d..a0a28c0 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ 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/front/*) $(wildcard rainbow-quox/back/*) \ + rainbow-quox/palette.svg SCRIPTS = $(patsubst %.ts,%.js,$(wildcard script/*.ts rainbow-quox/*.ts)) MISC = $(shell find .well-known -type f) ALL = $(CSS) $(PAGES) $(MEDIA) $(SCRIPTS) $(MISC) @@ -59,6 +60,9 @@ $(BUILDDIR)/%.js: %.ts --lib dom,es2023 --target es2015 \ --outDir $(dir $@) $^ +$(BUILDDIR)/rainbow-quox/palette.svg: rainbow-quox/palette.svg.raku + raku $^ $@ + clean: $(RM) -r $(TMPDIR) $(BUILDDIR) diff --git a/rainbow-quox/back/pos.json b/rainbow-quox/back/pos.json index f93e41b..21db123 100644 --- a/rainbow-quox/back/pos.json +++ b/rainbow-quox/back/pos.json @@ -15,7 +15,7 @@ "static": [219, 41], "stripes": [219, 221], "vitiligo1": [4, 22], - "vitiligo2": [46, 48], - "vitiligo3": [101, 134], + "vitiligo2": [102, 134], + "vitiligo3": [46, 129], "vitiligo4": [221, 56] } diff --git a/rainbow-quox/back/vitiligo2.webp b/rainbow-quox/back/vitiligo2.webp index d17c37b..0899891 100644 --- a/rainbow-quox/back/vitiligo2.webp +++ b/rainbow-quox/back/vitiligo2.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1db28390d6407495f8c0a0125db81f8d5e15d363463c04dc77748808fbd97eb7 -size 2632 +oid sha256:195c7aaf80305f17cbee871b59bf3f797628aafb09701567b22549ae68626f27 +size 2578 diff --git a/rainbow-quox/back/vitiligo3.webp b/rainbow-quox/back/vitiligo3.webp index 0899891..a891ca5 100644 --- a/rainbow-quox/back/vitiligo3.webp +++ b/rainbow-quox/back/vitiligo3.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:195c7aaf80305f17cbee871b59bf3f797628aafb09701567b22549ae68626f27 -size 2578 +oid sha256:d299e372195d499b57c8f804d51ccba438f73c6aaa2342bec863e1ff0f0e1ea8 +size 1556 diff --git a/rainbow-quox/canvas.ts b/rainbow-quox/canvas.ts index 86d90d3..5ffe952 100644 --- a/rainbow-quox/canvas.ts +++ b/rainbow-quox/canvas.ts @@ -38,7 +38,6 @@ type Rgbs = Record; let rgbBuf = new OffscreenCanvas(1, 1).getContext('2d')!; function toRgb(col: Color.Oklch): Rgb { - // :) rgbBuf.fillStyle = col.css(); rgbBuf.fillRect(0, 0, 1, 1); const rgb = rgbBuf.getImageData(0, 0, 1, 1).data; @@ -49,6 +48,14 @@ function toRgbs(col: Color.Colors): Rgbs { return Color.makeColorInfo(l => toRgb(col[l])); } +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)}`; +} + function setImageDataRgb({ data }: ImageData, [r, g, b]: Rgb, a: number = -1) { for (let i = 0; i < data.length; i += 4) { @@ -84,7 +91,6 @@ async function loadSide(buf: OffscreenCanvasRenderingContext2D, await Promise.all( allLayers.map(l => load(buf, side, l).then(res => [l, res]))) ) as Record; - console.log(images); for (const l of allLayers) { layers[l] = { data: images[l], pos: pos[l] }; } @@ -131,7 +137,7 @@ function message(msg: string, ctx: CanvasRenderingContext2D) { ctx.clearRect(0, 0, WIDTH, HEIGHT); ctx.font = 'bold 100px Muller, sans-serif'; ctx.textAlign = 'center'; - ctx.fillText(msg, WIDTH/2, HEIGHT/2); + ctx.fillText(msg, WIDTH/2, HEIGHT/2, WIDTH-10); ctx.restore(); } @@ -167,13 +173,14 @@ class Side { layers() { return this.cur == 'front' ? this.fronts : this.backs; } async recolorOn(ctx: CanvasRenderingContext2D, - buf: OffscreenCanvasRenderingContext2D) { + buf: OffscreenCanvasRenderingContext2D): + Promise<[Color.Colors, Rgbs]> { 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; + return [cols, rgbs]; } async ensureComposed(buf: OffscreenCanvasRenderingContext2D) { @@ -187,15 +194,53 @@ function setBg(hue: number) { } +function getPalette(): XMLDocument | null { + const palette = document.getElementById('palette') as HTMLObjectElement; + return palette.contentDocument; +} + +function setPalette(oklch: Color.Colors, rgb: Rgbs) { + const palette = getPalette(); + + if (!palette) { + setTimeout(() => setPalette(oklch, rgb), 500); + return; + } + + palette.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); + console.log(palette); + + for (const l of Color.allLayers) { + let col = toHex(rgb[l]); + + const swatch = palette.getElementById(`s-${l}`); + if (swatch) swatch.style.fill = col; + + const text = palette.getElementById(`c-${l}`); + if (text) text.innerHTML = col; + + const bg = palette.getElementById(`p-${l}`); + if (bg) bg.style.fill = col; + + const item = palette.getElementById(`i-${l}`); + if (item) { + const textCol = oklch[l].l < 0.6 ? 'white' : 'black'; + item.style.setProperty('--text', textCol); + } + + } +} + + async function animateReroll(ctx1: CanvasRenderingContext2D, ctx2: CanvasRenderingContext2D, buf: OffscreenCanvasRenderingContext2D, - side: Side, - done: () => void) { + side: Side, done: () => void) { const duration = 200; - const hue = await side.recolorOn(ctx2, buf); + const [oklch, rgb] = await side.recolorOn(ctx2, buf); ctx2.canvas.style.animation = `${duration}ms ease fade-in`; - setBg(hue); + setBg(oklch.outer.h); + setPalette(oklch, rgb); setTimeout(finish, duration); async function finish() { @@ -255,8 +300,9 @@ document.addEventListener('DOMContentLoaded', async function() { throw err; }); - let hue = await side.recolorOn(picCtx, buf); - setBg(hue); + let [oklch, rgb] = await side.recolorOn(picCtx, buf); + setBg(oklch.outer.h); + setPalette(oklch, rgb); await redraw(picCtx, buf, side.layers()); const reroll = document.getElementById('reroll')!; @@ -264,6 +310,19 @@ document.addEventListener('DOMContentLoaded', async function() { addListeners(); + type Done = (finish: () => void) => void; + + function handleReroll(e: Event) { + wrap(reroll, + done => animateReroll(picCtx, pic2Ctx, buf, side, done), + 'reroll', e); + } + + function handleSwap(e: Event) { + wrap(swap, done => animateSwap(picCtx, pic2Ctx, side, buf, done), + 'swap', e); + } + function addListeners() { reroll.addEventListener('click', handleReroll); swap.addEventListener('click', handleSwap); @@ -274,19 +333,7 @@ document.addEventListener('DOMContentLoaded', async function() { swap.removeEventListener('click', handleSwap); } - function handleReroll(e: Event) { - wrap(reroll, done => animateReroll(picCtx, pic2Ctx, buf, side, done), - 'reroll', e); - } - - function handleSwap(e: Event) { - wrap(swap, done => animateSwap(picCtx, pic2Ctx, side, buf, done), - 'swap', e); - } - - type Handler = (finish: () => void) => void; - - function wrap(elem: Element, f: Handler, name: string, e: Event) { + function wrap(elem: Element, f: Done, name: string, e: Event) { if (elem != e.target) return; e.stopPropagation(); if (isRunning()) return; diff --git a/rainbow-quox/color.ts b/rainbow-quox/color.ts index 9de056b..b712e0d 100644 --- a/rainbow-quox/color.ts +++ b/rainbow-quox/color.ts @@ -108,7 +108,11 @@ export class Oklch { } css(alpha: number = 1): string { - return `oklch(${this.l} ${this.c} ${this.h} / ${alpha})`; + 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})`; } } with(lch: Partial>): Oklch { diff --git a/rainbow-quox/index.html b/rainbow-quox/index.html index 67e269f..45885bf 100644 --- a/rainbow-quox/index.html +++ b/rainbow-quox/index.html @@ -8,6 +8,11 @@ +
+ + +
+
if the canvas isn't working, @@ -17,11 +22,13 @@
-
- - +
+ + there should be a palette here but it failed to load for some reason +
-
diff --git a/rainbow-quox/palette.svg.raku b/rainbow-quox/palette.svg.raku index 416fb73..ac8e7c9 100644 --- a/rainbow-quox/palette.svg.raku +++ b/rainbow-quox/palette.svg.raku @@ -10,8 +10,12 @@ my $doc = q:to/EOP/; - EOP - -sub make-item($layer, :$display = $layer, :$use-vit?, :$vit?) { ... } - -make-item 'outer', upper => 'vitiligo1'; -make-item 'spines', use => 'vitiligo1'; -make-item 'stripes', upper => 'cuffs', display => 'socks'; -make-item 'belly1', upper => 'vitiligo3'; -make-item 'belly2', upper => 'vitiligo2'; -make-item 'fins1', upper => 'vitiligo4'; -make-item 'fins2', use => 'vitiligo4'; -make-item 'fins3', use => 'vitiligo4'; -make-item 'masks'; -make-item 'claws'; -make-item 'eyes'; - -$doc ~= ''; - -$out.IO.spurt: $doc; - - - - -sub make-item($layer, :$display = $layer, :$use?, :$upper?) { - state $index = 0; - my $str = - qq[\n]; - with $upper { - $str ~= qq{ - - - }; - } orwith $use { - $str ~= qq{ - - - }; - } else { - $str ~= qq{ }; - } - - $str ~= qq{ - - }; - - with $upper { - $str ~= qq{ - - $display - - - #?????? - - - #?????? - - }; - } orwith $use { - $str ~= qq{ - - $layer - - - #?????? - - - }; - } else { - $str ~= qq{ - - $layer - - - #?????? - - }; - } - $str ~= qq{ }; - - $index++; - - $doc ~= $str -} diff --git a/rainbow-quox/quox.ts b/rainbow-quox/quox.ts new file mode 100644 index 0000000..b2c51de --- /dev/null +++ b/rainbow-quox/quox.ts @@ -0,0 +1,378 @@ +import * as Color from './color.js'; + +type State = Color.Rand.State; + + +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.src = url; + return createImageBitmap(await img); +} + + +type Buffer = OffscreenCanvasRenderingContext2D; + +function dataViaBuffer(bmp: ImageBitmap, buf: Buffer): ImageData { + buf.clearRect(0, 0, bmp.width, bmp.height); + buf.drawImage(bmp, 0, 0); + 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); +} + +function loadImageData(url: string, buf?: Buffer): Promise { + if (buf && navigator.locks) return loadDataLocking(url, buf); + else return loadDataFresh(url); +} + +const WIDTH = 1000; +const HEIGHT = 673; + +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) makeBuffer(width, height); + else return undefined; +} + +export type Layer = 'static' | 'eyeshine' | Color.Layer; + +// in compositing order +export const allLayers: Layer[] = + ['static', 'outer', 'spines', 'stripes', 'cuffs', 'fins1', 'fins2', 'fins3', + 'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', 'vitiligo3', + 'vitiligo4', 'eyes', 'eyeshine', 'lines']; + +function makeLayerInfo(f: (l: Layer) => A): Record { + return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record; +} + +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); +} + + +function loadLayers(dir: string): Promise> { + let buf = makeBufferIfLocks(WIDTH, HEIGHT); + return makeLayerInfoAsync(l => loadImageData(`./${dir}/${l}.webp`, buf)); +} + + + +type Position = [x: number, y: number]; +type Positions = Record; + +async function loadPos(dir: string): Promise { + return (await fetch(`./${dir}/pos.json`)).json(); +} + + +type Side = 'front' | 'back'; + +function swapSide(s: Side): Side { + return s == 'front' ? 'back' : 'front'; +} + +type SideData = Record; + +type LayerData = { + front: SideData, back: SideData, + frontImage?: ImageData, backImage?: ImageData, +}; + +type ComposedData = Required; + +async function loadData(): Promise { + let [fl, fp, bl, bp] = await Promise.all([ + loadLayers('front'), loadPos('front'), + loadLayers('back'), loadPos('back') + ]); + return { + front: makeLayerInfo(l => [fl[l], fp[l]]), + back: makeLayerInfo(l => [bl[l], bp[l]]), + } +} + + +function singleColor({ data }: ImageData, { r, g, b }: Color.Rgb) { + for (let i = 0; i < data.length; i += 4) { + data[i] = r; data[i+1] = g; data[i+2] = b; + } +} + +async function recolorLayers(layers: LayerData, cols: Color.Rgbs) { + await Promise.all(Color.allLayers.map(l => { + singleColor(layers.front[l][0], cols[l]); + singleColor(layers.back[l][0], cols[l]); + })); + delete layers.frontImage; delete layers.backImage; +} + +type ComposeLayer = [ImageData, Position, GlobalCompositeOperation]; + +async function compose(buf: Buffer, layers: ComposeLayer[], + width: number, height: number): Promise { + buf.save(); + buf.clearRect(0, 0, width, height); + const bmps = await Promise.all(layers.map(async ([l, [x, y], m]) => + [await createImageBitmap(l), x, y, m] as const)); + for (const [bmp, x, y, m] of bmps) { + buf.globalCompositeOperation = m; + buf.drawImage(bmp, x, y); + } + buf.restore(); + return buf.getImageData(0, 0, width, height); +} + +async function +ensureComposed(buf: Buffer, data: LayerData): Promise { + let { front, back } = data; + data.frontImage ??= await composeLayers(front); + data.backImage ??= await composeLayers(back); + return data as ComposedData; + + function composeLayers(sdata: SideData): Promise { + return compose(buf, allLayers.map(l => makeLayer(l, sdata)), WIDTH, HEIGHT); + } + function makeLayer(l: Layer, sdata: SideData): ComposeLayer { + let [i, p] = sdata[l]; + return [i, p, l == 'eyeshine' ? 'luminosity' : 'source-over']; + } +} + + +async function redraw(ctx: CanvasRenderingContext2D, + buf: Buffer, data: ComposedData, side: Side) { + await ensureComposed(buf, data); + ctx.putImageData(data[`${side}Image`], 0, 0); +} + + +function message(msg: string, error = false) { + const ctx = getCanvasCtx('main'); + const size = error ? 30 : 100; + ctx.save(); + ctx.clearRect(0, 0, WIDTH, HEIGHT); + ctx.font = `bold ${size}px Muller, sans-serif`; + ctx.textAlign = 'center'; + ctx.fillText(msg, WIDTH/2, HEIGHT/2, WIDTH-10); + ctx.restore(); +} + + +function urlState(): State | undefined { + const str = document.location.hash; + if (str?.match(/^#\d+$/)) return parseInt(str.substring(1)); +} + +function updateUrl(state: State): void { + history.replaceState({}, '', `#${state}`); +} + +type ApplyStateOpts = + { side: Side, state: State, firstLoad: boolean, buf: Buffer, done: Done }; + +async function +applyState(data: LayerData, opts: Partial): Promise { + let { side, state, firstLoad, buf, done } = opts; + side ??= 'front'; + firstLoad ??= false; + buf ??= new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; + + let r = new Color.Rand(state); + const initState = r.state; + + const oklch = Color.colors(r); + const rgb = Color.toRgbs(oklch); + const newState = r.state; + + await recolorLayers(data, rgb); + + updateBg(oklch); + updatePalette(oklch, rgb); + updateLabel(initState); + + if (firstLoad) { + await instantUpdateImage(side, await ensureComposed(buf, data)); + } else { + updateUrl(initState); + await animateUpdateImage(buf, side, data, done ?? (() => {})); + } + + return newState; +} + +type CanvasId = 'main' | 'aux'; + +function getCanvasCtx(id: CanvasId) { + const canvas = document.getElementById(id) as HTMLCanvasElement; + return canvas.getContext('2d')!; +} + +async function +instantUpdateImage(side: Side, data: ComposedData) { + getCanvasCtx('main').putImageData(data[`${side}Image`], 0, 0); +} + +type Done = () => void; + +async function +animateUpdateImage(buf: Buffer, side: Side, data: LayerData, done: Done) { + const duration = 200; + + const main = getCanvasCtx('main'); + const aux = getCanvasCtx('aux'); + + document.documentElement.dataset.running = 'reroll'; + const cdata = await ensureComposed(buf, data); + redraw(aux, buf, cdata, side); + + aux.canvas.addEventListener('animationend', async () => { + await redraw(main, buf, cdata, side); + aux.canvas.style.removeProperty('animation'); + delete document.documentElement.dataset.running; + done(); + }); + + setTimeout(() => + aux.canvas.style.animation = `${duration}ms ease forwards fade-in` + ); +} + +async function +animateSwapImage(buf: Buffer, newSide: Side, data: ComposedData, done: Done) { + const duration = 400; + + const main = getCanvasCtx('main'); + const aux = getCanvasCtx('aux'); + + document.documentElement.dataset.running = 'swap'; + await redraw(aux, buf, data, newSide); + + aux.canvas.addEventListener('animationend', async () => { + const image = aux.getImageData(0, 0, WIDTH, HEIGHT); + main.putImageData(image, 0, 0); + + main.canvas.style.removeProperty('animation'); + aux.canvas.style.removeProperty('animation'); + delete document.documentElement.dataset.running; + done(); + }); + + main.canvas.style.animation = `${duration}ms ease swap1`; + aux.canvas.style.animation = `${duration}ms ease swap2`; +} + +function updateBg(cols: Color.Colors) { + document.documentElement.style.setProperty('--hue', `${cols.outer.h}`); +} + +function updateLabel(st: State) { + const stateLabel = document.getElementById('state'); + if (stateLabel) stateLabel.innerHTML = `${st}`; +} + +function updatePalette(oklch: Color.Colors, rgb: Color.Rgbs) { + const paletteObj = document.getElementById('palette') as HTMLObjectElement; + const palette = paletteObj.contentDocument as XMLDocument | null; + + if (!palette) return; + + palette.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); + const get = (id: string) => palette.getElementById(id); + + for (const layer of Color.allLayers) { + let col = rgb[layer].css(); + let elem; + + // main group + if (elem = get(`i-${layer}`)) { + if (oklch[layer].l < 0.6) { + elem.classList.add('light'); elem.classList.remove('dark'); + } else { + elem.classList.add('dark'); elem.classList.remove('light'); + } + elem.style.setProperty('--col', col); + } + + // label + if (elem = get(`c-${layer}`)) elem.innerHTML = col; + // minor swatch, if applicable + if (elem = get(`s-${layer}`)) elem.style.setProperty('--col', col); + } +} + + +async function setup() { + message('loading layers…'); + + let data = await loadData().catch(e => { message(e, true); throw e }); + + let buf = new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; + + let state = urlState(); + let side: Side = 'front'; + state = await applyState(data, { state, buf, firstLoad: true }); + + const reroll = document.getElementById('reroll')!; + const swap = document.getElementById('swap')!; + + addListeners(); + document.documentElement.dataset.ready = '1'; + + async function run(task: (k: Done) => Promise): Promise { + removeListeners(); + await task(addListeners); + } + + function updateFromUrl() { + run(async k => { + const newState = urlState(); + if (newState) { + state = await applyState(data, { side, state: newState, buf, done: k }); + } + }); + } + function runReroll() { + run(async k => { + state = await applyState(data, { side, state, buf, done: k }); + }); + } + function runSwap() { + run(async k => { + side = swapSide(side); + const cdata = await ensureComposed(buf, data); + await animateSwapImage(buf, side, cdata, k); + }); + } + + function addListeners() { + window.addEventListener('hashchange', updateFromUrl); + reroll.addEventListener('click', runReroll); + swap.addEventListener('click', runSwap); + } + function removeListeners() { + window.removeEventListener('hashchange', updateFromUrl); + reroll.removeEventListener('click', runReroll); + swap.removeEventListener('click', runSwap); + } +} + +document.addEventListener('DOMContentLoaded', setup); diff --git a/rainbow-quox/rand.ts b/rainbow-quox/rand.ts new file mode 100644 index 0000000..aae8bdb --- /dev/null +++ b/rainbow-quox/rand.ts @@ -0,0 +1,45 @@ +// https://stackoverflow.com/a/424445 thanks my dude + +export type State = number; + +const M = 0x80000000; +const A = 1103515245; +const C = 12345; + +export class Rand { + state: number; + + constructor(state?: State) { + this.state = typeof state == 'number' && !isNaN(state) ? + state : Math.floor(Math.random() * (M - 1)); + } + + #next(): number { return this.state = (A * this.state + C) % M; } + + #float(x?: number, y?: number): number { + const [lo, hi] = + x === undefined ? [0, 1] : + y === undefined ? [0, x] : + [Math.min(x, y), Math.max(x, y)]; + + return lo + this.#next() / (M - 1) * (hi - lo); + } + + int(): number; // whole int32 range + int(x: number): number; // [0, x) + int(x: number, y: number): number; // [x, y) + int(x?: number, y?: number): number { + return x === undefined ? this.#next() : Math.floor(this.#float(x, y)); + } + + float(): number; // [0, 1) + float(x: number): number; // [0, x) + float(x: number, y: number): number; // [x, y) + float(x?: number, y?: number): number { return this.#float(x, y); } + + choice(array: A[]): A { + return array[this.int(0, array.length)]!; + } + + boolean(): boolean { return this.float() > 0.5; } +} diff --git a/rainbow-quox/style.css b/rainbow-quox/style.css deleted file mode 100644 index 7818943..0000000 --- a/rainbow-quox/style.css +++ /dev/null @@ -1,148 +0,0 @@ -@import url(/fonts/muller/muller.css); - -@property --transition { - syntax: "*"; - inherits: true; - initial-value: none; -} - -* { box-sizing: border-box; } - -@media (prefers-reduced-motion: reduce) { - * { transition: none; } -} - -@keyframes swap1 { - 0% { transform: none; } - 49% { transform: translateX(-150vw); } - 50% { content-visibility: hidden; } - 100% { content-visibility: hidden; 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 { - from { opacity: 0 } - to { opacity: 1 } -} - -:root, body { - min-height: 100vh; - margin: 0; -} - -:root { - --hue: 300; - --c-hue: calc(180 + var(--hue)); - align-items: center; justify-content: center; - - --font: Muller, sans-serif; - font-family: var(--font); - - --shadow-color: 6px 6px 2px oklch(0.4 0.2 var(--hue) / 0.45); - @media (prefers-color-scheme: dark) { - --shadow-color: 6px 6px 2px oklch(0.1 0.15 var(--hue) / 0.45); - } - - color-scheme: light dark; - background: - url(bright-squares.png), - linear-gradient(to bottom in oklch, - oklch(0.9 0.08 var(--hue)), - oklch(0.7 0.10 var(--hue))); - background-blend-mode: screen; - - @media (prefers-color-scheme: dark) { - background: - url(bright-squares.png), - linear-gradient(to bottom in oklch, - oklch(0.3 0.08 var(--hue)), - oklch(0.2 0.09 var(--hue))); - } - - width: 100vw; - overflow-x: hidden; -} - -body { - display: flex; - flex-flow: column; - justify-content: space-between; -} - -#pic-holder, #pic, #pic2 { - width: min(1000px, 75vw); - margin: auto; - aspect-ratio: 2000/1346; - flex: 1; -} - -#pic-holder { position: relative; } -#pic, #pic2 { position: absolute; inset: 0; } - -#pic2 { - opacity: 0; - pointer-events: none; - z-index: 2; -} - -#buttons { - flex: none; - display: flex; - margin: 0 2rem 2.5rem; - align-items: start; - z-index: 1; - translate: -2em -6em; -} - -button { - --fg: var(--c-hue); - --bg: var(--hue); - font: 700 20pt var(--font); - background: oklch(0.5 0.2 var(--bg)); - color: oklch(0.95 0.075 var(--fg)); - border: 3px solid oklch(0.2 0.05 var(--bg)); - padding: 0.2em 4em 0.2em 0.5em; - transform-origin: center right; - box-shadow: var(--shadow-color); - transition: all 0.4s ease; - - [data-ready] & { transform: rotate(-60deg); } - - :root:not([data-running]) &:active, - [data-running=swap] &#swap, - [data-running=reroll] &#reroll { - transform: rotate(-60deg) translate(-1em); - } - - + button { margin-left: -6.5em; } -} - -#back { - position: absolute; - top: 22px; left: 22px; - - a { - color: oklch(0.4 0.15 var(--c-hue)); - text-decoration: 3px solid underline; - text-decoration-color: oklch(0.6 0.1 var(--c-hue)); - } - - @media (prefers-color-scheme: dark) { - color: oklch(0.9 0.19 var(--c-hue)); - } -} - -/* #palette-holder { */ -/* overflow: auto; */ -/* } */ - -#palette-holder { - width: min(90vw, 1126px); - margin: 2.5rem auto 0; -} -#palette { width: 100%; } diff --git a/rainbow-quox/style.scss b/rainbow-quox/style.scss new file mode 100644 index 0000000..cb056ba --- /dev/null +++ b/rainbow-quox/style.scss @@ -0,0 +1,142 @@ +@import url(/fonts/muller/muller.css); + +@keyframes swap1 { + 0% { transform: none; } + 49% { transform: translateX(-150vw); } + 50% { content-visibility: hidden; } + 100% { content-visibility: hidden; 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 { + from { opacity: 0 } + to { opacity: 1 } +} + + +@mixin transy { + transition: transform 0.25s cubic-bezier(.47,.74,.61,1.2); +} + +@mixin box { + font: 700 20pt var(--font); // respecify font family for diff --git a/rainbow-quox/make-palette/make-palette.hs b/rainbow-quox/make-palette/make-palette.hs index 55a7d08..f2ce368 100644 --- a/rainbow-quox/make-palette/make-palette.hs +++ b/rainbow-quox/make-palette/make-palette.hs @@ -13,7 +13,7 @@ main = do document :: Element document = - svg11_ (stylesheet "palette.css" <> foldMap makeItem (zip [0..] items)) + svg11_ (stylesheet "style/palette.css" <> foldMap makeItem (zip [0..] items)) `with` [Width_ <<- width, Height_ <<- "179", ViewBox_ <<- viewBox] where diff --git a/rainbow-quox/color.ts b/rainbow-quox/script/color.ts similarity index 100% rename from rainbow-quox/color.ts rename to rainbow-quox/script/color.ts diff --git a/rainbow-quox/quox.ts b/rainbow-quox/script/quox.ts similarity index 100% rename from rainbow-quox/quox.ts rename to rainbow-quox/script/quox.ts diff --git a/rainbow-quox/rand.ts b/rainbow-quox/script/rand.ts similarity index 100% rename from rainbow-quox/rand.ts rename to rainbow-quox/script/rand.ts diff --git a/rainbow-quox/palette.scss b/rainbow-quox/style/palette.scss similarity index 100% rename from rainbow-quox/palette.scss rename to rainbow-quox/style/palette.scss diff --git a/rainbow-quox/style.scss b/rainbow-quox/style/style.scss similarity index 100% rename from rainbow-quox/style.scss rename to rainbow-quox/style/style.scss From 303fd65bab5143a679feb759069da6eae67baf95 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Tue, 10 Dec 2024 02:51:53 +0100 Subject: [PATCH 07/19] fancier back button --- rainbow-quox/back.svg | 53 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/rainbow-quox/back.svg b/rainbow-quox/back.svg index 89231e3..5ca488c 100644 --- a/rainbow-quox/back.svg +++ b/rainbow-quox/back.svg @@ -1,7 +1,52 @@ - + width="65" height="65" viewBox="-5 -2.5 65 65" + stroke="#123" stroke-width="2"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3eddfe6e6a91e4d91b1c05a524371dc111a2f303 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Tue, 10 Dec 2024 02:52:11 +0100 Subject: [PATCH 08/19] put minor swatches behind major so the shadows work --- rainbow-quox/make-palette/make-palette.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rainbow-quox/make-palette/make-palette.hs b/rainbow-quox/make-palette/make-palette.hs index f2ce368..1634793 100644 --- a/rainbow-quox/make-palette/make-palette.hs +++ b/rainbow-quox/make-palette/make-palette.hs @@ -76,8 +76,8 @@ makeOnly layer display index = makeMinor :: Text -> Maybe Text -> Text -> Maybe Text -> Int -> Element makeMinor layer1 display1 layer2 _display2 index = itemGroup layer1 index - [makeRect layer1 RMaj, - makeRect layer2 RMin, + [makeRect layer2 RMin, + makeRect layer1 RMaj, makeTextBg layer1, makeName (fromMaybe layer1 display1) Two, makeHex layer1 SFirst, @@ -86,8 +86,8 @@ makeMinor layer1 display1 layer2 _display2 index = makeUse :: Text -> Maybe Text -> Text -> Int -> Element makeUse layer1 display1 layer2 index = itemGroup layer1 index - [makeRect layer1 RMaj, - use_ [makeAttribute "href" $ "#s-" <> layer2], + [use_ [makeAttribute "href" $ "#s-" <> layer2], + makeRect layer1 RMaj, makeTextBg layer1, makeName (fromMaybe layer1 display1) Two, makeHex layer1 SFirst, From 6b960decc9b0e3dd7bfbc5c5b9fa10940b379d4b Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Tue, 10 Dec 2024 02:52:41 +0100 Subject: [PATCH 09/19] rotate flags now they're svg --- style/base.css | 1 + 1 file changed, 1 insertion(+) diff --git a/style/base.css b/style/base.css index ab1ddb2..a713965 100644 --- a/style/base.css +++ b/style/base.css @@ -252,6 +252,7 @@ strong { font-weight: 700; } #flags img { height: 2em; border: 2px solid hsl(var(--hue) 30% 5% / 60%); + rotate: 8deg; } From 578176c845d1e35c780fe51a7c69bab5368dc441 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Tue, 10 Dec 2024 04:21:39 +0100 Subject: [PATCH 10/19] style stuff --- Makefile | 5 +- rainbow-quox/index.html | 2 +- rainbow-quox/script/color.ts | 3 +- rainbow-quox/script/quox.ts | 2 +- rainbow-quox/{ => style}/bright-squares.png | 0 rainbow-quox/style/palette.scss | 10 +-- rainbow-quox/style/style.scss | 83 ++++++++++----------- 7 files changed, 51 insertions(+), 54 deletions(-) rename rainbow-quox/{ => style}/bright-squares.png (100%) diff --git a/Makefile b/Makefile index 3decea3..4181a75 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,10 @@ MEDIA = \ $(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/*) \ - rainbow-quox/palette.svg rainbow-quox/bright-squares.png \ - rainbow-quox/back.svg + rainbow-quox/palette.svg rainbow-quox/back.svg CSS = $(shell find fonts -type f) \ $(patsubst %.scss,%.css, \ - $(wildcard rainbow-quox/style/*.scss) $(wildcard style/*.css)) + $(wildcard rainbow-quox/style/*) $(wildcard style/*)) SCRIPTS = $(patsubst %.ts,%.js,$(wildcard script/*.ts rainbow-quox/script/*.ts)) MISC = $(shell find .well-known -type f) ALL = $(CSS) $(PAGES) $(MEDIA) $(SCRIPTS) $(MISC) diff --git a/rainbow-quox/index.html b/rainbow-quox/index.html index 30a2e8a..d57bd28 100644 --- a/rainbow-quox/index.html +++ b/rainbow-quox/index.html @@ -1,5 +1,5 @@ - + diff --git a/rainbow-quox/script/color.ts b/rainbow-quox/script/color.ts index dd1a9b9..2fa9fab 100644 --- a/rainbow-quox/script/color.ts +++ b/rainbow-quox/script/color.ts @@ -272,7 +272,8 @@ function mkFins(r: Rand, h: Hue, outer: Oklch): FinCols { 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(r, fins1); + const lighter = fins1.l >= fins3.l ? fins1 : fins3; + const vitiligo4 = mkVitiligo(r, lighter); return { fins1, fins2, fins3, vitiligo4 }; } diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index b2c51de..d3cdc81 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -335,7 +335,7 @@ async function setup() { const swap = document.getElementById('swap')!; addListeners(); - document.documentElement.dataset.ready = '1'; + document.documentElement.dataset.state = 'ready'; async function run(task: (k: Done) => Promise): Promise { removeListeners(); diff --git a/rainbow-quox/bright-squares.png b/rainbow-quox/style/bright-squares.png similarity index 100% rename from rainbow-quox/bright-squares.png rename to rainbow-quox/style/bright-squares.png diff --git a/rainbow-quox/style/palette.scss b/rainbow-quox/style/palette.scss index a1b62c5..1bda3c4 100644 --- a/rainbow-quox/style/palette.scss +++ b/rainbow-quox/style/palette.scss @@ -1,5 +1,5 @@ -@import url(../fonts/muller/muller.css); -@import url(../fonts/pragmatapro/pragmatapro.css); +@import url(../../fonts/muller/muller.css); +@import url(../../fonts/pragmatapro/pragmatapro.css); :root { --hue: 300; @@ -14,7 +14,7 @@ .swatch, .text-bg { transition: fill 0.2s ease; fill: var(--col); - stroke: oklch(from var(--col) 0.15 0.25 calc(h + 180)); + stroke: oklch(from var(--col) 0.15 0.15 h); stroke-width: 2; filter: drop-shadow(6px 6px 0 oklch(0.4 0.2 var(--hue) / 0.45)); @@ -24,8 +24,8 @@ } } -.light :is(text, use) { fill: oklch(from var(--col) 0.95 0.075 calc(h + 180)); } -.dark :is(text, use) { fill: oklch(from var(--col) 0.15 0.25 calc(h + 180)); } +.light :is(text, use) { fill: oklch(from var(--col) 0.96 0.05 calc(h + 180)); } +.dark :is(text, use) { fill: oklch(from var(--col) 0.18 0.2 calc(h + 180)); } .name { font-weight: 700; diff --git a/rainbow-quox/style/style.scss b/rainbow-quox/style/style.scss index cb056ba..7be4f59 100644 --- a/rainbow-quox/style/style.scss +++ b/rainbow-quox/style/style.scss @@ -1,4 +1,4 @@ -@import url(/fonts/muller/muller.css); +@import url(../../fonts/muller/muller.css); @keyframes swap1 { 0% { transform: none; } @@ -32,7 +32,10 @@ padding: 0.2rem 0.5rem; } -@layer layout { + +@layer base, layering, come-in; + +@layer base { * { box-sizing: border-box; } :root { @@ -57,12 +60,11 @@ --shadow-color: 6px 6px 0 oklch(0.1 0.15 var(--hue) / 0.45); } background-blend-mode: screen; - - overflow-x: hidden; } body { - min-height: 100vh; + height: 100vh; + min-height: 100lvh; padding: 1em; margin: 0; @@ -72,7 +74,7 @@ } #pic-holder, #main, #aux { - width: min(1000px, 75vw); + width: 1000px; margin: auto; aspect-ratio: 2000/1346; } @@ -85,11 +87,38 @@ pointer-events: none; } - #buttons { - position: absolute; top: -5em; left: 0; + #buttons { position: fixed; top: -60px; left: -20px; } + #reroll { position: absolute; bottom: 0; left: 0; } + #swap { position: absolute; bottom: 0; left: 2.5em; } + + #buttons button { + @include box; + width: calc(6em + 100px); + text-align: left; + + $rotate: rotate(-60deg); + transform: $rotate; + transform-origin: center right; + + :root:not([data-running]) &:active, + [data-running=swap] &#swap, + [data-running=reroll] &#reroll { + transform: $rotate translateX(-1.5em); + } + } + + #back { position: fixed; top: 22px; left: 22px; } + + #palette-holder { margin: -60px auto 0; } + + #state-message { + @include box; + position: fixed; top: 1em; right: -1em; + padding-right: 2em; } } + @layer layering { #main { z-index: 0; } #aux { z-index: 1; } @@ -97,46 +126,14 @@ #buttons, #state-message, #palette-holder { z-index: 2; } } -@layer a { - - button { - @include box; - transform-origin: center right; - padding-right: 3em; - - $rotate: rotate(-60deg); - transform: $rotate; - &:not(:first-child) { margin-left: -5em; } - - :root:not([data-running]) &:active, - [data-running=swap] &#swap, - [data-running=reroll] &#reroll { - transform: $rotate translate(-1.5em); - } - } - - #back { position: absolute; top: 22px; left: 22px; } - - #palette-holder { - width: min(90vw, 1126px); - margin: 2.5rem auto 0; - } - #palette { width: 100%; } - - #state-message { - @include box; - position: absolute; top: 1em; right: -1em; - padding-right: 2em; - } - -} @layer come-in { - button, #state-message, #palette { @include transy; } + button, #state-message, #palette, #back { @include transy; } - :root:not([data-ready]) { + [data-state=loading] { button { transform: none; } #state-message { transform: translateX(100%); } #palette { transform: translateY(125%); } + #back { transform: translateX(-200%); } } } From 621827c6aa0adfe1068fbf3ec8648d5e069a731a Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Tue, 10 Dec 2024 05:19:14 +0100 Subject: [PATCH 11/19] show/hide ui buttons --- Makefile | 2 +- rainbow-quox/index.html | 15 +++++---- rainbow-quox/script/quox.ts | 59 +++++++++++++++++++++------------ rainbow-quox/showui.svg | 26 +++++++++++++++ rainbow-quox/style/palette.scss | 6 ---- rainbow-quox/style/style.scss | 50 +++++++++++++++++++--------- 6 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 rainbow-quox/showui.svg diff --git a/Makefile b/Makefile index 4181a75..c4ca613 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ MEDIA = \ $(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/*) \ - rainbow-quox/palette.svg rainbow-quox/back.svg + $(wildcard rainbow-quox/*.svg) CSS = $(shell find fonts -type f) \ $(patsubst %.scss,%.css, \ $(wildcard rainbow-quox/style/*) $(wildcard style/*)) diff --git a/rainbow-quox/index.html b/rainbow-quox/index.html index d57bd28..ff59dd6 100644 --- a/rainbow-quox/index.html +++ b/rainbow-quox/index.html @@ -11,8 +11,17 @@
+
+
+ show ui +
+ +
+
quox #0
@@ -28,9 +37,3 @@ there should be a palette here but it failed to load for some reason
- - diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index d3cdc81..4a2b7d8 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -206,13 +206,13 @@ applyState(data: LayerData, opts: Partial): Promise { await recolorLayers(data, rgb); updateBg(oklch); - updatePalette(oklch, rgb); + updateSvgs(oklch, rgb); updateLabel(initState); + updateUrl(initState); if (firstLoad) { await instantUpdateImage(side, await ensureComposed(buf, data)); } else { - updateUrl(initState); await animateUpdateImage(buf, side, data, done ?? (() => {})); } @@ -289,33 +289,40 @@ function updateLabel(st: State) { if (stateLabel) stateLabel.innerHTML = `${st}`; } -function updatePalette(oklch: Color.Colors, rgb: Color.Rgbs) { +function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { const paletteObj = document.getElementById('palette') as HTMLObjectElement; const palette = paletteObj.contentDocument as XMLDocument | null; - if (!palette) return; + if (palette) { + palette.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); + const get = (id: string) => palette.getElementById(id); - palette.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); - const get = (id: string) => palette.getElementById(id); + for (const layer of Color.allLayers) { + let col = rgb[layer].css(); + let elem; - for (const layer of Color.allLayers) { - let col = rgb[layer].css(); - let elem; - - // main group - if (elem = get(`i-${layer}`)) { - if (oklch[layer].l < 0.6) { - elem.classList.add('light'); elem.classList.remove('dark'); - } else { - elem.classList.add('dark'); elem.classList.remove('light'); + // main group + if (elem = get(`i-${layer}`)) { + if (oklch[layer].l < 0.6) { + elem.classList.add('light'); elem.classList.remove('dark'); + } else { + elem.classList.add('dark'); elem.classList.remove('light'); + } + elem.style.setProperty('--col', col); } - elem.style.setProperty('--col', col); - } - // label - if (elem = get(`c-${layer}`)) elem.innerHTML = col; - // minor swatch, if applicable - if (elem = get(`s-${layer}`)) elem.style.setProperty('--col', col); + // label + if (elem = get(`c-${layer}`)) elem.innerHTML = col; + // minor swatch, if applicable + if (elem = get(`s-${layer}`)) elem.style.setProperty('--col', col); + } + } + + const showuiObj = document.getElementById('showui') as HTMLObjectElement; + const showui = showuiObj.contentDocument as XMLDocument | null; + + if (showui) { + showui.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); } } @@ -335,6 +342,14 @@ async function setup() { const swap = document.getElementById('swap')!; addListeners(); + // these ones don't need to be toggled + document.getElementById('hideui')?.addEventListener('click', () => { + document.documentElement.dataset.state = 'fullquox'; + }); + document.getElementById('showui')?.addEventListener('click', () => { + document.documentElement.dataset.state = 'ready'; + }); + document.documentElement.dataset.state = 'ready'; async function run(task: (k: Done) => Promise): Promise { diff --git a/rainbow-quox/showui.svg b/rainbow-quox/showui.svg new file mode 100644 index 0000000..5fdd6f1 --- /dev/null +++ b/rainbow-quox/showui.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/rainbow-quox/style/palette.scss b/rainbow-quox/style/palette.scss index 1bda3c4..087d7dd 100644 --- a/rainbow-quox/style/palette.scss +++ b/rainbow-quox/style/palette.scss @@ -16,12 +16,6 @@ fill: var(--col); stroke: oklch(from var(--col) 0.15 0.15 h); stroke-width: 2; - - filter: drop-shadow(6px 6px 0 oklch(0.4 0.2 var(--hue) / 0.45)); - - @media (prefers-color-scheme: dark) { - filter: drop-shadow(6px 6px 0 oklch(0.1 0.15 var(--hue) / 0.45)); - } } .light :is(text, use) { fill: oklch(from var(--col) 0.96 0.05 calc(h + 180)); } diff --git a/rainbow-quox/style/style.scss b/rainbow-quox/style/style.scss index 7be4f59..5be1ac5 100644 --- a/rainbow-quox/style/style.scss +++ b/rainbow-quox/style/style.scss @@ -23,12 +23,19 @@ transition: transform 0.25s cubic-bezier(.47,.74,.61,1.2); } +@mixin shadow { + filter: drop-shadow(6px 6px 0 oklch(0.4 0.2 var(--hue) / 0.45)); + @media (prefers-color-scheme: dark) { + filter: drop-shadow(6px 6px 0 oklch(0.1 0.15 var(--hue) / 0.45)); + } +} + @mixin box { + @include shadow; font: 700 20pt var(--font); // respecify font family for -
quox #0
+
+ + +
+ hello my name is + anonymous + +
+ +
+
+
diff --git a/rainbow-quox/script/color.ts b/rainbow-quox/script/color.ts index 2fa9fab..1cd1d39 100644 --- a/rainbow-quox/script/color.ts +++ b/rainbow-quox/script/color.ts @@ -52,11 +52,26 @@ function isLight(l: Luma): boolean { return l >= MINL_LIGHT; } export namespace Rand { export type State = R.State; } -export class Rand extends R.Rand { - constructor(seed?: R.State) { super(seed); } +type CloseFar = 'close' | 'far'; - lightFor(baseL: Luma): Luma { return this.float(baseL, MAXL); } - darkFor(baseL: Luma): Luma { return this.float(MINL, baseL); } +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); @@ -140,14 +155,6 @@ export class Oklch { } } - 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})`; } - } - with(maps: Oklch.With): Oklch { function call(comp: Oklch.With1, x: number) { switch (typeof comp) { @@ -163,7 +170,23 @@ export class Oklch { }); } + 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); + } + } } @@ -196,9 +219,15 @@ 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'; -export function colors(r: Rand = new Rand()): Scheme { - const outer = new Oklch(r, 'dark'); +type KnownPalette = + Record & Partial>; + + +export function colors(r: Rand = new Rand(), base?: KnownPalette): Scheme { + const outer = base?.outer ?? new Oklch(r, 'dark'); let outerCols: OuterCols = { outer, spines: mkSpines(r, outer), vitiligo1: mkVitiligo(r, outer) }; @@ -210,18 +239,18 @@ export function colors(r: Rand = new Rand()): Scheme { if (whichBody > 2/3) { type = 'triad'; const [f, b] = r.triad(outer.h); - finCols = mkFins(r, f, outer); bellyCols = mkBelly(r, b); + 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); - finCols = mkFins(r, f!, outer); bellyCols = mkBelly(r, b!); + finCols = mkFins(r, f!, outer, base); bellyCols = mkBelly(r, b!, base); } else { type = 'fin-body'; - finCols = mkFins(r, r.analogous1(outer.h), outer); - bellyCols = mkBelly(r, r.complementary1(outer.h)); + finCols = mkFins(r, r.analogous1(outer.h), outer, base); + bellyCols = mkBelly(r, r.complementary1(outer.h), base); } - let miscCols = mkMisc(r, outerCols, finCols, bellyCols); + let miscCols = mkMisc(r, outerCols, finCols, bellyCols, base); return merge(outerCols, sockCols, finCols, bellyCols, miscCols, type); } @@ -229,9 +258,9 @@ export function colors(r: Rand = new Rand()): Scheme { function mkSpines(r: Rand, outer: Oklch): Oklch { return outer.with({ - l: x => x * 0.8, - c: x => x * 1.1, - h: x => r.float(x + 12, x - 12), + l: l => r.darkFor(l), + c: c => r.brightFor(outer.l, c), + h: h => r.float(h + 12, h - 12), }) } @@ -258,8 +287,10 @@ function mkCuffs(r: Rand, sock: Oklch): Oklch { }); } -function mkFins(r: Rand, h: Hue, outer: Oklch): FinCols { - const [fin1Hue, fin2Hue, fin3Hue] = r.analogous(h, 3); +function mkFins(r: Rand, h: Hue, outer: Oklch, base?: KnownPalette): FinCols { + const baseFin1 = base?.fins; + const [fin1Hue, fin2Hue, fin3Hue] = r.analogous(baseFin1?.h ?? h, 3); + const direction: 'lighter' | 'darker' = r.choice(['lighter', 'darker']); function ll(l: Luma): Luma { @@ -269,17 +300,18 @@ function mkFins(r: Rand, h: Hue, outer: Oklch): FinCols { return direction == 'lighter' ? r.dullFor(l, c) : r.brightFor(l, c); } - 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 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 vitiligo4 = mkVitiligo(r, lighter); return { fins1, fins2, fins3, vitiligo4 }; } -function mkBelly(r: Rand, h: Hue): BellyCols { - const [belly1Hue, belly2Hue] = r.analogous(h, 2); - const belly1 = new Oklch({ +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! @@ -294,7 +326,8 @@ function mkBelly(r: Rand, h: Hue): BellyCols { return { belly1, belly2, vitiligo2, vitiligo3 }; } -function mkMisc(r: Rand, o: OuterCols, f: FinCols, b: BellyCols): MiscCols { +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), @@ -302,7 +335,7 @@ function mkMisc(r: Rand, o: OuterCols, f: FinCols, b: BellyCols): MiscCols { }); return { masks, - eyes: new Oklch({ + 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) @@ -365,6 +398,14 @@ export class Rgb { } 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; @@ -375,8 +416,8 @@ export function toRgbViaCanvas(col: Oklch): Rgb { rgbBuf ??= new OffscreenCanvas(1, 1).getContext('2d')!; rgbBuf.fillStyle = col.css(); rgbBuf.fillRect(0, 0, 1, 1); - const rgb = rgbBuf.getImageData(0, 0, 1, 1).data; - return new Rgb(rgb[0]!, rgb[1]!, rgb[2]!); + const pix = rgbBuf.getImageData(0, 0, 1, 1).data; + return rgb(pix[0]!, pix[1]!, pix[2]!); } export function toRgbs(col: Colors): Rgbs { @@ -390,3 +431,82 @@ export function toHex({r, g, b}: Rgb): string { } 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), + belly: oklch(0.87, 0.082, 99), + fins: 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), + }, + 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), + }, + '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), + }, + '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), + }, + 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), + }, + 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), + }, + 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), + }, + 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), + }, + 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), + }, + 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), + }, +}; diff --git a/rainbow-quox/script/history.ts b/rainbow-quox/script/history.ts new file mode 100644 index 0000000..af8f71e --- /dev/null +++ b/rainbow-quox/script/history.ts @@ -0,0 +1,128 @@ +import { Colors as Oklchs, Rgbs } from './color.js'; +import * as Color from './color.js'; + +export class HistoryItem { + name: string; + oklch: Oklchs; + rgb: Rgbs; + + constructor(name: string, oklch: Oklchs, rgb: Rgbs) { + this.oklch = oklch; + this.rgb = rgb; + this.name = name; + } + + asHtml(): HTMLButtonElement { + const { lines: bg, outer, belly1: belly, fins1: fins } = this.rgb; + + const content = ` + + + + fin colour: ${fins.css()} + + + belly colour: ${belly.css()} + + + outer body colour: ${outer.css()} + + + sample of the palette for ${this.name}. + fin colour: ${fins.css()}. + belly colour: ${belly.css()}. + outer body colour: ${outer.css()}. + + + ${this.name} + `; + + let button = document.createElement('button'); + button.className = 'history-item'; + button.dataset.name = this.name; + button.innerHTML = content; + return button; + } +} + + +export class History { + items: string[]; + + constructor(items: string[] = []) { this.items = items; } + + add(name: string): void { this.items.push(name); } + + *iterNames(maxLength?: number | null): Iterable { + let seen = new Set; + let done = 0; + if (maxLength === undefined) maxLength = 100; + + for (let i = this.items.length - 1; i >= 0; i--) { + if (maxLength !== null && done > maxLength) break; + const name = this.items[i]!; + if (!name || seen.has(name)) continue; + seen.add(name); done++; + yield name; + } + } + + *iterItems(maxLength?: number | null): Iterable { + for (const name of this.iterNames(maxLength)) { + const oklch = Color.colors(new Color.Rand(name), Color.KNOWN[name]); + const rgbs = Color.toRgbs(oklch); + + yield new HistoryItem(name, oklch, rgbs); + } + } + + static validate(x: unknown): History | undefined { + if (!Array.isArray(x)) return; + if (!x.every(i => typeof i === 'string')) return; + return new History(x); + } + + toJSON() { return this.items; } + + save(persist = true) { + const storage = persist ? localStorage : sessionStorage; + storage.setItem('history', JSON.stringify(this)); + } + + // if the json was invalid, return it + // if no history exists just start a new one + static load(): History | string { + const json = + sessionStorage.getItem('history') ?? + localStorage.getItem('history'); + if (json != null) { + let h = History.validate(JSON.parse(json)); + if (h) { h.prune(); return h; } + else return json; + } else { + return new History; + } + } + + // if the json is invalid, discard it + static loadOrClear(): History { + const h = History.load(); + return h instanceof History ? h : new History; + } + + addSave(name: string, persist = true): void { + this.add(name); + this.save(persist); + } + + prune(maxLength?: number | null) { + let keep = []; + for (let name of this.iterNames(maxLength)) keep.push(name); + this.items = keep.reverse(); + } + + pruneSave(maxLength?: number | null, persist = true) { + this.prune(maxLength); + this.save(persist); + } +} diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index 4a2b7d8..d23f667 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -1,6 +1,5 @@ import * as Color from './color.js'; - -type State = Color.Rand.State; +import { History } from './history.js'; async function loadBitmap(url: string): Promise { @@ -177,46 +176,55 @@ function message(msg: string, error = false) { } -function urlState(): State | undefined { - const str = document.location.hash; - if (str?.match(/^#\d+$/)) return parseInt(str.substring(1)); +function urlState(): string | undefined { + let hash = document.location.hash?.substring(1); + if (hash != '' && hash !== undefined) return decodeURI(hash); } -function updateUrl(state: State): void { - history.replaceState({}, '', `#${state}`); +function updateUrl(seed: string): void { + history.replaceState({}, '', `#${encodeURI(seed)}`); } -type ApplyStateOpts = - { side: Side, state: State, firstLoad: boolean, buf: Buffer, done: Done }; +type ApplyStateOpts = { + seed: string, + side?: Side, + firstLoad?: boolean, + buf?: Buffer, + history?: History, + done?: Done, +}; async function -applyState(data: LayerData, opts: Partial): Promise { - let { side, state, firstLoad, buf, done } = opts; +applyState(data: LayerData, opts: ApplyStateOpts): Promise { + let { side, seed, firstLoad, buf, history, done } = opts; side ??= 'front'; firstLoad ??= false; buf ??= new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; + done ??= () => {}; - let r = new Color.Rand(state); - const initState = r.state; + let rand = new Color.Rand(seed); - const oklch = Color.colors(r); + const oklch = Color.colors(rand, Color.KNOWN[seed]); const rgb = Color.toRgbs(oklch); - const newState = r.state; + const newSeed = rand.alphaNum(); await recolorLayers(data, rgb); updateBg(oklch); updateSvgs(oklch, rgb); - updateLabel(initState); - updateUrl(initState); + updateLabel(seed); + updateUrl(seed); if (firstLoad) { await instantUpdateImage(side, await ensureComposed(buf, data)); + done(); } else { - await animateUpdateImage(buf, side, data, done ?? (() => {})); + await animateUpdateImage(buf, side, data, done); } - return newState; + if (history) history.addSave(seed); + + return newSeed; } type CanvasId = 'main' | 'aux'; @@ -233,8 +241,16 @@ instantUpdateImage(side: Side, data: ComposedData) { type Done = () => void; +const noAnim = matchMedia('(prefers-reduced-motion: reduce)'); + async function animateUpdateImage(buf: Buffer, side: Side, data: LayerData, done: Done) { + if (noAnim.matches) { + instantUpdateImage(side, await ensureComposed(buf, data)); + done(); + return; + } + const duration = 200; const main = getCanvasCtx('main'); @@ -258,6 +274,12 @@ animateUpdateImage(buf: Buffer, side: Side, data: LayerData, done: Done) { async function animateSwapImage(buf: Buffer, newSide: Side, data: ComposedData, done: Done) { + if (noAnim.matches) { + instantUpdateImage(newSide, data); + done(); + return; + } + const duration = 400; const main = getCanvasCtx('main'); @@ -284,9 +306,9 @@ function updateBg(cols: Color.Colors) { document.documentElement.style.setProperty('--hue', `${cols.outer.h}`); } -function updateLabel(st: State) { - const stateLabel = document.getElementById('state'); - if (stateLabel) stateLabel.innerHTML = `${st}`; +function updateLabel(seed: string) { + const stateLabel = document.getElementById('current-name'); + if (stateLabel) stateLabel.innerHTML = seed; } function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { @@ -327,21 +349,56 @@ function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { } +function showHistory(history: History, data: LayerData, + opts: Omit) { + const list = document.getElementById('history-items'); + if (!list) return; + list.innerHTML = ''; + let { side, firstLoad, buf, 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); + }); + list.appendChild(elem); + } + + setTimeout(() => { + document.documentElement.dataset.state = 'history'; + const elem = document.createElement('div'); + elem.id = 'history-close-target'; + elem.addEventListener('click', closeHistory); + document.body.appendChild(elem); + }); +} + +function closeHistory() { + document.getElementById('history-items')?. + scroll({top: 0, left: 0, behavior: 'smooth'}); + let field = document.getElementById('history-close-target'); + if (field) field.parentElement?.removeChild(field); + document.documentElement.dataset.state = 'ready'; +} + async function setup() { message('loading layers…'); let data = await loadData().catch(e => { message(e, true); throw e }); + let history = History.loadOrClear(); let buf = new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; - let state = urlState(); + let seed = urlState() ?? new Color.Rand().alphaNum(); let side: Side = 'front'; - state = await applyState(data, { state, buf, firstLoad: true }); + seed = await applyState(data, { seed, buf, history, firstLoad: true }); const reroll = document.getElementById('reroll')!; const swap = document.getElementById('swap')!; addListeners(); + // these ones don't need to be toggled document.getElementById('hideui')?.addEventListener('click', () => { document.documentElement.dataset.state = 'fullquox'; @@ -349,6 +406,24 @@ async function setup() { document.getElementById('showui')?.addEventListener('click', () => { document.documentElement.dataset.state = 'ready'; }); + document.getElementById('history-button')?.addEventListener('click', () => { + // does this need the add/remove listeners dance + // actually does anything any more? + showHistory(history, data, { side, buf }); + }); + document.getElementById('close-history')?.addEventListener('click', closeHistory); + document.getElementById('current-name')?.addEventListener('focusout', async e => { + const space = String.raw`(\n|\s|
| )`; + const re = new RegExp(`^${space}+|${space}+$`, 'msgu'); + + let 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 + + seed = await applyState(data, { side, seed: str, buf, history }); + }); document.documentElement.dataset.state = 'ready'; @@ -359,15 +434,16 @@ async function setup() { function updateFromUrl() { run(async k => { - const newState = urlState(); - if (newState) { - state = await applyState(data, { side, state: newState, buf, done: k }); + const newSeed = urlState(); + if (newSeed) { + const opts = { history, side, seed: newSeed, buf, done: k }; + seed = await applyState(data, opts); } }); } function runReroll() { run(async k => { - state = await applyState(data, { side, state, buf, done: k }); + seed = await applyState(data, { side, seed, buf, history, done: k }); }); } function runSwap() { diff --git a/rainbow-quox/script/rand.ts b/rainbow-quox/script/rand.ts index aae8bdb..efc8a1c 100644 --- a/rainbow-quox/script/rand.ts +++ b/rainbow-quox/script/rand.ts @@ -1,45 +1,123 @@ -// https://stackoverflow.com/a/424445 thanks my dude +// https://stackoverflow.com/questions/521295#47593316 -export type State = number; +export type State = [number, number, number, number]; -const M = 0x80000000; -const A = 1103515245; -const C = 12345; - -export class Rand { - state: number; - - constructor(state?: State) { - this.state = typeof state == 'number' && !isNaN(state) ? - state : Math.floor(Math.random() * (M - 1)); - } - - #next(): number { return this.state = (A * this.state + C) % M; } - - #float(x?: number, y?: number): number { - const [lo, hi] = - x === undefined ? [0, 1] : - y === undefined ? [0, x] : - [Math.min(x, y), Math.max(x, y)]; - - return lo + this.#next() / (M - 1) * (hi - lo); - } +export interface Randy { + state: State; int(): number; // whole int32 range int(x: number): number; // [0, x) int(x: number, y: number): number; // [x, y) + + float(): number; // [0, 1) + float(x: number): number; // [0, x) + float(x: number, y: number): number; // [x, y) + + choice
(array: A[]): A; + boolean(): boolean; +} + + +export function fromHex(s: string): State { + const a = s.substring(0, 8); + const b = s.substring(8, 16); + const c = s.substring(16, 24); + const d = s.substring(24, 32); + return [h(a), h(b), h(c), h(d)]; + + function h(x: string) { return parseInt(x, 16); } +} + +export function toHex([a, b, c, d]: State): string { + return `${h(a)}${h(b)}${h(c)}${h(d)}`; + function h(x: number) { return x.toString(16); } +} + + +const UINT_MAX = 4294967296; + +export class Rand implements Randy { + #a: number; #b: number; #c: number; #d: number; + + constructor(); + constructor([a, b, c, d]: State); + constructor(str: string); + constructor(st?: State | string) { + const [a, b, c, d] = + st === '' ? s4() : + typeof st === 'string' ? Rand.stateFrom(st) : + st ?? s4(); + this.#a = a; this.#b = b; this.#c = c; this.#d = d; + + for (let i = 0; i < 20; ++i) this.#next(); + + function s() { return (Math.random() * 2**32) >>> 0; } + function s4() { return [s(), s(), s(), s()] as const; } + } + + get state(): State { return [this.#a, this.#b, this.#c, this.#d]; } + + get stateString() { return toHex(this.state); } + + static stateFrom(str: string): State { + let h1 = 1779033703, h2 = 3144134277, + h3 = 1013904242, h4 = 2773480762; + for (let i = 0, k; i < str.length; i++) { + k = str.charCodeAt(i); + h1 = h2 ^ Math.imul(h1 ^ k, 597399067); + h2 = h3 ^ Math.imul(h2 ^ k, 2869860233); + h3 = h4 ^ Math.imul(h3 ^ k, 951274213); + h4 = h1 ^ Math.imul(h4 ^ k, 2716044179); + } + h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067); + 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; + return [h1>>>0, h2>>>0, h3>>>0, h4>>>0]; + } + + int(): number; // whole int32 range + int(to: number): number; // [0, x) + int(from: number, to: number): number; // [x, y) int(x?: number, y?: number): number { - return x === undefined ? this.#next() : Math.floor(this.#float(x, y)); + if (x === undefined) return this.#next(); + return Math.floor(y === undefined ? this.float(x) : this.float(x, y)); } float(): number; // [0, 1) float(x: number): number; // [0, x) float(x: number, y: number): number; // [x, y) - float(x?: number, y?: number): number { return this.#float(x, y); } + float(x?: number, y?: number): number { + const in01 = this.#next() / UINT_MAX; + if (x === undefined) return in01; + const [lo, hi] = y === undefined? [0, x] : [x, y]; + return lo + in01 * (hi - lo); + } choice(array: A[]): A { - return array[this.int(0, array.length)]!; + return array[this.int(array.length)]!; } boolean(): boolean { return this.float() > 0.5; } + + alphaNum(len?: number): string { + let res = ""; + // [todo] is there a better way to make a string in js + if (len === undefined || len <= 0) len = 16; // idk + for (let i = 0; i < len; ++i) + res += this.choice(Array.from("abcdefghijklmnopqrstuvwxyz0123456789")); + return res; + } + + #next(): number { + this.#a |= 0; this.#b |= 0; this.#c |= 0; this.#d |= 0; + let 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; + this.#c = (this.#c << 21 | this.#c >>> 11); + this.#c = this.#c + t | 0; + return (t >>> 0); + } } diff --git a/rainbow-quox/showui.svg b/rainbow-quox/showui.svg index 5fdd6f1..add1e2c 100644 --- a/rainbow-quox/showui.svg +++ b/rainbow-quox/showui.svg @@ -1,6 +1,6 @@ diff --git a/rainbow-quox/style/3px-tile.png b/rainbow-quox/style/3px-tile.png new file mode 100644 index 0000000..8acbe8c --- /dev/null +++ b/rainbow-quox/style/3px-tile.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8dc7cd84f755aa0d799d5f9b51383329b699d09a66cd274fa3527cfc398e1bcc +size 5898 diff --git a/rainbow-quox/style/bright-squares.png b/rainbow-quox/style/bright-squares.png deleted file mode 100644 index bacde4f..0000000 --- a/rainbow-quox/style/bright-squares.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6446c779f15729bb9c47103bf4f9a2c831439ce7246943bea3715dfbdfaebb27 -size 41392 diff --git a/rainbow-quox/style/groovepaper.dark.png b/rainbow-quox/style/groovepaper.dark.png new file mode 100644 index 0000000..1e73900 --- /dev/null +++ b/rainbow-quox/style/groovepaper.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5494b4cf3fb6fd035fe82caf38283d1f6a8f932c84e1c9e9ff08635514af6cf0 +size 61201 diff --git a/rainbow-quox/style/groovepaper.png b/rainbow-quox/style/groovepaper.png new file mode 100644 index 0000000..f334520 --- /dev/null +++ b/rainbow-quox/style/groovepaper.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb51825f76616c381250a05993c2124152f1c5af467c2d9c43360cb963d23038 +size 59534 diff --git a/rainbow-quox/style/style.scss b/rainbow-quox/style/style.scss index 5be1ac5..8047321 100644 --- a/rainbow-quox/style/style.scss +++ b/rainbow-quox/style/style.scss @@ -1,3 +1,4 @@ +@use 'sass:math'; @import url(../../fonts/muller/muller.css); @keyframes swap1 { @@ -19,26 +20,59 @@ } -@mixin transy { - transition: transform 0.25s cubic-bezier(.47,.74,.61,1.2); +// https://oakreef.ie/transy :) +$transy-default: 250ms; +@mixin transy($prop: transform, $duration: $transy-default) { + transition: $prop $duration cubic-bezier(.47,.74,.61,1.2); } @mixin shadow { filter: drop-shadow(6px 6px 0 oklch(0.4 0.2 var(--hue) / 0.45)); @media (prefers-color-scheme: dark) { - filter: drop-shadow(6px 6px 0 oklch(0.1 0.15 var(--hue) / 0.45)); + filter: drop-shadow(6px 6px 0 oklch(0.1 0.125 var(--hue) / 0.45)); } } -@mixin box { +$box-texture: url(3px-tile.png); +$box-bg: oklch(0.3 0.2 var(--hue)); +$box-fg: oklch(0.95 0.075 var(--c-hue)); + +$button-bg: oklch(0.5 0.25 var(--hue)); +$button-fg: oklch(0.98 0.1 var(--c-hue)); + +@mixin box($font-scale: 1, $button: false) { @include shadow; - font: 700 20pt var(--font); // respecify font family for
- + the canvas failed to load. sorry. - +
diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index d23f667..192f4fe 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -37,8 +37,8 @@ function loadImageData(url: string, buf?: Buffer): Promise { else return loadDataFresh(url); } -const WIDTH = 1000; -const HEIGHT = 673; +const WIDTH = 1040; +const HEIGHT = 713; function makeBuffer(width = WIDTH, height = HEIGHT): Buffer { return new OffscreenCanvas(width, height).getContext('2d')!; @@ -49,13 +49,13 @@ function makeBufferIfLocks(width?: number, height?: number): Buffer | undefined else return undefined; } -export type Layer = 'static' | 'eyeshine' | Color.Layer; +export type Layer = 'stroke' | 'static' | 'eyeshine' | Color.Layer; // in compositing order export const allLayers: Layer[] = - ['static', 'outer', 'spines', 'stripes', 'cuffs', 'fins1', 'fins2', 'fins3', - 'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', 'vitiligo3', - 'vitiligo4', 'eyes', 'eyeshine', 'lines']; + ['stroke', 'static', 'outer', 'spines', 'stripes', 'cuffs', 'fins1', 'fins2', + 'fins3', 'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', + 'vitiligo3', 'vitiligo4', 'eyes', 'eyeshine', 'lines']; function makeLayerInfo(f: (l: Layer) => A): Record { return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record; From 0689aa1f8725cf946728904a6fd2c09323478a91 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sat, 14 Dec 2024 00:34:22 +0100 Subject: [PATCH 16/19] deneb --- index.html | 3 +++ style/base.css | 1 + 2 files changed, 4 insertions(+) diff --git a/index.html b/index.html index fceb022..6305350 100644 --- a/index.html +++ b/index.html @@ -434,6 +434,9 @@ blau's blog + +
  • + deneb diff --git a/style/base.css b/style/base.css index a713965..8711df0 100644 --- a/style/base.css +++ b/style/base.css @@ -491,6 +491,7 @@ strong { font-weight: 700; } #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; From c6a12a73d1ae8ffc510ddaa0b6fc3f6892850a6e Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Sun, 15 Dec 2024 04:06:53 +0100 Subject: [PATCH 17/19] remove old useless code --- rainbow-quox/script/quox.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index 192f4fe..fc5cb70 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -339,13 +339,6 @@ function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { if (elem = get(`s-${layer}`)) elem.style.setProperty('--col', col); } } - - const showuiObj = document.getElementById('showui') as HTMLObjectElement; - const showui = showuiObj.contentDocument as XMLDocument | null; - - if (showui) { - showui.documentElement.style.setProperty('--hue', `${oklch.outer.h}`); - } } From 845c0b9708dec76b029668e2adacc5986b80cc8d Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Mon, 16 Dec 2024 19:32:39 +0100 Subject: [PATCH 18/19] faealchemist & deneb buttons --- index.html | 12 ++++++++---- media/buttons/FaeAlchemist.gif | 3 +++ media/buttons/deneb.gif | 3 +++ media/buttons/deneb_still.webp | 3 +++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 media/buttons/FaeAlchemist.gif create mode 100644 media/buttons/deneb.gif create mode 100644 media/buttons/deneb_still.webp diff --git a/index.html b/index.html index 6305350..68ab674 100644 --- a/index.html +++ b/index.html @@ -345,10 +345,8 @@ - faealchemist + faealchemist -
  • @@ -436,7 +434,13 @@
  • - deneb + + + + deneb + + diff --git a/media/buttons/FaeAlchemist.gif b/media/buttons/FaeAlchemist.gif new file mode 100644 index 0000000..abc354b --- /dev/null +++ b/media/buttons/FaeAlchemist.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b3110b0a64cad092084d1a63e0c453d47a6c2155a826558cf6063cfd504bd97 +size 5363 diff --git a/media/buttons/deneb.gif b/media/buttons/deneb.gif new file mode 100644 index 0000000..c8b169b --- /dev/null +++ b/media/buttons/deneb.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3b50cf4132822b62250edccc57530ea4be20fc731e0b894e8fdabb0f9385642 +size 7330 diff --git a/media/buttons/deneb_still.webp b/media/buttons/deneb_still.webp new file mode 100644 index 0000000..bf1c8ef --- /dev/null +++ b/media/buttons/deneb_still.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aeb9ba3b1e0c0f7178803f0f18d54e2ab5f3d0db320008472f7382a1e23b4054 +size 1650 From 7e4518bdaf1be730e0d86b61855cd8f00d779e39 Mon Sep 17 00:00:00 2001 From: Rhiannon Morris Date: Mon, 16 Dec 2024 22:40:26 +0100 Subject: [PATCH 19/19] rainbow-quox download button --- rainbow-quox/back.svg | 2 +- rainbow-quox/download.svg | 25 ++++++++++++++++++++++++ rainbow-quox/index.html | 15 ++++++++++----- rainbow-quox/script/quox.ts | 36 +++++++++++++++++++++++++++++++++-- rainbow-quox/style/style.scss | 21 +++++++++++++++----- 5 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 rainbow-quox/download.svg diff --git a/rainbow-quox/back.svg b/rainbow-quox/back.svg index 6db0442..d609303 100644 --- a/rainbow-quox/back.svg +++ b/rainbow-quox/back.svg @@ -34,7 +34,7 @@ - + diff --git a/rainbow-quox/download.svg b/rainbow-quox/download.svg new file mode 100644 index 0000000..448feb6 --- /dev/null +++ b/rainbow-quox/download.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/rainbow-quox/index.html b/rainbow-quox/index.html index 65ed9c2..7c9d0e2 100644 --- a/rainbow-quox/index.html +++ b/rainbow-quox/index.html @@ -49,9 +49,14 @@
  • -
    - - there should be a palette here but it failed to load for some reason - +
    + +
    + + there should be a palette here but it failed to load for some reason + +
    diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index fc5cb70..0228127 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -375,6 +375,31 @@ function closeHistory() { document.documentElement.dataset.state = 'ready'; } +function download(seed: string) { + const colors = Color.toRgbs(Color.colors(new Color.Rand(seed))); + + let lines = [ + "GIMP Palette\n", + `Name: quox ${seed}\n\n`, + ]; + + for (const name of Color.allLayers) { + let { r, g, b } = colors[name]; + lines.push(`${r} ${g} ${b} ${name}\n`); + } + + const blob = new Blob(lines, { type: 'application/x-gimp-palette' }); + + // there must be a better way to push out a file than + // this autohotkey-ass nonsense + const elem = document.createElement('a'); + elem.download = `quox-${seed}.gpl`; + const url = URL.createObjectURL(blob); + elem.href = url; + elem.click(); + URL.revokeObjectURL(url); +} + async function setup() { message('loading layers…'); @@ -383,9 +408,10 @@ async function setup() { let buf = new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; - let seed = urlState() ?? new Color.Rand().alphaNum(); + let prevSeed = urlState() ?? new Color.Rand().alphaNum(); + let seed = + await applyState(data, { seed: prevSeed, buf, history, firstLoad: true }); let side: Side = 'front'; - seed = await applyState(data, { seed, buf, history, firstLoad: true }); const reroll = document.getElementById('reroll')!; const swap = document.getElementById('swap')!; @@ -415,8 +441,12 @@ async function setup() { elem.innerText = str; // todo allow images cos it's funny + prevSeed = seed; seed = await applyState(data, { side, seed: str, buf, history }); }); + document.getElementById('download-button')?.addEventListener('click', () => { + download(prevSeed); + }); document.documentElement.dataset.state = 'ready'; @@ -430,12 +460,14 @@ async function setup() { const newSeed = urlState(); if (newSeed) { const opts = { history, side, seed: newSeed, buf, done: k }; + prevSeed = seed; seed = await applyState(data, opts); } }); } function runReroll() { run(async k => { + prevSeed = seed; seed = await applyState(data, { side, seed, buf, history, done: k }); }); } diff --git a/rainbow-quox/style/style.scss b/rainbow-quox/style/style.scss index 8047321..4a16007 100644 --- a/rainbow-quox/style/style.scss +++ b/rainbow-quox/style/style.scss @@ -165,12 +165,21 @@ $button-fg: oklch(0.98 0.1 var(--c-hue)); } #showui { transform: translateX(-200%); } - #palette-holder { + #palette-bar { margin: -60px auto 0; + position: relative; + } + #palette-holder { padding-top: 25px; overflow-y: hidden; } #palette { @include shadow; } + #download-button { + @include image-button; + position: absolute; + bottom: 100%; + right: 20px; + } @mixin history-flex { @@ -286,13 +295,15 @@ $button-fg: oklch(0.98 0.1 var(--c-hue)); #current { @include transy(all); } #close-history { @include transy; } + #download-button { @include transy(opacity); } } [data-state=fullquox], [data-state=loading] { - #buttons button { transform: none; } - #current { transform: translateX(100%); } - #palette { transform: translateY(125%); } - #back { transform: translateX(-200%); } + #buttons button { transform: none; } + #current { transform: translateX(100%); } + #palette { transform: translateY(125%); } + #download-button { opacity: 0; } + #back { transform: translateX(-200%); } } [data-state=fullquox] {