Better Theater Mode for YouTube

Improves YouTube's theater mode with a Twitch.tv-like design, enhancing video and chat layouts, while maintaining performance and compatibility. Also fixes the broken fullscreen UI from the recent YouTube update.

Verze ze dne 13. 04. 2025. Zobrazit nejnovější verzi.

// ==UserScript==
// @name                Better Theater Mode for YouTube
// @name:zh-TW          更佳 YouTube 劇場模式
// @name:zh-CN          更佳 YouTube 剧场模式
// @name:ja             より良いYouTubeシアターモード
// @icon                https://www.youtube.com/img/favicon_48.png
// @author              ElectroKnight22
// @namespace           electroknight22_youtube_better_theater_mode_namespace
// @version             1.11.1
// @match               *://www.youtube.com/*
// @match               *://www.youtube-nocookie.com/*
// @grant               GM.getValue
// @grant               GM.setValue
// @grant               GM.deleteValue
// @grant               GM.listValues
// @grant               GM.registerMenuCommand
// @grant               GM.unregisterMenuCommand
// @grant               GM.notification
// @noframes
// @license             MIT
// @description         Improves YouTube's theater mode with a Twitch.tv-like design, enhancing video and chat layouts, while maintaining performance and compatibility. Also fixes the broken fullscreen UI from the recent YouTube update.
// @description:zh-TW   改善 YouTube 劇場模式,參考 Twitch.tv 的設計,增強影片與聊天室佈局,同時維持效能與相容性。並修復近期 YouTube 更新中損壞的全螢幕介面。
// @description:zh-CN   改进 YouTube 剧场模式,参考 Twitch.tv 的设计,增强视频与聊天室布局,同时保持性能与兼容性。并修复近期 YouTube 更新中损坏的全屏界面。
// @description:ja      YouTubeのシアターモードを改善し、Twitch.tvのデザインを参考にして、動画とチャットのレイアウトを強化しつつ、パフォーマンスと互換性を維持します。また、最近のYouTubeアップデートによる壊れたフルスクリーンUIを修正します。
// ==/UserScript==

/*jshint esversion: 11 */
(function () {
    "use strict";

    // CONFIG AND CONSTANTS
    const CONFIG = {
        // UI Constants
        MIN_CHAT_SIZE: { width: '300px', height: '355px' },
        DRAG_BAR_HEIGHT: '35px',

        // Default settings
        DEFAULT_SETTINGS: {
            isScriptActive: true,
            isSimpleMode: true,
            enableOnlyForLiveStreams: false,
            modifyVideoPlayer: true,
            modifyChat: true,
            setLowHeadmast: false,
            useCustomPlayerHeight: false,
            floatingChat: false,
            chatOpacity: '0.95',
            chatOffset: { left: '0px', top: '-500px' },
            chatSize: { width: '300px', height: '355px' },
            debug: false
        },

        // Default empty blacklist
        DEFAULT_BLACKLIST: [],

        // Version requirements
        REQUIRED_VERSIONS: {
            Tampermonkey: '5.4.624'
        }
    };

    // TRANSLATIONS
    const BROWSER_LANGUAGE = navigator.language || navigator.userLanguage;

    function getPreferredLanguage() {
        if (BROWSER_LANGUAGE.startsWith('zh') && BROWSER_LANGUAGE !== 'zh-TW') {
            return 'zh-CN';
        }
        // Check if language is supported, otherwise fall back to English
        return ['en-US', 'zh-TW', 'zh-CN', 'ja'].includes(BROWSER_LANGUAGE)
            ? BROWSER_LANGUAGE
            : 'en-US';
    }

    const TRANSLATIONS = {
        'en-US': {
            tampermonkeyOutdatedAlert: "It looks like you're using an older version of Tampermonkey that might cause menu issues. For the best experience, please update to version 5.4.6224 or later.",
            turnOn: 'Turn On',
            turnOff: 'Turn Off',
            livestreamOnlyMode: 'Livestream Only Mode',
            applyChatStyles: 'Apply Chat Styles',
            applyVideoPlayerStyles: 'Apply Video Player Styles',
            moveHeadmastBelowVideoPlayer: 'Move Headmast Below Video Player',
            useCustomPlayerHeight: 'Use Custom Player Height',
            playerHeightText: 'Player Height',
            floatingChat: 'Floating Chat',
            blacklistVideo: 'Blacklist Video',
            unblacklistVideo: 'Unblacklist Video',
            simpleMode: 'Simple Mode',
            advancedMode: 'Advanced Mode',
            debug: 'DEBUG'
        },
        'zh-TW': {
            tampermonkeyOutdatedAlert: "看起來您正在使用較舊版本的篡改猴,可能會導致選單問題。為了獲得最佳體驗,請更新至 5.4.6224 或更高版本。",
            turnOn: '開啟',
            turnOff: '關閉',
            livestreamOnlyMode: '僅限直播模式',
            applyChatStyles: '套用聊天樣式',
            applyVideoPlayerStyles: '套用影片播放器樣式',
            moveHeadmastBelowVideoPlayer: '將頁首橫幅移到影片播放器下方',
            useCustomPlayerHeight: '使用自訂播放器高度',
            playerHeightText: '播放器高度',
            floatingChat: '浮動聊天室',
            blacklistVideo: '將影片加入黑名單',
            unblacklistVideo: '從黑名單中移除影片',
            simpleMode: '簡易模式',
            advancedMode: '進階模式',
            debug: '偵錯'
        },
        'zh-CN': {
            tampermonkeyOutdatedAlert: "看起来您正在使用旧版本的篡改猴,这可能会导致菜单问题。为了获得最佳体验,请更新到 5.4.6224 或更高版本。",
            turnOn: '开启',
            turnOff: '关闭',
            livestreamOnlyMode: '仅限直播模式',
            applyChatStyles: '应用聊天样式',
            applyVideoPlayerStyles: '应用视频播放器样式',
            moveHeadmastBelowVideoPlayer: '将页首横幅移动到视频播放器下方',
            useCustomPlayerHeight: '使用自定义播放器高度',
            playerHeightText: '播放器高度',
            floatingChat: '浮动聊天室',
            blacklistVideo: '将视频加入黑名单',
            unblacklistVideo: '从黑名单中移除视频',
            simpleMode: '简易模式',
            advancedMode: '高级模式',
            debug: '调试'
        },
        'ja': {
            tampermonkeyOutdatedAlert: "ご利用のTampermonkeyのバージョンが古いため、メニューに問題が発生する可能性があります。より良い体験のため、バージョン5.4.6224以上に更新してください。",
            turnOn: "オンにする",
            turnOff: "オフにする",
            livestreamOnlyMode: "ライブ配信専用モード",
            applyChatStyles: "チャットスタイルを適用",
            applyVideoPlayerStyles: "ビデオプレイヤースタイルを適用",
            moveHeadmastBelowVideoPlayer: "ヘッドマストをビデオプレイヤーの下に移動",
            useCustomPlayerHeight: "カスタムプレイヤーの高さを使用",
            playerHeightText: "プレイヤーの高さ",
            floatingChat: "フローティングチャット",
            blacklistVideo: "動画をブラックリストに追加",
            unblacklistVideo: "ブラックリストから動画を解除",
            simpleMode: "シンプルモード",
            advancedMode: "高度モード",
            debug: "デバッグ"
        }
    };

    function getLocalizedText() {
        return TRANSLATIONS[getPreferredLanguage()] || TRANSLATIONS['en-US'];
    }

    // STATE VARIABLES
    const state = {
        userSettings: { ...CONFIG.DEFAULT_SETTINGS },
        advancedSettingsBackup: null,
        blacklist: new Set(),
        useCompatibilityMode: false,
        menuItems: new Set(),
        activeStyles: new Map(),
        resizeObserver: null,
        moviePlayer: null,
        videoId: null,
        chatFrame: null,
        currentPageType: '',
        isFullscreen: false,
        isTheaterMode: false,
        chatCollapsed: true,
        isLiveStream: false,
        chatWidth: 0,
        moviePlayerHeight: 0,
        isOldTampermonkey: false,
        isScriptRecentlyUpdated: false
    };

    // GM API COMPATIBILITY
    const GM = {
        registerMenuCommand: state.useCompatibilityMode ? GM_registerMenuCommand : window.GM?.registerMenuCommand,
        unregisterMenuCommand: state.useCompatibilityMode ? GM_unregisterMenuCommand : window.GM?.unregisterMenuCommand,
        getValue: state.useCompatibilityMode ? GM_getValue : window.GM?.getValue,
        setValue: state.useCompatibilityMode ? GM_setValue : window.GM?.setValue,
        listValues: state.useCompatibilityMode ? GM_listValues : window.GM?.listValues,
        deleteValue: state.useCompatibilityMode ? GM_deleteValue : window.GM?.deleteValue,
        notification: state.useCompatibilityMode ? GM_notification : window.GM?.notification
    };

    // STYLE DEFINITIONS
    const styleRules = {
        chatStyle: {
            id: "betterTheater-chatStyle",
            getRule: () => `
                ytd-live-chat-frame[theater-watch-while][rounded-container] {
                    border-radius: 0 !important;
                    border-top: 0 !important;
                }
                ytd-watch-flexy[fixed-panels] #chat.ytd-watch-flexy {
                    top: 0 !important;
                    border-top: 0 !important;
                    border-bottom: 0 !important;
                }
            `,
        },

        videoPlayerStyle: {
            id: "betterTheater-videoPlayerStyle",
            getRule: () => {
                if (state.userSettings.useCustomPlayerHeight) {
                    return `
                        ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy {
                            min-height: 0px !important;
                            height: ${state.userSettings.playerHeightPx}px !important;
                        }
                    `;
                } else {
                    return `
                        ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy {
                            max-height: calc(100vh - var(--ytd-watch-flexy-masthead-height)) !important;
                        }
                    `;
                }
            }
        },

        headmastStyle: {
            id: "betterTheater-headmastStyle",
            getRule: () => `
                #masthead-container.ytd-app {
                    max-width: calc(100% - ${state.chatWidth}px) !important;
                }
            `,
        },

        lowHeadmastStyle: {
            id: "betterTheater-lowHeadmastStyle",
            getRule: () => `
                #page-manager.ytd-app {
                    margin-top: 0 !important;
                    top: calc(-1 * var(--ytd-toolbar-offset)) !important;
                    position: relative !important;
                }
                ytd-watch-flexy[flexy]:not([full-bleed-player][full-bleed-no-max-width-columns]) #columns.ytd-watch-flexy {
                    margin-top: var(--ytd-toolbar-offset) !important;
                }
                ${state.userSettings.modifyVideoPlayer ? `
                    ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy {
                        max-height: 100vh !important;
                    }
                ` : ''}
                #masthead-container.ytd-app {
                    z-index: 599 !important;
                    top: ${state.moviePlayerHeight}px !important;
                    position: relative !important;
                }
            `,
        },

        videoPlayerFixStyle: {
            id: "betterTheater-videoPlayerFixStyle",
            getRule: () => `
                .html5-video-container {
                    top: -1px !important;
                }
                #skip-navigation.ytd-masthead {
                    left: -500px;
                }
            `,
        },

        chatFrameFixStyle: {
            id: "betterTheater-chatFrameFixStyle",
            getRule: () => {
                const chatInputContainer = document.querySelector(
                    "tp-yt-iron-pages#panel-pages.style-scope.yt-live-chat-renderer"
                );
                const shouldHideChatInputContainerTopBorder =
                    chatInputContainer?.clientHeight === 0;
                const borderTopStyle = shouldHideChatInputContainerTopBorder
                    ? 'border-top: 0 !important;'
                    : '';

                return `
                    #panel-pages.yt-live-chat-renderer {
                        ${borderTopStyle}
                        border-bottom: 0 !important;
                    }
                `;
            },
        },

        chatRendererFixStyle: {
            id: "betterTheater-chatRendererFixStyle",
            getRule: () => `
                ytd-live-chat-frame[theater-watch-while][rounded-container] {
                    border-bottom: 0 !important;
                }
            `,
        },

        floatingChatStyle: {
            id: "betterTheater-floatingChatStyle",
            getRule: () => `
                #chat-container {
                    min-width: ${CONFIG.MIN_CHAT_SIZE.width} !important;
                    max-width: 100vw !important;
                    max-height: 100vh !important;
                    position: absolute;
                    border-radius: 0 0 10px 10px !important;
                }
                #chat {
                    top: ${CONFIG.DRAG_BAR_HEIGHT} !important;
                    height: calc(100% - ${CONFIG.DRAG_BAR_HEIGHT}) !important;
                    width: inherit !important;
                    min-width: inherit !important;
                    max-width: inherit !important;
                    min-height: ${parseInt(CONFIG.MIN_CHAT_SIZE.height) - parseInt(CONFIG.DRAG_BAR_HEIGHT)}px !important;
                    max-height: 100vh !important;
                }
            `,
        },

        floatingChatStyleExpanded: {
            id: "betterTheater-floatingChatStyleExpanded",
            getRule: () => `
                #chat-container {
                    min-height: ${CONFIG.MIN_CHAT_SIZE.height} !important;
                }
                ytd-live-chat-frame:not([theater-watch-while])[rounded-container] {
                    border-top-left-radius: 0 !important;
                    border-top-right-radius: 0 !important;
                    border-top: 0 !important;
                }
                ytd-live-chat-frame:not([theater-watch-while])[rounded-container] iframe.ytd-live-chat-frame {
                    border-top-left-radius: 0 !important;
                    border-top-right-radius: 0 !important;
                }
            `,
        },

        floatingChatStyleCollapsed: {
            id: "betterTheater-floatingChatStyleCollapsed",
            getRule: () => `
                ytd-live-chat-frame[round-background] #show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame,
                ytd-live-chat-frame[round-background] #show-hide-button.ytd-live-chat-frame>ytd-button-renderer.ytd-live-chat-frame {
                    margin: 0 !important;
                    border-radius: 0 0 12px 12px !important;
                    border-left: 1px solid var(--yt-spec-10-percent-layer) !important;
                    border-right: 1px solid var(--yt-spec-10-percent-layer) !important;
                    border-bottom: 1px solid var(--yt-spec-10-percent-layer) !important;
                    background-clip: padding-box !important;
                }
                ytd-live-chat-frame[modern-buttons][collapsed] {
                    border-radius: 0 0 12px 12px !important;
                }
                button.yt-spec-button-shape-next.yt-spec-button-shape-next--outline.yt-spec-button-shape-next--mono.yt-spec-button-shape-next--size-m {
                    border-radius: 0 0 12px 12px !important;
                    border: none !important;
                }
                .chat-resize-handle {
                    visibility: hidden !important;
                }
            `,
        },

        debugResizeHandleStyle: {
            id: "betterTheater-debugResizeHandleStyle",
            getRule: () => `
                /* Default state for resize handles */
                #chat-container .chat-resize-handle {
                    background: transparent;
                    opacity: 0;
                }
                /* Debug state for resize handles when #chat-container has [debug] attribute */
                #chat-container[debug] .chat-resize-handle {
                    opacity: 0.5;
                }
                #chat-container[debug] .chat-resize-handle.rs-right {
                    background: rgba(255, 0, 0, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-left {
                    background: rgba(0, 255, 0, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-bottom {
                    background: rgba(0, 0, 255, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-top {
                    background: rgba(255, 255, 0, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-bottom-left {
                    background: rgba(0, 255, 255, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-top-left {
                    background: rgba(255, 255, 0, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-top-right {
                    background: rgba(255, 0, 0, 0.5);
                }
                #chat-container[debug] .chat-resize-handle.rs-bottom-right {
                    background: rgba(255, 0, 255, 0.5);
                }
            `,
        },

        chatSliderStyle: {
            id: "betterTheater-chatSliderStyle",
            getRule: () => `
                .chat-drag-bar input[type=range] {
                    -webkit-appearance: none;
                    width: 100px;
                    height: 4px;
                    background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
                    border-radius: 2px;
                    outline: none;
                }
                .chat-drag-bar input[type=range]::-webkit-slider-thumb {
                    -webkit-appearance: none;
                    appearance: none;
                    width: 14px;
                    height: 14px;
                    border-radius: 50%;
                    background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
                    cursor: pointer;
                }
                .chat-drag-bar input[type=range]::-moz-range-thumb {
                    width: 14px;
                    height: 14px;
                    border-radius: 50%;
                    background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
                    cursor: pointer;
                }
                .chat-drag-bar input[type=range]::-moz-range-track {
                    background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
                    height: 4px;
                    border-radius: 2px;
                }
                .chat-drag-bar input[type=range]::-ms-thumb {
                    background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
                }
                .chat-drag-bar input[type=range]::-ms-track {
                    background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
                    height: 4px;
                    border-radius: 2px;
                }
            `
        }
    };

    // STYLE MANAGEMENT
    function applyStyle(style, setPersistent = false) {
        if (typeof style.getRule !== 'function') return;
        if (state.activeStyles.has(style.id)) {
            removeStyle(style);
        }

        const styleElement = document.createElement('style');
        styleElement.id = style.id;
        styleElement.type = 'text/css';
        styleElement.textContent = style.getRule();
        (document.head || document.documentElement).appendChild(styleElement);
        state.activeStyles.set(style.id, {
            element: styleElement,
            persistent: setPersistent
        });
    }

    function removeStyle(style) {
        if (!state.activeStyles.has(style.id)) return;

        const { element: styleElement } = state.activeStyles.get(style.id);
        if (styleElement && styleElement.parentNode) {
            styleElement.parentNode.removeChild(styleElement);
        }

        state.activeStyles.delete(style.id);
    }

    function removeAllStyles() {
        state.activeStyles.forEach((styleData, styleId) => {
            if (!styleData.persistent) {
                removeStyle({ id: styleId });
            }
        });
    }

    function setStyleState(style, on = true) {
        on ? applyStyle(style) : removeStyle(style);
    }

    // RESIZE HANDLES
    function isAtMinSize(element) {
        const style = window.getComputedStyle(element);
        const width = parseFloat(style.width);
        const height = parseFloat(style.height);
        const minWidth = parseFloat(style.minWidth);
        const minHeight = parseFloat(style.minHeight);

        return {
            isAtMinWidth: width <= minWidth,
            isAtMinHeight: height <= minHeight
        };
    }

    function addResizeHandles(chatContainer) {
        const handleConfigs = {
            right: {
                width: "6px", top: "0", right: "0", bottom: "0",
                cursor: "ew-resize", horizontal: true, vertical: false
            },
            left: {
                width: "6px", top: "0", left: "0", bottom: "0",
                cursor: "ew-resize", horizontal: true, vertical: false
            },
            bottom: {
                height: "6px", left: "0", bottom: "0", right: "0",
                cursor: "ns-resize", horizontal: false, vertical: true
            },
            top: {
                height: "6px", left: "0", top: "0", right: "0",
                cursor: "ns-resize", horizontal: false, vertical: true
            },
            bottomLeft: {
                width: "12px", height: "12px", left: "0", bottom: "0",
                cursor: "nesw-resize", horizontal: true, vertical: true
            },
            topLeft: {
                width: "12px", height: "12px", left: "0", top: "0",
                cursor: "nwse-resize", horizontal: true, vertical: true
            },
            topRight: {
                width: "12px", height: "12px", right: "0", top: "0",
                cursor: "nesw-resize", horizontal: true, vertical: true
            },
            bottomRight: {
                width: "12px", height: "12px", right: "0", bottom: "0",
                cursor: "nwse-resize", horizontal: true, vertical: true
            }
        };

        const handles = {};
        for (const [position, config] of Object.entries(handleConfigs)) {
            const handle = document.createElement("div");
            handle.className = `chat-resize-handle rs-${position}`;
            handle.style.position = "absolute";
            handle.style.zIndex = "10001";
            Object.assign(handle.style, config);
            chatContainer.appendChild(handle);
            handles[position] = handle;
            initResizeHandler(handle, config);
        }
        return handles;

        function initResizeHandler(handle, config) {
            let startX, startY, startWidth, startHeight, startLeft, startTop;
            async function saveChatSize() {
                state.userSettings.chatSize = {
                    width: chatContainer.style.width,
                    height: chatContainer.style.height
                };
                state.userSettings.chatOffset = {
                    left: chatContainer.style.left,
                    top: chatContainer.style.top
                };
                await updateSetting('chatSize', state.userSettings.chatSize);
                await updateSetting('chatOffset', state.userSettings.chatOffset);
            }

            handle.addEventListener("pointerdown", function (e) {
                if (e.pointerType === "mouse" && e.button !== 0) return;
                e.preventDefault();
                startX = e.clientX;
                startY = e.clientY;
                startWidth = chatContainer.offsetWidth;
                startHeight = chatContainer.offsetHeight;
                startLeft = parseFloat(getComputedStyle(chatContainer).left) || 0;
                startTop = parseFloat(getComputedStyle(chatContainer).top) || 0;

                handle.setPointerCapture(e.pointerId);
            });

            handle.addEventListener("pointermove", function (e) {
                if (!handle.hasPointerCapture(e.pointerId)) return;
                e.preventDefault();

                const movieRect = state.moviePlayer.getBoundingClientRect();
                const chatParentRect = chatContainer.parentElement.getBoundingClientRect();
                const chatRect = chatContainer.getBoundingClientRect();
                const minWidth = parseInt(CONFIG.MIN_CHAT_SIZE.width);
                const minHeight = parseInt(CONFIG.MIN_CHAT_SIZE.height);
                let dx = e.clientX - startX;
                let dy = e.clientY - startY;
                if (dx === 0 && dy === 0) return;
                dx = Math.max(-startX, Math.min(dx, movieRect.right - startX));
                dy = Math.max(-startY, Math.min(dy, movieRect.bottom - startY));

                const tooSmall = isAtMinSize(chatContainer);

                if (config.horizontal) {
                    console.log('horizontal');
                    const isRightSide = handle.className.toLowerCase().includes('right');
                    const isLeftSide = handle.className.toLowerCase().includes('left');
                    if (!isRightSide && !isLeftSide) {
                        console.log(handle.className);
                    }
                    if (isRightSide) {
                        if (!tooSmall.isAtMinWidth || dx > 0) {
                            if (startWidth + dx < minWidth) dx = minWidth - startWidth;
                            if (chatRect.left + startWidth + dx > movieRect.right) {
                                dx = movieRect.right - chatRect.left - startWidth;
                            }
                            chatContainer.style.width = Math.max(minWidth, startWidth + dx) + "px";
                        }
                    } else if (isLeftSide) {
                        if (!tooSmall.isAtMinWidth || dx < 0) {
                            if (startWidth - dx < minWidth) dx = startWidth - minWidth;
                            if (startLeft + chatParentRect.left + dx < movieRect.left) {
                                dx = movieRect.left - startLeft - chatParentRect.left;
                            }
                            chatContainer.style.width = Math.max(minWidth, startWidth - dx) + "px";
                            chatContainer.style.left = (startLeft + dx) + "px";
                        }
                    }
                }

                if (config.vertical) {
                    console.log('vertical');
                    const isBottomSide = handle.className.toLowerCase().includes('bottom');
                    const isTopSide = handle.className.toLowerCase().includes('top');

                    if (isBottomSide) {
                        if (!tooSmall.isAtMinHeight || dy > 0) {
                            if (startHeight + dy < minHeight) dy = minHeight - startHeight;
                            if (chatRect.top + startHeight + dy > movieRect.bottom) {
                                dy = movieRect.bottom - chatRect.top - startHeight;
                            }
                            chatContainer.style.height = Math.max(minHeight, startHeight + dy) + "px";
                        }
                    } else if (isTopSide) {
                        if (!tooSmall.isAtMinHeight || dy < 0) {
                            if (startHeight - dy < minHeight) dy = startHeight - minHeight;
                            if (startTop + chatParentRect.top + dy < movieRect.top) {
                                dy = movieRect.top - startTop - chatParentRect.top;
                            }
                            chatContainer.style.height = Math.max(minHeight, startHeight - dy) + "px";
                            chatContainer.style.top = (startTop + dy) + "px";
                        }
                    }
                }
            });

            handle.addEventListener("pointerup", function (e) {
                handle.releasePointerCapture(e.pointerId);
                saveChatSize();
            });
        }
    }

    // Removes all resize handles from a container
    function removeResizeHandles(chatContainer) {
        if (!chatContainer) return;
        const handles = chatContainer.querySelectorAll(".chat-resize-handle");
        handles.forEach(handle => handle.remove());
    }

    // DRAG BAR & CHAT CONTROLS
    function addDragBarWithOpacitySlider(chatContainer) {
        let existingBar = chatContainer.querySelector('.chat-drag-bar');
        if (existingBar) return existingBar;

        // Add chat slider style if not present
        applyStyle(styleRules.chatSliderStyle, true);

        const dragBar = document.createElement("div");
        dragBar.className = "chat-drag-bar";
        dragBar.style.position = "absolute";
        dragBar.style.top = "0";
        dragBar.style.left = "0";
        dragBar.style.right = "0";
        dragBar.style.height = "15px";
        dragBar.style.background = "var(--yt-live-chat-background-color)";
        dragBar.style.color = "var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color))";
        dragBar.style.border = "1px solid var(--yt-spec-10-percent-layer)";
        dragBar.style.backgroundClip = "padding-box";
        dragBar.style.display = "flex";
        dragBar.style.alignItems = "center";
        dragBar.style.justifyContent = "space-between";
        dragBar.style.padding = (parseInt(CONFIG.DRAG_BAR_HEIGHT) - 15) / 2 + "px";
        dragBar.style.zIndex = "10000";
        dragBar.style.borderRadius = "12px 12px 0 0";

        const dragLabel = document.createElement("div");
        dragLabel.innerText = "⋮⋮";
        dragLabel.style.fontSize = "var(--yt-live-chat-header-font-size, 18px)";
        dragLabel.style.userSelect = "none";

        const opacitySlider = document.createElement("input");
        opacitySlider.type = "range";
        opacitySlider.min = "20";
        opacitySlider.max = "100";
        opacitySlider.value = Math.round(parseFloat(state.userSettings.chatOpacity) * 100).toString();
        opacitySlider.style.marginLeft = "10px";

        opacitySlider.addEventListener("input", () => {
            const newOpacity = opacitySlider.value / 100;
            chatContainer.style.opacity = newOpacity;
        });

        opacitySlider.addEventListener("mouseup", () => {
            updateSetting('chatOpacity', chatContainer.style.opacity);
        });

        ["pointerdown", "pointermove", "pointerup"].forEach(eventType => {
            opacitySlider.addEventListener(eventType, (e) => {
                e.stopPropagation();
            });
        });

        dragBar.appendChild(dragLabel);
        dragBar.appendChild(opacitySlider);
        chatContainer.insertBefore(dragBar, chatContainer.firstChild);

        // Add drag functionality
        setupDragBehavior(dragBar, chatContainer);

        return dragBar;
    }

    function setupDragBehavior(dragBar, chatContainer) {
        let dragging = false;
        let startX = 0, startY = 0;
        let containerStartLeft = 0;
        let containerStartTop = 0;
        const dragThreshold = 5;

        async function saveChatPosition() {
            state.userSettings.chatOffset = {
                left: chatContainer.style.left,
                top: chatContainer.style.top
            };
            await updateSetting('chatOffset', state.userSettings.chatOffset);
        }

        // Handle pointer down event
        dragBar.addEventListener("pointerdown", function (e) {
            if (e.pointerType === "mouse" && e.button !== 0) return;

            dragging = false;
            startX = e.clientX;
            startY = e.clientY;
            containerStartLeft = parseFloat(getComputedStyle(chatContainer).left) || 0;
            containerStartTop = parseFloat(getComputedStyle(chatContainer).top) || 0;

            dragBar.setPointerCapture(e.pointerId);
            e.preventDefault();
        });

        // Handle pointer move event
        dragBar.addEventListener("pointermove", function (e) {
            if (!dragBar.hasPointerCapture(e.pointerId)) return;

            let dx = e.clientX - startX;
            let dy = e.clientY - startY;

            if (dx === 0 && dy === 0) return;

            // Start dragging after threshold is crossed
            if (!dragging && Math.hypot(dx, dy) > dragThreshold) {
                dragging = true;
            }

            if (dragging) {
                // Calculate new position
                let newLeft = containerStartLeft + dx;
                let newTop = containerStartTop + dy;

                // Get boundaries
                const movieRect = state.moviePlayer.getBoundingClientRect();
                const chatParentRect = chatContainer.parentElement.getBoundingClientRect();

                // Constrain to left edge
                if (newLeft + chatParentRect.left < 0) {
                    newLeft = -chatParentRect.left;
                }

                // Constrain to top edge
                if (newTop + chatParentRect.top < 0) {
                    newTop = -chatParentRect.top;
                }

                // Constrain to right edge
                if (newLeft > movieRect.right - (chatParentRect.left + chatContainer.offsetWidth)) {
                    newLeft = movieRect.right - (chatParentRect.left + chatContainer.offsetWidth);
                }

                // Constrain to bottom edge, accounting for chat collapsed state
                if (state.chatCollapsed) {
                    const showHideButton = chatContainer.querySelector('#show-hide-button');
                    if (showHideButton && newTop > movieRect.bottom - (chatParentRect.top + showHideButton.offsetHeight + dragBar.offsetHeight)) {
                        newTop = movieRect.bottom - (chatParentRect.top + showHideButton.offsetHeight + dragBar.offsetHeight);
                    }
                } else {
                    if (newTop > movieRect.bottom - (chatParentRect.top + chatContainer.offsetHeight)) {
                        newTop = movieRect.bottom - (chatParentRect.top + chatContainer.offsetHeight);
                    }
                }

                // Apply new position
                chatContainer.style.left = newLeft + "px";
                chatContainer.style.top = newTop + "px";

                e.preventDefault();
            }
        });

        // Handle pointer up event
        dragBar.addEventListener("pointerup", function (e) {
            dragBar.releasePointerCapture(e.pointerId);
            dragging = false;
            saveChatPosition();
        });
    }

    // Removes drag bar from chat container
    function removeDragBarWithOpacitySlider(chatContainer) {
        if (!chatContainer) return;
        const dragBar = chatContainer.querySelector('.chat-drag-bar');
        if (dragBar) dragBar.remove();
    }

    // Removes all chat-related styles from container
    function removeAllChatStyles(chatContainer) {
        removeStyle(styleRules.floatingChatStyleCollapsed);
        removeStyle(styleRules.floatingChatStyleExpanded);
        removeStyle(styleRules.floatingChatStyle);

        if (chatContainer) chatContainer.style = '';
    }

    /**
     * Applies saved chat style to container
     * @param {HTMLElement} chatContainer - The chat container
     */
    function applySavedChatStyle(chatContainer) {
        if (!chatContainer) return;

        const chatPrison = chatContainer.parentElement.getBoundingClientRect();

        // Apply width
        if (state.userSettings.chatSize?.width) {
            chatContainer.style.width = Math.min(
                window.innerWidth,
                parseFloat(state.userSettings.chatSize.width)
            ) + 'px';
        }

        // Apply height
        if (state.userSettings.chatSize?.height) {
            chatContainer.style.height = Math.min(
                window.innerHeight,
                parseFloat(state.userSettings.chatSize.height)
            ) + 'px';
        }

        // Apply left position
        if (state.userSettings.chatOffset?.left !== undefined) {
            const leftPos = parseFloat(state.userSettings.chatOffset.left);
            chatContainer.style.left = Math.min(
                Math.max(leftPos, chatPrison.left),
                window.innerWidth - chatPrison.width
            ) + 'px';
        }

        // Apply top position
        if (state.userSettings.chatOffset?.top !== undefined) {
            const topPos = parseFloat(state.userSettings.chatOffset.top);
            chatContainer.style.top = Math.min(
                Math.max(topPos, chatPrison.top),
                window.innerHeight - chatPrison.height
            ) + 'px';
        }

        // Apply opacity
        chatContainer.style.opacity = parseFloat(state.userSettings.chatOpacity);
    }

    // STYLE UPDATE FUNCTIONS
    /**
     * Updates styles based on current settings and state
     */
    function updateStyles() {
        try {
            // Enforce dependency: custom player height requires modifying player
            if (state.userSettings.useCustomPlayerHeight) {
                state.userSettings.modifyVideoPlayer = true;
            }

            // Check if script should be disabled
            const shouldNotActivate =
                !state.userSettings.isScriptActive ||
                (state.blacklist && state.blacklist.has(state.videoId)) ||
                (state.userSettings.enableOnlyForLiveStreams && !state.isLiveStream);

            if (shouldNotActivate) {
                removeAllStyles();
                if (state.moviePlayer && state.moviePlayer.setCenterCrop) {
                    state.moviePlayer.setCenterCrop();
                }
                return;
            }

            // Apply main styles based on settings
            setStyleState(styleRules.chatStyle, state.userSettings.modifyChat);
            setStyleState(styleRules.videoPlayerStyle, state.userSettings.modifyVideoPlayer);

            // Update header styles
            updateHeadmastStyle();

            // Reset player crop if needed
            if (state.moviePlayer && state.moviePlayer.setCenterCrop) {
                state.moviePlayer.setCenterCrop();
            }
        } catch (error) {
            logDebug(`Error when updating styles: ${error}`, 'error');
        }
    }

    /**
     * Updates the headmast styles based on current state
     */
    function updateHeadmastStyle() {
        updateLowHeadmastStyle();

        // Determine if headmast should be shrunk to account for chat
        const shouldShrinkHeadmast =
            state.isTheaterMode &&
            state.chatFrame?.getAttribute('theater-watch-while') === '' &&
            (state.userSettings.setLowHeadmast || state.userSettings.modifyChat);

        // Update chat width for style calculation
        state.chatWidth = state.chatFrame?.offsetWidth || 0;

        // Apply or remove headmast style
        setStyleState(styleRules.headmastStyle, shouldShrinkHeadmast);
    }

    /**
     * Updates low headmast style based on current state
     */
    function updateLowHeadmastStyle() {
        if (!state.moviePlayer) return;

        const shouldApplyLowHeadmast =
            state.userSettings.setLowHeadmast &&
            state.isTheaterMode &&
            !state.isFullscreen &&
            state.currentPageType === 'watch';

        setStyleState(styleRules.lowHeadmastStyle, shouldApplyLowHeadmast);
    }

    /**
     * Updates floating chat styles based on fullscreen state
     */
    function updateFullscreenFloatingChatStyle() {
        try {
            const chatContainer = document.querySelector('#chat-container');

            // Update collapsed/expanded styles based on chat state
            setStyleState(styleRules.floatingChatStyleCollapsed,
                state.chatCollapsed && state.isFullscreen);
            setStyleState(styleRules.floatingChatStyleExpanded,
                !state.chatCollapsed && state.isFullscreen);
            setStyleState(styleRules.floatingChatStyle, state.isFullscreen);

            // Configure floating chat if needed
            if (state.userSettings.isScriptActive &&
                state.userSettings.floatingChat &&
                state.isLiveStream &&
                chatContainer &&
                state.isFullscreen) {

                applySavedChatStyle(chatContainer);
                removeDragBarWithOpacitySlider(chatContainer);
                addDragBarWithOpacitySlider(chatContainer);
                addResizeHandles(chatContainer);

                // Apply saved settings
                chatContainer.style.opacity = parseFloat(state.userSettings.chatOpacity);
                chatContainer.style.width = Math.min(
                    window.innerWidth,
                    parseFloat(state.userSettings.chatSize.width)
                ) + 'px';
                chatContainer.style.height = Math.min(
                    window.innerHeight,
                    parseFloat(state.userSettings.chatSize.height)
                ) + 'px';
                chatContainer.style.left = parseFloat(state.userSettings.chatOffset.left) + 'px';
                chatContainer.style.top = parseFloat(state.userSettings.chatOffset.top) + 'px';
            } else if (chatContainer) {
                // Remove chat modifications if not needed
                removeAllChatStyles(chatContainer);
                removeDragBarWithOpacitySlider(chatContainer);
                removeResizeHandles(chatContainer);
            }
        } catch (error) {
            logDebug(`Error when updating fullscreen chat styles: ${error}`, 'error');
        }
    }

    /**
     * Updates debug visual indicators
     */
    function updateDebugStyles() {
        const chatContainer = document.querySelector('#chat-container');
        if (chatContainer) {
            if (state.userSettings.debug) {
                chatContainer.setAttribute("debug", "");
            } else {
                chatContainer.removeAttribute("debug");
            }
        }
    }

    // EVENT HANDLERS
    /**
     * Updates fullscreen status and related styles
     */
    function updateFullscreenStatus() {
        state.isFullscreen = !!document.fullscreenElement;
        updateFullscreenFloatingChatStyle();
    }

    /**
     * Handles theater mode toggle events
     * @param {Event} event - The theater mode event
     */
    function updateTheaterStatus(event) {
        state.isTheaterMode = !!event?.detail?.enabled;
        updateStyles();
    }

    /**
     * Handles chat status change events
     * @param {Event} event - The chat status event
     */
    function updateChatStatus(event) {
        state.chatFrame = event.target;
        state.chatCollapsed = event.detail !== false;
        updateFullscreenFloatingChatStyle();

        // Wait for player API to be ready before updating styles
        window.addEventListener('player-api-ready', updateStyles, { once: true });
    }

    /**
     * Updates movie player reference and sets up resize observer
     */
    function updateMoviePlayer() {
        const newMoviePlayer = document.querySelector('#movie_player');

        // Create resize observer if needed
        if (!state.resizeObserver) {
            state.resizeObserver = new ResizeObserver(() => {
                state.moviePlayerHeight = state.moviePlayer?.offsetHeight || 0;
                updateStyles();
            });
        }

        // Stop observing old player
        if (state.moviePlayer) {
            state.resizeObserver.unobserve(state.moviePlayer);
        }

        // Start observing new player
        state.moviePlayer = newMoviePlayer;
        if (state.moviePlayer) {
            state.resizeObserver.observe(state.moviePlayer);
        }
    }

    /**
     * Handles video status update events
     * @param {Event} event - The video status event
     */
    function updateVideoStatus(event) {
        try {
            state.currentPageType = event.detail.pageData.page;
            state.videoId = event.detail.pageData.playerResponse.videoDetails.videoId;
            state.isLiveStream = event.detail.pageData.playerResponse.videoDetails.isLiveContent;

            updateMoviePlayer();
            refreshMenuOptions();
        } catch (error) {
            logDebug(`Failed to update video status: ${error}`, 'error');
        }
    }

    // SETTINGS MANAGEMENT
    /**
     * Updates a setting in storage
     * @param {string} key - The setting key
     * @param {any} value - The setting value
     */
    async function updateSetting(key, value) {
        try {
            let currentSettings = await GM.getValue('settings', CONFIG.DEFAULT_SETTINGS);
            currentSettings[key] = value;
            await GM.setValue('settings', currentSettings);
            state.userSettings[key] = value;
        } catch (error) {
            logDebug(`Error updating setting: ${error}`, 'error');
        }
    }

    /**
     * Loads user settings from storage
     */
    async function loadUserSettings() {
        try {
            const storedSettings = await GM.getValue('settings', CONFIG.DEFAULT_SETTINGS);
            const newSettings = {};
            let needsSave = false;

            // Use stored settings or defaults
            for (const key in CONFIG.DEFAULT_SETTINGS) {
                if (key in storedSettings) {
                    newSettings[key] = storedSettings[key];
                } else {
                    newSettings[key] = CONFIG.DEFAULT_SETTINGS[key];
                    needsSave = true;
                }
            }

            // Check for obsolete settings
            for (const key in storedSettings) {
                if (!(key in CONFIG.DEFAULT_SETTINGS)) {
                    needsSave = true;
                }
            }

            // Save settings if needed
            state.userSettings = newSettings;
            if (needsSave) {
                await GM.setValue('settings', state.userSettings);
            }

            updateMode();
        } catch (error) {
            logDebug(`Error loading user settings: ${error}`, 'error');
            throw new Error(`Error loading user settings: ${error}. Aborting script.`);
        }
    }

    /**
     * Updates the mode (simple/advanced) based on user settings
     */
    function updateMode() {
        if (state.userSettings.isSimpleMode === true) {
            // Backup advanced settings before switching to simple mode
            state.advancedSettingsBackup = {
                ...state.userSettings,
                isSimpleMode: false
            };

            // Apply simple mode settings
            state.userSettings = {
                ...CONFIG.DEFAULT_SETTINGS,
                isScriptActive: state.userSettings.isScriptActive,
                isSimpleMode: true
            };

            logDebug('Using simple mode');
        } else if (state.advancedSettingsBackup) {
            // Restore advanced settings
            state.userSettings = {
                ...state.advancedSettingsBackup,
                isSimpleMode: false
            };

            logDebug('Using advanced mode');
            logDebug('Advanced settings backup:', state.advancedSettingsBackup);
        }

        logDebug(`Loaded settings: ${JSON.stringify(state.userSettings)}`);
    }

    /**
     * Loads video blacklist from storage
     */
    async function loadBlacklist() {
        try {
            let storedBlacklist = await GM.getValue('blacklist', CONFIG.DEFAULT_BLACKLIST);
            state.blacklist = new Set(
                Array.isArray(storedBlacklist) ? storedBlacklist : []
            );

            logDebug(`Loaded blacklist: ${JSON.stringify(Array.from(state.blacklist))}`);
        } catch (error) {
            logDebug(`Error loading blacklist: ${error}`, 'error');
            throw new Error(`Error loading blacklist: ${error}. Aborting script.`);
        }
    }

    /**
     * Updates the blacklist in storage
     */
    async function updateBlacklist() {
        try {
            await GM.setValue('blacklist', Array.from(state.blacklist));
        } catch (error) {
            logDebug(`Error updating blacklist: ${error}`, 'error');
        }
    }

    /**
     * Updates script info in storage and checks for updates
     */
    async function updateScriptInfo() {
        try {
            const oldScriptInfo = await GM.getValue('scriptInfo', null);
            const newScriptInfo = {
                version: getScriptVersionFromMeta(),
            };

            await GM.setValue('scriptInfo', newScriptInfo);

            // Check if script was updated
            if (!oldScriptInfo || compareVersions(newScriptInfo.version, oldScriptInfo?.version) !== 0) {
                state.isScriptRecentlyUpdated = true;
            }

            logDebug(`Previous script info: ${JSON.stringify(oldScriptInfo)}`);
            logDebug(`Updated script info: ${JSON.stringify(newScriptInfo)}`);
        } catch (error) {
            logDebug(`Error updating script info: ${error}`, 'error');
        }
    }

    /**
     * Cleans up old storage keys
     */
    async function cleanupOldStorage() {
        try {
            const allowedKeys = ['settings', 'scriptInfo', 'blacklist'];
            const keys = await GM.listValues();

            for (const key of keys) {
                if (!allowedKeys.includes(key)) {
                    await GM.deleteValue(key);
                    logDebug(`Deleted leftover key: ${key}`);
                }
            }
        } catch (error) {
            logDebug(`Error cleaning up old storage keys: ${error}`, 'error');
        }
    }

    // MENU MANAGEMENT
    /**
     * Removes all menu options
     */
    function removeMenuOptions() {
        state.menuItems.forEach((menuItem) => {
            GM.unregisterMenuCommand(menuItem);
        });
        state.menuItems.clear();
    }

    /**
     * Updates and shows menu options based on current state
     */
    async function refreshMenuOptions() {
        const shouldAutoClose = state.isOldTampermonkey;
        removeMenuOptions();

        // Advanced mode menu options
        const advancedMenuOptions = state.userSettings.isSimpleMode ? {} : {
            toggleOnlyLiveStreamMode: {
                alwaysShow: true,
                label: () => `${state.userSettings.enableOnlyForLiveStreams ? "✅" : "❌"} ${getLocalizedText().livestreamOnlyMode}`,
                menuId: "toggleOnlyLiveStreamMode",
                handleClick: async function () {
                    state.userSettings.enableOnlyForLiveStreams = !state.userSettings.enableOnlyForLiveStreams;
                    await updateSetting('enableOnlyForLiveStreams', state.userSettings.enableOnlyForLiveStreams);
                    updateStyles();
                    refreshMenuOptions();
                },
            },
            toggleChatStyle: {
                alwaysShow: true,
                label: () => `${state.userSettings.modifyChat ? "✅" : "❌"} ${getLocalizedText().applyChatStyles}`,
                menuId: "toggleChatStyle",
                handleClick: async function () {
                    state.userSettings.modifyChat = !state.userSettings.modifyChat;
                    await updateSetting('modifyChat', state.userSettings.modifyChat);
                    updateStyles();
                    refreshMenuOptions();
                },
            },
            ...(!state.userSettings.useCustomPlayerHeight ? {
                toggleVideoPlayerStyle: {
                    alwaysShow: true,
                    label: () => `${state.userSettings.modifyVideoPlayer ? "✅" : "❌"} ${getLocalizedText().applyVideoPlayerStyles}`,
                    menuId: "toggleVideoPlayerStyle",
                    handleClick: async function () {
                        state.userSettings.modifyVideoPlayer = !state.userSettings.modifyVideoPlayer;
                        await updateSetting('modifyVideoPlayer', state.userSettings.modifyVideoPlayer);
                        updateStyles();
                        refreshMenuOptions();
                    },
                },
            } : {}),
            toggleLowHeadmast: {
                alwaysShow: true,
                label: () => `${state.userSettings.setLowHeadmast ? "✅" : "❌"} ${getLocalizedText().moveHeadmastBelowVideoPlayer}`,
                menuId: "toggleLowHeadmast",
                handleClick: async function () {
                    state.userSettings.setLowHeadmast = !state.userSettings.setLowHeadmast;
                    await updateSetting('setLowHeadmast', state.userSettings.setLowHeadmast);
                    updateStyles();
                    refreshMenuOptions();
                },
            },
            toggleCustomPlayerHeight: {
                alwaysShow: true,
                label: () => `${state.userSettings.useCustomPlayerHeight ? "✅" : "❌"} ${getLocalizedText().useCustomPlayerHeight}`,
                menuId: "toggleCustomPlayerHeight",
                handleClick: async function () {
                    state.userSettings.useCustomPlayerHeight = !state.userSettings.useCustomPlayerHeight;
                    await updateSetting('useCustomPlayerHeight', state.userSettings.useCustomPlayerHeight);
                    updateStyles();
                    refreshMenuOptions();
                },
            },
            ...(state.userSettings.useCustomPlayerHeight ? {
                customHeightInputSelector: {
                    alwaysShow: true,
                    label: () => `🔢 ${getLocalizedText().playerHeightText} (${state.userSettings.playerHeightPx}px)`,
                    menuId: "customHeightInputSelector",
                    handleClick: async function () {
                        const playerHeightInputValue = await promptForNumber();
                        if (playerHeightInputValue === null) return;

                        state.userSettings.playerHeightPx = playerHeightInputValue;
                        await updateSetting('playerHeightPx', playerHeightInputValue);
                        updateStyles();
                        refreshMenuOptions();
                    },
                },
            } : {}),
            toggleFloatingChat: {
                alwaysShow: true,
                label: () => `${state.userSettings.floatingChat ? "✅" : "❌"} ${getLocalizedText().floatingChat}`,
                menuId: "toggleFloatingChat",
                handleClick: async function () {
                    state.userSettings.floatingChat = !state.userSettings.floatingChat;
                    await updateSetting('floatingChat', state.userSettings.floatingChat);
                    refreshMenuOptions();
                },
            },
            toggleDebug: {
                alwaysShow: true,
                label: () => `${state.userSettings.debug ? "✅" : "❌"} ${getLocalizedText().debug}`,
                menuId: "toggleDebug",
                handleClick: async function () {
                    state.userSettings.debug = !state.userSettings.debug;
                    await updateSetting('debug', state.userSettings.debug);
                    updateDebugStyles();
                    refreshMenuOptions();
                }
            }
        };

        // Common menu options for both simple and advanced modes
        const commonMenuOptions = {
            toggleScript: {
                alwaysShow: true,
                label: () => `🔄 ${state.userSettings.isScriptActive ? getLocalizedText().turnOff : getLocalizedText().turnOn}`,
                menuId: "toggleScript",
                handleClick: async function () {
                    state.userSettings.isScriptActive = !state.userSettings.isScriptActive;
                    await updateSetting('isScriptActive', state.userSettings.isScriptActive);
                    updateStyles();
                    refreshMenuOptions();
                },
            },
            addVideoToBlacklist: {
                alwaysShow: true,
                label: () => `🚫 ${state.blacklist.has(state.videoId) ? getLocalizedText().unblacklistVideo : getLocalizedText().blacklistVideo} [id: ${state.videoId}]`,
                menuId: "addVideoToBlacklist",
                handleClick: async function () {
                    if (state.blacklist.has(state.videoId)) {
                        state.blacklist.delete(state.videoId);
                    } else {
                        state.blacklist.add(state.videoId);
                    }
                    await updateBlacklist();
                    updateStyles();
                    refreshMenuOptions();
                },
            },
            toggleSimpleMode: {
                alwaysShow: true,
                label: () => `${state.userSettings.isSimpleMode ? "🚀 " + getLocalizedText().simpleMode : "🔧 " + getLocalizedText().advancedMode}`,
                menuId: "toggleSimpleMode",
                handleClick: async function () {
                    state.userSettings.isSimpleMode = !state.userSettings.isSimpleMode;
                    await updateSetting('isSimpleMode', state.userSettings.isSimpleMode);
                    updateMode();
                    updateStyles();
                    refreshMenuOptions();
                },
            },
        };

        // Combine menu options
        const menuOptions = {
            ...commonMenuOptions,
            ...advancedMenuOptions
        };

        // Process and register all menu options
        for (const [_, item] of Object.entries(menuOptions)) {
            if (!item.alwaysShow && !state.userSettings.expandMenu) continue;

            const menuId = GM.registerMenuCommand(item.label(), item.handleClick, {
                id: item.menuId,
                autoClose: shouldAutoClose,
            });

            state.menuItems.add(item.menuId);
        }
    }

    /**
     * Shows a number input prompt with validation
     * @param {string} message - The prompt message
     * @param {Function} validator - Optional validation function
     * @returns {number|null} The entered number or null if cancelled
     */
    async function promptForNumber(message = "Enter a number:", validator = null) {
        while (true) {
            const input = prompt(message);

            if (input === null) return null;

            const value = Number(input.trim());
            const isValidNumber = input.trim() !== "" && !isNaN(value);
            const passesCustomValidator = typeof validator === "function" ? validator(value) : true;

            if (isValidNumber && passesCustomValidator) {
                return value;
            } else {
                alert("⚠️ Please enter a valid number.");
            }
        }
    }

    // UTILITY FUNCTIONS
    /**
     * Logs debug information if debug mode is enabled
     * @param {string} message - The message to log
     * @param {string} [level='log'] - The log level ('log', 'warn', 'error')
     * @param {*} [data] - Optional data to log
     */
    function logDebug(message, level = 'log', data) {
        if (!state.userSettings.debug) return;

        const consoleMethod = console[level] || console.log;

        if (data !== undefined) {
            consoleMethod('[Better Theater] ' + message, data);
        } else {
            consoleMethod('[Better Theater] ' + message);
        }
    }

    /**
     * Compares two version strings
     * @param {string} v1 - First version string
     * @param {string} v2 - Second version string
     * @returns {number} 1 if v1 > v2, -1 if v1 < v2, 0 if equal
     */
    function compareVersions(v1, v2) {
        if (!v1 || !v2) return 0;

        const parts1 = v1.split('.').map(Number);
        const parts2 = v2.split('.').map(Number);
        const len = Math.max(parts1.length, parts2.length);

        for (let i = 0; i < len; i++) {
            const num1 = parts1[i] || 0;
            const num2 = parts2[i] || 0;

            if (num1 > num2) return 1;
            if (num1 < num2) return -1;
        }

        return 0;
    }

    /**
     * Gets the script version from metadata
     * @returns {string} The script version
     */
    function getScriptVersionFromMeta() {
        const versionMatch = GM_info.scriptMetaStr.match(/@version\s+([^\r\n]+)/);
        return versionMatch ? versionMatch[1].trim() : null;
    }

    /**
     * Checks if we have the required Greasemonkey API
     * @returns {boolean} Whether the required API is available
     */
    function detectGreasemonkeyAPI() {
        if (typeof GM !== 'undefined') return true;

        if (typeof GM_info !== 'undefined') {
            state.useCompatibilityMode = true;
            logDebug("Running in compatibility mode", 'warn');
            return true;
        }

        return false;
    }

    /**
     * Checks if Tampermonkey is up to date
     */
    function checkTampermonkeyVersion() {
        if (GM_info.scriptHandler === "Tampermonkey" &&
            compareVersions(GM_info.version, CONFIG.REQUIRED_VERSIONS.Tampermonkey) !== 1) {

            state.isOldTampermonkey = true;

            if (state.isScriptRecentlyUpdated) {
                GM.notification({
                    text: getLocalizedText().tampermonkeyOutdatedAlert,
                    timeout: 15000
                });
            }
        }
    }

    /**
     * Checks if the current page is a live chat iframe
     * @returns {boolean} Whether this is a live chat iframe
     */
    function isLiveChatIFrame() {
        return /^https?:\/\/.*youtube\.com\/live_chat.*$/.test(window.location.href);
    }

    // EVENT LISTENERS
    /**
     * Attaches all necessary event listeners
     */
    function attachEventListeners() {
        // YouTube-specific events
        window.addEventListener('yt-set-theater-mode-enabled', updateTheaterStatus, true);
        window.addEventListener('yt-chat-collapsed-changed', updateChatStatus, true);
        window.addEventListener('yt-page-data-fetched', updateVideoStatus, true);
        window.addEventListener('yt-page-data-updated', updateStyles, true);

        // Standard events
        window.addEventListener('fullscreenchange', updateFullscreenStatus, true);
        window.addEventListener('yt-navigate-finish', updateDebugStyles, { once: true });
    }

    // INITIALIZATION
    /**
     * Initializes the script
     */
    async function initialize() {
        try {
            // Check for Greasemonkey API
            if (!detectGreasemonkeyAPI()) {
                throw new Error("Did not detect valid Greasemonkey API");
            }

            // Initialize static styles
            applyStyle(styleRules.debugResizeHandleStyle, true);

            // Clean up and load settings
            await cleanupOldStorage();
            await loadUserSettings();
            await loadBlacklist();
            await updateScriptInfo();

            // Check Tampermonkey version
            checkTampermonkeyVersion();

            // Handle iframe case
            if (isLiveChatIFrame()) {
                applyStyle(styleRules.chatFrameFixStyle, true);
                return;
            }

            // Apply fixes and initialize
            applyStyle(styleRules.chatRendererFixStyle, true);
            applyStyle(styleRules.videoPlayerFixStyle, true);
            updateStyles();
            attachEventListeners();
            refreshMenuOptions();

        } catch (error) {
            logDebug(`Error when initializing script: ${error}. Aborting script.`, 'error');
        }
    }

    // Start the script
    initialize();
})();