YouTube Channel Blocker v5

Block YouTube channels with optimized performance and fullscreen scroll prevention

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         YouTube Channel Blocker v5
// @namespace    http://tampermonkey.net/
// @version      5
// @description  Block YouTube channels with optimized performance and fullscreen scroll prevention
// @author       You
// @match        https://www.youtube.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const CONFIG = {
        PROCESS_INTERVAL: 1500,
        URL_CHECK_INTERVAL: 800,
        INIT_DELAY: 400,
        DEBOUNCE_DELAY: 150,
        SELECTORS: {
            VIDEO: [
                'ytd-video-renderer',
                'ytd-grid-video-renderer',
                'ytd-compact-video-renderer',
                'ytd-rich-item-renderer',
                'ytd-playlist-video-renderer',
                'ytd-reel-item-renderer',
                'ytd-movie-renderer'
            ]
        }
    };

    // State management with performance optimization
    const state = {
        blockedChannels: GM_getValue('blockedChannels', []),
        lastUrl: location.href,
        cache: new Map(),
        processingQueue: new Set(),
        isProcessing: false,
        isFullscreen: false,
        scrollPreventionActive: false
    };

    // Utility functions
    const utils = {
        saveBlocked() {
            GM_setValue('blockedChannels', state.blockedChannels);
            state.cache.clear();
        },

        isBlocked(channelName, channelId) {
            const cacheKey = `${channelName}:${channelId}`;
            if (state.cache.has(cacheKey)) {
                return state.cache.get(cacheKey);
            }

            const blocked = state.blockedChannels.some(b => {
                const lower = b.toLowerCase();
                return channelName.toLowerCase().includes(lower) ||
                       (channelId && channelId.toLowerCase() === lower);
            });

            state.cache.set(cacheKey, blocked);
            return blocked;
        },

        getChannelInfo(video, selector) {
            let channelLink;

            if (selector === 'ytd-rich-item-renderer') {
                const richMedia = video.querySelector('ytd-rich-grid-media');
                channelLink = richMedia?.querySelector('a[href*="/@"], a[href*="/channel/"], ytd-channel-name a');
            } else {
                channelLink = video.querySelector('ytd-channel-name a, #channel-name a, #text a[href*="/@"], a[href*="/channel/"]');
            }

            if (!channelLink) return null;

            const href = channelLink.getAttribute('href') || '';
            const channelId = href.split('/').pop().split('?')[0];

            return {
                name: channelLink.textContent.trim(),
                id: channelId,
                link: channelLink
            };
        },

        getInsertTarget(video, selector) {
            if (selector === 'ytd-rich-item-renderer') {
                const richMedia = video.querySelector('ytd-rich-grid-media');
                return richMedia?.querySelector('#details, #metadata, #meta');
            }
            return video.querySelector('#metadata-line, #meta, #metadata, #details, ytd-channel-name');
        },

        debounce(func, delay) {
            let timeoutId;
            return function(...args) {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(() => func.apply(this, args), delay);
            };
        }
    };

    // Channel management
    const channelManager = {
        block(channelName, channelId) {
            const identifier = channelId || channelName;
            if (state.blockedChannels.includes(identifier)) {
                alert(`Channel "${channelName}" is already blocked.`);
                return false;
            }

            state.blockedChannels.push(identifier);
            utils.saveBlocked();
            alert(`✅ Blocked: ${channelName}\n\nRefresh the page to see changes.`);
            ui.updateToggleButton();
            return true;
        },

        unblock(identifier) {
            const index = state.blockedChannels.indexOf(identifier);
            if (index === -1) return false;

            state.blockedChannels.splice(index, 1);
            utils.saveBlocked();
            ui.updateToggleButton();
            return true;
        },

        exportBlocked() {
            const data = JSON.stringify(state.blockedChannels, null, 2);
            const blob = new Blob([data], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `youtube-blocked-channels-${Date.now()}.json`;
            a.click();
            URL.revokeObjectURL(url);
        },

        importBlocked(jsonData) {
            try {
                const imported = JSON.parse(jsonData);
                if (Array.isArray(imported)) {
                    const newChannels = imported.filter(ch => !state.blockedChannels.includes(ch));
                    state.blockedChannels.push(...newChannels);
                    utils.saveBlocked();
                    alert(`✅ Imported ${newChannels.length} new channels.\nTotal blocked: ${state.blockedChannels.length}`);
                    ui.updateToggleButton();
                    ui.updateBlockedList();
                    return true;
                }
            } catch (e) {
                alert('❌ Invalid JSON file');
            }
            return false;
        }
    };

    // Fullscreen handler - NEW ENHANCED VERSION IN v5
    const fullscreenHandler = {
        preventScroll(e) {
            if (state.isFullscreen) {
                e.preventDefault();
                e.stopPropagation();
                return false;
            }
        },

        hideUIElements() {
            // Hide all YouTube UI overlays during fullscreen
            const elementsToHide = [
                '.ytp-chrome-top',
                '.ytp-chrome-bottom',
                '.ytp-gradient-top',
                '.ytp-gradient-bottom',
                '.ytp-pause-overlay',
                'ytd-engagement-panel-section-list-renderer',
                '.ytp-endscreen-content',
                '.ytp-ce-element',
                '.ytp-cards-teaser',
                '.ytp-suggestion-set'
            ];

            elementsToHide.forEach(selector => {
                document.querySelectorAll(selector).forEach(el => {
                    if (state.isFullscreen) {
                        el.style.setProperty('display', 'none', 'important');
                        el.style.setProperty('opacity', '0', 'important');
                        el.style.setProperty('pointer-events', 'none', 'important');
                    } else {
                        el.style.removeProperty('display');
                        el.style.removeProperty('opacity');
                        el.style.removeProperty('pointer-events');
                    }
                });
            });
        },

        enableScrollPrevention() {
            if (state.scrollPreventionActive) return;

            // Prevent wheel scrolling
            document.addEventListener('wheel', this.preventScroll, { passive: false, capture: true });
            // Prevent touch scrolling
            document.addEventListener('touchmove', this.preventScroll, { passive: false, capture: true });
            // Prevent keyboard scrolling
            document.addEventListener('keydown', (e) => {
                if (state.isFullscreen && ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Space', 'Home', 'End'].includes(e.key)) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            }, { capture: true });

            state.scrollPreventionActive = true;
            console.log('🚫 Fullscreen scroll prevention enabled');
        },

        disableScrollPrevention() {
            if (!state.scrollPreventionActive) return;

            document.removeEventListener('wheel', this.preventScroll, { capture: true });
            document.removeEventListener('touchmove', this.preventScroll, { capture: true });

            state.scrollPreventionActive = false;
            console.log('✅ Fullscreen scroll prevention disabled');
        },

        handleFullscreenChange() {
            const isFullscreen = !!(
                document.fullscreenElement ||
                document.webkitFullscreenElement ||
                document.mozFullScreenElement ||
                document.msFullscreenElement ||
                document.querySelector('.html5-video-player.ytp-fullscreen')
            );

            // Update state
            state.isFullscreen = isFullscreen;

            // Hide/show toggle button
            const btn = document.getElementById('blocker-toggle');
            if (btn) {
                btn.style.display = isFullscreen ? 'none' : 'flex';
            }

            // Hide/show management panel
            const panel = document.getElementById('blocker-panel');
            if (panel && isFullscreen) {
                panel.style.display = 'none';
            }

            // Enable/disable scroll prevention
            if (isFullscreen) {
                this.enableScrollPrevention();
                this.hideUIElements();
            } else {
                this.disableScrollPrevention();
                this.hideUIElements(); // Reset UI elements
            }
        }
    };

    // Enhanced style injection with better visual indicators
    const styles = {
        inject() {
            const existingStyle = document.getElementById('channel-blocker-style');
            if (existingStyle) existingStyle.remove();

            const style = document.createElement('style');
            style.id = 'channel-blocker-style';
            style.textContent = `
                ${CONFIG.SELECTORS.VIDEO.map(s => `${s}[data-blocked="true"]`).join(',\n')} {
                    position: relative;
                    border: 3px solid #ffd700 !important;
                    border-radius: 8px !important;
                    padding: 4px !important;
                    background: rgba(255, 215, 0, 0.05) !important;
                }
                ${CONFIG.SELECTORS.VIDEO.map(s => `${s}[data-blocked="true"] ytd-thumbnail`).join(',\n')} {
                    border: 4px solid #ffd700 !important;
                    border-radius: 12px !important;
                    box-sizing: border-box !important;
                    pointer-events: none !important;
                    opacity: 0.6 !important;
                }
                ${CONFIG.SELECTORS.VIDEO.map(s => `${s}[data-blocked="true"] ytd-thumbnail *`).join(',\n')} {
                    pointer-events: none !important;
                }
                ${CONFIG.SELECTORS.VIDEO.map(s => `${s}[data-blocked="true"] ytd-thumbnail video`).join(',\n')} {
                    display: none !important;
                }
                ${CONFIG.SELECTORS.VIDEO.map(s => `${s}[data-blocked="true"] #mouseover-overlay, ${s}[data-blocked="true"] #hover-overlays`).join(',\n')} {
                    display: none !important;
                }
                .channel-block-btn {
                    transition: all 0.2s ease !important;
                }
                .channel-block-btn:hover {
                    transform: scale(1.05) !important;
                    filter: brightness(1.1) !important;
                }
                /* Hide toggle button in fullscreen mode */
                .html5-video-player.ytp-fullscreen ~ #blocker-toggle,
                body.fullscreen #blocker-toggle,
                html[data-fullscreen="true"] #blocker-toggle {
                    display: none !important;
                }
                /* NEW in v5: Prevent all overlays in fullscreen */
                body.ytp-fullscreen .ytp-pause-overlay,
                body.ytp-fullscreen .ytp-chrome-top,
                body.ytp-fullscreen .ytp-chrome-bottom {
                    opacity: 0 !important;
                    pointer-events: none !important;
                }
            `;
            document.head.appendChild(style);
        }
    };

    // Optimized video processing with batching
    const videoProcessor = {
        mark() {
            const batch = [];
            CONFIG.SELECTORS.VIDEO.forEach(selector => {
                document.querySelectorAll(`${selector}:not([data-checked])`).forEach(video => {
                    batch.push({ video, selector });
                });
            });

            // Process in batches to avoid blocking
            batch.forEach(({ video, selector }) => {
                video.dataset.checked = 'true';

                const info = utils.getChannelInfo(video, selector);
                if (!info) return;

                if (utils.isBlocked(info.name, info.id)) {
                    video.dataset.blocked = 'true';
                }
            });
        },

        addBlockButtons() {
            CONFIG.SELECTORS.VIDEO.forEach(selector => {
                document.querySelectorAll(`${selector}:not([data-has-button])`).forEach(video => {
                    if (video.dataset.blocked === 'true') return;

                    video.dataset.hasButton = 'true';

                    const info = utils.getChannelInfo(video, selector);
                    if (!info) return;

                    const insertTarget = utils.getInsertTarget(video, selector);
                    if (!insertTarget) return;

                    const alreadyBlocked = utils.isBlocked(info.name, info.id);
                    const button = this.createButton(info.name, info.id, alreadyBlocked);
                    insertTarget.appendChild(button);
                });
            });
        },

        addBadges() {
            CONFIG.SELECTORS.VIDEO.forEach(selector => {
                document.querySelectorAll(`${selector}[data-blocked="true"]:not([data-has-badge])`).forEach(video => {
                    video.dataset.hasBadge = 'true';

                    const info = utils.getChannelInfo(video, selector);
                    if (!info) return;

                    const insertTarget = utils.getInsertTarget(video, selector);
                    if (!insertTarget) return;

                    const badge = this.createBadge(info.name, info.id);
                    insertTarget.appendChild(badge);
                });
            });
        },

        createButton(channelName, channelId, alreadyBlocked) {
            const button = document.createElement('button');
            button.innerHTML = alreadyBlocked ? '✓ Blocked' : '🚫 Block';
            button.className = 'channel-block-btn';
            button.style.cssText = `
                background: ${alreadyBlocked ? '#666' : '#c00'};
                color: white; border: none; padding: 4px 10px;
                border-radius: 12px; cursor: pointer; font-size: 11px;
                font-weight: 500; margin: 4px 0 4px 8px;
                display: inline-block;
            `;

            button.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (alreadyBlocked) {
                    if (confirm(`Unblock "${channelName}"?`)) {
                        channelManager.unblock(channelId || channelName);
                        location.reload();
                    }
                } else {
                    channelManager.block(channelName, channelId);
                }
            };

            return button;
        },

        createBadge(channelName, channelId) {
            const badge = document.createElement('button');
            badge.innerHTML = '✓ Blocked';
            badge.className = 'blocked-channel-badge channel-block-btn';
            badge.style.cssText = `
                background: #ffd700; color: #000; border: none;
                padding: 4px 12px; border-radius: 12px; cursor: pointer;
                font-size: 11px; font-weight: bold; margin: 4px 0 4px 8px;
                display: inline-block;
            `;

            badge.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (confirm(`Unblock channel "${channelName}"?\n\nVideos from this channel will no longer be marked.`)) {
                    if (channelManager.unblock(channelId || channelName)) {
                        alert(`✅ Unblocked: ${channelName}`);
                        location.reload();
                    }
                }
            };

            return badge;
        }
    };

    // Enhanced channel page handler
    const channelPage = {
        addButton() {
            if (!location.pathname.match(/\/@|\/channel\//)) return;
            if (document.getElementById('channel-block-btn')) return;

            const nameEl = document.querySelector('yt-dynamic-text-view-model h1 span[role="text"]');
            const handleEl = document.querySelector('yt-content-metadata-view-model span[role="text"]');

            const channelName = nameEl?.textContent.trim().split('\n')[0] || '';
            let channelId = handleEl?.textContent.trim() || '';
            if (!channelId.startsWith('@')) {
                channelId = '@' + (location.pathname.split('/@')[1] || location.pathname.split('/channel/')[1] || '').split('/')[0];
            }

            if (!channelName && !channelId) return;

            const flexActions = document.querySelector('yt-flexible-actions-view-model');
            if (!flexActions) return;

            const blocked = utils.isBlocked(channelName, channelId);
            const wrapper = document.createElement('div');
            wrapper.id = 'channel-block-btn';
            wrapper.className = 'ytFlexibleActionsViewModelAction';

            const button = document.createElement('button');
            button.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m channel-block-btn';
            if (blocked) {
                button.style.cssText = `
                    background: #ffd700 !important; color: #000 !important;
                    border: 2px solid #000 !important; padding: 10px 24px !important;
                    font-weight: bold !important;
                `;
            }
            button.innerHTML = `<div class="yt-spec-button-shape-next__button-text-content">${blocked ? '✓ Blocked' : '🚫 Block'}</div>`;
            button.onclick = () => {
                if (blocked) {
                    if (confirm(`Unblock "${channelName}"?`)) {
                        channelManager.unblock(channelId || channelName);
                        location.reload();
                    }
                } else {
                    if (confirm(`Block "${channelName}"?`)) {
                        channelManager.block(channelName, channelId);
                    }
                }
            };

            wrapper.appendChild(button);
            flexActions.appendChild(wrapper);
        }
    };

    // Enhanced UI with import/export functionality
    const ui = {
        createToggleButton() {
            const btn = document.createElement('button');
            btn.id = 'blocker-toggle';
            btn.innerHTML = `🚫 (${state.blockedChannels.length})`;
            btn.title = 'Manage Blocked Channels';
            btn.style.cssText = `
                position: fixed; top: 70px; right: 20px; background: #c00;
                color: white; border: none; padding: 8px 12px; border-radius: 50%;
                cursor: grab; z-index: 9999; font-weight: 600; font-size: 16px;
                box-shadow: 0 2px 10px rgba(204,0,0,0.5); width: 44px; height: 44px;
                display: flex; align-items: center; justify-content: center;
                transition: all 0.2s;
            `;

            btn.onmouseover = () => {
                btn.style.transform = 'scale(1.1)';
                btn.style.boxShadow = '0 4px 15px rgba(204,0,0,0.7)';
            };
            btn.onmouseout = () => {
                btn.style.transform = 'scale(1)';
                btn.style.boxShadow = '0 2px 10px rgba(204,0,0,0.5)';
            };

            btn.onclick = () => {
                const panel = document.getElementById('blocker-panel');
                if (panel.style.display === 'none') {
                    this.updateBlockedList();
                    panel.style.display = 'block';
                } else {
                    panel.style.display = 'none';
                }
            };

            document.body.appendChild(btn);
            this.makeDraggable(btn);
        },

        updateToggleButton() {
            const btn = document.getElementById('blocker-toggle');
            if (btn) btn.innerHTML = `🚫 (${state.blockedChannels.length})`;
        },

        createManagementPanel() {
            const panel = document.createElement('div');
            panel.id = 'blocker-panel';
            panel.style.cssText = `
                position: fixed; top: 150px; right: 20px; background: #282828;
                border: 2px solid #c00; border-radius: 12px; padding: 20px;
                z-index: 10000; max-width: 380px; max-height: 550px;
                overflow-y: auto; display: none; color: white;
                box-shadow: 0 4px 20px rgba(0,0,0,0.5);
            `;

            panel.innerHTML = `
                <h3 style="margin: 0 0 16px 0; font-size: 18px; border-bottom: 2px solid #c00; padding-bottom: 8px;">
                    🚫 Blocked Channels
                </h3>
                <div id="blocked-count" style="margin-bottom: 12px; color: #aaa; font-size: 13px;">
                    Total: ${state.blockedChannels.length}
                </div>
                <div id="blocked-list" style="margin-bottom: 16px;"></div>
                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 16px;">
                    <button id="close-panel" style="background: #065fd4; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer;">Close</button>
                    <button id="clear-all" style="background: #c00; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer;">Clear All</button>
                    <button id="export-blocked" style="background: #0a8a0a; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer;">Export</button>
                    <button id="import-blocked" style="background: #f59e0b; color: white; border: none; padding: 10px; border-radius: 6px; cursor: pointer;">Import</button>
                </div>
                <input type="file" id="import-file" accept=".json" style="display: none;">
            `;

            document.body.appendChild(panel);

            document.getElementById('close-panel').onclick = () => panel.style.display = 'none';
            document.getElementById('clear-all').onclick = () => {
                if (confirm('Unblock all channels?')) {
                    state.blockedChannels = [];
                    utils.saveBlocked();
                    this.updateBlockedList();
                    location.reload();
                }
            };
            document.getElementById('export-blocked').onclick = () => channelManager.exportBlocked();
            document.getElementById('import-blocked').onclick = () => document.getElementById('import-file').click();
            document.getElementById('import-file').onchange = (e) => {
                const file = e.target.files[0];
                if (file) {
                    const reader = new FileReader();
                    reader.onload = (ev) => channelManager.importBlocked(ev.target.result);
                    reader.readAsText(file);
                }
            };
        },

        updateBlockedList() {
            const list = document.getElementById('blocked-list');
            const count = document.getElementById('blocked-count');
            if (!list) return;

            if (count) count.textContent = `Total: ${state.blockedChannels.length}`;
            list.innerHTML = state.blockedChannels.length === 0
                ? '<p style="color: #aaa; text-align: center; padding: 20px;">No blocked channels</p>'
                : state.blockedChannels.map(ch => `
                    <div style="display: flex; justify-content: space-between; align-items: center; padding: 10px; margin-bottom: 6px; background: #3a3a3a; border-radius: 6px;">
                        <span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${ch}">${ch}</span>
                        <button onclick="window.unblockChannelFromPanel('${ch.replace(/'/g, "\\'")}'))" style="background: #065fd4; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 8px;">Unblock</button>
                    </div>
                `).join('');
        },

        makeDraggable(el) {
            let pos1=0, pos2=0, pos3=0, pos4=0;
            el.onmousedown = (e) => {
                pos3 = e.clientX;
                pos4 = e.clientY;
                el.style.cursor = 'grabbing';
                document.onmouseup = () => {
                    document.onmouseup = null;
                    document.onmousemove = null;
                    el.style.cursor = 'grab';
                };
                document.onmousemove = (e) => {
                    pos1 = pos3 - e.clientX;
                    pos2 = pos4 - e.clientY;
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    el.style.top = (el.offsetTop - pos2) + "px";
                    el.style.left = (el.offsetLeft - pos1) + "px";
                    el.style.right = 'auto';
                };
            };
        }
    };

    // Global function for panel
    window.unblockChannelFromPanel = (ch) => {
        if (channelManager.unblock(ch)) {
            alert(`✅ Unblocked: ${ch}`);
            ui.updateBlockedList();
            location.reload();
        }
    };

    // Optimized main processing with debouncing
    const debouncedProcess = utils.debounce(() => {
        if (state.isProcessing) return;
        state.isProcessing = true;

        try {
            videoProcessor.mark();
            videoProcessor.addBlockButtons();
            videoProcessor.addBadges();
            channelPage.addButton();
        } finally {
            state.isProcessing = false;
        }
    }, CONFIG.DEBOUNCE_DELAY);

    function processPage() {
        debouncedProcess();
    }

    // URL change detection with optimization
    function detectUrlChange() {
        if (location.href !== state.lastUrl) {
            state.lastUrl = location.href;
            setTimeout(() => {
                styles.inject();
                processPage();
            }, CONFIG.INIT_DELAY);
        }
    }

    // Initialize with performance monitoring
    setTimeout(() => {
        console.log('🚫 YouTube Channel Blocker v5 initialized');
        console.log(`📊 Blocking ${state.blockedChannels.length} channels`);
        console.log('✨ NEW: Enhanced fullscreen mode with scroll prevention');

        ui.createToggleButton();
        ui.createManagementPanel();
        styles.inject();
        processPage();

        // Initialize fullscreen detection
        fullscreenHandler.handleFullscreenChange();
    }, CONFIG.INIT_DELAY);

    // Optimized intervals
    setInterval(processPage, CONFIG.PROCESS_INTERVAL);
    setInterval(detectUrlChange, CONFIG.URL_CHECK_INTERVAL);

    // Listen for fullscreen changes - ENHANCED IN v5
    document.addEventListener('fullscreenchange', () => fullscreenHandler.handleFullscreenChange());
    document.addEventListener('webkitfullscreenchange', () => fullscreenHandler.handleFullscreenChange());
    document.addEventListener('mozfullscreenchange', () => fullscreenHandler.handleFullscreenChange());
    document.addEventListener('MSFullscreenChange', () => fullscreenHandler.handleFullscreenChange());

    // Also check for YouTube's custom fullscreen class changes
    const fullscreenObserver = new MutationObserver(() => fullscreenHandler.handleFullscreenChange());
    setTimeout(() => {
        const videoPlayer = document.querySelector('.html5-video-player');
        if (videoPlayer) {
            fullscreenObserver.observe(videoPlayer, { attributes: true, attributeFilter: ['class'] });
        }
    }, 1000);

    // Performance monitoring
    if (state.blockedChannels.length > 0) {
        console.log('✅ YouTube Channel Blocker v5 active with enhanced fullscreen control');
    }
})();