/** * Klonkt Audio Player — v9 mini-player + Spotify-style sheet. * * Two surfaces: * 1. .audio-player — bottom strip: cover + meta + controls + progress + volume * 2. .audio-sheet — full-height now-playing panel (slides up from bottom) * * Features: * - Click cover/meta on mini-player → open sheet * - Sheet handle (pill) or backdrop click → close * - Touch swipe-down on the drag-zone → close (mobile only; desktop has the X) * - Reads data-pcms-track-url + data-pcms-track + data-pcms-album from posts * - body.has-audio-player adds bottom padding when player visible * - body.audio-sheet-locked prevents body scroll when sheet open * - Survives HTMX swaps + history-restores via event delegation on document.body * * Singleton — guards against double-init. */ (function() { if (window.pcmsAudioPlayer) return; const SVG = { play: '', pause: '', prev: '', next: '', vol: '', mute: '', musicNote: '', }; // ============================================================ // 1. Build DOM // ============================================================ const root = document.createElement('aside'); root.id = 'pcms-audio-player'; root.className = 'audio-player'; root.setAttribute('aria-label', 'Audio speler'); root.innerHTML = `
0:00
0:00
`; document.body.appendChild(root); // FIX: Move .audio-sheet out of the .audio-player root so its // position:fixed is anchored to the viewport, not to the mini-bar. // The mini-bar has backdrop-filter, which makes it a containing // block for fixed descendants — that broke the sheet's left/right/top/bottom. const _detachedSheet = root.querySelector('.audio-sheet'); if (_detachedSheet) document.body.appendChild(_detachedSheet); // ============================================================ // 2. Element references // ============================================================ const $ = (id) => document.getElementById(id); const audio = $('audio-element'); const cover = $('audio-cover'); const titleEl = $('audio-title'); const artistEl = $('audio-artist'); const seek = $('audio-seek'); const seekFill = $('audio-seek-fill'); const currentEl = $('audio-current'); const totalEl = $('audio-total'); const playBtn = $('audio-play'); const prevBtn = $('audio-prev'); const nextBtn = $('audio-next'); const muteBtn = $('audio-mute'); const volumeSlider = $('audio-volume'); const expandTrigger = $('audio-expand-trigger'); const sheet = $('audio-sheet'); const sheetBackdrop = $('audio-sheet-backdrop'); const sheetPanel = sheet.querySelector('.audio-sheet-panel'); const sheetClose = $('audio-sheet-close'); const sheetCover = $('audio-sheet-cover'); const sheetTitle = $('audio-sheet-title'); const sheetArtist = $('audio-sheet-artist'); const sheetAlbum = $('audio-sheet-album'); const sheetSeek = $('audio-sheet-seek'); const sheetSeekFill = $('audio-sheet-seek-fill'); const sheetCurrent = $('audio-sheet-current'); const sheetTotal = $('audio-sheet-total'); const sheetPlay = $('audio-sheet-play'); const sheetPrev = $('audio-sheet-prev'); const sheetNext = $('audio-sheet-next'); const sheetQueue = $('audio-sheet-queue'); const sheetQueueList = $('audio-sheet-queue-list'); const dragZone = $('audio-sheet-drag-zone'); // ============================================================ // 3. State // ============================================================ let queue = []; let currentIndex = 0; let isPlaying = false; let albumName = ''; // Playback pipeline. We fetch each track's bytes ourselves (X-Audio-Player // gate; no plain media URL is ever exposed to the page) and feed them to the //