diff --git a/index.html b/index.html
index 09181c4..1eeaf45 100644
--- a/index.html
+++ b/index.html
@@ -324,7 +324,7 @@
-
+
@@ -464,7 +464,7 @@
-
⚙
+
⚙
Einstellungen
NovaSonic
@@ -509,11 +509,18 @@
+
-
+
@@ -537,7 +544,7 @@
-
+
diff --git a/src/main.js b/src/main.js
index 4901040..73d0b53 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,6 +2,7 @@ import { S } from './state.js';
import { toast } from './utils.js';
import { api } from './api.js';
import { injectPWA } from './pwa.js';
+import { loadSavedTheme } from './themes.js';
import {
playQueue, playCurrent, togglePlay, prevSong, nextSong, onEnded,
toggleShuffle, toggleRepeat, toggleQueue, setVolume, seekTo,
@@ -31,6 +32,7 @@ import {
deletePlaylist, _confirmOk, _confirmCancel,
toggleMobileSearch, closeMobileSearch, switchSetupTab,
toggleNotifPanel, deleteNotif, clearNotifs, initNotifBell, _notifNavigate,
+ _pickTheme,
} from './ui.js';
// ─── CONNECT ───
@@ -92,6 +94,8 @@ async function startApp() {
// ─── INIT ───
window.addEventListener('load', () => {
+ loadSavedTheme();
+
// Wire up audio elements from DOM
S.audio = document.getElementById('audio-element');
S.previewAudio = document.getElementById('preview-audio');
@@ -241,7 +245,7 @@ Object.assign(window, {
openEditPlModal, closeEditPlModal, saveEditPlaylist,
editPlUploadCover, editPlCoverSelected, editPlRemoveCover, deletePlaylist, _confirmOk, _confirmCancel,
toggleMobileSearch, closeMobileSearch, switchSetupTab,
- toggleNotifPanel, deleteNotif, clearNotifs, _notifNavigate,
+ toggleNotifPanel, deleteNotif, clearNotifs, _notifNavigate, _pickTheme,
// App
doConnect, disconnect,
});
diff --git a/src/themes.js b/src/themes.js
new file mode 100644
index 0000000..aa0b23d
--- /dev/null
+++ b/src/themes.js
@@ -0,0 +1,70 @@
+const THEME_KEY = 'ns_theme';
+const DEFAULT_ID = 'dark-amber';
+
+export const THEMES = [
+ {
+ id: 'dark-amber',
+ name: 'Dark Amber',
+ accent: '#e8a020',
+ bg: '#07070f',
+ vars: {
+ '--bg': '#07070f',
+ '--bg2': '#0d0d1a',
+ '--bg3': '#12121f',
+ '--surface': '#1a1a2e',
+ '--surface2': '#22223a',
+ '--border': 'rgba(255,255,255,0.07)',
+ '--border2': 'rgba(255,255,255,0.12)',
+ '--accent': '#e8a020',
+ '--accent2': '#f0b840',
+ '--accent-dark':'#c07010',
+ '--accent-dim': 'rgba(232,160,32,0.15)',
+ '--accent-glow':'rgba(232,160,32,0.3)',
+ '--text': '#f0eefc',
+ '--text2': '#8888aa',
+ '--text3': '#555577',
+ '--danger': '#e05555',
+ },
+ },
+ {
+ id: 'dark-ocean',
+ name: 'Dark Ocean',
+ accent: '#4f8ef7',
+ bg: '#070d14',
+ vars: {
+ '--bg': '#070d14',
+ '--bg2': '#0c1520',
+ '--bg3': '#111c28',
+ '--surface': '#182435',
+ '--surface2': '#1f2f42',
+ '--border': 'rgba(255,255,255,0.07)',
+ '--border2': 'rgba(255,255,255,0.13)',
+ '--accent': '#4f8ef7',
+ '--accent2': '#70aaff',
+ '--accent-dark':'#2a6cd6',
+ '--accent-dim': 'rgba(79,142,247,0.15)',
+ '--accent-glow':'rgba(79,142,247,0.3)',
+ '--text': '#edf2ff',
+ '--text2': '#7088aa',
+ '--text3': '#445566',
+ '--danger': '#e05555',
+ },
+ },
+];
+
+export function applyTheme(id) {
+ const theme = THEMES.find(t => t.id === id) || THEMES.find(t => t.id === DEFAULT_ID);
+ const root = document.documentElement;
+ Object.entries(theme.vars).forEach(([k, v]) => root.style.setProperty(k, v));
+ localStorage.setItem(THEME_KEY, theme.id);
+}
+
+export function loadSavedTheme() {
+ const saved = localStorage.getItem(THEME_KEY) || DEFAULT_ID;
+ applyTheme(saved);
+ return saved;
+}
+
+export function getCurrentThemeId() {
+ return localStorage.getItem(THEME_KEY) || DEFAULT_ID;
+}
diff --git a/src/ui.js b/src/ui.js
index bc2b8e2..94c3ccb 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -1,6 +1,7 @@
import { S } from './state.js';
import { esc, formatDur, toast } from './utils.js';
import { api, coverUrl, ndAuth } from './api.js';
+import { THEMES, applyTheme, getCurrentThemeId } from './themes.js';
// Circular imports — safe (only used inside function bodies)
import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon } from './player.js';
import { navigate, loadPlaylists } from './views.js';
@@ -61,9 +62,26 @@ export function openSettings() {
document.getElementById('s-pass').value = '';
document.getElementById('s-lastfm').value = S.cfg.lastfmKey || '';
document.getElementById('s-backend').value = S.cfg.backendUrl || '';
+ _renderThemePicker();
document.getElementById('settings-modal').classList.add('open');
checkBackend();
}
+
+function _renderThemePicker() {
+ const el = document.getElementById('theme-picker');
+ if (!el) return;
+ const current = getCurrentThemeId();
+ el.innerHTML = THEMES.map(t => `
+ `).join('');
+}
+
+export function _pickTheme(id) {
+ applyTheme(id);
+ _renderThemePicker();
+}
export function closeSettings() { document.getElementById('settings-modal').classList.remove('open'); }
export function saveSettings() {
const pass = document.getElementById('s-pass').value;
diff --git a/styles.css b/styles.css
index 5d1be12..583256e 100644
--- a/styles.css
+++ b/styles.css
@@ -2,7 +2,7 @@
--bg:#07070f;--bg2:#0d0d1a;--bg3:#12121f;
--surface:#1a1a2e;--surface2:#22223a;
--border:rgba(255,255,255,0.07);--border2:rgba(255,255,255,0.12);
- --accent:#e8a020;--accent2:#f0b840;
+ --accent:#e8a020;--accent2:#f0b840;--accent-dark:#c07010;
--accent-dim:rgba(232,160,32,0.15);--accent-glow:rgba(232,160,32,0.3);
--blue:#4f8ef7;--blue-dim:rgba(79,142,247,0.15);
--green:#4caf7d;--green-dim:rgba(76,175,125,0.15);
@@ -20,7 +20,7 @@ html,body{height:100%;font-family:var(--font);background:var(--bg);color:var(--t
#setup-screen{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:var(--bg);background-image:radial-gradient(ellipse 60% 50% at 30% 50%,rgba(232,160,32,.08) 0%,transparent 70%),radial-gradient(ellipse 40% 60% at 80% 30%,rgba(80,60,160,.1) 0%,transparent 70%)}
.setup-card{width:min(420px,calc(100vw - 32px));background:var(--surface);border:1px solid var(--border2);border-radius:20px;padding:40px 36px;box-shadow:0 40px 80px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.04)}
.setup-logo{display:flex;align-items:center;gap:12px;margin-bottom:28px}
-.logo-icon{width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,var(--accent),#c07010);display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:0 8px 24px var(--accent-glow)}
+.logo-icon{width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,var(--accent),var(--accent-dark));display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:0 8px 24px var(--accent-glow)}
.setup-logo h1{font-size:24px;font-weight:700;letter-spacing:-.5px}
.setup-logo h1 em{color:var(--accent);font-style:normal}
.setup-tabs{display:flex;gap:4px;background:var(--bg2);border-radius:8px;padding:3px;margin-bottom:24px}
@@ -32,7 +32,7 @@ html,body{height:100%;font-family:var(--font);background:var(--bg);color:var(--t
.field input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-dim)}
.field input::placeholder{color:var(--text3)}
.field .hint{font-size:11px;color:var(--text3);margin-top:4px;line-height:1.4}
-.btn-primary{width:100%;margin-top:20px;padding:13px;background:linear-gradient(135deg,var(--accent),#c07010);color:#000;font-family:var(--font);font-size:15px;font-weight:700;border:none;border-radius:var(--radius-sm);cursor:pointer;transition:opacity .2s,transform .15s;letter-spacing:.2px}
+.btn-primary{width:100%;margin-top:20px;padding:13px;background:linear-gradient(135deg,var(--accent),var(--accent-dark));color:#000;font-family:var(--font);font-size:15px;font-weight:700;border:none;border-radius:var(--radius-sm);cursor:pointer;transition:opacity .2s,transform .15s;letter-spacing:.2px}
.btn-primary:hover{opacity:.9;transform:translateY(-1px)}
.btn-primary:disabled{opacity:.5;cursor:default;transform:none}
.setup-error{margin-top:12px;padding:10px 14px;background:rgba(224,85,85,.12);border:1px solid rgba(224,85,85,.3);border-radius:var(--radius-sm);color:#ff8a8a;font-size:13px;display:none}
@@ -46,7 +46,7 @@ html,body{height:100%;font-family:var(--font);background:var(--bg);color:var(--t
.sidebar{width:var(--sidebar-w);flex-shrink:0;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;padding-bottom:var(--player-h)}
.sidebar-header{padding:18px 18px 10px;display:flex;align-items:center;justify-content:space-between}
.sidebar-logo{display:flex;align-items:center;gap:9px}
-.sidebar-logo .icon{width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,var(--accent),#c07010);display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 4px 12px var(--accent-glow)}
+.sidebar-logo .icon{width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,var(--accent),var(--accent-dark));display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 4px 12px var(--accent-glow)}
.sidebar-logo span{font-size:16px;font-weight:700}
.sidebar-logo em{color:var(--accent);font-style:normal}
.header-btns{display:flex;gap:4px}
@@ -737,6 +737,13 @@ nav{flex:1;overflow-y:auto;padding:4px 10px}
@keyframes toastIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
@keyframes toastOut{to{opacity:0;transform:translateY(8px)}}
+/* ── THEME PICKER ── */
+.theme-picker{display:flex;flex-wrap:wrap;gap:8px}
+.theme-swatch{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:9px;border:2px solid var(--border2);background:var(--bg2);cursor:pointer;transition:border-color .15s,background .15s;font-size:13px;font-weight:500;color:var(--text2)}
+.theme-swatch:hover{border-color:var(--border2);background:var(--surface)}
+.theme-swatch.active{border-color:var(--accent);color:var(--text)}
+.theme-swatch-dot{width:18px;height:18px;border-radius:50%;background:var(--sw-accent);flex-shrink:0;box-shadow:inset 0 0 0 2px rgba(0,0,0,.2)}
+
/* ── SETTINGS MODAL ── */
.settings-section{margin-bottom:20px}
.settings-section h3{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text2);margin-bottom:10px}