diff --git a/static/css/app.css b/static/css/app.css
index f9e8bc2..b214a74 100644
--- a/static/css/app.css
+++ b/static/css/app.css
@@ -984,3 +984,17 @@ body.dnd-mode .timer-display {
align-items: center;
gap: 8px;
}
+
+.volume-num {
+ width: 44px;
+ background: var(--surface, #1e1e2e);
+ border: 1px solid var(--border, #333);
+ border-radius: 4px;
+ color: var(--fg, #fff);
+ font-size: 0.8rem;
+ padding: 2px 4px;
+ text-align: center;
+}
+
+.volume-num::-webkit-inner-spin-button,
+.volume-num::-webkit-outer-spin-button { opacity: 0.4; }
diff --git a/static/js/app.js b/static/js/app.js
index 2659dac..28d3940 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -56,7 +56,7 @@ function playStation(url, name, stationId) {
audio.src = url;
const volSlider = document.getElementById('volume');
- if (volSlider) audio.volume = volSlider.value / 100;
+ if (volSlider) audio.volume = volSlider.value / 255;
audio.play().catch(() => {
// Browser may block autoplay; the user needs to interact first
console.warn('Audio play blocked by browser policy.');
@@ -181,10 +181,28 @@ function escapeAttr(s) {
// Volume
// ---------------------------------------------------------------------------
-document.getElementById('volume').addEventListener('input', function () {
- audio.volume = this.value / 100;
- localStorage.setItem('diora_volume', this.value);
-});
+function setVolume(val) {
+ val = Math.max(0, Math.min(255, Math.round(val)));
+ audio.volume = val / 255;
+ localStorage.setItem('diora_volume', val);
+ const slider = $('volume');
+ const numInput = $('volume-num');
+ if (slider) slider.value = val;
+ if (numInput) numInput.value = val;
+}
+
+const volSliderEl = document.getElementById('volume');
+if (volSliderEl) {
+ ['input', 'change', 'mousemove', 'touchmove'].forEach(evt =>
+ volSliderEl.addEventListener(evt, function () { setVolume(this.value); })
+ );
+}
+
+const volNumEl = document.getElementById('volume-num');
+if (volNumEl) {
+ volNumEl.addEventListener('change', function () { setVolume(this.value); });
+ volNumEl.addEventListener('input', function () { setVolume(this.value); });
+}
// ---------------------------------------------------------------------------
// SSE metadata
@@ -1045,8 +1063,7 @@ function toggleContrast() {
if (volSlider) {
const saved = localStorage.getItem('diora_volume');
const vol = saved !== null ? parseInt(saved, 10) : parseInt(volSlider.value, 10);
- volSlider.value = vol;
- audio.volume = vol / 100;
+ setVolume(vol);
}
// Load recommendations on page load
diff --git a/templates/radio/player.html b/templates/radio/player.html
index dfcc89a..3fa2da6 100644
--- a/templates/radio/player.html
+++ b/templates/radio/player.html
@@ -13,7 +13,8 @@