Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Give every landscape a second and third hue

df894c4f529477af69f50f52662bb7517843c3c9 · Keenan Nemetz · 7d ago · parent 51381592

Give every landscape a second and third hue

A mark was one palette hue in shades of itself, so a field of avatars was
seven colours in seven degrees of darkness. That is not enough for an
avatar to tell people apart.

The sky is still the account's tone, but the hills now take other tones
from the same seven, chosen from a name-seeded shuffle and never the
sky's own hue. Each hill is shaded toward the palette's darkest ground
rather than toward black, which darkens it and ties different hues into
one landscape. Every mark therefore carries at least two hues, and most
carry three plus the disc.

The seven grounds moved to :root so the bundle reads the palette from
vole.css instead of carrying a second copy of it. A tone class now just
points at one.

Colour sets go from 21 per theme to 7 skies x (30 two-hill orders + 120
three-hill orders) x 3 discs = 3,150, before ridge, radius and disc
position vary them further. keenan, which was clay and four shades of
clay, is now clay with an olive hill, a slate hill and a moon.

scripts/avatars-entry.js +45−9

@@ -23,15 +23,24 @@ const moonColors = ["f0e6d2", "e7e8ea", "cdd6d5"];
23 23
24 24 // Full daylight would wash out at 20px and read as a pastel dot; this is the
25 25 // last hour of it. The mark is drawn in the 100-unit box DiceBear gives a style,
26 // and the ground comes from the element so vole.css stays the only palette: the
27 // sky is the account's tone and the hills are steps darker than it, which is what
28 // makes a landscape read at 17px without an outline.
29 const depth = [0.34, 0.5, 0.62];
26 // and the palette comes from vole.css: the sky is the account's tone, the hills
27 // take other tones from the same seven, and all of them are dark enough to read
28 // at 17px without an outline.
29 const depth = [0.4, 0.56, 0.68];
30 30
31 // Hills are shaded toward the palette's darkest ground rather than toward black:
32 // it darkens them and ties different hues together, so a clay sky with river
33 // hills still reads as one landscape.
34 const ink = "272e33";
31 35 const shade = (hex, step) => {
32 const n = parseInt(hex, 16);
33 const channel = (v) => Math.round(v * (1 - step)).toString(16).padStart(2, "0");
34 return channel((n >> 16) & 255) + channel((n >> 8) & 255) + channel(n & 255);
36 const a = parseInt(hex, 16);
37 const b = parseInt(ink, 16);
38 const channel = (shift) => {
39 const va = (a >> shift) & 255;
40 const vb = (b >> shift) & 255;
41 return Math.round(va + (vb - va) * step).toString(16).padStart(2, "0");
42 };
43 return channel(16) + channel(8) + channel(0);
35 44 };
36 45
37 46 // DiceBear ships no landscape style, so this one is Vole's. It uses only the
@@ -49,6 +58,7 @@ const landscape = {
49 58 type: "object",
50 59 properties: {
51 60 skyColor: { type: "array", items: { type: "string" }, default: ["6b7561"] },
61 palette: { type: "array", items: { type: "string" }, default: [] },
52 62 discColor: { type: "array", items: { type: "string" }, default: sunColors },
53 63 hillCount: { type: "array", items: { type: "integer", minimum: 2, maximum: 3 }, default: [2, 3] },
54 64 },
@@ -59,6 +69,15 @@ const landscape = {
59 69 const hills = prng.pick(options.hillCount ?? [2, 3], 2);
60 70 const body = [`<rect width="100" height="100" fill="#${sky}"/>`];
61 71
72 // Hills take hues from the rest of the palette, never the sky's own, so every
73 // mark has at least two colours. Shuffling keeps the order stable per seed
74 // while making the pairing depend on the name rather than on the sky.
75 const ground = (options.palette ?? []).filter((c) => c && c !== sky);
76 for (let i = ground.length - 1; i > 0; i--) {
77 const j = prng.integer(0, i);
78 [ground[i], ground[j]] = [ground[j], ground[i]];
79 }
80
62 81 // The sun or moon sits above the highest ridge: the tallest a hill may rise
63 82 // is 66, so the highest a ridge can reach is y=34 and the disc never sets
64 83 // behind it.
@@ -67,12 +86,14 @@ const landscape = {
67 86 );
68 87
69 88 // Each hill is a circle whose centre is below the box, so only the ridge
70 // shows. Drawing them back to front, each a step darker, gives the depth.
89 // shows. Back to front, each one is a palette hue shaded a step darker, so
90 // the depth reads without an outline.
71 91 for (let i = 0; i < hills; i++) {
72 92 const r = prng.integer(70, 130);
73 93 const rise = prng.integer(40, 66) - i * prng.integer(5, 10);
94 const hue = ground.length ? ground[i % ground.length] : sky;
74 95 body.push(
75 `<circle cx="${prng.integer(-20, 120)}" cy="${100 + r - rise}" r="${r}" fill="#${shade(sky, depth[i])}"/>`,
96 `<circle cx="${prng.integer(-20, 120)}" cy="${100 + r - rise}" r="${r}" fill="#${shade(hue, depth[i])}"/>`,
76 97 );
77 98 }
78 99 return { attributes: { viewBox: "0 0 100 100" }, body: body.join("") };
@@ -91,6 +112,20 @@ const ground = (el) => {
91 112 .join("");
92 113 };
93 114
115 // The seven grounds, read once from the root where vole.css keeps them. Reading
116 // them keeps this file from carrying a second copy of the palette.
117 let palette;
118 const grounds = () => {
119 if (palette) return palette;
120 const css = getComputedStyle(document.documentElement);
121 palette = [];
122 for (let i = 1; i <= 7; i++) {
123 const value = css.getPropertyValue(`--av-c${i}`).trim();
124 if (/^#[0-9a-f]{6}$/i.test(value)) palette.push(value.slice(1));
125 }
126 return palette;
127 };
128
94 129 // The theme is on <html> and one of the six Everforest variants, because
95 130 // theme.js resolves "system" to a concrete one before the first paint.
96 131 const moonlit = () => (document.documentElement.getAttribute("data-theme") || "").includes("dark");
@@ -108,6 +143,7 @@ const render = (el) => {
108 143 clip: false,
109 144 backgroundColor: [],
110 145 skyColor: sky ? [sky] : undefined,
146 palette: grounds(),
111 147 discColor: moonlit() ? moonColors : sunColors,
112 148 hillCount: [2, 3],
113 149 }).toString();