diff --git a/webui/index.html b/webui/index.html
index 78d90660..53cc9fc8 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -3156,8 +3156,16 @@
Balanced
diff --git a/webui/static/discover.js b/webui/static/discover.js
index 6c8d7107..6d745212 100644
--- a/webui/static/discover.js
+++ b/webui/static/discover.js
@@ -36,9 +36,10 @@ function _advState(v) {
return 'Deep cuts only';
}
// green (120°) → red (0°) through the warm spectrum (yellow, orange) as the orb moves right.
-function _advColor(v, light) {
+function _advColor(v, light, alpha) {
const hue = 120 * (1 - Math.max(0, Math.min(1, v)));
- return `hsl(${hue.toFixed(0)}, 85%, ${light || 55}%)`;
+ const l = light || 55;
+ return alpha != null ? `hsla(${hue.toFixed(0)}, 85%, ${l}%, ${alpha})` : `hsl(${hue.toFixed(0)}, 85%, ${l}%)`;
}
// Wave height (viewBox units, centre 40) at position u (0..1) for adventurousness v.
function _advWaveY(u, v) {
@@ -57,14 +58,16 @@ function _advDraw() {
if (track.offsetParent !== null) { // skip the work while the Discover page is hidden
const v = _advWave.value;
_advWave.phase += 0.03 + v * 0.07; // waves faster the more adventurous
- let d = '';
+ let line = '';
const N = 90;
for (let i = 0; i <= N; i++) {
const u = i / N;
- d += (i === 0 ? 'M ' : ' L ') + (u * 1000).toFixed(1) + ' ' + _advWaveY(u, v).toFixed(1);
+ line += (i === 0 ? 'M ' : ' L ') + (u * 1000).toFixed(1) + ' ' + _advWaveY(u, v).toFixed(1);
}
- path.setAttribute('d', d);
- path.setAttribute('stroke', _advColor(v));
+ path.setAttribute('d', line);
+ // the luminous filled area = the wave closed down to the baseline
+ const area = document.getElementById('adv-wave-area');
+ if (area) area.setAttribute('d', line + ' L 1000 80 L 0 80 Z');
const orb = document.getElementById('adv-wave-orb');
if (orb) orb.style.top = (_advWaveY(v, v) / 80 * 100).toFixed(2) + '%'; // orb rides the wave
}
@@ -73,15 +76,27 @@ function _advDraw() {
function _advApply(v) {
v = Math.max(0, Math.min(1, v));
_advWave.value = v;
+ const c = _advColor(v, 55);
+ const cBright = _advColor(v, 62);
+ // Line stroke + its colour glow (set on change, not every frame, so the rAF stays cheap).
+ const path = document.getElementById('adv-wave-path');
+ if (path) { path.setAttribute('stroke', c); path.style.filter = `drop-shadow(0 0 7px ${c})`; }
+ const fillTop = document.getElementById('adv-wave-fill-top');
+ if (fillTop) fillTop.setAttribute('stop-color', c); // luminous filled area
const orb = document.getElementById('adv-wave-orb');
if (orb) {
orb.style.left = (v * 100).toFixed(2) + '%';
- const c = _advColor(v, 58);
- orb.style.background = c;
- orb.style.boxShadow = `0 0 18px 2px ${c}, 0 0 0 5px rgba(255,255,255,0.08), inset 0 0 0 2px rgba(255,255,255,0.55)`;
+ orb.style.color = c; // currentColor for the pulsing ring
+ orb.style.background = cBright;
+ orb.style.boxShadow = `0 0 22px 3px ${c}, 0 0 0 6px rgba(255,255,255,0.08), inset 0 0 0 2px rgba(255,255,255,0.6)`;
+ }
+ const aura = document.getElementById('adv-wave-aura'); // colour wash that follows the orb
+ if (aura) {
+ aura.style.left = (v * 100).toFixed(2) + '%';
+ aura.style.background = `radial-gradient(circle, ${_advColor(v, 50, 0.42)} 0%, transparent 68%)`;
}
const stateEl = document.getElementById('adv-wave-state');
- if (stateEl) { stateEl.textContent = _advState(v); stateEl.style.color = _advColor(v, 62); }
+ if (stateEl) { stateEl.textContent = _advState(v); stateEl.style.color = cBright; }
}
let _advCommitTimer = null;
function _advCommit(v) {
diff --git a/webui/static/style.css b/webui/static/style.css
index 91017d4d..59ea721c 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -35168,35 +35168,52 @@ div.artist-hero-badge {
and wilder + redder at the right (obscure). The path + colours are drawn each frame in
JS (rAF). Syncs with Settings → Discovery. */
.adv-wave {
- max-width: 880px;
- margin: 0 0 26px;
- padding: 16px 22px 14px;
- border-radius: 16px;
- background: linear-gradient(135deg, rgba(255,255,255,0.035), rgba(255,255,255,0.01));
+ position: relative;
+ margin: 0 0 28px;
+ padding: 18px 26px 16px;
+ border-radius: 18px;
+ background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.012));
border: 1px solid rgba(255,255,255,0.07);
- box-shadow: 0 8px 28px rgba(0,0,0,0.25);
+ box-shadow: 0 10px 34px rgba(0,0,0,0.30);
+ overflow: hidden; /* contain the colour aura wash */
}
-.adv-wave-head { display: flex; align-items: baseline; gap: 12px; }
-.adv-wave-title { font-size: 14px; font-weight: 700; color: #fff; }
+.adv-wave-head { display: flex; align-items: baseline; gap: 12px; position: relative; z-index: 2; }
+.adv-wave-title { font-size: 15px; font-weight: 800; color: #fff; letter-spacing: 0.2px; }
.adv-wave-state { font-size: 12px; font-weight: 700; transition: color 0.25s; }
.adv-wave-track {
- position: relative; height: 84px; margin: 4px 0 2px;
+ position: relative; height: 104px; margin: 6px 0 2px;
cursor: grab; touch-action: none;
}
.adv-wave-track:active { cursor: grabbing; }
-.adv-wave-svg { width: 100%; height: 100%; display: block; overflow: visible; }
-.adv-wave-orb {
+.adv-wave-aura {
position: absolute; top: 50%; left: 30%;
- width: 20px; height: 20px; border-radius: 50%;
+ width: 360px; height: 230px; transform: translate(-50%, -50%);
+ pointer-events: none; filter: blur(14px); z-index: 0;
+ will-change: left, background;
+}
+.adv-wave-svg { width: 100%; height: 100%; display: block; overflow: visible; position: relative; z-index: 1; }
+.adv-wave-orb {
+ position: absolute; top: 50%; left: 30%; z-index: 2;
+ width: 22px; height: 22px; border-radius: 50%;
transform: translate(-50%, -50%);
- background: #1DB954;
- box-shadow: 0 0 18px 2px #1DB954, 0 0 0 5px rgba(255,255,255,0.08), inset 0 0 0 2px rgba(255,255,255,0.55);
+ background: #1DB954; color: #1DB954;
+ box-shadow: 0 0 20px 3px #1DB954, 0 0 0 6px rgba(255,255,255,0.08), inset 0 0 0 2px rgba(255,255,255,0.6);
pointer-events: none; /* the track captures the pointer; the orb is purely visual */
will-change: left, top;
}
+.adv-wave-orb::after {
+ content: ''; position: absolute; inset: -7px; border-radius: 50%;
+ border: 2px solid currentColor; opacity: 0.5;
+ animation: adv-orb-pulse 2.4s ease-out infinite;
+}
+@keyframes adv-orb-pulse {
+ 0% { transform: scale(0.7); opacity: 0.55; }
+ 100% { transform: scale(1.95); opacity: 0; }
+}
.adv-wave-ends {
display: flex; justify-content: space-between;
font-size: 11px; font-weight: 500; color: rgba(255,255,255,0.35);
+ position: relative; z-index: 2;
}
/* Watchlist toggle on recommended-artist .ya-cards — corner pill, appears on hover,