Greasy Fork is available in English.

YouTube Temporary Playlist (Floating + Dockable + Drag&Drop + Autoplay)

A userscript that adds a floating, dockable playlist to YouTube with drag & drop functionality, autoplay, and cross-tab synchronization.

// ==UserScript==
// @name            YouTube Temporary Playlist (Floating + Dockable + Drag&Drop + Autoplay)
// @name:de         YouTube Temporary Playlist (Floating + Dockable + Drag&Drop + Autoplay)
// @namespace       https://greasyfork.org/users/928242
// @version         1.2.0
// @description     A userscript that adds a floating, dockable playlist to YouTube with drag & drop functionality, autoplay, and cross-tab synchronization.
// @description:de  Ein Benutzerskript, das YouTube eine schwebende, andockbare Wiedergabeliste mit Drag & Drop-Funktionalität, Autoplay und tabübergreifender Synchronisierung hinzufügt.
// @author          Kamikaze (https://github.com/Kamiikaze)
// @supportURL      https://github.com/Kamiikaze/Tampermonkey/issues
// @icon            https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @match           https://www.youtube.com/*
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_addValueChangeListener
// @license         MIT
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Fix for TrustedHTML issues in some browsers
     */
    if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {
        window.trustedTypes.createPolicy('default', {
            createHTML: string => string
        });
    }

    /**
     * Constants and Configuration
     */
    const CONFIG = {
        // Storage keys for persistence across tabs/sessions
        STORAGE: {
            PLAYLIST: "tempPlaylist",
            AUTOPLAY: "playlistActive",
            POSITION: "tempPlaylistPos",
            DOCKED: "tempPlaylistDocked"
        },
        // UI settings
        UI: {
            DEFAULT_WIDTH: "400px",
            COLLAPSED_WIDTH: "200px",
            MAX_HEIGHT: "400px",
            MIN_HEIGHT: "40px",
            AUTO_HIDE_DELAY: 3000 // 3 seconds delay before auto-hiding
        }
    };

    /**
     * State variables
     */
    let state = {
        playlist: GM_getValue(CONFIG.STORAGE.PLAYLIST, []),
        autoplayEnabled: GM_getValue(CONFIG.STORAGE.AUTOPLAY, false),
        savedPos: GM_getValue(CONFIG.STORAGE.POSITION, { left: null, top: null }),
        isDocked: GM_getValue(CONFIG.STORAGE.DOCKED, false),
        collapsed: false,
        isDragging: false,
        currentDraggedVideoInfo: null,
        dragOffset: { x: 0, y: 0 },
        autoHideTimer: null,
        videoPlaying: false
    };

    /**
     * Creates and injects CSS styles for the playlist
     */
    function injectStyles() {
        const style = document.createElement('style');
        style.innerHTML = `
        .autoPlaylistOverlay {
            position: fixed;
            background: rgba(0,0,0,0.85);
            color: white;
            padding: 10px;
            border-radius: 12px;
            z-index: 9999;
            font-size: 14px;
            display: flex;
            flex-direction: column;
            gap: 5px;
            transition: all 0.3s ease;
            box-shadow: 0 4px 20px rgba(0,0,0,0.4);
        }
        .autoPlaylistOverlay #playlist {
            display: flex;
            flex-direction: column;
            flex: 1;
            min-height: 100px;
            border: 1px dashed white;
            padding: 5px;
            overflow-y: auto;
            transition: max-height 0.5s ease;
            padding-bottom: 50px;
            scrollbar-width: thin;
            scrollbar-color: white transparent;
        }
        .autoPlaylistOverlay #playlist::-webkit-scrollbar {
            width: 6px;
        }
        .autoPlaylistOverlay #playlist::-webkit-scrollbar-thumb {
            background: white;
            border-radius: 10px;
        }
        .autoPlaylistOverlay button {
            padding: 5px 10px;
            background: rgba(255,255,255,0.1);
            border: none;
            border-radius: 8px;
            color: white;
            cursor: pointer;
            transition: background 0.3s;
            font-weight: bold;
        }
        .autoPlaylistOverlay button:hover {
            background: rgba(255,255,255,0.3);
        }
        .autoPlaylistOverlay button[data-index] {
            padding: 5px;
            background: none;
            border: none;
            border-radius: 8px;
            color: red;
            font-size: 16px;
            cursor: pointer;
            transition: background 0.3s;
        }
        .autoPlaylistOverlay button[data-index]:hover {
            background: rgba(255, 0, 0, 0.2);
        }
        .playlist-item {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 8px;
            border-bottom: 1px solid rgba(255,255,255,0.2);
            background: rgba(255,255,255,0.05);
            transition: background 0.2s;
            cursor: pointer;
        }
        .playlist-item:hover {
            background: rgba(255,255,255,0.1);
        }
        `;
        document.head.appendChild(style);
    }

    /**
     * Creates the main floating overlay interface
     * @returns {Object} References to DOM elements
     */
    function createInterface() {
        const overlay = document.createElement('div');
        overlay.className = "autoPlaylistOverlay";
        overlay.innerHTML = `
            <div id="header" style="display:flex; justify-content:space-between; align-items:center; cursor:move;">
                <h3 id="overlayTitle" style="margin:0; font-size:16px;">AutoPlayList</h3>
                <button id="collapseButton" style="background:none; border:none; color:white; font-size:18px; cursor:pointer;">_</button>
            </div>
            <div id="playlist">Drag Videos Here</div>
            <div id="controls" style="display:flex; flex-wrap:wrap; justify-content:center; gap:5px;">
                <button id="addCurrentVideo" title="Add current video to playlist">Current Video</button>
                <button id="toggleAutoplay" title="Enables autplay for AutoPlayList after video end">Enable Autoplay</button>
                <button id="clearPlaylist" title="Delete all items in playlist">Clear Playlist</button>
                <button id="dockToggle" title="Toggle between docked and moveable overlay">Dock</button>
            </div>
        `;
        document.body.appendChild(overlay);

        return {
            overlay,
            playlistDiv: overlay.querySelector("#playlist"),
            addCurrentButton: overlay.querySelector("#addCurrentVideo"),
            toggleButton: overlay.querySelector("#toggleAutoplay"),
            clearButton: overlay.querySelector("#clearPlaylist"),
            dockButton: overlay.querySelector("#dockToggle"),
            collapseButton: overlay.querySelector("#collapseButton"),
            overlayTitle: overlay.querySelector("#overlayTitle"),
            controlsDiv: overlay.querySelector("#controls"),
            header: overlay.querySelector("#header")
        };
    }

    /**
     * Storage/Persistence methods
     */
    const storage = {
        savePlaylist: () => GM_setValue(CONFIG.STORAGE.PLAYLIST, state.playlist),
        saveAutoplayState: () => GM_setValue(CONFIG.STORAGE.AUTOPLAY, state.autoplayEnabled),
        savePosition: (left, top) => GM_setValue(CONFIG.STORAGE.POSITION, { left, top }),
        saveDockState: () => GM_setValue(CONFIG.STORAGE.DOCKED, state.isDocked)
    };

    /**
     * Updates the playlist container title with current count
     * @param {Object} elements - DOM elements references
     */
    function updateTitle(elements) {
        elements.overlayTitle.textContent = `AutoPlayList (${state.playlist.length})`;
    }

    /**
     * Applies docked or floating position based on current state
     * @param {Object} elements - DOM elements references
     */
    function applyDockState(elements) {
        const { overlay, dockButton } = elements;
        const { isDocked, collapsed } = state;

        if (isDocked) {
            overlay.style.position = 'fixed';
            overlay.style.top = 'unset';
            overlay.style.left = 'unset';
            overlay.style.bottom = '0';
            overlay.style.right = '10px';
            overlay.style.borderRadius = '12px 12px 0 0';
            overlay.style.width = collapsed ? CONFIG.UI.COLLAPSED_WIDTH : CONFIG.UI.DEFAULT_WIDTH;
            overlay.style.maxHeight = collapsed ? CONFIG.UI.MIN_HEIGHT : CONFIG.UI.MAX_HEIGHT;
            overlay.style.height = 'auto';
            overlay.style.padding = collapsed ? '5px 10px' : '10px';
            dockButton.textContent = 'Undock';
        } else {
            overlay.style.position = 'fixed';
            overlay.style.top = 'unset';
            overlay.style.left = 'unset';
            overlay.style.bottom = '20px';
            overlay.style.right = '20px';
            overlay.style.borderRadius = '12px';
            overlay.style.width = collapsed ? CONFIG.UI.COLLAPSED_WIDTH : CONFIG.UI.DEFAULT_WIDTH;
            overlay.style.maxHeight = collapsed ? CONFIG.UI.MIN_HEIGHT : CONFIG.UI.MAX_HEIGHT;
            overlay.style.height = 'auto';
            overlay.style.padding = collapsed ? '5px 10px' : '10px';
            dockButton.textContent = 'Dock';
        }
    }

    /**
     * Ensures the overlay stays within window boundaries
     * @param {Object} pos - Position coordinates
     * @param {Object} elements - DOM elements references
     * @returns {Object} Adjusted position coordinates
     */
    function clampPosition(pos, elements) {
        const overlayWidth = elements.overlay.offsetWidth;
        const overlayHeight = elements.overlay.offsetHeight;
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;

        let left = pos.left;
        let top = pos.top;

        if (left < 0) left = 0;
        if (top < 0) top = 0;
        if (left + overlayWidth > windowWidth) left = windowWidth - overlayWidth-50;
        if (top + overlayHeight > windowHeight) top = windowHeight - overlayHeight-50;

        return { left, top };
    }

    /**
     * Renders the playlist items based on current state
     * @param {Object} elements - DOM elements references
     */
    function renderPlaylist(elements) {
        const { playlistDiv } = elements;

        updateTitle(elements);
        playlistDiv.innerHTML = '';

        if (state.playlist.length === 0) {
            playlistDiv.textContent = 'Drag Videos Here';
            return;
        }

        state.playlist.forEach((video, index) => {
            const thumbnailUrl = `https://i.ytimg.com/vi/${video.id}/hqdefault.jpg`;

            const item = document.createElement('div');
            item.classList.add('playlist-item');
            item.draggable = true;
            item.dataset.index = index;

            item.innerHTML = `
                <div style="font-weight:bold; width:20px; text-align:center;">${index + 1}</div>
                <img src="${thumbnailUrl}" style="width: 80px; height: 45px; object-fit: cover; border-radius:4px;">
                <div style="flex:1; overflow:hidden;">
                    <div class="title" style="
                        font-size:14px;
                        font-weight:bold;
                        overflow:hidden;
                        display:-webkit-box;
                        -webkit-line-clamp:2;
                        -webkit-box-orient:vertical;
                        text-overflow: ellipsis;
                    ">${video.title}</div>
                    <div style="font-size:12px; color:lightgray;">${video.channel}</div>
                </div>
                <button data-index="${index}" style="color:red;">❌</button>
            `;

            setupPlaylistItemEvents(item, index, elements);
            playlistDiv.appendChild(item);
        });
    }

    /**
     * Sets up event handlers for playlist items
     * @param {HTMLElement} item - The playlist item element
     * @param {number} index - Index of the item in the playlist
     * @param {Object} elements - DOM elements references
     */
    function setupPlaylistItemEvents(item, index, elements) {
        let isItemDragging = false;

        // Drag & drop reordering
        item.addEventListener('dragstart', (e) => {
            isItemDragging = true;
            e.dataTransfer.setData('text/plain', index);
        });

        item.addEventListener('dragend', () => {
            isItemDragging = false;
        });

        // Play video on click
        item.addEventListener('click', (e) => {
            if (isItemDragging || e.target.tagName === 'BUTTON') return;
            window.location.href = state.playlist[index].url;
        });

        // Title expansion on hover
        item.addEventListener('mouseenter', () => {
            const title = item.querySelector('.title');
            title.style.overflow = 'visible';
            title.style.webkitLineClamp = 'unset';
        });

        item.addEventListener('mouseleave', () => {
            const title = item.querySelector('.title');
            title.style.overflow = 'hidden';
            title.style.webkitLineClamp = '2';
        });

        // Item reordering via drag & drop
        item.addEventListener('dragover', (e) => {
            e.preventDefault();
            item.style.background = 'rgba(255,255,255,0.1)';
        });

        item.addEventListener('dragleave', () => {
            item.style.background = 'rgba(255,255,255,0.05)';
        });

        item.addEventListener('drop', (e) => {
            e.preventDefault();
            item.style.background = 'rgba(255,255,255,0.05)';

            const fromIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
            const toIndex = parseInt(item.dataset.index, 10);

            if (fromIndex !== toIndex) {
                const movedItem = state.playlist.splice(fromIndex, 1)[0];
                state.playlist.splice(toIndex, 0, movedItem);
                storage.savePlaylist();
                renderPlaylist(elements);
            }
        });
    }

    /**
     * Sets up the overlay drag functionality with improved performance
     * @param {Object} elements - DOM elements references
     */
    function setupOverlayDrag(elements) {
        const { header, overlay } = elements;
        let rafId = null;

        // Use requestAnimationFrame for smooth rendering
        function updatePosition(x, y) {
            if (rafId) {
                cancelAnimationFrame(rafId);
            }

            rafId = requestAnimationFrame(() => {
                const left = x - state.dragOffset.x;
                const top = y - state.dragOffset.y;

                overlay.style.left = `${left}px`;
                overlay.style.top = `${top}px`;
                overlay.style.bottom = 'unset';
                overlay.style.right = 'unset';

                rafId = null;
            });
        }

        header.addEventListener('mousedown', (e) => {
            if (state.isDocked || e.target.id === 'collapseButton') return;

            state.isDragging = true;

            // Calculate offset relative to the header
            const rect = overlay.getBoundingClientRect();
            state.dragOffset.x = e.clientX - rect.left;
            state.dragOffset.y = e.clientY - rect.top;

            // Make sure we're using fixed positioning
            overlay.style.position = 'fixed';
            overlay.style.bottom = 'unset';
            overlay.style.right = 'unset';

            document.body.style.userSelect = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (state.isDocked || !state.isDragging) return;
            updatePosition(e.clientX, e.clientY);
        });

        document.addEventListener('mouseup', () => {
            if (!state.isDragging) return;

            state.isDragging = false;
            document.body.style.userSelect = '';

            if (!state.isDocked) {
                // Apply clamping to ensure we stay within window boundaries
                const left = parseFloat(overlay.style.left) || 0;
                const top = parseFloat(overlay.style.top) || 0;

                const finalPosition = clampPosition({
                    left: left,
                    top: top
                }, elements);

                // Apply the clamped position
                overlay.style.left = `${finalPosition.left}px`;
                overlay.style.top = `${finalPosition.top}px`;

                storage.savePosition(finalPosition.left, finalPosition.top);
            }
        });
    }

    /**
     * Sets up behavior for docking the overlay
     * @param {Object} elements - DOM elements references
     */
    function setupDockButton(elements) {
        const { dockButton } = elements;

        dockButton.addEventListener('click', () => {
            state.isDocked = !state.isDocked;
            storage.saveDockState();
            applyDockState(elements);
        });
    }

    /**
     * Sets up collapse/expand behavior
     * @param {Object} elements - DOM elements references
     */
    function setupCollapseButton(elements) {
        const { collapseButton, playlistDiv, controlsDiv, overlay } = elements;

        collapseButton.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleCollapse(elements);
        });
    }

    /**
     * Toggles the collapsed state of the playlist
     * @param {Object} elements - DOM elements references
     * @param {boolean} [forceCollapse] - Force a specific collapse state
     */
    function toggleCollapse(elements, forceCollapse = null) {
        const { playlistDiv, controlsDiv, overlay } = elements;

        if (forceCollapse !== null) {
            state.collapsed = forceCollapse;
        } else {
            state.collapsed = !state.collapsed;
        }

        if (state.collapsed) {
            playlistDiv.style.maxHeight = '0';
            playlistDiv.style.display = 'none';
            controlsDiv.style.display = 'none';
            overlay.style.width = CONFIG.UI.COLLAPSED_WIDTH;
            overlay.style.padding = '5px 10px';
            if (state.isDocked) {
                overlay.style.bottom = '0';
                overlay.style.right = '10px';
                overlay.style.borderRadius = '12px 12px 0 0';
            }
        } else {
            playlistDiv.style.maxHeight = '300px';
            playlistDiv.style.display = 'flex';
            controlsDiv.style.display = 'flex';
            overlay.style.width = CONFIG.UI.DEFAULT_WIDTH;
            overlay.style.padding = '10px';
            if (state.isDocked) {
                overlay.style.bottom = '0';
                overlay.style.right = '10px';
                overlay.style.borderRadius = '12px 12px 0 0';
            }
        }
    }

    /**
     * Sets up drag & drop to add videos to playlist
     * @param {Object} elements - DOM elements references
     */
    function setupPlaylistDrop(elements) {
        const { playlistDiv } = elements;

        // Handle dragover events
        playlistDiv.addEventListener('dragover', (e) => {
            e.preventDefault();
            playlistDiv.style.background = 'rgba(0, 150, 255, 0.2)';
            playlistDiv.style.border = '2px dashed #00f';
        });

        // Reset styles when drag leaves
        playlistDiv.addEventListener('dragleave', () => {
            playlistDiv.style.background = '';
            playlistDiv.style.border = '1px dashed white';
        });

        // Process dropped items
        playlistDiv.addEventListener('drop', (e) => {
            e.preventDefault();
            playlistDiv.style.background = '';
            playlistDiv.style.border = '1px dashed white';

            if (state.currentDraggedVideoInfo) {
                state.playlist.push(state.currentDraggedVideoInfo);
                storage.savePlaylist();
                renderPlaylist(elements);
                state.currentDraggedVideoInfo = null;
            } else {
                const data = e.dataTransfer.getData('text/plain');
                if (data && data.includes('youtube.com/watch')) {
                    const videoId = (new URL(data)).searchParams.get('v') || '';
                    state.playlist.push({
                        url: data,
                        id: videoId,
                        title: "Unknown Title",
                        channel: "Unknown Channel"
                    });
                    storage.savePlaylist();
                    renderPlaylist(elements);
                }
            }
        });
    }

    /**
     * Sets up delete functionality for playlist items
     * @param {Object} elements - DOM elements references
     */
    function setupPlaylistDelete(elements) {
        const { playlistDiv } = elements;

        playlistDiv.addEventListener('click', (e) => {
            if (e.target.tagName === 'BUTTON' && e.target.hasAttribute('data-index')) {
                const idx = parseInt(e.target.getAttribute('data-index'), 10);
                if (!isNaN(idx)) {
                    state.playlist.splice(idx, 1);
                    storage.savePlaylist();
                    renderPlaylist(elements);
                }
                e.stopPropagation();
            }
        });
    }

    /**
     * Extracts current video information from the YouTube page
     * @returns {Object|null} Video information or null if not on a video page
     */
    function getCurrentVideoInfo() {
        // Check if we're on a video page
        if (!window.location.pathname.includes('/watch')) {
            return null;
        }

        try {
            // Get video ID from URL
            const videoId = new URL(window.location.href).searchParams.get('v');
            if (!videoId) return null;

            // Get video title
            const titleElement = document.querySelector('h1.ytd-watch-metadata');
            const title = titleElement ? titleElement.textContent.trim() : 'Unknown Title';

            // Get channel name
            const channelElement = document.querySelector('ytd-channel-name#channel-name a, #owner-name a');
            const channel = channelElement ? channelElement.textContent.trim() : 'Unknown Channel';

            return {
                url: window.location.href,
                id: videoId,
                title: title,
                channel: channel
            };
        } catch (error) {
            console.error("Error getting current video info:", error);
            return null;
        }
    }

    /**
     * Adds current video button to the controls
     * @param {Object} elements - DOM elements references
     */
    function setupCurrentVideoButton(elements) {
        const { addCurrentButton } = elements;

        // Add icon for better visibility
        addCurrentButton.innerHTML = `<span style="font-size:14px;">➕</span> Current Video`;

        // Setup click handler
        addCurrentButton.addEventListener('click', () => {
            const videoInfo = getCurrentVideoInfo();

            if (videoInfo) {
                // Check if video is already in playlist
                const existingIndex = state.playlist.findIndex(v => v.id === videoInfo.id);

                if (existingIndex >= 0) {
                    // Video already exists - provide visual feedback
                    addCurrentButton.textContent = 'Already Added';
                    addCurrentButton.style.background = 'rgba(255,255,255,0.2)';

                    // Reset button after a short delay
                    setTimeout(() => {
                        addCurrentButton.innerHTML = `<span style="font-size:14px;">➕</span> Current Video`;
                        addCurrentButton.style.background = 'rgba(255,255,255,0.1)';
                    }, 1500);

                    return;
                }

                // Add to playlist
                state.playlist.push(videoInfo);
                storage.savePlaylist();
                renderPlaylist(elements);

                // Provide visual feedback
                addCurrentButton.textContent = 'Added! ✓';
                addCurrentButton.style.background = 'rgba(0,255,0,0.2)';

                // Reset button after a short delay
                setTimeout(() => {
                    addCurrentButton.innerHTML = `<span style="font-size:14px;">➕</span> Current Video`;
                    addCurrentButton.style.background = 'rgba(255,255,255,0.1)';
                }, 1500);
            } else {
                // Not on a video page or couldn't get info
                addCurrentButton.textContent = 'No Video Found';
                addCurrentButton.style.background = 'rgba(255,0,0,0.2)';

                // Reset button after a short delay
                setTimeout(() => {
                    addCurrentButton.innerHTML = `<span style="font-size:14px;">➕</span> Current Video`;
                    addCurrentButton.style.background = 'rgba(255,255,255,0.1)';
                }, 1500);
            }
        });

        // Update button visibility based on page type
        function updateButtonVisibility() {
            const isVideoPage = window.location.pathname.includes('/watch');
            addCurrentButton.style.opacity = isVideoPage ? '1' : '0.5';
        }

        // Initial visibility update
        updateButtonVisibility();

        // Listen for navigation changes
        const observer = new MutationObserver(() => {
            updateButtonVisibility();
        });

        observer.observe(document.querySelector('head > title'), { subtree: true, characterData: true, childList: true });
    }

    /**
     * Sets up autoplay toggle functionality
     * @param {Object} elements - DOM elements references
     */
    function setupAutoplayToggle(elements) {
        const { toggleButton } = elements;

        toggleButton.addEventListener('click', () => {
            state.autoplayEnabled = !state.autoplayEnabled;
            toggleButton.textContent = state.autoplayEnabled ? "Disable Autoplay" : "Enable Autoplay";
            storage.saveAutoplayState();
        });
    }

    /**
     * Sets up playlist clear button
     * @param {Object} elements - DOM elements references
     */
    function setupClearButton(elements) {
        const { clearButton } = elements;

        clearButton.addEventListener('click', () => {
            state.playlist = [];
            storage.savePlaylist();
            renderPlaylist(elements);
        });
    }

    /**
     * Sets up autoplay functionality for videos
     */
    function setupVideoAutoplay() {
        const observer = new MutationObserver(() => {
            const video = document.querySelector('video');
            if (!video) return;

            video.addEventListener('ended', () => {
                if (!state.autoplayEnabled || state.playlist.length === 0) return;

                const currentUrl = location.href;
                const nextIndex = state.playlist.findIndex(v => currentUrl.includes(v.id));

                if (nextIndex >= 0 && nextIndex < state.playlist.length - 1) {
                    // Add a small delay before navigating to next video
                    setTimeout(() => {
                        window.location.href = state.playlist[nextIndex + 1].url;
                    }, 300);
                } else {
                    state.autoplayEnabled = false;
                    storage.saveAutoplayState();
                    document.querySelector("#toggleAutoplay").textContent = "Enable Autoplay";
                }
            }, { once: true });
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    /**
     * Setup auto-hide functionality when video is playing
     * @param {Object} elements - DOM elements references
     */
    function setupAutoHideOnPlay(elements) {
        const { overlay } = elements;

        // Video play/pause state detection
        const observeVideo = () => {
            const observer = new MutationObserver(() => {
                const video = document.querySelector('video');
                if (!video) return;

                // Setup play/pause event listeners if not already attached
                if (!video.dataset.playlistEventsBound) {
                    video.dataset.playlistEventsBound = "true";

                    // When video plays, dock and collapse after delay
                    video.addEventListener('play', () => {
                        state.videoPlaying = true;
                        // Clear any existing timer
                        if (state.autoHideTimer) {
                            clearTimeout(state.autoHideTimer);
                        }

                        // Set timer to hide after delay
                        state.autoHideTimer = setTimeout(() => {
                            if (!state.isDocked) {
                                state.isDocked = true;
                                storage.saveDockState();
                                applyDockState(elements);
                            }

                            if (!state.collapsed) {
                                toggleCollapse(elements, true); // Force collapse
                            }
                        }, CONFIG.UI.AUTO_HIDE_DELAY);
                    });

                    // When video pauses, show the playlist again
                    video.addEventListener('pause', () => {
                        state.videoPlaying = false;

                        // Clear any hide timer
                        if (state.autoHideTimer) {
                            clearTimeout(state.autoHideTimer);
                            state.autoHideTimer = null;
                        }

                        // Only expand if it was auto-collapsed (while playing)
                        if (state.collapsed && !state.isDragging) {
                            toggleCollapse(elements, false); // Force expand
                        }
                    });
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        };

        // Start observing for video elements
        observeVideo();

        // Also handle navigation events to reattach listeners
        window.addEventListener('yt-navigate-finish', () => {
            // Reset bound state so we can reattach listeners
            const video = document.querySelector('video');
            if (video) {
                delete video.dataset.playlistEventsBound;
            }
            observeVideo();
        });
    }

    /**
     * Sets up handling for YouTube video dragging
     */
    function setupYouTubeDragHandling() {
        document.addEventListener('dragstart', (e) => {
            const videoEl = e.target.closest('ytd-video-renderer, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-playlist-video-renderer');
            if (videoEl) {
                const anchor = videoEl.querySelector('a[href*="/watch?v="]');
                if (!anchor) return;

                const videoUrl = anchor.href;
                const videoId = (new URL(videoUrl)).searchParams.get('v') || '';

                const titleEl = videoEl.querySelector('#video-title, yt-formatted-string#video-title');
                const title = titleEl ? titleEl.textContent.trim() : 'Unknown Title';

                const channelEl = videoEl.querySelector('ytd-channel-name a, .ytd-channel-name #text');
                const channel = channelEl ? channelEl.textContent.trim() : 'Unknown Channel';

                state.currentDraggedVideoInfo = {
                    url: videoUrl,
                    id: videoId,
                    title: title,
                    channel: channel
                };
            }
        });
    }

    /**
     * Sets up synchronization across tabs
     * @param {Object} elements - DOM elements references
     */
    function setupCrossBrowserSync(elements) {
        GM_addValueChangeListener(CONFIG.STORAGE.PLAYLIST, (_, __, newValue) => {
            state.playlist = newValue;
            renderPlaylist(elements);
        });

        GM_addValueChangeListener(CONFIG.STORAGE.AUTOPLAY, (_, __, newValue) => {
            state.autoplayEnabled = newValue;
            elements.toggleButton.textContent = state.autoplayEnabled ? "Disable Autoplay" : "Enable Autoplay";
        });

        GM_addValueChangeListener(CONFIG.STORAGE.POSITION, (_, __, newValue) => {
            if (state.isDocked) return;
            const clamped = clampPosition(newValue, elements);
            elements.overlay.style.left = `${clamped.left}px`;
            elements.overlay.style.top = `${clamped.top}px`;
        });

        GM_addValueChangeListener(CONFIG.STORAGE.DOCKED, (_, __, newValue) => {
            state.isDocked = newValue;
            applyDockState(elements);
        });
    }

    /**
     * Handles window resize events
     * @param {Object} elements - DOM elements references
     */
    function setupWindowEvents(elements) {
        const { overlay } = elements;

        // Handle window resize
        window.addEventListener('resize', () => {
            if (state.isDocked) return;
            const clamped = clampPosition({ left: overlay.offsetLeft, top: overlay.offsetTop }, elements);
            overlay.style.left = `${clamped.left}px`;
            overlay.style.top = `${clamped.top}px`;
            storage.savePosition(clamped.left, clamped.top);
        });

        // Hide overlay during fullscreen video
        document.addEventListener('fullscreenchange', () => {
            const isFullscreen = !!document.fullscreenElement;
            overlay.style.display = isFullscreen ? 'none' : 'flex';
        });
    }

    /**
     * Restores the overlay position from saved state
     * @param {Object} elements - DOM elements references
     */
    function restorePosition(elements) {
        const { overlay } = elements;
        const { savedPos } = state;

        if (!state.isDocked && savedPos.left !== null && savedPos.top !== null) {
            const clamped = clampPosition(savedPos, elements);
            overlay.style.left = `${clamped.left}px`;
            overlay.style.top = `${clamped.top}px`;
            overlay.style.bottom = 'unset';
            overlay.style.right = 'unset';
        }
    }

    /**
     * Main initialization function
     */
    function init() {
        // Setup UI
        injectStyles();
        const elements = createInterface();

        // Apply initial state
        applyDockState(elements);
        renderPlaylist(elements);
        restorePosition(elements);

        // Set initial autoplay button text
        elements.toggleButton.textContent = state.autoplayEnabled ? "Disable Autoplay" : "Enable Autoplay";

        // Setup event handlers
        setupOverlayDrag(elements);
        setupCollapseButton(elements);
        setupPlaylistDrop(elements);
        setupPlaylistDelete(elements);
        setupCurrentVideoButton(elements);
        setupAutoplayToggle(elements);
        setupClearButton(elements);
        setupDockButton(elements);
        setupVideoAutoplay();
        setupAutoHideOnPlay(elements);
        setupYouTubeDragHandling();
        setupCrossBrowserSync(elements);
        setupWindowEvents(elements);
    }

    // Start everything
    init();
})();