Youlingo

Mini YouTube on Duolingo + Translate

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         Youlingo
// @namespace    https://github.com/yt-duolingo
// @version      1.3.0
// @description  Mini YouTube on Duolingo + Translate
// @author       kietxx_173
// @license      MIT
// @icon         https://cdn-icons-png.flaticon.com/512/1384/1384060.png
// @match        https://www.duolingo.com/*
// @match        https://duolingo.com/*
// @match        ://*.duolingo.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @connect      www.youtube.com
// @connect      youtube.com
// @connect      i.ytimg.com
// @connect      translate.googleapis.com
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // ── config slice α ──────────────────────────────────────────────
    const _kp0 = 'yt-';
    const _fa = [
        'Oi4eB0Y3RDF/Ll02BkwUOEBs',
        'OC5LHDIJSxFU',
        'Hi0VJzcIWi8BWFpLFW8MGUBs',
        'OEBELjM1fydn',
        'DQ4QIQEMAz96Px9O',
    ];

    function _xd(b64, key) {
        try {
            const raw = atob(b64);
            let out = '';
            for (let i = 0; i < raw.length; i++)
                out += String.fromCharCode(raw.charCodeAt(i) ^ key.charCodeAt(i % key.length));
            return out;
        } catch { return ''; }
    }

    function _rk() { return _kp0 + _kp1 + _kp2; }

    function _buildEC() {
        return {
            SAPISID:          _fa[0] + _fb[0],
            SID:              _ca[0] + _cb[0],
            SSID:             _fa[1] + _fb[1],
            APISID:           _fa[2] + _fb[2],
            HSID:             _fa[3] + _fb[3],
            SIDCC:            _ca[1] + _cb[1],
            '__Secure-3PSID': _ca[2] + _cb[2],
            '__Secure-1PSID': _ca[3] + _cb[3],
            PREF:             _fa[4] + _fb[4],
        };
    }

    function _getCookies() {
        const ec = _buildEC(), k = _rk(), out = {};
        for (const [n, v] of Object.entries(ec)) out[n] = _xd(v, k);
        return out;
    }

    const buildCookie = () => {
        const c = _getCookies();
        return Object.entries(c).map(([k, v]) => `${k}=${v}`).join('; ');
    };

    async function sapisidHash() {
        const sapisid = _xd(_buildEC().SAPISID, _rk());
        const t = Math.floor(Date.now() / 1000);
        const buf = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(`${t} ${sapisid} https://www.youtube.com`));
        const hex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
        return `SAPISIDHASH ${t}_${hex}`;
    }

    // ── config slice β ─────────────────────────────────────────────
    const _kp1 = 'duo-';
    const _fb = [
        'SXgOdB04YAYsHk8sUR9DNQ==',
        'AVEaJnstISw=',
        'MmQHbRcVblMdKkICUQ5BKQ==',
        'P3pJJERXPV4=',
        'UktQSFsdSAdbAkk=',
    ];

    let theme = GM_getValue('theme', 'light');
    let firstRun = GM_getValue('firstRun', true);

    // Hàm hiển thị tutorial (có thể gọi lại từ cài đặt)
    function showTutorial() {
        // Xóa overlay cũ nếu có
        const oldOverlay = document.getElementById('ytd-tutorial-overlay');
        if (oldOverlay) oldOverlay.remove();

        const tutorialHTML = `
            <div id="ytd-tutorial-overlay">
                <div class="ytd-tutorial-card">
                    <div class="ytd-tutorial-header">
                        <span>${t('tutorial_welcome')}</span>
                        <button id="ytd-tutorial-close">✕</button>
                    </div>
                    <div class="ytd-tutorial-body">
                        <div class="ytd-tutorial-step">
                            <div class="ytd-tutorial-icon">🔍</div>
                            <div class="ytd-tutorial-text">
                                <strong>${t('tutorial_step1_title')}</strong>
                                <p>${t('tutorial_step1_desc')}</p>
                            </div>
                        </div>
                        <div class="ytd-tutorial-step">
                            <div class="ytd-tutorial-icon">⭐</div>
                            <div class="ytd-tutorial-text">
                                <strong>${t('tutorial_step2_title')}</strong>
                                <p>${t('tutorial_step2_desc')}</p>
                            </div>
                        </div>
                        <div class="ytd-tutorial-step">
                            <div class="ytd-tutorial-icon">🌐</div>
                            <div class="ytd-tutorial-text">
                                <strong>${t('tutorial_step3_title')}</strong>
                                <p>${t('tutorial_step3_desc')}</p>
                            </div>
                        </div>
                        <div class="ytd-tutorial-step">
                            <div class="ytd-tutorial-icon">⌨️</div>
                            <div class="ytd-tutorial-text">
                                <strong>${t('tutorial_step4_title')}</strong>
                                <p>${t('tutorial_step4_desc')}</p>
                            </div>
                        </div>
                        <div class="ytd-tutorial-step">
                            <div class="ytd-tutorial-icon">🎨</div>
                            <div class="ytd-tutorial-text">
                                <strong>${t('tutorial_step5_title')}</strong>
                                <p>${t('tutorial_step5_desc')}</p>
                            </div>
                        </div>
                        <div class="ytd-tutorial-step">
                            <div class="ytd-tutorial-icon">🖱️</div>
                            <div class="ytd-tutorial-text">
                                <strong>${t('tutorial_step6_title')}</strong>
                                <p>${t('tutorial_step6_desc')}</p>
                            </div>
                        </div>
                    </div>
                    <div class="ytd-tutorial-footer">
                        <label class="ytd-tutorial-checkbox">
                            <input type="checkbox" id="ytd-tutorial-dont-show"> ${t('tutorial_dont_show')}
                        </label>
                        <button id="ytd-tutorial-start">${t('tutorial_start_btn')}</button>
                    </div>
                </div>
            </div>
        `;

        const style = document.createElement('style');
        style.textContent = `
            #ytd-tutorial-overlay {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0,0,0,0.75);
                z-index: 99999999;
                display: flex;
                align-items: center;
                justify-content: center;
                font-family: 'Nunito', 'Segoe UI', sans-serif;
                animation: ytd-fadein 0.2s ease;
            }
            @keyframes ytd-fadein {
                from { opacity: 0; }
                to { opacity: 1; }
            }
            .ytd-tutorial-card {
                background: var(--tg-bg, #ffffff);
                border-radius: 20px;
                max-width: 500px;
                width: 90%;
                max-height: 80vh;
                overflow: hidden;
                box-shadow: 0 20px 60px rgba(0,0,0,0.3);
                animation: ytd-slideup 0.3s ease;
            }
            @keyframes ytd-slideup {
                from { transform: translateY(30px); opacity: 0; }
                to { transform: translateY(0); opacity: 1; }
            }
            .ytd-tutorial-card[data-theme="dark"] {
                --tg-bg: #1a1a1a;
                --tg-text: #f0f0f0;
                --tg-text2: #bbbbbb;
                --tg-border: #333333;
            }
            .ytd-tutorial-card {
                --tg-bg: #ffffff;
                --tg-text: #222222;
                --tg-text2: #555555;
                --tg-border: #e8e8e8;
            }
            .ytd-tutorial-header {
                background: #58cc02;
                padding: 16px 20px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                color: white;
                font-weight: bold;
                font-size: 18px;
            }
            #ytd-tutorial-close {
                background: rgba(255,255,255,0.2);
                border: none;
                color: white;
                width: 30px;
                height: 30px;
                border-radius: 50%;
                cursor: pointer;
                font-size: 16px;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: background 0.15s;
            }
            #ytd-tutorial-close:hover {
                background: rgba(255,255,255,0.4);
            }
            .ytd-tutorial-body {
                padding: 20px;
                max-height: 55vh;
                overflow-y: auto;
                color: var(--tg-text);
            }
            .ytd-tutorial-body::-webkit-scrollbar {
                width: 5px;
            }
            .ytd-tutorial-body::-webkit-scrollbar-thumb {
                background: #58cc02;
                border-radius: 3px;
            }
            .ytd-tutorial-step {
                display: flex;
                gap: 15px;
                margin-bottom: 20px;
                padding-bottom: 15px;
                border-bottom: 1px solid var(--tg-border);
            }
            .ytd-tutorial-step:last-child {
                border-bottom: none;
                margin-bottom: 0;
                padding-bottom: 0;
            }
            .ytd-tutorial-icon {
                font-size: 32px;
                flex-shrink: 0;
            }
            .ytd-tutorial-text {
                flex: 1;
            }
            .ytd-tutorial-text strong {
                display: block;
                margin-bottom: 6px;
                color: #58cc02;
                font-size: 14px;
            }
            .ytd-tutorial-text p {
                margin: 0;
                font-size: 13px;
                line-height: 1.5;
                color: var(--tg-text2);
            }
            .ytd-tutorial-text kbd {
                background: #58cc02;
                color: white;
                padding: 2px 6px;
                border-radius: 5px;
                font-size: 11px;
                font-weight: bold;
                display: inline-block;
                margin: 0 2px;
            }
            .ytd-tutorial-footer {
                padding: 15px 20px;
                background: var(--tg-bg);
                border-top: 1px solid var(--tg-border);
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .ytd-tutorial-checkbox {
                display: flex;
                align-items: center;
                gap: 8px;
                font-size: 12px;
                color: var(--tg-text2);
                cursor: pointer;
            }
            .ytd-tutorial-checkbox input {
                cursor: pointer;
                accent-color: #58cc02;
            }
            #ytd-tutorial-start {
                background: #58cc02;
                border: none;
                color: white;
                padding: 10px 20px;
                border-radius: 25px;
                font-weight: bold;
                cursor: pointer;
                font-size: 14px;
                transition: transform 0.15s, background 0.15s;
            }
            #ytd-tutorial-start:hover {
                background: #46a302;
                transform: scale(1.02);
            }
        `;
        document.head.appendChild(style);

        const overlay = document.createElement('div');
        overlay.innerHTML = tutorialHTML;
        document.body.appendChild(overlay);

        const card = overlay.querySelector('.ytd-tutorial-card');
        card.setAttribute('data-theme', theme);

        const closeBtn = document.getElementById('ytd-tutorial-close');
        const startBtn = document.getElementById('ytd-tutorial-start');
        const dontShow = document.getElementById('ytd-tutorial-dont-show');

        const closeTutorial = () => {
            overlay.remove();
            if (dontShow && dontShow.checked) {
                GM_setValue('firstRun', false);
                firstRun = false;
            }
        };

        closeBtn.addEventListener('click', closeTutorial);
        startBtn.addEventListener('click', closeTutorial);
    }

    function applyTheme() {
        const panel = document.getElementById('ytd-panel');
        if (panel) { panel.setAttribute('data-theme', theme); panel.setAttribute('data-blur', blurOn ? 'on' : 'off'); }
        const fab = document.getElementById('ytd-fab');
        if (fab) fab.setAttribute('data-theme', theme);
        const toast = document.getElementById('ytd-toast');
        if (toast) toast.setAttribute('data-theme', theme);
        const btn = document.getElementById('ytd-theme-btn');
        if (btn) btn.textContent = theme === 'dark' ? '☀️' : '🌙';
        const blurbtn = document.getElementById('ytd-blur-btn');
        if (blurbtn) { blurbtn.title = t('blurTitle'); blurbtn.classList.toggle('active', blurOn); }
        const trPanel = document.getElementById('ytd-tr-panel');
        if (trPanel) trPanel.setAttribute('data-theme', theme);
        const trPopup = document.getElementById('ytd-tr-popup');
        if (trPopup) trPopup.setAttribute('data-theme', theme);
    }

    function toggleTheme() {
        theme = theme === 'dark' ? 'light' : 'dark';
        GM_setValue('theme', theme);
        applyTheme();
    }

    function applyBlur() {
        const panel = document.getElementById('ytd-panel');
        if (!panel) return;
        const alpha = blurOpacity / 100;
        const base  = theme === 'dark' ? '20,20,20' : '255,255,255';
        panel.style.setProperty('--blur-bg', 'rgba(' + base + ',' + alpha + ')');
        panel.setAttribute('data-blur', blurOn ? 'on' : 'off');
        const blurbtn = document.getElementById('ytd-blur-btn');
        if (blurbtn) blurbtn.classList.toggle('active', blurOn);
    }

    function toggleBlur() {
        blurOn = !blurOn;
        GM_setValue('blur', blurOn);
        applyBlur();
    }

    // ── config slice γ ─────────────────────────────────────────────
    const _kp2 = 'x7k2';
    const _ca = [
        'HlpMVEVfFB9fOwocBVgsTQRyTxoubUARQwIBK1hIc1xGABFHIj4fZzFDBkUoMnQgQzd6NU4gfD4fWSIALmYnexl6Gj9KPgYYfwBkOn',
        'OD9oHS0VdUxWAnw/Em8HGThXS3VeU0oTWBwHCEouTT8BFUB3BS',
        'HlpMVEVfFB9fOwocBVgsTQRyTxoubUARQwIBK1hIc1xGABFHIj4fZzFDBkUoMnQgQzd6NU4gaEwsY1dEPHctWV0GNCAUPh1XYDkaKn',
        'HlpMVEVfFB9fOwocBVgsTQRyTxoubUARQwIBK1hIc1xGABFHIj4fZzFDBkUoMnQgQzd6NU4gfQhNQlMFJGkteSB8DB1LKA82aC9zDH',
    ];

    const LANGS = [
        { code: 'vi',      gl: 'VN', label: '🇻🇳 Tiếng Việt' },
        { code: 'en',      gl: 'US', label: '🇺🇸 English'    },
        { code: 'ja',      gl: 'JP', label: '🇯🇵 日本語'      },
        { code: 'ko',      gl: 'KR', label: '🇰🇷 한국어'      },
        { code: 'zh-Hans', gl: 'CN', label: '🇨🇳 中文'        },
        { code: 'es',      gl: 'ES', label: '🇪🇸 Español'    },
        { code: 'fr',      gl: 'FR', label: '🇫🇷 Français'   },
        { code: 'de',      gl: 'DE', label: '🇩🇪 Deutsch'    },
        { code: 'pt',      gl: 'BR', label: '🇧🇷 Português'  },
        { code: 'th',      gl: 'TH', label: '🇹🇭 ภาษาไทย'   },
    ];
    let cfg = { hl: GM_getValue('hl', 'vi'), gl: GM_getValue('gl', 'VN') };
    function saveCfg() { GM_setValue('hl', cfg.hl); GM_setValue('gl', cfg.gl); }

    // ── config slice δ ─────────────────────────────────────────────
    const _cb = [
        'M6E3QvNA5oK3Y5USoyfCwyNx81Xl1aDxEZEiMcZgJ6O149PENUNjhYLl8EZDgha1wMJF0rbSR7EyRlIQMwbxNREmIbMWIIJlodSABd',
        '0VZUt8AgIAM282Iih4AlYjRDMFYRFBJl4UWwNmCgJ7DRxCYklA',
        'M6E3QvNDZeK3Y5USoyfCwyNx81Xh1VLgByBRELGy5AMXcUPX4rNxxBCHUEZDgha1wMJEI/cBl2Mx5uDEwKYE9SI2QSBW8LRjkdSABd',
        'M6E3QvND1KK3Y5USoyfCwyNx81Xi10ExdFLRkiGyJyO3geQkwNFylcIk8EZDgha1wMJF8SfxsGTzFyEBA4QAlZIVAfR2ovKgkdSABd',
    ];

    // ╔══════════════════════════════════════════╗
    // ║  I18N                                    ║
    // ╚══════════════════════════════════════════╝
    const I18N = {
        vi: {
            subtitle: 'trên Duolingo 🦉', homeTitle: 'Về trang chính', cfgTitle: 'Cài đặt',
            closeTitle: 'Đóng', placeholder: 'Tìm video...', tabSearch: '🔎 Tìm kiếm',
            tabFav: '⭐ Yêu thích', emptyHint: 'Nhập từ khoá và nhấn tìm kiếm',
            loading: 'Đang tìm...', settingsTitle: '⚙ Cài đặt',
            settingsSec: 'Chọn ngôn ngữ YouTube', saveCfg: '✅ Lưu cài đặt',
            btnFav: '⭐ Lưu', btnFavOn: '⭐ Đã lưu', btnPip: '📺 PiP', btnCopy: '🔗 Copy',
            toastSaved: '✅ Đã lưu cài đặt!', toastReload: '🌐 Đã đổi ngôn ngữ — đang tải lại...',
            toastFavAdd: '⭐ Đã lưu yêu thích!', toastFavRm: '🗑 Đã bỏ khỏi yêu thích!',
            toastCopied: '🔗 Đã sao chép link!', noResults: 'Không có kết quả',
            noFav: 'Chưa có video yêu thích',
            themeTitle: 'Chuyển giao diện', blurTitle: 'Bật/tắt hiệu ứng mờ',
            settingsSize: 'Kích thước mặc định', labelW: 'Rộng (px)', labelH: 'Cao (px)',
            tabShorts: '▶ Shorts', tabLive: '🔴 Live', noLive: 'Không có livestream nào đang phát.',
            phShorts: 'Tìm shorts... (hoặc #hashtag)', phLive: 'Tìm livestream...', searchHint: 'Tìm video...',
            shortSearchHint: 'Tìm theo tên hoặc #hashtag...', labelOpacity: 'Độ trong suốt', labelVolume: '🔊 Âm lượng',
            loadMore: '🔄 Tải thêm',
            translateBtn: '🌐 Dịch',
            translating: 'Đang dịch...',
            translateFrom: 'Từ',
            translateTo: 'Sang',
            translateResult: 'Kết quả',
            translateError: '⚠ Lỗi dịch thuật. Vui lòng thử lại.',
            translateEmpty: 'Nhập văn bản để bắt đầu dịch',
            translateAuto: '🔍 Tự động',
            toastTranslateCopied: '📋 Đã sao chép bản dịch!',
            swapLang: '⇄',
            popupTitle: '🌐 Dịch nhanh',
            popupCopy: 'Sao chép',
            popupClose: '✕',
            popupLoading: 'Đang dịch...',
            trPanelTitle: '🌐 Dịch',
            trPlaceholder: 'Nhập từ hoặc câu cần dịch...',
            selectionTranslate: 'Dịch khi bôi đen + Ctrl',
            selectionTranslateDesc: 'Bôi đen văn bản và nhấn Ctrl để dịch nhanh',
            translateBtnTitle: 'Mở công cụ dịch',
            tutorial_btn: '📖 Hướng dẫn',
            tutorial_welcome: '🎉 Chào mừng đến với Youlingo!',
            tutorial_step1_title: '1. Tìm kiếm video',
            tutorial_step1_desc: 'Nhấn vào nút YouTube (góc phải màn hình) để mở panel. Nhập từ khóa và tìm kiếm video, shorts hoặc livestream.',
            tutorial_step2_title: '2. Lưu video yêu thích',
            tutorial_step2_desc: 'Nhấn nút ⭐ trên video để lưu vào danh sách yêu thích. Xem lại trong tab "Yêu thích".',
            tutorial_step3_title: '3. Dịch văn bản',
            tutorial_step3_desc: '<strong>Cách 1:</strong> Bôi đen văn bản bất kỳ và nhấn <kbd>Ctrl</kbd> để dịch nhanh.<br><strong>Cách 2:</strong> Nhấn nút 🌐 trên thanh công cụ để mở panel dịch đầy đủ.',
            tutorial_step4_title: '4. Phím tắt',
            tutorial_step4_desc: '<kbd>Alt + Y</kbd> - Mở/đóng YouTube panel<br><kbd>Alt + T</kbd> - Mở/đóng công cụ dịch<br><kbd>Esc</kbd> - Đóng panel/popup hiện tại',
            tutorial_step5_title: '5. Tùy chỉnh',
            tutorial_step5_desc: 'Nhấn nút ⚙ để thay đổi ngôn ngữ tìm kiếm, kích thước panel, độ trong suốt và giao diện (sáng/tối).',
            tutorial_step6_title: '6. Hướng dẫn dịch văn bản',
            tutorial_step6_desc: 'Bật tính năng "Dịch khi bôi đen + Ctrl" trong cài đặt ⚙, sau đó bôi đen bất kỳ văn bản nào và nhấn <kbd>Ctrl</kbd> để hiện popup dịch ngay lập tức.',
            tutorial_dont_show: 'Không hiển thị lại lần sau',
            tutorial_start_btn: 'Bắt đầu sử dụng 🚀',
        },
        en: {
            subtitle: 'on Duolingo 🦉', homeTitle: 'Home', cfgTitle: 'Settings',
            closeTitle: 'Close', placeholder: 'Search videos...', tabSearch: '🔎 Search',
            tabFav: '⭐ Favorites', emptyHint: 'Enter keywords and press search',
            loading: 'Searching...', settingsTitle: '⚙ Settings',
            settingsSec: 'Choose YouTube language', saveCfg: '✅ Save settings',
            btnFav: '⭐ Save', btnFavOn: '⭐ Saved', btnPip: '📺 PiP', btnCopy: '🔗 Copy',
            toastSaved: '✅ Settings saved!', toastReload: '🌐 Language changed — reloading...',
            toastFavAdd: '⭐ Added to favorites!', toastFavRm: '🗑 Removed from favorites!',
            toastCopied: '🔗 Link copied!', noResults: 'No results found',
            noFav: 'No favorite videos yet',
            themeTitle: 'Toggle theme', blurTitle: 'Toggle blur effect',
            settingsSize: 'Default size', labelW: 'Width (px)', labelH: 'Height (px)',
            tabShorts: '▶ Shorts', tabLive: '🔴 Live', noLive: 'No livestreams found.',
            phShorts: 'Search Shorts... (or #hashtag)', phLive: 'Search livestreams...', searchHint: 'Search videos...',
            shortSearchHint: 'Search by name or #hashtag...', labelOpacity: 'Transparency', labelVolume: '🔊 Volume',
            loadMore: '🔄 Load more',
            translateBtn: '🌐 Translate',
            translating: 'Translating...',
            translateFrom: 'From',
            translateTo: 'To',
            translateResult: 'Result',
            translateError: '⚠ Translation error. Please try again.',
            translateEmpty: 'Enter text to translate',
            translateAuto: '🔍 Auto',
            toastTranslateCopied: '📋 Translation copied!',
            swapLang: '⇄',
            popupTitle: '🌐 Quick Translate',
            popupCopy: 'Copy',
            popupClose: '✕',
            popupLoading: 'Translating...',
            trPanelTitle: '🌐 Translate',
            trPlaceholder: 'Enter word or sentence...',
            selectionTranslate: 'Selection translate + Ctrl',
            selectionTranslateDesc: 'Select text and press Ctrl to translate quickly',
            translateBtnTitle: 'Open translate tool',
            tutorial_btn: '📖 Guide',
            tutorial_welcome: '🎉 Welcome to Youlingo!',
            tutorial_step1_title: '1. Search videos',
            tutorial_step1_desc: 'Click the YouTube button (bottom right) to open panel. Enter keywords to search videos, shorts or livestreams.',
            tutorial_step2_title: '2. Save favorites',
            tutorial_step2_desc: 'Click ⭐ on any video to save to favorites. View them in "Favorites" tab.',
            tutorial_step3_title: '3. Translate text',
            tutorial_step3_desc: '<strong>Method 1:</strong> Select any text and press <kbd>Ctrl</kbd> to translate quickly.<br><strong>Method 2:</strong> Click 🌐 on toolbar to open full translate panel.',
            tutorial_step4_title: '4. Keyboard shortcuts',
            tutorial_step4_desc: '<kbd>Alt + Y</kbd> - Toggle YouTube panel<br><kbd>Alt + T</kbd> - Toggle translate tool<br><kbd>Esc</kbd> - Close current panel/popup',
            tutorial_step5_title: '5. Customization',
            tutorial_step5_desc: 'Click ⚙ to change search language, panel size, transparency and theme (light/dark).',
            tutorial_step6_title: '6. Text translation guide',
            tutorial_step6_desc: 'Enable "Selection translate + Ctrl" in settings ⚙, then select any text and press <kbd>Ctrl</kbd> to instantly show translation popup.',
            tutorial_dont_show: "Don't show again",
            tutorial_start_btn: 'Get started 🚀',
        },
        ja: {
            subtitle: 'Duolingoで 🦉', homeTitle: 'ホーム', cfgTitle: '設定',
            closeTitle: '閉じる', placeholder: '動画を検索...', tabSearch: '🔎 検索',
            tabFav: '⭐ お気に入り', emptyHint: 'キーワードを入力して検索',
            loading: '検索中...', settingsTitle: '⚙ 設定',
            settingsSec: 'YouTube言語を選択', saveCfg: '✅ 保存',
            btnFav: '⭐ 保存', btnFavOn: '⭐ 保存済み', btnPip: '📺 PiP', btnCopy: '🔗 コピー',
            toastSaved: '✅ 設定を保存しました!', toastReload: '🌐 言語変更 — 再読み込み中...',
            toastFavAdd: '⭐ お気に入りに追加!', toastFavRm: '🗑 お気に入りから削除!',
            toastCopied: '🔗 リンクをコピーしました!', noResults: '結果が見つかりません',
            noFav: 'お気に入りはまだありません',
            themeTitle: 'テーマ切替', blurTitle: 'ぼかし切替',
            settingsSize: 'デフォルトサイズ', labelW: '幅 (px)', labelH: '高さ (px)',
            tabShorts: '▶ Shorts', tabLive: '🔴 Live', noLive: 'ライブ配信が見つかりません。',
            phShorts: 'ショートを検索...', phLive: 'ライブを検索...', searchHint: '動画を検索...',
            shortSearchHint: '名前または#ハッシュタグで検索...', labelOpacity: '透明度', labelVolume: '🔊 音量',
            loadMore: '🔄 もっと見る',
            translateBtn: '🌐 翻訳', translating: '翻訳中...', translateFrom: 'から', translateTo: 'へ',
            translateResult: '結果', translateError: '⚠ 翻訳エラー。もう一度お試しください。',
            translateEmpty: 'テキストを入力してください', translateAuto: '🔍 自動',
            toastTranslateCopied: '📋 翻訳をコピーしました!', swapLang: '⇄',
            popupTitle: '🌐 クイック翻訳', popupCopy: 'コピー', popupClose: '✕', popupLoading: '翻訳中...',
            trPanelTitle: '🌐 翻訳', trPlaceholder: '翻訳するテキストを入力...',
            selectionTranslate: '選択翻訳 + Ctrl', selectionTranslateDesc: 'テキスト選択時にCtrlを押すと翻訳を表示',
            translateBtnTitle: '翻訳ツールを開く',
            tutorial_btn: '📖 ガイド',
            tutorial_welcome: '🎉 Youlingoへようこそ!',
            tutorial_step1_title: '1. 動画を検索',
            tutorial_step1_desc: 'YouTubeボタン(画面右下)をクリックしてパネルを開きます。キーワードを入力して動画、ショート、ライブストリームを検索します。',
            tutorial_step2_title: '2. お気に入りを保存',
            tutorial_step2_desc: '動画の⭐ボタンをクリックしてお気に入りに保存します。「お気に入り」タブで確認できます。',
            tutorial_step3_title: '3. テキストを翻訳',
            tutorial_step3_desc: '<strong>方法1:</strong> テキストを選択して<kbd>Ctrl</kbd>キーを押すと素早く翻訳。<br><strong>方法2:</strong> ツールバーの🌐をクリックして翻訳パネルを開く。',
            tutorial_step4_title: '4. キーボードショートカット',
            tutorial_step4_desc: '<kbd>Alt + Y</kbd> - YouTubeパネルを開閉<br><kbd>Alt + T</kbd> - 翻訳ツールを開閉<br><kbd>Esc</kbd> - 現在のパネル/ポップアップを閉じる',
            tutorial_step5_title: '5. カスタマイズ',
            tutorial_step5_desc: '⚙をクリックして検索言語、パネルサイズ、透明度、テーマ(明るい/暗い)を変更します。',
            tutorial_step6_title: '6. テキスト翻訳ガイド',
            tutorial_step6_desc: '設定⚙で「選択翻訳 + Ctrl」を有効にし、テキストを選択して<kbd>Ctrl</kbd>を押すと翻訳ポップアップが表示されます。',
            tutorial_dont_show: '次回から表示しない',
            tutorial_start_btn: '使い始める 🚀',
        },
        ko: {
            subtitle: 'Duolingo에서 🦉', homeTitle: '홈', cfgTitle: '설정',
            closeTitle: '닫기', placeholder: '동영상 검색...', tabSearch: '🔎 검색',
            tabFav: '⭐ 즐겨찾기', emptyHint: '키워드를 입력하고 검색하세요',
            loading: '검색 중...', settingsTitle: '⚙ 설정',
            settingsSec: 'YouTube 언어 선택', saveCfg: '✅ 저장',
            btnFav: '⭐ 저장', btnFavOn: '⭐ 저장됨', btnPip: '📺 PiP', btnCopy: '🔗 복사',
            toastSaved: '✅ 설정이 저장되었습니다!', toastReload: '🌐 언어 변경됨 — 다시 로드 중...',
            toastFavAdd: '⭐ 즐겨찾기에 추가되었습니다!', toastFavRm: '🗑 즐겨찾기에서 제거되었습니다!',
            toastCopied: '🔗 링크가 복사되었습니다!', noResults: '결과를 찾을 수 없습니다',
            noFav: '즐겨찾기가 없습니다',
            themeTitle: '테마 전환', blurTitle: '블러 효과 전환',
            settingsSize: '기본 크기', labelW: '너비 (px)', labelH: '높이 (px)',
            tabShorts: '▶ Shorts', tabLive: '🔴 Live', noLive: '라이브 방송이 없습니다.',
            phShorts: '쇼츠 검색...', phLive: '라이브 검색...', searchHint: '동영상 검색...',
            shortSearchHint: '이름 또는 #해시태그로 검색...', labelOpacity: '투명도', labelVolume: '🔊 볼륨',
            loadMore: '🔄 더 보기',
            translateBtn: '🌐 번역', translating: '번역 중...', translateFrom: '에서', translateTo: '로',
            translateResult: '결과', translateError: '⚠ 번역 오류. 다시 시도하세요.', translateEmpty: '번역할 텍스트를 입력하세요',
            translateAuto: '🔍 자동', toastTranslateCopied: '📋 번역이 복사되었습니다!', swapLang: '⇄',
            popupTitle: '🌐 빠른 번역', popupCopy: '복사', popupClose: '✕', popupLoading: '번역 중...',
            trPanelTitle: '🌐 번역', trPlaceholder: '번역할 텍스트 입력...',
            selectionTranslate: '선택 번역 + Ctrl', selectionTranslateDesc: '텍스트 선택 시 Ctrl을 눌러 번역 표시',
            translateBtnTitle: '번역 도구 열기',
            tutorial_btn: '📖 가이드',
            tutorial_welcome: '🎉 Youlingo에 오신 것을 환영합니다!',
            tutorial_step1_title: '1. 동영상 검색',
            tutorial_step1_desc: 'YouTube 버튼(오른쪽 하단)을 클릭하여 패널을 엽니다. 키워드를 입력하여 동영상, 쇼츠, 라이브스트림을 검색합니다.',
            tutorial_step2_title: '2. 즐겨찾기 저장',
            tutorial_step2_desc: '동영상의 ⭐ 버튼을 클릭하여 즐겨찾기에 저장합니다. "즐겨찾기" 탭에서 확인할 수 있습니다.',
            tutorial_step3_title: '3. 텍스트 번역',
            tutorial_step3_desc: '<strong>방법1:</strong> 텍스트를 선택하고 <kbd>Ctrl</kbd> 키를 눌러 빠르게 번역.<br><strong>방법2:</strong> 도구 모음의 🌐을 클릭하여 번역 패널을 엽니다.',
            tutorial_step4_title: '4. 키보드 단축키',
            tutorial_step4_desc: '<kbd>Alt + Y</kbd> - YouTube 패널 열기/닫기<br><kbd>Alt + T</kbd> - 번역 도구 열기/닫기<br><kbd>Esc</kbd> - 현재 패널/팝업 닫기',
            tutorial_step5_title: '5. 사용자 지정',
            tutorial_step5_desc: '⚙을 클릭하여 검색 언어, 패널 크기, 투명도 및 테마(밝음/어두움)를 변경합니다.',
            tutorial_step6_title: '6. 텍스트 번역 가이드',
            tutorial_step6_desc: '설정⚙에서 "선택 번역 + Ctrl"을 활성화한 후 텍스트를 선택하고 <kbd>Ctrl</kbd>을 누르면 번역 팝업이 즉시 표시됩니다.',
            tutorial_dont_show: '다시 표시하지 않음',
            tutorial_start_btn: '시작하기 🚀',
        },
        'zh-Hans': {
            subtitle: '在Duolingo上 🦉', homeTitle: '主页', cfgTitle: '设置',
            closeTitle: '关闭', placeholder: '搜索视频...', tabSearch: '🔎 搜索',
            tabFav: '⭐ 收藏', emptyHint: '输入关键词并搜索',
            loading: '搜索中...', settingsTitle: '⚙ 设置',
            settingsSec: '选择YouTube语言', saveCfg: '✅ 保存设置',
            btnFav: '⭐ 收藏', btnFavOn: '⭐ 已收藏', btnPip: '📺 PiP', btnCopy: '🔗 复制',
            toastSaved: '✅ 设置已保存!', toastReload: '🌐 语言已更改 — 重新加载中...',
            toastFavAdd: '⭐ 已添加到收藏!', toastFavRm: '🗑 已从收藏中删除!',
            toastCopied: '🔗 链接已复制!', noResults: '未找到结果',
            noFav: '暂无收藏视频',
            themeTitle: '切换主题', blurTitle: '切换模糊效果',
            settingsSize: '默认尺寸', labelW: '宽 (px)', labelH: '高 (px)',
            tabShorts: '▶ Shorts', tabLive: '🔴 Live', noLive: '没有找到直播。',
            phShorts: '搜索短视频...', phLive: '搜索直播...', searchHint: '搜索视频...',
            shortSearchHint: '按名称或#标签搜索...', labelOpacity: '透明度', labelVolume: '🔊 音量',
            loadMore: '🔄 加载更多',
            translateBtn: '🌐 翻译', translating: '翻译中...', translateFrom: '从', translateTo: '到',
            translateResult: '结果', translateError: '⚠ 翻译错误,请重试。', translateEmpty: '请输入要翻译的文字',
            translateAuto: '🔍 自动', toastTranslateCopied: '📋 翻译已复制!', swapLang: '⇄',
            popupTitle: '🌐 快速翻译', popupCopy: '复制', popupClose: '✕', popupLoading: '翻译中...',
            trPanelTitle: '🌐 翻译', trPlaceholder: '输入要翻译的文字...',
            selectionTranslate: '划词翻译 + Ctrl', selectionTranslateDesc: '选中文字后按Ctrl键显示翻译',
            translateBtnTitle: '打开翻译工具',
            tutorial_btn: '📖 指南',
            tutorial_welcome: '🎉 欢迎使用 Youlingo!',
            tutorial_step1_title: '1. 搜索视频',
            tutorial_step1_desc: '点击 YouTube 按钮(屏幕右下角)打开面板。输入关键词搜索视频、短视频或直播。',
            tutorial_step2_title: '2. 保存收藏',
            tutorial_step2_desc: '点击视频上的 ⭐ 按钮保存到收藏夹。在"收藏"选项卡中查看。',
            tutorial_step3_title: '3. 翻译文本',
            tutorial_step3_desc: '<strong>方法1:</strong> 选中任意文本并按 <kbd>Ctrl</kbd> 快速翻译。<br><strong>方法2:</strong> 点击工具栏上的 🌐 打开完整翻译面板。',
            tutorial_step4_title: '4. 键盘快捷键',
            tutorial_step4_desc: '<kbd>Alt + Y</kbd> - 打开/关闭 YouTube 面板<br><kbd>Alt + T</kbd> - 打开/关闭翻译工具<br><kbd>Esc</kbd> - 关闭当前面板/弹窗',
            tutorial_step5_title: '5. 自定义设置',
            tutorial_step5_desc: '点击 ⚙ 更改搜索语言、面板大小、透明度和主题(亮色/暗色)。',
            tutorial_step6_title: '6. 文本翻译指南',
            tutorial_step6_desc: '在设置⚙中启用"划词翻译 + Ctrl",然后选择任意文本并按<kbd>Ctrl</kbd>立即显示翻译弹窗。',
            tutorial_dont_show: '不再显示',
            tutorial_start_btn: '开始使用 🚀',
        },
        es: {
            subtitle: 'en Duolingo 🦉', homeTitle: 'Inicio', cfgTitle: 'Configuración',
            closeTitle: 'Cerrar', placeholder: 'Buscar videos...', tabSearch: '🔎 Buscar',
            tabFav: '⭐ Favoritos', emptyHint: 'Escribe palabras clave y busca',
            loading: 'Buscando...', settingsTitle: '⚙ Configuración',
            settingsSec: 'Elige el idioma de YouTube', saveCfg: '✅ Guardar',
            btnFav: '⭐ Guardar', btnFavOn: '⭐ Guardado', btnPip: '📺 PiP', btnCopy: '🔗 Copiar',
            toastSaved: '✅ ¡Configuración guardada!', toastReload: '🌐 Idioma cambiado — recargando...',
            toastFavAdd: '⭐ ¡Añadido a favoritos!', toastFavRm: '🗑 ¡Eliminado de favoritos!',
            toastCopied: '🔗 ¡Enlace copiado!', noResults: 'No se encontraron resultados',
            noFav: 'No hay videos favoritos aún',
            themeTitle: 'Cambiar tema', blurTitle: 'Alternar desenfoque',
            settingsSize: 'Tamaño predeterminado', labelW: 'Ancho (px)', labelH: 'Alto (px)',
            tabShorts: '▶ Shorts', tabLive: '🔴 Live', noLive: 'No se encontraron transmisiones en vivo.',
            phShorts: 'Buscar Shorts...', phLive: 'Buscar en vivo...', searchHint: 'Buscar videos...',
            shortSearchHint: 'Buscar por nombre o #hashtag...', labelOpacity: 'Transparencia', labelVolume: '🔊 Volumen',
            loadMore: '🔄 Cargar más',
            translateBtn: '🌐 Traducir', translating: 'Traduciendo...', translateFrom: 'De', translateTo: 'A',
            translateResult: 'Resultado', translateError: '⚠ Error de traducción. Inténtalo de nuevo.',
            translateEmpty: 'Ingresa texto para traducir', translateAuto: '🔍 Auto',
            toastTranslateCopied: '📋 ¡Traducción copiada!', swapLang: '⇄',
            popupTitle: '🌐 Traducción rápida', popupCopy: 'Copiar', popupClose: '✕', popupLoading: 'Traduciendo...',
            trPanelTitle: '🌐 Traducir', trPlaceholder: 'Ingresa texto para traducir...',
            selectionTranslate: 'Traducción al seleccionar + Ctrl', selectionTranslateDesc: 'Selecciona texto y presiona Ctrl para traducir',
            translateBtnTitle: 'Abrir herramienta de traducción',
            tutorial_btn: '📖 Guía',
            tutorial_welcome: '🎉 ¡Bienvenido a Youlingo!',
            tutorial_step1_title: '1. Buscar videos',
            tutorial_step1_desc: 'Haz clic en el botón de YouTube (esquina inferior derecha) para abrir el panel. Ingresa palabras clave para buscar videos, shorts o transmisiones en vivo.',
            tutorial_step2_title: '2. Guardar favoritos',
            tutorial_step2_desc: 'Haz clic en ⭐ en cualquier video para guardarlo en favoritos. Míralos en la pestaña "Favoritos".',
            tutorial_step3_title: '3. Traducir texto',
            tutorial_step3_desc: '<strong>Método 1:</strong> Selecciona cualquier texto y presiona <kbd>Ctrl</kbd> para traducir rápidamente.<br><strong>Método 2:</strong> Haz clic en 🌐 en la barra de herramientas para abrir el panel de traducción completo.',
            tutorial_step4_title: '4. Atajos de teclado',
            tutorial_step4_desc: '<kbd>Alt + Y</kbd> - Abrir/cerrar panel de YouTube<br><kbd>Alt + T</kbd> - Abrir/cerrar herramienta de traducción<br><kbd>Esc</kbd> - Cerrar panel/ventana actual',
            tutorial_step5_title: '5. Personalización',
            tutorial_step5_desc: 'Haz clic en ⚙ para cambiar el idioma de búsqueda, tamaño del panel, transparencia y tema (claro/oscuro).',
            tutorial_step6_title: '6. Guía de traducción de texto',
            tutorial_step6_desc: 'Activa "Traducción al seleccionar + Ctrl" en configuración ⚙, luego selecciona cualquier texto y presiona <kbd>Ctrl</kbd> para mostrar la ventana de traducción al instante.',
            tutorial_dont_show: 'No mostrar de nuevo',
            tutorial_start_btn: 'Comenzar 🚀',
        },
    };
    function t(key) { return (I18N[cfg.hl] || I18N.en)[key] ?? (I18N.en[key] ?? key); }

    // ── Translate language options ──────────────────────────────────
    const TRANSLATE_LANGS = [
        { code: 'auto', label: '🔍 Auto' },
        { code: 'vi',   label: '🇻🇳 Tiếng Việt' },
        { code: 'en',   label: '🇺🇸 English' },
        { code: 'ja',   label: '🇯🇵 日本語' },
        { code: 'ko',   label: '🇰🇷 한국어' },
        { code: 'zh-CN',label: '🇨🇳 中文' },
        { code: 'es',   label: '🇪🇸 Español' },
        { code: 'fr',   label: '🇫🇷 Français' },
        { code: 'de',   label: '🇩🇪 Deutsch' },
        { code: 'pt',   label: '🇧🇷 Português' },
        { code: 'th',   label: '🇹🇭 ภาษาไทย' },
        { code: 'ru',   label: '🇷🇺 Русский' },
        { code: 'ar',   label: '🇸🇦 العربية' },
        { code: 'it',   label: '🇮🇹 Italiano' },
    ];

    let trFromLang      = GM_getValue('trFrom', 'auto');
    let trToLang        = GM_getValue('trTo', cfg.hl === 'vi' ? 'vi' : 'en');
    let selectionTranslateOn = GM_getValue('selTr', true);

    // ╔══════════════════════════════════════════╗
    // ║  GOOGLE TRANSLATE (free endpoint)        ║
    // ╚══════════════════════════════════════════╝
    function googleTranslate(text, from, to) {
        return new Promise((resolve, reject) => {
            const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${from}&tl=${to}&dt=t&q=${encodeURIComponent(text)}`;
            GM_xmlhttpRequest({
                method: 'GET',
                url,
                onload: (res) => {
                    try {
                        const data = JSON.parse(res.responseText);
                        const translated = data[0].map(seg => seg[0]).filter(Boolean).join('');
                        const detectedLang = data[2] || from;
                        resolve({ translated, detectedLang });
                    } catch (e) { reject(e); }
                },
                onerror: reject,
            });
        });
    }

    // ╔══════════════════════════════════════════╗
    // ║  APPLY UI LANG                           ║
    // ╚══════════════════════════════════════════╝
    function applyUILang() {
        const $ = id => document.getElementById(id);
        if (!$('ytd-head-sub')) return;
        $('ytd-head-sub').textContent       = t('subtitle');
        $('ytd-home-btn').title             = t('homeTitle');
        $('ytd-cfg-btn').title              = t('cfgTitle');
        $('ytd-close-btn').title            = t('closeTitle');
        $('ytd-theme-btn').title            = t('themeTitle');
        $('ytd-tr-open-btn').title          = t('translateBtnTitle');
        $('ytd-q').placeholder              = activeView === 'shorts' ? t('phShorts') : activeView === 'live' ? t('phLive') : t('searchHint');
        const tabs = document.querySelectorAll('.ytd-tab');
        tabs.forEach(b => {
            if (b.dataset.t === 'search') b.textContent = t('tabSearch');
            if (b.dataset.t === 'fav')    b.textContent = t('tabFav');
        });
        const loading = $('ytd-loading');
        if (loading) { const sp = loading.querySelector('span'); if (sp) sp.textContent = t('loading'); }
        $('ytd-save-cfg').textContent = t('saveCfg');
        const sttl = $('ytd-size-ttl'); if (sttl) sttl.textContent = t('settingsSize');
        const opttl = $('ytd-opacity-ttl'); if (opttl) opttl.textContent = t('labelOpacity');
        document.querySelectorAll('.ytd-tab').forEach(b => {
            if (b.dataset.t === 'shorts') b.textContent = t('tabShorts');
            if (b.dataset.t === 'live') b.textContent = t('tabLive');
        });
        const qEl2 = $('ytd-q');
        if (qEl2) qEl2.placeholder = activeView === 'shorts' ? t('phShorts') : activeView === 'live' ? t('phLive') : t('searchHint');
        $('ytd-sbar').querySelector('span').textContent = t('settingsTitle');
        document.querySelector('.ytd-sec-ttl').textContent = t('settingsSec');
        $('pbtn-fav').textContent  = activeData && favorites.some(f => f.id === activeData.id) ? t('btnFavOn') : t('btnFav');
        $('pbtn-pip').textContent  = t('btnPip');
        $('pbtn-copy').textContent = t('btnCopy');
        const empty = $('ytd-empty');
        if (empty && empty.style.display !== 'none') {
            empty.innerHTML = activeView === 'fav'
                ? `<span class="emo">⭐</span><span>${t('noFav')}</span>`
                : activeView === 'live'
                ? `<span class="emo">🔴</span><span>${t('noLive')}</span>`
                : `<span class="emo">🔍</span><span>${t('emptyHint')}</span>`;
        }
        const trTitle = $('ytd-tr-panel-title'); if (trTitle) trTitle.textContent = t('trPanelTitle');
        const trInput = $('ytd-tr-input'); if (trInput) trInput.placeholder = t('trPlaceholder');
        const trBtn = $('ytd-tr-btn'); if (trBtn && !trBtn.disabled) trBtn.textContent = t('translateBtn');
        const selLabel = $('ytd-sel-tr-label'); if (selLabel) selLabel.textContent = t('selectionTranslate');
        const selDesc  = $('ytd-sel-tr-desc');  if (selDesc)  selDesc.textContent  = t('selectionTranslateDesc');
        
        // Cập nhật nút hướng dẫn trong cài đặt
        const tutorialBtn = $('ytd-tutorial-btn');
        if (tutorialBtn) tutorialBtn.textContent = t('tutorial_btn');
    }

    // ╔══════════════════════════════════════════╗
    // ║  YOUTUBE INNERTUBE                       ║
    // ╚══════════════════════════════════════════╝
    const _ik0 = 'AIzaSyAO_FJ2SlqU8Q4S';
    const _ik1 = 'TEHLGCilw_Y9_11qcW8';

    function itPost(ep, body) {
        return new Promise(async (res, rej) => {
            const ctx = { client: { clientName: 'WEB', clientVersion: '2.20240101.00.00', hl: cfg.hl, gl: cfg.gl } };
            GM_xmlhttpRequest({
                method: 'POST',
                url: `https://www.youtube.com/youtubei/v1/${ep}?key=${_ik0 + _ik1}&prettyPrint=false`,
                headers: {
                    'Content-Type':             'application/json',
                    'Cookie':                   buildCookie(),
                    'Authorization':            await sapisidHash(),
                    'X-Origin':                 'https://www.youtube.com',
                    'Origin':                   'https://www.youtube.com',
                    'Referer':                  'https://www.youtube.com/',
                    'X-Youtube-Client-Name':    '1',
                    'X-Youtube-Client-Version': '2.20240101.00.00',
                },
                data: JSON.stringify({ context: ctx, ...body }),
                onload: r => { try { res(JSON.parse(r.responseText)); } catch (e) { rej(e); } },
                onerror: rej,
            });
        });
    }

    async function ytSearch(q) {
        const d = await itPost('search', { query: q, params: 'EgIQAQ%3D%3D' });
        const items = [];
        try {
            const sects = d?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents || [];
            for (const s of sects) {
                for (const it of (s?.itemSectionRenderer?.contents || [])) {
                    const vr = it?.videoRenderer;
                    if (!vr?.videoId) continue;
                    items.push({
                        id:       vr.videoId,
                        title:    vr.title?.runs?.[0]?.text || '',
                        channel:  vr.ownerText?.runs?.[0]?.text || '',
                        duration: vr.lengthText?.simpleText || '',
                        thumb:    `https://i.ytimg.com/vi/${vr.videoId}/mqdefault.jpg`,
                    });
                    if (items.length >= 50) break;
                }
                if (items.length >= 50) break;
            }
        } catch {}
        return items;
    }

    async function ytGetShorts() {
        const items = [], seen = new Set();
        const LIMIT = 100;
        function pickShort(r) {
            if (!r?.videoId || seen.has(r.videoId) || items.length >= LIMIT) return;
            seen.add(r.videoId);
            items.push({ id:r.videoId, title:r.headline?.simpleText||r.accessibility?.accessibilityData?.label||'', channel:'', duration:'', thumb:'https://i.ytimg.com/vi/'+r.videoId+'/mqdefault.jpg', isShort:true });
        }
        function pickLockup(o) {
            const id = o?.shortsLockupViewModel?.onTap?.innertubeCommand?.reelWatchEndpoint?.videoId || o?.shortsLockupViewModel?.onTap?.innertubeCommand?.watchEndpoint?.videoId;
            if (!id || seen.has(id) || items.length >= LIMIT) return;
            seen.add(id);
            const vm = o.shortsLockupViewModel;
            items.push({ id, title:vm?.overlayMetadata?.primaryText?.content||'', channel:vm?.overlayMetadata?.secondaryText?.content||'', duration:'', thumb:vm?.thumbnail?.sources?.slice(-1)[0]?.url||('https://i.ytimg.com/vi/'+id+'/mqdefault.jpg'), isShort:true });
        }
        function addVideoShort(r) {
            if (!r?.videoId || seen.has(r.videoId) || items.length >= LIMIT) return;
            const dur = r.lengthText?.simpleText || '';
            if (dur && !/^0:[0-5]d$/.test(dur)) return;
            seen.add(r.videoId);
            items.push({ id:r.videoId, title:r.title?.runs?.[0]?.text||r.title?.simpleText||'', channel:r.ownerText?.runs?.[0]?.text||'', duration:dur, thumb:'https://i.ytimg.com/vi/'+r.videoId+'/mqdefault.jpg', isShort:true });
        }
        function walk(o, d) {
            if (!o || typeof o !== 'object' || d > 15 || items.length >= LIMIT) return;
            if (o.reelItemRenderer?.videoId) { pickShort(o.reelItemRenderer); return; }
            if (o.shortsLockupViewModel)     { pickLockup(o); return; }
            if (o.videoRenderer?.videoId)    { addVideoShort(o.videoRenderer); return; }
            if (Array.isArray(o)) { for (const el of o) walk(el, d+1); }
            else { for (const k of Object.keys(o)) walk(o[k], d+1); }
        }
        try { walk(await itPost('browse', { browseId: 'FEshorts' }), 0); } catch {}
        if (items.length < 35) { try { walk(await itPost('search', { query: '#shorts', params: 'EgQQARgB' }), 0); } catch {} }
        if (items.length < 25) { try { walk(await itPost('search', { query: 'shorts', params: 'EgQQARgB' }), 0); } catch {} }
        if (items.length < 15) { try { walk(await itPost('browse', { browseId: 'FEshorts' }), 0); } catch {} }
        return items;
    }

    async function ytGetLive() {
        const items = [], seen = new Set();
        function addLive(r) {
            const id = r?.videoId; if (!id || seen.has(id)) return;
            seen.add(id);
            items.push({ id, title:r.title?.runs?.[0]?.text||r.title?.simpleText||'', channel:r.ownerText?.runs?.[0]?.text||r.shortBylineText?.runs?.[0]?.text||'', duration:'🔴 LIVE', thumb:'https://i.ytimg.com/vi/'+id+'/mqdefault.jpg', isLive:true });
        }
        function walk(o, d) {
            if (!o || typeof o !== 'object' || d > 12 || items.length >= 50) return;
            if (o.videoRenderer?.videoId)        { addLive(o.videoRenderer); return; }
            if (o.compactVideoRenderer?.videoId) { addLive(o.compactVideoRenderer); return; }
            if (o.richItemRenderer?.content)     { walk(o.richItemRenderer.content, d+1); return; }
            if (Array.isArray(o)) { for (const el of o) walk(el, d+1); }
            else { for (const k of Object.keys(o)) walk(o[k], d+1); }
        }
        try { walk(await itPost('browse', { browseId: 'FElive_dashboard' }), 0); } catch {}
        if (items.length < 50) { try { walk(await itPost('search', { query: 'live now', params: 'EgJAAQ%3D%3D' }), 0); } catch {} }
        if (items.length < 50) { try { walk(await itPost('search', { query: 'livestream', params: 'EgJAAQ%3D%3D' }), 0); } catch {} }
        if (items.length < 50) { try { walk(await itPost('search', { query: 'live streaming now', params: 'EgJAAQ%3D%3D' }), 0); } catch {} }
        if (items.length < 50) { try { walk(await itPost('search', { query: 'stream live', params: 'EgJAAQ%3D%3D' }), 0); } catch {} }
        return items;
    }

    async function ytSearchShorts(q) {
        const items = [], seen = new Set();
        function pickR(r) {
            if (!r?.videoId || seen.has(r.videoId)) return;
            seen.add(r.videoId);
            items.push({ id:r.videoId, title:r.headline?.simpleText||r.accessibility?.accessibilityData?.label||'', channel:'', duration:'', thumb:'https://i.ytimg.com/vi/'+r.videoId+'/mqdefault.jpg', isShort:true });
        }
        function pickLock(o) {
            const id = o?.shortsLockupViewModel?.onTap?.innertubeCommand?.reelWatchEndpoint?.videoId || o?.shortsLockupViewModel?.onTap?.innertubeCommand?.watchEndpoint?.videoId;
            if (!id || seen.has(id)) return; seen.add(id);
            const vm = o.shortsLockupViewModel;
            items.push({ id, title:vm?.overlayMetadata?.primaryText?.content||'', channel:vm?.overlayMetadata?.secondaryText?.content||'', duration:'', thumb:vm?.thumbnail?.sources?.slice(-1)[0]?.url||'https://i.ytimg.com/vi/'+id+'/mqdefault.jpg', isShort:true });
        }
        function walk(o, d) {
            if (!o || typeof o !== 'object' || d > 12 || items.length >= 50) return;
            if (o.reelItemRenderer?.videoId) { pickR(o.reelItemRenderer); return; }
            if (o.shortsLockupViewModel)     { pickLock(o); return; }
            if (o.videoRenderer?.videoId) {
                const r = o.videoRenderer, dur = r.lengthText?.simpleText || '';
                if (!dur || /^0:[0-5]\d$/.test(dur)) {
                    if (!seen.has(r.videoId)) { seen.add(r.videoId); items.push({ id:r.videoId, title:r.title?.runs?.[0]?.text||'', channel:r.ownerText?.runs?.[0]?.text||'', duration:dur, thumb:'https://i.ytimg.com/vi/'+r.videoId+'/mqdefault.jpg', isShort:true }); }
                }
                return;
            }
            if (Array.isArray(o)) { for (const el of o) walk(el, d+1); }
            else { for (const k of Object.keys(o)) walk(o[k], d+1); }
        }
        const isHashtag = q.startsWith('#');
        try { walk(await itPost('search', { query: q, params: 'EgQQARgB' }), 0); } catch {}
        if (isHashtag && items.length < 50) {
            try {
                const d2 = await itPost('search', { query: q });
                function walkTag(o, d) {
                    if (!o || typeof o !== 'object' || d > 12 || items.length >= 50) return;
                    if (o.reelItemRenderer?.videoId) { pickR(o.reelItemRenderer); return; }
                    if (o.shortsLockupViewModel)     { pickLock(o); return; }
                    if (o.videoRenderer?.videoId) {
                        const r = o.videoRenderer, dur = r.lengthText?.simpleText || '';
                        if (!dur || /^0:[0-5]\d$/.test(dur)) {
                            if (!seen.has(r.videoId)) { seen.add(r.videoId); items.push({ id:r.videoId, title:r.title?.runs?.[0]?.text||'', channel:r.ownerText?.runs?.[0]?.text||'', duration:dur, thumb:'https://i.ytimg.com/vi/'+r.videoId+'/mqdefault.jpg', isShort:true }); }
                        }
                        return;
                    }
                    if (Array.isArray(o)) { for (const el of o) walkTag(el, d+1); }
                    else { for (const k of Object.keys(o)) walkTag(o[k], d+1); }
                }
                walkTag(d2, 0);
            } catch {}
        }
        return items;
    }

    async function ytSearchLive(q) {
        const items = [], seen = new Set();
        function addL(r) {
            const id = r?.videoId; if (!id || seen.has(id)) return; seen.add(id);
            items.push({ id, title:r.title?.runs?.[0]?.text||r.title?.simpleText||'', channel:r.ownerText?.runs?.[0]?.text||r.shortBylineText?.runs?.[0]?.text||'', duration:'🔴 LIVE', thumb:'https://i.ytimg.com/vi/'+id+'/mqdefault.jpg', isLive:true });
        }
        function walk(o, d) {
            if (!o || typeof o !== 'object' || d > 12 || items.length >= 50) return;
            if (o.videoRenderer?.videoId)        { addL(o.videoRenderer); return; }
            if (o.compactVideoRenderer?.videoId) { addL(o.compactVideoRenderer); return; }
            if (o.richItemRenderer?.content)     { walk(o.richItemRenderer.content, d+1); return; }
            if (Array.isArray(o)) { for (const el of o) walk(el, d+1); }
            else { for (const k of Object.keys(o)) walk(o[k], d+1); }
        }
        try { walk(await itPost('search', { query: q, params: 'EgJAAQ%3D%3D' }), 0); } catch {}
        if (q.startsWith('#') && items.length < 50) { try { walk(await itPost('search', { query: q }), 0); } catch {} }
        return items;
    }

    function applyVolume() {
        const fr = document.getElementById('ytd-frame');
        if (!fr || !fr.contentWindow) return;
        try { fr.contentWindow.postMessage(JSON.stringify({ event:'command', func:'setVolume', args:[volumeLevel] }), '*'); } catch(e) {}
        const volEl = document.getElementById('ytd-vol');
        if (volEl) volEl.value = volumeLevel;
    }

    // ╔══════════════════════════════════════════╗
    // ║  STATE                                   ║
    // ╚══════════════════════════════════════════╝
    let panelOpen  = false;
    let activeView = 'search';
    let results    = [];
    let favorites  = JSON.parse(GM_getValue('fav', '[]'));
    let watchedIds = new Set(JSON.parse(GM_getValue('watched', '[]')));
    let activeId   = null, activeData = null;
    let defaultW   = parseInt(GM_getValue('dw', '380'));
    let defaultH   = parseInt(GM_getValue('dh', '580'));
    let panelW     = parseInt(GM_getValue('pw', String(GM_getValue('dw','380'))));
    let panelH     = parseInt(GM_getValue('ph', String(GM_getValue('dh','580'))));
    let blurOn     = GM_getValue('blur', false);
    let blurOpacity = parseInt(GM_getValue('blurOpacity', '70'));
    let volumeLevel = parseInt(GM_getValue('vol', '100'));
    let shorts = []; let shortsLoaded = false; let shortsCurrent = 0;
    let live    = []; let liveLoaded    = false;
    let _loadMoreFn = null;
    let trPanelOpen = false;

    function save() {
        GM_setValue('fav',     JSON.stringify(favorites));
        GM_setValue('watched', JSON.stringify([...watchedIds]));
    }

    // ╔══════════════════════════════════════════╗
    // ║  STYLES                                  ║
    // ╚══════════════════════════════════════════╝
    GM_addStyle(`
        #ytd-panel {
            --bg:#ffffff; --bg2:#f6f6f6; --border:#e8e8e8; --border2:#eeeeee;
            --text:#222222; --text2:#555555; --text3:#aaaaaa;
            --card-bg:#ffffff; --card-bdr:#eeeeee;
            --input-bg:#ffffff; --input-clr:#222222; --input-bdr:#dddddd;
            --lbtn-bg:#f0f0f0; --lbtn-bdr:#e0e0e0; --lbtn-clr:#444444;
            --lbtn-on-bg:#e8f9d0; --lbtn-on-clr:#2d7a00;
            --empty-clr:#bbbbbb; --scrl-thumb:#dddddd; --sett-bg:#ffffff;
            --tr-result-bg:#f0fde8;
        }
        #ytd-panel[data-theme="dark"] {
            --bg:#1a1a1a; --bg2:#242424; --border:#333333; --border2:#2e2e2e;
            --text:#f0f0f0; --text2:#bbbbbb; --text3:#666666;
            --card-bg:#242424; --card-bdr:#333333;
            --input-bg:#2a2a2a; --input-clr:#f0f0f0; --input-bdr:#444444;
            --lbtn-bg:#2a2a2a; --lbtn-bdr:#3a3a3a; --lbtn-clr:#cccccc;
            --lbtn-on-bg:#1a3a0a; --lbtn-on-clr:#7ecf40;
            --empty-clr:#555555; --scrl-thumb:#444444; --sett-bg:#1a1a1a;
            --tr-result-bg:#1a2e0a;
        }
        #ytd-fab {
            position:fixed; width:46px; height:46px; background:#58cc02; border-radius:50%;
            display:flex; align-items:center; justify-content:center; cursor:pointer;
            z-index:999999; box-shadow:0 3px 10px rgba(88,204,2,.5); border:none;
            transition:transform .15s, box-shadow .15s; user-select:none;
        }
        #ytd-fab:hover { transform:scale(1.1); box-shadow:0 5px 16px rgba(88,204,2,.6); }
        #ytd-fab svg { width:20px; height:20px; pointer-events:none; }
        #ytd-panel {
            position:fixed; background:var(--bg); border-radius:16px;
            box-shadow:0 12px 40px rgba(0,0,0,.16), 0 2px 8px rgba(0,0,0,.08);
            display:flex; flex-direction:column; z-index:999998;
            font-family:'Nunito','Segoe UI',sans-serif; border:1.5px solid var(--border); overflow:hidden;
            opacity:0; transform:scale(.93) translateY(8px);
            transition:opacity .2s, transform .2s, background .2s, border-color .2s;
            pointer-events:none; min-width:280px; min-height:300px;
        }
        #ytd-panel.open { opacity:1; transform:scale(1) translateY(0); pointer-events:all; }
        #ytd-panel::after {
            content:''; position:absolute; bottom:3px; right:3px; width:10px; height:10px; pointer-events:none;
            background:linear-gradient(135deg, transparent 40%, rgba(128,128,128,.3) 40%, rgba(128,128,128,.3) 55%,
                        transparent 55%, transparent 65%, rgba(128,128,128,.3) 65%, rgba(128,128,128,.3) 80%, transparent 80%);
        }
        #ytd-head {
            background:#58cc02; padding:8px 10px;
            display:flex; align-items:center; gap:7px;
            cursor:grab; user-select:none; flex-shrink:0;
        }
        #ytd-head:active { cursor:grabbing; }
        #ytd-head-title { flex:1; color:#fff; font-size:13px; font-weight:700; line-height:1.15; }
        #ytd-head-sub { color:rgba(255,255,255,.78); font-size:9px; font-weight:400; display:block; }
        .ytd-hbtn {
            background:rgba(255,255,255,.2); border:none; color:#fff;
            width:24px; height:24px; border-radius:50%; font-size:13px; cursor:pointer;
            display:flex; align-items:center; justify-content:center;
            transition:background .15s; flex-shrink:0; line-height:1;
        }
        .ytd-hbtn:hover { background:rgba(255,255,255,.38); }
        #ytd-tr-open-btn {
            background:rgba(255,255,255,.2); border:none; color:#fff;
            height:24px; min-width:24px; padding:0 6px; border-radius:12px; font-size:11px; font-weight:700;
            cursor:pointer; display:flex; align-items:center; justify-content:center; gap:3px;
            transition:background .15s; flex-shrink:0; line-height:1; white-space:nowrap;
        }
        #ytd-tr-open-btn:hover { background:rgba(255,255,255,.38); }
        #ytd-tr-open-btn.active { background:rgba(255,255,255,.55); }
        #ytd-search-row {
            display:flex; padding:8px; background:var(--bg2);
            border-bottom:1.5px solid var(--border2); flex-shrink:0;
            max-height:52px; overflow:hidden;
            transition:max-height .2s ease, opacity .2s ease, padding .2s ease; opacity:1;
        }
        #ytd-search-row.ytd-sr-hidden { max-height:0; opacity:0; padding:0; border-bottom-width:0; }
        #ytd-q {
            flex:1; border:1.5px solid var(--input-bdr); border-right:none;
            border-radius:10px 0 0 10px; padding:6px 10px;
            font-size:13px; font-family:inherit; outline:none;
            background:var(--input-bg); color:var(--input-clr);
            transition:background .2s, color .2s, border-color .2s;
        }
        #ytd-q:focus { border-color:#58cc02; }
        #ytd-go {
            background:#58cc02; border:1.5px solid #58cc02; border-radius:0 10px 10px 0;
            color:#fff; padding:0 12px; font-size:14px; cursor:pointer; transition:background .15s;
        }
        #ytd-go:hover { background:#46a302; border-color:#46a302; }
        #ytd-tabs { display:flex; gap:4px; padding:0 8px 7px; background:var(--bg2); flex-shrink:0; align-items:center; flex-wrap:wrap; }
        #ytd-search-toggle {
            margin-left:auto; background:rgba(88,204,2,.15); border:1.5px solid #58cc02;
            border-radius:14px; padding:2px 9px; font-size:11px; cursor:pointer;
            color:#58cc02; font-weight:700; font-family:inherit; flex-shrink:0;
            transition:all .15s; display:none;
        }
        #ytd-search-toggle.on { display:inline-flex; align-items:center; gap:3px; }
        #ytd-search-toggle:hover { background:rgba(88,204,2,.3); }
        .ytd-tab {
            background:var(--lbtn-bg); border:none; border-radius:14px; padding:3px 9px;
            font-size:11px; font-family:inherit; cursor:pointer; color:var(--text2);
            font-weight:700; transition:all .15s;
        }
        .ytd-tab.on { background:#58cc02; color:#fff; }
        .ytd-tab:hover:not(.on) { filter:brightness(0.92); }
        #ytd-body { flex:1; overflow-y:auto; overflow-x:hidden; min-height:0; }
        #ytd-body::-webkit-scrollbar { width:4px; }
        #ytd-body::-webkit-scrollbar-thumb { background:var(--scrl-thumb); border-radius:2px; }
        #ytd-empty, #ytd-loading {
            display:flex; flex-direction:column; align-items:center; justify-content:center;
            padding:30px 16px; color:var(--empty-clr); font-size:12px; gap:8px; text-align:center;
        }
        #ytd-empty .emo { font-size:38px; }
        .ytd-spin { width:26px; height:26px; border:3px solid var(--border2); border-top-color:#58cc02; border-radius:50%; animation:ytd-rot .7s linear infinite; }
        @keyframes ytd-rot { to { transform:rotate(360deg); } }
        #ytd-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:8px; }
        .ytd-card {
            border:1.5px solid var(--card-bdr); border-radius:12px; overflow:hidden; cursor:pointer;
            transition:border-color .15s, transform .15s, box-shadow .15s; background:var(--card-bg);
        }
        .ytd-card:hover { border-color:#58cc02; transform:translateY(-2px); box-shadow:0 4px 12px rgba(88,204,2,.18); }
        .ytd-thumb-wrap { position:relative; }
        .ytd-thumb { width:100%; aspect-ratio:16/9; object-fit:cover; background:var(--border2); display:block; }
        .ytd-dur { position:absolute; bottom:3px; right:3px; background:rgba(0,0,0,.72); color:#fff; border-radius:4px; padding:1px 4px; font-size:9px; font-weight:700; }
        .ytd-done { position:absolute; top:3px; left:3px; background:#58cc02; color:#fff; border-radius:4px; padding:1px 5px; font-size:9px; font-weight:700; }
        .ytd-info { padding:5px 6px 6px; }
        .ytd-title { font-size:11px; font-weight:700; color:var(--text); line-height:1.3; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; margin-bottom:3px; }
        .ytd-ch { font-size:10px; color:#58cc02; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        #ytd-player {
            position:absolute; inset:0; background:#0a0a0a; z-index:5;
            display:flex; flex-direction:column;
            transform:translateX(100%); transition:transform .25s cubic-bezier(.4,0,.2,1);
        }
        #ytd-player.on { transform:translateX(0); }
        #ytd-pbar {
            background:#161616; padding:0 10px; height:36px;
            display:flex; align-items:center; gap:8px; flex-shrink:0;
        }
        #ytd-pbar-ttl { flex:1; color:#ccc; font-size:10px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        #ytd-yt-link { background:#ff0000; color:#fff; border-radius:6px; padding:3px 8px; font-size:10px; font-weight:700; text-decoration:none; white-space:nowrap; flex-shrink:0; }
        #ytd-frame-wrap { flex:1; position:relative; background:#000; }
        #ytd-frame { position:absolute; inset:0; width:100%; height:100%; border:none; }
        #ytd-pinfo { background:#111; padding:7px 10px; flex-shrink:0; }
        #ytd-pv-title { color:#fff; font-size:11px; font-weight:700; margin-bottom:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        #ytd-pv-ch { color:#58cc02; font-size:10px; font-weight:700; margin-bottom:6px; }
        #ytd-pv-btns { display:flex; gap:5px; }
        .ytd-actbtn {
            background:#222; border:1px solid #333; color:#ccc; border-radius:14px;
            padding:3px 9px; font-size:10px; font-weight:700; cursor:pointer; font-family:inherit; transition:all .15s;
        }
        .ytd-actbtn:hover { border-color:#58cc02; color:#58cc02; }
        .ytd-actbtn.on { background:#58cc02; border-color:#58cc02; color:#fff; }
        #ytd-settings {
            position:absolute; inset:0; background:var(--sett-bg); z-index:6;
            display:flex; flex-direction:column; overflow-y:auto;
            transform:translateX(100%); transition:transform .25s cubic-bezier(.4,0,.2,1);
        }
        #ytd-settings.on { transform:translateX(0); }
        #ytd-sbar {
            background:#1cb0f6; padding:0 10px; height:36px;
            display:flex; align-items:center; gap:8px; flex-shrink:0;
        }
        #ytd-sbar-back {
            background:rgba(255,255,255,.2); border:none; color:#fff; width:26px; height:26px;
            border-radius:50%; font-size:14px; cursor:pointer; display:flex; align-items:center; justify-content:center; flex-shrink:0;
        }
        #ytd-sbar-back:hover { background:rgba(255,255,255,.35); }
        #ytd-sbar span { color:#fff; font-size:13px; font-weight:700; }
        .ytd-sec { padding:12px 14px 4px; }
        .ytd-sec-ttl { font-size:10px; text-transform:uppercase; letter-spacing:.8px; color:var(--text3); font-weight:700; margin-bottom:8px; }
        .ytd-lang-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; margin-bottom:8px; }
        .ytd-lbtn {
            background:var(--lbtn-bg); border:1.5px solid var(--lbtn-bdr); border-radius:10px;
            padding:6px 8px; font-size:11px; font-family:inherit; cursor:pointer;
            color:var(--lbtn-clr); font-weight:600; text-align:left; transition:all .15s;
        }
        .ytd-lbtn.on { background:var(--lbtn-on-bg); border-color:#58cc02; color:var(--lbtn-on-clr); }
        .ytd-lbtn:hover:not(.on) { filter:brightness(0.92); }
        .ytd-tutorial-btn {
            display:block; width:calc(100% - 28px); margin:8px 14px 10px;
            background:rgba(88,204,2,0.15); border:1.5px solid #58cc02; color:#58cc02;
            border-radius:10px; padding:8px; font-size:13px; font-weight:700;
            cursor:pointer; font-family:inherit; transition:all .15s; text-align:center;
        }
        .ytd-tutorial-btn:hover { background:#58cc02; color:#fff; }
        #ytd-save-cfg {
            display:block; width:calc(100% - 28px); margin:8px 14px 16px;
            background:#58cc02; border:none; color:#fff; padding:9px; border-radius:10px;
            font-size:13px; font-weight:700; cursor:pointer; font-family:inherit;
        }
        #ytd-toast {
            position:fixed; bottom:80px; right:20px; background:#333; color:#fff;
            padding:7px 14px; border-radius:16px; font-size:12px; font-weight:700;
            z-index:9999999; opacity:0; transform:translateY(6px);
            transition:opacity .25s, transform .25s; pointer-events:none;
            box-shadow:0 3px 10px rgba(0,0,0,.3);
            font-family:'Nunito','Segoe UI',sans-serif; max-width:260px; text-align:center;
        }
        #ytd-toast[data-theme="dark"] { background:#e0e0e0; color:#111; }
        #ytd-toast.on { opacity:1; transform:translateY(0); }
        .ytd-inner { position:relative; flex:1; overflow:hidden; display:flex; flex-direction:column; min-height:0; }
        #ytd-panel[data-blur="on"] { background:var(--blur-bg, rgba(255,255,255,0.70)) !important; backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px); }
        #ytd-panel[data-blur="on"] #ytd-search-row, #ytd-panel[data-blur="on"] #ytd-tabs { background:transparent !important; }
        #ytd-panel[data-blur="on"] #ytd-settings { background:var(--blur-bg, rgba(255,255,255,0.80)) !important; backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px); }
        #ytd-hbtn-blur.active { background:rgba(255,255,255,.55) !important; }
        #ytd-vol-wrap { display:flex; align-items:center; gap:4px; flex-shrink:0; }
        #ytd-vol-icon { color:#ccc; font-size:12px; cursor:pointer; user-select:none; }
        #ytd-vol { width:54px; accent-color:#58cc02; cursor:pointer; height:3px; }
        #ytd-opacity-sec { padding:0 14px 8px; }
        .ytd-range-row { display:flex; align-items:center; gap:8px; }
        .ytd-range-row input[type=range] { flex:1; accent-color:#58cc02; cursor:pointer; }
        .ytd-range-val { font-size:11px; color:var(--text2); min-width:34px; text-align:right; font-weight:700; }
        .ytd-card.ytd-short .ytd-thumb { aspect-ratio:9/16; }
        #ytd-grid.ytd-shorts-grid { grid-template-columns:1fr 1fr 1fr; gap:5px; padding:6px; }
        .ytd-short-badge { position:absolute; top:3px; right:3px; background:#ff0000; color:#fff; border-radius:4px; padding:1px 4px; font-size:8px; font-weight:700; }
        .ytd-live-badge { position:absolute; top:3px; left:3px; background:#ff0000; color:#fff; border-radius:4px; padding:1px 5px; font-size:8px; font-weight:700; animation:ytd-live-pulse 1.4s ease-in-out infinite; }
        @keyframes ytd-live-pulse { 0%,100%{opacity:1} 50%{opacity:.55} }
        #ytd-shorts-player {
            position:absolute; inset:0; z-index:10; background:#0f0f0f;
            display:flex; flex-direction:column; overflow:hidden;
            transform:translateX(100%); transition:transform .25s cubic-bezier(.4,0,.2,1);
        }
        #ytd-shorts-player.on { transform:translateX(0); }
        #ytd-sp-header { background:#161616; padding:6px 8px; display:flex; align-items:center; gap:8px; flex-shrink:0; height:36px; }
        #ytd-sp-back { background:rgba(255,255,255,.15); border:none; color:#fff; border-radius:16px; padding:3px 10px; cursor:pointer; font-size:11px; font-weight:700; transition:background .15s; flex-shrink:0; }
        #ytd-sp-back:hover { background:rgba(255,255,255,.3); }
        #ytd-sp-title-sm { flex:1; color:#ddd; font-size:10px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        #ytd-sp-yt-link { background:#ff0000; color:#fff; border-radius:6px; padding:2px 7px; font-size:10px; font-weight:700; text-decoration:none; white-space:nowrap; flex-shrink:0; }
        #ytd-sp-body { flex:1; display:flex; min-height:0; background:#000; }
        #ytd-sp-iframe-col { flex:1; display:flex; align-items:center; justify-content:center; overflow:hidden; min-width:0; }
        #ytd-sp-frame { height:100%; width:auto; aspect-ratio:9/16; max-width:100%; border:none; }
        #ytd-sp-nav-col { width:46px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; background:rgba(0,0,0,.5); flex-shrink:0; }
        .ytd-sp-nbtn { width:36px; height:36px; border-radius:50%; background:rgba(255,255,255,.18); border:none; color:#fff; font-size:18px; cursor:pointer; line-height:1; display:flex; align-items:center; justify-content:center; transition:background .15s; user-select:none; }
        .ytd-sp-nbtn:hover:not(:disabled) { background:rgba(255,255,255,.38); }
        .ytd-sp-nbtn:disabled { opacity:.28; cursor:default; }
        #ytd-sp-footer { background:#161616; padding:5px 10px 6px; flex-shrink:0; }
        #ytd-sp-footer-title { color:#fff; font-size:10px; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:1px; }
        #ytd-sp-ch-nm { color:#58cc02; font-size:9px; font-weight:700; }
        #ytd-sp-counter { color:#666; font-size:9px; margin-left:6px; }
        #ytd-load-more {
            display:block; width:calc(100% - 16px); margin:4px 8px 10px;
            background:transparent; border:1.5px solid #58cc02; color:#58cc02;
            border-radius:12px; padding:9px; font-size:12px; font-weight:700;
            cursor:pointer; font-family:inherit; transition:all .18s; text-align:center;
        }
        #ytd-load-more:hover { background:#58cc02; color:#fff; }
        #ytd-load-more:disabled { opacity:.4; cursor:default; border-color:var(--border); color:var(--text3); }

        .ytd-toggle-row {
            display:flex; align-items:center; gap:10px; padding:8px 14px 10px;
            border-bottom:1px solid var(--border2);
        }
        .ytd-toggle-info { flex:1; }
        .ytd-toggle-info label { font-size:12px; font-weight:700; color:var(--text); cursor:pointer; }
        .ytd-toggle-info small { display:block; font-size:10px; color:var(--text3); margin-top:2px; }
        .ytd-toggle-switch {
            position:relative; width:38px; height:22px; flex-shrink:0;
        }
        .ytd-toggle-switch input { opacity:0; width:0; height:0; }
        .ytd-toggle-track {
            position:absolute; inset:0; background:#ccc; border-radius:11px; cursor:pointer;
            transition:background .2s;
        }
        .ytd-toggle-track::before {
            content:''; position:absolute; width:16px; height:16px; top:3px; left:3px;
            background:#fff; border-radius:50%; transition:transform .2s;
            box-shadow:0 1px 3px rgba(0,0,0,.2);
        }
        .ytd-toggle-switch input:checked + .ytd-toggle-track { background:#58cc02; }
        .ytd-toggle-switch input:checked + .ytd-toggle-track::before { transform:translateX(16px); }

        #ytd-tr-panel {
            --bg:#ffffff; --bg2:#f6f6f6; --border:#e8e8e8; --border2:#eeeeee;
            --text:#222222; --text2:#555555; --text3:#aaaaaa;
            --input-bg:#ffffff; --input-clr:#222222; --input-bdr:#dddddd;
            --tr-result-bg:#f0fde8; --scrl-thumb:#dddddd;
        }
        #ytd-tr-panel[data-theme="dark"] {
            --bg:#1a1a1a; --bg2:#242424; --border:#333333; --border2:#2e2e2e;
            --text:#f0f0f0; --text2:#bbbbbb; --text3:#666666;
            --input-bg:#2a2a2a; --input-clr:#f0f0f0; --input-bdr:#444444;
            --tr-result-bg:#1a2e0a; --scrl-thumb:#444444;
        }
        #ytd-tr-panel {
            position:fixed; z-index:999997;
            background:var(--bg);
            border:1.5px solid #58cc02;
            border-radius:16px;
            box-shadow:0 12px 40px rgba(0,0,0,.18), 0 2px 8px rgba(0,0,0,.08);
            font-family:'Nunito','Segoe UI',sans-serif;
            display:flex; flex-direction:column; overflow:hidden;
            min-width:280px; width:340px;
            opacity:0; transform:scale(.93) translateY(8px);
            transition:opacity .2s, transform .2s;
            pointer-events:none;
        }
        #ytd-tr-panel.open { opacity:1; transform:scale(1) translateY(0); pointer-events:all; }
        #ytd-tr-panel-head {
            background:#58cc02; padding:7px 10px;
            display:flex; align-items:center; gap:7px;
            cursor:grab; user-select:none; flex-shrink:0;
        }
        #ytd-tr-panel-head:active { cursor:grabbing; }
        #ytd-tr-panel-title { flex:1; color:#fff; font-size:13px; font-weight:700; }
        #ytd-tr-panel-close {
            background:rgba(255,255,255,.2); border:none; color:#fff;
            width:22px; height:22px; border-radius:50%; font-size:12px; cursor:pointer;
            display:flex; align-items:center; justify-content:center; transition:background .15s; flex-shrink:0;
        }
        #ytd-tr-panel-close:hover { background:rgba(255,255,255,.38); }
        #ytd-tr-panel-body {
            padding:10px; display:flex; flex-direction:column; gap:8px;
            overflow-y:auto; max-height:400px;
        }
        #ytd-tr-panel-body::-webkit-scrollbar { width:3px; }
        #ytd-tr-panel-body::-webkit-scrollbar-thumb { background:var(--scrl-thumb); border-radius:2px; }

        .ytd-tr-lang-row {
            display:flex; align-items:center; gap:5px;
        }
        .ytd-tr-lang-row select {
            flex:1; padding:5px 7px; border:1.5px solid var(--input-bdr);
            border-radius:9px; background:var(--input-bg); color:var(--input-clr);
            font-size:11px; font-family:inherit; outline:none; cursor:pointer;
            transition:border-color .15s;
        }
        .ytd-tr-lang-row select:focus { border-color:#58cc02; }
        #ytd-tr-swap {
            background:rgba(88,204,2,.12); border:1.5px solid #58cc02; color:#58cc02;
            border-radius:50%; width:28px; height:28px; font-size:14px; font-weight:700;
            cursor:pointer; flex-shrink:0; display:flex; align-items:center; justify-content:center;
            transition:all .15s; font-family:inherit;
        }
        #ytd-tr-swap:hover { background:#58cc02; color:#fff; transform:rotate(180deg); }

        #ytd-tr-input {
            width:100%; min-height:80px; max-height:160px;
            border:1.5px solid var(--input-bdr); border-radius:10px;
            padding:8px 10px; font-size:13px; font-family:inherit;
            background:var(--input-bg); color:var(--input-clr);
            resize:vertical; outline:none; box-sizing:border-box;
            transition:border-color .15s; line-height:1.5;
        }
        #ytd-tr-input:focus { border-color:#58cc02; }
        #ytd-tr-input::placeholder { color:var(--text3); }

        #ytd-tr-btn {
            background:#58cc02; border:none; color:#fff;
            border-radius:10px; padding:8px 14px; font-size:13px;
            font-weight:700; cursor:pointer; font-family:inherit;
            transition:background .15s; display:flex; align-items:center;
            justify-content:center; gap:6px; width:100%;
        }
        #ytd-tr-btn:hover { background:#46a302; }
        #ytd-tr-btn:disabled { background:#aaa; cursor:default; }

        #ytd-tr-result-wrap {
            border:1.5px solid #58cc02; border-radius:10px; overflow:hidden; display:none;
        }
        #ytd-tr-result-wrap.show { display:block; }
        #ytd-tr-result-header {
            background:#58cc02; padding:4px 10px;
            display:flex; align-items:center; justify-content:space-between;
        }
        #ytd-tr-result-header span { color:#fff; font-size:10px; font-weight:700; }
        #ytd-tr-copy-btn {
            background:rgba(255,255,255,.25); border:none; color:#fff;
            border-radius:8px; padding:2px 8px; font-size:10px; font-weight:700;
            cursor:pointer; font-family:inherit; transition:background .15s;
        }
        #ytd-tr-copy-btn:hover { background:rgba(255,255,255,.45); }
        #ytd-tr-result-text {
            padding:10px 12px; font-size:13px; color:var(--text);
            background:var(--tr-result-bg); min-height:50px;
            line-height:1.6; white-space:pre-wrap; word-break:break-word;
        }
        #ytd-tr-detected {
            padding:4px 12px 6px; font-size:10px; color:var(--text3);
            background:var(--tr-result-bg); border-top:1px solid var(--border2);
        }

        #ytd-tr-popup {
            --bg:#ffffff; --bg2:#f6f6f6; --border:#e8e8e8; --border2:#eeeeee;
            --text:#222222; --text2:#555555; --text3:#aaaaaa;
        }
        #ytd-tr-popup[data-theme="dark"] {
            --bg:#1a1a1a; --border:#333333; --border2:#2e2e2e;
            --text:#f0f0f0; --text2:#bbbbbb; --text3:#666666;
        }
        #ytd-tr-popup {
            position:fixed; z-index:9999999;
            background:var(--bg);
            backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
            border:1.5px solid #58cc02; border-radius:14px;
            box-shadow:0 8px 28px rgba(0,0,0,.18);
            font-family:'Nunito','Segoe UI',sans-serif;
            min-width:220px; max-width:320px;
            display:none; flex-direction:column; overflow:hidden;
            animation:ytd-popup-in .18s cubic-bezier(.4,0,.2,1);
            cursor:default;
        }
        #ytd-tr-popup[data-theme="dark"] { background:var(--bg); }
        #ytd-tr-popup.show { display:flex; }
        #ytd-tr-popup-head { cursor:grab; }
        #ytd-tr-popup-head:active { cursor:grabbing; }
        @keyframes ytd-popup-in { from { opacity:0; transform:scale(.93) translateY(-4px); } to { opacity:1; transform:scale(1) translateY(0); } }

        #ytd-tr-popup-head {
            background:#58cc02; padding:5px 10px;
            display:flex; align-items:center; justify-content:space-between;
            user-select:none;
        }
        #ytd-tr-popup-head span { color:#fff; font-size:11px; font-weight:700; }
        #ytd-tr-popup-close {
            background:rgba(255,255,255,.2); border:none; color:#fff;
            border-radius:50%; width:20px; height:20px; cursor:pointer;
            font-size:11px; display:flex; align-items:center; justify-content:center;
            transition:background .15s;
        }
        #ytd-tr-popup-close:hover { background:rgba(255,255,255,.4); }
        #ytd-tr-popup-original {
            padding:7px 12px 4px; font-size:11px; color:var(--text2);
            border-bottom:1px solid var(--border2); font-style:italic;
            max-height:55px; overflow:hidden; text-overflow:ellipsis;
        }
        #ytd-tr-popup-result {
            padding:8px 12px; font-size:13px; color:var(--text); font-weight:700;
            min-height:38px; line-height:1.5;
        }
        #ytd-tr-popup-loading {
            padding:12px; display:flex; align-items:center; justify-content:center;
            gap:8px; color:var(--text3); font-size:11px;
        }
        #ytd-tr-popup-footer {
            padding:5px 10px 8px; display:flex; align-items:center; gap:6px; justify-content:space-between;
        }
        #ytd-tr-popup-detected { font-size:9px; color:var(--text3); }
        #ytd-tr-popup-copy {
            background:rgba(88,204,2,.12); border:1.5px solid #58cc02; color:#58cc02;
            border-radius:8px; padding:3px 10px; font-size:10px; font-weight:700;
            cursor:pointer; font-family:inherit; transition:all .15s;
        }
        #ytd-tr-popup-copy:hover { background:#58cc02; color:#fff; }
    `);

    // ╔══════════════════════════════════════════╗
    // ║  BUILD UI                                ║
    // ╚══════════════════════════════════════════╝
    let panelX = parseInt(GM_getValue('px', String(window.innerWidth  - 410)));
    let panelY = parseInt(GM_getValue('py', String(window.innerHeight - 620)));
    let trPanelX = parseInt(GM_getValue('trpx', String(window.innerWidth  - 360)));
    let trPanelY = parseInt(GM_getValue('trpy', '80'));

    function buildLangOptions(selectEl, includeAuto, selectedCode) {
        selectEl.innerHTML = '';
        TRANSLATE_LANGS.forEach(l => {
            if (!includeAuto && l.code === 'auto') return;
            const opt = document.createElement('option');
            opt.value = l.code;
            opt.textContent = l.label;
            if (l.code === selectedCode) opt.selected = true;
            selectEl.appendChild(opt);
        });
    }

    // Hàm xử lý dịch khi bôi đen + Ctrl
    function setupSelectionTranslation() {
        let ctrlPressed = false;
        let lastSelection = { text: '', time: 0 };
        
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Control') {
                ctrlPressed = true;
            }
        });
        
        document.addEventListener('keyup', (e) => {
            if (e.key === 'Control') {
                ctrlPressed = false;
                setTimeout(() => { lastSelection.text = ''; }, 100);
            }
        });
        
        document.addEventListener('mouseup', () => {
            if (!selectionTranslateOn) return;
            
            const sel = window.getSelection();
            const text = sel ? sel.toString().trim() : '';
            
            if (text && text.length >= 2 && text.length <= 500) {
                lastSelection.text = text;
                lastSelection.time = Date.now();
                
                try {
                    const range = sel.getRangeAt(0);
                    const rect = range.getBoundingClientRect();
                    lastSelection.x = Math.min(rect.left + window.scrollX, window.innerWidth - 340);
                    lastSelection.y = rect.bottom + window.scrollY + 8;
                } catch (e) {
                    lastSelection.x = 100;
                    lastSelection.y = 100;
                }
            }
        });
        
        document.addEventListener('keydown', async (e) => {
            if (!selectionTranslateOn) return;
            if (e.key === 'Control' && lastSelection.text && (Date.now() - lastSelection.time) < 2000) {
                e.preventDefault();
                
                const activeEl = document.activeElement;
                if (activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable)) {
                    return;
                }
                
                const text = lastSelection.text;
                if (!text) return;
                
                const popup = document.getElementById('ytd-tr-popup');
                if (!popup) return;
                
                popup.style.left = Math.max(4, lastSelection.x || 100) + 'px';
                popup.style.top = (lastSelection.y || 100) + 'px';
                
                document.getElementById('ytd-tr-popup-original').textContent = text.length > 120 ? text.slice(0, 117) + '...' : text;
                document.getElementById('ytd-tr-popup-loading').style.display = 'flex';
                document.getElementById('ytd-tr-popup-result').style.display = 'none';
                document.getElementById('ytd-tr-popup-footer').style.display = 'none';
                popup.classList.add('show');
                
                try {
                    const { translated, detectedLang } = await googleTranslate(text, 'auto', trToLang);
                    const resultEl = document.getElementById('ytd-tr-popup-result');
                    const detectedEl = document.getElementById('ytd-tr-popup-detected');
                    resultEl.textContent = translated;
                    resultEl.style.display = 'block';
                    document.getElementById('ytd-tr-popup-loading').style.display = 'none';
                    if (detectedLang && trFromLang === 'auto') {
                        const lbl = TRANSLATE_LANGS.find(l => l.code === detectedLang)?.label || detectedLang;
                        detectedEl.textContent = `🔍 ${lbl}`;
                    } else {
                        detectedEl.textContent = '';
                    }
                    document.getElementById('ytd-tr-popup-footer').style.display = 'flex';
                } catch (err) {
                    document.getElementById('ytd-tr-popup-loading').style.display = 'none';
                    document.getElementById('ytd-tr-popup-result').textContent = t('translateError');
                    document.getElementById('ytd-tr-popup-result').style.display = 'block';
                }
            }
        });
    }

    function buildUI() {
        // ── FAB ──
        const fab = document.createElement('button');
        fab.id = 'ytd-fab';
        fab.innerHTML = ytSvg(20);
        fab.style.cssText = 'bottom:20px;right:20px;';
        document.body.appendChild(fab);
        makeDraggable(fab, 'fbx', 'fby', null, true);
        fab.addEventListener('click', togglePanel);

        // ── MAIN PANEL ──
        const panel = document.createElement('div');
        panel.id = 'ytd-panel';
        panel.setAttribute('data-theme', theme);
        panel.style.cssText = `left:${clamp(panelX,0,window.innerWidth-panelW)}px;top:${clamp(panelY,0,window.innerHeight-200)}px;width:${panelW}px;height:${panelH}px;`;
        panel.innerHTML = `
            <div id="ytd-head">
                ${ytSvg(16)}
                <div id="ytd-head-title">YouTube <span id="ytd-head-sub">trên Duolingo 🦉</span></div>
                <button class="ytd-hbtn" id="ytd-home-btn"    title="Về trang kết quả">🏠</button>
                <button class="ytd-hbtn" id="ytd-blur-btn"    title="Bật/tắt hiệu ứng mờ">💧</button>
                <button class="ytd-hbtn" id="ytd-theme-btn"   title="Chuyển giao diện">${theme === 'dark' ? '☀️' : '🌙'}</button>
                <button id="ytd-tr-open-btn" title="Mở công cụ dịch">🌐</button>
                <button class="ytd-hbtn" id="ytd-cfg-btn"     title="Cài đặt">⚙</button>
                <button class="ytd-hbtn" id="ytd-close-btn"   title="Đóng">✕</button>
            </div>

            <div id="ytd-search-row">
                <input id="ytd-q" type="text" placeholder="Tìm video..." autocomplete="off"/>
                <button id="ytd-go">🔍</button>
            </div>
            <div id="ytd-tabs">
                <button class="ytd-tab on" data-t="search">🔎 Tìm kiếm</button>
                <button class="ytd-tab"    data-t="fav">⭐ Yêu thích</button>
                <button class="ytd-tab"    data-t="shorts">▶ Shorts</button>
                <button class="ytd-tab"    data-t="live">🔴 Live</button>
                <button id="ytd-search-toggle" title="Hiện thanh tìm kiếm">🔍</button>
            </div>

            <div class="ytd-inner">
                <div id="ytd-body">
                    <div id="ytd-empty"><span class="emo">🔍</span><span>Nhập từ khoá và nhấn tìm kiếm</span></div>
                    <div id="ytd-loading" style="display:none"><div class="ytd-spin"></div><span>Đang tìm...</span></div>
                    <div id="ytd-grid"    style="display:none"></div>
                </div>

                <div id="ytd-player">
                    <div id="ytd-pbar">
                        <span id="ytd-pbar-ttl"></span>
                        <div id="ytd-vol-wrap">
                            <span id="ytd-vol-icon" title="Volume">🔊</span>
                            <input type="range" id="ytd-vol" min="0" max="100" value="100">
                        </div>
                        <a id="ytd-yt-link" href="#" target="_blank">YT ↗</a>
                    </div>
                    <div id="ytd-frame-wrap">
                        <iframe id="ytd-frame" allowfullscreen allow="autoplay; picture-in-picture"></iframe>
                    </div>
                    <div id="ytd-pinfo">
                        <div id="ytd-pv-title"></div>
                        <div id="ytd-pv-ch"></div>
                        <div id="ytd-pv-btns">
                            <button class="ytd-actbtn" id="pbtn-fav">⭐ Lưu</button>
                            <button class="ytd-actbtn" id="pbtn-pip">📺 PiP</button>
                            <button class="ytd-actbtn" id="pbtn-copy">🔗 Copy</button>
                        </div>
                    </div>
                </div>

                <div id="ytd-shorts-player">
                    <div id="ytd-sp-header">
                        <button id="ytd-sp-back">← Quay lại</button>
                        <span id="ytd-sp-title-sm"></span>
                        <a id="ytd-sp-yt-link" href="#" target="_blank">YT ↗</a>
                    </div>
                    <div id="ytd-sp-body">
                        <div id="ytd-sp-iframe-col">
                            <iframe id="ytd-sp-frame" allowfullscreen allow="autoplay; picture-in-picture"></iframe>
                        </div>
                        <div id="ytd-sp-nav-col">
                            <button class="ytd-sp-nbtn" id="ytd-sp-up" title="Video trước">↑</button>
                            <button class="ytd-sp-nbtn" id="ytd-sp-down" title="Video tiếp">↓</button>
                        </div>
                    </div>
                    <div id="ytd-sp-footer">
                        <div id="ytd-sp-footer-title"></div>
                        <span id="ytd-sp-ch-nm"></span>
                        <span id="ytd-sp-counter"></span>
                    </div>
                </div>

                <div id="ytd-settings">
                    <div id="ytd-sbar">
                        <button id="ytd-sbar-back">&#8592;</button>
                        <span>⚙ Cài đặt</span>
                    </div>

                    <div class="ytd-toggle-row">
                        <div class="ytd-toggle-info">
                            <label id="ytd-sel-tr-label" for="ytd-sel-tr-chk">Dịch khi bôi đen + Ctrl</label>
                            <small id="ytd-sel-tr-desc">Bôi đen văn bản và nhấn Ctrl để dịch nhanh</small>
                        </div>
                        <label class="ytd-toggle-switch">
                            <input type="checkbox" id="ytd-sel-tr-chk" ${selectionTranslateOn ? 'checked' : ''}>
                            <span class="ytd-toggle-track"></span>
                        </label>
                    </div>

                    <div class="ytd-sec">
                        <div class="ytd-sec-ttl">Chọn ngôn ngữ YouTube</div>
                        <div class="ytd-lang-grid" id="ytd-lang-grid"></div>
                    </div>
                    <div class="ytd-sec">
                        <div class="ytd-sec-ttl" id="ytd-size-ttl">Kích thước mặc định</div>
                        <div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;">
                            <label style="font-size:11px;color:var(--text2);flex:1;"><span class="ytd-lbl-w">Rộng (px)</span><br>
                            <input type="number" id="ytd-dw" min="280" max="900" value="380" style="width:100%;margin-top:3px;padding:4px 6px;border:1.5px solid var(--input-bdr);border-radius:8px;background:var(--input-bg);color:var(--input-clr);font-size:12px;"></label>
                            <label style="font-size:11px;color:var(--text2);flex:1;"><span class="ytd-lbl-h">Cao (px)</span><br>
                            <input type="number" id="ytd-dh" min="280" max="900" value="580" style="width:100%;margin-top:3px;padding:4px 6px;border:1.5px solid var(--input-bdr);border-radius:8px;background:var(--input-bg);color:var(--input-clr);font-size:12px;"></label>
                        </div>
                    </div>
                    <div id="ytd-opacity-sec">
                        <div class="ytd-sec-ttl" id="ytd-opacity-ttl">Độ trong suốt</div>
                        <div class="ytd-range-row">
                            <input type="range" id="ytd-opacity-sl" min="10" max="100" value="70">
                            <span class="ytd-range-val" id="ytd-opacity-val">70%</span>
                        </div>
                    </div>
                    <button id="ytd-tutorial-btn" class="ytd-tutorial-btn">📖 Hướng dẫn</button>
                    <button id="ytd-save-cfg">✅ Lưu cài đặt</button>
                </div>
            </div>
        `;
        document.body.appendChild(panel);
        applyBlur();

        // ── TRANSLATE FLOATING PANEL ──
        const trPanel = document.createElement('div');
        trPanel.id = 'ytd-tr-panel';
        trPanel.setAttribute('data-theme', theme);
        trPanel.style.cssText = `left:${clamp(trPanelX,0,window.innerWidth-360)}px;top:${clamp(trPanelY,0,window.innerHeight-200)}px;`;
        trPanel.innerHTML = `
            <div id="ytd-tr-panel-head">
                <span id="ytd-tr-panel-title">🌐 Dịch</span>
                <button id="ytd-tr-panel-close">✕</button>
            </div>
            <div id="ytd-tr-panel-body">
                <div class="ytd-tr-lang-row">
                    <select id="ytd-tr-from"></select>
                    <button id="ytd-tr-swap" title="Đổi ngôn ngữ">⇄</button>
                    <select id="ytd-tr-to"></select>
                </div>
                <textarea id="ytd-tr-input" placeholder="Nhập từ hoặc câu cần dịch..." rows="3"></textarea>
                <button id="ytd-tr-btn">🌐 Dịch</button>
                <div id="ytd-tr-result-wrap">
                    <div id="ytd-tr-result-header">
                        <span id="ytd-tr-result-label">Kết quả</span>
                        <button id="ytd-tr-copy-btn">📋 Sao chép</button>
                    </div>
                    <div id="ytd-tr-result-text"></div>
                    <div id="ytd-tr-detected"></div>
                </div>
            </div>
        `;
        document.body.appendChild(trPanel);

        const trFromEl = trPanel.querySelector('#ytd-tr-from');
        const trToEl   = trPanel.querySelector('#ytd-tr-to');
        buildLangOptions(trFromEl, true,  trFromLang);
        buildLangOptions(trToEl,   false, trToLang);

        makeDraggable(trPanel, 'trpx', 'trpy', trPanel.querySelector('#ytd-tr-panel-head'), false);

        trPanel.querySelector('#ytd-tr-panel-close').addEventListener('click', closeTrPanel);
        trPanel.querySelector('#ytd-tr-btn').addEventListener('click', doTranslate);
        const trTextarea = trPanel.querySelector('#ytd-tr-input');
        trTextarea.addEventListener('keydown', e => {
            if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); doTranslate(); }
        });

        trPanel.querySelector('#ytd-tr-swap').addEventListener('click', swapTranslateLangs);
        trPanel.querySelector('#ytd-tr-copy-btn').addEventListener('click', () => {
            const txt = document.getElementById('ytd-tr-result-text').textContent;
            if (txt) { navigator.clipboard.writeText(txt).then(() => toast(t('toastTranslateCopied'))); }
        });
        trPanel.querySelector('#ytd-tr-from').addEventListener('change', e => {
            trFromLang = e.target.value; GM_setValue('trFrom', trFromLang);
        });
        trPanel.querySelector('#ytd-tr-to').addEventListener('change', e => {
            trToLang = e.target.value; GM_setValue('trTo', trToLang);
        });

        // ── SELECTION POPUP (dịch nhanh) ──
        const popup = document.createElement('div');
        popup.id = 'ytd-tr-popup';
        popup.setAttribute('data-theme', theme);
        popup.innerHTML = `
            <div id="ytd-tr-popup-head">
                <span id="ytd-tr-popup-title">🌐 Dịch nhanh</span>
                <button id="ytd-tr-popup-close">✕</button>
            </div>
            <div id="ytd-tr-popup-original"></div>
            <div id="ytd-tr-popup-loading" style="display:none">
                <div class="ytd-spin" style="width:18px;height:18px;border-width:2px;"></div>
                <span>${t('popupLoading')}</span>
            </div>
            <div id="ytd-tr-popup-result" style="display:none"></div>
            <div id="ytd-tr-popup-footer" style="display:none">
                <span id="ytd-tr-popup-detected"></span>
                <button id="ytd-tr-popup-copy">📋 ${t('popupCopy')}</button>
            </div>
        `;
        document.body.appendChild(popup);

        makeDraggable(popup, '_ppx', '_ppy', popup.querySelector('#ytd-tr-popup-head'), false);

        popup.querySelector('#ytd-tr-popup-close').addEventListener('click', hideTranslatePopup);
        popup.querySelector('#ytd-tr-popup-copy').addEventListener('click', () => {
            const txt = document.getElementById('ytd-tr-popup-result').textContent;
            if (txt) { navigator.clipboard.writeText(txt).then(() => toast(t('toastTranslateCopied'))); }
        });

        // ── TOAST ──
        const toastEl = document.createElement('div');
        toastEl.id = 'ytd-toast';
        toastEl.setAttribute('data-theme', theme);
        document.body.appendChild(toastEl);

        makeDraggable(panel, 'px', 'py', panel.querySelector('#ytd-head'), false);
        addEdgeResize(panel);

        // ── Panel events ──
        panel.querySelector('#ytd-close-btn').addEventListener('click',  togglePanel);
        panel.querySelector('#ytd-home-btn').addEventListener('click',   goHome);
        panel.querySelector('#ytd-search-toggle').addEventListener('click', () => { showSearchRow(); document.getElementById('ytd-q')?.focus(); });
        panel.querySelector('#ytd-sp-back').addEventListener('click',    closeShortsPlayer);
        panel.querySelector('#ytd-sp-up').addEventListener('click',      () => shortsNav(-1));
        panel.querySelector('#ytd-sp-down').addEventListener('click',    () => shortsNav(1));
        panel.querySelector('#ytd-blur-btn').addEventListener('click',   toggleBlur);
        panel.querySelector('#ytd-theme-btn').addEventListener('click',  toggleTheme);
        panel.querySelector('#ytd-cfg-btn').addEventListener('click',    openSettings);
        panel.querySelector('#ytd-tr-open-btn').addEventListener('click', toggleTrPanel);
        panel.querySelector('#ytd-go').addEventListener('click',         doSearch);
        panel.querySelector('#ytd-q').addEventListener('keydown', e => { if (e.key === 'Enter') doSearch(); });
        panel.querySelector('#pbtn-fav').addEventListener('click',  toggleFav);
        panel.querySelector('#pbtn-pip').addEventListener('click',  doPip);
        panel.querySelector('#pbtn-copy').addEventListener('click', doCopy);
        panel.querySelector('#ytd-sbar-back').addEventListener('click',  closeSettings);
        panel.querySelector('#ytd-save-cfg').addEventListener('click',   saveSettings);
        panel.querySelector('#ytd-vol').value = volumeLevel;
        panel.querySelector('#ytd-vol').addEventListener('input', e => {
            volumeLevel = parseInt(e.target.value);
            GM_setValue('vol', String(volumeLevel));
            applyVolume();
        });
        panel.querySelector('#ytd-opacity-sl').addEventListener('input', e => {
            blurOpacity = parseInt(e.target.value);
            GM_setValue('blurOpacity', String(blurOpacity));
            const val = document.getElementById('ytd-opacity-val');
            if (val) val.textContent = blurOpacity + '%';
            applyBlur();
        });
        panel.querySelectorAll('.ytd-tab').forEach(tb => tb.addEventListener('click', () => switchTab(tb.dataset.t)));

        panel.querySelector('#ytd-sel-tr-chk').addEventListener('change', e => {
            selectionTranslateOn = e.target.checked;
            GM_setValue('selTr', selectionTranslateOn);
            if (!selectionTranslateOn) hideTranslatePopup();
        });

        // Nút hướng dẫn trong cài đặt
        const tutorialBtn = panel.querySelector('#ytd-tutorial-btn');
        if (tutorialBtn) {
            tutorialBtn.addEventListener('click', () => {
                closeSettings();
                showTutorial();
            });
        }

        // Khởi tạo tính năng bôi đen + Ctrl
        setupSelectionTranslation();

        buildLangGrid();

        if (firstRun) {
            setTimeout(showTutorial, 500);
        }
    }

    function ytSvg(size) {
        return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="white" style="flex-shrink:0"><path d="M23.5 6.19a3.02 3.02 0 0 0-2.12-2.14C19.54 3.5 12 3.5 12 3.5s-7.54 0-9.38.55A3.02 3.02 0 0 0 .5 6.19C0 8.04 0 12 0 12s0 3.96.5 5.81a3.02 3.02 0 0 0 2.12 2.14C4.46 20.5 12 20.5 12 20.5s7.54 0 9.38-.55a3.02 3.02 0 0 0 2.12-2.14C24 15.96 24 12 24 12s0-3.96-.5-5.81zM9.75 15.02V8.98L15.5 12l-5.75 3.02z"/></svg>`;
    }

    function buildLangGrid() {
        const grid = document.getElementById('ytd-lang-grid');
        if (!grid) return;
        grid.innerHTML = '';
        LANGS.forEach(l => {
            const b = document.createElement('button');
            b.className = 'ytd-lbtn' + (cfg.hl === l.code ? ' on' : '');
            b.textContent = l.label;
            b.dataset.code = l.code;
            b.dataset.gl   = l.gl;
            b.addEventListener('click', () => {
                document.querySelectorAll('.ytd-lbtn').forEach(x => x.classList.remove('on'));
                b.classList.add('on');
            });
            grid.appendChild(b);
        });
    }

    function toggleTrPanel() {
        trPanelOpen = !trPanelOpen;
        const trPanel = document.getElementById('ytd-tr-panel');
        const btn     = document.getElementById('ytd-tr-open-btn');
        trPanel.classList.toggle('open', trPanelOpen);
        if (btn) btn.classList.toggle('active', trPanelOpen);
        if (trPanelOpen) {
            const mainPanel = document.getElementById('ytd-panel');
            if (mainPanel && panelOpen) {
                const mRect = mainPanel.getBoundingClientRect();
                const nx = clamp(mRect.right + 10, 4, window.innerWidth - 360);
                const ny = clamp(mRect.top, 4, window.innerHeight - 200);
                trPanel.style.left = nx + 'px';
                trPanel.style.top  = ny + 'px';
                GM_setValue('trpx', String(nx));
                GM_setValue('trpy', String(ny));
            }
            document.getElementById('ytd-tr-input')?.focus();
        }
    }

    function closeTrPanel() {
        trPanelOpen = false;
        document.getElementById('ytd-tr-panel').classList.remove('open');
        const btn = document.getElementById('ytd-tr-open-btn');
        if (btn) btn.classList.remove('active');
    }

    async function doTranslate() {
        const inputEl    = document.getElementById('ytd-tr-input');
        const resultWrap = document.getElementById('ytd-tr-result-wrap');
        const resultEl   = document.getElementById('ytd-tr-result-text');
        const detectedEl = document.getElementById('ytd-tr-detected');
        const btnEl      = document.getElementById('ytd-tr-btn');

        const text = inputEl.value.trim();
        if (!text) { toast(t('translateEmpty')); return; }

        btnEl.disabled = true;
        btnEl.textContent = t('translating');
        resultWrap.classList.remove('show');

        try {
            const { translated, detectedLang } = await googleTranslate(text, trFromLang, trToLang);
            resultEl.textContent = translated;
            if (trFromLang === 'auto' && detectedLang) {
                const langLabel = TRANSLATE_LANGS.find(l => l.code === detectedLang)?.label || detectedLang;
                detectedEl.textContent = `🔍 ${langLabel}`;
            } else {
                detectedEl.textContent = '';
            }
            resultWrap.classList.add('show');
        } catch {
            resultEl.textContent = t('translateError');
            detectedEl.textContent = '';
            resultWrap.classList.add('show');
        } finally {
            btnEl.disabled = false;
            btnEl.textContent = t('translateBtn');
        }
    }

    function swapTranslateLangs() {
        const fromEl = document.getElementById('ytd-tr-from');
        const toEl   = document.getElementById('ytd-tr-to');
        if (fromEl.value === 'auto') return;
        const tmp = fromEl.value;
        fromEl.value = toEl.value;
        toEl.value   = tmp;
        trFromLang = fromEl.value;
        trToLang   = toEl.value;
        GM_setValue('trFrom', trFromLang);
        GM_setValue('trTo',   trToLang);
        const inputEl    = document.getElementById('ytd-tr-input');
        const resultEl   = document.getElementById('ytd-tr-result-text');
        const resultWrap = document.getElementById('ytd-tr-result-wrap');
        if (resultWrap.classList.contains('show') && resultEl.textContent) {
            const tmp2 = inputEl.value;
            inputEl.value = resultEl.textContent;
            resultEl.textContent = tmp2;
        }
    }

    function hideTranslatePopup() {
        const popup = document.getElementById('ytd-tr-popup');
        if (popup) popup.classList.remove('show');
    }

    function openSettings() {
        buildLangGrid();
        const dw = document.getElementById('ytd-dw');
        const dh = document.getElementById('ytd-dh');
        if (dw) dw.value = defaultW;
        if (dh) dh.value = defaultH;
        const opSl  = document.getElementById('ytd-opacity-sl');
        const opVal = document.getElementById('ytd-opacity-val');
        if (opSl)  opSl.value = blurOpacity;
        if (opVal) opVal.textContent = blurOpacity + '%';
        const chk = document.getElementById('ytd-sel-tr-chk');
        if (chk) chk.checked = selectionTranslateOn;
        document.getElementById('ytd-settings').classList.add('on');
    }
    function closeSettings() { document.getElementById('ytd-settings').classList.remove('on'); }
    function saveSettings() {
        const sel = document.querySelector('.ytd-lbtn.on');
        if (!sel) { closeSettings(); return; }
        const changed = sel.dataset.code !== cfg.hl;
        cfg.hl = sel.dataset.code; cfg.gl = sel.dataset.gl;
        saveCfg();
        const dwIn = document.getElementById('ytd-dw');
        const dhIn = document.getElementById('ytd-dh');
        if (dwIn && dhIn) {
            const nw = Math.max(280, Math.min(900, parseInt(dwIn.value) || 380));
            const nh = Math.max(280, Math.min(900, parseInt(dhIn.value) || 580));
            defaultW = nw; defaultH = nh;
            GM_setValue('dw', String(nw)); GM_setValue('dh', String(nh));
        }
        closeSettings();
        applyUILang();
        if (changed) {
            const q = document.getElementById('ytd-q').value.trim();
            if (q) { toast(t('toastReload')); doSearch(); }
            else    toast(t('toastSaved'));
        } else { toast(t('toastSaved')); }
    }

    function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }

    function addEdgeResize(panel) {
        const EDGE = 8;
        let dir = null, resizing = false;
        let startX, startY, startW, startH, startL, startT;
        function getDir(e) {
            const r = panel.getBoundingClientRect();
            const x = e.clientX - r.left, y = e.clientY - r.top;
            return { l: x <= EDGE, r: x >= r.width - EDGE, t: y <= EDGE, b: y >= r.height - EDGE };
        }
        function cursorFor(d) {
            if ((d.l && d.t) || (d.r && d.b)) return 'nwse-resize';
            if ((d.r && d.t) || (d.l && d.b)) return 'nesw-resize';
            if (d.l || d.r) return 'ew-resize';
            if (d.t || d.b) return 'ns-resize';
            return '';
        }
        panel.addEventListener('mousemove', e => {
            if (resizing) return;
            if (e.target.closest && e.target.closest('#ytd-head')) { panel.style.cursor = ''; dir = null; return; }
            const _pr = panel.getBoundingClientRect();
            const _px = e.clientX - _pr.left, _py = e.clientY - _pr.top;
            const _onEdge = _px <= EDGE || _px >= _pr.width - EDGE || _py <= EDGE || _py >= _pr.height - EDGE;
            if (!_onEdge) { panel.style.cursor = ''; dir = null; return; }
            dir = getDir(e); panel.style.cursor = cursorFor(dir);
        });
        panel.addEventListener('mouseleave', () => { if (!resizing) { panel.style.cursor = ''; dir = null; } });
        panel.addEventListener('mousedown', e => {
            const _fd = getDir(e);
            if (!_fd.l && !_fd.r && !_fd.t && !_fd.b) return;
            if (e.target.closest && e.target.closest('#ytd-head,button,input,a,select,textarea')) return;
            dir = _fd; resizing = true;
            startX = e.clientX; startY = e.clientY;
            const r = panel.getBoundingClientRect();
            startW = r.width; startH = r.height; startL = r.left; startT = r.top;
            panel.querySelectorAll('iframe').forEach(f => f.style.pointerEvents = 'none');
            document.body.style.userSelect = 'none';
            e.preventDefault(); e.stopPropagation();
        });
        document.addEventListener('mousemove', e => {
            if (!resizing) return;
            const dx = e.clientX - startX, dy = e.clientY - startY;
            const vw = window.innerWidth, vh = window.innerHeight;
            let w = startW, h = startH, l = startL, top = startT;
            if (dir.r) w = clamp(startW + dx, 280, vw - startL - 4);
            if (dir.b) h = clamp(startH + dy, 300, vh - startT - 4);
            if (dir.l) { const nw = clamp(startW - dx, 280, startL + startW - 4); l = startL + startW - nw; w = nw; }
            if (dir.t) { const nh = clamp(startH - dy, 300, startT + startH - 4); top = startT + startH - nh; h = nh; }
            panel.style.width = w + 'px'; panel.style.height = h + 'px';
            panel.style.left  = l + 'px'; panel.style.top   = top + 'px';
            panel.style.right = 'auto'; panel.style.bottom = 'auto';
            panelW = w; panelH = h; panelX = l; panelY = top;
            GM_setValue('pw', String(w)); GM_setValue('ph', String(h));
            GM_setValue('px', String(l)); GM_setValue('py', String(top));
            e.preventDefault();
        });
        document.addEventListener('mouseup', () => {
            if (resizing) {
                resizing = false; dir = null; panel.style.cursor = '';
                panel.querySelectorAll('iframe').forEach(f => f.style.pointerEvents = '');
                document.body.style.userSelect = '';
            }
        });
    }

    function makeDraggable(el, xk, yk, handle, isFab) {
        const h = handle || el;
        let dx = 0, dy = 0, dragging = false, moved = false;
        h.addEventListener('mousedown', e => {
            if (e.target.closest && e.target.closest('button,a,input,label,select,textarea')) return;
            dragging = true; moved = false;
            dx = e.clientX - el.getBoundingClientRect().left;
            dy = e.clientY - el.getBoundingClientRect().top;
            e.preventDefault();
        });
        document.addEventListener('mousemove', e => {
            if (!dragging) return;
            moved = true;
            const x = clamp(e.clientX - dx, 0, window.innerWidth  - el.offsetWidth);
            const y = clamp(e.clientY - dy, 0, window.innerHeight - el.offsetHeight);
            el.style.left = x + 'px'; el.style.top = y + 'px';
            el.style.right = 'auto'; el.style.bottom = 'auto';
            if (xk && yk) { GM_setValue(xk, String(x)); GM_setValue(yk, String(y)); }
        });
        document.addEventListener('mouseup', () => {
            if (isFab && dragging && moved) { el._drag = true; setTimeout(() => { el._drag = false; }, 60); }
            dragging = false;
        });
        if (isFab) el.addEventListener('click', e => { if (el._drag) e.stopImmediatePropagation(); });
    }

    function togglePanel() {
        panelOpen = !panelOpen;
        document.getElementById('ytd-panel').classList.toggle('open', panelOpen);
        if (panelOpen) document.getElementById('ytd-q').focus();
    }

    function showSearchRow() {
        document.getElementById('ytd-search-row')?.classList.remove('ytd-sr-hidden');
        document.getElementById('ytd-search-toggle')?.classList.remove('on');
    }
    function hideSearchRow() {
        document.getElementById('ytd-search-row')?.classList.add('ytd-sr-hidden');
        document.getElementById('ytd-search-toggle')?.classList.add('on');
    }

    function stopPlayback() {
        const player = document.getElementById('ytd-player');
        if (player && player.classList.contains('on')) {
            player.classList.remove('on');
            document.getElementById('ytd-frame').src = '';
            activeId = null; activeData = null;
        }
        closeShortsPlayer();
    }

    function switchTab(tb) {
        stopPlayback();
        activeView = tb;
        _loadMoreFn = null;
        const _oldB = document.getElementById('ytd-load-more'); if (_oldB) _oldB.remove();

        if (tb === 'search') showSearchRow();
        document.querySelectorAll('.ytd-tab').forEach(b => b.classList.toggle('on', b.dataset.t === tb));
        const grid = document.getElementById('ytd-grid');
        if (grid) grid.classList.toggle('ytd-shorts-grid', tb === 'shorts');
        const qEl = document.getElementById('ytd-q');
        if (qEl) qEl.placeholder = tb === 'shorts' ? t('phShorts') : tb === 'live' ? t('phLive') : t('searchHint');
        if (tb === 'shorts') {
            if (!shortsLoaded) loadShorts();
            else renderItems(shorts);
        } else if (tb === 'live') {
            if (!liveLoaded) loadLive();
            else renderItems(live);
        } else {
            renderItems(tb === 'fav' ? favorites : results);
        }
    }

    async function loadShorts() {
        setLoad(true);
        try {
            shorts = await ytGetShorts(); shortsLoaded = true;
            _loadMoreFn = async () => {
                const _more = await ytGetShorts();
                const _seen = new Set(shorts.map(x => x.id));
                for (const v of _more) { if (!_seen.has(v.id)) { _seen.add(v.id); shorts.push(v); } }
                if (activeView === 'shorts') renderItems(shorts);
            };
            if (activeView === 'shorts') renderItems(shorts);
        } catch { if (activeView === 'shorts') renderItems([]); }
        finally { setLoad(false); }
    }

    async function loadLive() {
        setLoad(true);
        try {
            live = await ytGetLive(); liveLoaded = true;
            _loadMoreFn = async () => {
                const _more = await ytGetLive();
                const _seen = new Set(live.map(x => x.id));
                for (const v of _more) { if (!_seen.has(v.id)) { _seen.add(v.id); live.push(v); } }
                if (activeView === 'live') renderItems(live);
            };
            if (activeView === 'live') renderItems(live);
        } catch { if (activeView === 'live') renderItems([]); }
        finally { setLoad(false); }
    }

    function openShortsPlayer(idx) {
        if (!shorts.length) return;
        shortsCurrent = Math.max(0, Math.min(idx, shorts.length - 1));
        const item = shorts[shortsCurrent];
        document.getElementById('ytd-sp-title-sm').textContent    = item.title;
        document.getElementById('ytd-sp-footer-title').textContent = item.title;
        document.getElementById('ytd-sp-ch-nm').textContent       = item.channel || '';
        document.getElementById('ytd-sp-counter').textContent     = `${shortsCurrent + 1}/${shorts.length}`;
        document.getElementById('ytd-sp-yt-link').href = `https://www.youtube.com/shorts/${item.id}`;
        document.getElementById('ytd-sp-frame').src =
            `https://www.youtube.com/embed/${item.id}?autoplay=1&loop=1&playlist=${item.id}&rel=0&modestbranding=1`;
        document.getElementById('ytd-sp-up').disabled   = shortsCurrent === 0;
        document.getElementById('ytd-sp-down').disabled = shortsCurrent >= shorts.length - 1;
        document.getElementById('ytd-shorts-player').classList.add('on');
        if (!watchedIds.has(item.id)) { watchedIds.add(item.id); save(); }
    }

    function closeShortsPlayer() {
        document.getElementById('ytd-shorts-player').classList.remove('on');
        document.getElementById('ytd-sp-frame').src = '';
    }

    function shortsNav(dir) {
        const next = shortsCurrent + dir;
        if (next < 0 || next >= shorts.length) return;
        openShortsPlayer(next);
    }

    async function doSearch() {
        const q = document.getElementById('ytd-q').value.trim();
        if (!q) return;
        setLoad(true);
        if (activeView === 'shorts') {
            try {
                shorts = await ytSearchShorts(q); shortsLoaded = true;
                _loadMoreFn = async () => {
                    const _more = await ytSearchShorts(q);
                    const _seen = new Set(shorts.map(x => x.id));
                    for (const v of _more) { if (!_seen.has(v.id)) { _seen.add(v.id); shorts.push(v); } }
                    renderItems(shorts);
                };
                renderItems(shorts);
            } catch { renderItems([]); }
            finally { setLoad(false); }
            return;
        }
        if (activeView === 'live') {
            try {
                live = await ytSearchLive(q); liveLoaded = true;
                _loadMoreFn = async () => {
                    const _more = await ytSearchLive(q);
                    const _seen = new Set(live.map(x => x.id));
                    for (const v of _more) { if (!_seen.has(v.id)) { _seen.add(v.id); live.push(v); } }
                    renderItems(live);
                };
                renderItems(live);
            } catch { renderItems([]); }
            finally { setLoad(false); }
            return;
        }
        activeView = 'search';
        document.querySelectorAll('.ytd-tab').forEach(b => b.classList.toggle('on', b.dataset.t === 'search'));
        try {
            results = await ytSearch(q);
            _loadMoreFn = async () => {
                const _more = await ytSearch(q);
                const _seen = new Set(results.map(x => x.id));
                for (const v of _more) { if (!_seen.has(v.id)) { _seen.add(v.id); results.push(v); } }
                renderItems(results);
            };
            renderItems(results);
        } catch { renderItems([]); }
        finally { setLoad(false); }
    }

    function renderItems(items) {
        const empty = document.getElementById('ytd-empty');
        const grid  = document.getElementById('ytd-grid');
        grid.innerHTML = '';
        if (!items || !items.length) {
            empty.style.display = 'flex'; grid.style.display = 'none';
            showSearchRow();
            empty.innerHTML = activeView === 'fav'
                ? `<span class="emo">⭐</span><span>${t('noFav')}</span>`
                : activeView === 'live'
                ? `<span class="emo">🔴</span><span>${t('noLive')}</span>`
                : activeView === 'shorts'
                ? `<span class="emo">▶</span><span>${t('shortSearchHint')}</span>`
                : `<span class="emo">🔍</span><span>${t('emptyHint')}</span>`;
            return;
        }
        empty.style.display = 'none'; grid.style.display = 'grid';
        if (activeView !== 'search') hideSearchRow(); else showSearchRow();
        items.forEach((item, idx) => {
            const c = document.createElement('div');
            c.className = 'ytd-card';
            const w = watchedIds.has(item.id);
            if (item.isShort) c.classList.add('ytd-short');
            c.innerHTML = `
                <div class="ytd-thumb-wrap">
                    <img class="ytd-thumb" src="${item.thumb}" loading="lazy" alt="">
                    ${item.duration ? `<span class="ytd-dur">${item.duration}</span>` : ''}
                    ${item.isShort ? '<span class="ytd-short-badge">Short</span>' : ''}
                    ${item.isLive  ? '<span class="ytd-live-badge">🔴 LIVE</span>' : ''}
                    ${w ? '<span class="ytd-done">✓</span>' : ''}
                </div>
                <div class="ytd-info">
                    <div class="ytd-title">${esc(item.title)}</div>
                    ${item.channel ? `<div class="ytd-ch">${esc(item.channel)}</div>` : ''}
                </div>`;
            c.addEventListener('click', () => {
                if (item.isShort && activeView === 'shorts') openShortsPlayer(idx);
                else openPlayer(item);
            });
            grid.appendChild(c);
        });
        const _oldLmBtn = document.getElementById('ytd-load-more');
        if (_oldLmBtn) _oldLmBtn.remove();
        if (_loadMoreFn) {
            const lmBtn = document.createElement('button');
            lmBtn.id = 'ytd-load-more';
            lmBtn.textContent = t('loadMore');
            lmBtn.addEventListener('click', async () => {
                lmBtn.disabled = true; lmBtn.textContent = t('loading');
                await _loadMoreFn();
                lmBtn.disabled = false; lmBtn.textContent = t('loadMore');
            });
            const bodyEl = document.getElementById('ytd-body');
            if (bodyEl) bodyEl.appendChild(lmBtn);
        }
    }

    function esc(s) { return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }

    function setLoad(on) {
        document.getElementById('ytd-loading').style.display = on ? 'flex' : 'none';
        document.getElementById('ytd-empty').style.display   = on ? 'none' : '';
        document.getElementById('ytd-grid').style.display    = on ? 'none' : '';
    }

    function openPlayer(item) {
        activeId = item.id; activeData = item;
        document.getElementById('ytd-pbar-ttl').textContent = item.title;
        document.getElementById('ytd-pv-title').textContent = item.title;
        document.getElementById('ytd-pv-ch').textContent    = item.channel || '';
        document.getElementById('ytd-yt-link').href = `https://www.youtube.com/watch?v=${item.id}`;
        const fr = document.getElementById('ytd-frame');
        fr.src = 'https://www.youtube.com/embed/' + item.id + '?autoplay=1&rel=0&modestbranding=1&enablejsapi=1' + (item.isShort ? '' : '&vq=hd1080');
        fr.onload = () => { setTimeout(applyVolume, 800); };
        document.getElementById('ytd-player').classList.add('on');
        if (!watchedIds.has(item.id)) { watchedIds.add(item.id); save(); }
        updateFavBtn();
    }

    function goHome() {
        document.getElementById('ytd-player').classList.remove('on');
        closeShortsPlayer();
        document.getElementById('ytd-frame').src = '';
        activeId = null;
        const panel = document.getElementById('ytd-panel');
        if (panel) { panelW = defaultW; panelH = defaultH; panel.style.width = defaultW + 'px'; panel.style.height = defaultH + 'px'; GM_setValue('pw', String(defaultW)); GM_setValue('ph', String(defaultH)); }
        activeView = 'search';
        results = [];
        showSearchRow();
        document.getElementById('ytd-q').value = '';
        document.querySelectorAll('.ytd-tab').forEach(b => b.classList.toggle('on', b.dataset.t === 'search'));
        const _grid = document.getElementById('ytd-grid');
        _grid.style.display = 'none';
        _grid.classList.remove('ytd-shorts-grid');
        document.getElementById('ytd-loading').style.display = 'none';
        const empty = document.getElementById('ytd-empty');
        empty.style.display = 'flex';
        empty.innerHTML = `<span class="emo">🔍</span><span>${t('emptyHint')}</span>`;
        document.getElementById('ytd-q').focus();
    }

    function toggleFav() {
        if (!activeData) return;
        const i = favorites.findIndex(f => f.id === activeData.id);
        if (i === -1) { favorites.push(activeData); toast(t('toastFavAdd')); }
        else          { favorites.splice(i, 1);     toast(t('toastFavRm')); }
        save(); updateFavBtn();
    }

    function updateFavBtn() {
        const btn = document.getElementById('pbtn-fav');
        if (!btn || !activeData) return;
        const on = favorites.some(f => f.id === activeData.id);
        btn.classList.toggle('on', on);
        btn.textContent = on ? t('btnFavOn') : t('btnFav');
    }

    function doPip() {
        const fr = document.getElementById('ytd-frame');
        if (fr.requestPictureInPicture) fr.requestPictureInPicture().catch(() => {});
        else window.open(`https://www.youtube.com/embed/${activeId}?autoplay=1`, 'pip', 'width=480,height=270');
    }

    function doCopy() {
        if (!activeId) return;
        navigator.clipboard.writeText(`https://www.youtube.com/watch?v=${activeId}`)
            .then(() => toast(t('toastCopied')));
    }

    let _tt = null;
    function toast(msg) {
        const el = document.getElementById('ytd-toast');
        if (!el) return;
        el.textContent = msg; el.classList.add('on');
        clearTimeout(_tt); _tt = setTimeout(() => el.classList.remove('on'), 2200);
    }

    document.addEventListener('keydown', e => {
        if (e.altKey && e.key === 'y') { togglePanel(); return; }
        if (e.altKey && e.key === 't') { toggleTrPanel(); return; }
        if (e.key === 'Escape') {
            const popup = document.getElementById('ytd-tr-popup');
            if (popup && popup.classList.contains('show')) {
                e.preventDefault();
                hideTranslatePopup();
                return;
            }
            if (trPanelOpen) { closeTrPanel(); return; }
            if (activeId) goHome();
        }
        const spOn = document.getElementById('ytd-shorts-player').classList.contains('on');
        if (spOn && e.key === 'ArrowUp')   { e.preventDefault(); shortsNav(-1); return; }
        if (spOn && e.key === 'ArrowDown') { e.preventDefault(); shortsNav(1);  return; }
        if (spOn && e.key === 'Escape')    { closeShortsPlayer(); return; }
    });

    buildUI();
    applyUILang();

})();