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 adds an optional, customized floating chat for fullscreen mode, seamlessly integrated with YouTube's design.

Pasang skrip ini?
Sugesti pemilik skrip

Kamu mungkin juga suka YouTube Music Opus Codec.

Pasang skrip ini
// ==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.12.3.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
// @run-at              document-start
// @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 adds an optional, customized floating chat for fullscreen mode, seamlessly integrated with YouTube's design.
// @description:zh-TW   改善 YouTube 劇場模式,參考 Twitch.tv 的設計,增強影片與聊天室佈局,同時維持效能與相容性。另新增可選的、自製風格的浮動聊天室功能(僅限全螢幕模式),與 YouTube 原有的設計語言相融合。
// @description:zh-CN   改进 YouTube 剧场模式,参考 Twitch.tv 的设计,增强视频与聊天室布局,同时保持性能与兼容性,也达到了类似B站的网页全屏功能。同时新增可选的、自制风格的浮动聊天室功能(仅限全屏模式),融入了 YouTube 原有的设计语言。
// @description:ja      YouTubeのシアターモードを改善し、Twitch.tvのデザインを参考にして、動画とチャットのレイアウトを強化しつつ、パフォーマンスと互換性を維持します。また、全画面モード専用のオプションとして、カスタマイズ済みフローティングチャット機能を、YouTubeのデザイン言語に沿って統合しています。
// ==/UserScript==

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

    const DRAG_BAR_HEIGHT = '35px';
    const MIN_CHAT_SIZE = { width: '300px', height: 320 + parseInt(DRAG_BAR_HEIGHT) + 'px' };

    const CONFIG = {
        DEFAULT_SETTINGS: {
            isSimpleMode: true,
            enableOnlyForLiveStreams: false,
            modifyVideoPlayer: true,
            modifyChat: true,
            setLowHeadmast: false,
            useCustomPlayerHeight: false,
            playerHeightPx: 600,
            floatingChat: false,
            theaterChatWidth: MIN_CHAT_SIZE.width,
            chatStyle: {
                left: '0px',
                top: '-500px',
                width: MIN_CHAT_SIZE.width,
                height: MIN_CHAT_SIZE.height,
                opacity: '0.95',
            },
            debug: false
        },
        DEFAULT_BLACKLIST: [],
        REQUIRED_VERSIONS: {
            Tampermonkey: '5.4.624'
        }
    };

    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-staticChatFrameFixStyle",
            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: ${MIN_CHAT_SIZE.width} !important;
                    max-width: 100vw !important;
                    max-height: 100vh !important;
                    position: absolute;
                    border-radius: 0 0 12px 12px !important;
                }
                #chat {
                    top: ${DRAG_BAR_HEIGHT} !important;
                    height: calc(100% - ${DRAG_BAR_HEIGHT}) !important;
                    width: inherit !important;
                    min-width: inherit !important;
                    max-width: inherit !important;
                    min-height: ${parseInt(MIN_CHAT_SIZE.height) - parseInt(DRAG_BAR_HEIGHT)}px !important;
                    max-height: 100vh !important;
                }
            `,
        },

        floatingChatStyleExpanded: {
            id: "betterTheater-floatingChatStyleExpanded",
            getRule: () => `
                #chat-container {
                    min-height: ${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;
                }
            `
        },

        chatClampLimits: {
            id: "betterTheater-chatClampLimits",
            getRule: () => {
                const hostElement = document.querySelector("ytd-watch-flexy");
                let originalWidth = "402px", originalMinWidth = "402px";

                if (hostElement) {
                    const style = window.getComputedStyle(hostElement);
                    const width = style.getPropertyValue("--ytd-watch-flexy-sidebar-width");
                    const minWidth = style.getPropertyValue("--ytd-watch-flexy-sidebar-min-width");
                    if (width && width.trim().endsWith("px")) originalWidth = width.trim();
                    if (minWidth && minWidth.trim().endsWith("px")) originalMinWidth = minWidth.trim();
                }

                return `
                    ytd-live-chat-frame[theater-watch-while] {
                        min-width: ${MIN_CHAT_SIZE.width} !important;
                        max-width: 33.33vw !important;
                    }

                    .ytd-watch-flexy {
                        --ytd-watch-flexy-sidebar-width: clamp(${MIN_CHAT_SIZE.width}, var(--bt-chat-width), 33.33vw) !important;
                        --ytd-watch-flexy-sidebar-min-width: clamp(${MIN_CHAT_SIZE.width}, var(--bt-chat-width), 33.33vw) !important;
                    }

                    ytd-watch-flexy[flexy] #secondary.ytd-watch-flexy {
                        --ytd-watch-flexy-sidebar-width: ${originalWidth} !important;
                        --ytd-watch-flexy-sidebar-min-width: ${originalMinWidth} !important;
                    }

                    ytd-watch-next-secondary-results-renderer {
                        --ytd-reel-item-compact-layout-width: calc((${originalWidth} - 8px) / 3) !important;
                        --ytd-reel-item-thumbnail-height: calc(${originalWidth} / 3 / 9 * 16) !important;
                    }

                    ytd-live-chat-frame[theater-watch-while] yt-live-chat-renderer {
                        width: 100% !important;
                        max-width: 100%!important;
                    }
                `;
            }
        },
    };

    // 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);
    }

    function addTheaterChatWidthHandle() {
        if (window.innerWidth / 3 <= 300) return;
        const chat = document.querySelector('ytd-live-chat-frame');
        if (!chat) return;
        const ytdWatchFlexy = document.querySelector('ytd-watch-flexy');
        const stored = state.userSettings.theaterChatWidth
            ?? CONFIG.DEFAULT_SETTINGS.theaterChatWidth;
        let initialPx = parseFloat(stored) || parseInt(MIN_CHAT_SIZE.width);
        applyWidth(initialPx + 'px');

        if (chat.querySelector('#chat-width-resize-handle')) return;

        const handle = document.createElement('div');
        handle.id = 'chat-width-resize-handle';
        handle.className = 'style-scope ytd-live-chat-frame';
        Object.assign(handle.style, {
            position: 'absolute',
            top: '0',
            left: '0',
            width: '6px',
            height: '100%',
            cursor: 'ew-resize',
            zIndex: '10001'
        });
        chat.appendChild(handle);

        let startX = 0, startWidth = 0;

        handle.addEventListener('pointerdown', e => {
            if (e.pointerType === 'mouse' && e.button !== 0) return;
            e.preventDefault();
            document.body.click();
            startX = e.clientX;
            startWidth = chat.getBoundingClientRect().width;
            handle.setPointerCapture(e.pointerId);
        });

        handle.addEventListener('pointermove', e => {
            if (!handle.hasPointerCapture(e.pointerId)) return;
            e.preventDefault();
            const dx = startX - e.clientX;
            let newPx = Math.max(parseInt(MIN_CHAT_SIZE.width), startWidth + dx);
            const cssValue = newPx + 'px';
            applyWidth(cssValue);
            state.userSettings.theaterChatWidth = cssValue;
        });

        handle.addEventListener('pointerup', async e => {
            handle.releasePointerCapture(e.pointerId);
            await updateSetting('theaterChatWidth', state.userSettings.theaterChatWidth);
        });

        function applyWidth(cssValue) {
            chat.style.width = cssValue;
            chat.style.zIndex = '1999';
            ytdWatchFlexy.style.setProperty('--bt-chat-width', cssValue);
        }
    }

    function removeTheaterChatWidthHandle() {
        const chat = document.querySelector('ytd-live-chat-frame');
        const chatRenderer = document.querySelector('yt-live-chat-renderer');
        const ytdWatchFlexy = document.querySelector('ytd-watch-flexy');
        const handle = chat?.querySelector('#chat-width-resize-handle');

        if (chat) {
            chat.style.width = '';
            chat.style.zIndex = '';
        }
        if (chatRenderer) chatRenderer.style.width = '';
        if (ytdWatchFlexy) ytdWatchFlexy.style.removeProperty('--bt-chat-width');
        if (handle) handle.remove();
    }

    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 saveChatStyle() {
                Object.assign(state.userSettings.chatStyle ??= {}, {
                    width: chatContainer.style.width,
                    height: chatContainer.style.height,
                    left: chatContainer.style.left,
                    top: chatContainer.style.top
                });
                await updateSetting('chatStyle', state.userSettings.chatStyle);
            }

            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(MIN_CHAT_SIZE.width);
                const minHeight = parseInt(MIN_CHAT_SIZE.height);
                let dx = e.clientX - startX;
                let dy = e.clientY - startY;
                let newLeft = startLeft;
                let newTop = startTop;
                let newWidth = startWidth;
                let newHeight = startHeight;
                dx = Math.max(-startX, Math.min(dx, movieRect.right - startX));
                dy = Math.max(-startY, Math.min(dy, movieRect.bottom - startY));

                if (config.horizontal) {
                    const isRightSide = handle.className.toLowerCase().includes('right');
                    const isLeftSide = handle.className.toLowerCase().includes('left');
                    if (isRightSide) {
                        newWidth += dx;
                    } else if (isLeftSide) {
                        newWidth -= dx;
                        newLeft += Math.min(dx, startWidth - minWidth);
                    }
                }

                if (config.vertical) {
                    const isBottomSide = handle.className.toLowerCase().includes('bottom');
                    const isTopSide = handle.className.toLowerCase().includes('top');
                    if (isBottomSide) {
                        newHeight += dy;
                    } else if (isTopSide) {
                        newHeight -= dy;
                        newTop += Math.min(dy, startHeight - minHeight);
                    }
                }

                const correctedTopBound = movieRect.top - chatParentRect.top;
                const correctedLeftBound = movieRect.left - chatParentRect.left;
                newWidth = Math.min(Math.max(minWidth, newWidth), movieRect.right - chatRect.left);
                newHeight = Math.min(Math.max(minHeight, newHeight), movieRect.bottom - chatRect.top);
                newTop = Math.max(newTop, correctedTopBound);
                newLeft = Math.max(newLeft, correctedLeftBound);
                Object.assign(chatContainer.style, {
                    left: newLeft + "px",
                    top: newTop + "px",
                    width: newWidth + "px",
                    height: newHeight + "px"
                });
            });

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

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

    function addDragBarWithOpacitySlider(chatContainer) {
        let existingBar = chatContainer.querySelector('.chat-drag-bar');
        if (existingBar) return existingBar;

        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(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.chatStyle.opacity) * 100).toString();
        opacitySlider.style.marginLeft = "10px";

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

        opacitySlider.addEventListener("mouseup", () => {
            Object.assign(state.userSettings.chatStyle ??= {}, {
                opacity: chatContainer.style.opacity
            });
            updateSetting('chatStyle', state.userSettings.chatStyle);
        });

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

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

        setupDragBehavior(dragBar, chatContainer);

        return dragBar;
    }

    function setupDragBehavior(dragBar, chatContainer) {
        let startX = 0, startY = 0;
        let startLeft = 0, startTop = 0;

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

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

            startX = e.clientX;
            startY = e.clientY;
            startLeft = parseFloat(getComputedStyle(chatContainer).left) || 0;
            startTop = parseFloat(getComputedStyle(chatContainer).top) || 0;

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

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

            let dx = e.clientX - startX;
            let dy = e.clientY - startY;
            let newLeft = startLeft + dx;
            let newTop = startTop + dy;

            const movieRect = state.moviePlayer.getBoundingClientRect();
            const chatParentRect = chatContainer.parentElement.getBoundingClientRect();
            const correctedTopBound = movieRect.top - chatParentRect.top;
            const correctedLeftBound = movieRect.left - chatParentRect.left;
            const correctedLowerBound = movieRect.bottom - chatParentRect.top - (
                state.chatCollapsed
                    ? parseInt(DRAG_BAR_HEIGHT) + chatContainer.querySelector('#show-hide-button').offsetHeight
                    : chatContainer.offsetHeight
            );
            const correctedRightBound = movieRect.right - chatParentRect.left - chatContainer.offsetWidth;

            newTop = Math.min(Math.max(newTop, correctedTopBound), correctedLowerBound);
            newLeft = Math.min(Math.max(newLeft, correctedLeftBound), correctedRightBound);
            Object.assign(chatContainer.style, {
                left: newLeft + "px",
                top: newTop + "px",
            });
            e.preventDefault();
        });

        dragBar.addEventListener("pointerup", function (e) {
            dragBar.releasePointerCapture(e.pointerId);
            saveChatPosition();
        });
    }

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

    function removeAllChatStyles(chatContainer) {
        removeStyle(styleRules.floatingChatStyleCollapsed);
        removeStyle(styleRules.floatingChatStyleExpanded);
        removeStyle(styleRules.floatingChatStyle);

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

    function applySavedChatStyle(chatContainer, shouldSave = false) {
        if (!chatContainer) return;

        const movieRect = state.moviePlayer.getBoundingClientRect();
        const chatParentRect = chatContainer.parentElement.getBoundingClientRect();
        const chatRect = chatContainer.getBoundingClientRect();
        const minWidth = parseInt(MIN_CHAT_SIZE.width);
        const minHeight = parseInt(MIN_CHAT_SIZE.height);

        let width = parseFloat(state.userSettings.chatStyle.width);
        let height = parseFloat(state.userSettings.chatStyle.height);
        let top = parseFloat(state.userSettings.chatStyle.top);
        let left = parseFloat(state.userSettings.chatStyle.left);
        const correctedTopBound = movieRect.top - chatParentRect.top;
        const correctedLowerBound = movieRect.bottom - chatParentRect.top;
        const correctedLeftBound = movieRect.left - chatParentRect.left;
        const correctedRightBound = chatRect.right - movieRect.right;

        if ([correctedLeftBound, correctedTopBound, correctedRightBound, correctedLowerBound].includes(0)) return;
        width = Math.min(Math.max(minWidth, width), movieRect.width);
        height = Math.min(Math.max(minHeight, height), movieRect.height);
        top = Math.max(top, correctedTopBound) - (
            state.chatCollapsed ?
                0 :
                Math.max(0, correctedLowerBound)
        );

        left = Math.max(left, correctedLeftBound) - (
            state.chatCollapsed ?
                0 :
                Math.max(0, correctedRightBound)
        );
        Object.assign(chatContainer.style, {
            left: left + "px",
            top: top + "px",
            width: width + "px",
            height: height + "px",
            opacity: parseFloat(state.userSettings.chatStyle.opacity)
        });

        if (shouldSave && state.isFullscreen) {
            Object.assign(state.userSettings.chatStyle ??= {}, {
                width: chatContainer.style.width,
                height: chatContainer.style.height,
                left: chatContainer.style.left,
                top: chatContainer.style.top
            });
            updateSetting('chatStyle', state.userSettings.chatStyle);
        }
    }

    function updateStyles(shouldSave = false) {
        try {
            if (state.userSettings.useCustomPlayerHeight) {
                state.userSettings.modifyVideoPlayer = true;
            }

            const shouldNotActivate =
                (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;
            }

            setStyleState(styleRules.videoPlayerStyle, state.userSettings.modifyVideoPlayer);

            updateChatStyle();

            updateFullscreenFloatingChatStyle(shouldSave);
            if (state.moviePlayer && state.moviePlayer.setCenterCrop) {
                state.moviePlayer.setCenterCrop();
            }
        } catch (error) {
            logDebug(`Error when updating styles: ${error}`, 'error');
        }
    }

    function updateChatStyle() {
        const chatBoundingBox = document.querySelector('#chat')?.getBoundingClientRect();
        const shouldApplyChatStyle =
            state.userSettings.modifyChat &&
            state.isTheaterMode &&
            !state.chatCollapsed &&
            !!chatBoundingBox &&
            (chatBoundingBox?.width > 0 || chatBoundingBox?.height > 0) &&
            document.querySelector('.style-scope.ytd-watch-flexy#secondary')?.style.display !== 'none';

        setStyleState(styleRules.chatStyle, shouldApplyChatStyle);
        setStyleState(styleRules.chatClampLimits, shouldApplyChatStyle);
        if (shouldApplyChatStyle) {
            addTheaterChatWidthHandle();
        } else {
            removeTheaterChatWidthHandle();
        }
        updateHeadmastStyle(shouldApplyChatStyle);
    }

    function updateHeadmastStyle(shouldApplyChatStyle) {
        updateLowHeadmastStyle();

        const shouldShrinkHeadmast =
            shouldApplyChatStyle &&
            state.isTheaterMode &&
            state.chatFrame?.getAttribute('theater-watch-while') === '' &&
            (state.userSettings.setLowHeadmast || state.userSettings.modifyChat);

        state.chatWidth = state.chatFrame?.offsetWidth || 0;
        setStyleState(styleRules.headmastStyle, shouldShrinkHeadmast);
    }

    function updateLowHeadmastStyle() {
        if (!state.moviePlayer) return;

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

        setStyleState(styleRules.lowHeadmastStyle, shouldApplyLowHeadmast);
    }

    function updateFullscreenFloatingChatStyle(shouldSave = false) {
        try {
            const chatContainer = document.querySelector('#chat-container');
            setStyleState(styleRules.floatingChatStyleCollapsed,
                state.chatCollapsed && state.isFullscreen);
            setStyleState(styleRules.floatingChatStyleExpanded,
                !state.chatCollapsed && state.isFullscreen);
            setStyleState(styleRules.floatingChatStyle, state.isFullscreen);

            if (state.userSettings.floatingChat &&
                chatContainer.querySelector('#chat') &&
                chatContainer &&
                state.isFullscreen) {

                applySavedChatStyle(chatContainer, shouldSave);
                removeDragBarWithOpacitySlider(chatContainer);
                addDragBarWithOpacitySlider(chatContainer);
                addResizeHandles(chatContainer);
            } else if (chatContainer) {
                removeAllChatStyles(chatContainer);
                removeDragBarWithOpacitySlider(chatContainer);
                removeResizeHandles(chatContainer);
            }
        } catch (error) {
            logDebug(`Error when updating fullscreen chat styles: ${error}`, 'error');
        }
    }

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

    // EVENT HANDLERS
    function updateFullscreenStatus() {
        state.isFullscreen = !!document.fullscreenElement;
        updateStyles();
    }

    function updateTheaterStatus(event) {
        state.isTheaterMode = !!event?.detail?.enabled;
        updateStyles();
    }

    function updateChatStatus(event) {
        state.chatFrame = event.target;
        state.chatCollapsed = event.detail !== false;

        window.addEventListener('player-api-ready', () => {
            updateStyles(true);
        }, { once: true });
    }

    function updateMoviePlayer() {
        const newMoviePlayer = document.querySelector('#movie_player');

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

        if (state.moviePlayer) {
            state.resizeObserver.unobserve(state.moviePlayer);
        }

        state.moviePlayer = newMoviePlayer;
        if (state.moviePlayer) {
            state.resizeObserver.observe(state.moviePlayer);
        }
    }

    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
    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');
        }
    }

    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.`);
        }
    }

    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,
                isSimpleMode: true
            };

            logDebug('Using simple mode');
        } else if (state.advancedSettingsBackup) {

            state.userSettings = {
                ...state.advancedSettingsBackup,
                isSimpleMode: false
            };

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

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

    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.`);
        }
    }

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

    async function updateScriptInfo() {
        try {
            const oldScriptInfo = await GM.getValue('scriptInfo', null);
            const newScriptInfo = {
                version: getScriptVersionFromMeta(),
            };

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

            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');
        }
    }

    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
    function removeMenuOptions() {
        state.menuItems.forEach((menuItem) => {
            GM.unregisterMenuCommand(menuItem);
        });
        state.menuItems.clear();
    }

    async function refreshMenuOptions() {
        const shouldAutoClose = state.isOldTampermonkey;
        removeMenuOptions();

        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();
                }
            }
        };

        const commonMenuOptions = {
            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();
                },
            },
        };

        const menuOptions = {
            ...commonMenuOptions,
            ...advancedMenuOptions
        };

        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);
        }
    }

    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
    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);
        }
    }

    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;
    }

    function getScriptVersionFromMeta() {
        const versionMatch = GM_info.scriptMetaStr.match(/@version\s+([^\r\n]+)/);
        return versionMatch ? versionMatch[1].trim() : null;
    }

    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;
    }

    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
                });
            }
        }
    }

    function isLiveChatIFrame() {
        return /^https?:\/\/.*youtube\.com\/live_chat.*$/.test(window.location.href);
    }

    function attachEventListeners() {
        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);
        window.addEventListener('fullscreenchange', updateFullscreenStatus, true);
        window.addEventListener('yt-navigate-finish', updateDebugStyles, { once: true });

        let resizeInterval = null;
        let resizeTimeout = null;
        window.addEventListener('resize', function () {
            if (!resizeInterval) {
                updateStyles();
                resizeInterval = setInterval(updateStyles, 500);
            }
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(function () {
                clearInterval(resizeInterval);
                resizeInterval = null;
                updateStyles();
            }, 500);
        });
    }

    async function initialize() {
        try {
            if (!detectGreasemonkeyAPI()) {
                throw new Error("Did not detect valid Greasemonkey API");
            }
            applyStyle(styleRules.debugResizeHandleStyle, true);
            await cleanupOldStorage();
            await loadUserSettings();
            await loadBlacklist();
            await updateScriptInfo();
            checkTampermonkeyVersion();
            if (isLiveChatIFrame()) {
                applyStyle(styleRules.chatFrameFixStyle, true);
                return;
            }
            applyStyle(styleRules.chatRendererFixStyle, true);
            applyStyle(styleRules.videoPlayerFixStyle, true);
            updateStyles();
            attachEventListeners();
            refreshMenuOptions();

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

    initialize();
})();