diff --git a/Makefile b/Makefile index 59126da..243abe0 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,15 @@ -PAGES = index.html pubkey.txt rainbow-quox/index.html \ - dnd/index.html $(wildcard dnd/*/index.html) +PAGES = index.html pubkey.txt rainbow-quox/index.html velzek/index.html MEDIA = \ $(wildcard media/*.png) $(wildcard media/*.gif) $(wildcard media/*.webp) \ $(wildcard media/flags/*) $(wildcard media/buttons/*) \ $(wildcard media/icons/*) $(wildcard media/bg/*) 8831.png 8831-quox.png \ $(wildcard rainbow-quox/front/*) $(wildcard rainbow-quox/back/*) \ $(wildcard rainbow-quox/*.svg) rainbow-quox/palette.svg \ - $(wildcard dnd/*.png) $(wildcard dnd/*.webp) $(wildcard dnd/*/*.webp) + $(wildcard velzek/*.webp) $(wildcard velzek/*.png) CSS = $(shell find fonts -type f) \ $(patsubst %.scss,%.css, \ $(wildcard rainbow-quox/style/*) $(wildcard style/*)) \ - dnd/base.css dnd/bio.css dnd/index.css $(wildcard dnd/*/style.css) + velzek/style.css 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/dnd/bio.css b/dnd/bio.css deleted file mode 100644 index 39e00cb..0000000 --- a/dnd/bio.css +++ /dev/null @@ -1,38 +0,0 @@ -@import url(base.css); - -@layer base.headings { - h2 { - margin: 2rem 3rem; - position: relative; - - border-bottom: 3px double currentcolor; - - &::before { - font-feature-settings: "ornm" 5; - position: absolute; - left: -1.15em; - bottom: 7%; - rotate: -5deg; - } - } -} - -@layer base.other { - table { - min-width: 20rem; - margin: 0 3rem; - border-bottom: 2px solid currentcolor; - } - caption { - border-top: 2px solid currentcolor; - border-bottom: 1px solid currentcolor; - - font-weight: 700; - text-align: center; - .and { font-weight: 450; } - } - - th, td, caption { padding: 0.125lh 0.5em 0.0625lh; } - - tbody th { text-align: right; } -} diff --git a/dnd/index.css b/dnd/index.css deleted file mode 100644 index 16f6994..0000000 --- a/dnd/index.css +++ /dev/null @@ -1,71 +0,0 @@ -@import url(base.css); - -@layer { - header { row-gap: 0; } - header p { - grid-area: 2/1/2/4; - margin: 0; - text-align: center; - - font-size: 1.5rem; - font-weight: 600; - font-stretch: 90%; - } -} - -@layer { - .portrait { - margin: 0; - border: 4px solid currentcolor; - box-shadow: var(--shadow); - img { - display: block; - width: 200px; - height: 200px; - } - } - - .char { - width: 80%; - margin-left: auto; - margin-right: auto; - - padding-left: 1em; - padding-right: 1em; - - display: grid; - gap: 0.5em 2em; - - &:nth-of-type(odd) { - grid-template: - "portrait name" auto - "portrait desc" 1fr / min-content auto; - } - &:nth-of-type(even) { - grid-template: - "name portrait" auto - "desc portrait" 1fr / auto min-content; - } - - .portrait { grid-area: portrait; } - h2 { grid-area: name; margin: 0; } - > div, p { grid-area: desc; align-self: start; } - } -} - -@layer { - .char { - margin-top: 2em; - padding-top: 2em; - border-top: 3px double currentcolor; - } - .char:last-of-type { - padding-bottom: 2em; - border-bottom: 3px double currentcolor; - } - - h2 small { - font-size: 70%; - font-weight: 700; - } -} diff --git a/dnd/index.html b/dnd/index.html deleted file mode 100644 index 5237e5a..0000000 --- a/dnd/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - -d&d chars - - - - - -
-

d&d chars

-

blorbos from my tabletop -

- - -
-
- - - -
- -

- velzek hawthorne -

- -

- anthropologist observing the humanoid society in marikest, when a suspicious - sequence of events plunge the city into chaos. - (2024–) -

- -
-
- - - -
- -

- (call me) marigold -

- -

- disgraced acolyte of bahamut sent out into the world to atone for her - mistakes. - (2024–) -

- -
-
- -
- -

- nex -

- -

- went to hell and back to stop the world from disintegrating. you know, - normal stuff. - (2020–24) -

- -
-
- -
- -

- kezda -

- -

- little lizard who saw magic one time and never stopped thinking about it -
- (2019; game abandoned but i still like this lil gremlin) -

- - - diff --git a/dnd/kezda.webp b/dnd/kezda.webp deleted file mode 100644 index 9615727..0000000 --- a/dnd/kezda.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84889cc3c07b72572efac3d4a2aa5120946c2f3689d21de548b3b2ed0418f258 -size 7602 diff --git a/dnd/kezda/index.html b/dnd/kezda/index.html deleted file mode 100644 index 39fa785..0000000 --- a/dnd/kezda/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - -kezda - - - - - - -
-

kezda

-
- -
-

basic info

- -
- kezda with their familar poking out of a hole in their hat -
- - a lil creature -
- - diff --git a/dnd/kezda/kezda.webp b/dnd/kezda/kezda.webp deleted file mode 100644 index 68c7cc3..0000000 --- a/dnd/kezda/kezda.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1522761b3c8c882348018df874c5166ea6f2257e0ffc60ad2e7b5b14bc3cf589 -size 34762 diff --git a/dnd/kezda/kezda2x.webp b/dnd/kezda/kezda2x.webp deleted file mode 100644 index 3307bc5..0000000 --- a/dnd/kezda/kezda2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3bdfdca3389270f2828515c7fc89255f464d560d05a595c45b1043fb5dc27ec -size 86876 diff --git a/dnd/kezda/src/kezda-full.kra b/dnd/kezda/src/kezda-full.kra deleted file mode 100644 index 6cfc0da..0000000 --- a/dnd/kezda/src/kezda-full.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ddb0310e2477891474493330e6af61d5a78c3e3ba7fc2e639d54613075d29bdd -size 1806236 diff --git a/dnd/kezda/style.css b/dnd/kezda/style.css deleted file mode 100644 index 5117218..0000000 --- a/dnd/kezda/style.css +++ /dev/null @@ -1,3 +0,0 @@ -@import url(../bio.css); - - diff --git a/dnd/kezda2x.webp b/dnd/kezda2x.webp deleted file mode 100644 index 0e95e0e..0000000 --- a/dnd/kezda2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76ab6baf47f3a3da6702629dc8b33faf18a644240fb977276ceb4715392eecdd -size 29554 diff --git a/dnd/marigold.webp b/dnd/marigold.webp deleted file mode 100644 index 5b4c9d2..0000000 --- a/dnd/marigold.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4d211a5b1e6c080c216b7c4df974a088a56fc692aac7c26cca25dd10194d566b -size 13806 diff --git a/dnd/marigold/index.html b/dnd/marigold/index.html deleted file mode 100644 index 387e731..0000000 --- a/dnd/marigold/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - -marigold - - - - - - - -
-

marigold

-
- -
-

basic info

- - - -
warlock of bahamut -
height 198 cm (6ʹ 6ʺ) -
weight 121 kg (267 ℔) -
age 37 -
- -

- let’s get it out of the way. her actual name is, um, let’s see here… - [ˈqχḁʂx̩kɬ̩ːχ]. which is why she goes by ‘marigold’ - among the humanoids. -

- - diff --git a/dnd/marigold/style.css b/dnd/marigold/style.css deleted file mode 100644 index 4ee941d..0000000 --- a/dnd/marigold/style.css +++ /dev/null @@ -1,6 +0,0 @@ -@import url(../bio.css); - -.ipa { - font-feature-settings: "ss03" 1; - font-variation-settings: "ENLA" 0; -} diff --git a/dnd/marigold2x.webp b/dnd/marigold2x.webp deleted file mode 100644 index 60ed6e4..0000000 --- a/dnd/marigold2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a568dbba03fc5694bff95dd39785dcd60c44d93129396078a2f0d1dddcbcff7 -size 59848 diff --git a/dnd/nex.kra-autosave.kra b/dnd/nex.kra-autosave.kra deleted file mode 100644 index 35651f4..0000000 --- a/dnd/nex.kra-autosave.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e112a526af68ebc0d3d38fc0778383a9774e4cc7a793e3261b7c861c44ca235 -size 266113 diff --git a/dnd/nex.webp b/dnd/nex.webp deleted file mode 100644 index 5d40a2b..0000000 --- a/dnd/nex.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:040cb5a75480d2531220fc9ffe7ae3b53f1972859bc707615e4946bd909e7dcc -size 11400 diff --git a/dnd/nex/index.html b/dnd/nex/index.html deleted file mode 100644 index 3a0afcb..0000000 --- a/dnd/nex/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - -nex - - - - - - - -
-

nex

-
- -
-

basic info

-
- - diff --git a/dnd/nex2x.webp b/dnd/nex2x.webp deleted file mode 100644 index 7b044a6..0000000 --- a/dnd/nex2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:532577340ef26027f56d0e2bb64af46759fc0409b4ce2506c7c10249af16e82d -size 38638 diff --git a/dnd/src/kezda.kra b/dnd/src/kezda.kra deleted file mode 100644 index 8128943..0000000 --- a/dnd/src/kezda.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5a1223583e49404f8c793193f4d646844102e2c419162ac750c59528b22c00a -size 1350142 diff --git a/dnd/src/marigold.kra b/dnd/src/marigold.kra deleted file mode 100644 index 99ae534..0000000 --- a/dnd/src/marigold.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6dd0f7aaceda0d3d48f080af4af3cea64ecb8ec642ed1f30243fea6eeb5832a5 -size 5978311 diff --git a/dnd/src/nex.kra b/dnd/src/nex.kra deleted file mode 100644 index 131d5dc..0000000 --- a/dnd/src/nex.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecdb9ea0372c2de9af613092452b92faa2baa615ea222f01c98373a67bc26201 -size 6118966 diff --git a/dnd/src/velzek.kra b/dnd/src/velzek.kra deleted file mode 100644 index ff5502c..0000000 --- a/dnd/src/velzek.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eedeb81b2fdd30a7186c4d9a4b794383803eeb750451ce0b387b9eec4a4a056e -size 7874406 diff --git a/dnd/velzek.webp b/dnd/velzek.webp deleted file mode 100644 index e9ea264..0000000 --- a/dnd/velzek.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4787f6f3a64557f9f1ba013ce3af6411b3ebe6fe4044de45b6bff79fd230510 -size 13502 diff --git a/dnd/velzek/armour.s.webp b/dnd/velzek/armour.s.webp deleted file mode 100644 index 28a6d27..0000000 --- a/dnd/velzek/armour.s.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f21e7756e02bd2fad3b40afea724980e1785fe71379abbbd06ba5aaa2c616911 -size 36174 diff --git a/dnd/velzek/armour.s2x.webp b/dnd/velzek/armour.s2x.webp deleted file mode 100644 index b71b42a..0000000 --- a/dnd/velzek/armour.s2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:454cf6c7b94fabb17e3db8677037b90e2d57c409af5d7d927e341170b6d7811f -size 91868 diff --git a/dnd/velzek/clothes.s.webp b/dnd/velzek/clothes.s.webp deleted file mode 100644 index 8d33088..0000000 --- a/dnd/velzek/clothes.s.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0ccfc7ee5c154ca2c439edb2b7ce781a15adedc13af9101c72652de0674ddad -size 39720 diff --git a/dnd/velzek/clothes.s2x.webp b/dnd/velzek/clothes.s2x.webp deleted file mode 100644 index dc66fbe..0000000 --- a/dnd/velzek/clothes.s2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:462307197185a1c7298acb4563e271775dce1055abb239c3b9d1a805c2f604f1 -size 99460 diff --git a/dnd/velzek/map_k.s.webp b/dnd/velzek/map_k.s.webp deleted file mode 100644 index 90c4adf..0000000 --- a/dnd/velzek/map_k.s.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b63f59886ba49ad391235c91193a76a68839f5a754fcf72d63652676bb3196f -size 74660 diff --git a/dnd/velzek/map_k.s2x.webp b/dnd/velzek/map_k.s2x.webp deleted file mode 100644 index b011374..0000000 --- a/dnd/velzek/map_k.s2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a7ce46d4514e066c859bc07243a65ef9974aa5a89f69c6501f25f9e29e8f6d7 -size 281220 diff --git a/dnd/velzek/src/map_k_full.kra b/dnd/velzek/src/map_k_full.kra deleted file mode 100644 index 3b682d2..0000000 --- a/dnd/velzek/src/map_k_full.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9eeb8c0af5989115f66288858bcbab1b79eea6f02920de077f1abd846a0a4b4 -size 43950802 diff --git a/dnd/velzek/src/suveesha.kra b/dnd/velzek/src/suveesha.kra deleted file mode 100644 index 7852db2..0000000 --- a/dnd/velzek/src/suveesha.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee1c7d69c79ceada273e5e67152fa72dbad05085c11449f6817925a648f5e337 -size 3564434 diff --git a/dnd/velzek/src/velzek.kra b/dnd/velzek/src/velzek.kra deleted file mode 100644 index 111537f..0000000 --- a/dnd/velzek/src/velzek.kra +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b27b27fc732464790da6db965a5c8a06a53de63d163663d0e72009760435423e -size 54608598 diff --git a/dnd/velzek/style.css b/dnd/velzek/style.css deleted file mode 100644 index 144c30d..0000000 --- a/dnd/velzek/style.css +++ /dev/null @@ -1,8 +0,0 @@ -@import url(../bio.css); - -@layer { - #char-pic { - shape-outside: polygon(100% 0%, 13% 0%, 13% 25%, 0% 27%, - 0% 51%, 18% 60%, 21% 100%, 100% 100%); - } -} diff --git a/dnd/velzek/suveesha.s.webp b/dnd/velzek/suveesha.s.webp deleted file mode 100644 index 5f24280..0000000 --- a/dnd/velzek/suveesha.s.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35ed9e13f71104a2fb06cb48952fafc8e3710abb7e23b94f21782a4163956031 -size 42238 diff --git a/dnd/velzek/suveesha.s2x.webp b/dnd/velzek/suveesha.s2x.webp deleted file mode 100644 index 56403dc..0000000 --- a/dnd/velzek/suveesha.s2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b347734640aa6c35bfbbfe594449d537e8c46d023706db155f9791059c30e6b -size 129808 diff --git a/dnd/velzek2x.webp b/dnd/velzek2x.webp deleted file mode 100644 index 2ce318b..0000000 --- a/dnd/velzek2x.webp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8222f32d216aa50a9094dbde7683d0036d0a2eb2ab0b911817a0e5fc63caaaaf -size 49264 diff --git a/rainbow-quox/edit.svg b/rainbow-quox/edit.svg deleted file mode 100644 index fc19cae..0000000 --- a/rainbow-quox/edit.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rainbow-quox/script/history.ts b/rainbow-quox/script/history.ts index 47fe42e..af8f71e 100644 --- a/rainbow-quox/script/history.ts +++ b/rainbow-quox/script/history.ts @@ -13,11 +13,11 @@ export class HistoryItem { } asHtml(): HTMLButtonElement { - const { lines, outer, belly1: belly, fins1: fins } = this.rgb; + const { lines: bg, outer, belly1: belly, fins1: fins } = this.rgb; const content = ` - + fin colour: ${fins.css()} @@ -53,23 +53,21 @@ export class History { add(name: string): void { this.items.push(name); } - *iterNames(maxLength: number = 100): Iterable { + *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 >= 0 && done > maxLength) break; + if (maxLength !== null && done > maxLength) break; const name = this.items[i]!; - if (!name || seen.has(name)) continue; + seen.add(name); done++; yield name; - seen.add(name); - done++; } } - // pass a negative number to iterate over all - *iterItems(maxLength?: number): Iterable { + *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); @@ -79,26 +77,37 @@ export class History { } static validate(x: unknown): History | undefined { - if (Array.isArray(x) && x.every(i => typeof i == 'string')) - return new History(x); + if (!Array.isArray(x)) return; + if (!x.every(i => typeof i === 'string')) return; + return new History(x); } - toJSON(): unknown { return this.items; } + toJSON() { return this.items; } - save(persist = true): void { + save(persist = true) { const storage = persist ? localStorage : sessionStorage; storage.setItem('history', JSON.stringify(this)); } - // if no history exists, or it's invalid, just start a new one - static load(): History { + // 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 (json === null) return new History; - - return History.validate(JSON.parse(json)) ?? 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 { @@ -106,9 +115,14 @@ export class History { this.save(persist); } - prune(maxLength?: number): void { + 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/layer.ts b/rainbow-quox/script/layer.ts deleted file mode 100644 index 4727b20..0000000 --- a/rainbow-quox/script/layer.ts +++ /dev/null @@ -1,161 +0,0 @@ -import * as Color from './color.js'; - -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); -} - - -export 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); -} - -export function loadImageData(url: string, buf?: Buffer): Promise { - if (buf && navigator.locks) return loadDataLocking(url, buf); - else return loadDataFresh(url); -} - - -export const WIDTH = 1040; -export const HEIGHT = 713; - -export function makeBuffer(width = WIDTH, height = HEIGHT): Buffer { - return new OffscreenCanvas(width, height).getContext('2d')!; -} - -function makeBufferIfLocks(width?: number, height?: number): Buffer | undefined { - if (navigator.locks) return makeBuffer(width, height); -} - -export type Layer = 'stroke' | 'static' | 'eyeshine' | Color.Layer; - -// in compositing order -export const allLayers: Layer[] = - ['stroke', 'static', 'outer', 'spines', 'stripes', 'cuffs', 'fins1', 'fins2', - 'fins3', 'belly1', 'belly2', 'masks', 'claws', 'vitiligo1', 'vitiligo2', - 'vitiligo3', 'vitiligo4', 'eyes', 'eyeshine', 'lines']; - -export function makeLayerInfo(f: (l: Layer) => A): Record { - return Object.fromEntries(allLayers.map(l => [l, f(l)])) as Record; -} - -export async function makeLayerInfoAsync(f: (l: Layer) => Promise): -Promise> { - let list = await Promise.all(allLayers.map(l => f(l).then(res => [l, res]))); - return Object.fromEntries(list); -} - - -export function loadLayers(dir: string): Promise> { - let buf = makeBufferIfLocks(WIDTH, HEIGHT); - return makeLayerInfoAsync(l => loadImageData(`./${dir}/${l}.webp`, buf)); -} - - -export type Position = [x: number, y: number]; -export type Positions = Record; - -export async function loadPos(dir: string): Promise { - return (await fetch(`./${dir}/pos.json`)).json(); -} - - -export type Side = 'front' | 'back'; - -export function swapSide(s: Side): Side { - return s == 'front' ? 'back' : 'front'; -} - -export type SideData = Record; - -export type Data = { - front: SideData, back: SideData, - frontImage?: ImageData, backImage?: ImageData, -}; - -export type ComposedData = Required; - -export async function loadData(): Promise { - let [fl, fp, bl, bp] = await Promise.all([ - loadLayers('front'), loadPos('front'), - loadLayers('back'), loadPos('back') - ]); - return { - front: makeLayerInfo(l => [fl[l], fp[l]]), - back: makeLayerInfo(l => [bl[l], bp[l]]), - } -} - - -function recolor({ 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; - } -} - -export async function recolorAll(layers: Data, cols: Color.Rgbs) { - await Promise.all(Color.allLayers.map(l => { - recolor(layers.front[l][0], cols[l]); - recolor(layers.back[l][0], cols[l]); - })); - delete layers.frontImage; delete layers.backImage; -} - -export 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); -} - -export async function -ensureComposed(buf: Buffer, data: Data): 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']; - } -} - - -export async function redraw(ctx: CanvasRenderingContext2D, - buf: Buffer, data: ComposedData, side: Side) { - await ensureComposed(buf, data); - ctx.putImageData(data[`${side}Image`], 0, 0); -} diff --git a/rainbow-quox/script/palette.ts b/rainbow-quox/script/palette.ts deleted file mode 100644 index 1b4501b..0000000 --- a/rainbow-quox/script/palette.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Rgb, Rgbs, rgb } from './color.js'; -import { Layer } from './layer.js'; - -export type Color = - Exclude - | 'collars' | 'bells' | 'tongues' | 'socks' | 'sclera'; - -// in palette order -export const COLORS: Color[] = - ['lines', 'outer', 'vitiligo1', 'spines', 'fins1', 'fins2', 'fins3', - 'vitiligo4', 'belly1', 'vitiligo3', 'belly2', 'vitiligo2', 'sclera', - 'eyes', 'tongues', 'masks', 'claws', 'socks', 'stripes', 'cuffs', - 'collars', 'bells']; - -export const NAMES: Partial> = { - outer: 'outer body', - stripes: 'sock stripes', - cuffs: 'sock cuffs', - fins1: 'fins (outer)', - fins2: 'fins (mid)', - fins3: 'fins (inner)', - belly1: 'belly 1', - belly2: 'belly 2', - vitiligo1: 'outer body vitiligo', - vitiligo2: 'belly 2 vitiligo', - vitiligo3: 'belly 1 vitiligo', - vitiligo4: 'fins vitiligo', -}; - -export function name(l: Color): string { - return NAMES[l] ?? l; -} - -export type StaticColor = Exclude; - -export const STATIC_COLS: Record = { - collars: rgb(206, 75, 101), - bells: rgb(235, 178, 79), - tongues: rgb(222, 165, 184), - socks: rgb(238, 239, 228), - sclera: rgb(238, 239, 228), -}; - -export function get(col: Color, palette: Rgbs): Rgb { - type PPalette = Partial>; - let p = palette as PPalette; - let s = STATIC_COLS as PPalette; - return (p[col] ?? s[col])!; -} - -export function make(seed: string, palette: Rgbs): Blob { - let lines = [ - "GIMP Palette\n", - `Name: quox ${seed}\n`, - "Columns: 6\n\n", - ]; - - for (const col of COLORS) { - let { r, g, b } = get(col, palette); - lines.push(`${r} ${g} ${b} ${name(col)}\n`); - } - - return new Blob(lines, { type: 'application/x-gimp-palette' }); -} diff --git a/rainbow-quox/script/quox.ts b/rainbow-quox/script/quox.ts index ab19ebf..0228127 100644 --- a/rainbow-quox/script/quox.ts +++ b/rainbow-quox/script/quox.ts @@ -1,16 +1,177 @@ import * as Color from './color.js'; import { History } from './history.js'; -import * as Layer from './layer.js'; -import * as Palette from './palette.js'; -function message(msg: string, size = 100) { +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 = 1040; +const HEIGHT = 713; + +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 = 'stroke' | 'static' | 'eyeshine' | Color.Layer; + +// in compositing order +export const allLayers: Layer[] = + ['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; +} + +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, Layer.WIDTH, Layer.HEIGHT); + ctx.clearRect(0, 0, WIDTH, HEIGHT); ctx.font = `bold ${size}px Muller, sans-serif`; ctx.textAlign = 'center'; - ctx.fillText(msg, Layer.WIDTH/2, Layer.HEIGHT/2, Layer.WIDTH-10); + ctx.fillText(msg, WIDTH/2, HEIGHT/2, WIDTH-10); ctx.restore(); } @@ -26,19 +187,19 @@ function updateUrl(seed: string): void { type ApplyStateOpts = { seed: string, - side?: Layer.Side, + side?: Side, firstLoad?: boolean, - buf?: Layer.Buffer, + buf?: Buffer, history?: History, done?: Done, }; async function -applyState(data: Layer.Data, opts: ApplyStateOpts): Promise { +applyState(data: LayerData, opts: ApplyStateOpts): Promise { let { side, seed, firstLoad, buf, history, done } = opts; side ??= 'front'; firstLoad ??= false; - buf ??= Layer.makeBuffer(); + buf ??= new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; done ??= () => {}; let rand = new Color.Rand(seed); @@ -47,7 +208,7 @@ applyState(data: Layer.Data, opts: ApplyStateOpts): Promise { const rgb = Color.toRgbs(oklch); const newSeed = rand.alphaNum(); - await Layer.recolorAll(data, rgb); + await recolorLayers(data, rgb); updateBg(oklch); updateSvgs(oklch, rgb); @@ -55,7 +216,7 @@ applyState(data: Layer.Data, opts: ApplyStateOpts): Promise { updateUrl(seed); if (firstLoad) { - await instantUpdateImage(side, await Layer.ensureComposed(buf, data)); + await instantUpdateImage(side, await ensureComposed(buf, data)); done(); } else { await animateUpdateImage(buf, side, data, done); @@ -74,7 +235,7 @@ function getCanvasCtx(id: CanvasId) { } async function -instantUpdateImage(side: Layer.Side, data: Layer.ComposedData) { +instantUpdateImage(side: Side, data: ComposedData) { getCanvasCtx('main').putImageData(data[`${side}Image`], 0, 0); } @@ -83,10 +244,9 @@ type Done = () => void; const noAnim = matchMedia('(prefers-reduced-motion: reduce)'); async function -animateUpdateImage(buf: Layer.Buffer, side: Layer.Side, - data: Layer.Data, done: Done) { +animateUpdateImage(buf: Buffer, side: Side, data: LayerData, done: Done) { if (noAnim.matches) { - instantUpdateImage(side, await Layer.ensureComposed(buf, data)); + instantUpdateImage(side, await ensureComposed(buf, data)); done(); return; } @@ -97,11 +257,11 @@ animateUpdateImage(buf: Layer.Buffer, side: Layer.Side, const aux = getCanvasCtx('aux'); document.documentElement.dataset.running = 'reroll'; - const cdata = await Layer.ensureComposed(buf, data); - Layer.redraw(aux, buf, cdata, side); + const cdata = await ensureComposed(buf, data); + redraw(aux, buf, cdata, side); aux.canvas.addEventListener('animationend', async () => { - await Layer.redraw(main, buf, cdata, side); + await redraw(main, buf, cdata, side); aux.canvas.style.removeProperty('animation'); delete document.documentElement.dataset.running; done(); @@ -113,8 +273,7 @@ animateUpdateImage(buf: Layer.Buffer, side: Layer.Side, } async function -animateSwapImage(buf: Layer.Buffer, newSide: Layer.Side, - data: Layer.ComposedData, done: Done) { +animateSwapImage(buf: Buffer, newSide: Side, data: ComposedData, done: Done) { if (noAnim.matches) { instantUpdateImage(newSide, data); done(); @@ -127,10 +286,10 @@ animateSwapImage(buf: Layer.Buffer, newSide: Layer.Side, const aux = getCanvasCtx('aux'); document.documentElement.dataset.running = 'swap'; - await Layer.redraw(aux, buf, data, newSide); + await redraw(aux, buf, data, newSide); aux.canvas.addEventListener('animationend', async () => { - const image = aux.getImageData(0, 0, Layer.WIDTH, Layer.HEIGHT); + const image = aux.getImageData(0, 0, WIDTH, HEIGHT); main.putImageData(image, 0, 0); main.canvas.style.removeProperty('animation'); @@ -183,7 +342,7 @@ function updateSvgs(oklch: Color.Colors, rgb: Color.Rgbs) { } -function showHistory(history: History, data: Layer.Data, +function showHistory(history: History, data: LayerData, opts: Omit) { const list = document.getElementById('history-items'); if (!list) return; @@ -210,7 +369,7 @@ function showHistory(history: History, data: Layer.Data, function closeHistory() { document.getElementById('history-items')?. - scroll({ top: 0, left: 0, behavior: 'smooth' }); + 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'; @@ -218,7 +377,18 @@ function closeHistory() { function download(seed: string) { const colors = Color.toRgbs(Color.colors(new Color.Rand(seed))); - const blob = Palette.make(seed, colors); + + 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 @@ -233,15 +403,15 @@ function download(seed: string) { async function setup() { message('loading layers…'); - let data = await Layer.loadData().catch(e => { message(e, 30); throw e }); - let history = History.load(); + let data = await loadData().catch(e => { message(e, true); throw e }); + let history = History.loadOrClear(); - let buf = Layer.makeBuffer(); + let buf = new OffscreenCanvas(WIDTH, HEIGHT).getContext('2d')!; let prevSeed = urlState() ?? new Color.Rand().alphaNum(); let seed = await applyState(data, { seed: prevSeed, buf, history, firstLoad: true }); - let side: Layer.Side = 'front'; + let side: Side = 'front'; const reroll = document.getElementById('reroll')!; const swap = document.getElementById('swap')!; @@ -303,8 +473,8 @@ async function setup() { } function runSwap() { run(async k => { - side = Layer.swapSide(side); - const cdata = await Layer.ensureComposed(buf, data); + side = swapSide(side); + const cdata = await ensureComposed(buf, data); await animateSwapImage(buf, side, cdata, k); }); } diff --git a/rainbow-quox/style/defs.scss b/rainbow-quox/style/defs.scss deleted file mode 100644 index 74b1202..0000000 --- a/rainbow-quox/style/defs.scss +++ /dev/null @@ -1,71 +0,0 @@ -$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)); - - -// https://oakreef.ie/transy :) -$transition-duration: 250ms; -$transition-curve: cubic-bezier(.47,.74,.61,1.2); - -@mixin transy($prop: transform, $duration: $transition-duration) { - transition: $prop $duration $transition-curve; -} - -@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.125 var(--hue) / 0.45)); - } -} - - -@mixin box-base { - @include shadow; - // respecify font family for + + + + + + + +

velzek hawthorne

@@ -15,33 +23,36 @@

basic info

-
+
-
- - -
- cleric of suveesha - and anthropologist -
height 74 cm (2ʹ 5ʺ) -
weight 18 kg (40 ℔) -
age 32 -
year of birth 1187 -
+

+ cleric of suveesha + and anthropologist + +

+
height
74 cm (2ʹ 5ʺ) +
weight
18 kg (40 ℔) +
age
32 +
year of birth
1187 +
alignment
neutral good +

velzek is a kobold from a community called @@ -49,26 +60,24 @@ eight years ago, she and four other kobolds arrived at the temple of berei, next to the green on the south border of marikest. - - -
velzek’s companions -
yarva bitterbrush 26, he/him -
keshku æstivæ 28, she/her -
volek ruba 23, she/her -
tokil arceuthus 25, he/him -
- +

+ her companions are: + +

+
yarva bitterbrush
26, he/him + +
keshku aestivae
28, she/her + +
volek ruba
23, she/her + +
tokil arceuthus
25, he/him + +

until arriving in marikest, the concept of a surname was totally unknown to the kobolds, so they all invented names based on plants for themselves once - they became needed. in the city they’ve been under the mentorship of a - halfling named bobbie fairchild. + they became needed.

the purpose of the kobolds’ arrival in marikest is to study outside @@ -82,9 +91,9 @@

ekkel

-
+
- ekkel is north of the ruins of avaro’s dungeon @@ -95,7 +104,7 @@ the windswept wall. despite the historical friction between kobolds and humanoids, ekkel has enjoyed peace for decades, due to its location far away from any major surface roads. like all burrows, ekkel is considered to be a - single huge family; kobolds don’t consciously keep track of closer kinship + single huge family; kobolds don't consciously keep track of closer kinship bonds.

@@ -117,10 +126,8 @@

suveesha

-
- - - +
+

@@ -139,10 +146,10 @@

while amongst the humanoids, velzek and the others have been instructed to - adopt berei’s more familiar symbols: a bundle of wheat, a sickle, and a + adopt berei’s symbols for familiarity: a bundle of wheat, a sickle, and a rising sun.

diff --git a/velzek/map_k.webp b/velzek/map_k.webp new file mode 100644 index 0000000..37fd4e6 --- /dev/null +++ b/velzek/map_k.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54024ca807e077f28d68b3b8e2fa631ac19ad6cbed07b5eef07eade8b05d8af0 +size 184560 diff --git a/dnd/velzek/map_k_full.webp b/velzek/map_k_full.webp similarity index 100% rename from dnd/velzek/map_k_full.webp rename to velzek/map_k_full.webp diff --git a/velzek/src/armour.png b/velzek/src/armour.png new file mode 100644 index 0000000..43e114a --- /dev/null +++ b/velzek/src/armour.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf9c2f10254936207e637901b85e9d8bc3061f68ebb13b6384b8122cf7adcde9 +size 7469685 diff --git a/velzek/src/armour_small.png b/velzek/src/armour_small.png new file mode 100644 index 0000000..752cb2b --- /dev/null +++ b/velzek/src/armour_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f1d46c549d1fb05d711d217f4f2f98b5557e3e081a1e242ff3b09950dcdaacd +size 151304 diff --git a/velzek/src/armour_small2x.png b/velzek/src/armour_small2x.png new file mode 100644 index 0000000..8680197 --- /dev/null +++ b/velzek/src/armour_small2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c84121e5920ebc5066b8d1cd068e77e887951a026181f7e70bec509594fc9f9 +size 465969 diff --git a/velzek/src/clothes.png b/velzek/src/clothes.png new file mode 100644 index 0000000..14bb9ba --- /dev/null +++ b/velzek/src/clothes.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f577c2ee76c5e753642c4862ea21474e3af4ab672334fe85b21daf8361f7438 +size 6055818 diff --git a/velzek/src/clothes_small.png b/velzek/src/clothes_small.png new file mode 100644 index 0000000..33f79e3 --- /dev/null +++ b/velzek/src/clothes_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f65b2da85d8296cffff5ca000e675b52e45a4a0bad4dd88a828b2640aad9f015 +size 143908 diff --git a/velzek/src/clothes_small2x.png b/velzek/src/clothes_small2x.png new file mode 100644 index 0000000..bb60850 --- /dev/null +++ b/velzek/src/clothes_small2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67a750712a690482ce72ff44ac32f02f5a30623dba238f6fc2bb56e9abe281ab +size 3403727 diff --git a/velzek/src/icon.png b/velzek/src/icon.png new file mode 100644 index 0000000..2f78382 --- /dev/null +++ b/velzek/src/icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f9ef1ed3b15e1d2c3738c0149df8443f33927fdcee0c7c0852a206d360f9a29 +size 36939 diff --git a/dnd/velzek/src/koboldowo.png b/velzek/src/koboldowo.png similarity index 100% rename from dnd/velzek/src/koboldowo.png rename to velzek/src/koboldowo.png diff --git a/velzek/src/map_k.png b/velzek/src/map_k.png new file mode 100644 index 0000000..9b5fe34 --- /dev/null +++ b/velzek/src/map_k.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f435e78dc1db828523485927cc9e0564057c4852081e30c2056f3b9067aef7b4 +size 1186818 diff --git a/velzek/src/map_k_full.kra b/velzek/src/map_k_full.kra new file mode 100644 index 0000000..3df16d5 --- /dev/null +++ b/velzek/src/map_k_full.kra @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bd927a348afc97cbb8ef2e8046f4257ac9eeef8267a7e0ae40a5f64bc08f420 +size 40284398 diff --git a/velzek/src/map_k_full.png b/velzek/src/map_k_full.png new file mode 100644 index 0000000..431ec05 --- /dev/null +++ b/velzek/src/map_k_full.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2363edeaa21e5d41c08f825b0a324d8737b51735bb381cebac7f88031ed3554 +size 15150085 diff --git a/velzek/src/suveesha.kra b/velzek/src/suveesha.kra new file mode 100644 index 0000000..da06749 --- /dev/null +++ b/velzek/src/suveesha.kra @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcd8cb4c8832c86dde464034ca4e61306ea272be787319a4acb4ddfff8f9b45e +size 4644238 diff --git a/velzek/src/suveesha.png b/velzek/src/suveesha.png new file mode 100644 index 0000000..2e38b59 --- /dev/null +++ b/velzek/src/suveesha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5c456d26829ad355c80dc9c5e6affce281010aca5a6d90380cc02c0b8f7d6dd +size 585072 diff --git a/velzek/src/velzek icon.kra b/velzek/src/velzek icon.kra new file mode 100644 index 0000000..9d20665 --- /dev/null +++ b/velzek/src/velzek icon.kra @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75794b67b242c1611e12a43a551351ab184a6fe5e4d1c9a7b583cadcfbbfcc30 +size 5176297 diff --git a/dnd/base.css b/velzek/style.css similarity index 72% rename from dnd/base.css rename to velzek/style.css index 3de9e93..cac4894 100644 --- a/dnd/base.css +++ b/velzek/style.css @@ -1,5 +1,5 @@ -@layer fonts, base, header, footer, images, switcher, phone; -@import url(../fonts/junicodevf/junicodevf.css) layer(fonts); +@layer base, header, footer, images, switcher, phone; +@import url(../fonts/junicodevf/junicodevf.css) layer(base); @layer base { * { box-sizing: border-box; } @@ -10,8 +10,8 @@ --text-color: hsl(350deg 40% 15%); --accent-color: hsl(80deg 50% 35%); --shadow-color: hsl(from var(--text-color) h 20% 3% / 20%); - --shadow: 3px 2px 0 var(--shadow-color); - --shadow2: -2px 0 0 hsl(from var(--text-color) h 20% 7% / 15%); + --shadow: drop-shadow(3px 2px 0 var(--shadow-color)); + --shadow2: drop-shadow(-2px 0 0 hsl(from var(--text-color) h 20% 7% / 15%)); } :root { @@ -34,18 +34,17 @@ background: url(beige-paper.png), hsl(40deg 80% 80%); background-blend-mode: multiply; border: 10px solid currentcolor; - box-shadow: var(--shadow), var(--shadow2); + filter: var(--shadow) var(--shadow2); } + figure { filter: var(--shadow2); } } @layer base.fonts { :root { font-family: JunicodeVF, serif; - --base-features: - "ccmp", "calt", "liga", "loca", "rlig", "kern", "mark", "mkmk", - "ss09", "cv69" 6; /* nice */ - font-feature-settings: var(--base-features); + font-feature-settings: + "ccmp", "calt", "liga", "loca", "rlig", "kern", "mark", "mkmk"; font-variation-settings: "ENLA" 25; font-stretch: 125%; font-weight: 450; @@ -73,6 +72,32 @@ } } +@layer base.headings { + h1, h2, h3, h4, h5, h6 { + font-stretch: 75%; + font-variation-settings: "ENLA" 0; + } + + h2 { + margin: 1em; + position: relative; + border-bottom: 3px double currentcolor; + + font-size: 225%; + font-weight: 500; + + &::before { + content: '•'; + font-size: 80%; + font-feature-settings: "ornm" 5; + position: absolute; + left: -1.15em; + bottom: 7%; + rotate: -5deg; + } + } +} + @layer base.other { a { color: inherit; @@ -86,35 +111,19 @@ font-style: italic; } - .amp { - font-size: 75%; - font-weight: 550; - } + dl { margin: 0 1em; } + dt { font-weight: bold; } + dd { margin: 0; } - small { font-stretch: 100%; } -} - -@layer base.headings { - h1, h2, h3, h4, h5, h6 { - font-stretch: 75%; - font-feature-settings: var(--base-features), - "cv02" 1, "cv08" 1, "cv10" 1, "cv12" 10, "cv14" 6, "cv15" 4, "cv16" 1, - "cv24" 5, "cv38" 2, "cv48" 1; - font-variation-settings: "ENLA" 0; - - small { font-stretch: 65%; } - } - - h2 { - margin: 0 0 0.5rem; - font-size: 225%; - font-weight: 500; - - &::before { - content: '•'; - font-size: 80%; - font-feature-settings: "ornm" 2; + @media (width >= 70rem) { + dl { + display: grid; + grid-template-columns: auto 1fr; + column-gap: 1em; } + + dt { grid-column-start: 1; } + dd { grid-column-start: 2; } } } @@ -164,7 +173,7 @@ } @layer images { - #char-img-holder { + #velzek-img-holder { display: grid; grid-template: "i"; align-items: center; @@ -175,36 +184,37 @@ margin: auto; } - .mainfig { + img { + filter: var(--shadow2); max-width: 100%; + &.bordered { border: 10px solid currentcolor; } } - .bordered { - box-shadow: var(--shadow), var(--shadow2); - border: 10px solid currentcolor; - } - #char-pic { filter: drop-shadow(var(--shadow)); } - - figure img { width: 100%; } @media (width >= 70rem) { - .mainfig { + figure { width: 480px; float: right; margin: 0 calc(0px - var(--protrude)) 1em 1em; img { width: 100%; } } - section:nth-of-type(even) .mainfig { rotate: -1deg; } - section:nth-of-type(odd) .mainfig { rotate: 1.5deg; } - #char-pic { rotate: 0deg; } + #velzek-pic { + shape-outside: polygon(100% 0%, 13% 0%, 13% 25%, 0% 27%, + 0% 51%, 18% 60%, 21% 100%, 100% 100%); + } + + #ekkel-pic { rotate: -2deg; } + #suveesha-pic { rotate: 3deg; } } } @layer switcher { - #pic1, #pic2 { transition: all ease 200ms 175ms; } + #velzek-pic img { + transition: all ease 200ms 175ms; + } - :root:has(#switch:checked) #pic1, - :root:not(:has(#switch:checked)) #pic2 { + :root:has(#switch:checked) #clothes-pic, + :root:not(:has(#switch:checked)) #armour-pic { opacity: 0; transition-delay: 0ms; pointer-events: none; @@ -215,7 +225,7 @@ position: absolute; } - #char-pic label { + #velzek-pic label { display: block; width: min-content; margin: 0 auto 1em; diff --git a/dnd/velzek/suveesha.webp b/velzek/suveesha.webp similarity index 100% rename from dnd/velzek/suveesha.webp rename to velzek/suveesha.webp