Index: src/assets/js/audio-player.js
===================================================================
--- src/assets/js/audio-player.js	(revision b56afa72e85e0b0e9069a678f867ead0c9ffb621)
+++ src/assets/js/audio-player.js	(revision d43ea3fdecb5572a1087888ee3ba2b4ee47b2719)
@@ -341,5 +341,8 @@
       isPlaying = false; root.classList.remove('is-playing'); stopYTTick();
     } else if (s === 0) {
-      stopYTTick(); next();
+      stopYTTick();
+      // Bij een afspeellijst regelt YouTube zelf het doorschakelen → niet dubbel.
+      const t = queue[currentIndex];
+      if (!(t && t.ytList)) next();
     }
   }
@@ -356,8 +359,14 @@
   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);
+      const t = queue[currentIndex]; if (!t || (!t.yt && !t.ytList) || !ytPlayer.getVideoData) return;
+      const vd = ytPlayer.getVideoData() || {};
+      // Bij een afspeellijst wisselt de titel per nummer → altijd bijwerken.
+      if (vd.title && (t.ytList || !t.title || t.title === 'YouTube')) {
+        titleEl.textContent = vd.title; sheetTitle.textContent = vd.title;
+        if (!t.ytList) t.title = vd.title;
+      }
+      if (vd.video_id) {
+        setYtCover(vd.video_id);
+        markPlaying(vd.video_id);  // licht de bijbehorende album-rij op (id="track-<videoId>")
       }
     } catch (e) {}
@@ -395,6 +404,6 @@
     if (metaOnly) return;
 
-    // YouTube-bron → verborgen YT-speler i.p.v. de blob-<audio>.
-    if (t.yt) {
+    // YouTube-bron (los nummer óf afspeellijst/album) → verborgen YT-speler.
+    if (t.yt || t.ytList) {
       ytMode = true;
       try { audio.pause(); } catch (e) {}
@@ -408,5 +417,10 @@
         if (!p) { onLoadError(new Error('YT API niet beschikbaar'), loadSeq); return; }
         try {
-          if (autoplay) p.loadVideoById(t.yt); else p.cueVideoById(t.yt);
+          if (t.ytList) {
+            const idx = t.ytIndex || 0;
+            if (autoplay) p.loadPlaylist({ list: t.ytList, index: idx }); else p.cuePlaylist({ list: t.ytList, index: idx });
+          } else {
+            if (autoplay) p.loadVideoById(t.yt); else p.cueVideoById(t.yt);
+          }
           p.setVolume(audio.muted ? 0 : Math.round(audio.volume * 100));
         } catch (e) {}
@@ -500,8 +514,12 @@
   function togglePlay() { if (ytMode) { isPlaying ? pause() : play(); return; } audio.paused ? play() : pause(); }
   function next() {
+    const t = queue[currentIndex];
+    if (t && t.ytList && ytPlayer && ytMode) { try { ytPlayer.nextVideo(); } catch (e) {} return; }
     if (!queue.length) return;
     loadTrack((currentIndex + 1) % queue.length, true);
   }
   function prev() {
+    const t = queue[currentIndex];
+    if (t && t.ytList && ytPlayer && ytMode) { try { ytPlayer.previousVideo(); } catch (e) {} return; }
     if (!queue.length) return;
     loadTrack(currentIndex === 0 ? queue.length - 1 : currentIndex - 1, true);
@@ -916,7 +934,9 @@
   // 'ie in de onderbalk én speelt 'ie door bij navigeren.
   function playYouTube(opts) {
-    if (!opts || !opts.id) return;
+    if (!opts || (!opts.id && !opts.list)) return;
     setQueue([{
-      yt: opts.id,
+      yt: opts.id || null,
+      ytList: opts.list || null,     // YouTube-afspeellijst/album (OLAK…/PL…)
+      ytIndex: opts.index || 0,      // startindex binnen de lijst
       title: opts.title || 'YouTube',
       artist: opts.artist || '',
@@ -924,14 +944,20 @@
       postUrl: opts.postUrl || '',
     }], 0);
-    // Upgrade de cover async naar de scherpe maxresdefault (ook 16:9, geen balken)
-    // als die bestaat. Async → de user-gesture voor afspelen blijft intact; de
-    // direct gezette mqdefault toont al meteen een balkloze cover.
+    // De scherpe cover + echte titel komen via pullYTMeta() zodra het nummer speelt.
+  }
+
+  // Zet de bar-cover op de thumbnail van een video (mqdefault = balkloos), en
+  // upgrade async naar de scherpe maxresdefault als die bestaat.
+  function setYtCover(videoId) {
+    if (!videoId) return;
+    const mq = 'https://i.ytimg.com/vi/' + videoId + '/mqdefault.jpg';
+    setCoverImage(cover, mq); setCoverImage(sheetCover, mq);
     try {
-      const maxres = 'https://i.ytimg.com/vi/' + opts.id + '/maxresdefault.jpg';
+      const maxres = 'https://i.ytimg.com/vi/' + videoId + '/maxresdefault.jpg';
       const im = new Image();
       im.onload = () => {
-        if (im.naturalWidth <= 120) return;  // 120x90 = YouTube-placeholder (geen maxres)
-        const t = queue[currentIndex];
-        if (t && t.yt === opts.id) { t.cover = maxres; setCoverImage(cover, maxres); setCoverImage(sheetCover, maxres); }
+        if (im.naturalWidth <= 120) return;
+        const cur = ytPlayer && ytPlayer.getVideoData ? (ytPlayer.getVideoData() || {}).video_id : null;
+        if (cur === videoId) { setCoverImage(cover, maxres); setCoverImage(sheetCover, maxres); }
       };
       im.src = maxres;
Index: src/assets/js/embed-player.js
===================================================================
--- src/assets/js/embed-player.js	(revision b56afa72e85e0b0e9069a678f867ead0c9ffb621)
+++ src/assets/js/embed-player.js	(revision d43ea3fdecb5572a1087888ee3ba2b4ee47b2719)
@@ -161,4 +161,10 @@
     const url = el.dataset.embedUrl || '';
     if (!provider) return;
+
+    // Audio-only YouTube met een afspeellijst (?list=…) → album-kaart met tracklijst.
+    if (provider === 'youtube' && el.dataset.embedMode === 'audio') {
+      const lm = url.match(/[?&]list=([A-Za-z0-9_-]+)/);
+      if (lm) { try { buildYtAlbum(el, lm[1]); } catch (e) { console.error('[pcms-embed] yt-album faalde', e); } return; }
+    }
 
     el.classList.add('pcms-embed-card', 'pcms-embed-card--' + provider);
@@ -381,4 +387,87 @@
   function escAttr(s) {
     return String(s || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  }
+
+  // Haal de video-IDs van een YouTube-afspeellijst op via een verborgen,
+  // tijdelijke speler (cuePlaylist → getPlaylist). Geen API-key nodig.
+  function ytPlaylistIds(listId) {
+    return ytApi().then((YT) => new Promise((resolve) => {
+      if (!YT || !YT.Player) { resolve([]); return; }
+      const host = document.createElement('div');
+      host.style.cssText = 'position:absolute;left:-99999px;top:0;width:320px;height:180px;opacity:0;pointer-events:none';
+      const mount = document.createElement('div'); host.appendChild(mount); document.body.appendChild(host);
+      let done = false;
+      const finish = (ids) => { if (done) return; done = true; try { p.destroy(); } catch (e) {} try { host.remove(); } catch (e) {} resolve(ids || []); };
+      const p = new YT.Player(mount, {
+        host: 'https://www.youtube-nocookie.com', playerVars: { playsinline: 1 },
+        events: { onReady: () => {
+          try { p.cuePlaylist({ list: listId }); } catch (e) {}
+          setTimeout(() => { let ids = []; try { ids = p.getPlaylist() || []; } catch (e) {} finish(ids); }, 2500);
+        } },
+      });
+      setTimeout(() => finish([]), 9000);
+    }));
+  }
+
+  // Render een audio-only YouTube-AFSPEELLIJST als album-kaart (zelfde stijl als
+  // de eigen albums) met tracklijst; afspelen loopt via de site-audiospeler.
+  async function buildYtAlbum(el, listId) {
+    el.className = 'folio-embed pcms-ytalbum';  // geen losse card-chrome; .post-album stuurt de stijl
+    const thumb = (id) => `https://i.ytimg.com/vi/${id}/mqdefault.jpg`;
+    el.innerHTML = ''
+      + '<div class="post-album">'
+      +   '<div class="post-album-header">'
+      +     '<button type="button" class="post-album-cover-btn" aria-label="Album afspelen">'
+      +       '<img class="post-album-cover-img" src="" alt="">'
+      +       '<span class="post-album-play-overlay" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 4l12 8-12 8z"/></svg></span>'
+      +     '</button>'
+      +     '<div class="post-album-info">'
+      +       '<h3 class="post-album-title">Album laden…</h3>'
+      +       '<p class="post-album-artist"></p>'
+      +       '<p class="post-album-count">YouTube</p>'
+      +     '</div>'
+      +   '</div>'
+      +   '<ol class="post-album-tracks"><li class="pcms-ytalbum-msg">Tracks laden…</li></ol>'
+      + '</div>';
+    const coverImg = el.querySelector('.post-album-cover-img');
+    const titleEl = el.querySelector('.post-album-title');
+    const countEl = el.querySelector('.post-album-count');
+    const olEl = el.querySelector('.post-album-tracks');
+    const coverBtn = el.querySelector('.post-album-cover-btn');
+
+    // Albumnaam via de oEmbed-proxy (playlist-url).
+    fetch('/audio/yt-title?url=' + encodeURIComponent('https://www.youtube.com/playlist?list=' + listId))
+      .then((r) => (r.ok ? r.json() : null)).then((d) => { if (d && d.title) titleEl.textContent = d.title; }).catch(() => {});
+
+    let ids = [];
+    try { ids = await ytPlaylistIds(listId); } catch (e) { ids = []; }
+    if (!ids.length) { olEl.innerHTML = '<li class="pcms-ytalbum-msg">Kon de afspeellijst niet laden.</li>'; return; }
+    countEl.textContent = ids.length + ' track' + (ids.length === 1 ? '' : 's');
+    if (coverImg) coverImg.src = thumb(ids[0]);
+
+    olEl.innerHTML = ids.map((id, i) =>
+      `<li class="post-album-track-compact" id="track-${id}" data-yt-id="${id}" data-yt-index="${i}">`
+      + `<button type="button" class="pat-row" aria-label="Speel track ${i + 1}">`
+      +   `<span class="pat-cover" style="background-image:url('${thumb(id)}')" aria-hidden="true"></span>`
+      +   `<span class="pat-meta"><span class="pat-title">Track ${i + 1}</span><span class="pat-artist"></span></span>`
+      +   `<span class="pat-duration pat-duration-empty">—:—</span>`
+      + `</button></li>`
+    ).join('');
+
+    // Tracktitels lazy invullen (oEmbed-proxy per video).
+    ids.forEach((id, i) => {
+      fetch('/audio/yt-title?url=' + encodeURIComponent('https://www.youtube.com/watch?v=' + id))
+        .then((r) => (r.ok ? r.json() : null))
+        .then((d) => { if (d && d.title) { const tt = olEl.querySelector(`[data-yt-index="${i}"] .pat-title`); if (tt) tt.textContent = d.title; } })
+        .catch(() => {});
+    });
+
+    function playAt(i) {
+      if (!(window.pcmsAudioPlayer && window.pcmsAudioPlayer.playYouTube)) return;
+      const tt = (olEl.querySelector(`[data-yt-index="${i}"] .pat-title`) || {}).textContent || '';
+      window.pcmsAudioPlayer.playYouTube({ list: listId, index: i, title: tt, cover: thumb(ids[i]), postUrl: location.pathname + location.search });
+    }
+    coverBtn.addEventListener('click', () => playAt(0));
+    olEl.addEventListener('click', (e) => { const li = e.target.closest('[data-yt-index]'); if (li) playAt(parseInt(li.dataset.ytIndex, 10) || 0); });
   }
 
