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.
Read the colour the browser actually resolved
aceaabbf44f3e20ab97e39c1de5047d11547a283 · Keenan Nemetz · 7d ago · parent 214145e5
Read the colour the browser actually resolved The palette came out black-skied and oversaturated because the bundle read every colour with "the first three numbers", and getComputedStyle serializes a color-mix() result as color(srgb 0.571765 0.658824 0.461961). Those decimals contain digits, so the parser built #00b36b600 — not a colour — for the sky and garbage for every hill. Parse both forms the browser can return: rgb()/rgba() for a legacy sRGB value and color(srgb r g b) with components in 0..1 for one that came through color-mix(). Clamp, because color() components are floats. Reproduced and checked in headless Chromium against a real instance, in both themes and at both sizes, before and after: the fills are now #bba575 / #9da9a0 / #886759 / #596855 / #445a52 on dark-medium and #e7c76e / #f57d26 / #b6987a / #889371 / #6f8984 on light-medium.
scripts/avatars-entry.js +32−15
| @@ -88,9 +88,38 @@ const landscape = { | |||
| 88 | 88 | }, | |
| 89 | 89 | }; | |
| 90 | 90 | ||
| 91 | // getComputedStyle serializes a resolved colour two ways: rgb()/rgba() for a | ||
| 92 | // legacy sRGB value, and color(srgb r g b) with components in 0..1 when the value | ||
| 93 | // came through color-mix(). Reading "the first three numbers" therefore splits | ||
| 94 | // 0.571765 into 0 and 571765 and builds a six-digit-looking string that is not a | ||
| 95 | // colour; the browser then paints the sky black. Parse both forms, and clamp, | ||
| 96 | // because color() components are floats. | ||
| 97 | const toHex = (value) => { | ||
| 98 | const text = value.trim(); | ||
| 99 | const srgb = /^color\(srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)/.exec(text); | ||
| 100 | if (srgb) { | ||
| 101 | return [1, 2, 3] | ||
| 102 | .map((i) => Math.round(Math.min(1, Math.max(0, parseFloat(srgb[i]))) * 255)) | ||
| 103 | .map((n) => n.toString(16).padStart(2, "0")) | ||
| 104 | .join(""); | ||
| 105 | } | ||
| 106 | const legacy = /^rgba?\(([^)]+)\)/.exec(text); | ||
| 107 | if (legacy) { | ||
| 108 | const parts = legacy[1].split(/[\s,/]+/).filter(Boolean).map(Number); | ||
| 109 | if (parts.length >= 3) { | ||
| 110 | return parts | ||
| 111 | .slice(0, 3) | ||
| 112 | .map((n) => Math.round(Math.min(255, Math.max(0, n)))) | ||
| 113 | .map((n) => n.toString(16).padStart(2, "0")) | ||
| 114 | .join(""); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | return ""; | ||
| 118 | }; | ||
| 119 | |||
| 91 | 120 | // A custom property can hold a color-mix(), and getComputedStyle reads that back | |
| 92 | 121 | // as text rather than as a colour. One hidden element resolves any of them to the | |
| 93 | // rgb the browser actually paints, so the palette follows the theme exactly. | ||
| 122 | // colour the browser actually paints, so the palette follows the theme exactly. | ||
| 94 | 123 | let probe; | |
| 95 | 124 | const resolve = (name) => { | |
| 96 | 125 | if (!probe) { | |
| @@ -99,12 +128,7 @@ const resolve = (name) => { | |||
| 99 | 128 | document.body.appendChild(probe); | |
| 100 | 129 | } | |
| 101 | 130 | probe.style.color = `var(${name})`; | |
| 102 | const parts = (getComputedStyle(probe).color.match(/\d+/g) || []).map(Number); | ||
| 103 | if (parts.length < 3) return ""; | ||
| 104 | return parts | ||
| 105 | .slice(0, 3) | ||
| 106 | .map((n) => n.toString(16).padStart(2, "0")) | ||
| 107 | .join(""); | ||
| 131 | return toHex(getComputedStyle(probe).color); | ||
| 108 | 132 | }; | |
| 109 | 133 | ||
| 110 | 134 | // The palette follows the active Everforest variant, so it is resolved once per | |
| @@ -130,14 +154,7 @@ const themePalette = () => { | |||
| 130 | 154 | // The sky comes from the element, not from this file: vole.css and everforest.css | |
| 131 | 155 | // own the seven grounds, and reading the computed colour keeps the initial the | |
| 132 | 156 | // server renders and the finished landscape on the same sky. | |
| 133 | const ground = (el) => { | ||
| 134 | const parts = (getComputedStyle(el).backgroundColor.match(/\d+/g) || []).map(Number); | ||
| 135 | if (parts.length < 3) return ""; | ||
| 136 | return parts | ||
| 137 | .slice(0, 3) | ||
| 138 | .map((n) => n.toString(16).padStart(2, "0")) | ||
| 139 | .join(""); | ||
| 140 | }; | ||
| 157 | const ground = (el) => toHex(getComputedStyle(el).backgroundColor); | ||
| 141 | 158 | ||
| 142 | 159 | const render = (el) => { | |
| 143 | 160 | if (el.querySelector("svg")) return; | |