YouTube Downloader - Local Server Interface - PRO

The Best YouTube Downloader! Download Video (Full HD/4K/8K), Audio (MP3) & Images via Local Server. Features: Universal Support, Batch Download, Shortcuts, Resizable UI.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Downloader - Local Server Interface - PRO
// @name:pt-BR   YouTube Downloader - Local Server Interface - PRO
// @name:es      YouTube Downloader - Local Server Interface - PRO
// @name:fr      YouTube Downloader - Local Server Interface - PRO
// @name:de      YouTube Downloader - Local Server Interface - PRO
// @name:it      YouTube Downloader - Local Server Interface - PRO
// @name:ru      YouTube Downloader - Local Server Interface - PRO
// @name:zh-CN   YouTube Downloader - Local Server Interface - PRO
// @name:ja      YouTube Downloader - Local Server Interface - PRO
// @name:ko      YouTube Downloader - Local Server Interface - PRO
// @name:hi      YouTube Downloader - Local Server Interface - PRO
// @name:id      YouTube Downloader - Local Server Interface - PRO
// @namespace    http://tampermonkey.net/
// @version      3.12.3
// @description  The Best YouTube Downloader! Download Video (Full HD/4K/8K), Audio (MP3) & Images via Local Server. Features: Universal Support, Batch Download, Shortcuts, Resizable UI.
// @description:pt-BR A melhor ferramenta para baixar YouTube! Baixe Vídeos (Full HD/4K/8K), Áudio (MP3) e Imagens via Servidor Local. Recursos: Suporte Universal, Download em Lote, Atalhos, UI Redimensionável.
// @description:es   ¡El mejor descargador de YouTube! Descarga Video (Full HD/4K/8K), Audio (MP3) e Imágenes a través del Servidor Local. Características: Soporte Universal, Descarga por Lotes, Atajos, UI Redimensionable.
// @description:zh-CN 最好的YouTube下载器!通过本地服务器下载视频(全高清/4K/8K)、音频(MP3)和图片。功能:通用支持、批量下载、快捷键、可调整大小的UI。
// @description:ru   Лучший загрузчик YouTube! Скачивайте Видео (Full HD/4K/8K), Аудио (MP3) и Картинки через локальный сервер. Функции: Универсальная поддержка, Горячие клавиши, Изменяемый размер UI.
// @description:fr   Le meilleur téléchargeur YouTube ! Téléchargez Vidéo (Full HD/4K/8K), Audio (MP3) et Images via serveur local. Fonctionnalités : Support Universel, Raccourcis clavier, UI redimensionnable.
// @description:de   Der beste YouTube-Downloader! Video (Full HD/4K/8K), Audio (MP3) & Bilder über lokalen Server herunterladen. Features: Universelle Unterstützung, Tastenkürzel, Anpassbare UI.
// @description:ja   最高のYouTubeダウンローダー!ローカルサーバー経由でビデオ(フルHD/4K/8K)、オーディオ(MP3)、画像をダウンロード。機能:ユニバーサルサポート、ショートカット、サイズ変更可能なUI。
// @description:it   Il miglior downloader di YouTube! Scarica Video (Full HD/4K/8K), Audio (MP3) e Immagini tramite server locale. Funzioni: Supporto Universale, Scorciatoie da tastiera, UI ridimensionabile.
// @description:hi   सर्वश्रेष्ठ यूट्यूब डाउनलोडर! स्थानीय सर्वर के माध्यम से वीडियो (पूर्ण एचडी/4K/8K), ऑडियो (MP3) और चित्र डाउनलोड करें। विशेषताएं: यूनिवर्सल समर्थन, कीबोर्ड शॉर्टकट, आकार बदलने योग्य यूआई।
// @description:id   Pengunduh YouTube Terbaik! Unduh Video (Full HD/4K/8K), Audio (MP3) & Gambar melalui Server Lokal. Fitur: Dukungan Universal, Pintasan Keyboard, UI yang Dapat Diubah Ukurannya.
// @description:ko   최고의 YouTube 다운로더! 로컬 서버를 통해 비디오(Full HD/4K/8K), 오디오(MP3) 및 이미지를 다운로드하십시오. 기능: 범용 지원, 단축키, 크기 조정 가능한 UI.
// @description:ar   أفضل تنزيل يوتيوب! قم بتنزيل الفيديو (Full HD/4K/8K) والصوت (MP3) والصور عبر الخادم المحلي. الميزات: الدعم العالمي ، اختصارات لوحة المفاتيح ، واجهة المستخدم القابلة لتغيير الحجم.
// @copyright    2025, Tauã B. Kloch Leite - All Rights Reserved.
// @author       Tauã B. Kloch Leite
// @icon         https://img.icons8.com/?size=100&id=9F8aDI7mYs6V&format=png&color=000000
// @match        https://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_openInTab
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @connect      127.0.0.1
// @connect      *
// ==/UserScript==

(function () {
  'use strict';

  // --- PREVENT IFRAME INJECTION ---
  // This ensures the script only runs on the main page, not in chat frames
  if (window.self !== window.top) return;

  // --- SECURITY POLICY (TrustedTypes) ---
  let policy = null;
  if (window.trustedTypes && window.trustedTypes.createPolicy) {
      try { policy = window.trustedTypes.createPolicy('yt-dl-policy', { createHTML: (s) => s }); } catch (e) { }
  }
  const safeHTML = (html) => policy ? policy.createHTML(html) : html;

  // --- CONFIGURATION ---
  const SERVER_URL = "http://127.0.0.1:5000";
  const DRIVE_LINK = "https://drive.google.com/file/d/1MHOYc9haviNrfOZX_IeFwszBLj6K-f3o/view?usp=sharing";
  const UPDATE_URL = "https://greasyfork.org/en/scripts/557579-youtube-downloader-local-server-interface-pro";
  const POLLING_INTERVAL = 1500;

  // --- ICONS ---
  const ICONS = {
        pix: "https://upload.wikimedia.org/wikipedia/commons/a/a2/Logo%E2%80%94pix_powered_by_Banco_Central_%28Brazil%2C_2020%29.svg",
        paypal: "https://www.paypalobjects.com/webstatic/icon/pp258.png",
        btc: "https://cryptologos.cc/logos/bitcoin-btc-logo.svg?v=025",
        eth: "https://cryptologos.cc/logos/ethereum-eth-logo.svg?v=025",
        sol: "https://cryptologos.cc/logos/solana-sol-logo.svg?v=025",
        bnb: "https://cryptologos.cc/logos/bnb-bnb-logo.svg?v=025",
        matic: "https://cryptologos.cc/logos/polygon-matic-logo.svg?v=025",
        usdt: "https://cryptologos.cc/logos/tether-usdt-logo.svg?v=025",
        bubble: "https://img.icons8.com/?size=100&id=9F8aDI7mYs6V&format=png&color=ffffff",
        warn: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Antu_dialog-warning.svg/200px-Antu_dialog-warning.svg.png"
  };

  // --- LOCALIZATION ---
  const EN_BASE = {
        title: "Local Downloader PRO", tab_dl: "Downloads", tab_batch: "Batch List", tab_sup: "Donate", tab_help: "Help",
        vid: "🎬 Video", aud: "🎵 Audio", img: "🖼️ Image", queue: "Queue", done: "Done", err: "Error", refresh: "🔄 Refresh", clear: "🗑️ Clear",
        conn_err: "Server Offline? Start the App!", open: "Open", folder: "Folder", sup_title: "SUPPORT THE CODE", sup_desc: "Help keep updates coming!",
        lbl_pix: "PIX KEY (BR)", btn_copy: "COPY", auto_dl: "⬇️ Saved: ", wallet_title: "CRYPTO WALLETS", login_err: "⚠️ LOGIN NEEDED",
        retry: "Retry", cancel: "Cancel", open_panel: "🚀 Open Server Panel", toggle: "👁️ Show/Hide UI",
        help_btn: "❓ Help / Install", back: "Back to Panel",
        batch_ph: "Paste links here (one per line)...", batch_btn: "PROCESS LIST", batch_sent: "Links sent: ",
        sc_vid: "SHIFT + Right Click", sc_aud: "ALT + Right Click", sc_img: "CTRL + Right Click",
        pro_tip: "💡 PRO TIP: No need to open the video! Hold the shortcut key and use Right Click directly on the thumbnail (Home or Sidebar) to download instantly.",
        err_old_ver: "⚠️ Requires New Universal Server! (See Help)",
        help_login_err: "Login Error? Click the yellow warning.",
        footer_msg: "Tauã B. Kloch Leite - All Rights Reserved 2025",
        help_title: "INSTALLATION REQUIRED",
        help_s1: "1. Download Universal_Downloader.exe", help_s2: "2. Open the App", help_s3: "3. Click 'Start Server'",
        help_btn_dl: "DOWNLOAD SERVER", help_warn: "The script needs this app!",
        univ_note: "NOTE: The new server is UNIVERSAL (works on any site). Update now!",
        menu_toggle: "👁️ Show/Hide UI (Alt+Shift+Y)", menu_help: "❓ Help / Shortcuts", menu_panel: "⚙️ Open Panel", menu_dl: "📥 Download Server",
        menu_update: "🔄 Check Update",
        btn_panel: "Panel",
        tip_title: "SHORTCUTS (Right Click on Thumb):", tip_1: "SHIFT: Video", tip_2: "ALT: Audio", tip_3: "CTRL: Image"
  };

  const STRINGS = {
    en: EN_BASE,
    pt: {
        ...EN_BASE,
        title: "Downloader Local PRO", tab_dl: "Downloads", tab_batch: "Lista Batch", tab_sup: "Doação", tab_help: "Ajuda",
        vid: "🎬 Vídeo", aud: "🎵 Áudio", img: "🖼️ Imagem", queue: "Fila", done: "Prontos", err: "Erros", refresh: "🔄 Atualizar", clear: "🗑️ Limpar",
        conn_err: "Servidor Offline? Inicie o App!", open: "Abrir", folder: "Pasta", sup_title: "APOIE O PROJETO", sup_desc: "Mantenha as atualizações vivas!",
        lbl_pix: "CHAVE PIX", btn_copy: "COPIAR", auto_dl: "⬇️ Salvo: ", wallet_title: "CARTEIRAS CRIPTO", login_err: "⚠️ LOGIN NECESSÁRIO",
        retry: "🔄 Reiniciar", cancel: "❌ Cancelar", open_panel: "🚀 Abrir Painel Server", toggle: "👁️ Mostrar/Ocultar UI",
        help_btn: "❓ Ajuda / Instalação", back: "Voltar para o Painel",
        batch_ph: "Cole os links aqui (um por linha)...", batch_btn: "PROCESSAR LISTA", batch_sent: "Links enviados: ",
        sc_vid: "SHIFT + Clique Direito", sc_aud: "ALT + Clique Direito", sc_img: "CTRL + Clique Direito",
        pro_tip: "💡 DICA PRO: Não precisa abrir o vídeo! Segure a tecla de atalho e use o Clique Direito direto na miniatura (Home ou Lateral) para baixar instantaneamente.",
        err_old_ver: "⚠️ Requer Novo Servidor Universal! (Ver Ajuda)",
        help_login_err: "Erro de Login? Clique no aviso amarelo.",
        footer_msg: "Tauã B. Kloch Leite - All Rights Reserved 2025",
        help_title: "INSTALAÇÃO NECESSÁRIA",
        help_s1: "1. Baixe o Universal_Downloader.exe", help_s2: "2. Abra o Aplicativo", help_s3: "3. Clique em 'Start Server'",
        help_btn_dl: "BAIXAR SERVIDOR", help_warn: "O script precisa disso!",
        univ_note: "NOTA: O novo servidor é UNIVERSAL (funciona em todos os sites). Atualize!",
        menu_toggle: "👁️ Mostrar/Ocultar UI (Alt+Shift+Y)", menu_help: "❓ Ajuda / Atalhos", menu_panel: "⚙️ Abrir Painel", menu_dl: "📥 Baixar Servidor",
        menu_update: "🔄 Verificar Atualização",
        btn_panel: "Painel",
        tip_title: "ATALHOS (Clique Direito na Miniatura):", tip_1: "SHIFT: Vídeo", tip_2: "ALT: Áudio", tip_3: "CTRL: Imagem"
    },
    es: { ...EN_BASE, title: "Descargador Local PRO", sc_vid: "SHIFT + Clic Derecho", sc_aud: "ALT + Clic Derecho", sc_img: "CTRL + Clic Derecho", pro_tip: "💡 TIP PRO: ¡Usa atajos con Clic Derecho en la miniatura para descargar sin abrir el video!", err_old_ver: "⚠️ ¡Requiere Nuevo Servidor Universal!", univ_note: "NOTA: El nuevo servidor es UNIVERSAL. ¡Actualiza!", menu_help: "❓ Ayuda / Atajos", btn_panel: "Panel", conn_err: "¿Servidor Offline? ¡Inicia la App!", menu_update: "🔄 Buscar Actualización", tip_title: "ATAJOS:", tip_1: "SHIFT: Video", tip_2: "ALT: Audio", tip_3: "CTRL: Imagen" },
    ru: { ...EN_BASE, title: "Локальный Загрузчик PRO", sc_vid: "SHIFT + ПКМ", sc_aud: "ALT + ПКМ", sc_img: "CTRL + ПКМ", pro_tip: "💡 СОВЕТ: Используйте горячие клавиши + ПКМ по миниатюре для быстрой загрузки!", err_old_ver: "⚠️ Требуется новый универсальный сервер!", univ_note: "ПРИМЕЧАНИЕ: Новый сервер УНИВЕРСАЛЕН. Обновите!", menu_help: "❓ Помощь / Ярлыки", btn_panel: "Панель", conn_err: "Сервер офлайн? Запустите приложение!", menu_update: "🔄 Проверить Обновление", tip_title: "ГОРЯЧИЕ КЛАВИШИ:", tip_1: "SHIFT: Видео", tip_2: "ALT: Аудио", tip_3: "CTRL: Фото" },
    fr: { ...EN_BASE, title: "Téléchargeur Local PRO", sc_vid: "SHIFT + Clic Droit", sc_aud: "ALT + Clic Droit", sc_img: "CTRL + Clic Droit", pro_tip: "💡 ASTUCE PRO : Utilisez les raccourcis + Clic Droit sur la miniature pour télécharger sans ouvrir !", err_old_ver: "⚠️ Nouveau serveur universel requis !", univ_note: "NOTE : Le nouveau serveur est UNIVERSEL. Mettez à jour !", menu_help: "❓ Aide / Raccourcis", btn_panel: "Panneau", conn_err: "Serveur hors ligne ? Démarrez l'appli !", menu_update: "🔄 Vérifier Mise à Jour", tip_title: "RACCOURCIS:", tip_1: "SHIFT: Vidéo", tip_2: "ALT: Audio", tip_3: "CTRL: Image" },
    de: { ...EN_BASE, title: "Lokaler Downloader PRO", sc_vid: "SHIFT + Rechtsklick", sc_aud: "ALT + Rechtsklick", sc_img: "CTRL + Rechtsklick", pro_tip: "💡 PRO TIPP: Tastenkürzel + Rechtsklick auf Thumbnail zum sofortigen Download!", err_old_ver: "⚠️ Neuer Universal-Server erforderlich!", univ_note: "HINWEIS: Der neue Server ist UNIVERSELL. Aktualisieren!", menu_help: "❓ Hilfe / Verknüpfungen", btn_panel: "Panel", conn_err: "Server offline? Starten Sie die App!", menu_update: "🔄 Update Prüfen", tip_title: "VERKNÜPFUNGEN:", tip_1: "SHIFT: Video", tip_2: "ALT: Audio", tip_3: "CTRL: Bild" },
    it: { ...EN_BASE, title: "Downloader Locale PRO", sc_vid: "SHIFT + Tasto Destro", sc_aud: "ALT + Tasto Destro", sc_img: "CTRL + Tasto Destro", pro_tip: "💡 SUGGERIMENTO: Usa scorciatoie + Tasto Destro sulla miniatura per scaricare subito!", err_old_ver: "⚠️ Richiede Nuovo Server Universale!", univ_note: "NOTA: Il nuovo server è UNIVERSALE. Aggiorna!", menu_help: "❓ Aiuto / Scorciatoie", btn_panel: "Pannello", conn_err: "Server offline? Avvia l'app!", menu_update: "🔄 Controlla Aggiornamento", tip_title: "SCORCIATOIE:", tip_1: "SHIFT: Video", tip_2: "ALT: Audio", tip_3: "CTRL: Immagine" },
    zh: { ...EN_BASE, title: "本地下载器 PRO", sc_vid: "SHIFT + 右键", sc_aud: "ALT + 右键", sc_img: "CTRL + 右键", pro_tip: "💡以此提示:使用快捷键+右键点击缩略图即可直接下载!", err_old_ver: "⚠️ 需要新的通用服务器!", univ_note: "注意:新服务器是通用的。请更新!", menu_help: "❓ 帮助/快捷方式", btn_panel: "面板", conn_err: "服务器离线?启动应用程序!", menu_update: "🔄 检查更新", tip_title: "快捷键:", tip_1: "SHIFT: 视频", tip_2: "ALT: 音频", tip_3: "CTRL: 图片" },
    ja: { ...EN_BASE, title: "ローカルダウンローダー PRO", sc_vid: "SHIFT + 右クリック", sc_aud: "ALT + 右クリック", sc_img: "CTRL + 右クリック", pro_tip: "💡 ヒント: ショートカットキーを押しながらサムネイルを右クリックすると、すぐにダウンロードできます!", err_old_ver: "⚠️ 新しいユニバーサルサーバーが必要です!", univ_note: "注意:新しいサーバーはユニバーサルです。更新してください!", menu_help: "❓ ヘルプ / ショートカット", btn_panel: "パネル", conn_err: "サーバーオフライン?アプリを起動!", menu_update: "🔄 更新を確認", tip_title: "ショートカット:", tip_1: "SHIFT: ビデオ", tip_2: "ALT: オーディオ", tip_3: "CTRL: 画像" },
    ko: { ...EN_BASE, title: "로컬 다운로더 PRO", sc_vid: "SHIFT + 우클릭", sc_aud: "ALT + 우클릭", sc_img: "CTRL + 우클릭", pro_tip: "💡 팁: 단축키를 누른 상태에서 썸네일을 우클릭하면 즉시 다운로드됩니다!", err_old_ver: "⚠️ 새로운 유니버설 서버 필요!", univ_note: "참고: 새 서버는 범용입니다. 업데이트하세요!", menu_help: "❓ 도움말 / 단축키", btn_panel: "패널", conn_err: "서버 오프라인? 앱 실행!", menu_update: "🔄 업데이트 확인", tip_title: "단축키:", tip_1: "SHIFT: 비디오", tip_2: "ALT: 오디오", tip_3: "CTRL: 이미지" }
  };

  const getLang = () => {
      const l = navigator.language || "en";
      const code = l.split('-')[0];
      if (STRINGS[l]) return { ...EN_BASE, ...STRINGS[l] };
      if (STRINGS[code]) return { ...EN_BASE, ...STRINGS[code] };
      return EN_BASE;
  };
  const T = getLang();

  // --- STATE ---
  const state = { uiMode: GM_getValue("yt_dl_uiMode", 1), stats: {}, items: [], activeTab: 'dl' };
  const imgCache = {};
  let lastHtml = '';
  let isServerOnline = false;
  let isProcessingClick = false;

  let bubblePos = { left: '20px', bottom: '20px', top: 'auto', right: 'auto' };
  let panelPos = null;

  const setUIMode = (m) => {
      if (container) {
          if (state.uiMode === 1) {
              bubblePos = { left: container.style.left, top: container.style.top, bottom: container.style.bottom, right: container.style.right };
          } else if (state.uiMode === 2) {
              panelPos = { left: container.style.left, top: container.style.top, width: container.style.width, height: container.style.height };
          }
      }

      state.uiMode = m;
      GM_setValue("yt_dl_uiMode", m);
      renderUI();

      if (!container) return;

      if (m === 1) {
          container.style.width = '';
          container.style.height = '';
          container.style.resize = 'none';
          applyStyles(container, bubblePos);
      } else if (m === 2) {
          container.style.resize = 'both';

          if (panelPos) {
              applyStyles(container, { ...panelPos, bottom: 'auto', right: 'auto' });
              if(panelPos.width) container.style.width = panelPos.width;
              if(panelPos.height) container.style.height = panelPos.height;
          } else {
              const bRect = container.getBoundingClientRect();
              container.style.bottom = 'auto'; container.style.right = 'auto';

              let startLeft = bubblePos.left;
              if(!startLeft || startLeft === 'auto') startLeft = '20px';

              let calcTop = parseInt(bubblePos.top);
              if (bubblePos.bottom && bubblePos.bottom !== 'auto') {
                  const winH = window.innerHeight;
                  const bottomVal = parseInt(bubblePos.bottom);
                  calcTop = winH - bottomVal - 460;
              } else {
                  if (!calcTop) calcTop = 60;
              }

              if (calcTop < 10) calcTop = 10;
              if (calcTop > window.innerHeight - 100) calcTop = window.innerHeight - 450;

              container.style.left = startLeft;
              container.style.top = calcTop + 'px';
          }
      }
  };

  const applyStyles = (el, styles) => {
      if(styles.left) el.style.left = styles.left;
      if(styles.top) el.style.top = styles.top;
      if(styles.bottom) el.style.bottom = styles.bottom;
      if(styles.right) el.style.right = styles.right;
  };

  const getHistory = () => GM_getValue('yt_dl_history_local', []);
  const addToHistory = (f) => { let h=getHistory(); if(!h.includes(f)){ h.push(f); if(h.length>50)h.shift(); GM_setValue('yt_dl_history_local', h); }};

  // --- HELPERS ---
  const cleanFileName = (name) => name.replace(/[^a-z0-9\u00a0-\uffff _-]/gi, '_').trim();
  const generateRandomId = () => Math.floor(Math.random() * 900000) + 100000;

  const getYoutubeVideoID = (url) => {
      try {
          const u = new URL(url);
          if (u.hostname.includes('youtube.com')) {
              if (u.pathname.startsWith('/shorts/')) return u.pathname.split('/')[2];
              return u.searchParams.get('v');
          }
          if (u.hostname.includes('youtu.be')) return u.pathname.slice(1);
      } catch(e){}
      return null;
  };

  const gmFetch = (url, options = {}) => {
      return new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
              method: options.method || "GET",
              url: url,
              headers: options.headers || {},
              data: options.body,
              timeout: options.customTimeout || 2000,
              responseType: options.responseType || null,
              onload: (res) => {
                  if (!res.status || res.status === 0) return reject("OFFLINE");
                  try {
                      if(options.responseType === 'arraybuffer' || options.responseType === 'blob') {
                            resolve(res.response);
                      } else {
                          resolve({ json: () => JSON.parse(res.responseText), ok: true, status: res.status });
                      }
                  } catch (e) { reject(e); }
              },
              onerror: () => reject("OFFLINE"),
              ontimeout: () => reject("OFFLINE")
          });
      });
  };

  const bufferToBase64 = (buffer) => {
      let binary = '';
      const bytes = new Uint8Array(buffer);
      const len = bytes.byteLength;
      for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
      return window.btoa(binary);
  };

  const tunnelUniversalImage = (imgElement, path, id) => {
      if (imgCache[id]) { imgElement.src = imgCache[id]; return; }
      let url = path.startsWith('/') ? `${SERVER_URL}${path}` : path;
      gmFetch(url, { responseType: 'arraybuffer', customTimeout: 5000 }).then(buffer => {
          const base64 = bufferToBase64(buffer);
          let mime = 'image/jpeg';
          if(path.toLowerCase().endsWith('.png')) mime = 'image/png';
          if(path.toLowerCase().endsWith('.webp')) mime = 'image/webp';
          const dataUri = `data:${mime};base64,${base64}`;
          imgCache[id] = dataUri;
          imgElement.src = dataUri;
      }).catch(() => { imgElement.src = ""; });
  };

  const getImgFromContext = (el) => {
      if (!el) return null;
      if (el.tagName === 'IMG') return el;
      let img = el.querySelector('img');
      if (img) return img;
      let link = el.closest('a');
      if (link) img = link.querySelector('img');
      if (img) return img;
      let parent = el.parentElement;
      for(let i=0; i<5 && parent; i++) {
          img = parent.querySelector('img');
          if(img) return img;
          parent = parent.parentElement;
      }
      return null;
  };

  // --- SMART GRABBER ---
  const findMediaUrl = (target, mode) => {
      let foundUrl = null, foundThumb = null, foundTitle = null;

      // 1. Image Specific Logic
      if (mode === 'image') {
          const container = target.closest('ytd-compact-video-renderer, ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-playlist-panel-video-renderer, ytd-reel-item-renderer');

          if (container) {
              const link = container.querySelector('a#thumbnail, a[href*="/watch"]');
              const imgEl = container.querySelector('ytd-thumbnail img') || container.querySelector('img');
              const titleEl = container.querySelector('#video-title');

              if (imgEl && imgEl.src) {
                    foundUrl = imgEl.src.split('?')[0];
                    foundThumb = foundUrl;
              } else if (link) {
                    const deepImg = link.querySelector('img');
                    if(deepImg) {
                        foundUrl = deepImg.src.split('?')[0];
                        foundThumb = foundUrl;
                    }
              }

              if (titleEl) {
                  foundTitle = titleEl.textContent.trim() || titleEl.title;
              }

              if (foundUrl) {
                  if (!foundTitle) foundTitle = "Image_Sidebar";
                  const uniqueTitle = `${cleanFileName(foundTitle)}_${generateRandomId()}`;
                  return { url: foundUrl, thumb: foundThumb, title: uniqueTitle };
              }
          }
      }

      // 2. Logic for Video/Audio
      if (!foundUrl) {
          const link = target.closest('a[href*="/watch"], a[href*="/shorts/"]');
          if (link) {
              foundUrl = link.href;
              const vidId = getYoutubeVideoID(foundUrl);
              if (vidId) {
                  foundThumb = `https://i.ytimg.com/vi/${vidId}/hqdefault.jpg`;
              }

              const container = target.closest('ytd-compact-video-renderer') || target.closest('ytd-video-renderer') || target.closest('ytd-rich-item-renderer') || target.closest('ytd-grid-video-renderer');
              if (container) {
                  const titleEl = container.querySelector('#video-title');
                  if (titleEl) foundTitle = titleEl.textContent.trim();
              }
          }
      }

      // 3. Fallback to current page
      if (!foundUrl) {
          foundUrl = window.location.href;
          foundTitle = document.title.replace(" - YouTube", "");
      }

      // 4. MAIN PAGE THUMBNAIL FIX
      if (foundUrl && !foundThumb && (window.location.pathname.startsWith('/watch') || window.location.pathname.startsWith('/shorts/'))) {
            const vidId = getYoutubeVideoID(foundUrl);
            if(vidId) {
                foundThumb = `https://i.ytimg.com/vi/${vidId}/maxresdefault.jpg`;
            }
      }

      // 5. Ensure Title is unique
      if (!foundTitle) foundTitle = "Media";
      const uniqueTitle = `${cleanFileName(foundTitle)}_${generateRandomId()}`;

      return { url: foundUrl, thumb: foundThumb, title: uniqueTitle };
  };

  const handleShortcut = (e, type) => {
      e.preventDefault();
      const media = findMediaUrl(e.target, type);
      if(media.url) {
          send(type, media);
      } else {
          toast("Media Not Found", false);
      }
  };

  document.addEventListener('contextmenu', (e) => {
      if (e.shiftKey) handleShortcut(e, 'video');
      if (e.altKey) handleShortcut(e, 'audio');
      if (e.ctrlKey) handleShortcut(e, 'image');
  });

  // --- DRAG LOGIC ---
  let isDraggingUI = false;

  const makeDraggable = (el) => {
      let startX, startY, initialLeft, initialTop;

      const onMouseDown = (e) => {
          if (state.uiMode === 2 && !e.target.closest('.yt-dl-head') && !e.target.closest('.yt-dl-footer')) return;
          if (state.uiMode === 1 && !e.target.closest('.yt-dl-bubble')) return;

          if (state.uiMode === 2) {
              const rect = el.getBoundingClientRect();
              if (e.clientX > rect.right - 20 && e.clientY > rect.bottom - 20) return;
          }

          isDraggingUI = true;
          el.dataset.moved = "false";
          startX = e.clientX; startY = e.clientY;

          const rect = el.getBoundingClientRect();
          initialLeft = rect.left; initialTop = rect.top;

          el.style.bottom = 'auto'; el.style.right = 'auto';
          el.style.left = initialLeft + 'px'; el.style.top = initialTop + 'px';

          e.preventDefault();
      };

      const onMouseMove = (e) => {
          if (!isDraggingUI) return;
          const dx = e.clientX - startX;
          const dy = e.clientY - startY;
          if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
              el.dataset.moved = "true";
              el.style.left = (initialLeft + dx) + 'px';
              el.style.top = (initialTop + dy) + 'px';
          }
      };

      const onMouseUp = () => {
          if (isDraggingUI) {
              isDraggingUI = false;
              if (state.uiMode === 1) {
                  bubblePos = { left: el.style.left, top: el.style.top, bottom: 'auto', right: 'auto' };
              } else {
                  panelPos = { left: el.style.left, top: el.style.top, width: el.style.width, height: el.style.height };
              }
          }
      };

      el.addEventListener('mousedown', onMouseDown);
      window.addEventListener('mousemove', onMouseMove);
      window.addEventListener('mouseup', onMouseUp);
  };

  // --- API ---
  const clearList = async () => { try { await gmFetch(`${SERVER_URL}/clear`, { method: 'POST', customTimeout: 1000 }); } catch(e){ } GM_setValue('yt_dl_history_local', []); state.items = []; state.stats = { total:0, in_progress:0, finished:0, errors:0 }; lastHtml = ''; updateListContent(); };
  const openLocalFile = async (filename) => { try { await gmFetch(`${SERVER_URL}/open_file`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({filename: filename}), customTimeout: 1000 }); } catch(e) { if(e === "OFFLINE") toast(T.conn_err, false); } };
  const openFolder = async (type) => { try { await gmFetch(`${SERVER_URL}/open_folder`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({type: type}), customTimeout: 1000 }); } catch(e) { if(e === "OFFLINE") toast(T.conn_err, false); } };
  const copyToClipboard = (text) => { GM_setClipboard(text); toast(T.btn_copy + " OK!"); };
  const cancelDownload = async (id) => { try { await gmFetch(`${SERVER_URL}/cancel`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({id: id}), customTimeout: 1000 }); toast(T.cancel + " OK"); refreshData(); } catch(e) { } };

  // --- BUTTON STATE CHECKER ---
  const updateButtonState = () => {
      if(!container || state.uiMode !== 2) return;

      const path = window.location.pathname;
      const isVideoPage = path.startsWith('/watch') || path.startsWith('/shorts/');

      ['btn-vid', 'btn-aud', 'btn-img'].forEach(id => {
          const btn = document.getElementById(id);
          if(btn) btn.disabled = !isVideoPage;
      });
  };

  const processBatch = () => {
      const area = document.getElementById('yt-dl-batch-area');
      if(!area) return;
      const lines = area.value.split('\n');
      let count = 0;
      lines.forEach(line => {
          const url = line.trim();
          if(url.startsWith('http')) {
              send('video', { url: url, thumb: null, title: `Batch_${generateRandomId()}` });
              count++;
          }
      });
      area.value = '';
      lastHtml = '';
      toast(`${T.batch_sent}${count}`);
      state.activeTab = 'dl';
      renderUI();
  };

  const refreshData = async () => {
      updateButtonState();
      try {
          const [sRes, fRes] = await Promise.all([
              gmFetch(`${SERVER_URL}/stats`, { customTimeout: 1000 }),
              gmFetch(`${SERVER_URL}/files`, { customTimeout: 1000 })
          ]);
          isServerOnline = true;
          state.stats = await sRes.json();
          const files = await fRes.json();
          state.items = files.items || [];
          state.items.forEach(i => {
              if(i.status === 'finished' && i.filename && !getHistory().includes(i.filename)) {
                  addToHistory(i.filename);
                  toast(T.auto_dl + i.title.substring(0,20)+"...");
              }
          });
          if(state.uiMode === 2) updateListContent();
      } catch (e) {
          isServerOnline = false;
      }
  };

  const send = async (type, mediaData) => {
      if (!isServerOnline) {
          toast(T.conn_err, false);
          gmFetch(`${SERVER_URL}/stats`, { customTimeout: 500 }).then(()=> isServerOnline=true).catch(()=>{});
          return;
      }

      if (isProcessingClick) return;
      isProcessingClick = true;
      setTimeout(() => isProcessingClick = false, 500);

      try {
          let finalUrl, thumbUrl, title;

          if (typeof mediaData === 'object' && mediaData.url) {
              finalUrl = mediaData.url;
              thumbUrl = mediaData.thumb;
              title = mediaData.title;
          } else {
              finalUrl = location.href;
              const extracted = findMediaUrl(document.body, type);
              thumbUrl = extracted.thumb;
              title = extracted.title;
          }

          let endpoint = 'download';
          if (type === 'audio') endpoint = 'download_audio';
          if (type === 'image') endpoint = 'download_image';

          const response = await gmFetch(`${SERVER_URL}/${endpoint}`, {
              method: 'POST', headers: {'Content-Type': 'application/json'},
              body: JSON.stringify({ videoUrl: finalUrl, thumb: thumbUrl, type: type, title: title }),
              customTimeout: 2500
          });

          if (!response.ok) {
              if (type === 'image') throw new Error("OLD_SERVER");
              throw new Error("Generic Error");
          }
          lastHtml = ''; // Force refresh
          refreshData();
          toast(`${type.toUpperCase()} OK 🚀`);
          if(state.uiMode === 1) setUIMode(2);
      } catch(e) {
          if (e === "OFFLINE") {
               toast(T.conn_err, false);
               isServerOnline = false;
          } else if (e.message === "OLD_SERVER") {
               toast(T.err_old_ver, false);
          } else {
               toast(T.conn_err, false);
          }
      }
  };

  // --- CSS ---
  const css = `
    .yt-dl-container { font-family: 'Roboto', sans-serif; z-index: 2147483647; position: fixed; bottom: 20px; left: 20px; }
    @media (max-width: 768px) { .yt-dl-panel { width: 90% !important; left: 5% !important; bottom: 10px !important; } }
    .yt-dl-bubble { width: 50px; height: 50px; background: #d63384; border-radius: 50%; box-shadow: 0 4px 15px rgba(0,0,0,0.5); cursor: move; display: flex; align-items: center; justify-content: center; transition: transform 0.2s; border: 2px solid #fff; }
    .yt-dl-bubble:hover { transform: scale(1.1); }
    .yt-dl-bubble img { width: 30px; height: 30px; }
    .yt-dl-panel { width: 350px; min-width: 320px; min-height: 200px; max-width: 95vw; max-height: 95vh;
                   resize: both; overflow: hidden; display: flex; flex-direction: column;
                   background: #0f0f0f; color: #fff; border-radius: 12px; border: 1px solid #333; font-size: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.9); animation: slideUp 0.3s ease-out; }
    @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
    .yt-dl-head { background: #1a1a1a; padding: 10px 15px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; cursor: move; flex-shrink: 0; }
    .yt-dl-min-btn { cursor: pointer; font-size: 18px; color: #aaa; padding: 0 5px; }
    .yt-dl-min-btn:hover { color: #fff; }
    .progress-bg { width: 100%; height: 4px; background: #333; margin-top: 4px; border-radius: 2px; overflow: hidden; }
    .progress-fill { height: 100%; background: #4caf50; width: 0%; transition: width 0.3s ease; }
    .prog-text { font-size: 9px; color: #888; text-align: right; margin-top: 2px; }
    .yt-dl-tabs { display: flex; background: #111; flex-shrink: 0; }
    .yt-dl-tab { flex: 1; text-align: center; padding: 10px 0; cursor: pointer; color: #aaa; border-bottom: 2px solid transparent; font-weight: 700; text-transform: uppercase; font-size: 10px; }
    .yt-dl-tab.active { color: #fff; border-bottom: 2px solid #d63384; background: #222; }
    .yt-dl-body { flex: 1; overflow-y: auto; padding: 15px; }
    .yt-dl-footer { text-align: center; font-size: 9px; color: #555; border-top: 1px solid #222; padding: 8px 0; flex-shrink: 0; background: #0f0f0f; cursor: move; }
    .yt-dl-btn-group { display: flex; gap: 8px; margin-bottom: 5px; }
    .yt-dl-btn { flex: 1; border: none; padding: 10px; border-radius: 6px; cursor: pointer; color: #fff; font-weight: 700; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 5px; transition: 0.2s; }
    .yt-dl-btn:hover { filter: brightness(1.1); }
    .yt-dl-btn:disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(100%); }
    .btn-blue { background: #3ea6ff; color: #000; } .btn-purple { background: #d63384; } .btn-gray { background: #333; border: 1px solid #444; } .btn-red { background: #d32f2f; } .btn-orange { background: #ff9800; color:#000; }
    .yt-dl-item { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid #222; }
    .yt-dl-thumb { width: 50px; height: 50px; background: #000; border-radius: 6px; object-fit: cover; }
    .yt-dl-info { flex: 1; overflow: hidden; }
    .yt-dl-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; font-size: 12px; margin-bottom: 4px; }
    .yt-dl-status { font-size: 10px; display: flex; align-items: center; gap: 6px; }
    .tag-type { padding: 2px 6px; border-radius: 4px; font-weight: bold; font-size: 9px; text-transform: uppercase; }
    .tag-vid { background: #0f3d5c; color: #3ea6ff; border: 1px solid #1e5985; }
    .tag-aud { background: #3c1f30; color: #ff66b2; border: 1px solid #7d2a58; }
    .tag-img { background: #3d2b0f; color: #ff9800; border: 1px solid #855a15; }
    .ctrl-btn { background: #333; border: 1px solid #444; color: #ccc; cursor: pointer; font-size: 10px; border-radius: 4px; padding: 3px 8px; margin-left: 5px; }
    .ctrl-btn:hover { background: #555; color: #fff; }
    .btn-retry { color: #4caf50; border-color: #2e7d32; } .btn-cancel { color: #f44336; border-color: #c62828; }
    .sup-row { display: flex; align-items: center; gap: 8px; background: #1a1a1a; padding: 8px; border-radius: 6px; border: 1px solid #333; margin-bottom: 8px; }
    .sup-icon { width: 20px; height: 20px; object-fit: contain; }
    .sup-val { flex: 1; background: none; border: none; color: #eee; font-size: 11px; font-family: monospace; outline: none; }
    .sup-copy { background: #d63384; border: none; color: #fff; border-radius: 4px; cursor: pointer; font-size: 10px; padding: 4px 8px; }
    .auth-fix-btn { cursor: pointer; text-decoration: underline; }
    .auth-fix-btn:hover { color: #fff !important; }
    .batch-area { width: 100%; height: 100px; background: #0a0a0a; color: #ddd; border: 1px solid #333; padding: 10px; font-size: 11px; box-sizing: border-box; resize: vertical; margin-bottom: 10px; border-radius: 6px; }
    .yt-dl-toast { position: fixed; top: 20px; right: 20px; background: #28a745; color: white; padding: 10px 20px; border-radius: 4px; z-index: 2147483648; font-weight: bold; animation: fadein 0.5s; }
    @keyframes fadein { from { opacity:0; transform:translateY(-10px); } to { opacity:1; transform:translateY(0); } }
  `;
  const injectCSS = () => { if(!document.getElementById("yt-dl-style")) { const s=document.createElement("style"); s.id="yt-dl-style"; s.textContent=css; document.head.appendChild(s); }};

  const toast = (msg, success=true) => {
      const existing = document.querySelector('.yt-dl-toast');
      if (existing) existing.remove();

      const el=document.createElement("div");
      el.className="yt-dl-toast";
      el.textContent=msg;
      if(!success) el.style.background="#f44336";
      document.body.appendChild(el);
      setTimeout(()=> { if(el.parentNode) el.remove(); }, 3000);
  };

  let container;

  // --- HTML GENERATOR ---
  const generateListHTML = () => {
      if(state.items.length === 0) return `<div style="text-align:center;color:#444;padding:20px;">Empty list</div>`;
      return state.items.slice().reverse().slice(0,5).map(i => {
            const isAud = i.type === 'audio'; const isImg = i.type === 'image';
            let tagClass = 'tag-vid'; let tagTxt = 'MP4';
            if(isAud) { tagClass='tag-aud'; tagTxt='MP3'; }
            if(isImg) { tagClass='tag-img'; tagTxt='IMG'; }

            let statusHtml = `<span style="color:${i.status==='finished'?'#4caf50':(i.status==='error'?'#f44336':'#aaa')}">${i.status}</span>`;
            if(i.status==='auth_error') statusHtml = `<span class="auth-fix-btn" style="color:#ff9800;font-weight:bold" title="Click to Fix">${T.login_err}</span>`;
            if(i.status==='cancelled') statusHtml = `<span style="color:#f44336;font-size:10px">${T.cancel}</span>`;

            let progressHtml = '';
            if (i.status === 'downloading' || i.status === 'recording') {
                let pct = i.progress ? i.progress : 0;
                if(i.status === 'recording') pct = 100;

                progressHtml = `
                <div class="progress-bg">
                    <div class="progress-fill" style="width:${pct}%"></div>
                </div>
                <div class="prog-text">${i.status === 'recording' ? 'REC ●' : pct + '%'}</div>
                `;
            }

            let actions = '';
            if(i.status === 'downloading' || i.status === 'queued' || i.status === 'recording') {
                actions = `<button class="ctrl-btn btn-cancel" data-act="cancel" data-id="${i.id}">${T.cancel}</button>`;
            } else if(i.status === 'finished') {
                actions = `<button class="ctrl-btn" data-act="open" data-file="${encodeURIComponent(i.filename)}">▶️</button> <button class="ctrl-btn" data-act="folder" data-type="${i.type}">📂</button>`;
            } else if(i.status === 'error' || i.status === 'cancelled' || i.status === 'auth_error') {
                actions = `<button class="ctrl-btn btn-retry" data-act="retry" data-url="${i.url}" data-type="${i.type}" data-thumb="${i.thumb}">${T.retry}</button>`;
            }

            let thumbSrc = "";
            let useTunnel = false, dataTunnel = "";

            if (i.thumb && i.thumb.length > 5) {
                thumbSrc = i.thumb;
                if (!i.thumb.startsWith('https://')) useTunnel = true;
                dataTunnel = i.thumb;
            } else if (i.status === 'finished' && i.type === 'image' && i.filename) {
                dataTunnel = `/file/${encodeURIComponent(i.filename)}`;
                useTunnel = true;
            }
            if(imgCache[i.id]) { thumbSrc = imgCache[i.id]; useTunnel = false; }

            const imgHTML = `<img class="yt-dl-thumb" src="${useTunnel ? '' : thumbSrc}" ${useTunnel && !imgCache[i.id] ? `data-tunnel="${dataTunnel}" data-id="${i.id}"` : ''} onerror="this.style.display='none'">`;

            return `
            <div class="yt-dl-item">
                ${imgHTML}
                <div class="yt-dl-info">
                    <div class="yt-dl-name" title="${i.title}">${isAud?'🎵':(isImg?'🖼️':'🎬')} ${i.title||'...'}</div>
                    <div class="yt-dl-status">
                        <span class="tag-type ${tagClass}">${tagTxt}</span>
                        ${statusHtml}
                    </div>
                    ${progressHtml}
                </div>
                <div style="display:flex; flex-direction:column; gap:2px;">${actions}</div>
            </div>`;
      }).join('');
  };

  const updateListContent = () => {
      if(!container || state.uiMode !== 2) return;
      const listEl = document.getElementById('yt-dl-list');
      const statsEl = document.getElementById('yt-dl-stats-bar');

      const newHtml = generateListHTML();
      if(listEl && newHtml !== lastHtml) {
          listEl.innerHTML = safeHTML(newHtml);
          lastHtml = newHtml;

          listEl.querySelectorAll('img[data-tunnel]').forEach(img => {
              const url = img.getAttribute('data-tunnel');
              const id = img.getAttribute('data-id');
              if(url && id) tunnelUniversalImage(img, url, id);
          });
          bindListButtons();
      }

      if(statsEl) statsEl.innerHTML = safeHTML(`<span>${T.queue}: <b style="color:#ffeb3b">${state.stats.in_progress||0}</b></span> <span>${T.done}: <b style="color:#4caf50">${state.stats.finished||0}</b></span> <span>${T.err}: <b style="color:#f44336">${state.stats.errors||0}</b></span>`);
  };

  const bindListButtons = () => {
      if(!container) return;
      container.querySelectorAll('.ctrl-btn').forEach(b => {
          b.onclick = (e) => {
              const d = e.target.dataset;
              if(d.act === 'open') openLocalFile(decodeURIComponent(d.file));
              if(d.act === 'folder') openFolder(d.type);
              if(d.act === 'cancel') cancelDownload(d.id);
              if(d.act === 'retry') send(d.type, d.url, d.thumb);
          };
      });
      container.querySelectorAll('.auth-fix-btn').forEach(b => { b.onclick = (e) => { e.preventDefault(); GM_openInTab(`${SERVER_URL}/panel?tab=cook`, {active: true}); }; });
  };

  // --- UI RENDERER ---
  const renderUI = () => {
      injectCSS();
      if(!container) { container=document.createElement('div'); container.className='yt-dl-container'; document.body.appendChild(container); makeDraggable(container); }
      if(state.uiMode === 0) { container.style.display = 'none'; return; }
      container.style.display = 'block';

      if(state.uiMode === 1) {
          container.innerHTML = safeHTML(`<div class="yt-dl-bubble" id="yt-dl-bubble-btn" title="${T.open}"><img src="${ICONS.bubble}"></div>`);
          document.getElementById('yt-dl-bubble-btn').onclick = () => {
              if(container.dataset.moved !== "true") setUIMode(2);
          };
          return;
      }

      const dlContent = `
        <div class="yt-dl-btn-group">
            <button class="yt-dl-btn btn-blue" id="btn-vid">${T.vid}</button>
            <button class="yt-dl-btn btn-purple" id="btn-aud">${T.aud}</button>
            <button class="yt-dl-btn btn-orange" id="btn-img">${T.img}</button>
        </div>

        <div style="display:flex; gap:8px; margin-bottom:5px; text-align:center; font-size:9px; font-weight:bold;">
             <div style="flex:1; color:#3ea6ff;">${T.sc_vid}</div>
             <div style="flex:1; color:#d63384;">${T.sc_aud}</div>
             <div style="flex:1; color:#ff9800;">${T.sc_img}</div>
        </div>

        <div style="background:#222; color:#ffeb3b; padding:8px; border-radius:6px; font-size:10px; margin-bottom:12px; line-height:1.4; border:1px solid #444;">
           ${T.pro_tip}
        </div>

        <div id="yt-dl-stats-bar" style="font-size:10px; color:#aaa; display:flex; justify-content:space-between; margin-bottom:10px; background:#1a1a1a; padding:8px; border-radius:6px;">
            <span>${T.queue}: ...</span>
        </div>
        <div id="yt-dl-list">${generateListHTML()}</div>

        <div style="margin-top:15px; display:flex; gap:5px;">
            <button class="yt-dl-btn btn-gray" id="btn-refresh" style="font-size:11px; padding:6px; flex:1;">${T.refresh}</button>
            <button class="yt-dl-btn btn-blue" id="btn-open-panel" style="font-size:11px; padding:6px; flex:1;">${T.btn_panel}</button>
            <button class="yt-dl-btn btn-red" id="btn-clear" style="font-size:11px; padding:6px; flex:1;">${T.clear}</button>
        </div>`;

      const batchContent = `
        <div style="padding:5px">
            <textarea id="yt-dl-batch-area" class="batch-area" placeholder="${T.batch_ph}"></textarea>
            <button id="btn-batch-proc" class="yt-dl-btn btn-purple" style="width:100%">${T.batch_btn}</button>
        </div>`;

      const helpContent = `
        <div style="padding:20px; text-align:center;">
             <img src="${ICONS.warn}" style="width:50px;margin-bottom:10px;" onerror="this.src='https://img.icons8.com/?size=100&id=42452&format=png&color=ff9800'">
             <h3 style="color:#fff;margin:0 0 15px 0;font-size:16px;">${T.help_title}</h3>

             <div style="background:#1a1a1a; border-radius:8px; padding:20px; text-align:left; font-size:12px; line-height:1.8; color:#ccc; border:1px solid #333;">
                <div style="margin-bottom:5px"><b>${T.help_s1}</b></div>
                <div style="margin-bottom:5px"><b>${T.help_s2}</b></div>
                <div style="margin-bottom:5px"><b>${T.help_s3}</b></div>
             </div>

             <p style="color:#f44336; font-size:11px; font-weight:bold; margin:15px 0 15px;">${T.help_warn}</p>

             <button id="btn-do-download" style="background:#4caf50; color:white; border:none; padding:12px 20px; border-radius:6px; font-weight:bold; cursor:pointer; width:100%; font-size:14px; box-shadow:0 4px 15px rgba(76,175,80,0.3); text-transform:uppercase;">${T.help_btn_dl}</button>

             <div style="margin-top:10px; font-size:10px; color:#4caf50; font-weight:bold">${T.univ_note}</div>

             <div class="tip-box" style="margin-top:20px;">
                <div style="color:#ffeb3b;font-weight:bold;margin-bottom:5px">${T.tip_title}</div>
                <div>${T.tip_1}</div>
                <div>${T.tip_2}</div>
                <div>${T.tip_3}</div>
             </div>

             <div style="background:#b71c1c; color:#fff; font-weight:bold; padding:8px; border-radius:6px; font-size:11px; margin-top:10px;">
                ${T.help_login_err}
             </div>

             <div id="btn-back-dl" style="margin-top:20px; font-size:12px; color:#aaa; cursor:pointer; text-decoration:underline;">${T.back}</div>
        </div>`;

      const cryptoList = [
          {img: ICONS.btc, name: "BTC", val: "bc1q6gz3dtj9qvlxyyh3grz35x8xc7hkuj07knlemn"},
          {img: ICONS.eth, name: "ETH", val: "0xd8724d0b19d355e9817d2a468f49e8ce067e70a6"},
          {img: ICONS.sol, name: "SOL", val: "7ztAogE7SsyBw7mwVHhUr5ZcjUXQr99JoJ6oAgP99aCn"},
          {img: ICONS.usdt, name: "USDT", val: "0xd8724d0b19d355e9817d2a468f49e8ce067e70a6"}
      ].map(c => `<div class="sup-row"><img src="${c.img}" class="sup-icon"><span style="font-size:9px;color:#888;width:30px">${c.name}</span><input type="text" class="sup-val" readonly value="${c.val}"><button class="sup-copy" data-val="${c.val}">${T.btn_copy}</button></div>`).join('');

      const supContent = `<div style="padding:15px;text-align:center"><div style="color:#d63384;font-weight:bold;margin-bottom:5px">${T.sup_title}</div><div style="color:#aaa;font-size:11px;margin-bottom:15px">${T.sup_desc}</div><div style="text-align:left;color:#d63384;font-weight:bold;font-size:10px;margin-bottom:5px">${T.lbl_pix}</div><div class="sup-row"><img src="${ICONS.pix}" class="sup-icon"><input type="text" class="sup-val" readonly value="69993230419"><button class="sup-copy" data-val="69993230419">${T.btn_copy}</button></div><div style="text-align:left;color:#d63384;font-weight:bold;font-size:10px;margin:15px 0 5px">${T.wallet_title}</div>${cryptoList}<a href="https://www.paypal.com/donate/?business=4J4UK7ACU3DS6" target="_blank" style="display:inline-flex;align-items:center;gap:8px;background:#003087;color:white;padding:8px 20px;border-radius:20px;text-decoration:none;font-weight:bold;margin-top:20px;font-size:12px"><img src="${ICONS.paypal}" style="height:20px"> PayPal</a></div>`;

      let activeContent = dlContent;
      if (state.activeTab === 'sup') activeContent = supContent;
      if (state.activeTab === 'help') activeContent = helpContent;
      if (state.activeTab === 'batch') activeContent = batchContent;

      const panelHtml = `
      <div class="yt-dl-panel">
          <div class="yt-dl-head">
              <span style="font-weight:700;color:#fff;font-size:13px;">${T.title}</span>
              <div style="display:flex;gap:10px;align-items:center">
                  <span id="yt-dl-help-btn" style="cursor:pointer;font-size:12px;color:${state.activeTab==='help'?'#fff':'#4caf50'};font-weight:bold" title="${T.help_btn}">[?]</span>
                  <span class="yt-dl-min-btn" id="yt-dl-min" title="Minimize">▼</span>
              </div>
          </div>
          <div class="yt-dl-tabs">
            <div class="yt-dl-tab ${state.activeTab==='dl'?'active':''}" id="tab-btn-dl">${T.tab_dl}</div>
            <div class="yt-dl-tab ${state.activeTab==='batch'?'active':''}" id="tab-btn-batch">${T.tab_batch}</div>
            <div class="yt-dl-tab ${state.activeTab==='sup'?'active':''}" id="tab-btn-sup">${T.tab_sup}</div>
            <div class="yt-dl-tab ${state.activeTab==='help'?'active':''}" id="tab-btn-help">${T.tab_help}</div>
          </div>
          <div class="yt-dl-body">${activeContent}</div>
          <div class="yt-dl-footer">${T.footer_msg}</div>
      </div>`;

      container.innerHTML = safeHTML(panelHtml);

      document.getElementById('yt-dl-min').onclick = () => setUIMode(1);
      document.getElementById('yt-dl-help-btn').onclick = () => { state.activeTab='help'; renderUI(); };
      document.getElementById('tab-btn-dl').onclick = () => { state.activeTab='dl'; renderUI(); };
      document.getElementById('tab-btn-batch').onclick = () => { state.activeTab='batch'; renderUI(); };
      document.getElementById('tab-btn-sup').onclick = () => { state.activeTab='sup'; renderUI(); };
      document.getElementById('tab-btn-help').onclick = () => { state.activeTab='help'; renderUI(); };

      if(state.activeTab === 'dl') {
          document.getElementById('btn-vid').onclick = () => send('video');
          document.getElementById('btn-aud').onclick = () => send('audio');
          document.getElementById('btn-img').onclick = () => send('image');
          document.getElementById('btn-refresh').onclick = refreshData;
          document.getElementById('btn-clear').onclick = clearList;
          document.getElementById('btn-open-panel').onclick = () => GM_openInTab(SERVER_URL + '/panel', {active:true});
          bindListButtons();
      } else if (state.activeTab === 'batch') {
          document.getElementById('btn-batch-proc').onclick = processBatch;
      } else if (state.activeTab === 'help') {
          document.getElementById('btn-do-download').onclick = () => GM_openInTab(DRIVE_LINK, {active:true});
          document.getElementById('btn-back-dl').onclick = () => { state.activeTab='dl'; renderUI(); };
      } else {
          container.querySelectorAll('.sup-copy').forEach(btn => { btn.onclick = (e) => copyToClipboard(e.target.dataset.val); });
      }
      updateListContent();
  };

  const addInlineButtons = () => {
    const container = document.querySelector('[id^="top-level-buttons"]');
    if (!container || container.querySelector("#yt-dl-inline-vid")) return;
    const style = "height:36px; padding:0 16px; border-radius:18px; margin-left:8px; cursor:pointer; font-weight:500; font-size:14px; border:none; display:inline-flex; align-items:center; justify-content:center;";
    const btnV = document.createElement("button");
    btnV.id = "yt-dl-inline-vid"; btnV.textContent = T.vid; btnV.style.cssText = style + "background:#3ea6ff; color:#0f0f0f;";
    btnV.onclick = (e) => { e.preventDefault(); send('video'); };
    const btnA = document.createElement("button");
    btnA.id = "yt-dl-inline-aud"; btnA.textContent = T.aud;
    btnA.style.cssText = style + "background:#d63384; color:#fff;";
    btnA.onclick = (e) => { e.preventDefault(); send('audio'); };
    container.appendChild(btnV); container.appendChild(btnA);
  };
  const observer = new MutationObserver(addInlineButtons);
  observer.observe(document.body, { childList: true, subtree: true });

  setInterval(refreshData, POLLING_INTERVAL);

  window.addEventListener("keydown", (e) => {
      if (e.altKey && e.shiftKey && (e.key === "Y" || e.key === "y")) {
          setUIMode(state.uiMode === 0 ? 1 : 0);
          e.preventDefault();
      }
  });

  // --- MENU TAMPERMONKEY ---
  GM_registerMenuCommand(T.menu_update, () => GM_openInTab(UPDATE_URL, {active:true}));
  GM_registerMenuCommand(T.menu_toggle, () => setUIMode(state.uiMode === 0 ? 1 : 0));
  GM_registerMenuCommand(T.menu_help, () => { state.activeTab='help'; setUIMode(2); });
  GM_registerMenuCommand(T.menu_panel, () => GM_openInTab(SERVER_URL + '/panel', {active:true}));
  GM_registerMenuCommand(T.menu_dl, () => GM_openInTab(DRIVE_LINK, {active:true}));

  setTimeout(() => renderUI(), 1000);
  refreshData();
})();