| 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"); |