Initial implementation: Bordanlage boat onboard system

Complete multiroom audio + navigation dashboard:
- Docker stack: SignalK, Snapcast (4 zones), librespot, shairport-sync, Mopidy, Jellyfin, Portainer
- React 18 + Vite dashboard with nautical dark theme
- Full mock system (SignalK NMEA simulation, Snapcast zones, Mopidy player)
- Real API clients for all services with reconnect logic
- SVG instruments: Compass, WindRose, Gauge, DepthSounder, SpeedLog
- Pages: Overview, Navigation, Audio (zones/radio/library), Systems
- Dev mode runs fully without hardware (make dev)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:47:33 +01:00
commit 946c0a5377
57 changed files with 3450 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
const SOURCES = [
{ id: 'Spotify', label: 'Spotify', color: 'var(--spotify)', icon: '🎵' },
{ id: 'AirPlay', label: 'AirPlay', color: 'var(--airplay)', icon: '📡' },
{ id: 'Mopidy', label: 'Mopidy', color: 'var(--accent)', icon: '📻' },
]
export default function SourcePicker({ activeSource, onSelect }) {
return (
<div style={styles.row}>
{SOURCES.map(s => (
<button
key={s.id}
style={{
...styles.btn,
...(activeSource === s.id ? { background: s.color + '22', borderColor: s.color, color: s.color } : {}),
}}
onClick={() => onSelect(s.id)}
>
<span>{s.icon}</span>
<span style={{ fontSize: 12 }}>{s.label}</span>
</button>
))}
</div>
)
}
const styles = {
row: { display: 'flex', gap: 8 },
btn: {
flex: 1, height: 52, flexDirection: 'column',
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
background: 'var(--surface)', border: '1px solid var(--border)',
borderRadius: 'var(--radius)', color: 'var(--muted)',
transition: 'all 0.15s',
minHeight: 52,
},
}