Greasy Fork is available in English.

YouTube Video 4K/HD Downloader✨🚀 - NO ADS🚫🌐 (EasyTube V1)

EasyTube helps you enhance YouTube with safe and fast video & audio downloading, auto-scroll for Shorts, and classic view — all in one panel!

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 Video 4K/HD Downloader✨🚀 - NO ADS🚫🌐 (EasyTube V1)
// @name:vi            Trình tải video YouTube✨ - KHÔNG QUẢNG CÁO🚫
// @name:zh-CN         YouTube 视频下载器✨ - 无广告🚫
// @name:zh-TW         YouTube 影片下載器✨ - 無廣告🚫
// @name:ru            Загрузчик видео YouTube✨ - БЕЗ РЕКЛАМЫ🚫
// @name:ja            YouTube動画ダウンローダー✨ - 広告なし🚫
// @name:ko            YouTube 동영상 다운로더✨ - 광고 없음🚫
// @name:es            Descargador de videos de YouTube✨ - SIN ANUNCIOS🚫
// @name:pt-BR         Baixador de vídeos do YouTube✨ - SEM ANÚNCIOS🚫
// @name:fr            Téléchargeur de vidéos YouTube✨ - SANS PUB🚫
// @name:de            YouTube-Video-Downloader✨ - KEINE WERBUNG🚫
// @name:it            Downloader video YouTube✨ - NIENTE PUBBLICITÀ🚫
// @name:tr            YouTube Video İndirici✨ - REKLAMSIZ🚫
// @name:pl            Pobieracz filmów z YouTube✨ - BEZ REKLAM🚫
// @name:id            Pengunduh Video YouTube✨ - TANPA IKLAN🚫
// @name:ar            مُحمّل فيديوهات YouTube✨ - بدون إعلانات🚫 & تمرير تلقائي & عرض كلاسيكي (
// @description        EasyTube helps you enhance YouTube with safe and fast video & audio downloading, auto-scroll for Shorts, and classic view — all in one panel!
// @description:vi     EasyTube giúp bạn nâng cấp trải nghiệm YouTube với trình tải video & âm thanh an toàn và nhanh, tự cuộn cho Shorts và chế độ xem cổ điển — tất cả trong một bảng điều khiển!
// @description:zh-CN  EasyTube 让你的 YouTube 体验更强:安全且快速的视频与音频下载、Shorts 自动滚动和经典视图——全都集成在一个面板里!
// @description:zh-TW  EasyTube 強化你的 YouTube 體驗:安全且快速的影片與音訊下載、Shorts 自動捲動與經典檢視——全都整合在一個面板中!
// @description:ru     EasyTube улучшает YouTube: безопасная и быстрая загрузка видео и аудио, автопрокрутка для Shorts и классический вид — всё в одной панели!
// @description:ja     EasyTubeでYouTube体験を強化:安全で高速な動画・音声ダウンロード、Shortsの自動スクロール、クラシック表示を1つのパネルに統合!
// @description:ko     EasyTube로 YouTube 경험을 업그레이드하세요: 안전하고 빠른 동영상/오디오 다운로드, Shorts 자동 스크롤, 클래식 보기까지 한 패널에!
// @description:es     EasyTube mejora tu experiencia en YouTube con descargas seguras y rápidas de video y audio, desplazamiento automático para Shorts y vista clásica, ¡todo en un solo panel!
// @description:pt-BR  EasyTube melhora sua experiência no YouTube com download seguro e rápido de vídeos e áudios, rolagem automática para Shorts e visualização clássica — tudo em um só painel!
// @description:fr     EasyTube améliore votre expérience YouTube avec un téléchargeur sûr et rapide de vidéos et d’audios, le défilement automatique pour les Shorts et la vue classique — le tout dans un seul panneau !
// @description:de     EasyTube verbessert dein YouTube-Erlebnis mit sicherem und schnellem Video- und Audio-Download, Auto-Scroll für Shorts und klassischer Ansicht — alles in einem Panel!
// @description:it     EasyTube migliora la tua esperienza su YouTube con download sicuro e veloce di video e audio, scorrimento automatico per gli Shorts e vista classica — tutto in un unico pannello!
// @description:tr     EasyTube; güvenli ve hızlı video ve ses indirme, Shorts için otomatik kaydırma ve klasik görünüm ile YouTube deneyimini geliştirir — hepsi tek panelde!
// @description:pl     EasyTube ulepsza YouTube: bezpieczne i szybkie pobieranie wideo i audio, automatyczne przewijanie Shorts oraz widok klasyczny — wszystko w jednym panelu!
// @description:id     EasyTube meningkatkan pengalaman YouTube Anda dengan pengunduh video dan audio yang aman dan cepat, gulir otomatis untuk Shorts, dan tampilan klasik — semuanya dalam satu panel!
// @description:ar     EasyTube يحسّن تجربتك على YouTube عبر تنزيل آمن وسريع للفيديو والصوت، وتمرير تلقائي لـ Shorts، وعرض كلاسيكي — كل ذلك في لوحة واحدة!

// @namespace          http://twisk.fun/EasyTube
// @version            1.0.0
// @author             sleepycat
// @match              https://*.youtube.com/*
// @grant              GM_addStyle
// @run-at             document-end
// @icon               https://github.com/helloticc/TampermonkeyProjects/blob/main/EasyTube.png?raw=true
// @license            MIT
// ==/UserScript==

const FALLBACK_TITLE = 'YouTube EasyTube';
(function() {
  'use strict';

  const YT_SWITCH_SVG = '<svg class="ytHub-ytlogo" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="#FF0000" d="M23.2 7.1a3.0 3.0 0 0 0-2.1-2.1C19.2 4.5 12 4.5 12 4.5s-7.2 0-9.1.5A3.0 3.0 0 0 0.8 7.1 31.6 31.6 0 0 0 0.3 12c0 1.6.2 3.3.5 4.9a3.0 3.0 0 0 0 2.1 2.1c1.9.5 9.1.5 9.1.5s7.2 0 9.1-.5a3.0 3.0 0 0 0 2.1-2.1c.3-1.6.5-3.3.5-4.9 0-1.6-.2-3.3-.5-4.9z"/><path fill="#FFFFFF" d="M10 15.5v-7l6 3.5-6 3.5z"/></svg>';

  // for trustedtype
  const createYouTubeSvg = (cls) => {
    const NS = 'http://www.w3.org/2000/svg';
    const svg = document.createElementNS(NS, 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    if (cls) svg.setAttribute('class', cls);

    const p1 = document.createElementNS(NS, 'path');
    p1.setAttribute('d', 'M23.5 6.3a3.1 3.1 0 0 0-2.2-2.2C19.4 3.5 12 3.5 12 3.5s-7.4 0-9.3.6A3.1 3.1 0 0 0 .5 6.3 32.7 32.7 0 0 0 0 12a32.7 32.7 0 0 0 .5 5.7 3.1 3.1 0 0 0 2.2 2.2c1.9.6 9.3.6 9.3.6s7.4 0 9.3-.6a3.1 3.1 0 0 0 2.2-2.2A32.7 32.7 0 0 0 24 12a32.7 32.7 0 0 0-.5-5.7Z');
    p1.setAttribute('fill', '#FF0000');

    const p2 = document.createElementNS(NS, 'path');
    p2.setAttribute('d', 'M9.75 15.5V8.5L16 12l-6.25 3.5Z');
    p2.setAttribute('fill', '#FFFFFF');

    svg.appendChild(p1);
    svg.appendChild(p2);
    return svg;
  };

  const makeMacSwitch = (id) => {
    const btn = document.createElement('button');
    btn.className = 'ytHub-switch';
    btn.id = id;
    btn.type = 'button';
    btn.setAttribute('aria-pressed', 'false');
    // Build thumb + SVG without innerHTML (Trusted Types safe)
    const thumb = document.createElement('span');
    thumb.className = 'ytHub-thumb';
    thumb.setAttribute('aria-hidden', 'true');
    btn.appendChild(thumb);
    return btn;
  };
  const CONFIG = {
    panelId: 'ytHubPanel',
    toggleId: 'ytHubToggle',
    downloadUrl: '//evdfrance.fr//convert/?id=',
    supportUrl: 'https://twisk.fun/discord',
    shortsWheelDelta: 1200,
    shortsScrollPower: 900,
  };
  const styles = `
    #${CONFIG.panelId}, #${CONFIG.panelId} * { box-sizing: border-box; }
    #${CONFIG.toggleId} {
      position: fixed;
      bottom: 96px;
      right: 20px;
      width: 60px;
      height: 40px;
      border-radius: 999px;
      background: rgba(255, 255, 255, 0.18);
      border: 1px solid rgba(255, 255, 255, 0.22);
      box-shadow: 0 10px 26px rgba(0,0,0,0.22);
      z-index: 99998;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      backdrop-filter: blur(18px) saturate(180%);
      -webkit-backdrop-filter: blur(18px) saturate(180%);
      transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
    }
    #${CONFIG.toggleId}:hover {
      transform: translateY(-1px);
      box-shadow: 0 14px 34px rgba(0,0,0,0.28);
      background: rgba(255, 255, 255, 0.22);
    }
    #${CONFIG.toggleId}:active { transform: translateY(0) scale(0.98); }
    .ytHub-toggle-icon {
      font-size: 18px;
      color: rgba(15,15,15,0.92);
      transition: transform 0.22s ease;
      user-select: none;
    }
    .ytHub-toggle-icon svg {
      width: 20px;
      height: 20px;
      display: block;
    }
    #${CONFIG.toggleId}.active .ytHub-toggle-icon { transform: rotate(180deg); }
    #${CONFIG.panelId} {
      position: fixed;
      top: 0px;
      left: 0px;
      right: auto;
      bottom: auto;
      width: 360px;
      max-width: 92vw;
      max-height: min(520px, calc(100vh - 220px));
      display: flex;
      flex-direction: column;
      background: rgba(255, 255, 255, 0.15);
      border-radius: 28px;
      box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
      z-index: 99999;
      font-family: "Roboto", "YouTube Sans", Arial, sans-serif;
      overflow: hidden;
      opacity: 0;
      pointer-events: none;
      will-change: transform, opacity;
      backdrop-filter: blur(30px) saturate(180%);
      -webkit-backdrop-filter: blur(30px) saturate(180%);
      border: 1px solid rgba(255, 255, 255, 0.1);
      transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
      transform: translateY(30px);
    }
    #${CONFIG.panelId}.show {
      opacity: 1;
      pointer-events: all;
      transform: translateY(0);
    }
    #${CONFIG.panelId}.dragging { transition: none !important; }
    .ytHub-header {
      background: linear-gradient(135deg, #ff0000 0%, #cc0000 100%);
      padding: 14px 16px;
      cursor: move;
      user-select: none;
      display: flex;
      align-items: center;
      justify-content: space-between;
      position: relative;
      overflow: hidden;
      border-top-left-radius: 28px;
      border-top-right-radius: 28px;
      flex: 0 0 auto;
    }
    .ytHub-header::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: linear-gradient(135deg, transparent 0%, rgba(255,255,255,0.12) 100%);
      pointer-events: none;
    }
    .ytHub-logo-container {
      display: flex;
      align-items: center;
      gap: 10px;
      position: relative;
      z-index: 1;
    }
    .ytHub-logo {
      width: 54px;
      height: 36px;
      background: #ff0000;
      border-radius: 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 26px;
      font-weight: bold;
      color: #ffffff;
      box-shadow: 0 4px 12px rgba(255,0,0,0.3);
    }
    .ytHub-title-wrapper { display: flex; flex-direction: column; }
    .ytHub-title {
      color: #ffffff;
      font-size: 17px;
      font-weight: 600;
      letter-spacing: 0.2px;
      line-height: 1.2;
    }
    .ytHub-subtitle {
      color: rgba(255,255,255,0.85);
      font-size: 12px;
      font-weight: 400;
      letter-spacing: 0.3px;
    }
    .ytHub-drag-icon {
      color: rgba(255,255,255,0.92);
      font-size: 24px;
      cursor: move;
      line-height: 1;
      position: relative;
      z-index: 1;
    }
    .ytHub-content {
      padding: 14px 16px 16px 16px;
      background: transparent;
      overflow: auto;
      -webkit-overflow-scrolling: touch;
      flex: 1 1 auto;
      scrollbar-width: thin;
      scrollbar-color: rgba(255,255,255,0.35) transparent;
    }
    .ytHub-content::-webkit-scrollbar { width: 10px; }
    .ytHub-content::-webkit-scrollbar-track { background: transparent; }
    .ytHub-content::-webkit-scrollbar-thumb {
      background: rgba(255,255,255,0.28);
      border-radius: 999px;
      border: 2px solid transparent;
      background-clip: padding-box;
    }
    .ytHub-content::-webkit-scrollbar-thumb:hover {
      background: rgba(255,255,255,0.40);
      border: 2px solid transparent;
      background-clip: padding-box;
    }
    .ytHub-video-card {
      background: rgba(255, 255, 255, 0.25);
      border-radius: 22px;
      padding: 14px;
      margin-bottom: 12px;
      border: 1px solid rgba(255, 255, 255, 0.2);
      transition: all 0.2s ease;
      backdrop-filter: blur(14px);
      -webkit-backdrop-filter: blur(14px);
    }
    .ytHub-video-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.12); }
    .ytHub-card-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
    }
    .ytHub-info-label {
      font-size: 11px;
      color: #505050;
      text-transform: uppercase;
      font-weight: 600;
      letter-spacing: 0.8px;
    }
    .ytHub-status-badge {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 11px;
      color: #00a152;
      background: rgba(232, 245, 233, 0.9);
      padding: 4px 10px;
      border-radius: 20px;
      font-weight: 500;
    }
    .ytHub-status-dot {
      width: 6px;
      height: 6px;
      background: #00a152;
      border-radius: 50%;
    }
    .ytHub-video-title {
      font-size: 15px;
      color: #0f0f0f;
      font-weight: 600;
      line-height: 1.45;
      margin-bottom: 10px;
      word-break: break-word;
      overflow-wrap: break-word;
      hyphens: auto;
    }
    .ytHub-video-id-wrapper {
      display: flex;
      align-items: center;
      gap: 8px;
      flex-wrap: wrap;
    }
    .ytHub-id-label {
      font-size: 11px;
      color: #505050;
      font-weight: 600;
    }
    .ytHub-video-id {
      font-size: 12px;
      color: #0f0f0f;
      font-family: 'Consolas', 'Courier New', monospace;
      background: rgba(255, 255, 255, 0.7);
      padding: 5px 12px;
      border-radius: 20px;
      font-weight: 500;
    }
    .ytHub-buttons {
      display: flex;
      flex-direction: column;
      gap: 10px;
    }
    .ytHub-button {
      width: 100%;
      padding: 14px 18px;
      border: none;
      border-radius: 22px;
      font-size: 15px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      text-decoration: none;
      color: #ffffff;
      position: relative;
      overflow: hidden;
      letter-spacing: 0.3px;
    }
    .ytHub-button::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(255,255,255,0.18);
      opacity: 0;
      transition: opacity 0.25s ease;
    }
    .ytHub-button:hover::before { opacity: 1; }
    .ytHub-button:hover { transform: translateY(-2px); }
    .ytHub-button:active { transform: translateY(0); }
    .ytHub-download {
      background: linear-gradient(135deg, #ff0000 0%, #cc0000 100%);
      box-shadow: 0 4px 14px rgba(255,0,0,0.3);
    }
    .ytHub-download:hover { box-shadow: 0 8px 20px rgba(255,0,0,0.4); }
    .ytHub-like {
      background: linear-gradient(135deg, #ff4d6d 0%, #d81b60 100%);
      box-shadow: 0 4px 14px rgba(216,27,96,0.25);
    }
    .ytHub-like:hover { box-shadow: 0 8px 20px rgba(216,27,96,0.33); }
    .ytHub-autoscroll {
      background: linear-gradient(135deg, #222 0%, #111 100%);
      box-shadow: 0 4px 14px rgba(0,0,0,0.28);
    }
    .ytHub-autoscroll:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.36); }
    .ytHub-autoscroll.on {
      background: linear-gradient(135deg, #00a152 0%, #007a3d 100%);
      box-shadow: 0 4px 14px rgba(0,161,82,0.25);
    }

.ytHub-ram {
  background: linear-gradient(135deg, #ff9800 0%, #e65100 100%);
  box-shadow: 0 4px 14px rgba(255,152,0,0.25);
}
.ytHub-ram:hover { box-shadow: 0 8px 20px rgba(255,152,0,0.35); }
.ytHub-ram.on {
  background: linear-gradient(135deg, #00a152 0%, #007a3d 100%);
  box-shadow: 0 4px 14px rgba(0,161,82,0.25);
}

    .ytHub-toggle-list {
      display: flex;
      flex-direction: column;
      gap: 10px;
      margin-top: 6px;
      margin-bottom: 2px;
    }
    .ytHub-row {
      width: 100%;
      padding: 12px 12px;
      border-radius: 20px;
      background: rgba(255, 255, 255, 0.22);
      border: 1px solid rgba(255, 255, 255, 0.18);
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      backdrop-filter: blur(14px);
      -webkit-backdrop-filter: blur(14px);
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }
    .ytHub-row:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.12); transform: translateY(-1px); }
    .ytHub-row-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
    .ytHub-row-icon {
      width: 34px;
      height: 34px;
      border-radius: 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 18px;
      background: rgba(255,255,255,0.55);
      flex: 0 0 auto;
    }
    .ytHub-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
    .ytHub-row-title {
      font-size: 14px;
      font-weight: 700;
      color: #0f0f0f;
      line-height: 1.1;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .ytHub-row-sub {
      font-size: 11px;
      color: rgba(15,15,15,0.72);
      line-height: 1.2;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .ytHub-row-status {
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.5px;
      color: rgba(15,15,15,0.65);
      margin-right: 8px;
      min-width: 36px;
      text-align: right;
    }
    .ytHub-switch {
      width: 46px;
      height: 28px;
      border-radius: 999px;
      border: none;
      background: rgba(120,120,128,0.24);
      position: relative;
      cursor: pointer;
      flex: 0 0 auto;
      box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06);
      transition: background 0.18s ease;
    }
    .ytHub-switch .ytHub-thumb {
      position: absolute;
      top: 3px;
      left: 3px;
      width: 22px;
      height: 22px;
      border-radius: 999px;
      background: #ffffff;
      box-shadow: 0 6px 14px rgba(0,0,0,0.18);
      display: flex;
      align-items: center;
      justify-content: center;
      transition: transform 0.18s ease;
      overflow: hidden;
    }
    .ytHub-ytlogo { width: 14px; height: 14px; display: block; }
    .ytHub-switch.on { background: rgba(52,199,89,0.95); }
    .ytHub-switch.on .ytHub-thumb { transform: translateX(18px); }

    .ytHub-support {
      background: linear-gradient(135deg, #065fd4 0%, #0448a3 100%);
      box-shadow: 0 4px 14px rgba(6,95,212,0.3);
    }
    .ytHub-support:hover { box-shadow: 0 8px 20px rgba(6,95,212,0.4); }
    .ytHub-disabled {
      opacity: 0.5;
      cursor: not-allowed !important;
      pointer-events: none;
    }
    .ytHub-icon {
      font-size: 20px;
      display: flex;
      align-items: center;
    }
    .ytHub-footer {
      padding: 10px 16px;
      background: rgba(255, 255, 255, 0.15);
      border-top: 1px solid rgba(255, 255, 255, 0.1);
      text-align: center;
      border-bottom-left-radius: 28px;
      border-bottom-right-radius: 28px;
      flex: 0 0 auto;
    }
    .ytHub-footer-text {
      font-size: 11px;
      color: #505050;
      line-height: 1.4;
      font-weight: 400;
    }
  `;
  GM_addStyle(styles);
  class YouTubeHub {
    constructor() {
      this.panel = null;
      this.toggle = null;
      this.isVisible = false;
      this.isDragging = false;
      this.currentX = 0;
      this.currentY = 0;
      this.initialX = 0;
      this.initialY = 0;
      this.xOffset = 0;
      this.yOffset = 0;
      this.autoScrollEnabled = false;
      this.ramOptimizeEnabled = false;
      this._autoScrollTimer = null;
      this._lastShortsId = null;
    }
    init() {
      this.createToggleButton();
      this.createPanel();
      this.attachEventListeners();
      this.observePageChanges();
      setInterval(() => this.updateVideoInfo(), 1500);
      setInterval(() => this.autoScrollTick(), 700);
    }
    createToggleButton() {
      const existing = document.getElementById(CONFIG.toggleId);
      if (existing) { this.toggle = existing; return; }
      const toggle = document.createElement('div');
      toggle.id = CONFIG.toggleId;
      const icon = document.createElement('div');
      icon.className = 'ytHub-toggle-icon';
      icon.appendChild(createYouTubeSvg('ytHub-yt-svg'));
      toggle.appendChild(icon);
      document.body.appendChild(toggle);
      this.toggle = toggle;
      toggle.addEventListener('click', () => this.togglePanel());
    }
    togglePanel() {
      this.isVisible = !this.isVisible;

      // Re-bind panel/toggle after SPA navigations
      if (!this.panel) this.panel = document.getElementById(CONFIG.panelId);
      if (!this.toggle) this.toggle = document.getElementById(CONFIG.toggleId);
      if (!this.panel) { this.createPanel(); }
      if (!this.panel) return;
      if (this.isVisible) {
        this.panel.classList.add('show');
        this.toggle.classList.add('active');
      } else {
        this.panel.classList.remove('show');
        this.toggle.classList.remove('active');
      }
    }
    createPanel() {
      const existingPanel = document.getElementById(CONFIG.panelId);
      if (existingPanel) { this.panel = existingPanel; return; }
      const panel = document.createElement('div');
      panel.id = CONFIG.panelId;
      const header = document.createElement('div');
      header.className = 'ytHub-header';
      const logoContainer = document.createElement('div');
      logoContainer.className = 'ytHub-logo-container';
      const logo = document.createElement('div');
      logo.className = 'ytHub-logo';
      logo.textContent = '▶';
      const titleWrapper = document.createElement('div');
      titleWrapper.className = 'ytHub-title-wrapper';
      const title = document.createElement('div');
      title.className = 'ytHub-title';
      title.textContent = 'EasyTube by Mint';
      const subtitle = document.createElement('div');
      subtitle.className = 'ytHub-subtitle';
      subtitle.textContent = 'Improve your YouTube performance';
      titleWrapper.appendChild(title);
      titleWrapper.appendChild(subtitle);
      logoContainer.appendChild(logo);
      logoContainer.appendChild(titleWrapper);
      const dragIcon = document.createElement('div');
      dragIcon.className = 'ytHub-drag-icon';
      dragIcon.textContent = '⋮';
      header.appendChild(logoContainer);
      header.appendChild(dragIcon);
      const content = document.createElement('div');
      content.className = 'ytHub-content';
      const videoCard = document.createElement('div');
      videoCard.className = 'ytHub-video-card';
      const cardHeader = document.createElement('div');
      cardHeader.className = 'ytHub-card-header';
      const infoLabel = document.createElement('div');
      infoLabel.className = 'ytHub-info-label';
      infoLabel.textContent = 'CURRENT VIDEO';
      const statusBadge = document.createElement('div');
      statusBadge.className = 'ytHub-status-badge';
      const statusDot = document.createElement('div');
      statusDot.className = 'ytHub-status-dot';
      const statusText = document.createElement('span');
      statusText.textContent = 'Ready';
      statusBadge.appendChild(statusDot);
      statusBadge.appendChild(statusText);
      cardHeader.appendChild(infoLabel);
      cardHeader.appendChild(statusBadge);
      const videoTitle = document.createElement('div');
      videoTitle.className = 'ytHub-video-title';
      videoTitle.textContent = 'This type of videos isn\'t supported yet.';
      videoTitle.id = 'ytHubVideoTitle';
      const videoIdWrapper = document.createElement('div');
      videoIdWrapper.className = 'ytHub-video-id-wrapper';
      const idLabel = document.createElement('div');
      idLabel.className = 'ytHub-id-label';
      idLabel.textContent = 'VIDEO ID:';
      const videoId = document.createElement('div');
      videoId.className = 'ytHub-video-id';
      videoId.textContent = 'N/A';
      videoId.id = 'ytHubVideoId';
      videoIdWrapper.appendChild(idLabel);
      videoIdWrapper.appendChild(videoId);
      videoCard.appendChild(cardHeader);
      videoCard.appendChild(videoTitle);
      videoCard.appendChild(videoIdWrapper);
      const buttonsContainer = document.createElement('div');
      buttonsContainer.className = 'ytHub-buttons';
      const downloadBtn = document.createElement('a');
      downloadBtn.href = '#';
      downloadBtn.className = 'ytHub-button ytHub-download';
      downloadBtn.id = 'ytHubDownloadBtn';
      const downloadIcon = document.createElement('span');
      downloadIcon.className = 'ytHub-icon';
      downloadIcon.textContent = '⬇';
      const downloadText = document.createElement('span');
      downloadText.textContent = 'Download Video';
      downloadBtn.appendChild(downloadIcon);
      downloadBtn.appendChild(downloadText);
      const likeBtn = document.createElement('a');
      likeBtn.href = '#';
      likeBtn.className = 'ytHub-button ytHub-like';
      likeBtn.id = 'ytHubLikeBtn';
      const likeIcon = document.createElement('span');
      likeIcon.className = 'ytHub-icon';
      likeIcon.textContent = '👍';
      const likeText = document.createElement('span');
      likeText.textContent = 'Like This Video';
      likeBtn.appendChild(likeIcon);
      likeBtn.appendChild(likeText);
const toggleList = document.createElement('div');
toggleList.className = 'ytHub-toggle-list';

const autoRow = document.createElement('div');
autoRow.className = 'ytHub-row';
const autoLeft = document.createElement('div');
autoLeft.className = 'ytHub-row-left';
const autoRowIcon = document.createElement('div');
autoRowIcon.className = 'ytHub-row-icon';
autoRowIcon.textContent = '⬇';
const autoTextWrap = document.createElement('div');
autoTextWrap.className = 'ytHub-row-text';
const autoTitle = document.createElement('div');
autoTitle.className = 'ytHub-row-title';
autoTitle.textContent = 'Auto-Scroll (Shorts)';
const autoSub = document.createElement('div');
autoSub.className = 'ytHub-row-sub';
autoSub.textContent = 'Auto-advance Shorts every few seconds';
autoTextWrap.appendChild(autoTitle);
autoTextWrap.appendChild(autoSub);
autoLeft.appendChild(autoRowIcon);
autoLeft.appendChild(autoTextWrap);

const autoRight = document.createElement('div');
autoRight.style.display = 'flex';
autoRight.style.alignItems = 'center';
const autoStatus = document.createElement('div');
autoStatus.className = 'ytHub-row-status';
autoStatus.id = 'ytHubAutoScrollStatus';
autoStatus.textContent = 'OFF';
const autoSwitch = makeMacSwitch('ytHubAutoScrollSwitch');
autoRight.appendChild(autoStatus);
autoRight.appendChild(autoSwitch);

autoRow.appendChild(autoLeft);
autoRow.appendChild(autoRight);
toggleList.appendChild(autoRow);


const ramRow = document.createElement('div');
ramRow.className = 'ytHub-row';
const ramLeft = document.createElement('div');
ramLeft.className = 'ytHub-row-left';
const ramRowIcon = document.createElement('div');
ramRowIcon.className = 'ytHub-row-icon';
ramRowIcon.textContent = '🧹';
const ramTextWrap = document.createElement('div');
ramTextWrap.className = 'ytHub-row-text';
const ramTitle = document.createElement('div');
ramTitle.className = 'ytHub-row-title';
ramTitle.textContent = 'Classic View';
const ramSub = document.createElement('div');
ramSub.className = 'ytHub-row-sub';
ramSub.textContent = 'Hide comments/sidebar to reduce RAM';
ramTextWrap.appendChild(ramTitle);
ramTextWrap.appendChild(ramSub);
ramLeft.appendChild(ramRowIcon);
ramLeft.appendChild(ramTextWrap);

const ramRight = document.createElement('div');
ramRight.style.display = 'flex';
ramRight.style.alignItems = 'center';
const ramStatus = document.createElement('div');
ramStatus.className = 'ytHub-row-status';
ramStatus.id = 'ytHubRamStatus';
ramStatus.textContent = 'OFF';
const ramSwitch = makeMacSwitch('ytHubRamSwitch');
ramSwitch.setAttribute('aria-pressed', 'false');

ramRight.appendChild(ramStatus);
ramRight.appendChild(ramSwitch);

ramRow.appendChild(ramLeft);
ramRow.appendChild(ramRight);
toggleList.appendChild(ramRow);
      const supportBtn = document.createElement('a');
      supportBtn.href = CONFIG.supportUrl;
      supportBtn.target = '_blank';
      supportBtn.rel = 'noopener noreferrer';
      supportBtn.className = 'ytHub-button ytHub-support';
      const supportIcon = document.createElement('span');
      supportIcon.className = 'ytHub-icon';
      supportIcon.textContent = '💬';
      const supportText = document.createElement('span');
      supportText.textContent = 'Join our community/Report a bug';
      supportBtn.appendChild(supportIcon);
      supportBtn.appendChild(supportText);
      buttonsContainer.appendChild(downloadBtn);
      buttonsContainer.appendChild(likeBtn);
      buttonsContainer.appendChild(toggleList);
      buttonsContainer.appendChild(supportBtn);
      content.appendChild(videoCard);
      content.appendChild(buttonsContainer);
      const footer = document.createElement('div');
      footer.className = 'ytHub-footer';
      const footerText = document.createElement('div');
      footerText.className = 'ytHub-footer-text';
      footerText.textContent = '© EasyTube by Mint • Fast & Secure';
      footer.appendChild(footerText);
      panel.appendChild(header);
      panel.appendChild(content);
      panel.appendChild(footer);
      document.body.appendChild(panel);
      this.panel = panel;

      // Use top-left coordinate system for dragging (so X movement works even if the panel was originally anchored with right/bottom)
      panel.style.left = '0px';
      panel.style.top = '0px';
      panel.style.right = 'auto';
      panel.style.bottom = 'auto';

      // Default position near bottom-right (similar to old layout)
      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const pw = panel.offsetWidth || 360;
      const ph = panel.offsetHeight || 320;
      const maxX = Math.max(8, vw - pw - 8);
      const maxY = Math.max(8, vh - ph - 8);
      const defaultX = Math.max(8, Math.min(maxX, vw - pw - 20));
      const defaultY = Math.max(8, Math.min(maxY, vh - ph - 170));

      this.xOffset = defaultX;
      this.yOffset = defaultY;
      this.currentX = defaultX;
      this.currentY = defaultY;
      this.setTranslate(defaultX, defaultY);
      this.updateVideoInfo();
      this.syncAutoScrollUi();
      this.applyRamOptimize(this.ramOptimizeEnabled);
      this.syncRamUi();
    }
    attachEventListeners() {
      const header = this.panel.querySelector('.ytHub-header');
      // Use Pointer Events + attach move/up listeners only while dragging (less lag)
      this._boundDragStart = this.dragStart.bind(this);
      this._boundDragMove  = this.drag.bind(this);
      this._boundDragEnd   = this.dragEnd.bind(this);

      header.addEventListener('pointerdown', this._boundDragStart, { passive: false });
      const downloadBtn = document.getElementById('ytHubDownloadBtn');
      downloadBtn.addEventListener('click', (e) => {
        e.preventDefault();
        const videoId = this.extractVideoId(window.location.href);
        if (videoId) window.open(`${CONFIG.downloadUrl}${videoId}`, '_blank');
      });
      const likeBtn = document.getElementById('ytHubLikeBtn');
      likeBtn.addEventListener('click', (e) => {
        e.preventDefault();
        this.clickLikeButtonOnce();
      });
      const autoSwitch = document.getElementById('ytHubAutoScrollSwitch');
      autoSwitch.addEventListener('click', (e) => {
        e.preventDefault();
        this.autoScrollEnabled = !this.autoScrollEnabled;
        this.resetAutoScrollTimer(true);
        this.syncAutoScrollUi();
      });


const ramSwitch = document.getElementById('ytHubRamSwitch');
ramSwitch.addEventListener('click', (e) => {
  e.preventDefault();
  this.ramOptimizeEnabled = !this.ramOptimizeEnabled;
  this.applyRamOptimize(this.ramOptimizeEnabled);
  this.syncRamUi();
});
    }
    dragStart(e) {
      if (!e || !e.target || !e.target.closest('.ytHub-header')) return;
      e.preventDefault();

      // Re-bind in case YouTube SPA replaced the DOM
      this.panel = this.panel || document.getElementById(CONFIG.panelId);
      if (!this.panel) return;

      this.isDragging = true;
      this.panel.classList.add('dragging');

      // Ensure we are positioned from the top-left; otherwise translateX may appear "locked" when right/bottom anchoring is active.
      this.panel.style.left = '0px';
      this.panel.style.top = '0px';
      this.panel.style.right = 'auto';
      this.panel.style.bottom = 'auto';

      // Cache panel size to avoid layout reads on every frame (smoother)
      const rect = this.panel.getBoundingClientRect();
      this._panelW = rect.width;
      this._panelH = rect.height;

      // Pointer capture for smoother dragging
      try {
        const header = this.panel.querySelector('.ytHub-header');
        if (header && typeof header.setPointerCapture === 'function' && e.pointerId != null) {
          header.setPointerCapture(e.pointerId);
        }
      } catch (_) {}

      // Store active pointer
      this._activePointerId = (e && typeof e.pointerId === 'number') ? e.pointerId : null;

      // Track both axes
      this.initialX = e.clientX - this.xOffset;
      this.initialY = e.clientY - this.yOffset;
      this._nextX = this.xOffset || 0;
      this._nextY = this.yOffset || 0;

      window.addEventListener('pointermove', this._boundDragMove, { passive: false });
      window.addEventListener('pointerup', this._boundDragEnd, { passive: true });
      window.addEventListener('pointercancel', this._boundDragEnd, { passive: true });
    }
    drag(e) {
      if (!this.isDragging) return;
      if (this._activePointerId != null && e.pointerId !== this._activePointerId) return;
      e.preventDefault();

      // Track both axes
      this._nextX = e.clientX - this.initialX;
      this._nextY = e.clientY - this.initialY;

      if (this._rafPending) return;
      this._rafPending = true;

      requestAnimationFrame(() => {
        if (!this.panel) { this._rafPending = false; return; }

        const vw = window.innerWidth;
        const vh = window.innerHeight;
        const maxX = vw - (this._panelW || this.panel.offsetWidth || 0) - 8;
        const maxY = vh - (this._panelH || this.panel.offsetHeight || 0) - 8;

        this.currentX = Math.max(8, Math.min(maxX, this._nextX));
        this.currentY = Math.max(8, Math.min(maxY, this._nextY));

        // Persist offsets
        this.xOffset = this.currentX;
        this.yOffset = this.currentY;

        this.setTranslate(this.currentX, this.currentY);
        this._rafPending = false;
      });
    }
    dragEnd(e) {
      if (!this.isDragging) return;
      if (e && this._activePointerId != null && e.pointerId !== this._activePointerId) return;

      this.initialX = this.currentX;
      this.initialY = this.currentY;
      this.isDragging = false;

      // Release pointer capture (if any)
      try {
        const header = this.panel && this.panel.querySelector('.ytHub-header');
        if (header && typeof header.releasePointerCapture === 'function' && e && e.pointerId != null) {
          header.releasePointerCapture(e.pointerId);
        }
      } catch (_) {}

      if (this.panel) this.panel.classList.remove('dragging');

      window.removeEventListener('pointermove', this._boundDragMove);
      window.removeEventListener('pointerup', this._boundDragEnd);
      window.removeEventListener('pointercancel', this._boundDragEnd);

      this._activePointerId = null;
    }
    setTranslate(xPos, yPos) {
      this.panel.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
    }
    extractVideoId(url) {
      const patterns = [
        /[?&]v=([^&#]*)/,
        /youtu\.be\/([^?&#]*)/,
        /embed\/([^?&#]*)/,
        /shorts\/([^?&#]*)/
      ];
      for (const pattern of patterns) {
        const match = url.match(pattern);
        if (match && match[1] && match[1].length === 11) return match[1];
      }
      return null;
    }
    getVideoTitle() {
      const isShorts = window.location.pathname.startsWith('/shorts/');
      if (isShorts) {
        const activeReel =
          document.querySelector('ytd-reel-video-renderer[is-active]') ||
          document.querySelector('ytd-reel-video-renderer') ||
          document;
        const shortsSelectors = [
          'h2 span.yt-core-attributed-string[role="text"]',
          'h2.ytd-reel-player-header-renderer span.yt-core-attributed-string[role="text"]',
          'ytd-reel-player-header-renderer h2 span[role="text"]',
          'ytd-reel-player-header-renderer span.yt-core-attributed-string[role="text"]'
        ];
        for (const sel of shortsSelectors) {
          const el = activeReel.querySelector(sel);
          const text = el?.textContent?.trim();
          if (text && text.length > 1 && !this.isBadTitle(text)) return text;
        }
        const doc = document.title?.replace(/\s*-\s*YouTube\s*$/i, '').trim();
        if (doc && doc.length > 1 && !this.isBadTitle(doc)) return doc;
        return "This type of videos isn't supported yet.";
      }
      const selectors = [
        'ytd-watch-metadata h1 yt-formatted-string',
        'h1.ytd-watch-metadata yt-formatted-string',
        '#title h1 yt-formatted-string'
      ];
      for (const selector of selectors) {
        const el = document.querySelector(selector);
        const text = el?.textContent?.trim();
        if (text && text.length > 1 && !this.isBadTitle(text)) return text;
      }
      const doc = document.title?.replace(/\s*-\s*YouTube\s*$/i, '').trim();
      if (doc && doc.length > 1 && !this.isBadTitle(doc)) return doc;
      return "This type of videos isn't supported yet.";
    }
    isBadTitle(text) {
      const bad = [
        'Bỏ qua điều hướng', 'Skip navigation',
        'Trang chủ', 'Home',
        'Đăng ký', 'Subscribe'
      ];
      if (bad.includes(text)) return true;
      if (text.length < 2) return true;
      return false;
    }
    updateVideoInfo() {
      const videoId = this.extractVideoId(window.location.href);
      const titleElement = document.getElementById('ytHubVideoTitle');
      const idElement = document.getElementById('ytHubVideoId');
      const downloadBtn = document.getElementById('ytHubDownloadBtn');
      if (!videoId) {
        if (titleElement) titleElement.textContent = FALLBACK_TITLE;
        if (idElement) idElement.textContent = 'N/A';
        if (downloadBtn) downloadBtn.classList.add('ytHub-disabled');
        return;
      }
      const videoTitle = this.getVideoTitle();
      if (titleElement) titleElement.textContent = videoTitle;
      if (idElement) idElement.textContent = videoId;
      if (downloadBtn) downloadBtn.classList.remove('ytHub-disabled');
    }
    observePageChanges() {
      let lastUrl = location.href;
      const observer = new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
          lastUrl = url;
          setTimeout(() => this.updateVideoInfo(), 500);
        }
      });
      observer.observe(document.body, { childList: true, subtree: true });
      window.addEventListener('yt-navigate-finish', () => {
        setTimeout(() => this.updateVideoInfo(), 500);
      });
    }
    syncAutoScrollUi() {
      const sw = document.getElementById('ytHubAutoScrollSwitch');
      const st = document.getElementById('ytHubAutoScrollStatus');
      if (sw) {
        sw.classList.toggle('on', !!this.autoScrollEnabled);
        sw.setAttribute('aria-pressed', this.autoScrollEnabled ? 'true' : 'false');
      }
      if (st) st.textContent = this.autoScrollEnabled ? 'ON' : 'OFF';
    }


syncRamUi() {
  const sw = document.getElementById('ytHubRamSwitch');
  const st = document.getElementById('ytHubRamStatus');
  if (sw) {
    sw.classList.toggle('on', !!this.ramOptimizeEnabled);
    sw.setAttribute('aria-pressed', this.ramOptimizeEnabled ? 'true' : 'false');
  }
  if (st) st.textContent = this.ramOptimizeEnabled ? 'ON' : 'OFF';
}

applyRamOptimize(on) {
  const styleId = 'ytHubRamOptimizeStyle';
  let styleEl = document.getElementById(styleId);
  if (!on) {
    if (styleEl) styleEl.remove();
    return;
  }
  if (!styleEl) {
    styleEl = document.createElement('style');
    styleEl.id = styleId;
    styleEl.textContent = `
      /* Hide heavy sections to reduce DOM work/memory */
      #comments, ytd-comments, ytd-comments-header-renderer { display: none !important; }
      #secondary, ytd-watch-next-secondary-results-renderer { display: none !important; }
      ytd-live-chat-frame, #chat, #chatframe, ytd-live-chat-renderer { display: none !important; }
      ytd-merch-shelf-renderer, ytd-engagement-panel-section-list-renderer { display: none !important; }
      /* Stop hover previews where possible */
      ytd-rich-item-renderer video, ytd-video-preview video { display: none !important; }
    `;
    document.documentElement.appendChild(styleEl);
  }

  // Best-effort: unload thumbnails by forcing lazy loading
  try {
    document.querySelectorAll('img').forEach(img => {
      if (!img.getAttribute('loading')) img.setAttribute('loading', 'lazy');
      if (!img.getAttribute('decoding')) img.setAttribute('decoding', 'async');
    });
  } catch (e) {}

  // Best-effort: pause any offscreen videos/previews (won't affect the main player)
  try {
    const main = document.querySelector('video.html5-main-video');
    document.querySelectorAll('video').forEach(v => {
      if (main && v === main) return;
      if (!v.paused) v.pause();
      v.removeAttribute('src');
      v.load?.();
    });
  } catch (e) {}
}

    resetAutoScrollTimer(immediate) {
      if (this._autoScrollTimer) {
        clearTimeout(this._autoScrollTimer);
        this._autoScrollTimer = null;
      }
      if (!this.autoScrollEnabled) return;
      const delay = immediate ? 0 : (3000 + Math.floor(Math.random() * 2001));
      this._autoScrollTimer = setTimeout(() => {
        this.tryAdvanceShorts();
        this.resetAutoScrollTimer(false);
      }, delay);
    }
    autoScrollTick() {
      const isShorts = location.pathname.startsWith('/shorts/');
      if (!this.autoScrollEnabled || !isShorts) {
        if (this._autoScrollTimer) {
          clearTimeout(this._autoScrollTimer);
          this._autoScrollTimer = null;
        }
        this._lastShortsId = null;
        return;
      }
      const id = this.extractVideoId(location.href) || '';
      if (id && id !== this._lastShortsId) {
        this._lastShortsId = id;
        this.resetAutoScrollTimer(false);
      }
      if (!this._autoScrollTimer) this.resetAutoScrollTimer(false);
    }
    tryAdvanceShorts() {
  if (!location.pathname.startsWith('/shorts/')) return;
  const root =
    document.querySelector('ytd-reel-video-renderer[is-active]') ||
    document.querySelector('ytd-reel-video-renderer') ||
    document;
  const candidates = [
    document.querySelector('ytd-reel-player-overlay-renderer #navigation-button-down'),
    document.querySelector('ytd-reel-player-overlay-renderer [id="navigation-button-down"]'),
    document.querySelector('tp-yt-paper-icon-button#navigation-button-down'),
    document.querySelector('button[aria-label*="Next"]'),
    document.querySelector('button[aria-label*="Tiếp"]'),
    document.querySelector('tp-yt-paper-icon-button[aria-label*="Next"]'),
    document.querySelector('tp-yt-paper-icon-button[aria-label*="Tiếp"]')
  ].filter(Boolean);
  const fill =
    document.querySelector('#navigation-button-down .yt-spec-touch-feedback-shape__fill') ||
    document.querySelector('.yt-spec-touch-feedback-shape__fill');
  if (fill) candidates.unshift(fill);
  for (const el of candidates) {
    if (fireRealClick(el)) return;
  }
  const wheelTarget =
    root.querySelector('#shorts-player') ||
    root.querySelector('ytd-reel-player-renderer') ||
    root;
  wheelTarget.dispatchEvent(new WheelEvent('wheel', {
    bubbles: true,
    cancelable: true,
    deltaY: CONFIG.shortsWheelDelta
  }));
  window.scrollBy({ top: CONFIG.shortsScrollPower, left: 0, behavior: 'smooth' });
  const ev = new KeyboardEvent('keydown', { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40, which: 40, bubbles: true });
  document.dispatchEvent(ev);
}
clickLikeButtonOnce() {
  const pickFrom = (root) => {
    const buttons = Array.from(root.querySelectorAll('button[aria-label]'));
    const likeBtns = buttons.filter(b => {
      const a = (b.getAttribute('aria-label') || '').toLowerCase();
      return a.includes('like') || a.includes('thích');
    });
    return likeBtns[0] || null;
  };
  const tryWatchPage = () => {
    const root =
      document.querySelector('ytd-watch-metadata') ||
      document.querySelector('#top-level-buttons-computed') ||
      document;
    let btn = pickFrom(root) || pickFrom(document);
    if (!btn) {
      const maybe = document.querySelector('ytd-segmented-like-dislike-button-renderer button') ||
                    document.querySelector('ytd-toggle-button-renderer button') ||
                    null;
      btn = maybe || btn;
    }
    if (btn) btn.click();
  };
  const tryShorts = () => {
    const active =
      document.querySelector('ytd-reel-video-renderer[is-active]') ||
      document.querySelector('ytd-reel-video-renderer') ||
      document;
    let btn = pickFrom(active) || pickFrom(document);
    if (!btn) {
      const candidate = active.querySelector('button') || null;
      btn = candidate || btn;
    }
    if (btn) btn.click();
  };
  if (location.pathname.startsWith('/shorts/')) tryShorts();
  else tryWatchPage();
}
  }
function fireRealClick(el) {
  if (!el) return false;
  const clickable =
    el.closest('button, tp-yt-paper-icon-button, a, ytd-button-renderer, yt-button-shape') || el;
  const rect = clickable.getBoundingClientRect();
  const x = rect.left + rect.width / 2;
  const y = rect.top + rect.height / 2;
  clickable.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true, pointerType: 'mouse', clientX: x, clientY: y }));
  clickable.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, clientX: x, clientY: y }));
  clickable.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, clientX: x, clientY: y }));
  clickable.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, clientX: x, clientY: y }));
  return true;
}
  function initialize() {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => {
        setTimeout(() => { const hub = new YouTubeHub(); hub.init(); }, 1000);
      });
    } else {
      setTimeout(() => { const hub = new YouTubeHub(); hub.init(); }, 1000);
    }
  }
  initialize();
})();