(function() {
    // Find the active media element (video or audio), or null for static content
    var activeMedia = document.getElementById('watch-video') || document.getElementById('watch-audio');

    // SPA state
    var standbyMedia = null;
    var preloadedData = null;
    var queue = [];
    var countdownTimer = null;
    var crossfadeTimer = null;
    var staticTimer = null;
    var COUNTDOWN_SECONDS = 7;
    var CROSSFADE_DURATION = 3000;
    var CROSSFADE_STEPS = 60;
    var STATIC_DISPLAY_SECONDS = 42;
    var BUFFER_AHEAD_SECONDS = 30;
    var bufferingStarted = false;
    var djCrossfadeActive = false;
    var djFadeoutActive = false;
    var djCrossfadeData = null;
    var djStartVolume = 1; // active tab's volume captured when a DJ fade begins
    var preloadingId = null;
    var currentProductId = null;
    var currentCanonicalUrl = null;
    var currentProductMediaType = null;
    var currentThumbnailUrl = null;
    var urlRefreshTimer = null;
    var URL_REFRESH_MS = 7 * 60 * 1000; // 7 minutes
    var lastUrlRefreshTs = Date.now();
    var PROGRESS_KEY = 'mps_watch_progress';
    var PROGRESS_SAVE_INTERVAL = 7000;
    var lastProgressSave = 0;
    var navigationPending = false;

    // --- Karaoke state ---
    var karaokeMode = 0; // 0=original, 1=instrumentals, 2=vocals
    var KARAOKE_LABELS = ['Original', 'Instrumentals', 'Vocals'];
    var karaokeUrls = {original: null, instrumentals: null, vocals: null};
    var karaokeEligible = false;
    var karaokeProcessing = false;

    // --- Cache version: flushes stale ring state on deploy / reforge ---
    // Server stamps every page (meta[name="mps-cache-version"]) and every
    // watch_json response. Stored client version is compared on each
    // page load and SPA nav; mismatch drops cached ringProductIds,
    // ringPosition, ringHistory, and ringLoops before anything reads them.
    function enforceCacheVersion(serverVersion) {
        if (!serverVersion) return;
        var stored = null;
        try { stored = localStorage.getItem('watchCacheVersion'); } catch (e) {}
        if (stored && stored !== serverVersion) {
            try {
                localStorage.removeItem('watchRing');
                localStorage.removeItem('watchRingPosition');
                localStorage.removeItem('watchRingHistory');
                localStorage.removeItem('watchRingLoops');
                localStorage.removeItem('watchQueue');
            } catch (e) {}
        }
        try { localStorage.setItem('watchCacheVersion', serverVersion); } catch (e) {}
    }
    (function initialCacheCheck() {
        var meta = document.querySelector('meta[name="mps-cache-version"]');
        if (meta) enforceCacheVersion(meta.getAttribute('content'));
    })();

    // --- Ring state (persisted in localStorage) ---
    var ringProductIds = [];
    var ringPosition = 0;
    var ringDirection = 1; // 1 = forward, -1 = reverse
    var ringHistory = {};  // { productId: true } — set of watched IDs
    var ringLoops = 0;     // completed full circuits of the ring

    // Load ring state from localStorage
    try {
        var savedRing = localStorage.getItem('watchRing');
        if (savedRing) ringProductIds = JSON.parse(savedRing);
    } catch (e) {}
    try {
        var savedPos = localStorage.getItem('watchRingPosition');
        if (savedPos !== null) ringPosition = parseInt(savedPos, 10) || 0;
    } catch (e) {}
    try {
        var savedDir = localStorage.getItem('watchRingDirection');
        if (savedDir !== null) ringDirection = parseInt(savedDir, 10) || 1;
    } catch (e) {}
    try {
        var savedHistory = localStorage.getItem('watchRingHistory');
        if (savedHistory) ringHistory = JSON.parse(savedHistory);
    } catch (e) {}
    try {
        var savedLoops = localStorage.getItem('watchRingLoops');
        if (savedLoops !== null) ringLoops = parseInt(savedLoops, 10) || 0;
    } catch (e) {}

    function saveRingState() {
        try {
            localStorage.setItem('watchRing', JSON.stringify(ringProductIds));
            localStorage.setItem('watchRingPosition', String(ringPosition));
            localStorage.setItem('watchRingDirection', String(ringDirection));
            localStorage.setItem('watchRingHistory', JSON.stringify(ringHistory));
            localStorage.setItem('watchRingLoops', String(ringLoops));
        } catch (e) {}
    }

    function markWatched(productId) {
        if (!productId) return;
        ringHistory[productId] = true;

        // Check for complete loop — all ring items watched
        if (ringProductIds.length > 0) {
            var allWatched = true;
            for (var i = 0; i < ringProductIds.length; i++) {
                if (!ringHistory[ringProductIds[i]]) {
                    allWatched = false;
                    break;
                }
            }
            if (allWatched) {
                ringLoops++;
                ringHistory = {};
                // Current item starts the new loop as already watched
                ringHistory[productId] = true;
            }
        }

        saveRingState();
        updateProgressDisplay();
    }

    function isWatched(productId) {
        return !!ringHistory[productId];
    }

    // Sync ring position to the current product
    function syncRingPosition(productId) {
        if (!ringProductIds.length || !productId) return;
        var idx = ringProductIds.indexOf(productId);
        if (idx !== -1) {
            ringPosition = idx;
            saveRingState();
        }
    }

    // Update progress display ("23 / 87") and loop badge
    function updateProgressDisplay() {
        var el = document.getElementById('ring-progress');
        if (!el) return;
        if (!ringProductIds.length) {
            el.textContent = '';
            return;
        }
        var watched = 0;
        for (var id in ringHistory) {
            if (ringProductIds.indexOf(id) !== -1) watched++;
        }
        el.textContent = watched + ' / ' + ringProductIds.length;

        // Loop badge
        var badge = document.getElementById('ring-loops-badge');
        if (badge) {
            if (ringLoops > 0) {
                badge.textContent = '+' + ringLoops;
                badge.style.display = '';
            } else {
                badge.style.display = 'none';
            }
        }
    }

    // Autoplay preference (persisted in localStorage)
    var autoplayEnabled = true;
    try {
        var saved = localStorage.getItem('watchAutoplay');
        if (saved !== null) autoplayEnabled = saved === '1';
    } catch (e) {}

    // Fresh mode preference (hide watched items in ring)
    var freshMode = false;
    try {
        var savedFresh = localStorage.getItem('watchFreshMode');
        if (savedFresh !== null) freshMode = savedFresh === '1';
    } catch (e) {}

    // Cinema mode preference (full-width video, restructured page layout)
    var cinemaMode = false;
    try {
        var savedCinema = localStorage.getItem('watchCinemaMode');
        if (savedCinema !== null) cinemaMode = savedCinema === '1';
    } catch (e) {}

    // Cache the task bar's original position so we can put it back when
    // cinema mode toggles off. Done once at init — before any relocation.
    var _taskbarOriginalParent = null;
    var _taskbarOriginalNextSibling = null;
    (function cacheTaskbarHome() {
        var tb = document.querySelector('section.windows-95-task-bar');
        if (tb) {
            _taskbarOriginalParent = tb.parentElement;
            _taskbarOriginalNextSibling = tb.nextSibling;
        }
    })();

    // Cinema activates only for landscape media (aspect > 1). Portrait
    // and square content stays in the normal watch layout so tall phone
    // videos don't get forced into a full-viewport wide treatment that
    // leaves the page as a skinny column in a sea of black.
    function isMediaWide() {
        var video = document.querySelector('#watch-video');
        if (video && video.videoWidth && video.videoHeight) {
            return video.videoWidth / video.videoHeight > 1;
        }
        var img = document.querySelector('.product-images .product-main');
        if (!img) img = document.querySelector('.product-images img');
        if (img && img.naturalWidth && img.naturalHeight) {
            return img.naturalWidth / img.naturalHeight > 1;
        }
        return false; // unknown — don't activate cinema layout yet
    }

    function bindCinemaAspectHandlers() {
        var video = document.querySelector('#watch-video');
        if (video && !video._cinemaAspectBound) {
            video._cinemaAspectBound = true;
            video.addEventListener('loadedmetadata', applyCinemaMode);
        }
        var img = document.querySelector('.product-images .product-main');
        if (!img) img = document.querySelector('.product-images img');
        if (img && img.tagName === 'IMG' && !img._cinemaAspectBound) {
            img._cinemaAspectBound = true;
            img.addEventListener('load', applyCinemaMode);
        }
    }

    // Cinema layout only kicks in at >=800px — below that, mobile watch
    // mode is already the right shape. Keep CSS + JS aligned on that
    // boundary so the taskbar doesn't relocate on a phone.
    var CINEMA_MIN_WIDTH = 800;
    function isViewportWideEnoughForCinema() {
        return window.innerWidth >= CINEMA_MIN_WIDTH;
    }

    function applyCinemaMode() {
        var section = document.querySelector('section.two-column');
        if (!section) return;

        bindCinemaAspectHandlers();

        var taskbar = document.querySelector('section.windows-95-task-bar');
        var productRight = document.querySelector('section.product-right');
        var wide = cinemaMode && isMediaWide() && isViewportWideEnoughForCinema();

        section.classList.toggle('cinema-mode', cinemaMode);
        section.classList.toggle('cinema-wide', wide);

        if (wide) {
            if (taskbar && productRight) {
                var well = productRight.querySelector('.well');
                if (well && taskbar.parentElement !== productRight) {
                    productRight.insertBefore(taskbar, well);
                }
            }
        } else {
            if (taskbar && _taskbarOriginalParent && taskbar.parentElement !== _taskbarOriginalParent) {
                _taskbarOriginalParent.insertBefore(taskbar, _taskbarOriginalNextSibling);
            }
        }
    }
    applyCinemaMode();

    // Re-evaluate when the viewport crosses the cinema breakpoint so
    // rotation or browser resize flips the layout cleanly.
    var _cinemaResizeTimer = null;
    window.addEventListener('resize', function() {
        if (_cinemaResizeTimer) clearTimeout(_cinemaResizeTimer);
        _cinemaResizeTimer = setTimeout(applyCinemaMode, 120);
    });

    // Media type filter preferences (all on by default)
    var FILTER_TYPES = ['video', 'audio', 'image', 'document', 'other'];
    var FILTER_LABELS = {video: 'Video', audio: 'Audio', image: 'Image', document: 'Docs', other: 'Other'};
    var mediaFilters = {};
    FILTER_TYPES.forEach(function(t) { mediaFilters[t] = true; });
    try {
        var savedFilters = localStorage.getItem('watchMediaFilters');
        if (savedFilters) {
            var parsed = JSON.parse(savedFilters);
            FILTER_TYPES.forEach(function(t) {
                if (t in parsed) mediaFilters[t] = parsed[t];
            });
        }
    } catch (e) {}

    function saveMediaFilters() {
        try { localStorage.setItem('watchMediaFilters', JSON.stringify(mediaFilters)); } catch (e) {}
    }

    function applyMediaFilterClasses(container) {
        if (!container) container = document.querySelector('.related-content');
        if (!container) return;
        FILTER_TYPES.forEach(function(t) {
            if (mediaFilters[t]) {
                container.classList.remove('hide-' + t);
            } else {
                container.classList.add('hide-' + t);
            }
        });
    }

    // Set up autoplay toggle
    var autoplayToggle = document.getElementById('watch-autoplay-toggle');
    if (autoplayToggle) {
        autoplayToggle.checked = autoplayEnabled;
        autoplayToggle.addEventListener('change', function() {
            autoplayEnabled = this.checked;
            try { localStorage.setItem('watchAutoplay', autoplayEnabled ? '1' : '0'); } catch (e) {}
            if (!autoplayEnabled) {
                if (djCrossfadeActive) cancelDjCrossfade();
                cancelCountdown();
                if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }
            } else {
                // If on a static page, restart the timer
                if (!activeMedia) startStaticTimer();
            }
        });
    }

    // Restore queue from localStorage (shared across tabs)
    try {
        var savedQueue = localStorage.getItem('watchQueue');
        if (savedQueue) queue = JSON.parse(savedQueue);
    } catch (e) {}

    // Sync queue from other tabs via storage events
    window.addEventListener('storage', function(e) {
        if (e.key === 'watchQueue') {
            try { queue = e.newValue ? JSON.parse(e.newValue) : []; } catch (ex) {}
            renderQueue();
        }
    });

    // Mod status (shop owner/editor)
    var isMod = false;

    // Mark current item as watched and sync ring position
    var initialProductEl = document.querySelector('[data-watch-product-id]');
    if (initialProductEl) {
        var initialId = initialProductEl.getAttribute('data-watch-product-id');
        currentProductId = initialId;
        markWatched(initialId);

        // Invalidate stale ring (wrong shop or product removed from ring)
        if (ringProductIds.length && ringProductIds.indexOf(initialId) === -1) {
            ringProductIds = [];
            ringPosition = 0;
            ringHistory = {};
            ringLoops = 0;
            saveRingState();
        }

        syncRingPosition(initialId);
        if (initialProductEl.getAttribute('data-watch-is-mod') === '1') {
            isMod = true;
        }
    }

    // --- Karaoke: read initial URLs from data attributes ---
    function initKaraokeFromDom() {
        var container = document.getElementById('watch-media-container');
        karaokeUrls.instrumentals = container ? container.getAttribute('data-instrumentals-url') : null;
        karaokeUrls.vocals = container ? container.getAttribute('data-vocals-url') : null;
        karaokeUrls.original = activeMedia ? activeMedia.src : null;
        karaokeEligible = container ? container.getAttribute('data-karaoke-eligible') === '1' : false;
        karaokeProcessing = false;
        karaokeMode = 0;
        updateKaraokeButton();
    }

    function updateKaraokeButton() {
        var btn = document.getElementById('karaoke-toggle-btn');
        if (!btn) return;
        var hasKaraoke = !!(karaokeUrls.instrumentals || karaokeUrls.vocals);
        // Show button when tracks exist OR when eligible for on-demand processing
        btn.style.display = (hasKaraoke || karaokeEligible) ? '' : 'none';

        if (karaokeProcessing) {
            btn.textContent = '\u231B';  // hourglass
            btn.title = 'Processing karaoke tracks\u2026';
            btn.classList.add('active');
            btn.disabled = true;
        } else if (hasKaraoke) {
            btn.textContent = KARAOKE_LABELS[karaokeMode];
            btn.title = 'Cycle: Original / Instrumentals / Vocals';
            btn.disabled = false;
            if (karaokeMode === 0) {
                btn.classList.remove('active');
            } else {
                btn.classList.add('active');
            }
        } else {
            // Eligible but no tracks yet — show mic icon
            btn.textContent = '\uD83C\uDFA4';  // 🎤
            btn.title = 'Generate karaoke tracks';
            btn.classList.remove('active');
            btn.disabled = false;
        }
    }

    function getNextKaraokeMode() {
        // Build list of available modes
        var modes = [0]; // original is always available
        if (karaokeUrls.instrumentals) modes.push(1);
        if (karaokeUrls.vocals) modes.push(2);
        if (modes.length <= 1) return 0;
        var idx = modes.indexOf(karaokeMode);
        return modes[(idx + 1) % modes.length];
    }

    function triggerKaraokeProcessing() {
        if (karaokeProcessing || !currentProductId) return;
        karaokeProcessing = true;
        updateKaraokeButton();

        fetch('/karaoke/' + currentProductId, {method: 'POST'})
            .then(function(resp) { return resp.json(); })
            .then(function(data) {
                if (data.status === 'ready') {
                    // Tracks already exist — refresh to get URLs
                    karaokeProcessing = false;
                    fetchWatchData(currentProductId).then(function(fresh) {
                        if (fresh && fresh.instrumentals_url) {
                            karaokeUrls.instrumentals = fresh.instrumentals_url;
                            karaokeUrls.vocals = fresh.vocals_url;
                            karaokeUrls.original = fresh.media_url;
                            updateKaraokeButton();
                            // Auto-switch to instrumentals
                            cycleKaraoke();
                        }
                    });
                }
                // status === 'processing' — polling via URL refresh will pick it up
            })
            .catch(function() {
                karaokeProcessing = false;
                updateKaraokeButton();
            });
    }

    function cycleKaraoke() {
        if (!activeMedia) return;
        var hasKaraoke = !!(karaokeUrls.instrumentals || karaokeUrls.vocals);

        // No tracks yet but eligible — trigger on-demand processing
        if (!hasKaraoke && karaokeEligible) {
            triggerKaraokeProcessing();
            return;
        }
        if (!hasKaraoke) return;

        var nextMode = getNextKaraokeMode();
        if (nextMode === karaokeMode) return;

        var savedTime = activeMedia.currentTime;
        var wasPlaying = !activeMedia.paused;

        var newSrc;
        if (nextMode === 1) newSrc = karaokeUrls.instrumentals;
        else if (nextMode === 2) newSrc = karaokeUrls.vocals;
        else newSrc = karaokeUrls.original;

        karaokeMode = nextMode;
        activeMedia.src = newSrc;

        activeMedia.addEventListener('loadedmetadata', function onKLoad() {
            activeMedia.removeEventListener('loadedmetadata', onKLoad);
            activeMedia.currentTime = savedTime;
            if (wasPlaying) activeMedia.play().catch(function() {});
        });

        updateKaraokeButton();
    }

    initKaraokeFromDom();

    // --- Autoplay with sound (for video/audio) ---
    if (activeMedia) {
        // Restore saved progress on reload (seek + volume fade-in)
        activeMedia.addEventListener('loadeddata', function onInitLoad() {
            activeMedia.removeEventListener('loadeddata', onInitLoad);
            restoreProgress();
        });

        var playPromise = activeMedia.play();
        if (playPromise !== undefined) {
            playPromise.catch(function() {
                activeMedia.muted = true;
                activeMedia.play().then(function() {
                    showUnmuteOverlay(activeMedia);
                }).catch(function() {});
            });
        }
    }

    function showUnmuteOverlay(media) {
        var btn = document.querySelector('.unmute-overlay');
        if (!btn) return;
        btn.style.display = 'block';
        btn.addEventListener('click', function() {
            media.muted = false;
            btn.style.display = 'none';
        });
    }

    // Determine current media type
    var currentMediaType = activeMedia ? activeMedia.tagName.toLowerCase() : 'static';

    // --- Create standby media element ---
    function createStandbyMedia(type) {
        if (type === 'static') return null;
        var el = document.createElement(type === 'audio' ? 'audio' : 'video');
        el.preload = 'metadata';
        el.style.display = 'none';
        var container = document.getElementById('watch-media-container');
        if (container) container.appendChild(el);
        return el;
    }

    if (activeMedia) {
        standbyMedia = createStandbyMedia(currentMediaType);
    }

    // --- Fullscreen: promote <video> fullscreen to container for seamless crossfade ---
    // Native video controls fullscreen the <video> element directly. When the crossfade
    // swaps videos the fullscreened element gets hidden, exiting fullscreen. Promoting
    // fullscreen to the .watch-video-container keeps both videos inside the fullscreen
    // element so the transition is invisible.
    function promoteVideoFullscreen() {
        var fsEl = document.fullscreenElement || document.webkitFullscreenElement;
        if (!fsEl) return;
        if (fsEl !== activeMedia && fsEl !== standbyMedia) return;
        var container = fsEl.closest('.watch-video-container');
        if (!container) return;
        if (container.requestFullscreen) {
            container.requestFullscreen().catch(function() {});
        } else if (container.webkitRequestFullscreen) {
            container.webkitRequestFullscreen();
        }
    }
    document.addEventListener('fullscreenchange', promoteVideoFullscreen);
    document.addEventListener('webkitfullscreenchange', promoteVideoFullscreen);

    // --- Queue management ---
    function saveQueue() {
        try {
            localStorage.setItem('watchQueue', JSON.stringify(queue));
        } catch (e) {}
    }

    function addToQueue(item) {
        queue.push(item);
        saveQueue();
        renderQueue();
    }

    function removeFromQueue(index) {
        queue.splice(index, 1);
        saveQueue();
        renderQueue();
    }

    function renderQueue() {
        var container = document.getElementById('watch-queue');
        var items = document.getElementById('queue-items');
        if (!container || !items) return;

        if (queue.length === 0) {
            container.style.display = 'none';
            return;
        }

        container.style.display = 'block';
        items.innerHTML = '';

        queue.forEach(function(item, i) {
            var div = document.createElement('div');
            div.className = 'queue-item';

            var titleSpan = document.createElement('span');
            titleSpan.className = 'queue-item-title';
            titleSpan.textContent = item.title;
            titleSpan.style.cursor = 'pointer';
            titleSpan.addEventListener('click', function() {
                var playItem = queue.splice(i, 1)[0];
                saveQueue();
                renderQueue();
                fetchAndNavigate(playItem.id);
            });

            var removeBtn = document.createElement('button');
            removeBtn.className = 'queue-remove-btn';
            removeBtn.textContent = '\u00d7';
            removeBtn.title = 'Remove from queue';
            removeBtn.addEventListener('click', function() {
                removeFromQueue(i);
            });

            div.appendChild(titleSpan);
            div.appendChild(removeBtn);
            items.appendChild(div);
        });
    }

    // --- Fetch watch JSON (with retry for transient 502s) ---
    function fetchWatchData(productId, attempt) {
        attempt = attempt || 0;
        var url = '/watch/' + productId + '/json?dir=' + ringDirection;
        return fetch(url, {
            headers: {'X-Requested-With': 'XMLHttpRequest'}
        }).then(function(r) {
            if (r.status >= 502 && r.status <= 504 && attempt < 2) {
                return new Promise(function(resolve) {
                    setTimeout(resolve, (attempt + 1) * 1000);
                }).then(function() {
                    return fetchWatchData(productId, attempt + 1);
                });
            }
            if (!r.ok) throw new Error('Watch fetch failed');
            return r.json();
        });
    }

    // --- SPA navigation ---
    function fetchAndNavigate(productId) {
        if (djCrossfadeActive) cancelDjCrossfade();
        cancelCountdown();
        if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }
        navigationPending = true;
        fetchWatchData(productId).then(function(data) {
            navigationPending = false;
            transitionTo(data);
        }).catch(function() {
            navigationPending = false;
            // Fallback: hard navigate to the canonical URL
            // Try to find the link in the related items
            var link = document.querySelector('[data-watch-id="' + productId + '"]');
            if (link) {
                window.location.href = link.getAttribute('href');
            }
        });
    }

    function transitionTo(data) {
        // Dispatch signal reset before any DOM changes so signals.js can
        // finalize the current session and start tracking the new product.
        try {
            document.dispatchEvent(new CustomEvent('mps:signal:reset', {
                detail: {product_id: data.product_id, shop_id: data.shop_id}
            }));
        } catch (e) {}

        var isPlayable = (data.media_type === 'video' || data.media_type === 'audio');

        // From a static page, always hard navigate (no media container to swap into)
        if (!activeMedia) {
            if (data.canonical_url) {
                window.location.href = data.canonical_url;
            }
            return;
        }

        if (isPlayable && data.media_type === currentMediaType) {
            crossfade(data);
        } else if (isPlayable) {
            hardSwap(data);
        } else {
            // Next item is static content — hard navigate
            if (data.canonical_url) {
                window.location.href = data.canonical_url;
            }
        }
    }

    // --- Crossfade (user-initiated transitions: click related, queue, Play Now) ---
    function crossfade(data) {
        if (crossfadeTimer) clearInterval(crossfadeTimer);

        // Cancel any in-progress DJ crossfade
        if (djCrossfadeActive) cancelDjCrossfade();

        if (!standbyMedia || standbyMedia.tagName.toLowerCase() !== data.media_type) {
            hardSwap(data);
            return;
        }

        // Read the active tab's current volume — both tracks fade within this
        // ceiling so the crossfade can never jump louder than what's playing.
        var startVolume = activeMedia.volume;
        if (!(startVolume >= 0)) startVolume = 1;

        standbyMedia.src = data.media_url;
        standbyMedia.volume = 0;
        standbyMedia.currentTime = 0;

        // Move standby into same container for visual overlay
        var mediaContainer = activeMedia.parentNode;
        if (mediaContainer && standbyMedia.parentNode !== mediaContainer) {
            mediaContainer.appendChild(standbyMedia);
        }
        standbyMedia.className = (currentMediaType === 'video')
            ? 'product-main crossfade-incoming' : 'crossfade-incoming';
        standbyMedia.style.display = '';
        standbyMedia.style.opacity = '0';
        standbyMedia.controls = false;

        var standbyPlayPromise = standbyMedia.play();
        if (standbyPlayPromise !== undefined) {
            standbyPlayPromise.catch(function() {
                hardSwap(data);
                return;
            });
        }

        var step = 0;
        var interval = CROSSFADE_DURATION / CROSSFADE_STEPS;

        crossfadeTimer = setInterval(function() {
            step++;
            var progress = step / CROSSFADE_STEPS;
            try {
                activeMedia.volume = Math.max(0, startVolume * (1 - progress));
                standbyMedia.volume = Math.min(startVolume, startVolume * progress);
            } catch (e) {}

            // Visual opacity crossfade
            activeMedia.style.opacity = String(1 - progress);
            standbyMedia.style.opacity = String(progress);

            if (step === Math.floor(CROSSFADE_STEPS / 2)) {
                updatePageContent(data);
            }

            if (step >= CROSSFADE_STEPS) {
                clearInterval(crossfadeTimer);
                crossfadeTimer = null;
                activeMedia.pause();
                activeMedia.style.display = 'none';
                activeMedia.style.opacity = '1';

                standbyMedia.style.opacity = '1';
                standbyMedia.className = (currentMediaType === 'video') ? 'product-main' : '';
                standbyMedia.controls = true;

                var tmp = activeMedia;
                activeMedia = standbyMedia;
                standbyMedia = tmp;

                activeMedia.id = currentMediaType === 'video' ? 'watch-video' : 'watch-audio';

                // Move standby back to main container
                var mainContainer = document.getElementById('watch-media-container');
                if (mainContainer && standbyMedia.parentNode !== mainContainer) {
                    mainContainer.appendChild(standbyMedia);
                }
                standbyMedia.style.display = 'none';

                setupMediaEvents();
                preloadNext();
            }
        }, interval);
    }

    // --- Hard swap (different media types or fallback) ---
    function hardSwap(data) {
        if (activeMedia) activeMedia.pause();

        var container = document.getElementById('watch-media-container');
        if (!container) return;

        if (standbyMedia && standbyMedia.parentNode) {
            standbyMedia.parentNode.removeChild(standbyMedia);
        }

        var newType = data.media_type;
        currentMediaType = newType;

        if (newType === 'audio') {
            container.innerHTML = '';

            var audioWrap = document.createElement('div');
            audioWrap.className = 'watch-audio-container';

            if (data.thumbnail_url) {
                var img = document.createElement('img');
                img.src = data.thumbnail_url;
                img.className = 'product-main audio-cover';
                audioWrap.appendChild(img);
            }

            var audio = document.createElement('audio');
            audio.id = 'watch-audio';
            audio.src = data.media_url;
            audio.autoplay = true;
            audio.controls = true;
            audioWrap.appendChild(audio);

            container.appendChild(audioWrap);
            activeMedia = audio;
        } else {
            container.innerHTML = '';

            var videoWrap = document.createElement('div');
            videoWrap.className = 'watch-video-container';

            var video = document.createElement('video');
            video.id = 'watch-video';
            video.src = data.media_url;
            video.autoplay = true;
            video.controls = true;
            video.playsInline = true;
            video.className = 'product-main';
            videoWrap.appendChild(video);

            var unmuteBtn = document.createElement('button');
            unmuteBtn.className = 'unmute-overlay';
            unmuteBtn.textContent = 'Tap to unmute';
            videoWrap.appendChild(unmuteBtn);

            container.appendChild(videoWrap);
            activeMedia = video;
        }

        standbyMedia = createStandbyMedia(newType);

        // Countdown lives outside the media container (between media and title)
        // — no need to move it during hard swaps.

        updatePageContent(data);
        setupMediaEvents();

        var p = activeMedia.play();
        if (p !== undefined) {
            p.catch(function() {
                activeMedia.muted = true;
                activeMedia.play().then(function() {
                    showUnmuteOverlay(activeMedia);
                }).catch(function() {});
            });
        }

        preloadNext();
    }

    // --- Update page content (title, description, related, URL) ---
    function updatePageContent(data) {
        // Server may have deployed or reforged since last nav — drop
        // stale ring state before applying new data.
        if (data.cache_version) enforceCacheVersion(data.cache_version);

        // Update ring and mod status from server response
        if (data.ring && data.ring.length) {
            ringProductIds = data.ring;
        }
        if (data.is_mod) isMod = true;
        currentProductId = data.product_id;
        currentCanonicalUrl = data.canonical_url || null;
        currentProductMediaType = data.media_type || null;
        currentThumbnailUrl = data.thumbnail_url || null;
        markWatched(data.product_id);
        syncRingPosition(data.product_id);
        lastUrlRefreshTs = Date.now();
        scheduleUrlRefresh();

        // Reset karaoke state for the new product
        karaokeMode = 0;
        karaokeProcessing = false;
        karaokeEligible = !!data.karaoke_eligible;
        karaokeUrls.original = data.media_url || null;
        karaokeUrls.instrumentals = data.instrumentals_url || null;
        karaokeUrls.vocals = data.vocals_url || null;
        updateKaraokeButton();

        document.title = data.title;

        var h1 = document.querySelector('.product-images h1');
        if (!h1) h1 = document.querySelector('h1');
        if (h1) {
            var titleNode = h1.firstChild;
            if (titleNode && titleNode.nodeType === Node.TEXT_NODE) {
                titleNode.textContent = data.title;
            }
        }

        var descSection = document.querySelector('.product-description section');
        if (descSection) {
            descSection.innerHTML = data.description_html;
        }

        // Update CTA edit button href for mod users
        if (isMod && data.product_id) {
            var editBtn = document.querySelector('.call-to-action .product-edit-button');
            if (editBtn) {
                var prefix = data.is_sellable ? '/p/' : '/c/';
                editBtn.href = prefix + data.product_id + '/edit';
            }
        }

        // Update download button
        var dlBtn = document.querySelector('.product-download-button');
        var dlWell = dlBtn ? dlBtn.closest('.well') : null;
        if (data.has_product_file && data.download_url) {
            if (dlBtn) {
                dlBtn.href = data.download_url;
            } else if (dlWell) {
                dlWell.innerHTML = '<a href="' + data.download_url
                    + '" class="product-download-button mps-button" target="_blank" download>&#11123 Download</a>';
            }
            if (dlWell) dlWell.style.display = '';
        } else {
            if (dlWell) dlWell.style.display = 'none';
        }

        // Update non-media product-main images (audio cover art, thumbnail-to-file links)
        var mainImgs = document.querySelectorAll('img.product-main');
        for (var mi = 0; mi < mainImgs.length; mi++) {
            if (data.thumbnail_url) {
                mainImgs[mi].src = data.thumbnail_url;
            }
            var parentLink = mainImgs[mi].closest('a');
            if (parentLink && data.file_url) {
                parentLink.href = data.file_url;
            }
        }

        // Clear stale extra thumbnails from previous product
        var staleThumbs = document.querySelectorAll('.product-thumbnail');
        for (var st = 0; st < staleThumbs.length; st++) {
            var thumbLink = staleThumbs[st].closest('a');
            if (thumbLink) thumbLink.remove();
            else staleThumbs[st].remove();
        }

        // Update file type/size info
        var descDiv = document.querySelector('.product-description');
        if (descDiv) {
            var fileInfoB = descDiv.querySelectorAll('b');
            for (var i = 0; i < fileInfoB.length; i++) {
                if (fileInfoB[i].textContent === 'File Type') {
                    // The text node after <b>File Type</b><br/> holds "content/type size"
                    var sibling = fileInfoB[i].nextSibling;
                    while (sibling && sibling.nodeName === 'BR') sibling = sibling.nextSibling;
                    if (sibling && sibling.nodeType === Node.TEXT_NODE) {
                        if (data.has_product_file && data.file_type) {
                            sibling.textContent = data.file_type + ' ' + (data.file_size || '');
                        } else {
                            sibling.textContent = '';
                        }
                    }
                    break;
                }
            }
        }

        // Update comment form product_id and clear stale comments
        var commentForms = document.querySelectorAll('input[name="product_id"]');
        for (var j = 0; j < commentForms.length; j++) {
            commentForms[j].value = data.product_id;
        }
        var commentsList = document.getElementById('comments-list');
        if (commentsList) commentsList.innerHTML = '';
        var commentsHeading = document.querySelector('.comments-section h3');
        if (commentsHeading) commentsHeading.textContent = 'Comments & Reviews';

        // Update og:image for now-playing thumbnail
        var ogImage = document.querySelector('meta[property="og:image"]');
        if (ogImage && data.thumbnail_url) {
            ogImage.setAttribute('content', data.thumbnail_url);
        }

        // Update meta dates and view count
        var metaDatesEl = document.querySelector('.product-meta-dates');
        if (metaDatesEl && data.show_dates) {
            var datesText = 'Created ' + data.human_created_timestamp;
            if (data.human_created_timestamp !== data.human_updated_timestamp) {
                datesText += ' · Updated ' + data.human_updated_timestamp;
            }
            if (data.human_view_count) {
                datesText += ' · <span class="product-view-count">' + escapeHtml(data.human_view_count) + '</span>';
            }
            metaDatesEl.innerHTML = datesText;
        } else if (metaDatesEl && !data.show_dates) {
            metaDatesEl.innerHTML = '';
        }

        var viewCountEl = document.querySelector('.product-view-count');
        if (viewCountEl) {
            viewCountEl.textContent = data.human_view_count || '';
        }

        // Update price history (mod-only)
        var priceDetails = document.querySelector('.price-history-details');
        if (data.price_history && data.price_history.length > 1) {
            var phHtml = '<summary>Price History (' + data.price_history.length + ')</summary>'
                + '<div class="analytics-table-wrap"><table class="analytics-table">'
                + '<thead><tr><th>Price</th><th>When</th></tr></thead><tbody>';
            data.price_history.forEach(function(ph) {
                phHtml += '<tr' + (ph.is_current ? ' class="price-history-current"' : '') + '>'
                    + '<td>' + escapeHtml(ph.price_formatted) + '</td>'
                    + '<td title="' + escapeHtml(ph.datetime) + '">' + escapeHtml(ph.ago) + '</td>'
                    + '</tr>';
            });
            phHtml += '</tbody></table></div>';
            if (priceDetails) {
                priceDetails.innerHTML = phHtml;
            } else {
                var well = document.querySelector('.product-right .well');
                if (well) {
                    var details = document.createElement('details');
                    details.className = 'price-history-details';
                    details.innerHTML = phHtml;
                    well.parentNode.insertBefore(details, well.nextSibling);
                }
            }
        } else if (priceDetails) {
            priceDetails.remove();
        }

        // Update signal data attributes for signals.js
        var signalRoot = document.querySelector('[data-signal-product-id]');
        if (signalRoot) {
            signalRoot.setAttribute('data-signal-product-id', data.product_id);
            if (data.shop_id) {
                signalRoot.setAttribute('data-signal-shop-id', data.shop_id);
            }
        }

        // Update canonical link
        var canonicalLink = document.querySelector('link[rel="canonical"]');
        if (canonicalLink && data.canonical_url) {
            canonicalLink.href = window.location.origin + data.canonical_url;
        }

        updateRelated(data.related, data.comments_enabled, data.comment_count);

        // Update the product-level comments link
        var pcLink = document.querySelector('.product-comments-link');
        if (pcLink && data.comments_enabled) {
            pcLink.textContent = 'Comments (' + (data.comment_count || 0) + ')';
        }

        if (data.canonical_url) {
            history.pushState({productId: data.product_id}, data.title, data.canonical_url);
        }

        renderQueue();

        // Re-apply sandbox filters to newly loaded media elements
        if (window.sandboxReapply) {
            window.sandboxReapply();
        }

        // New media element — re-detect aspect and update cinema classes
        applyCinemaMode();
    }

    function updateRelated(related, commentsEnabled, commentCount) {
        var container = document.querySelector('.related-content');
        if (!container) {
            var productRight = document.querySelector('.product-right');
            if (!productRight) return;
            container = document.createElement('div');
            container.className = 'related-content';
            productRight.appendChild(container);
        }

        var autoplayChecked = autoplayEnabled ? ' checked' : '';
        var directionChecked = ringDirection === -1 ? ' checked' : '';
        var freshChecked = freshMode ? ' checked' : '';
        var cinemaChecked = cinemaMode ? ' checked' : '';
        var modBadge = isMod ? '<span class="ring-mod-badge">mod</span>' : '';
        var loopsBadge = ringLoops > 0
            ? '<span id="ring-loops-badge" class="ring-loops-badge">+' + ringLoops + '</span>'
            : '<span id="ring-loops-badge" class="ring-loops-badge" style="display:none"></span>';
        var headerHtml = '<div class="related-content-header">'
            + '<div class="ring-header-info">'
            + '<h3>Up Next</h3>'
            + modBadge
            + loopsBadge
            + '<span id="ring-progress" class="ring-progress"></span>'
            + '</div>'
            + '<div class="ring-header-controls">'
            + '<label class="watch-fresh-label js-only">'
            + '<span class="fresh-label-text">Fresh</span>'
            + '<input type="checkbox" id="watch-fresh-toggle"' + freshChecked + ' />'
            + '<span class="autoplay-slider"></span>'
            + '</label>'
            + '<label class="watch-direction-label js-only">'
            + '<span class="direction-label-text">Reverse</span>'
            + '<input type="checkbox" id="watch-direction-toggle"' + directionChecked + ' />'
            + '<span class="autoplay-slider"></span>'
            + '</label>'
            + '<label class="watch-cinema-label js-only">'
            + '<span class="cinema-label-text">Cinema</span>'
            + '<input type="checkbox" id="watch-cinema-toggle"' + cinemaChecked + ' />'
            + '<span class="autoplay-slider"></span>'
            + '</label>'
            + '<label class="watch-autoplay-label js-only">'
            + '<span class="autoplay-label-text">Autoplay</span>'
            + '<input type="checkbox" id="watch-autoplay-toggle"' + autoplayChecked + ' />'
            + '<span class="autoplay-slider"></span>'
            + '</label>'
            + '<button id="karaoke-toggle-btn" class="ring-karaoke-btn mps-button js-only" style="display:' + (karaokeUrls.instrumentals || karaokeUrls.vocals || karaokeEligible ? '' : 'none') + '" title="Karaoke"></button>'
            + '<button id="skip-prev-btn" class="skip-next-btn mps-button js-only" title="Go to previous item">&#9664; Prev</button>'
            + '<button id="skip-random-btn" class="skip-next-btn mps-button js-only" title="Jump to random item">&#127922;</button>'
            + '<button id="skip-next-btn" class="skip-next-btn mps-button js-only" title="Skip to next unwatched">Next &#9654;</button>'
            + '</div>'
            + '<div class="ring-filter-controls js-only">';
        FILTER_TYPES.forEach(function(t) {
            var activeClass = mediaFilters[t] ? ' active' : '';
            headerHtml += '<button class="ring-filter-btn' + activeClass + '" data-filter-type="' + t + '">'
                + FILTER_LABELS[t] + '</button>';
        });
        headerHtml += '</div></div>';

        // Apply fresh-mode class to container
        if (freshMode) {
            container.classList.add('fresh-mode');
        } else {
            container.classList.remove('fresh-mode');
        }

        if (!related || related.length === 0) {
            container.innerHTML = headerHtml + '<p>No more items</p>';
            rebindToggles();
            return;
        }

        var html = headerHtml;
        var currentTitle = document.title;
        var insertedCurrent = false;

        related.forEach(function(item) {
            var offset = item.offset || 1;
            var watched = isWatched(item.id);
            var dimClass = watched ? ' related-content-row-watched' : '';
            var prevClass = offset < 0 ? ' related-content-row-prev' : '';
            var overflowClass = offset > 7 ? ' related-content-overflow' : '';

            // Insert "now playing" row before the first forward item
            if (offset > 0 && !insertedCurrent) {
                insertedCurrent = true;
                var ogImg = document.querySelector('meta[property="og:image"]');
                var currentThumb = ogImg ? ogImg.getAttribute('content') : '';
                html += '<div class="related-content-row related-content-row-current">';
                html += '<span class="related-content-index">&#9654;</span>';
                html += '<span class="related-content-item related-content-now-playing">';
                if (currentThumb) {
                    html += '<img src="' + currentThumb + '" />';
                } else {
                    html += '<span class="related-content-no-thumb"></span>';
                }
                html += '<span class="related-content-title">' + escapeHtml(currentTitle) + '</span>';
                html += '</span>';
                html += '<button class="queue-add-btn js-only" data-product-id="' + currentProductId + '" data-title="' + escapeHtml(currentTitle) + '" data-url="' + (currentCanonicalUrl || '') + '" data-media-type="' + (currentProductMediaType || '') + '" data-thumbnail="' + (currentThumb || '') + '" title="Add to queue">+</button>';
                html += '</div>';
            }

            var mediaType = item.media_type || 'other';
            html += '<div class="related-content-row' + dimClass + prevClass + overflowClass + '" data-media-type="' + mediaType + '">';
            html += '<span class="related-content-index">' + offset + '</span>';
            html += '<a href="' + item.url + '" class="related-content-item" data-watch-id="' + item.id + '">';
            if (item.thumbnail_url) {
                var lazyAttr = offset > 7 ? ' loading="lazy"' : '';
                html += '<img' + lazyAttr + ' src="' + item.thumbnail_url + '" />';
            } else {
                html += '<span class="related-content-no-thumb"></span>';
            }
            html += '<span class="related-content-title">' + escapeHtml(item.title) + '</span>';
            html += '</a>';
            html += '<button class="queue-add-btn js-only" data-product-id="' + item.id + '" data-title="' + escapeHtml(item.title) + '" data-url="' + item.url + '" data-media-type="' + (item.media_type || '') + '" data-thumbnail="' + (item.thumbnail_url || '') + '" title="Add to queue">+</button>';
            html += '</div>';
        });

        if (commentsEnabled) {
            html += '<a href="#comments" class="related-content-comments-link">Comments (' + (commentCount || 0) + ')</a>';
        }

        container.innerHTML = html;
        applyMediaFilterClasses(container);
        rebindToggles();
    }

    function rebindToggles() {
        autoplayToggle = document.getElementById('watch-autoplay-toggle');
        if (autoplayToggle) {
            autoplayToggle.checked = autoplayEnabled;
            autoplayToggle.addEventListener('change', function() {
                autoplayEnabled = this.checked;
                try { localStorage.setItem('watchAutoplay', autoplayEnabled ? '1' : '0'); } catch (e) {}
                if (!autoplayEnabled) {
                    if (djCrossfadeActive) cancelDjCrossfade();
                    cancelCountdown();
                    if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }
                } else {
                    if (!activeMedia) startStaticTimer();
                }
            });
        }

        var directionToggle = document.getElementById('watch-direction-toggle');
        if (directionToggle) {
            directionToggle.checked = (ringDirection === -1);
            directionToggle.addEventListener('change', function() {
                ringDirection = this.checked ? -1 : 1;
                saveRingState();
            });
        }

        var freshToggle = document.getElementById('watch-fresh-toggle');
        if (freshToggle) {
            freshToggle.checked = freshMode;
            freshToggle.addEventListener('change', function() {
                freshMode = this.checked;
                try { localStorage.setItem('watchFreshMode', freshMode ? '1' : '0'); } catch (e) {}
                var rc = document.querySelector('.related-content');
                if (rc) {
                    if (freshMode) {
                        rc.classList.add('fresh-mode');
                    } else {
                        rc.classList.remove('fresh-mode');
                    }
                }
            });
        }

        var cinemaToggle = document.getElementById('watch-cinema-toggle');
        if (cinemaToggle) {
            cinemaToggle.checked = cinemaMode;
            cinemaToggle.addEventListener('change', function() {
                cinemaMode = this.checked;
                try { localStorage.setItem('watchCinemaMode', cinemaMode ? '1' : '0'); } catch (e) {}
                applyCinemaMode();
            });
        }

        // Media type filter buttons
        var filterBtns = document.querySelectorAll('.ring-filter-btn');
        for (var fi = 0; fi < filterBtns.length; fi++) {
            filterBtns[fi].addEventListener('click', function() {
                var type = this.getAttribute('data-filter-type');
                mediaFilters[type] = !mediaFilters[type];
                this.classList.toggle('active', mediaFilters[type]);
                saveMediaFilters();
                applyMediaFilterClasses();
            });
        }

        updateProgressDisplay();
        updateKaraokeButton();
    }

    function escapeHtml(text) {
        var div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // --- Helper: check if a product's media type is filtered out ---
    function isItemFiltered(productId) {
        var el = document.querySelector('[data-watch-id="' + productId + '"]');
        if (!el) return false; // unknown — don't filter
        var row = el.closest('.related-content-row');
        if (!row) return false;
        var type = row.getAttribute('data-media-type');
        return type ? !mediaFilters[type] : false;
    }

    function itemFromElement(el, id) {
        return {
            id: id || el.getAttribute('data-watch-id'),
            title: el.querySelector('span') ? el.querySelector('span').textContent : '',
            thumbnail_url: el.querySelector('img') ? el.querySelector('img').src : null,
            url: el.getAttribute('href') || ('/c/' + (id || el.getAttribute('data-watch-id')))
        };
    }

    // --- Ring-based getNextItem ---
    function getNextItem() {
        // Queue items splice into current position — play queue first
        if (queue.length > 0) return queue[0];

        // Advance ring position by direction, skip filtered items
        if (!ringProductIds.length) {
            // No ring — fall back to first visible related item in DOM
            var allRelated = document.querySelectorAll('.related-content-item[data-watch-id]');
            for (var i = 0; i < allRelated.length; i++) {
                var el = allRelated[i];
                var row = el.closest('.related-content-row');
                var mtype = row ? row.getAttribute('data-media-type') : null;
                if (mtype && !mediaFilters[mtype]) continue;
                return itemFromElement(el);
            }
            return null;
        }

        // Walk ring in travel direction, skipping filtered items
        for (var i = 1; i <= ringProductIds.length; i++) {
            var nextPos = (ringPosition + i * ringDirection + ringProductIds.length) % ringProductIds.length;
            var nextId = ringProductIds[nextPos];
            if (isItemFiltered(nextId)) continue;
            var el = document.querySelector('[data-watch-id="' + nextId + '"]');
            return el ? itemFromElement(el, nextId) : {
                id: nextId, title: '', thumbnail_url: null, url: '/c/' + nextId
            };
        }
        return null; // all items filtered
    }

    // --- Skip to next unwatched ring item ---
    function getNextUnwatchedItem() {
        if (!ringProductIds.length) return getNextItem();

        // Walk forward through the ring, skipping watched and filtered items
        for (var i = 1; i < ringProductIds.length; i++) {
            var pos = (ringPosition + i * ringDirection + ringProductIds.length * ringProductIds.length) % ringProductIds.length;
            var id = ringProductIds[pos];
            if (!isWatched(id) && !isItemFiltered(id)) {
                var el = document.querySelector('[data-watch-id="' + id + '"]');
                return el ? itemFromElement(el, id) : {
                    id: id, title: '', thumbnail_url: null, url: '/c/' + id
                };
            }
        }

        // All items watched — fall back to regular next
        return getNextItem();
    }

    function skipToPrev() {
        if (!ringProductIds.length) return;
        if (djCrossfadeActive) cancelDjCrossfade();
        cancelCountdown();
        if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }

        // Step backward one position (opposite of ring direction)
        ringPosition = (ringPosition - ringDirection + ringProductIds.length) % ringProductIds.length;
        saveRingState();
        fetchAndNavigate(ringProductIds[ringPosition]);
    }

    function skipToRandom() {
        if (!ringProductIds.length) return;
        if (djCrossfadeActive) cancelDjCrossfade();
        cancelCountdown();
        if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }

        // Pick a random position that isn't the current one
        var candidates = [];
        for (var i = 0; i < ringProductIds.length; i++) {
            if (i !== ringPosition) candidates.push(i);
        }
        if (!candidates.length) return;

        var idx = candidates[Math.floor(Math.random() * candidates.length)];
        ringPosition = idx;
        saveRingState();
        fetchAndNavigate(ringProductIds[idx]);
    }

    // One true selector — every forward path (Next button, autoplay, preload)
    // walks our ring the same way. Fresh toggle controls watched-skip.
    function chooseNextInRing() {
        return freshMode ? getNextUnwatchedItem() : getNextItem();
    }

    function skipToNext() {
        if (djCrossfadeActive) cancelDjCrossfade();
        cancelCountdown();
        if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }

        var item = chooseNextInRing();
        if (!item || !item.id) return;

        var idx = ringProductIds.indexOf(item.id);
        if (idx !== -1) {
            ringPosition = idx;
            saveRingState();
        }

        fetchAndNavigate(item.id);
    }

    function showCountdownOverlay(next) {
        var overlay = document.getElementById('watch-countdown');
        if (!overlay) return;

        var thumb = document.getElementById('countdown-thumb');
        var titleEl = document.getElementById('countdown-title');
        var numberEl = document.getElementById('countdown-number');

        if (thumb) {
            if (next.thumbnail_url) {
                thumb.src = next.thumbnail_url;
                thumb.style.display = '';
            } else {
                thumb.style.display = 'none';
            }
        }
        if (titleEl) titleEl.textContent = 'Up next: ' + next.title;
        if (numberEl) numberEl.textContent = COUNTDOWN_SECONDS;

        overlay.style.display = '';
    }

    // --- DJ crossfade: starts 7s before end, blends audio + video ---
    function startDjCrossfade() {
        if (djCrossfadeActive) return;
        if (!autoplayEnabled) return;
        if (!activeMedia) return;

        var next = chooseNextInRing();
        if (!next || !next.id) return;

        // Need preloaded data for the next item
        var data = (preloadedData && preloadedData.product_id === next.id) ? preloadedData : null;
        if (!data) return;

        // Same media type: full DJ crossfade; different/static: DJ fadeout
        var nextIsPlayable = (data.media_type === 'video' || data.media_type === 'audio');
        if (!nextIsPlayable || data.media_type !== currentMediaType || !standbyMedia) {
            startDjFadeout(data);
            return;
        }

        djCrossfadeActive = true;
        djCrossfadeData = data;
        preloadedData = null;

        // Show countdown overlay during the crossfade
        showCountdownOverlay(next);

        // Prepare standby media
        if (!standbyMedia.src || standbyMedia.src !== data.media_url) {
            standbyMedia.src = data.media_url;
        }
        standbyMedia.volume = 0;
        standbyMedia.currentTime = 0;

        // Move standby into the same container as active for visual overlay
        var mediaContainer = activeMedia.parentNode;
        if (mediaContainer && standbyMedia.parentNode !== mediaContainer) {
            mediaContainer.appendChild(standbyMedia);
        }

        // Style for visual crossfade overlay
        standbyMedia.className = (currentMediaType === 'video')
            ? 'product-main crossfade-incoming'
            : 'crossfade-incoming';
        standbyMedia.style.display = '';
        standbyMedia.style.opacity = '0';
        standbyMedia.controls = false;

        // Start playing the standby
        var playPromise = standbyMedia.play();
        if (playPromise !== undefined) {
            playPromise.catch(function() {
                // Can't play standby — abort, fall back to ended→countdown
                djCrossfadeActive = false;
                djCrossfadeData = null;
                standbyMedia.style.display = 'none';
                standbyMedia.className = '';
            });
        }

        // Animate volume + opacity over COUNTDOWN_SECONDS
        var steps = COUNTDOWN_SECONDS * 20; // 20fps for smooth animation
        var step = 0;
        var interval = (COUNTDOWN_SECONDS * 1000) / steps;
        // Honor the active tab's current volume as the ceiling — never fade
        // louder than what's already playing.
        var startVolume = activeMedia.volume;
        if (!(startVolume >= 0)) startVolume = 1;
        djStartVolume = startVolume;

        if (crossfadeTimer) clearInterval(crossfadeTimer);
        crossfadeTimer = setInterval(function() {
            step++;
            var progress = step / steps;

            // Volume crossfade
            try {
                activeMedia.volume = Math.max(0, startVolume * (1 - progress));
                standbyMedia.volume = Math.min(startVolume, startVolume * progress);
            } catch(e) {}

            // Visual crossfade (opacity)
            activeMedia.style.opacity = String(1 - progress);
            standbyMedia.style.opacity = String(progress);

            // Update countdown number
            var remaining = COUNTDOWN_SECONDS - Math.floor(step * COUNTDOWN_SECONDS / steps);
            var numberEl = document.getElementById('countdown-number');
            if (numberEl) numberEl.textContent = Math.max(0, remaining);

            // At halfway point, update page content (URL, title, description)
            if (step === Math.floor(steps / 2)) {
                updatePageContent(data);
            }

            if (step >= steps) {
                completeDjCrossfade(data);
            }
        }, interval);
    }

    function completeDjCrossfade(data) {
        if (crossfadeTimer) {
            clearInterval(crossfadeTimer);
            crossfadeTimer = null;
        }

        // Finish the swap
        activeMedia.pause();
        activeMedia.style.display = 'none';
        activeMedia.style.opacity = '1';

        standbyMedia.style.opacity = '1';
        standbyMedia.className = (currentMediaType === 'video') ? 'product-main' : '';
        standbyMedia.controls = true;

        var tmp = activeMedia;
        activeMedia = standbyMedia;
        standbyMedia = tmp;

        activeMedia.id = (currentMediaType === 'video') ? 'watch-video' : 'watch-audio';

        // Move standby back to the main container for next cycle
        var mainContainer = document.getElementById('watch-media-container');
        if (mainContainer && standbyMedia.parentNode !== mainContainer) {
            mainContainer.appendChild(standbyMedia);
        }
        standbyMedia.style.display = 'none';

        // Pop queue if this was a queued item (ring position already
        // synced by updatePageContent → syncRingPosition at halfway mark)
        if (data && queue.length > 0 && queue[0].id === data.product_id) {
            queue.shift();
            saveQueue();
            renderQueue();
        }

        cancelCountdown();
        djCrossfadeActive = false;
        djCrossfadeData = null;

        setupMediaEvents();
        preloadNext();
    }

    // --- DJ fadeout: fade current media to silence/black, then navigate ---
    // Used for video/audio → static or cross-type transitions
    function startDjFadeout(data) {
        if (djCrossfadeActive) return;

        djCrossfadeActive = true;
        djFadeoutActive = true;
        djCrossfadeData = data;
        preloadedData = null;

        showCountdownOverlay({
            id: data.product_id,
            title: data.title,
            thumbnail_url: data.thumbnail_url
        });

        var steps = COUNTDOWN_SECONDS * 20; // 20fps
        var step = 0;
        var interval = (COUNTDOWN_SECONDS * 1000) / steps;
        // Honor the active tab's current volume as the ceiling — never fade
        // louder than what's already playing.
        var startVolume = activeMedia.volume;
        if (!(startVolume >= 0)) startVolume = 1;
        djStartVolume = startVolume;

        if (crossfadeTimer) clearInterval(crossfadeTimer);
        crossfadeTimer = setInterval(function() {
            step++;
            var progress = step / steps;

            // Fade volume and opacity to zero
            try { activeMedia.volume = Math.max(0, startVolume * (1 - progress)); } catch(e) {}
            activeMedia.style.opacity = String(1 - progress);

            // Countdown number
            var remaining = COUNTDOWN_SECONDS - Math.floor(step * COUNTDOWN_SECONDS / steps);
            var numberEl = document.getElementById('countdown-number');
            if (numberEl) numberEl.textContent = Math.max(0, remaining);

            if (step >= steps) {
                completeDjFadeout(data);
            }
        }, interval);
    }

    function completeDjFadeout(data) {
        if (crossfadeTimer) { clearInterval(crossfadeTimer); crossfadeTimer = null; }
        activeMedia.pause();
        activeMedia.style.opacity = '1';
        try { activeMedia.volume = 1; } catch(e) {}
        djCrossfadeActive = false;
        djFadeoutActive = false;
        djCrossfadeData = null;
        cancelCountdown();

        // Pop queue or sync ring position to target
        if (queue.length > 0 && queue[0].id === data.product_id) {
            queue.shift();
            saveQueue();
            renderQueue();
        } else if (ringProductIds.length) {
            var idx = ringProductIds.indexOf(data.product_id);
            if (idx !== -1) {
                ringPosition = idx;
            } else {
                ringPosition = (ringPosition + ringDirection + ringProductIds.length) % ringProductIds.length;
            }
            saveRingState();
        }

        transitionTo(data);
    }

    function cancelDjCrossfade() {
        if (!djCrossfadeActive) return;

        if (crossfadeTimer) {
            clearInterval(crossfadeTimer);
            crossfadeTimer = null;
        }

        // Restore active media (back to the volume it had before the fade started)
        activeMedia.volume = djStartVolume;
        activeMedia.style.opacity = '1';

        // Clean up standby only during full crossfade (not fadeout)
        if (!djFadeoutActive) {
            standbyMedia.pause();
            standbyMedia.style.display = 'none';
            standbyMedia.style.opacity = '0';
            standbyMedia.className = '';
            standbyMedia.volume = 0;

            var mainContainer = document.getElementById('watch-media-container');
            if (mainContainer && standbyMedia.parentNode !== mainContainer) {
                mainContainer.appendChild(standbyMedia);
            }
        }

        djCrossfadeActive = false;
        djFadeoutActive = false;
        djCrossfadeData = null;
        cancelCountdown();
    }

    function startCountdown() {
        if (!autoplayEnabled) return;
        if (djCrossfadeActive) return;

        var next = chooseNextInRing();
        if (!next) return;

        showCountdownOverlay(next);

        // Epic fade: smooth 20fps with content fade-out
        var fadeTarget = document.querySelector('.product-images');
        var steps = COUNTDOWN_SECONDS * 20;
        var step = 0;
        var interval = (COUNTDOWN_SECONDS * 1000) / steps;
        var lastShownRemaining = COUNTDOWN_SECONDS;

        countdownTimer = setInterval(function() {
            step++;
            var progress = step / steps;

            // Fade out current content
            if (fadeTarget) fadeTarget.style.opacity = String(1 - progress * 0.85);

            // Fade volume if media still active
            if (activeMedia && !activeMedia.paused) {
                try { activeMedia.volume = Math.max(0, 1 - progress); } catch(e) {}
            }

            // Update countdown number at second boundaries
            var remaining = COUNTDOWN_SECONDS - Math.floor(step * COUNTDOWN_SECONDS / steps);
            if (remaining !== lastShownRemaining) {
                lastShownRemaining = remaining;
                var numberEl = document.getElementById('countdown-number');
                if (numberEl) numberEl.textContent = Math.max(0, remaining);
            }

            if (step >= steps) {
                clearInterval(countdownTimer);
                countdownTimer = null;
                if (fadeTarget) fadeTarget.style.opacity = '1';
                navigateToNext(next);
            }
        }, interval);
    }

    function cancelCountdown() {
        if (countdownTimer) {
            clearInterval(countdownTimer);
            countdownTimer = null;
        }
        var overlay = document.getElementById('watch-countdown');
        if (overlay) overlay.style.display = 'none';
        // Restore content opacity from epic fade
        var fadeTarget = document.querySelector('.product-images');
        if (fadeTarget) fadeTarget.style.opacity = '1';
        if (activeMedia) {
            activeMedia.style.opacity = '1';
            try { activeMedia.volume = 1; } catch(e) {}
        }
    }

    function navigateToNext(next) {
        if (!next || !next.id) return;
        var productId = next.id;

        // If this came from the queue, pop it (ring position unchanged)
        if (queue.length > 0 && queue[0].id === productId) {
            queue.shift();
            saveQueue();
            renderQueue();
        } else if (ringProductIds.length) {
            // Sync ring position to target's actual index — honors Fresh-mode
            // jumps past watched items (which can be farther than +direction).
            var idx = ringProductIds.indexOf(productId);
            if (idx !== -1) {
                ringPosition = idx;
            } else {
                ringPosition = (ringPosition + ringDirection + ringProductIds.length) % ringProductIds.length;
            }
            saveRingState();
        }

        if (preloadedData && preloadedData.product_id === productId) {
            transitionTo(preloadedData);
            preloadedData = null;
        } else {
            fetchAndNavigate(productId);
        }
    }

    // --- Static content timer (PDF, images: 42 seconds then advance) ---
    function startStaticTimer() {
        if (!autoplayEnabled) return;
        if (staticTimer) clearTimeout(staticTimer);
        staticTimer = setTimeout(function() {
            staticTimer = null;
            startCountdown();
        }, STATIC_DISPLAY_SECONDS * 1000);
    }

    // --- Phase 1: fetch next item JSON (fast, no media download) ---
    function preloadNext() {
        var next = chooseNextInRing();
        if (!next || !next.id) return;

        // Idempotent: skip if already preloading or preloaded this item
        if (preloadingId === next.id) return;
        if (preloadedData && preloadedData.product_id === next.id) return;

        preloadingId = next.id;
        bufferingStarted = false;

        fetchWatchData(next.id).then(function(data) {
            preloadingId = null;
            preloadedData = data;
            if (standbyMedia && data.media_url && (data.media_type === 'video' || data.media_type === 'audio')) {
                standbyMedia.src = data.media_url;
                standbyMedia.preload = 'none';
            }
        }).catch(function() {
            preloadingId = null;
            preloadedData = null;
        });
    }

    // --- Phase 2: start buffering media ~30s before end for seamless crossfade ---
    function bufferNext() {
        if (bufferingStarted) return;
        if (!standbyMedia || !standbyMedia.src) return;
        bufferingStarted = true;
        standbyMedia.preload = 'auto';
        standbyMedia.load();
    }

    // --- Presigned URL refresh: swap src every 7 min to prevent TTL expiry ---
    function scheduleUrlRefresh() {
        if (urlRefreshTimer) clearTimeout(urlRefreshTimer);
        urlRefreshTimer = setTimeout(refreshMediaUrl, URL_REFRESH_MS);
    }

    function refreshMediaUrl() {
        urlRefreshTimer = null;
        if (!activeMedia || !currentProductId) return;
        // Skip refresh during crossfade — the new media already has a fresh URL
        if (djCrossfadeActive) { scheduleUrlRefresh(); return; }

        // Skip refresh while paused — no point re-buffering idle media.
        // The play listener will refresh stale URLs when the user resumes.
        if (activeMedia.paused) { scheduleUrlRefresh(); return; }

        fetchWatchData(currentProductId).then(function(data) {
            if (!data || !data.media_url) { scheduleUrlRefresh(); return; }
            // Only refresh if still on the same product
            if (data.product_id !== currentProductId) { scheduleUrlRefresh(); return; }

            // Refresh karaoke URLs from the fresh response
            var hadKaraoke = !!(karaokeUrls.instrumentals || karaokeUrls.vocals);
            karaokeUrls.original = data.media_url;
            if (data.instrumentals_url) karaokeUrls.instrumentals = data.instrumentals_url;
            if (data.vocals_url) karaokeUrls.vocals = data.vocals_url;
            if (data.karaoke_eligible !== undefined) karaokeEligible = !!data.karaoke_eligible;

            // Processing just completed — tracks appeared
            if (!hadKaraoke && karaokeProcessing && (data.instrumentals_url || data.vocals_url)) {
                karaokeProcessing = false;
                updateKaraokeButton();
                // Auto-switch to instrumentals
                cycleKaraoke();
            }

            var wasPlaying = !activeMedia.paused;
            var savedTime = activeMedia.currentTime;
            var savedVolume = activeMedia.volume;
            var savedMuted = activeMedia.muted;

            // Use the URL matching current karaoke mode
            var freshSrc;
            if (karaokeMode === 1 && karaokeUrls.instrumentals) freshSrc = karaokeUrls.instrumentals;
            else if (karaokeMode === 2 && karaokeUrls.vocals) freshSrc = karaokeUrls.vocals;
            else freshSrc = data.media_url;
            activeMedia.src = freshSrc;
            lastUrlRefreshTs = Date.now();

            activeMedia.addEventListener('loadeddata', function onLoaded() {
                activeMedia.removeEventListener('loadeddata', onLoaded);
                activeMedia.currentTime = savedTime;
                activeMedia.volume = savedVolume;
                activeMedia.muted = savedMuted;
                if (wasPlaying) activeMedia.play().catch(function() {});
                scheduleUrlRefresh();
            });

            // Fallback: if loadeddata never fires, reschedule anyway
            setTimeout(function() {
                if (!urlRefreshTimer) scheduleUrlRefresh();
            }, 10000);
        }).catch(function() {
            scheduleUrlRefresh();
        });
    }

    // --- Playback progress: save to localStorage, restore on reload ---
    function saveProgress() {
        if (!activeMedia || !currentProductId) return;
        if (!activeMedia.duration || !isFinite(activeMedia.duration)) return;
        try {
            localStorage.setItem(PROGRESS_KEY, JSON.stringify({
                id: currentProductId,
                time: activeMedia.currentTime,
                ts: Date.now()
            }));
        } catch(e) {}
    }

    function clearProgress() {
        try { localStorage.removeItem(PROGRESS_KEY); } catch(e) {}
    }

    function restoreProgress() {
        if (!activeMedia || !currentProductId) return;
        try {
            var raw = localStorage.getItem(PROGRESS_KEY);
            if (!raw) return;
            var saved = JSON.parse(raw);
            // Only restore if same product and saved within last 24 hours
            if (saved.id !== currentProductId) return;
            if (Date.now() - saved.ts > 24 * 60 * 60 * 1000) { clearProgress(); return; }
            if (!saved.time || saved.time < 3) return;

            activeMedia.currentTime = saved.time;
            // Fade volume up from 0 over 2 seconds
            activeMedia.volume = 0;
            var fadeSteps = 40;
            var fadeStep = 0;
            var fadeInterval = setInterval(function() {
                fadeStep++;
                activeMedia.volume = Math.min(1, fadeStep / fadeSteps);
                if (fadeStep >= fadeSteps) clearInterval(fadeInterval);
            }, 50);
        } catch(e) {}
    }

    // --- Named event handlers (must be named so removeEventListener works) ---
    function onMediaEnded() {
        clearProgress();
        // Fallback: only start countdown if DJ crossfade didn't handle it
        // Skip if user navigation is in-flight — the new song will take over
        if (!djCrossfadeActive && !navigationPending) {
            // Short clips: instant hard-cut to next if preloaded, no countdown
            if (activeMedia && activeMedia.duration <= COUNTDOWN_SECONDS
                && preloadedData && autoplayEnabled) {
                var data = preloadedData;
                preloadedData = null;
                transitionTo(data);
            } else {
                startCountdown();
            }
        }
    }

    function onMediaLoadedMetadata() {
        preloadNext();
    }

    function onMediaTimeUpdate() {
        if (!activeMedia || !activeMedia.duration || !isFinite(activeMedia.duration)) return;
        var remaining = activeMedia.duration - activeMedia.currentTime;
        if (remaining <= BUFFER_AHEAD_SECONDS) {
            bufferNext();
        }
        // DJ crossfade: start blending 7 seconds before end
        // Skip for short clips — they play fully then hard-cut on ended
        // Skip when user navigation is in-flight to prevent re-triggering
        if (remaining <= COUNTDOWN_SECONDS && remaining > 0 && !djCrossfadeActive
            && !navigationPending && activeMedia.duration > COUNTDOWN_SECONDS) {
            startDjCrossfade();
        }
        // Save progress every 7 seconds
        var now = Date.now();
        if (now - lastProgressSave >= PROGRESS_SAVE_INTERVAL) {
            lastProgressSave = now;
            saveProgress();
        }
    }

    // --- Stale URL check on play: if paused long enough for the presigned URL
    //     to expire, refresh before resuming playback ---
    function onMediaPlay() {
        if (Date.now() - lastUrlRefreshTs > URL_REFRESH_MS) {
            activeMedia.pause();
            refreshMediaUrl();
        }
    }

    // --- Media event setup ---
    function setupMediaEvents() {
        if (!activeMedia) return;

        // Remove from both elements to prevent listener accumulation after swaps
        activeMedia.removeEventListener('ended', onMediaEnded);
        activeMedia.removeEventListener('loadedmetadata', onMediaLoadedMetadata);
        activeMedia.removeEventListener('timeupdate', onMediaTimeUpdate);
        activeMedia.removeEventListener('play', onMediaPlay);
        if (standbyMedia) {
            standbyMedia.removeEventListener('ended', onMediaEnded);
            standbyMedia.removeEventListener('loadedmetadata', onMediaLoadedMetadata);
            standbyMedia.removeEventListener('timeupdate', onMediaTimeUpdate);
            standbyMedia.removeEventListener('play', onMediaPlay);
        }

        // Add fresh listeners to active element only
        activeMedia.addEventListener('ended', onMediaEnded);
        activeMedia.addEventListener('loadedmetadata', onMediaLoadedMetadata);
        activeMedia.addEventListener('timeupdate', onMediaTimeUpdate);
        activeMedia.addEventListener('play', onMediaPlay);
    }

    // Initial setup
    if (activeMedia) {
        setupMediaEvents();
        scheduleUrlRefresh();
    } else {
        // Static content (PDF, image, etc.) — start 42-second timer
        // Reset on scroll — scrolling means the user is still reading
        startStaticTimer();
        window.addEventListener('scroll', function() {
            if (!activeMedia && autoplayEnabled) {
                cancelCountdown();
                startStaticTimer();
            }
        });
    }

    // --- Event delegation for SPA link interception ---
    document.addEventListener('click', function(e) {
        var queueBtn = e.target.closest('.queue-add-btn');
        if (queueBtn) {
            e.preventDefault();
            e.stopPropagation();
            addToQueue({
                id: queueBtn.getAttribute('data-product-id'),
                title: queueBtn.getAttribute('data-title'),
                url: queueBtn.getAttribute('data-url'),
                media_type: queueBtn.getAttribute('data-media-type'),
                thumbnail_url: queueBtn.getAttribute('data-thumbnail')
            });
            var row = queueBtn.closest('.related-content-row');
            if (row) {
                row.classList.remove('queue-row-added');
                void row.offsetWidth;
                row.classList.add('queue-row-added');
            }
            return;
        }

        if (e.target.id === 'karaoke-toggle-btn' || e.target.closest('#karaoke-toggle-btn')) {
            e.preventDefault();
            cycleKaraoke();
            return;
        }

        if (e.target.id === 'skip-prev-btn' || e.target.closest('#skip-prev-btn')) {
            e.preventDefault();
            skipToPrev();
            return;
        }

        if (e.target.id === 'skip-random-btn' || e.target.closest('#skip-random-btn')) {
            e.preventDefault();
            skipToRandom();
            return;
        }

        if (e.target.id === 'skip-next-btn' || e.target.closest('#skip-next-btn')) {
            e.preventDefault();
            skipToNext();
            return;
        }

        var link = e.target.closest('.related-content-item[data-watch-id]');
        if (link) {
            e.preventDefault();
            var productId = link.getAttribute('data-watch-id');
            fetchAndNavigate(productId);
            return;
        }

        if (e.target.id === 'countdown-play-now' || e.target.closest('#countdown-play-now')) {
            e.preventDefault();
            if (djCrossfadeActive && djCrossfadeData) {
                if (djFadeoutActive) {
                    completeDjFadeout(djCrossfadeData);
                } else {
                    completeDjCrossfade(djCrossfadeData);
                }
            } else {
                cancelCountdown();
                var next = chooseNextInRing();
                if (next) navigateToNext(next);
            }
            return;
        }

        if (e.target.id === 'countdown-cancel' || e.target.closest('#countdown-cancel')) {
            e.preventDefault();
            if (djCrossfadeActive) {
                cancelDjCrossfade();
            } else {
                cancelCountdown();
            }
            return;
        }
    });

    // --- Keyboard shortcut: K to toggle karaoke ---
    document.addEventListener('keydown', function(e) {
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
        if (e.key === 'k' || e.key === 'K') {
            cycleKaraoke();
        }
    });

    // --- Browser back/forward ---
    window.addEventListener('popstate', function(e) {
        if (e.state && e.state.productId) {
            fetchAndNavigate(e.state.productId);
        }
    });

    // Store initial state
    var initialProductId = document.querySelector('[data-watch-product-id]');
    if (initialProductId) {
        history.replaceState(
            {productId: initialProductId.getAttribute('data-watch-product-id')},
            document.title,
            window.location.href
        );
    }

    // Dim already-watched items in server-rendered related content
    function dimWatchedInDOM() {
        var rows = document.querySelectorAll('.related-content-row');
        for (var i = 0; i < rows.length; i++) {
            var link = rows[i].querySelector('.related-content-item[data-watch-id]');
            if (link && isWatched(link.getAttribute('data-watch-id'))) {
                rows[i].classList.add('related-content-row-watched');
            }
        }
    }
    dimWatchedInDOM();
    // Apply fresh-mode and media filters to server-rendered related content
    if (freshMode) {
        var rc = document.querySelector('.related-content');
        if (rc) rc.classList.add('fresh-mode');
    }
    applyMediaFilterClasses();
    updateProgressDisplay();
    rebindToggles();

    renderQueue();

    // Seed ring from server if localStorage is empty (first visit, cache cleared)
    if (!ringProductIds.length && currentProductId) {
        fetchWatchData(currentProductId).then(function(data) {
            if (data.ring && data.ring.length) {
                ringProductIds = data.ring;
                syncRingPosition(currentProductId);
                saveRingState();
                updateProgressDisplay();
            }
            preloadNext();
        }).catch(function() {
            preloadNext();
        });
    } else {
        preloadNext();
    }
})();
