- Rename "Warteschlange" -> "Queue", tighten tab padding/letter-spacing so all 3 tabs fit on narrow screens - _loadNpLyrics now tries Navidrome getLyricsBySongId first (works once .lrc sidecar is rescanned), then falls back to fetching synced LRC directly from LRCLIB and parsing [mm:ss.xx] timestamps in the frontend - Plain text shown as last resort if no synced lyrics found Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
692 lines
45 KiB
HTML
692 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||
<meta name="theme-color" content="#07070f">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="apple-mobile-web-app-title" content="NovaSonic">
|
||
<link rel="manifest" href="manifest.json">
|
||
<link rel="apple-touch-icon" href="icon.svg">
|
||
<title>NovaSonic</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="styles.css">
|
||
<script>window.NS_SERVER_URL='';window.NS_BACKEND_URL='';</script>
|
||
<script src="config.js" onerror="0"></script>
|
||
<script type="module" src="/src/main.js"></script>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ─── SETUP ─── -->
|
||
<div id="setup-screen">
|
||
<div class="setup-card">
|
||
<div class="setup-logo">
|
||
<div class="logo-icon">♫</div>
|
||
<h1>Nova<em>Sonic</em></h1>
|
||
</div>
|
||
<div class="setup-tabs">
|
||
<button class="setup-tab active" onclick="switchSetupTab('navidrome')">Navidrome</button>
|
||
<button class="setup-tab" onclick="switchSetupTab('discover')">Discover APIs</button>
|
||
</div>
|
||
<div class="setup-pane active" id="pane-navidrome">
|
||
<div class="field"><label>Server URL</label><input id="srv-url" type="url" placeholder="https://music.example.com"></div>
|
||
<div class="field"><label>Benutzername</label><input id="srv-user" type="text" placeholder="admin"></div>
|
||
<div class="field"><label>Passwort</label><input id="srv-pass" type="password" placeholder="••••••••"></div>
|
||
</div>
|
||
<div class="setup-pane" id="pane-discover">
|
||
<div class="field">
|
||
<label>Last.fm API Key <span style="color:var(--text3);font-weight:400;text-transform:none">(optional)</span></label>
|
||
<input id="lastfm-key" type="text" placeholder="Kostenlos auf last.fm/api">
|
||
<div class="hint">Für Empfehlungen & Similar Artists. Kostenlos registrieren unter <strong>last.fm/api/account/create</strong></div>
|
||
</div>
|
||
<div class="field">
|
||
<label>NovaSonic Backend URL <span style="color:var(--text3);font-weight:400;text-transform:none">(optional)</span></label>
|
||
<input id="backend-url" type="url" placeholder="http://192.168.1.x:3700">
|
||
<div class="hint">Für Download-to-Server Funktion. Backend separat starten (novasonic-server.js)</div>
|
||
</div>
|
||
</div>
|
||
<button class="btn-primary" id="btn-connect" onclick="doConnect()">Verbinden</button>
|
||
<div class="setup-error" id="setup-error"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ─── APP ─── -->
|
||
<div id="app">
|
||
<div class="app-body">
|
||
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar" id="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="sidebar-logo">
|
||
<div class="icon">♫</div>
|
||
<span>Nova<em>Sonic</em></span>
|
||
</div>
|
||
<div class="header-btns">
|
||
<button class="hbtn" onclick="openSettings()" title="Einstellungen">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M14.24 9.76a4 4 0 0 1 0 4.48M4.93 4.93a10 10 0 0 0 0 14.14M9.76 9.76a4 4 0 0 0 0 4.48"/></svg>
|
||
</button>
|
||
<button class="hbtn danger" onclick="disconnect()" title="Trennen">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" y1="2" x2="12" y2="12"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="search-wrap">
|
||
<div class="search-box" onclick="document.getElementById('search-input').focus()">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
<input id="search-input" type="text" placeholder="Lokale Suche…" oninput="onLocalSearch(this.value)">
|
||
</div>
|
||
</div>
|
||
<nav>
|
||
<div class="nav-section">
|
||
<div class="nav-section-title">Bibliothek</div>
|
||
<div class="nav-item active" onclick="navigate('home')" data-page="home">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>Home
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('artists')" data-page="artists">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>Künstler
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('albums')" data-page="albums">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>Alben
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('starred')" data-page="starred">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>Favoriten
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('playlists')" data-page="playlists">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>Playlists
|
||
</div>
|
||
</div>
|
||
<div class="nav-section">
|
||
<div class="nav-section-title">Entdecken</div>
|
||
<div class="nav-item" onclick="navigate('discover')" data-page="discover">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>Global Suche
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('new-releases')" data-page="new-releases">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Neue Releases
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('recommendations')" data-page="recommendations">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>Für dich
|
||
</div>
|
||
<div class="nav-item" onclick="navigate('random')" data-page="random">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>Zufällig
|
||
</div>
|
||
</div>
|
||
<div class="nav-section">
|
||
<div class="nav-section-title">Player</div>
|
||
<div class="nav-item" onclick="toggleQueue()">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>Warteschlange
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
</aside>
|
||
|
||
<div class="mobile-overlay" id="mobile-overlay" onclick="closeSidebar()"></div>
|
||
|
||
<!-- MAIN -->
|
||
<main class="main" id="main-content">
|
||
|
||
<!-- Mobile header -->
|
||
<div id="mobile-header" style="position:sticky;top:0;background:var(--bg);z-index:10;border-bottom:1px solid var(--border)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 14px">
|
||
<div class="sidebar-logo">
|
||
<div class="icon" style="width:26px;height:26px;font-size:12px;border-radius:7px">♫</div>
|
||
<span style="font-size:15px;font-weight:700">Nova<em style="color:var(--accent);font-style:normal">Sonic</em></span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:4px">
|
||
<button class="hbtn" id="mobile-search-btn" onclick="toggleMobileSearch()" title="Suche" style="padding:8px;color:var(--text2)">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
</button>
|
||
<button class="hbtn" onclick="openSettings()" title="Einstellungen" style="padding:8px;color:var(--text2)">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="mobile-search-bar" style="display:none;padding:0 14px 10px">
|
||
<div class="search-box" style="background:var(--surface);border-color:var(--border2)">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
<input id="mobile-search-input" type="text" placeholder="Künstler, Album, Titel…" oninput="onMobileSearch(this.value)" style="font-size:15px">
|
||
<button onclick="closeMobileSearch()" style="background:none;border:none;color:var(--text3);cursor:pointer;font-size:16px;padding:0 2px;line-height:1">✕</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- HOME -->
|
||
<div class="page active" id="page-home">
|
||
<div class="section-header" style="margin-top:8px"><span class="section-title">Zufällig</span><span class="section-more" onclick="navigate('random')">Mehr</span></div>
|
||
<div class="scroll-row" id="home-random"></div>
|
||
<div class="section-header"><span class="section-title">Neueste Alben</span><span class="section-more" onclick="navigate('albums')">Mehr</span></div>
|
||
<div class="scroll-row" id="home-newest"></div>
|
||
<div class="section-header"><span class="section-title">Häufig gespielt</span></div>
|
||
<div class="scroll-row" id="home-frequent"></div>
|
||
</div>
|
||
|
||
<!-- ARTISTS -->
|
||
<div class="page" id="page-artists">
|
||
<div class="page-header"><div class="page-title">Künstler</div></div>
|
||
<div id="artists-grid" class="grid grid-4"></div>
|
||
</div>
|
||
|
||
<!-- ARTIST DETAIL -->
|
||
<div class="page" id="page-artist-detail">
|
||
<button class="back-btn" id="artist-back-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>Alle Künstler</button>
|
||
<div class="artist-hero" id="artist-detail-hero"><div class="artist-hero-overlay"><div class="artist-hero-name" id="artist-detail-name"></div></div></div>
|
||
<div id="artist-bio-section"></div>
|
||
<div id="artist-similar-section"></div>
|
||
<div id="artist-topsongs-section"></div>
|
||
<div class="section-title" style="margin-bottom:12px;margin-top:8px">Alben</div>
|
||
<div id="artist-albums-grid" class="grid grid-4"></div>
|
||
</div>
|
||
|
||
<!-- ALBUMS -->
|
||
<div class="page" id="page-albums">
|
||
<div class="page-header"><div class="page-title">Alben</div></div>
|
||
<div id="albums-grid" class="grid grid-4"></div>
|
||
</div>
|
||
|
||
<!-- ALBUM DETAIL -->
|
||
<div class="page" id="page-album-detail">
|
||
<button class="back-btn" id="album-back-btn"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>Zurück</button>
|
||
<div class="album-header">
|
||
<div class="album-cover" id="album-cover-wrap"><div class="no-art">💿</div></div>
|
||
<div class="album-meta">
|
||
<div class="album-type">Album</div>
|
||
<div class="album-name" id="album-detail-name">—</div>
|
||
<div class="album-artist-name" id="album-detail-artist"></div>
|
||
<div class="album-stats" id="album-detail-stats"></div>
|
||
<div class="album-actions">
|
||
<button class="btn-play-all" id="btn-play-album"><svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>Abspielen</button>
|
||
<button class="btn-outline" id="btn-dl-album"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>Download</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="song-list" id="album-songs"></div>
|
||
</div>
|
||
|
||
<!-- STARRED -->
|
||
<div class="page" id="page-starred">
|
||
<div class="page-header"><div class="page-title">Favoriten ⭐</div></div>
|
||
<div id="starred-songs" class="song-list"></div>
|
||
</div>
|
||
|
||
<!-- PLAYLISTS -->
|
||
<div class="page" id="page-playlists">
|
||
<div class="page-header">
|
||
<div class="page-title">Playlists 🎶</div>
|
||
<button class="btn-outline" style="font-size:12px;padding:8px 14px" onclick="createPlaylistPrompt()">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>Neue Playlist
|
||
</button>
|
||
</div>
|
||
<div id="playlists-grid" class="grid grid-4"></div>
|
||
</div>
|
||
|
||
<!-- PLAYLIST DETAIL -->
|
||
<div class="page" id="page-playlist-detail">
|
||
<button class="back-btn" id="playlist-back-btn" onclick="navigate('playlists')">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>Alle Playlists
|
||
</button>
|
||
<div class="album-header">
|
||
<div class="album-cover" id="playlist-cover"><div class="no-art">🎶</div></div>
|
||
<div class="album-meta">
|
||
<div class="album-type">Playlist</div>
|
||
<div class="album-name" id="playlist-detail-name">—</div>
|
||
<div class="album-stats" id="playlist-detail-stats"></div>
|
||
<div class="playlist-detail-desc" id="playlist-detail-desc"></div>
|
||
<div class="album-actions">
|
||
<button class="btn-play-all" id="btn-play-playlist"><svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>Abspielen</button>
|
||
<button class="btn-outline" id="btn-shuffle-playlist"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>Shuffle</button>
|
||
<button class="btn-outline" id="btn-edit-playlist"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>Bearbeiten</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="song-list" id="playlist-songs"></div>
|
||
</div>
|
||
|
||
<!-- RANDOM -->
|
||
<div class="page" id="page-random">
|
||
<div class="page-header">
|
||
<div class="page-title">Zufällig <em>♪</em></div>
|
||
<button class="btn-outline" onclick="loadRandom()" style="font-size:12px;padding:8px 14px"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>Neu mischen</button>
|
||
</div>
|
||
<div id="random-songs" class="song-list"></div>
|
||
</div>
|
||
|
||
<!-- LOCAL SEARCH -->
|
||
<div class="page" id="page-search">
|
||
<div class="page-header"><div class="page-title">Suche</div></div>
|
||
<div id="search-results"></div>
|
||
</div>
|
||
|
||
<!-- DISCOVER (Global) -->
|
||
<div class="page" id="page-discover">
|
||
<div class="page-header">
|
||
<div class="page-title"><span class="blue">Global</span> Suche</div>
|
||
<span class="badge badge-itunes" style="font-size:11px;padding:4px 10px">iTunes Store + Local</span>
|
||
</div>
|
||
<div class="search-wrap" style="padding:0 0 20px">
|
||
<div class="search-box" style="background:var(--surface);border-color:var(--border2)">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
<input id="global-search-input" type="text" placeholder="Künstler, Titel, Album suchen…" oninput="onGlobalSearch(this.value)" style="font-size:15px">
|
||
</div>
|
||
</div>
|
||
<div id="global-search-results">
|
||
<div class="empty-state"><div class="icon">🔍</div><p>Gib einen Suchbegriff ein um loszulegen</p></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- NEW RELEASES -->
|
||
<div class="page" id="page-new-releases">
|
||
<div class="page-header">
|
||
<div class="page-title">Neue <em>Releases</em></div>
|
||
<span class="badge badge-itunes" style="font-size:11px;padding:4px 10px">iTunes Charts</span>
|
||
</div>
|
||
<div class="section-header" style="margin-top:0"><span class="section-title">Top Alben weltweit</span></div>
|
||
<div id="nr-albums" class="grid grid-4"></div>
|
||
<div class="section-header"><span class="section-title">Top Songs</span></div>
|
||
<div id="nr-songs" class="song-list"></div>
|
||
</div>
|
||
|
||
<!-- RECOMMENDATIONS -->
|
||
<div class="page" id="page-recommendations">
|
||
<div class="page-header">
|
||
<div class="page-title">Für <em>dich</em></div>
|
||
</div>
|
||
<div id="reco-content">
|
||
<div class="empty-state"><div class="icon">🎵</div><p>Spiele zuerst Musik in deiner Bibliothek,<br>dann zeigen wir dir ähnliche Künstler und Tracks</p></div>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- QUEUE PANEL -->
|
||
<div class="queue-panel" id="queue-panel">
|
||
<div class="queue-title">Warteschlange</div>
|
||
<div class="queue-list" id="queue-list"></div>
|
||
</div>
|
||
|
||
<!-- PLAYER -->
|
||
<div class="player">
|
||
<div class="player-drag-handle"></div>
|
||
<div class="player-track">
|
||
<div class="player-art" id="player-art" style="cursor:pointer" title="Jetzt läuft öffnen"><div class="no-art">♪</div></div>
|
||
<div class="player-info">
|
||
<div style="display:flex;align-items:center;gap:6px">
|
||
<div class="player-title" id="player-title" onclick="playerBarGoAlbum()" style="cursor:pointer" title="Zum Album">Kein Titel</div>
|
||
<button id="btn-star-current" onclick="starCurrent()" title="Favorit" style="background:none;border:none;cursor:pointer;padding:2px;flex-shrink:0;display:flex;align-items:center">
|
||
<svg id="star-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--text3)" stroke-width="2" style="transition:fill .2s,stroke .2s"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="player-artist" id="player-artist">
|
||
<span id="player-artist-name" onclick="playerBarGoArtist()" style="cursor:pointer" title="Zum Künstler">—</span><span id="player-album-sep" style="color:var(--text3)"> · </span><span id="player-album-name" onclick="playerBarGoAlbum()" style="cursor:pointer" title="Zum Album"></span>
|
||
</div>
|
||
<div class="player-status-row">
|
||
<span class="status-badge badge-shuffle" id="badge-shuffle">🔀 Shuffle</span>
|
||
<span class="status-badge badge-repeat" id="badge-repeat">🔁 Repeat</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="player-controls">
|
||
<div class="ctrl-buttons">
|
||
<button class="ctrl-btn" id="btn-shuffle" onclick="toggleShuffle()" title="Shuffle">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>
|
||
</button>
|
||
<button class="ctrl-btn" onclick="prevSong()">
|
||
<svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><polygon points="19 20 9 12 19 4 19 20"/><line x1="5" y1="19" x2="5" y2="5" stroke="currentColor" stroke-width="2"/></svg>
|
||
</button>
|
||
<button class="play-pause-btn" id="btn-play-pause" onclick="togglePlay()">
|
||
<svg id="play-icon" width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
</button>
|
||
<button class="ctrl-btn" onclick="nextSong()">
|
||
<svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 4 15 12 5 20 5 4"/><line x1="19" y1="5" x2="19" y2="19" stroke="currentColor" stroke-width="2"/></svg>
|
||
</button>
|
||
<button class="ctrl-btn" id="btn-repeat" onclick="toggleRepeat()" title="Wiederholen">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="progress-bar-wrap">
|
||
<span class="progress-time" id="time-cur">0:00</span>
|
||
<div class="progress-bar" id="progress-bar" onclick="seekTo(event)"><div class="progress-fill" id="progress-fill"></div></div>
|
||
<span class="progress-time right" id="time-total">0:00</span>
|
||
</div>
|
||
</div>
|
||
<div class="player-right">
|
||
<button class="icon-btn" onclick="downloadCurrent()" title="Herunterladen">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||
</button>
|
||
<button class="icon-btn" onclick="toggleQueue()" title="Warteschlange">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
||
</button>
|
||
<button class="icon-btn" id="btn-radio-mode" onclick="toggleRadioMode()" title="Radio-Modus: endloser Shuffle">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="2"/><path d="M4.93 4.93a10 10 0 0 0 0 14.14"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M7.76 7.76a6 6 0 0 0 0 8.49"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49"/></svg>
|
||
</button>
|
||
<div class="volume-wrap">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--text3)"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
|
||
<input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="80" oninput="setVolume(this.value)">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PREVIEW BAR (iTunes 30s) -->
|
||
<div class="preview-bar" id="preview-bar">
|
||
<div class="player-art" id="preview-art" style="width:42px;height:42px;border-radius:6px"><div class="no-art" style="font-size:16px">♪</div></div>
|
||
<div class="preview-info">
|
||
<div class="preview-title" id="preview-title">—<span class="preview-badge">30s Preview</span></div>
|
||
<div class="preview-sub" id="preview-sub">iTunes Store</div>
|
||
</div>
|
||
<div class="preview-prog"><div class="preview-fill" id="preview-fill"></div></div>
|
||
<button class="preview-close" onclick="stopPreview()">✕</button>
|
||
</div>
|
||
|
||
<!-- DOWNLOAD MODAL -->
|
||
<div class="modal-overlay" id="download-modal">
|
||
<div class="modal">
|
||
<h2>Auf Server herunterladen</h2>
|
||
<p class="modal-sub">Der NovaSonic Backend lädt die Musik via yt-dlp herunter und speichert sie direkt in deinem Navidrome-Musikordner.</p>
|
||
<div class="modal-track-info" id="dl-modal-info"></div>
|
||
<div class="modal-opts" id="dl-modal-opts">
|
||
<div class="modal-opt selected" data-type="track" onclick="selectDlType(this,'track')">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>
|
||
<div><div class="opt-label">Einzelner Track</div><div class="opt-sub">Nur diesen Titel herunterladen</div></div>
|
||
</div>
|
||
<div class="modal-opt" data-type="album" onclick="selectDlType(this,'album')">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>
|
||
<div><div class="opt-label">Ganzes Album</div><div class="opt-sub" id="dl-album-sub">Album herunterladen</div></div>
|
||
</div>
|
||
<div class="modal-opt" data-type="artist" onclick="selectDlType(this,'artist')">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
|
||
<div><div class="opt-label">Ganzer Künstler</div><div class="opt-sub">Alle Top-Alben des Künstlers</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-disclaimer">
|
||
ℹ️ Bitte lade nur Musik herunter, die du besitzt oder für die du eine Lizenz hast. Dieses Tool ist für Backup-Zwecke und eigene Inhalte gedacht.
|
||
</div>
|
||
<div class="modal-btns">
|
||
<button class="modal-btns btn-cancel" style="flex:1;padding:12px;border-radius:9px;font-family:var(--font);font-size:14px;font-weight:600;cursor:pointer;border:none;background:var(--surface2);color:var(--text2)" onclick="closeDlModal()">Abbrechen</button>
|
||
<button class="modal-btns btn-download" style="flex:1;padding:12px;border-radius:9px;font-family:var(--font);font-size:14px;font-weight:600;cursor:pointer;border:none;background:linear-gradient(135deg,var(--blue),#2d6ad4);color:#fff" id="btn-do-download" onclick="executeDownload()">Herunterladen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SETTINGS MODAL -->
|
||
<div class="modal-overlay" id="settings-modal">
|
||
<div class="modal" style="padding:0;display:flex;flex-direction:column;max-height:85vh;overflow:hidden;width:min(480px,calc(100vw - 32px))">
|
||
<!-- Sticky header -->
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--border);flex-shrink:0">
|
||
<div style="display:flex;align-items:center;gap:12px">
|
||
<div style="width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--accent),#c07010);display:flex;align-items:center;justify-content:center;font-size:17px;box-shadow:0 4px 12px var(--accent-glow)">⚙</div>
|
||
<div>
|
||
<div style="font-size:17px;font-weight:800;letter-spacing:-.3px">Einstellungen</div>
|
||
<div style="font-size:11px;color:var(--text2);margin-top:1px">NovaSonic</div>
|
||
</div>
|
||
</div>
|
||
<button onclick="closeSettings()" style="background:var(--surface2);border:none;color:var(--text2);width:32px;height:32px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0;transition:background .15s" onmouseover="this.style.background='var(--surface)'" onmouseout="this.style.background='var(--surface2)'">✕</button>
|
||
</div>
|
||
<!-- Scrollable body -->
|
||
<div style="flex:1;overflow-y:auto;padding:18px 24px 8px">
|
||
<div class="settings-section">
|
||
<h3 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
|
||
<span style="width:24px;height:24px;border-radius:6px;background:var(--blue-dim);display:inline-flex;align-items:center;justify-content:center;font-size:12px">🌐</span>
|
||
Navidrome Server
|
||
</h3>
|
||
<div class="field"><label>Server URL</label><input id="s-url" type="url" placeholder="https://music.example.com"></div>
|
||
<div class="field"><label>Benutzername</label><input id="s-user" type="text" placeholder="admin"></div>
|
||
<div class="field"><label>Passwort</label><input id="s-pass" type="password" placeholder="(unverändert lassen)"></div>
|
||
</div>
|
||
<div class="settings-section">
|
||
<h3 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
|
||
<span style="width:24px;height:24px;border-radius:6px;background:var(--accent-dim);display:inline-flex;align-items:center;justify-content:center;font-size:12px">🔑</span>
|
||
Discover APIs
|
||
</h3>
|
||
<div class="field">
|
||
<label>Last.fm API Key</label>
|
||
<input id="s-lastfm" type="text" placeholder="Für Künstler-Infos & Empfehlungen">
|
||
<div class="hint">Kostenlos unter last.fm/api/account/create</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>NovaSonic Backend URL</label>
|
||
<input id="s-backend" type="url" placeholder="http://192.168.x.x:3700">
|
||
<div class="hint">Für Download-to-Server via yt-dlp</div>
|
||
</div>
|
||
<div class="settings-row" style="margin-top:8px;border-radius:9px">
|
||
<div>
|
||
<div style="font-size:13px;font-weight:500">Backend Status</div>
|
||
<div class="hint" id="backend-status-text">Nicht verbunden</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<div class="status-dot" id="backend-status-dot"></div>
|
||
<button class="btn-outline" style="padding:6px 12px;font-size:12px" onclick="checkBackend()">Testen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Sticky footer -->
|
||
<div style="display:flex;gap:8px;padding:12px 24px 16px;border-top:1px solid var(--border);flex-shrink:0;background:var(--surface);border-radius:0 0 18px 18px">
|
||
<button style="flex:1;padding:12px;border-radius:9px;font-family:var(--font);font-size:14px;font-weight:600;cursor:pointer;border:none;background:var(--surface2);color:var(--text2)" onclick="closeSettings()">Abbrechen</button>
|
||
<button style="flex:1;padding:12px;border-radius:9px;font-family:var(--font);font-size:14px;font-weight:600;cursor:pointer;border:none;background:linear-gradient(135deg,var(--accent),#c07010);color:#000" onclick="saveSettings()">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JOB PANEL -->
|
||
<div class="jobs-panel" id="jobs-panel"></div>
|
||
|
||
<div class="toast-container" id="toast-container"></div>
|
||
<audio id="audio-element"></audio>
|
||
<audio id="preview-audio"></audio>
|
||
|
||
<!-- CONTEXT MENU -->
|
||
<div class="ctx-menu" id="ctx-menu" style="display:none"></div>
|
||
|
||
<!-- ADD TO PLAYLIST MODAL -->
|
||
<div class="pl-modal" id="pl-modal">
|
||
<div class="inner">
|
||
<h3 id="pl-modal-title">Zu Playlist hinzufügen</h3>
|
||
<div class="pl-list" id="pl-list"></div>
|
||
<div class="pl-new-row">
|
||
<input class="pl-input" id="pl-new-name" placeholder="Neue Playlist erstellen…" type="text">
|
||
<div class="pl-btns">
|
||
<button style="background:var(--surface2);color:var(--text2)" onclick="closePlModal()">Abbrechen</button>
|
||
<button style="background:linear-gradient(135deg,var(--accent),#c07010);color:#000" onclick="createAndAddPlaylist()">Erstellen & Hinzufügen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- EDIT PLAYLIST MODAL -->
|
||
<div class="edit-pl-overlay" id="edit-pl-overlay" onclick="if(event.target===this)closeEditPlModal()">
|
||
<div class="edit-pl-box">
|
||
<div class="edit-pl-header">
|
||
<span>Playlist bearbeiten</span>
|
||
<button class="edit-pl-close" onclick="closeEditPlModal()">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="edit-pl-body">
|
||
<div class="edit-pl-cover-wrap">
|
||
<div class="edit-pl-cover" id="edit-pl-cover"><div class="no-art">🎶</div></div>
|
||
<div class="edit-pl-cover-actions">
|
||
<button class="edit-pl-cover-btn" onclick="document.getElementById('edit-pl-file').click()" title="Bild hochladen">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||
</button>
|
||
<button class="edit-pl-cover-btn" onclick="editPlRemoveCover()" title="Bild entfernen">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>
|
||
</button>
|
||
</div>
|
||
<input type="file" id="edit-pl-file" accept="image/*" style="display:none" onchange="editPlCoverSelected(this)">
|
||
</div>
|
||
<div class="edit-pl-fields">
|
||
<label class="edit-pl-label">Name <span style="color:var(--accent)">*</span></label>
|
||
<input class="edit-pl-input" id="edit-pl-name" type="text" placeholder="Playlist-Name">
|
||
<label class="edit-pl-label">Beschreibung</label>
|
||
<textarea class="edit-pl-input edit-pl-textarea" id="edit-pl-desc" placeholder="Optionale Beschreibung…" rows="3"></textarea>
|
||
<div class="edit-pl-toggle-row">
|
||
<span>Öffentlich</span>
|
||
<div class="toggle" id="edit-pl-public-toggle" onclick="this.classList.toggle('on')"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="edit-pl-footer">
|
||
<button class="edit-pl-btn-cancel" onclick="closeEditPlModal()">Abbrechen</button>
|
||
<button class="edit-pl-btn-save" onclick="saveEditPlaylist()">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SONG INFO MODAL -->
|
||
<div class="info-modal" id="info-modal">
|
||
<div class="inner">
|
||
<div class="info-header">
|
||
<div class="info-cover" id="info-cover"><div class="no-art" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:32px">🎵</div></div>
|
||
<div><div class="info-top-title" id="info-top-title">—</div><div class="info-top-sub" id="info-top-sub">—</div></div>
|
||
</div>
|
||
<div class="info-rows" id="info-rows"></div>
|
||
<button class="info-close" onclick="closeInfoModal()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- NOW PLAYING OVERLAY -->
|
||
<div class="np-overlay" id="np-overlay">
|
||
<div class="np-bg" id="np-bg"></div>
|
||
<div class="np-inner">
|
||
<div class="np-topbar">
|
||
<button class="np-close" onclick="closeNowPlaying()" title="Schließen">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
|
||
</button>
|
||
<div class="np-tabs">
|
||
<button class="np-tab active" id="np-tab-player" onclick="switchNpTab('player')">Jetzt läuft</button>
|
||
<button class="np-tab" id="np-tab-lyrics" onclick="switchNpTab('lyrics')">Lyrics</button>
|
||
<button class="np-tab" id="np-tab-queue" onclick="switchNpTab('queue')">Queue</button>
|
||
</div>
|
||
<button class="np-menu-btn" onclick="openNpMenu()" title="Optionen">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="5" r="1.5" fill="currentColor"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/><circle cx="12" cy="19" r="1.5" fill="currentColor"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div id="np-player-view">
|
||
<div class="np-cover" id="np-cover">
|
||
<div class="no-art">♪</div>
|
||
</div>
|
||
|
||
<div class="np-info">
|
||
<div class="np-text">
|
||
<div class="np-title" id="np-title">Kein Titel</div>
|
||
<div class="np-artist" id="np-artist">
|
||
<span id="np-artist-name" onclick="npGoArtist()" style="cursor:pointer">—</span><span id="np-album-sep" style="opacity:.5"> · </span><span id="np-album-name" onclick="npGoAlbum()" style="cursor:pointer;opacity:.8">—</span>
|
||
</div>
|
||
</div>
|
||
<button class="np-star" id="np-star-btn" onclick="npToggleStar()">
|
||
<svg id="np-star-icon" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,.5)" stroke-width="2" style="transition:fill .2s,stroke .2s"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="np-progress">
|
||
<div class="np-bar" id="np-bar" onclick="npSeek(event)">
|
||
<div class="np-bar-fill" id="np-fill"></div>
|
||
</div>
|
||
<div class="np-times">
|
||
<span id="np-cur">0:00</span>
|
||
<span id="np-tot">0:00</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="np-controls">
|
||
<button class="np-ctrl" id="np-shuffle-btn" onclick="toggleShuffle()" title="Shuffle">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>
|
||
</button>
|
||
<button class="np-ctrl" onclick="prevSong()">
|
||
<svg width="30" height="30" viewBox="0 0 24 24" fill="currentColor"><polygon points="19 20 9 12 19 4 19 20"/><line x1="5" y1="19" x2="5" y2="5" stroke="currentColor" stroke-width="2"/></svg>
|
||
</button>
|
||
<button class="np-play" id="np-play-btn" onclick="togglePlay()">
|
||
<svg id="np-play-icon" width="26" height="26" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||
</button>
|
||
<button class="np-ctrl" onclick="nextSong()">
|
||
<svg width="30" height="30" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 4 15 12 5 20 5 4"/><line x1="19" y1="5" x2="19" y2="19" stroke="currentColor" stroke-width="2"/></svg>
|
||
</button>
|
||
<button class="np-ctrl" id="np-repeat-btn" onclick="toggleRepeat()" title="Wiederholen">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="np-extras">
|
||
<button class="np-icon-btn" onclick="downloadCurrent()" title="Download">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||
</button>
|
||
<div class="np-vol">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:rgba(255,255,255,.45)"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/></svg>
|
||
<input type="range" class="np-vol-slider" id="np-vol-slider" min="0" max="100" value="80" oninput="setVolume(this.value);document.getElementById('volume-slider').value=this.value">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:rgba(255,255,255,.45)"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
|
||
</div>
|
||
<button class="np-icon-btn" id="np-radio-btn" onclick="toggleRadioMode()" title="Radio-Modus: endloser Shuffle">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="2"/><path d="M4.93 4.93a10 10 0 0 0 0 14.14"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M7.76 7.76a6 6 0 0 0 0 8.49"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49"/></svg>
|
||
</button>
|
||
</div>
|
||
</div><!-- /np-player-view -->
|
||
|
||
<div id="np-lyrics-view">
|
||
<div id="np-lyrics-content"></div>
|
||
</div>
|
||
|
||
<div id="np-queue-view">
|
||
<div id="np-queue-list"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- NP ACTION SHEET -->
|
||
<div class="np-sheet-overlay" id="np-sheet-overlay" onclick="closeNpMenu()"></div>
|
||
<div class="np-sheet" id="np-sheet">
|
||
<div class="np-sheet-handle"></div>
|
||
<div class="np-sheet-song" id="np-sheet-song"></div>
|
||
<div id="np-sheet-items"></div>
|
||
</div>
|
||
|
||
<nav class="bottom-nav" id="bottom-nav">
|
||
<div class="bottom-nav-inner">
|
||
<div class="bnav-tabs">
|
||
<button class="bnav-tab active" id="bnav-tab-library" onclick="switchNavMode('library')">Bibliothek</button>
|
||
<button class="bnav-tab" id="bnav-tab-discover" onclick="switchNavMode('discover')">Entdecken</button>
|
||
</div>
|
||
<div class="bnav-pages" id="bnav-library">
|
||
<button class="bnav-item active" onclick="navigate('home')" data-page="home">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||
Home
|
||
</button>
|
||
<button class="bnav-item" onclick="navigate('artists')" data-page="artists">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||
Künstler
|
||
</button>
|
||
<button class="bnav-item" onclick="navigate('playlists')" data-page="playlists">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
||
Playlists
|
||
</button>
|
||
<button class="bnav-item" onclick="navigate('starred')" data-page="starred">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
Favoriten
|
||
</button>
|
||
</div>
|
||
<div class="bnav-pages" id="bnav-discover" style="display:none">
|
||
<button class="bnav-item" onclick="navigate('discover')" data-page="discover">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
Suche
|
||
</button>
|
||
<button class="bnav-item" onclick="navigate('new-releases')" data-page="new-releases">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>
|
||
Releases
|
||
</button>
|
||
<button class="bnav-item" onclick="navigate('recommendations')" data-page="recommendations">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
|
||
Für dich
|
||
</button>
|
||
<button class="bnav-item" onclick="navigate('random')" data-page="random">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>
|
||
Zufällig
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
</body>
|
||
</html> |