eleven / messenger Public

web,prototype: dust motes go light-on-dark in dark themes (#360)

The loader's stage dust used --muted for every speck — a mid-grey that
all but vanishes on a dark background. The dust now contrasts with the
floor it sits on: dark specks (--muted) on a light background, LIGHT
specks (--ink) on a dark one. The product loader judges darkness by the
RESOLVED body background luminance (the user's theme + color-scheme
decide it, not the OS media query alone); the prototype uses the OS
query (it has no theme picker) and re-seeds on scheme change.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 7b083f55b8511b04ae38222d718aef135e55c520 parent a6d5239
2 files changed, +12 −2
  • prototypes/space-lever.html +5 −1
  • web/static/shell.js +7 −1
diff --git a/prototypes/space-lever.html b/prototypes/space-lever.html
index b517029..393810b 100644
--- a/prototypes/space-lever.html
+++ b/prototypes/space-lever.html
@@ -239,7 +239,10 @@ function initDust() {
const g = canvas.getContext("2d");
g.scale(dpr, dpr);
const css = getComputedStyle(document.documentElement);
- const ink = css.getPropertyValue("--muted").trim();
+ // Dust must contrast with the floor it sits on: dark specks (muted) on a light
+ // background, LIGHT specks (ink) on a dark one — muted-on-dark all but vanishes.
+ const darkMode = matchMedia("(prefers-color-scheme: dark)").matches;
+ const ink = css.getPropertyValue(darkMode ? "--ink" : "--muted").trim();
const accent = css.getPropertyValue("--accent").trim();
const parts = Array.from({ length: P.motes }, () => ({
x: Math.random() * w, y: h - 2 - Math.random() * 2, vx: 0, vy: 0,
@@ -539,6 +542,7 @@ bind("hold", "hold"); bind("shake", "shake"); bind("crank", "crank");
bind("kick", "kick", (v) => v.toFixed(2)); bind("motes", "motes"); bind("build", "build");
addEventListener("resize", initDust);
+matchMedia("(prefers-color-scheme: dark)").addEventListener("change", initDust);
initDust();
rafId = requestAnimationFrame(tick);
</script>
diff --git a/web/static/shell.js b/web/static/shell.js
index 484d9d6..4cf51fb 100644
--- a/web/static/shell.js
+++ b/web/static/shell.js
@@ -507,7 +507,13 @@ function initDust() {
if (!g) return;
g.scale(dpr, dpr);
const css = getComputedStyle(document.documentElement);
- const ink = (css.getPropertyValue("--muted") || "#888").trim();
+ // Dust must contrast with the floor it sits on: dark specks (muted) on a light
+ // background, LIGHT specks (ink) on a dark one — muted-on-dark all but
+ // vanishes. Judge dark by the RESOLVED page background (the user's theme +
+ // color-scheme decide it, not the OS media query alone).
+ const bgRGB = ((getComputedStyle(document.body).backgroundColor || "").match(/\d+/g) || []).map(Number);
+ const darkMode = bgRGB.length >= 3 && 0.299 * bgRGB[0] + 0.587 * bgRGB[1] + 0.114 * bgRGB[2] < 128;
+ const ink = (css.getPropertyValue(darkMode ? "--ink" : "--muted") || "#888").trim();
const accent = (css.getPropertyValue("--accent") || "#7c7cf4").trim();
const parts = Array.from({ length: Math.min(110, Math.max(60, Math.round(w / 14))) }, () => ({
x: Math.random() * w,