Nút Picture-in-Picture (PIP) Đa Năng

Bản tối ưu: Giao diện cài đặt mới. Hỗ trợ shortcut, cài đặt, tự động dịch và tự đóng. Tối ưu cấu trúc và sửa lỗi tương thích.

// ==UserScript==
// @name         Nút Picture-in-Picture (PIP) Đa Năng
// @namespace    http://tampermonkey.net/
// @version      5.5
// @description  Bản tối ưu: Giao diện cài đặt mới. Hỗ trợ shortcut, cài đặt, tự động dịch và tự đóng. Tối ưu cấu trúc và sửa lỗi tương thích.
// @author       Lu (Refactored by Gemini)
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Gom các giá trị hằng số vào một nơi để dễ quản lý.
    const CONSTANTS = {
        SETTINGS_PANEL_Z_INDEX: 10001,
        BUTTON_CONTAINER_Z_INDEX: 10000,
        SETTINGS_PANEL_HIDE_DELAY: 3000, // ms
        SETTINGS_PANEL_FORCE_HIDE_DELAY: 7000, // ms
    };

    // --- MODULE QUẢN LÝ CÀI ĐẶT ---
    const Settings = {
        config: {},
        defaults: {
            isAlwaysOn: false,
            customShortcut: 'CMD+SHIFT+P',
            language: 'auto',
            loopButtonEnabled: true,
        },
        load() {
            for (const key in this.defaults) {
                this.config[key] = GM_getValue(key, this.defaults[key]);
            }
        },
        save(key, value) {
            this.config[key] = value;
            GM_setValue(key, value);
        }
    };

    // --- MODULE ĐA NGÔN NGỮ ---
    const I18n = {
        translations: {
            en: { settingsTitle: "PIP Settings", alwaysOnLabel: "Always show buttons", shortcutLabel: "Shortcut Key", shortcutPrefix: "CMD/CTRL + SHIFT +", languageLabel: "Language", autoLang: "Auto-detect", loopButtonLabel: "Show Loop button", loopButtonTitle: "Toggle Loop", recordingShortcut: "Press a key...", blockedWarning: "This shortcut is blocked by the browser!", conflictWarning: "Note: May conflict with other extensions.", settingButtonTitle: "PIP Settings" },
            vi: { settingsTitle: "Cài đặt PIP", alwaysOnLabel: "Luôn hiển thị nút", shortcutLabel: "Phím tắt", shortcutPrefix: "CMD/CTRL + SHIFT +", languageLabel: "Ngôn ngữ", autoLang: "Tự động", loopButtonLabel: "Hiển thị nút Lặp lại", loopButtonTitle: "Bật/Tắt Lặp lại", recordingShortcut: "Nhấn một phím...", blockedWarning: "Phím tắt này bị trình duyệt chặn!", conflictWarning: "Lưu ý: Có thể trùng với tiện ích khác.", settingButtonTitle: "Cài đặt PIP" }
        },
        getLang() {
            const lang = Settings.config.language;
            return lang === 'auto' ? (navigator.language.startsWith('vi') ? 'vi' : 'en') : lang;
        },
        t(key) {
            const lang = this.getLang();
            // Sửa lỗi tương thích ESLint bằng cách bỏ Optional Chaining (`?.`)
            return (this.translations[lang] && this.translations[lang][key]) || this.translations.en[key];
        }
    };

    // --- MODULE QUẢN LÝ GIAO DIỆN ---
    const UI = {
        settingsPanel: null,
        panelCloseTimer: null,
        isRecordingShortcut: false,
        BLOCKED_SHORTCUTS: ['CMD+SHIFT+Q', 'CTRL+SHIFT+Q', 'CMD+SHIFT+W', 'CTRL+SHIFT+W', 'CMD+SHIFT+T', 'CTRL+SHIFT+T', 'CMD+SHIFT+R', 'CTRL+SHIFT+R', 'CMD+SHIFT+L', 'CTRL+SHIFT+L', 'CMD+SHIFT+F', 'CTRL+SHIFT+F', 'CMD+SHIFT+N', 'CTRL+SHIFT+N'],

        createIcon(type, size = 'default') {
            const iconSizes = {
                small: { iconSize: 16, dimensions: { w: 16, h: 11, lw: 1.2, rW: 6, rH: 4, rX: 8, rY: 5 } },
                medium: { iconSize: 18, dimensions: { w: 18, h: 13, lw: 1.5, rW: 7, rH: 5, rX: 9, rY: 6 } },
                default: { iconSize: 22, dimensions: { w: 22, h: 16, lw: 2, rW: 9, rH: 6, rX: 11, rY: 8 } }
            };
            const { iconSize, dimensions } = iconSizes[size];

            if (type === 'pip') {
                const canvas = document.createElement('canvas');
                canvas.width = dimensions.w;
                canvas.height = dimensions.h;
                const ctx = canvas.getContext('2d');
                if (ctx) {
                    ctx.strokeStyle = 'white';
                    ctx.lineWidth = dimensions.lw;
                    ctx.strokeRect(1, 1, dimensions.w - 2, dimensions.h - 2);
                    ctx.fillStyle = 'white';
                    ctx.fillRect(dimensions.rX, dimensions.rY, dimensions.rW, dimensions.rH);
                }
                return canvas;
            }
            // Sử dụng template literal để SVG dễ đọc hơn.
            if (type === 'settings') return `
                <svg xmlns="http://www.w3.org/2000/svg" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M12 2a10 10 0 1 0 10 10c0-2.28-.81-4.4-2.18-6.08a10.04 10.04 0 0 0-1.82-2.32A10.04 10.04 0 0 0 12 2zm0 13a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/>
                    <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
                </svg>`;
            if (type === 'loop') return `
                <svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M17 1l4 4-4 4"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/>
                    <path d="M7 23l-4-4 4-4"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/>
                </svg>`;
        },

        injectStyles() {
            const styles = `
                .Lu-pip-settings-panel {
                    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                    position: fixed; z-index: ${CONSTANTS.SETTINGS_PANEL_Z_INDEX};
                    background-color: rgba(40, 40, 40, 0.8);
                    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
                    color: white; border-radius: 0.75rem; padding: 1rem;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.4); display: none;
                    width: 17.5rem; border: 1px solid rgba(255, 255, 255, 0.1);
                }
                .Lu-pip-settings-panel h3 { margin: 0 0 0.75rem 0; font-size: 1rem; border-bottom: 1px solid #444; padding-bottom: 0.5rem; }
                .Lu-pip-settings-row { display: flex; align-items: center; justify-content: space-between; font-size: 0.875rem; padding: 0.5rem 0.25rem; }
                .settings-group { margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid rgba(255, 255, 255, 0.1); }
                .shortcut-container { display: flex; align-items: center; gap: 0.3125rem; }
                #Lu-pip-language-select { background-color: #333; color: white; border: 1px solid #555; border-radius: 0.375rem; padding: 0.375rem; cursor: pointer; }
                #Lu-pip-shortcut-input { border: 1px solid #555; background-color: #333; padding: 0.375rem 0.625rem; border-radius: 0.375rem; text-align: center; font-weight: 500; min-width: 3.125rem; transition: all 0.2s; cursor: pointer; }
                #Lu-pip-shortcut-input.recording { background-color: #007aff; color: white; border-color: #007aff; }
                #Lu-pip-shortcut-warning { color: #ffcc00; font-size: 0.6875rem; text-align: right; margin-top: 0.25rem; height: 0.875rem; }
                .Lu-pip-control-button { background-color: transparent; border: none; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; transition: background-color 0.2s ease-in-out, transform 0.2s ease-in-out; }
                .Lu-pip-control-button:hover { background-color: rgba(255, 255, 255, 0.15); transform: scale(1.1); }
                .Lu-pip-loop-button.active { background-color: #007aff; }
                .Lu-pip-loop-button.active:hover { background-color: #0056b3; }
                .switch { position: relative; display: inline-block; width: 34px; height: 20px; flex-shrink: 0; }
                .switch input { opacity: 0; width: 0; height: 0; }
                .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #444; transition: .4s; border-radius: 20px; }
                .slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
                input:checked + .slider { background-color: #007aff; }
                input:checked + .slider:before { transform: translateX(14px); }
            `;
            const styleSheet = document.createElement("style");
            styleSheet.innerText = styles;
            document.head.appendChild(styleSheet);
        },

        createSettingsPanel() {
            this.injectStyles();
            const panel = document.createElement('div');
            panel.className = 'Lu-pip-settings-panel';
            panel.innerHTML = `
                <h3 data-lang-key="settingsTitle"></h3>
                <div class="Lu-pip-settings-row">
                    <span data-lang-key="languageLabel"></span>
                    <select id="Lu-pip-language-select">
                        <option value="auto" data-lang-key="autoLang"></option>
                        <option value="en">English</option><option value="vi">Tiếng Việt</option>
                    </select>
                </div>
                <div class="settings-group">
                    <div class="Lu-pip-settings-row">
                        <span data-lang-key="alwaysOnLabel"></span>
                        <label class="switch"><input type="checkbox" id="Lu-pip-always-on"><span class="slider"></span></label>
                    </div>
                    <div class="Lu-pip-settings-row">
                        <span data-lang-key="loopButtonLabel"></span>
                        <label class="switch"><input type="checkbox" id="Lu-pip-loop-enabled"><span class="slider"></span></label>
                    </div>
                </div>
                <div class="settings-group">
                    <div class="Lu-pip-settings-row">
                        <span data-lang-key="shortcutLabel"></span>
                        <div class="shortcut-container">
                            <span data-lang-key="shortcutPrefix"></span>
                            <div id="Lu-pip-shortcut-input"></div>
                        </div>
                    </div>
                    <div class="Lu-pip-settings-row" style="justify-content: flex-end;">
                        <small id="Lu-pip-shortcut-warning"></small>
                    </div>
                </div>
            `;
            document.body.appendChild(panel);
            this.settingsPanel = panel;
            this.bindPanelEvents();
            this.updatePanelUI();
        },

        updatePanelUI() {
            this.settingsPanel.querySelectorAll('[data-lang-key]').forEach(el => {
                el.textContent = I18n.t(el.dataset.langKey);
            });
            this.settingsPanel.querySelector('#Lu-pip-always-on').checked = Settings.config.isAlwaysOn;
            this.settingsPanel.querySelector('#Lu-pip-loop-enabled').checked = Settings.config.loopButtonEnabled;
            this.settingsPanel.querySelector('#Lu-pip-shortcut-input').textContent = Settings.config.customShortcut.split('+').pop();
            this.settingsPanel.querySelector('#Lu-pip-language-select').value = Settings.config.language;
        },

        bindPanelEvents() {
            const { settingsPanel } = this;
            const alwaysOnCheckbox = settingsPanel.querySelector('#Lu-pip-always-on');
            const loopCheckbox = settingsPanel.querySelector('#Lu-pip-loop-enabled');
            const shortcutInput = settingsPanel.querySelector('#Lu-pip-shortcut-input');
            const langSelect = settingsPanel.querySelector('#Lu-pip-language-select');

            const hidePanel = () => {
                if (settingsPanel.style.display === 'block') {
                    settingsPanel.style.display = 'none';
                }
                clearTimeout(this.panelCloseTimer);
                if (this.isRecordingShortcut) {
                    this.stopRecordingShortcut(shortcutInput, true); // Cancel recording
                }
            };

            document.addEventListener('click', hidePanel);
            settingsPanel.addEventListener('click', (e) => e.stopPropagation());
            settingsPanel.addEventListener('mouseenter', () => clearTimeout(this.panelCloseTimer));
            settingsPanel.addEventListener('mouseleave', () => {
                this.panelCloseTimer = setTimeout(hidePanel, CONSTANTS.SETTINGS_PANEL_HIDE_DELAY);
            });

            langSelect.addEventListener('change', () => {
                Settings.save('language', langSelect.value);
                this.updatePanelUI();
            });

            alwaysOnCheckbox.addEventListener('change', () => {
                Settings.save('isAlwaysOn', alwaysOnCheckbox.checked);
                document.querySelectorAll('.Lu-pip-button-container').forEach(container => {
                    this.updateButtonVisibility(container, container.parentElement);
                });
            });

            loopCheckbox.addEventListener('change', () => {
                Settings.save('loopButtonEnabled', loopCheckbox.checked);
                document.querySelectorAll('.Lu-pip-loop-button').forEach(btn => {
                    btn.style.display = Settings.config.loopButtonEnabled ? 'flex' : 'none';
                });
            });

            shortcutInput.addEventListener('click', () => this.startRecordingShortcut(shortcutInput));
        },

        startRecordingShortcut(inputEl) {
            if (this.isRecordingShortcut) return;
            this.isRecordingShortcut = true;

            inputEl.textContent = '...';
            inputEl.classList.add('recording');
            this.settingsPanel.querySelector('#Lu-pip-shortcut-warning').textContent = I18n.t('recordingShortcut');

            const handleRecording = (e) => {
                e.preventDefault();
                e.stopPropagation();

                const key = e.key.toUpperCase();
                if (!['CONTROL', 'SHIFT', 'ALT', 'META'].includes(key) && key.length === 1 && /[A-Z0-9]/.test(key)) {
                    const newShortcut = `CMD+SHIFT+${key}`;
                    const newShortcutCtrl = `CTRL+SHIFT+${key}`;
                    if (this.BLOCKED_SHORTCUTS.includes(newShortcut) || this.BLOCKED_SHORTCUTS.includes(newShortcutCtrl)) {
                        this.settingsPanel.querySelector('#Lu-pip-shortcut-warning').textContent = I18n.t('blockedWarning');
                    } else {
                        Settings.save('customShortcut', newShortcut);
                        this.settingsPanel.querySelector('#Lu-pip-shortcut-warning').textContent = I18n.t('conflictWarning');
                    }
                }
                this.stopRecordingShortcut(inputEl);
            };

            const captureOptions = { capture: true, once: true };
            window.addEventListener('keydown', handleRecording, captureOptions);
        },

        stopRecordingShortcut(inputEl, cancelled = false) {
            if (!this.isRecordingShortcut) return;
            if (!cancelled) {
                inputEl.textContent = Settings.config.customShortcut.split('+').pop();
            } else {
                // Restore previous text if cancelled
                this.updatePanelUI();
                this.settingsPanel.querySelector('#Lu-pip-shortcut-warning').textContent = '';
            }
            inputEl.classList.remove('recording');
            this.isRecordingShortcut = false;
        },
        
        showSettingsPanel(anchorButton) {
            clearTimeout(this.panelCloseTimer);
            const buttonRect = anchorButton.getBoundingClientRect();

            // Calculate position before showing to avoid flicker
            this.settingsPanel.style.visibility = 'hidden';
            this.settingsPanel.style.display = 'block';
            const panelRect = this.settingsPanel.getBoundingClientRect();
            
            let top = buttonRect.top - panelRect.height - 10; // Position above button
            let left = buttonRect.right - panelRect.width;

            // Adjust if out of viewport
            if (top < 10) top = buttonRect.bottom + 10;
            if (top + panelRect.height > window.innerHeight - 10) top = window.innerHeight - panelRect.height - 10;
            if (left < 10) left = 10;
            
            this.settingsPanel.style.top = `${top}px`;
            this.settingsPanel.style.left = `${left}px`;
            
            this.settingsPanel.style.visibility = 'visible';
            
            this.panelCloseTimer = setTimeout(() => {
                this.settingsPanel.style.display = 'none';
            }, CONSTANTS.SETTINGS_PANEL_FORCE_HIDE_DELAY);
        },

        updateButtonVisibility(container, parent) {
            const isHovering = parent.matches(':hover');
            const shouldBeVisible = Settings.config.isAlwaysOn || isHovering;
            container.style.opacity = shouldBeVisible ? '1' : '0';
            container.style.pointerEvents = shouldBeVisible ? 'auto' : 'none';
        },

        addButtonsToVideo(videoElement) {
            if (videoElement.dataset.pipButtonsAdded) return;
            videoElement.dataset.pipButtonsAdded = 'true';

            const parent = videoElement.parentElement;
            if (getComputedStyle(parent).position === 'static') {
                parent.style.position = 'relative';
            }

            const buttonContainer = document.createElement('div');
            buttonContainer.className = 'Lu-pip-button-container';
            Object.assign(buttonContainer.style, {
                position: 'absolute', top: '10px', right: '10px',
                zIndex: CONSTANTS.BUTTON_CONTAINER_Z_INDEX,
                backgroundColor: 'rgba(28, 28, 28, 0.8)',
                borderRadius: '9999px', padding: '4px',
                display: 'flex', gap: '4px',
                opacity: '0', pointerEvents: 'none',
                transition: 'opacity 0.2s ease-in-out, right 0.2s ease-in-out, top 0.2s ease-in-out',
            });

            const createButton = (className, title, innerHTML) => {
                const button = document.createElement('button');
                button.className = `Lu-pip-control-button ${className}`;
                button.title = title;
                if (innerHTML) button.innerHTML = innerHTML;
                buttonContainer.appendChild(button);
                return button;
            };

            const loopButton = createButton('Lu-pip-loop-button', I18n.t('loopButtonTitle'));
            const pipButton = createButton('Lu-pip-button', 'Picture-in-Picture');
            const settingsButton = createButton('Lu-pip-settings-button', I18n.t('settingButtonTitle'));
            parent.appendChild(buttonContainer);
            
            const updateButtonAttributes = () => {
                const videoHeight = videoElement.clientHeight;
                let size = 'default';
                if (videoHeight < 200) size = 'small';
                else if (videoHeight < 360) size = 'medium';

                const buttonSizes = { small: 28, medium: 32, default: 40 };
                const buttonSize = buttonSizes[size];

                pipButton.innerHTML = ''; pipButton.appendChild(this.createIcon('pip', size));
                settingsButton.innerHTML = this.createIcon('settings', size);
                loopButton.innerHTML = this.createIcon('loop', size);

                [pipButton, settingsButton, loopButton].forEach(btn => {
                    btn.style.width = `${buttonSize}px`;
                    btn.style.height = `${buttonSize}px`;
                });

                // Logic to avoid overlapping existing buttons
                let topPos = 10;
                const parentRect = parent.getBoundingClientRect();
                const nativeControls = parent.querySelectorAll('button, [role="button"]');
                for (const control of nativeControls) {
                    if (control.closest('.Lu-pip-button-container') || control.offsetParent === null) continue;
                    const controlRect = control.getBoundingClientRect();
                    // If a control is in the top-right corner area
                    if (controlRect.top - parentRect.top < 45 && parentRect.right - controlRect.right < 150) {
                        topPos = 50;
                        break;
                    }
                }
                buttonContainer.style.top = `${topPos}px`;
            };

            updateButtonAttributes();
            new ResizeObserver(updateButtonAttributes).observe(videoElement);

            pipButton.addEventListener('click', async (e) => {
                e.stopPropagation();
                try {
                    if (videoElement !== document.pictureInPictureElement) {
                        await videoElement.requestPictureInPicture();
                    } else {
                        await document.exitPictureInPicture();
                    }
                } catch (error) {
                    console.error('PIP Error:', error);
                }
            });
            
            settingsButton.addEventListener('click', (e) => {
                e.stopPropagation();
                this.showSettingsPanel(settingsButton);
            });
            
            loopButton.addEventListener('click', (e) => {
                e.stopPropagation();
                videoElement.loop = !videoElement.loop;
                loopButton.classList.toggle('active', videoElement.loop);
            });

            loopButton.style.display = Settings.config.loopButtonEnabled ? 'flex' : 'none';
            loopButton.classList.toggle('active', videoElement.loop);

            parent.addEventListener('mouseenter', () => this.updateButtonVisibility(buttonContainer, parent));
            parent.addEventListener('mouseleave', () => this.updateButtonVisibility(buttonContainer, parent));
            
            // Initial visibility check
            this.updateButtonVisibility(buttonContainer, parent);
        }
    };

    // --- MODULE ỨNG DỤNG CHÍNH ---
    const App = {
        uiInitialized: false,

        isElementInViewport(el) {
            const rect = el.getBoundingClientRect();
            return rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth;
        },

        async handlePipShortcut(e) {
            if (UI.isRecordingShortcut) return;

            const shortcutParts = Settings.config.customShortcut.split('+');
            const key = shortcutParts.pop();
            const isModifierPressed = e.shiftKey && (e.metaKey || e.ctrlKey);

            if (e.key.toUpperCase() === key && isModifierPressed) {
                e.preventDefault();
                try {
                    if (document.pictureInPictureElement) {
                        await document.exitPictureInPicture();
                        return;
                    }
                    
                    const videos = Array.from(document.querySelectorAll('video'))
                        .filter(v => this.isElementInViewport(v) && v.readyState > 2 && v.disablePictureInPicture === false)
                        .sort((a, b) => {
                            // Ưu tiên video đang phát
                            if (!a.paused && b.paused) return -1;
                            if (a.paused && !b.paused) return 1;
                            // Ưu tiên video có diện tích lớn hơn
                            const areaA = a.clientWidth * a.clientHeight;
                            const areaB = b.clientWidth * b.clientHeight;
                            return areaB - areaA;
                        });

                    if (videos.length > 0) {
                        await videos[0].requestPictureInPicture();
                    }
                } catch (error) {
                    console.error('PIP Shortcut Error:', error);
                }
            }
        },

        scanForVideos() {
            const videos = document.querySelectorAll('video:not([data-pip-buttons-added])');
            if (videos.length > 0) {
                if (!this.uiInitialized) {
                    this.activateFeatures();
                }
                videos.forEach(video => UI.addButtonsToVideo(video));
            }
        },

        activateFeatures() {
            if (this.uiInitialized) return;
            this.uiInitialized = true;
            UI.createSettingsPanel();
            document.addEventListener('keydown', (e) => this.handlePipShortcut(e));
        },

        init() {
            Settings.load();
            
            this.scanForVideos();

            const observer = new MutationObserver(() => this.scanForVideos());
            observer.observe(document.body, { childList: true, subtree: true });
        }
    };

    // --- KHỞI CHẠY SCRIPT ---
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => App.init());
    } else {
        App.init();
    }
})();