diff --git a/app.js b/app.js
index aa3f15d..a1c0a6f 100644
--- a/app.js
+++ b/app.js
@@ -1329,9 +1329,38 @@ async function loadArtists(){
// ─── PWA ───
function injectPWA(){
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.register('sw.js').catch(() => {});
- }
+ if (!('serviceWorker' in navigator)) return;
+ navigator.serviceWorker.register('sw.js').then(reg => {
+ // Force-check for SW update every time the app opens
+ reg.update().catch(() => {});
+ // New SW found while page is open (e.g. user left tab open overnight)
+ reg.addEventListener('updatefound', () => {
+ const sw = reg.installing;
+ if (!sw) return;
+ sw.addEventListener('statechange', () => {
+ // 'installed' + existing controller = update ready but waiting for skipWaiting
+ // With skipWaiting() this fires briefly; the SW_UPDATED message handles the banner
+ });
+ });
+ }).catch(() => {});
+
+ // SW posts SW_UPDATED after it activates and claims this client
+ navigator.serviceWorker.addEventListener('message', e => {
+ if (e.data?.type === 'SW_UPDATED') showUpdateBanner(e.data.version);
+ });
+}
+
+function showUpdateBanner(version) {
+ if (document.getElementById('update-banner')) return;
+ const b = document.createElement('div');
+ b.id = 'update-banner';
+ b.className = 'update-banner';
+ b.innerHTML = `
+ ✦ Update verfügbar${version ? ' ('+version+')' : ''}
+
+
+ `;
+ document.body.appendChild(b);
}
diff --git a/styles.css b/styles.css
index 53525d1..fa90dcf 100644
--- a/styles.css
+++ b/styles.css
@@ -704,6 +704,34 @@ nav{flex:1;overflow-y:auto;padding:4px 10px}
.mobile-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:140}
.mobile-overlay.active{display:block}
+/* ── UPDATE BANNER ── */
+.update-banner{
+ position:fixed;bottom:calc(var(--player-h) + 20px);left:50%;
+ transform:translateX(-50%);
+ display:flex;align-items:center;gap:10px;
+ background:var(--surface);border:1px solid var(--accent);
+ border-radius:12px;padding:10px 14px;
+ box-shadow:0 8px 32px rgba(0,0,0,.55),0 0 0 1px rgba(232,160,32,.15);
+ z-index:202;white-space:nowrap;
+ animation:toastIn .3s ease;
+}
+.update-banner-text{font-size:13px;font-weight:600;color:var(--text)}
+.update-banner-btn{
+ background:var(--accent);color:#000;border:none;
+ padding:6px 14px;border-radius:7px;
+ font-size:12px;font-weight:700;cursor:pointer;font-family:var(--font);
+ transition:opacity .15s;
+}
+.update-banner-btn:hover{opacity:.85}
+.update-banner-close{
+ background:none;border:none;color:var(--text3);
+ cursor:pointer;font-size:15px;padding:0 2px;line-height:1;
+}
+.update-banner-close:hover{color:var(--text)}
+@media(max-width:768px){
+ .update-banner{bottom:calc(var(--player-h) + 80px + 12px)}
+}
+
/* ── SEARCH page ── */
.search-section{margin-bottom:24px}
.search-section-title{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px}
diff --git a/sw.js b/sw.js
index 8af5516..b3cc841 100644
--- a/sw.js
+++ b/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'ns-v27';
+const CACHE = 'ns-v28';
const PRECACHE = ['/', 'index.html', 'styles.css', 'app.js', 'manifest.json', 'icon.svg'];
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
@@ -12,8 +12,8 @@ self.addEventListener('activate', e => {
caches.keys()
.then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))))
.then(() => self.clients.claim())
- .then(() => self.clients.matchAll({type: 'window', includeUncontrolled: true}))
- .then(clients => clients.forEach(c => c.navigate(c.url)))
+ .then(() => self.clients.matchAll({ type: 'window', includeUncontrolled: true }))
+ .then(clients => clients.forEach(c => c.postMessage({ type: 'SW_UPDATED', version: CACHE })))
);
});