Index: src/assets/js/audio-player.js
===================================================================
--- src/assets/js/audio-player.js	(revision 5f47671b8c8beb7dbe7dcbfc46d88c69727697ba)
+++ src/assets/js/audio-player.js	(revision 9e6b54fd187cd53d7d9e8a697c036e2fc7302335)
@@ -289,4 +289,88 @@
   });
 
+  // ============================================================
+  // 4a. YouTube-backend — speelt YT-AUDIO via een verborgen IFrame-speler in de
+  // (persistente) speler-root. Zo speelt YouTube net als de site-tracks door bij
+  // htmx-navigatie en is 't bedienbaar in de onderbalk. Video wordt niet getoond.
+  // ============================================================
+  let ytMode = false, ytPlayer = null, ytReady = false, ytApiLoading = null, ytTick = null;
+
+  function loadYTApi() {
+    if (window.YT && window.YT.Player) return Promise.resolve(window.YT);
+    if (ytApiLoading) return ytApiLoading;
+    ytApiLoading = new Promise((resolve) => {
+      const prev = window.onYouTubeIframeAPIReady;
+      window.onYouTubeIframeAPIReady = function () { if (prev) { try { prev(); } catch (e) {} } resolve(window.YT); };
+      if (!document.querySelector('script[src*="youtube.com/iframe_api"]')) {
+        const s = document.createElement('script'); s.src = 'https://www.youtube.com/iframe_api'; s.async = true; document.head.appendChild(s);
+      }
+      setTimeout(() => { if (window.YT && window.YT.Player) resolve(window.YT); }, 8000);
+    });
+    return ytApiLoading;
+  }
+
+  function ensureYT() {
+    if (ytPlayer) return Promise.resolve(ytPlayer);
+    return loadYTApi().then((YT) => new Promise((resolve) => {
+      if (!YT || !YT.Player) { resolve(null); return; }
+      let host = document.getElementById('pcms-yt-host');
+      if (!host) {
+        host = document.createElement('div'); host.id = 'pcms-yt-host';
+        const m = document.createElement('div'); m.id = 'pcms-yt-mount'; host.appendChild(m);
+        root.appendChild(host);
+      }
+      ytPlayer = new YT.Player('pcms-yt-mount', {
+        host: 'https://www.youtube-nocookie.com',
+        playerVars: { controls: 0, playsinline: 1, rel: 0, modestbranding: 1, iv_load_policy: 3, origin: window.location.origin },
+        events: {
+          onReady: () => { ytReady = true; try { ytPlayer.setVolume(audio.muted ? 0 : Math.round(audio.volume * 100)); } catch (e) {} resolve(ytPlayer); },
+          onStateChange: onYTState,
+        },
+      });
+      setTimeout(() => resolve(ytPlayer), 8000);
+    }));
+  }
+
+  function onYTState(e) {
+    if (!ytMode) return;
+    const s = e.data;  // 1 playing, 2 paused, 0 ended
+    if (s === 1) {
+      isPlaying = true; root.classList.add('is-playing'); root.classList.remove('audio-needs-tap');
+      mediaRegistry().setActive(registrySelf); startYTTick(); pullYTMeta();
+    } else if (s === 2) {
+      isPlaying = false; root.classList.remove('is-playing'); stopYTTick();
+    } else if (s === 0) {
+      stopYTTick(); next();
+    }
+  }
+
+  function startYTTick() {
+    if (ytTick) return;
+    ytTick = setInterval(() => {
+      if (!ytPlayer || !ytMode) return;
+      try { updateProgressUI(ytPlayer.getCurrentTime() || 0, ytPlayer.getDuration() || 0); } catch (e) {}
+    }, 250);
+  }
+  function stopYTTick() { if (ytTick) { clearInterval(ytTick); ytTick = null; } }
+
+  function pullYTMeta() {
+    try {
+      const t = queue[currentIndex]; if (!t || !t.yt || !ytPlayer.getVideoData) return;
+      const vd = ytPlayer.getVideoData();
+      if (vd && vd.title && (!t.title || t.title === 'YouTube')) {
+        t.title = vd.title; titleEl.textContent = vd.title; sheetTitle.textContent = vd.title; renderQueue(); markPlaying(t.id);
+      }
+    } catch (e) {}
+  }
+
+  // Gedeelde voortgang-UI (beide backends). Audio gebruikt 'timeupdate', YT de tick.
+  function updateProgressUI(cur, dur) {
+    if (!dur || isNaN(dur)) return;
+    const pct = Math.max(0, Math.min(100, (cur / dur) * 100));
+    seekFill.style.width = pct + '%'; sheetSeekFill.style.width = pct + '%';
+    currentEl.textContent = formatTime(cur); totalEl.textContent = formatTime(dur);
+    sheetCurrent.textContent = formatTime(cur); sheetTotal.textContent = formatTime(dur);
+  }
+
   function loadTrack(index, autoplay, metaOnly) {
     if (!queue[index]) {
@@ -296,14 +380,8 @@
     currentIndex = index;
     const t = queue[index];
-    if (!t.url) {
-      console.error('[pcms-audio] track has no url', t);
-      return;
-    }
-    console.log('[pcms-audio] loading', t.title, t.url, metaOnly ? '(meta only)' : '');
-    // Metadata + chrome update synchronously so the UI reacts instantly while
-    // the bytes download.
-    titleEl.textContent  = t.title  || 'Untitled';
+    // Metadata + chrome update synchronously (geldt voor beide backends).
+    titleEl.textContent  = t.title  || (t.yt ? 'YouTube' : 'Untitled');
     artistEl.textContent = t.artist || '';
-    sheetTitle.textContent  = t.title  || 'Untitled';
+    sheetTitle.textContent  = t.title  || (t.yt ? 'YouTube' : 'Untitled');
     sheetArtist.textContent = t.artist || '';
     sheetAlbum.textContent  = albumName || '';
@@ -316,4 +394,30 @@
 
     if (metaOnly) return;
+
+    // YouTube-bron → verborgen YT-speler i.p.v. de blob-<audio>.
+    if (t.yt) {
+      ytMode = true;
+      try { audio.pause(); } catch (e) {}
+      loadSeq++; dropPreload();
+      updateProgressUI(0, 1); currentEl.textContent = '0:00'; totalEl.textContent = '0:00';
+      seekFill.style.width = '0%'; sheetSeekFill.style.width = '0%';
+      root.classList.add('audio-loading');
+      ensureYT().then((p) => {
+        if (queue[currentIndex] !== t) return;  // inmiddels andere track gekozen
+        root.classList.remove('audio-loading');
+        if (!p) { onLoadError(new Error('YT API niet beschikbaar'), loadSeq); return; }
+        try {
+          if (autoplay) p.loadVideoById(t.yt); else p.cueVideoById(t.yt);
+          p.setVolume(audio.muted ? 0 : Math.round(audio.volume * 100));
+        } catch (e) {}
+      });
+      return;
+    }
+
+    // Normale blob-audio track.
+    ytMode = false;
+    stopYTTick();
+    try { if (ytPlayer && ytPlayer.stopVideo) ytPlayer.stopVideo(); } catch (e) {}
+    if (!t.url) { console.error('[pcms-audio] track has no url', t); return; }
 
     const mySeq = ++loadSeq;
@@ -367,4 +471,9 @@
 
   function play() {
+    if (ytMode) {
+      if (ytPlayer && ytReady) { try { ytPlayer.playVideo(); } catch (e) {} }
+      else if (queue[currentIndex]) loadTrack(currentIndex, true);
+      return;
+    }
     if (!audio.src) {
       // Nothing fetched yet (pre-seed showed metadata only, or a load is still
@@ -388,6 +497,6 @@
     }
   }
-  function pause()      { audio.pause(); }
-  function togglePlay() { audio.paused ? play() : pause(); }
+  function pause()      { if (ytMode) { try { ytPlayer && ytPlayer.pauseVideo(); } catch (e) {} return; } audio.pause(); }
+  function togglePlay() { if (ytMode) { isPlaying ? pause() : play(); return; } audio.paused ? play() : pause(); }
   function next() {
     if (!queue.length) return;
@@ -404,4 +513,6 @@
     document.body.classList.remove('has-audio-player');
     closeSheet();
+    ytMode = false; stopYTTick();
+    try { if (ytPlayer && ytPlayer.stopVideo) ytPlayer.stopVideo(); } catch (e) {}
     queue = [];
     albumName = '';
@@ -462,5 +573,5 @@
     return r;
   }
-  const registrySelf = { pause() { try { audio.pause(); } catch (e) {} } };
+  const registrySelf = { pause() { try { audio.pause(); } catch (e) {} try { if (ytPlayer && ytPlayer.pauseVideo) ytPlayer.pauseVideo(); } catch (e) {} } };
 
   // ============================================================
@@ -498,12 +609,6 @@
   audio.addEventListener('volumechange', () => { root.classList.toggle('is-muted', audio.muted || audio.volume === 0); });
   audio.addEventListener('timeupdate', () => {
-    if (!audio.duration || isNaN(audio.duration)) return;
-    const pct = (audio.currentTime / audio.duration) * 100;
-    seekFill.style.width = pct + '%';
-    sheetSeekFill.style.width = pct + '%';
-    currentEl.textContent  = formatTime(audio.currentTime);
-    totalEl.textContent    = formatTime(audio.duration);
-    sheetCurrent.textContent = formatTime(audio.currentTime);
-    sheetTotal.textContent   = formatTime(audio.duration);
+    if (ytMode) return;  // YT-voortgang loopt via de tick
+    updateProgressUI(audio.currentTime, audio.duration);
   });
 
@@ -516,7 +621,11 @@
   function attachSeek(seekEl) {
     seekEl.addEventListener('click', (e) => {
-      if (!audio.duration) return;
       const rect = seekEl.getBoundingClientRect();
       const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
+      if (ytMode) {
+        try { const d = ytPlayer.getDuration() || 0; if (d) ytPlayer.seekTo(ratio * d, true); } catch (e2) {}
+        return;
+      }
+      if (!audio.duration) return;
       audio.currentTime = ratio * audio.duration;
     });
@@ -527,8 +636,14 @@
   audio.volume = 0.8;
   volumeSlider.addEventListener('input', () => {
-    audio.volume = volumeSlider.value / 100;
-    if (volumeSlider.value > 0) audio.muted = false;
-  });
-  muteBtn.addEventListener('click', () => { audio.muted = !audio.muted; });
+    const v = parseInt(volumeSlider.value, 10) || 0;
+    audio.volume = v / 100;
+    if (v > 0) audio.muted = false;
+    if (ytPlayer) { try { ytPlayer.setVolume(v); if (v > 0 && ytPlayer.unMute) ytPlayer.unMute(); } catch (e) {} }
+  });
+  muteBtn.addEventListener('click', () => {
+    audio.muted = !audio.muted;
+    if (ytPlayer) { try { audio.muted ? ytPlayer.mute() : ytPlayer.unMute(); } catch (e) {} }
+    root.classList.toggle('is-muted', audio.muted);
+  });
 
   // ============================================================
@@ -797,6 +912,20 @@
   // 9. Public API
   // ============================================================
+  // Speel een YouTube-bron als AUDIO af in deze (persistente) site-speler — door
+  // de embed-speler aangeroepen voor een audio-only YouTube-embed. Zo verschijnt
+  // 'ie in de onderbalk én speelt 'ie door bij navigeren.
+  function playYouTube(opts) {
+    if (!opts || !opts.id) return;
+    setQueue([{
+      yt: opts.id,
+      title: opts.title || 'YouTube',
+      artist: opts.artist || '',
+      cover: opts.cover || '',
+      postUrl: opts.postUrl || '',
+    }], 0);
+  }
+
   window.pcmsAudioPlayer = {
-    setQueue, play, pause, next, prev, close, openSheet, closeSheet,
+    setQueue, play, pause, next, prev, close, openSheet, closeSheet, playYouTube,
     isPlaying: () => isPlaying,
     currentTrack: () => queue[currentIndex] || null,
