Youlingo

Mini YouTube on Duolingo + Translate

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

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

})();