diff --git a/plugin/contents/ui/dancer.html b/plugin/contents/ui/dancer.html
index cd2ef1e..4c2893c 100644
--- a/plugin/contents/ui/dancer.html
+++ b/plugin/contents/ui/dancer.html
@@ -854,7 +854,7 @@ function resizeFloor() {
function drawFloor(ts) {
const dt = lastFloorTs ? Math.min((ts - lastFloorTs) / 1000, 0.1) : 0;
lastFloorTs = ts;
- floorScroll = (floorScroll + 0.06 * dt) % 1;
+ floorScroll = (floorScroll + 0.025 * dt * (isPlaying ? musicSpeed : 0.4)) % 1;
const W = floorCanvas.width;
const H = floorCanvas.height;
@@ -973,14 +973,31 @@ for (let i = 0; i < VU_COUNT; i++) {
// Beat detection
const BASS_BARS = 4;
-let bassSmooth = 0, bassEma = 0;
-const beatWrap = document.querySelector('.beat-wrap');
-const horizonEl = document.querySelector('.horizon');
+const BEAT_THRESH = 1.5;
+const BEAT_COOLDOWN = 280;
+let bassSmooth = 0, bassEma = 0, lastBeat = 0;
+let beatTimes = [], musicSpeed = 0.5;
+const beatWrap = document.querySelector('.beat-wrap');
+const horizonEl = document.querySelector('.horizon');
+const skeletonImg = document.querySelector('.skeleton-gif');
function detectBeat(vals) {
const bass = vals.slice(0, BASS_BARS).reduce((s, v) => s + v, 0) / BASS_BARS;
bassSmooth = bassSmooth * 0.6 + bass * 0.4;
bassEma = bassEma * 0.96 + bass * 0.04;
+ const now = performance.now();
+ if (bassSmooth > bassEma * BEAT_THRESH && bassSmooth > 8 && now - lastBeat > BEAT_COOLDOWN) {
+ lastBeat = now;
+ beatTimes.push(now);
+ if (beatTimes.length > 8) beatTimes.shift();
+ if (beatTimes.length >= 2) {
+ const avg = beatTimes.slice(1).reduce((s, t, i) => s + t - beatTimes[i], 0) / (beatTimes.length - 1);
+ musicSpeed = Math.min(2.0, Math.max(0.4, 60000 / avg / 120));
+ }
+ }
+ // Skeleton glow responds to bass energy
+ const glow = 15 + bassSmooth * 0.35;
+ skeletonImg.style.filter = `drop-shadow(0 0 ${glow.toFixed(1)}px rgba(0,200,255,${(0.25 + bassSmooth / 250).toFixed(2)}))`;
}
// Called with semicolon-separated CAVA values (0–100 per bar)