eleven / ocho Public

web: loader dust goes to eleven β€” taller hops on the climb, mid-to-top launches at the snap (#285)

* web: at eleven, the loader dust clears the viewport

The dust canvas was a 140px strip, clipping the eleven-snap bounces.
Full-viewport canvas + escape-velocity kicks at level > 1; volume-10
physics unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* web: tune the dust β€” mid-to-top at eleven, taller hops sooner on the climb

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* web: the dust fades out with the knob's bow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 5af97bc21d0da7eeb2339023e203feb6c4c50c1a parent fe4fcce
4 files changed, +31 −14
  • changelog/2026-07-07-dust-goes-to-eleven.md +1 −0
  • web/pages/index.html +3 −2
  • web/static/shell.js +23 −7
  • web/static/style.css +4 −5
diff --git a/changelog/2026-07-07-dust-goes-to-eleven.md b/changelog/2026-07-07-dust-goes-to-eleven.md
new file mode 100644
index 0000000..ba154b1
--- /dev/null
+++ b/changelog/2026-07-07-dust-goes-to-eleven.md
@@ -0,0 +1 @@
+πŸ€– The loading screen's dust now really goes to eleven β€” the specks bounce higher and higher as the volume knob climbs, and when it snaps past 10 they leap more than halfway up your screen.
diff --git a/web/pages/index.html b/web/pages/index.html
index 3de64fc..ad0c93d 100644
--- a/web/pages/index.html
+++ b/web/pages/index.html
@@ -60,8 +60,9 @@
fills and the pointer turns towards 10 while loading; when the app is
ready it goes to eleven, holds a beat, and glows. (Labels + motion are
built by startLoadingDial in shell.js.) -->
- <!-- Dust on the stage: particles along the bottom that get bounced around as
- the volume climbs (see stepDust in shell.js). Decorative only. -->
+ <!-- Dust on the stage: particles along the bottom that bounce ever higher as
+ the volume climbs β€” nearly to the top of the screen at eleven
+ (see stepDust in shell.js). Decorative only. -->
<canvas class="amp-dust" id="amp-dust" aria-hidden="true"></canvas>
<div class="amp-knob" id="amp-knob" role="progressbar" aria-label="Loading">
<div class="amp-fill" id="amp-fill"></div>
diff --git a/web/static/shell.js b/web/static/shell.js
index 403bd9e..cd4d11a 100644
--- a/web/static/shell.js
+++ b/web/static/shell.js
@@ -490,14 +490,17 @@ let dialT0 = 0;
let dialSweep = 0;
const DIAL_MIN_TURN_MS = 1800; // a fast load still gets a human-speed turn to 10
// Stage dust: particles resting along the loader's bottom edge that the volume
-// bounces around β€” still at 1, hopping by 7, popcorn at eleven.
+// bounces around β€” still at 1, leaping ever higher as the dial climbs, and at
+// ELEVEN launched to between the middle and the top of the viewport. The
+// canvas is full-height so nothing clips them.
let dust = null;
+const DUST_GRAVITY = 0.62; // heavy, so hops are snappy
function initDust() {
const canvas = document.getElementById("amp-dust");
if (!canvas || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const dpr = window.devicePixelRatio || 1;
const w = window.innerWidth;
- const h = 140;
+ const h = window.innerHeight;
canvas.width = w * dpr;
canvas.height = h * dpr;
const g = canvas.getContext("2d");
@@ -515,7 +518,9 @@ function initDust() {
c: Math.random() < 0.18 ? accent : ink,
a: 0.3 + Math.random() * 0.5,
}));
- dust = { g, w, h, parts };
+ // fade: the whole cloud's opacity β€” clickDialToEleven dims it during the bow
+ // so the motes fade out with the knob, still mid-flight.
+ dust = { g, w, h, parts, fade: 1 };
}
function stepDust(level) {
if (!dust) return;
@@ -523,12 +528,20 @@ function stepDust(level) {
g.clearRect(0, 0, w, h);
for (const p of parts) {
// Volume kicks: both the odds and the violence grow with the level. Sharp
- // impulses + heavy gravity = a BOUNCE, not a float.
+ // impulses + heavy gravity = a BOUNCE, not a float. Every kick is sized as
+ // a fraction of the viewport height it should peak at: climbing the dial
+ // the hops grow fast (levelΒ², so they're already leaping mid-climb), and
+ // AT eleven (level > 1 β€” only the snap goes there) they reach between the
+ // middle and the top of the page.
if (level > 0 && p.vy === 0 && Math.random() < 0.02 + level * 0.2) {
- p.vy = -(2.2 + Math.random() * 5.5) * level;
+ const peak =
+ level > 1
+ ? 0.5 + Math.random() * 0.4
+ : level * level * (0.12 + Math.random() * 0.22);
+ p.vy = -Math.sqrt(2 * DUST_GRAVITY * h * peak);
p.vx += (Math.random() - 0.5) * 2.2 * level;
}
- p.vy += 0.62; // gravity β€” heavy, so hops are snappy
+ p.vy += DUST_GRAVITY;
p.y += p.vy;
p.x += p.vx;
p.vx *= 0.97;
@@ -538,7 +551,7 @@ function stepDust(level) {
}
if (p.x < -4) p.x += w + 8;
else if (p.x > w + 4) p.x -= w + 8;
- g.globalAlpha = p.a;
+ g.globalAlpha = p.a * dust.fade;
g.fillStyle = p.c;
g.beginPath();
g.arc(p.x, p.y, p.r, 0, 7);
@@ -614,6 +627,9 @@ function clickDialToEleven() {
} else if (t < toTen + beat + eleven + bow) {
// The SNAP: no travel, no ease β€” one frame it's at 10, the next it's AT
// eleven, lit, shaking uncontrollably (and fading, during the bow).
+ // The dust takes the bow with the knob: same window, fading while it
+ // still rattles and flies. (Clamped to 1 until the bow starts.)
+ if (dust) dust.fade = Math.min(1, Math.max(0, 1 - (t - (toTen + beat + eleven)) / bow));
dialPaint(knob, fill, body, 300, CRANKED);
knob.classList.add("pop");
if (t >= toTen + beat + eleven) knob.classList.add("out");
diff --git a/web/static/style.css b/web/static/style.css
index 2de37b9..707233c 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -163,14 +163,13 @@ html, body {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.plugging-in.show { opacity: 1; transform: none; }
-/* The loader's stage dust: a strip of particles along the bottom edge. */
+/* The loader's stage dust: particles resting along the bottom edge. The canvas
+ covers the whole viewport so the eleven-snap bounces can clear the top. */
.amp-dust {
position: absolute;
- left: 0;
- right: 0;
- bottom: 0;
+ inset: 0;
width: 100%;
- height: 140px;
+ height: 100%;
pointer-events: none;
}
/* The boot amp knob (labelled 1–11): the pie arc fills and the pointer turns