From c17ba0e839bfac116ab5d8bcd4c843165da3ae33 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sat, 7 Mar 2026 13:42:17 +0100 Subject: [PATCH] Add Stockholm Mini (3) This is also a refactoring of our api paths --- cmd/soundtouch-service/main.go | 8 + pkg/service/handlers/handlers_stockholm.go | 124 +++++ pkg/service/handlers/server.go | 20 + .../handlers/web/stockholm-mini/app.js | 456 ++++++++++++++++++ .../handlers/web/stockholm-mini/bose.ttf | Bin 0 -> 67840 bytes .../handlers/web/stockholm-mini/index.html | 25 + .../handlers/web/stockholm-mini/style.css | 40 ++ 7 files changed, 673 insertions(+) create mode 100644 pkg/service/handlers/handlers_stockholm.go create mode 100644 pkg/service/handlers/web/stockholm-mini/app.js create mode 100644 pkg/service/handlers/web/stockholm-mini/bose.ttf create mode 100644 pkg/service/handlers/web/stockholm-mini/index.html create mode 100644 pkg/service/handlers/web/stockholm-mini/style.css diff --git a/cmd/soundtouch-service/main.go b/cmd/soundtouch-service/main.go index 76080a0..52a139e 100644 --- a/cmd/soundtouch-service/main.go +++ b/cmd/soundtouch-service/main.go @@ -753,6 +753,14 @@ func setupRouter(server *handlers.Server) *chi.Mux { r.Get("/proxy/*", server.HandleProxyRequest) + r.Get("/devices", server.HandleListDiscoveredDevices) + r.Get("/devices/{deviceId}/info", server.HandleGetStockholmDeviceInfo) + r.Post("/devices/{deviceId}/key/{key}", server.HandleDeviceKey) + r.Post("/devices/{deviceId}/volume/{level}", server.HandleDeviceVolume) + + // Stockholm Mini app + r.Handle("/stockholm-mini/*", http.StripPrefix("/stockholm-mini/", http.FileServer(http.Dir("pkg/service/handlers/web/stockholm-mini")))) + r.Route("/setup", func(r chi.Router) { r.Get("/devices", server.HandleListDiscoveredDevices) r.Post("/devices", server.HandleAddManualDevice) diff --git a/pkg/service/handlers/handlers_stockholm.go b/pkg/service/handlers/handlers_stockholm.go new file mode 100644 index 0000000..123f36f --- /dev/null +++ b/pkg/service/handlers/handlers_stockholm.go @@ -0,0 +1,124 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strconv" + + "github.com/gesellix/bose-soundtouch/pkg/client" + "github.com/gesellix/bose-soundtouch/pkg/service/setup" + "github.com/go-chi/chi/v5" +) + +// HandleGetStockholmDeviceInfo returns live information for a device. +func (s *Server) HandleGetStockholmDeviceInfo(w http.ResponseWriter, r *http.Request) { + deviceID := chi.URLParam(r, "deviceId") + if deviceID == "" { + http.Error(w, "Device ID is required", http.StatusBadRequest) + return + } + + deviceIP, err := s.lookupIP(deviceID) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + info, err := s.sm.GetLiveDeviceInfo(deviceIP) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + + // Include IP address in both snake_case and camelCase for frontend compatibility + type deviceInfoResponse struct { + *setup.DeviceInfoXML `json:",inline"` + IPAddress string `json:"ip_address"` + IPAddressCamel string `json:"ipAddress,omitempty"` + } + + resp := deviceInfoResponse{ + DeviceInfoXML: info, + IPAddress: deviceIP, + IPAddressCamel: deviceIP, + } + + if err := json.NewEncoder(w).Encode(resp); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + return + } +} + +// HandleDeviceKey sends a key command to a device. +func (s *Server) HandleDeviceKey(w http.ResponseWriter, r *http.Request) { + deviceID := chi.URLParam(r, "deviceId") + + key := chi.URLParam(r, "key") + if deviceID == "" || key == "" { + http.Error(w, "Device ID and Key are required", http.StatusBadRequest) + return + } + + deviceIP, err := s.lookupIP(deviceID) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + c := client.NewClientFromHost(deviceIP) + + err = c.SendKey(key) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to send key %s to %s: %v", key, deviceIP, err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "message": "Key sent"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} + +// HandleDeviceVolume sets the volume level for a device. +func (s *Server) HandleDeviceVolume(w http.ResponseWriter, r *http.Request) { + deviceID := chi.URLParam(r, "deviceId") + + levelStr := chi.URLParam(r, "level") + if deviceID == "" || levelStr == "" { + http.Error(w, "Device ID and Level are required", http.StatusBadRequest) + return + } + + deviceIP, err := s.lookupIP(deviceID) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + level, err := strconv.Atoi(levelStr) + if err != nil { + http.Error(w, "Invalid volume level", http.StatusBadRequest) + return + } + + c := client.NewClientFromHost(deviceIP) + + err = c.SetVolume(level) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to set volume to %d on %s: %v", level, deviceIP, err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "message": "Volume set"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} diff --git a/pkg/service/handlers/server.go b/pkg/service/handlers/server.go index 2593242..f1e6d73 100644 --- a/pkg/service/handlers/server.go +++ b/pkg/service/handlers/server.go @@ -840,3 +840,23 @@ func (s *Server) resolveDeviceIDToIP(deviceID string) (string, error) { return "", fmt.Errorf("device not found: %s", deviceID) } + +// lookupIP resolves a deviceId to its last known device IP. +func (s *Server) lookupIP(deviceId string) (string, error) { + devices, err := s.ds.ListAllDevices() + if err != nil { + return "", err + } + + for i := range devices { + if devices[i].DeviceID == deviceId { + if devices[i].IPAddress == "" { + return "", fmt.Errorf("no IP known for deviceId %s", deviceId) + } + + return devices[i].IPAddress, nil + } + } + + return "", fmt.Errorf("deviceId %s not found", deviceId) +} diff --git a/pkg/service/handlers/web/stockholm-mini/app.js b/pkg/service/handlers/web/stockholm-mini/app.js new file mode 100644 index 0000000..e8d1197 --- /dev/null +++ b/pkg/service/handlers/web/stockholm-mini/app.js @@ -0,0 +1,456 @@ +async function fetchDevices() { + try { + const response = await fetch('/devices'); + const devices = await response.json(); + const container = document.getElementById('device-list'); + const seen = new Set(); + + if (devices.length === 0) { + container.innerHTML = '

No devices found. Ensure they are on the same network.

'; + return; + } + + devices.forEach(device => { + seen.add(device.device_id); + const existing = document.getElementById(`device-${device.device_id}`); + if (existing) { + // Update product code/IP if changed, but keep title if we already have a better name + const title = existing.querySelector('.device-title'); + if (title && (!title.textContent || title.textContent === 'Unknown Device' || title.textContent.startsWith('SoundTouch-'))) { + title.textContent = device.name || 'Unknown Device'; + } + const subtitle = existing.querySelector('.device-subtitle span'); + if (subtitle) { + const currentSubtitle = subtitle.textContent || ''; + const parts = currentSubtitle.split(' | '); + const currentType = parts.length > 1 ? parts[1].trim() : ''; + const newType = device.product_code || 'Unknown'; + + // Don't downgrade type if we already have a specific one + const isGeneric = !currentType || currentType === 'Unknown' || currentType === 'N/A'; + const displayType = isGeneric ? newType : currentType; + subtitle.textContent = `${device.ip_address} | ${displayType}`; + } + const details = existing.querySelector(`#details-${device.device_id}`); + if (details) { + const idField = details.querySelector('p:nth-child(1) code'); + if (idField) { + const currentId = idField.textContent; + // Don't overwrite with serial if we have a real deviceID (usually hex) + if (!currentId || currentId === 'N/A' || currentId === device.device_serial_number) { + idField.textContent = device.device_id || 'N/A'; + } + } + const firmwareField = details.querySelector('p:nth-child(2) code'); + if (firmwareField) { + const cur = firmwareField.textContent; + if (!cur || cur === 'N/A' || cur === '0.0.0') { + firmwareField.textContent = device.firmware_version || 'N/A'; + } + } + const serialField = details.querySelector('p:nth-child(3) code'); + if (serialField && (!serialField.textContent || serialField.textContent === 'N/A')) { + serialField.textContent = device.device_serial_number || 'N/A'; + } + } + // Ensure WS is open + openDeviceWebSocket(device.device_id); + return; + } + + const card = document.createElement('div'); + card.className = 'device-card'; + card.id = `device-${device.device_id}`; + card.innerHTML = ` +
+
+
+
+

${device.name || 'Unknown Device'}

+ +
+

+ ${device.ip_address} | ${device.product_code} +

+
+ +
+
+

ID: ${device.device_id}

+

Firmware: ${device.firmware_version || 'N/A'}

+

Serial: ${device.device_serial_number || 'N/A'}

+

Discovery: ${device.discovery_method || 'N/A'}

+
+
+
+

Loading playback status...

+
+
+ + + + +
+
+ Vol: + +
+ `; + container.appendChild(card); + updateNowPlaying(device.device_id); + updateVolume(device.device_id); + openDeviceWebSocket(device.device_id); + }); + + // Remove cards for devices that no longer exist + Array.from(container.children).forEach(child => { + const id = child.id?.replace('device-', ''); + if (id && !seen.has(id)) { + container.removeChild(child); + } + }); + } catch (error) { + console.error('Failed to fetch devices', error); + document.getElementById('device-list').innerHTML = '

Error loading devices.

'; + } +} + +async function updateNowPlaying(deviceId) { + try { + const response = await fetch(`/devices/${deviceId}/info`); + if (!response.ok) return; + const info = await response.json(); + + // Update device name and type if available (live info is more accurate than discovery) + const title = document.querySelector(`#device-${deviceId} .device-title`); + if (title && info.name) { + title.textContent = info.name; + } + const subtitle = document.querySelector(`#device-${deviceId} .device-subtitle span`); + if (subtitle && info.type) { + subtitle.textContent = `${info.ipAddress || info.ip_address || 'N/A'} | ${info.type}`; + } + + // Update firmware version if available + const details = document.getElementById(`details-${deviceId}`); + if (details) { + if (info.deviceID) { + const idField = details.querySelector('p:nth-child(1) code'); + if (idField) idField.textContent = info.deviceID; + } + if (info.softwareVersion) { + const firmwareField = details.querySelector('p:nth-child(2) code'); + if (firmwareField) firmwareField.textContent = info.softwareVersion; + } + if (info.serialNumber) { + const serialField = details.querySelector('p:nth-child(3) code'); + if (serialField) serialField.textContent = info.serialNumber; + } + } + + const npContainer = document.getElementById(`np-${deviceId}`); + if (npContainer && info.nowPlaying) { + const np = info.nowPlaying; + const source = np.source || np.Source; + const powerIcon = document.querySelector(`#device-${deviceId} .power-icon`); + if (powerIcon) { + if (source === 'STANDBY') { + powerIcon.classList.add('off'); + powerIcon.classList.remove('on'); + } else { + powerIcon.classList.remove('off'); + powerIcon.classList.add('on'); + } + } + if (source === 'STANDBY') { + npContainer.innerHTML = '

Standby

'; + } else { + const track = np.track || np.Track || np.stationName || np.StationName || 'Unknown Track'; + const artist = np.artist || np.Artist || 'Unknown Artist'; + const album = np.album || np.Album || 'Unknown Album'; + const art = np.Art || np.art || {}; + const artStatus = art.ArtImageStatus || art.artImageStatus; + const artUrl = artStatus === 'IMAGE_PRESENT' ? (art.URL || art.url || '') : ''; + + npContainer.innerHTML = ` + Artwork +
+ ${track}
+ ${artist} - ${album} +
+ `; + } + } + } catch (error) { + console.warn('Failed to fetch now playing for ' + deviceId, error); + } +} + +async function updateVolume(deviceId) { + try { + const response = await fetch(`/devices/${deviceId}/info`); + if (!response.ok) return; + const info = await response.json(); + const slider = document.getElementById(`vol-${deviceId}`); + if (slider && info.volume && typeof info.volume.actualvolume === 'number' && !adjusting[deviceId]) { + slider.value = String(info.volume.actualvolume); + } + } catch (error) { + console.warn('Failed to fetch volume for ' + deviceId, error); + } +} + +async function control(deviceId, key) { + let deviceName = deviceId; + const title = document.querySelector(`#device-${deviceId} .device-title`); + if (title && title.textContent) { + deviceName = title.textContent; + } + + try { + const res = await fetch(`/devices/${encodeURIComponent(deviceId)}/key/${encodeURIComponent(key)}`, { + method: 'POST' + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(text || `HTTP ${res.status}`); + } + } catch (error) { + console.error('Control failed', error); + alert(`Failed to send ${key} to ${deviceName}: ${error.message}`); + } +} + +async function setVolume(deviceId, level) { + let deviceName = deviceId; + const title = document.querySelector(`#device-${deviceId} .device-title`); + if (title && title.textContent) { + deviceName = title.textContent; + } + + try { + const res = await fetch(`/devices/${encodeURIComponent(deviceId)}/volume/${encodeURIComponent(level)}`, { + method: 'POST' + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(text || `HTTP ${res.status}`); + } + } catch (error) { + console.error('Set volume failed', error); + alert(`Failed to set volume on ${deviceName} to ${level}: ${error.message}`); + } +} + +// Volume interaction helpers to avoid UI jumping while dragging +const adjusting = {}; +const volumeTimers = {}; + +function startAdjust(deviceId) { + adjusting[deviceId] = true; +} + +function endAdjust(deviceId) { + // Small delay to let the device send back its volume update + setTimeout(() => { adjusting[deviceId] = false; }, 300); +} + +function onVolumeInput(deviceId, el) { + startAdjust(deviceId); + const level = el.value; + // Debounce network calls per device + if (volumeTimers[deviceId]) { + clearTimeout(volumeTimers[deviceId]); + } + volumeTimers[deviceId] = setTimeout(() => { + setVolume(deviceId, level); + endAdjust(deviceId); + }, 150); +} + +let deviceSockets = {}; + +function openDeviceWebSocket(deviceId) { + const key = `${deviceId}`; + try { + const existing = deviceSockets[key]; + if (existing) { + // Reuse an already healthy connection instead of tearing it down every refresh + if (existing.readyState === WebSocket.OPEN || existing.readyState === WebSocket.CONNECTING) { + return; + } + try { existing.close(); } catch (_) {} + } + const proto = location.protocol === 'https:' ? 'wss' : 'ws'; + const wsUrl = `${proto}://${location.host}/devices/${encodeURIComponent(deviceId)}/ws`; + const ws = new WebSocket(wsUrl); + deviceSockets[key] = ws; + + ws.onopen = () => { + // console.log('WS connected for', deviceId); + }; + ws.onmessage = (ev) => { + try { + const msg = JSON.parse(ev.data); + const type = msg.type; + const payload = msg.payload || {}; + if (type === 'nowPlayingUpdated') { + const e = payload; + const np = e.NowPlaying || e.nowPlaying || {}; + const source = np.source || np.Source; + + // Also try to update name/type if they are present in the event (sometimes events carry device info) + const title = document.querySelector(`#device-${deviceId} .device-title`); + if (title && e.name) { + title.textContent = e.name; + } + const subtitle = document.querySelector(`#device-${deviceId} .device-subtitle span`); + if (subtitle && e.type) { + subtitle.textContent = `${e.ipAddress || e.ip_address || 'N/A'} | ${e.type}`; + } + + const powerIcon = document.querySelector(`#device-${deviceId} .power-icon`); + if (powerIcon) { + if (source === 'STANDBY') { + powerIcon.classList.add('off'); + powerIcon.classList.remove('on'); + } else { + powerIcon.classList.remove('off'); + powerIcon.classList.add('on'); + } + } + const npContainer = document.getElementById(`np-${deviceId}`); + if (npContainer) { + if (source === 'STANDBY') { + npContainer.innerHTML = '

Standby

'; + } else { + const track = np.track || np.Track || np.stationName || np.StationName || 'Unknown Track'; + const artist = np.artist || np.Artist || 'Unknown Artist'; + const album = np.album || np.Album || 'Unknown Album'; + const art = np.Art || np.art || {}; + const artStatus = art.ArtImageStatus || art.artImageStatus; + const artUrl = artStatus === 'IMAGE_PRESENT' ? (art.URL || art.url || '') : ''; + + npContainer.innerHTML = ` + Artwork +
+ ${track}
+ ${artist} - ${album} +
+ `; + } + } + } else if (type === 'volumeUpdated') { + const e = payload; + const vol = (e.Volume && (typeof e.Volume.actualvolume === 'number' ? e.Volume.actualvolume : (typeof e.Volume.actual === 'number' ? e.Volume.actual : e.Volume.target))) || + (e.volume && (typeof e.volume.actualvolume === 'number' ? e.volume.actualvolume : (typeof e.volume.actual === 'number' ? e.volume.actual : e.volume.target))); + const slider = document.getElementById(`vol-${deviceId}`); + if (slider && typeof vol === 'number' && !adjusting[deviceId]) { + slider.value = String(vol); + } + } else if (type === 'snapshotInfo') { + const info = payload || {}; + + // Update name and type from snapshot + const title = document.querySelector(`#device-${deviceId} .device-title`); + if (title && info.name) { + title.textContent = info.name; + } + const subtitle = document.querySelector(`#device-${deviceId} .device-subtitle span`); + if (subtitle && info.type) { + subtitle.textContent = `${info.ipAddress || info.ip_address || 'N/A'} | ${info.type}`; + } + + // Update firmware and ID from snapshot if available + const details = document.getElementById(`details-${deviceId}`); + if (details) { + if (info.deviceID) { + const idField = details.querySelector('p:nth-child(1) code'); + if (idField) idField.textContent = info.deviceID; + } + if (info.softwareVersion) { + const firmwareField = details.querySelector('p:nth-child(2) code'); + if (firmwareField) firmwareField.textContent = info.softwareVersion; + } + if (info.serialNumber) { + const serialField = details.querySelector('p:nth-child(3) code'); + if (serialField) serialField.textContent = info.serialNumber; + } + } + + if (info.nowPlaying) { + const np = info.nowPlaying; + const source = np.source || np.Source; + const powerIcon = document.querySelector(`#device-${deviceId} .power-icon`); + if (powerIcon) { + if (source === 'STANDBY') { + powerIcon.classList.add('off'); + powerIcon.classList.remove('on'); + } else { + powerIcon.classList.remove('off'); + powerIcon.classList.add('on'); + } + } + const npContainer = document.getElementById(`np-${deviceId}`); + if (npContainer) { + if (source === 'STANDBY') { + npContainer.innerHTML = '

Standby

'; + } else { + const track = np.track || np.Track || np.stationName || np.StationName || 'Unknown Track'; + const artist = np.artist || np.Artist || 'Unknown Artist'; + const album = np.album || np.Album || 'Unknown Album'; + const art = np.Art || np.art || {}; + const artStatus = art.ArtImageStatus || art.artImageStatus; + const artUrl = artStatus === 'IMAGE_PRESENT' ? (art.URL || art.url || '') : ''; + + npContainer.innerHTML = ` + Artwork +
+ ${track}
+ ${artist} - ${album} +
+ `; + } + } + } + const vol = info.actualVolume || (info.volume && (typeof info.volume.actualvolume === 'number' ? info.volume.actualvolume : (typeof info.volume.actual === 'number' ? info.volume.actual : null))); + const slider = document.getElementById(`vol-${deviceId}`); + if (slider && typeof vol === 'number' && !adjusting[deviceId]) slider.value = String(vol); + } + } catch (err) { + // console.warn('Bad WS message', err); + } + }; + ws.onerror = () => { + // console.warn('WS error for', ip); + }; + ws.onclose = () => { + // Try to reconnect after a delay + setTimeout(() => { + if (deviceSockets[key] === ws) { + delete deviceSockets[key]; + } + openDeviceWebSocket(deviceId); + }, 3000); + }; + } catch (e) { + // console.warn('Failed to open WS for', deviceId, e); + } +} + +function toggleDetails(deviceId) { + const el = document.getElementById(`details-${deviceId}`); + if (el) { + el.classList.toggle('visible'); + } +} + + +document.addEventListener('DOMContentLoaded', () => { + fetchDevices(); + fetchVersion(); + setInterval(fetchDevices, 30000); +}); diff --git a/pkg/service/handlers/web/stockholm-mini/bose.ttf b/pkg/service/handlers/web/stockholm-mini/bose.ttf new file mode 100644 index 0000000000000000000000000000000000000000..efabc4c911823607b6054c5479377d193d357c00 GIT binary patch literal 67840 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 + + + + Stockholm Mini - Reverse Engineered + + + + +
+

Stockholm Mini

+

A minimal reverse-engineered SoundTouch controller.

+

← Back to selection

+ +
+
+ +
+ AfterTouch +
+ + + + + diff --git a/pkg/service/handlers/web/stockholm-mini/style.css b/pkg/service/handlers/web/stockholm-mini/style.css new file mode 100644 index 0000000..67d0598 --- /dev/null +++ b/pkg/service/handlers/web/stockholm-mini/style.css @@ -0,0 +1,40 @@ +@font-face { + font-family: 'bose'; + src: url('bose.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; +} +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #121212; color: #e0e0e0; margin: 0; padding: 20px; } +.container { max-width: 800px; margin: 0 auto; } +h1 { color: #fff; border-bottom: 1px solid #333; padding-bottom: 10px; } +.device-card { background: #1e1e1e; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); } +.device-info h2 { margin-top: 0; color: #00bcd4; margin-bottom: 0; } +.device-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } +.device-title-row { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; } +.device-title { margin: 0; font-size: 1.5rem; line-height: 1.2; } +.device-subtitle { color: #888; font-size: 0.85rem; margin: 0; display: flex; align-items: center; } +.info-toggle { background: none; color: #555; padding: 0; width: 1.15rem; height: 1.15rem; display: inline-flex; align-items: center; justify-content: center; border: 1px solid #444; border-radius: 50%; font-size: 0.7rem; font-style: italic; cursor: pointer; line-height: 1; transition: all 0.2s; flex-shrink: 0; } +.info-toggle:hover { color: #aaa; border-color: #666; background: #2a2a2a; } +.device-details { display: none; margin-top: 10px; font-size: 0.8rem; background: #252525; padding: 10px; border-radius: 4px; color: #aaa; border-left: 2px solid #00bcd4; } +.device-details.visible { display: block; } +.device-details p { margin: 4px 0; } +.device-details code { color: #ccc; } +.controls { display: flex; gap: 10px; margin-top: 20px; } +button { background: #333; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; transition: background 0.2s; } +button:hover { background: #444; } +button.primary { background: #00bcd4; color: #000; font-weight: bold; } +button.primary:hover { background: #00acc1; } +.power-icon { font-family: bose, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 1.25rem; line-height: 1; height: 2.25rem; width: 2.25rem; padding: 0; display: inline-flex; align-items: center; justify-content: center; background: #2a2a2a; border-radius: 50%; color: #00bcd4; border: 1px solid #00bcd4; } +.power-icon:hover { background: #3a3a3a; } +.power-icon.off { color: #666; border-color: #444; background: #1a1a1a; } +.power-icon.on { background: #00bcd4; color: #000; border-color: #00bcd4; } +.power-icon.on:hover { background: #00acc1; } +.status-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.8em; background: #333; margin-left: 10px; } +.now-playing { margin-top: 20px; padding-top: 20px; border-top: 1px solid #333; display: flex; gap: 15px; align-items: center; min-height: 80px; } +.now-playing-info { flex-grow: 1; } +.album-art { width: 80px; height: 80px; border-radius: 4px; background: #2a2a2a; flex-shrink: 0; object-fit: cover; box-shadow: 0 2px 4px rgba(0,0,0,0.5); } +.album-art[src=""] { display: none; } +.volume-container { margin-top: 15px; display: flex; align-items: center; gap: 10px; } +input[type=range] { flex-grow: 1; } +#device-list:empty::after { content: "Searching for devices..."; color: #666; font-style: italic; }