OmniDown

An all-in-one, fast, and free HD video downloader. Supports video downloads from YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, TED, Facebook, X (formerly Twitter), and more. Enjoy seamless, high-quality downloads with no watermarks.

Pred inštaláciou vás Greasy Fork upozorňuje, že tento skript obsahuje antifeatures, ktoré sú skôr v prospech autora skriptu ako vo váš prospech.

Tento skript bude vkladať reklamu na stránky, ktoré navštívite. Vysvetlenie autora skriptu: This script uses external services (tool77, savefrom, y2meta, spotifydown, snaptik, ttdownloader, savetik, seekin, fdownloader, boilerplatehub, twittervideodownloader, ssstwitter, sssinstagram, saveclip, fastdl, downreels, storygrab, schatdownloader, pindown, pinpea) as fallback when direct download is blocked by CORS, DRM, or HLS streams. These services may display ads.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

Advertisement:

// ==UserScript==
// @name         OmniDown
// @name:ar      OmniDown
// @name:bg      OmniDown
// @name:cs      OmniDown
// @name:da      OmniDown
// @name:de      OmniDown
// @name:el      OmniDown
// @name:en      OmniDown
// @name:eo      OmniDown
// @name:es      OmniDown
// @name:es-419  OmniDown
// @name:fi      OmniDown
// @name:fr      OmniDown
// @name:fr-CA   OmniDown
// @name:he      OmniDown
// @name:hr      OmniDown
// @name:hu      OmniDown
// @name:id      OmniDown
// @name:it      OmniDown
// @name:ja      OmniDown
// @name:ka      OmniDown
// @name:ko      OmniDown
// @name:nb      OmniDown
// @name:nl      OmniDown
// @name:pl      OmniDown
// @name:pt-BR   OmniDown
// @name:ro      OmniDown
// @name:ru      OmniDown
// @name:sv      OmniDown
// @name:th      OmniDown
// @name:tr      OmniDown
// @name:uk      OmniDown
// @name:ug      OmniDown
// @name:vi      OmniDown
// @name:zh-CN   OmniDown
// @name:zh-TW   OmniDown
// @description       An all-in-one, fast, and free HD video downloader. Supports video downloads from YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, TED, Facebook, X (formerly Twitter), and more. Enjoy seamless, high-quality downloads with no watermarks.
// @description:ar    أداة شاملة وسريعة ومجانية لتحميل مقاطع الفيديو بدقة عالية. يدعم تحميل مقاطع الفيديو من يوتيوب، تيك توك، Douyin، إنستغرام، Facebook، ثريدز، XiaoHongShu، TED، إكس (تويتر سابقًا)، والمزيد. استمتع بتحميلات عالية الجودة بدون علامات مائية.
// @description:bg    Всичко в едно, бързо и безплатно изтегляне на видео с висока резолюция. Поддържа изтегляне на видеа от YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (бивш Twitter) и други. Насладете се на безпроблемни, висококачествени изтегляния без водни знаци.
// @description:cs    Vše v jednom, rychlý a bezplatný downloader videí ve vysoké kvalitě. Podporuje stahování videí z YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (dříve Twitter) og dalších. Užijte si plynulé, kvalitní stahování bez vodoznaků.
// @description:da    En alt-i-en, hurtig og gratis HD video downloader. Understøtter video downloads fra YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (tidligere Twitter) og mere. Nyd problemfri, høj kvalitet downloads uden vandmærker.
// @description:de    Ein All-in-One, schneller und kostenloser HD-Video-Downloader. Unterstützt das Herunterladen von Videos von YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (ehemals Twitter) und mehr. Genießen Sie nahtlose, hochqualitative Downloads ohne Wasserzeichen.
// @description:el    Ένας ολοκληρωμένος, γρήγορος και δωρεάν downloader HD βίντεο. Υποστηρίζει λήψη βίντεο από YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (πρώην Twitter) και άλλα. Απολαύστε απρόσκοπτες, υψηλής ποιότητας λήψεις χωρίς υδατογραφήματα.
// @description:en    An all-in-one, fast, and free HD video downloader. Supports video downloads from YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, TED, Facebook, X (formerly Twitter), and more. Enjoy seamless, high-quality downloads with no watermarks.
// @description:eo    Ĉio-en-unu, rapida kaj senpaga HD-video-elŝutilo. Subtenas video-elŝutojn de YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (antaŭe Twitter), kaj aliaj. Ŝatu seninterrompajn, altkvalitajn elŝutojn sen akvamaĵoj.
// @description:es    Un descargador de video todo en uno, rápido y gratuito en HD. Admite descargas de videos de YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anteriormente Twitter) y más. Disfruta de descargas de alta calidad sin marcas de agua.
// @description:fi    Kaikki yhdessä, nopea ja ilmainen HD-videoiden lataaja. Tukee videoiden lataamista YouTubesta, TikTokista, Douyinista, Instagramista, Facebookista, Threadsista, XiaoHongShusta, X:stä (entinen Twitter) ja muista. Naji saumatonta, korkealaatuista lataamista ilman vesileimoja.
// @description:fr    Un téléchargeur vidéo HD tout-en-un, rapide et gratuit. Prend en charge les téléchargements de vidéos depuis YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anciennement Twitter) et plus encore. Profitez de téléchargements fluides et de haute qualidade sans filigranes.
// @description:fr-CA Un téléchargeur vidéo HD tout-en-un, rapide et gratuit. Prend en charge les téléchargements de vidéos depuis YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anciennement Twitter) et plus encore. Profitez de téléchargements fluides et de haute qualidade sans filigranes.
// @description:he    מנוע הורדות וידאו HD מהיר, חינמי ומקיף. תומך בהורדת וידאו מיוטיوب, טיקטوك, אינסטגרם, Facebook, Threads, XiaoHongShu, TED, X (בעבר טוויטר) ועוד. תהנה מהורדות באיכות גבוהה ללא סימני מים.
// @description:hr    Svi-u-jednom, brzi i besplatni HD downloader za video. Podržava preuzimanje videa s YouTubea, TikToka, Instagrama, Facebooka, Threads, XiaoHongShu, TED, X (bivši Twitter) i još mnogo toga. Uživajte u besprijekornim, visokokvalitetnim preuzimanjima bez vodmarks.
// @description:hu    Egy minden-egyben, gyors és ingyenes HD videó letöltő. Támogatja a videók letöltését YouTube-ról, TikTok-ról, Instagram-ról, Facebookról, Threads, XiaoHongShu, TED, X (régen Twitter) és másról. Letöltheti a zökkenőmentes, magas minőségű letöltéseket vízjel nélkül.
// @description:id    Downloader video HD serba ada, cepat, dan gratis. Mendukung unduhan video dari YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (sebelumnya Twitter), dan banyak lagi. Nikmati unduhan berkualitas tinggi tanpa tanda air.
// @description:it    Un downloader video HD completo, veloce e gratuito. Supporta il download di video da YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (precedentemente Twitter) e altri. Goditi download fluidi e di alta qualità senza filigrane.
// @description:ja    YouTube の視聴体験を向上させるための高速で無料な HD 動画ダウンローダー。YouTube、TikTok、Douyin、Instagram、Facebook、Threads、XiaoHongShu、X(旧 Twitter)などからの動画ダウンロードをサポート。シームレスで高品質なダウンロードをウォーターマークなしでお楽しみください。
// @description:ka    ყველაფერი ერთში, სწრაფი და უფასო HD ვიდეო ჩამოტვირთვა. მხარს უჭერს ვიდეოს ჩამოტვირთვას YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (ყოფილი Twitter) და სხვა პლატფორმებიდან. დატკბით უარყოფითი ნიშნების გარეშე, უმაღლესი ხარისხის ჩამოტვირთვებით.
// @description:ko    YouTube 시청 경험을 향상시키는 모든 기능을 갖춘, 빠르고 무료인 HD 비디오 다운로드기. YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X(이전 Twitter) 등에서 비디오 다운로드를 지원합니다. 워터마크 없이 원활한 고품질 다운로드를 즐기세요.
// @description:nb    En alt-i-en, rask og gratis HD video nedlaster. Støtter video nedlastinger fra YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (tidligere Twitter), og mer. Nyt sømløse, høy kvalitet nedlastinger uten vannmerker.
// @description:nl    Een alles-in-één, snelle en gratis HD-video downloader. Ondersteunt video-downloads van YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (voorheen Twitter) en meer. Geniet van naadloze, hoogwaardige downloads zonder watermerken.
// @description:pl    Jedno w jednym, szybki i darmowy pobieracz wideo HD. Obsługuje pobieranie wideo z YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (dawniej Twitter) i innych. Ciesz się płynne, wysokiej jakości pobieranie bez znaków wodnych.
// @description:pt-BR Um downloader de vídeo HD completo, rápido e gratuito. Suporta downloads de vídeos do YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anteriormente Twitter) e muito mais. Aproveite downloads de alta qualidade sem marcas d'água.
// @description:ro    Un downloader video HD rapid și gratuit, totul într-un singur pachet. Suportă descărcarea de videoclipuri de pe YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (fost Twitter) și altele. Bucurați-vă de descărcări de înaltă calitate fără filigrane.
// @description:ru    Все-в-одном, быстрый и бесплатный HD видеозагрузчик. Поддерживает загрузку видео с YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (бывший Twitter) и других. Наслаждайтесь загрузками высокого качества без водяных знаков.
// @description:sv    En allt-i-ett, snabb og gratis HD-video nedladdare. Stöder video nedladdningar från YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (tidigare Twitter) och mer. Njut av sömlösa, högkvalitativa nedladdningar utan vattenmärken.
// @description:th    เครื่องมือดาวน์โหลดวิดีโอ HD ที่รวดเร็วและฟรีแบบครบวงจร รองรับการดาวน์โหลดวิดีโอจาก YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (เดิม Twitter) และอื่นๆ เพลิดเพลินกับการดาวน์โหลดที่มีคุณภาพสูงโดยไม่มีลายน้ำ
// @description:tr    Birçok platformdan video indirmenizi destekleyen, hızlı ve ücretsiz HD video indirme aracıdır. YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (eski Twitter) ve daha fazlasını destekler. Su işareti olmadan kesintisiz yüksek kaliteli indirmelerin keyfini çıkarın.
// @description:uk    Всі в одному, швидкий та безкоштовний HD відео-завантажувач. Підтримує завантаження відео з YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (колишній Twitter) та інших. Насолоджуйтесь безперешкодними завантаженнями без водяних знаків.
// @description:ug    YouTube كۆرۈش تەجرىبەڭىزنى ياخشىلايدىغان، تيز ۋە ھەقسىز HD ۋىدىئو چۈشۈرگۈچى. يۇتۇب، تيك توك، دۇيىن، ئىنستاگرام، Facebook، Threads، XiaoHongShu، TED، X (ئالدىنقى Twitter) ۋە باشقا پلاتفورمالاردىن ۋىدىئو چۈشۈرۈشنى قوللايدۇ. سۇ ئىشاراتسىز ئېغىزدىن سۈپەتلىك چۈشۈرۈشتىن لەززەتلىنىڭ.
// @description:vi    Trình tải video HD tất cả trong một, nhanh chóng và miễn phí. Hỗ trợ tải video từ YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (trước đây là Twitter) và nhiều nền tảng khác. Thưởng thức tải xuống chất lượng cao mượt mà mà không có watermark.
// @description:zh-CN 一款多合一、快速且免费的在线视频下载器。支持从 YouTube、TikTok、抖音、Instagram、Facebook、Threads、TED、小红书、X(Twitter)等平台下载视频。享受无水印、高质量、流畅便捷的下载体验
// @description:zh-TW 一款多合一、快速且免費的線上視頻下載器。支援從 YouTube、TikTok、抖音、Instagram、Facebook、Threads、TED、小紅書、X(Twitter)等平台下載視頻。享受無水印、高質量、流暢便捷的下載體驗
// @namespace    ksmo
// @version      2.9
// @author       ksmo
// @run-at       document-start
// @allFrames    true
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_openInTab
// @grant        unsafeWindow
// @match        *://*.youtube.com/*
// @match        *://x.com/*
// @match        *://twitter.com/*
// @match        *://*.threads.net/*
// @match        *://*.instagram.com/*
// @match        *://*.tiktok.com/*
// @match        *://*.douyin.com/*
// @match        *://*.xiaohongshu.com/*
// @match        *://*.facebook.com/*
// @match        *://*.snapchat.com/*
// @match        *://*.pinterest.com/*
// @match        *://*.ted.com/*
// @match        *://open.spotify.com/*
// @connect      youtube.com
// @connect      googlevideo.com
// @connect      tiktok.com
// @connect      tiktokcdn.com
// @connect      instagram.com
// @connect      cdninstagram.com
// @connect      twitter.com
// @connect      twimg.com
// @connect      facebook.com
// @connect      fbcdn.net
// @connect      threads.net
// @connect      douyin.com
// @connect      xiaohongshu.com
// @connect      ted.com
// @connect      snapchat.com
// @connect      sc-cdn.net
// @connect      pinterest.com
// @connect      pinimg.com
// @connect      spotify.com
// @connect      spotifydown.com
// @connect      corsproxy.io
// @connect      api.allorigins.win
// @connect      app.ytdown.to
// @connect      snapscooper.com
// @connect      y2meta.is
// @connect      savefrom.net
// @connect      snaptik.app
// @connect      ttdownloader.tools
// @connect      savetik.co
// @connect      seekin.ai
// @connect      fdownloader.net
// @connect      boilerplatehub.com
// @connect      twittervideodownloader.com
// @connect      ssstwitter.com
// @connect      sssinstagram.com
// @connect      saveclip.app
// @connect      fastdl.app
// @connect      downreels.com
// @connect      storygrab.io
// @connect      schatdownloader.com
// @connect      pindown.io
// @connect      pinpea.com
// @connect      tool77.com
// @antifeature  ads This script uses external services (tool77, savefrom, y2meta, spotifydown, snaptik, ttdownloader, savetik, seekin, fdownloader, boilerplatehub, twittervideodownloader, ssstwitter, sssinstagram, saveclip, fastdl, downreels, storygrab, schatdownloader, pindown, pinpea) as fallback when direct download is blocked by CORS, DRM, or HLS streams. These services may display ads.
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    if (window.__omniDownInitialized) return;
    window.__omniDownInitialized = true;

    const SETTINGS_KEYS = {
        showButton: 'ksmo_dl_show_button',
        autoFallback: 'ksmo_dl_auto_fallback',
        fallbackService: 'ksmo_dl_fallback_service',
        customProxy: 'ksmo_dl_custom_proxy',
        enableHotkey: 'ksmo_dl_enable_hotkey'
    };

    const getSetting = (key, defaultValue) => {
        if (typeof GM_getValue !== 'undefined') {
            return GM_getValue(key, defaultValue);
        }
        const val = localStorage.getItem(key);
        if (val === null) return defaultValue;
        if (val === 'true') return true;
        if (val === 'false') return false;
        return val;
    };

    const setSetting = (key, value) => {
        if (typeof GM_setValue !== 'undefined') {
            GM_setValue(key, value);
        } else {
            localStorage.setItem(key, value.toString());
        }
    };

    const FALLBACK_SERVICES = {
        ytdown: (url) => `https://app.ytdown.to/?url=${encodeURIComponent(url)}`,
        snapscooper: (url) => `https://snapscooper.com/tools/yt0?url=${encodeURIComponent(url)}`,
        y2meta: (url) => `https://y2meta.is/download/?url=${encodeURIComponent(url)}`,
        savefrom: (url) => `https://en1.savefrom.net/1-youtube-video-downloader-20pH/?url=${encodeURIComponent(url)}`,
        
        snaptik: (url) => `https://snaptik.app/?url=${encodeURIComponent(url)}`,
        ttdownloader: (url) => `https://ttdownloader.tools/?url=${encodeURIComponent(url)}`,
        
        savetik: (url) => `https://savetik.co/pt/douyin-downloader/?url=${encodeURIComponent(url)}`,
        seekin: (url) => `https://www.seekin.ai/douyin-downloader/?url=${encodeURIComponent(url)}`,
        
        fdownloader: (url) => `https://fdownloader.net/?url=${encodeURIComponent(url)}`,
        
        boilerplatehub: (url) => `https://boilerplatehub.com/free-tools/twitter-video-downloader?url=${encodeURIComponent(url)}`,
        twittervideodownloader: (url) => `https://twittervideodownloader.com/download?url=${encodeURIComponent(url)}`,
        ssstwitter: (url) => `https://ssstwitter.com/?url=${encodeURIComponent(url)}`,
        
        sssinstagram: (url) => `https://sssinstagram.com/?url=${encodeURIComponent(url)}`,
        saveclip: (url) => `https://saveclip.app/?url=${encodeURIComponent(url)}`,
        fastdl: (url) => `https://fastdl.app/?url=${encodeURIComponent(url)}`,
        downreels: (url) => `https://downreels.com/instagram-video-downloader-free/?url=${encodeURIComponent(url)}`,
        
        storygrab: (url) => `https://storygrab.io/en1?url=${encodeURIComponent(url)}`,
        schatdownloader: (url) => `https://schatdownloader.com/?url=${encodeURIComponent(url)}`,
        
        pindown: (url) => `https://pindown.io/pt1?url=${encodeURIComponent(url)}`,
        pinpea: (url) => `https://www.pinpea.com/pt/?url=${encodeURIComponent(url)}`,
        
        spotifydown: (url) => `https://spotifydown.com/?link=${encodeURIComponent(url)}`,
        tool77: (url) => `https://www.tool77.com/en/v/downloader?url=${encodeURIComponent(url)}`
    };

    const SITE_FALLBACKS = {
        'youtube.com': ['ytdown', 'snapscooper', 'y2meta', 'savefrom'],
        'tiktok.com': ['snaptik', 'ttdownloader'],
        'douyin.com': ['savetik', 'seekin'],
        'facebook.com': ['fdownloader'],
        'threads.net': ['fdownloader'],
        'twitter.com': ['boilerplatehub', 'twittervideodownloader', 'ssstwitter'],
        'x.com': ['boilerplatehub', 'twittervideodownloader', 'ssstwitter'],
        'instagram.com': ['sssinstagram', 'saveclip', 'fastdl', 'downreels'],
        'snapchat.com': ['storygrab', 'schatdownloader'],
        'pinterest.com': ['pindown', 'pinpea'],
        'spotify.com': ['spotifydown']
    };

    const getAvailableFallbacks = () => {
        const host = window.location.hostname;
        for (const domain of Object.keys(SITE_FALLBACKS)) {
            if (host.includes(domain)) {
                return SITE_FALLBACKS[domain];
            }
        }
        return ['tool77', 'savefrom', 'y2meta'];
    };

    const getFallbackServiceSettingKey = () => {
        const host = window.location.hostname;
        for (const domain of Object.keys(SITE_FALLBACKS)) {
            if (host.includes(domain)) {
                return `${SETTINGS_KEYS.fallbackService}_${domain}`;
            }
        }
        return `${SETTINGS_KEYS.fallbackService}_generic`;
    };

    const getCorsProxies = () => {
        const list = [];
        const custom = getSetting(SETTINGS_KEYS.customProxy, '');
        if (custom) {
            list.push((url) => `${custom}${encodeURIComponent(url)}`);
        }
        list.push((url) => `https://corsproxy.io/?${encodeURIComponent(url)}`);
        list.push((url) => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`);
        return list;
    };

    const css = `
        /* 1. Global Floating Button Styling */
        .ksmo-floating-dl {
            position: fixed !important;
            bottom: 96px !important;
            right: 24px !important;
            z-index: 2147483630 !important;
            width: 48px !important;
            height: 48px !important;
            border-radius: 50% !important;
            background: linear-gradient(135deg, #6c5ce7, #4b3fd4) !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(108, 92, 231, 0.2) !important;
            color: #f8fafc !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            cursor: pointer !important;
            opacity: 0;
            transform: scale(0.9) translateY(20px);
            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
            pointer-events: none !important;
        }
        .ksmo-floating-dl.visible {
            opacity: 1 !important;
            transform: scale(1) translateY(0) !important;
            pointer-events: auto !important;
        }
        .ksmo-floating-dl.downloading {
            background: linear-gradient(135deg, #ef4444, #b91c1c) !important;
            animation: ksmo-pulse 1.5s infinite !important;
        }
        .ksmo-floating-dl:hover {
            transform: scale(1.05) !important;
            box-shadow: 0 4px 12px rgba(108, 92, 231, 0.4) !important;
            color: #ffffff !important;
        }
        .ksmo-floating-dl svg {
            pointer-events: none !important;
        }

        /* 2. Embedded Video Hover Button Styling */
        .ksmo-dl-trigger {
            position: absolute !important;
            top: 16px !important;
            right: 16px !important;
            z-index: 2147483640 !important;
            width: 38px !important;
            height: 38px !important;
            border-radius: 50% !important;
            background: rgba(10, 10, 18, 0.82) !important;
            backdrop-filter: blur(16px) !important;
            -webkit-backdrop-filter: blur(16px) !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 15px rgba(108, 92, 231, 0.2) !important;
            color: #f8fafc !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            cursor: pointer !important;
            opacity: 0;
            transform: scale(0.9);
            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
            pointer-events: auto !important;
        }
        .ksmo-dl-trigger.visible {
            opacity: 1 !important;
            transform: scale(1) !important;
        }
        .ksmo-dl-trigger:hover {
            background: linear-gradient(135deg, #6c5ce7, #4b3fd4) !important;
            color: #ffffff !important;
            border-color: rgba(255, 255, 255, 0.15) !important;
            transform: scale(1.05) !important;
            box-shadow: 0 4px 12px rgba(108, 92, 231, 0.4) !important;
        }
        .ksmo-dl-trigger svg {
            pointer-events: none !important;
        }

        /* 3. YouTube Control Bar Integration */
        #ksmo-yt-control-btn svg {
            stroke: #f8fafc;
            opacity: 0.9;
            transition: opacity 0.1s cubic-bezier(0.4, 0, 1, 1);
        }
        #ksmo-yt-control-btn:hover svg {
            opacity: 1;
        }

        /* 4. Modal Overlay & Panel Styling */
        .ksmo-modal-backdrop {
            position: fixed !important;
            top: 0 !important;
            left: 0 !important;
            width: 100vw !important;
            height: 100vh !important;
            background: rgba(10, 10, 15, 0.6) !important;
            backdrop-filter: blur(12px) !important;
            -webkit-backdrop-filter: blur(12px) !important;
            z-index: 2147483645 !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            opacity: 0;
            transition: opacity 0.3s ease !important;
            pointer-events: auto !important;
        }
        .ksmo-modal-backdrop.show {
            opacity: 1 !important;
        }
        .ksmo-modal-backdrop.minimized {
            background: transparent !important;
            pointer-events: none !important;
            width: auto !important;
            height: auto !important;
            top: auto !important;
            left: auto !important;
            right: 0 !important;
            bottom: 0 !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-modal {
            position: fixed !important;
            bottom: 24px !important;
            right: 24px !important;
            width: 300px !important;
            padding: 14px !important;
            border-radius: 12px !important;
            transform: none !important;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5) !important;
            pointer-events: none !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-modal-header,
        .ksmo-modal-backdrop.minimized .ksmo-progress-container {
            pointer-events: auto !important;
        }
        .ksmo-modal {
            background: rgba(15, 15, 25, 0.85) !important;
            backdrop-filter: blur(20px) !important;
            -webkit-backdrop-filter: blur(20px) !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            border-radius: 16px !important;
            box-shadow: 0 20px 45px rgba(0, 0, 0, 0.6) !important;
            width: 90% !important;
            max-width: 440px !important;
            padding: 24px !important;
            color: #f8fafc !important;
            font-family: system-ui, -apple-system, sans-serif !important;
            transform: translateY(20px) scale(0.95) !important;
            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
            pointer-events: auto !important;
        }
        .ksmo-modal-backdrop.show .ksmo-modal {
            transform: translateY(0) scale(1) !important;
        }
        .ksmo-modal-header {
            display: flex !important;
            justify-content: space-between !important;
            align-items: center !important;
            margin-bottom: 20px !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-modal-header {
            margin-bottom: 8px !important;
        }
        .ksmo-modal-title {
            font-size: 16px !important;
            font-weight: 600 !important;
            overflow: hidden !important;
            text-overflow: ellipsis !important;
            white-space: nowrap !important;
            max-width: 280px !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-modal-title {
            max-width: 180px !important;
            font-size: 13px !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-modal-title::after {
            content: '';
            display: inline-block;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: #6c5ce7;
            margin-left: 8px;
            box-shadow: 0 0 8px #6c5ce7;
            animation: ksmo-pulse 1.5s infinite;
        }
        @keyframes ksmo-pulse {
            0% { transform: scale(0.9); opacity: 0.6; }
            50% { transform: scale(1.1); opacity: 1; }
            100% { transform: scale(0.9); opacity: 0.6; }
        }
        .ksmo-header-actions {
            display: flex !important;
            gap: 6px !important;
            align-items: center !important;
        }
        .ksmo-modal-close, .ksmo-modal-min {
            background: transparent !important;
            border: none !important;
            color: #94a3b8 !important;
            cursor: pointer !important;
            font-size: 20px !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            width: 28px !important;
            height: 28px !important;
            padding: 0 !important;
            transition: color 0.2s ease !important;
        }
        .ksmo-modal-close:hover, .ksmo-modal-min:hover {
            color: #f8fafc !important;
        }

        /* 5. Format Quality List Styles */
        .ksmo-quality-list {
            max-height: 240px !important;
            overflow-y: auto !important;
            margin-bottom: 20px !important;
            padding-right: 4px !important;
        }
        .ksmo-quality-list::-webkit-scrollbar {
            width: 6px !important;
        }
        .ksmo-quality-list::-webkit-scrollbar-track {
            background: rgba(255, 255, 255, 0.02) !important;
            border-radius: 3px !important;
        }
        .ksmo-quality-list::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.15) !important;
            border-radius: 3px !important;
        }
        .ksmo-quality-list::-webkit-scrollbar-thumb:hover {
            background: rgba(255, 255, 255, 0.25) !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-quality-list {
            display: none !important;
        }
        .ksmo-quality-row {
            display: flex !important;
            justify-content: space-between !important;
            align-items: center !important;
            padding: 10px 12px !important;
            background: rgba(255, 255, 255, 0.03) !important;
            border: 1px solid rgba(255, 255, 255, 0.04) !important;
            border-radius: 8px !important;
            margin-bottom: 8px !important;
            transition: all 0.2s ease !important;
        }
        .ksmo-quality-row:hover {
            background: rgba(255, 255, 255, 0.06) !important;
            border-color: rgba(255, 255, 255, 0.1) !important;
        }
        .ksmo-quality-info {
            display: flex !important;
            flex-direction: column !important;
            gap: 2px !important;
        }
        .ksmo-quality-name {
            font-size: 14px !important;
            font-weight: 500 !important;
        }
        .ksmo-quality-label {
            font-size: 11px !important;
            color: #94a3b8 !important;
        }
        .ksmo-dl-btn {
            background: linear-gradient(135deg, #6c5ce7, #4b3fd4) !important;
            color: #ffffff !important;
            border: none !important;
            border-radius: 6px !important;
            padding: 6px 12px !important;
            font-size: 13px !important;
            font-weight: 500 !important;
            cursor: pointer !important;
            transition: all 0.2s ease !important;
        }
        .ksmo-dl-btn:hover {
            transform: translateY(-1px) !important;
            box-shadow: 0 4px 12px rgba(108, 92, 231, 0.4) !important;
        }

        /* 6. Footer Controls */
        .ksmo-modal-footer {
            display: flex !important;
            flex-direction: column !important;
            gap: 12px !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-modal-footer {
            display: none !important;
        }
        .ksmo-footer-row {
            display: flex !important;
            gap: 8px !important;
        }
        .ksmo-select {
            flex: 1 !important;
            background: rgba(10, 10, 15, 0.8) !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            color: #f8fafc !important;
            border-radius: 8px !important;
            padding: 8px 12px !important;
            font-size: 13px !important;
            outline: none !important;
            cursor: pointer !important;
        }
        .ksmo-secondary-btn {
            background: rgba(255, 255, 255, 0.05) !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            color: #f8fafc !important;
            border-radius: 8px !important;
            padding: 8px 16px !important;
            font-size: 13px !important;
            cursor: pointer !important;
            transition: all 0.2s ease !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
        }
        .ksmo-secondary-btn:hover {
            background: rgba(255, 255, 255, 0.1) !important;
        }

        /* 7. Progress Bar Layouts */
        .ksmo-progress-container {
            display: none !important;
            flex-direction: column !important;
            gap: 6px !important;
            margin-bottom: 20px !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-progress-container {
            display: flex !important;
            margin-bottom: 0 !important;
        }
        .ksmo-progress-bar {
            width: 100% !important;
            height: 6px !important;
            background: rgba(255, 255, 255, 0.1) !important;
            border-radius: 3px !important;
            overflow: hidden !important;
        }
        .ksmo-progress-fill {
            width: 0% !important;
            height: 100% !important;
            background: linear-gradient(90deg, #6c5ce7, #a29bfe) !important;
            transition: width 0.1s linear !important;
        }
        .ksmo-progress-text {
            display: flex !important;
            justify-content: space-between !important;
            font-size: 12px !important;
            color: #94a3b8 !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-progress-cancel {
            font-size: 0 !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-progress-cancel::after {
            content: '✕' !important;
            font-size: 14px !important;
            font-weight: bold !important;
            color: #ef4444 !important;
        }
        .ksmo-warning-label {
            font-size: 11px !important;
            color: #f59e0b !important;
            text-align: center !important;
            margin-top: 4px !important;
        }
        .ksmo-modal-backdrop.minimized .ksmo-warning-label {
            display: none !important;
        }

        /* 8. Toast Alert Overlay */
        .ksmo-toast {
            position: fixed !important;
            bottom: 24px !important;
            left: 50% !important;
            transform: translateX(-50%) translateY(20px) !important;
            background: rgba(10, 10, 18, 0.9) !important;
            backdrop-filter: blur(12px) !important;
            -webkit-backdrop-filter: blur(12px) !important;
            color: #ffffff !important;
            padding: 10px 20px !important;
            border-radius: 10px !important;
            font-size: 13px !important;
            font-family: system-ui, -apple-system, sans-serif !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            box-shadow: 0 10px 25px rgba(0,0,0,0.5) !important;
            z-index: 2147483647 !important;
            pointer-events: none !important;
            opacity: 0;
            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
        }
        .ksmo-toast.show {
            opacity: 1 !important;
            transform: translateX(-50%) translateY(0) !important;
        }

        /* 9. Responsiveness Adjustments */
        @media (max-width: 400px) {
            .ksmo-modal {
                padding: 16px !important;
            }
            .ksmo-footer-row {
                flex-direction: column !important;
            }
        }
    `;

    if (typeof GM_addStyle !== 'undefined') {
        GM_addStyle(css);
    } else {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    const toastQueue = [];
    let activeToast = null;
    let toastTimeout = null;

    const processNextToast = () => {
        if (toastQueue.length === 0) return;
        const { message, duration } = toastQueue[0];
        
        const toast = document.createElement('div');
        toast.className = 'ksmo-toast';
        toast.textContent = message;
        document.body.appendChild(toast);
        activeToast = toast;

        setTimeout(() => toast.classList.add('show'), 10);
        
        toastTimeout = setTimeout(() => {
            toast.classList.remove('show');
            setTimeout(() => {
                toast.remove();
                activeToast = null;
                toastQueue.shift();
                processNextToast();
            }, 300);
        }, duration);
    };

    const showToast = (message, duration = 4000) => {
        toastQueue.push({ message, duration });
        if (toastQueue.length === 1) {
            processNextToast();
        } else if (activeToast) {
            clearTimeout(toastTimeout);
            activeToast.classList.remove('show');
            setTimeout(() => {
                if (activeToast) {
                    activeToast.remove();
                    activeToast = null;
                }
                toastQueue.shift();
                processNextToast();
            }, 200);
        }
    };

    const formatBytes = (bytes) => {
        if (!bytes) return '';
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(1024));
        return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
    };

    const utils = {
        getCodec: (mimeType) => {
            const mime = (mimeType || '').toLowerCase();
            if (mime.includes('webm')) return 'WebM';
            if (mime.includes('mp4')) return 'MP4';
            if (mime.includes('ogg')) return 'Ogg';
            return 'MP4';
        }
    };

    const cacheManager = {
        sizeCache: new Map(),
        embedCache: new Map(),
        setFileSize: function(url, size) {
            if (this.sizeCache.has(url)) {
                this.sizeCache.delete(url);
            }
            this.sizeCache.set(url, size);
            if (this.sizeCache.size > 200) {
                const firstKey = this.sizeCache.keys().next().value;
                this.sizeCache.delete(firstKey);
            }
        },
        getFileSize: function(url) {
            return this.sizeCache.get(url);
        },
        hasFileSize: function(url) {
            return this.sizeCache.has(url);
        },
        setEmbed: function(videoId, data) {
            if (this.embedCache.has(videoId)) {
                this.embedCache.delete(videoId);
            }
            this.embedCache.set(videoId, data);
            if (this.embedCache.size > 50) {
                const firstKey = this.embedCache.keys().next().value;
                this.embedCache.delete(firstKey);
            }
        },
        getEmbed: function(videoId) {
            return this.embedCache.get(videoId);
        },
        hasEmbed: function(videoId) {
            return this.embedCache.has(videoId);
        }
    };

    let activeRequest = null;
    const DownloadManager = {
        download: (url, filename, onProgress, onComplete, onError) => {
            let proxyIndex = -1;
            const attemptDownload = (currentUrl) => {
                const useGMDownload = typeof GM_download !== 'undefined';
                const proxies = getCorsProxies();
                if (useGMDownload && proxyIndex === -1) {
                    activeRequest = GM_download({
                        url: currentUrl,
                        name: filename,
                        onload: () => {
                            activeRequest = null;
                            onComplete();
                        },
                        onerror: (err) => {
                            activeRequest = null;
                            handleError(err);
                        },
                        onprogress: (e) => {
                            if (e.total) {
                                onProgress(Math.round((e.loaded / e.total) * 100));
                            }
                        }
                    });
                } else {
                    activeRequest = GM_xmlhttpRequest({
                        method: 'GET',
                        url: currentUrl,
                        responseType: 'blob',
                        onprogress: (e) => {
                            if (e.total) {
                                onProgress(Math.round((e.loaded / e.total) * 100));
                            }
                        },
                        onload: (resp) => {
                            activeRequest = null;
                            if (resp.status === 200) {
                                const blob = resp.response;
                                const blobUrl = URL.createObjectURL(blob);
                                const a = document.createElement('a');
                                a.href = blobUrl;
                                a.download = filename;
                                document.body.appendChild(a);
                                a.click();
                                a.remove();
                                URL.revokeObjectURL(blobUrl);
                                onComplete();
                            } else {
                                handleError(new Error(`Status ${resp.status}`));
                            }
                        },
                        onerror: (err) => {
                            activeRequest = null;
                            handleError(err);
                        }
                    });
                }
            };

            const handleError = (err) => {
                proxyIndex++;
                const proxies = getCorsProxies();
                if (proxyIndex < proxies.length) {
                    const nextUrl = proxies[proxyIndex](url);
                    attemptDownload(nextUrl);
                } else {
                    onError(err);
                }
            };

            attemptDownload(url);
        },
        cancel: () => {
            if (activeRequest && typeof activeRequest.abort === 'function') {
                activeRequest.abort();
            }
            activeRequest = null;
        }
    };

    const updateFabDownloadState = () => {
        const fab = document.getElementById('ksmo-floating-dl');
        if (fab) {
            if (DownloadQueue.active) {
                fab.classList.add('downloading');
            } else {
                fab.classList.remove('downloading');
            }
        }
    };

    const DownloadQueue = {
        list: [],
        active: null,
        add: (url, filename, onProgress, onComplete, onError) => {
            const item = { url, filename, onProgress, onComplete, onError, cancelled: false };
            DownloadQueue.list.push(item);
            if (!DownloadQueue.active) {
                DownloadQueue.processNext();
            }
        },
        processNext: () => {
            updateFabDownloadState();
            if (DownloadQueue.list.length === 0) {
                DownloadQueue.active = null;
                return;
            }
            const item = DownloadQueue.list.shift();
            DownloadQueue.active = item;
            DownloadManager.download(
                item.url,
                item.filename,
                item.onProgress,
                () => {
                    if (item.cancelled) return;
                    item.onComplete();
                    DownloadQueue.processNext();
                },
                (err) => {
                    if (item.cancelled) return;
                    item.onError(err);
                    DownloadQueue.processNext();
                }
            );
        },
        clear: () => {
            if (DownloadQueue.active) {
                DownloadQueue.active.cancelled = true;
            }
            DownloadQueue.list.forEach(item => {
                item.cancelled = true;
            });
            DownloadManager.cancel();
            DownloadQueue.list = [];
            DownloadQueue.active = null;
            updateFabDownloadState();
        }
    };

    const pendingSizes = new Map();

    const triggerSizeCallbacks = (targetUrl, size) => {
        if (size !== null) {
            cacheManager.setFileSize(targetUrl, size);
        }
        const list = pendingSizes.get(targetUrl) || [];
        pendingSizes.delete(targetUrl);
        list.forEach(cb => cb(size));
    };

    const fetchFileSize = (url, callback) => {
        if (cacheManager.hasFileSize(url)) {
            callback(cacheManager.getFileSize(url));
            return;
        }
        if (pendingSizes.has(url)) {
            pendingSizes.get(url).push(callback);
            return;
        }
        pendingSizes.set(url, [callback]);

        if (typeof GM_xmlhttpRequest === 'undefined') {
            triggerSizeCallbacks(url, null);
            return;
        }

        const attemptRangeRequest = () => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                headers: {
                    'Range': 'bytes=0-0'
                },
                onload: (rangeResp) => {
                    const rangeLen = rangeResp.responseHeaders.match(/content-range:\s*bytes\s*\d+-\d+\/(\d+)/i) || 
                                     rangeResp.responseHeaders.match(/content-length:\s*(\d+)/i);
                    triggerSizeCallbacks(url, rangeLen ? parseInt(rangeLen[1], 10) : null);
                },
                onerror: () => triggerSizeCallbacks(url, null)
            });
        };

        GM_xmlhttpRequest({
            method: 'HEAD',
            url: url,
            onload: (resp) => {
                const len = resp.responseHeaders.match(/content-length:\s*(\d+)/i);
                if (len && len[1]) {
                    triggerSizeCallbacks(url, parseInt(len[1], 10));
                } else {
                    attemptRangeRequest();
                }
            },
            onerror: () => {
                attemptRangeRequest();
            }
        });
    };

    const convertXmlToSrt = (xmlString) => {
        try {
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
            const texts = xmlDoc.getElementsByTagName('text');
            let srt = '';
            for (let i = 0; i < texts.length; i++) {
                const node = texts[i];
                const start = parseFloat(node.getAttribute('start') || '0');
                const dur = parseFloat(node.getAttribute('dur') || '0');
                const text = node.textContent.replace(/<[^>]*>/g, '');

                const formatTime = (seconds) => {
                    const date = new Date(null);
                    date.setSeconds(seconds);
                    const ms = Math.floor((seconds % 1) * 1000).toString().padStart(3, '0');
                    return date.toISOString().substr(11, 8) + ',' + ms;
                };

                srt += `${i + 1}\n`;
                srt += `${formatTime(start)} --> ${formatTime(start + dur)}\n`;
                srt += `${text}\n\n`;
            }
            return srt;
        } catch (e) {
            return '';
        }
    };

    /**
     * Extracts a JSON object assigned to a specific variable name inside a string of HTML.
     * Uses a bracket-balancing parser to accurately locate the closing brace of the JSON object,
     * handling string literals (with backslash escapes) to avoid matching inner string braces.
     */
    const extractJsonFromHtml = (html, varName) => {
        const pattern = new RegExp(varName + '\\s*=\\s*\\{');
        const match = html.match(pattern);
        if (!match) return null;
        
        const startIndex = match.index + match[0].length - 1;
        let braceCount = 0;
        let inString = false;
        let escape = false;

        for (let i = startIndex; i < html.length; i++) {
            const char = html[i];
            if (escape) {
                escape = false;
                continue;
            }
            if (char === '\\') {
                escape = true;
                continue;
            }
            if (char === '"' || char === "'") {
                if (!inString) {
                    inString = char;
                } else if (inString === char) {
                    inString = false;
                }
            }
            if (!inString) {
                if (char === '{') braceCount++;
                if (char === '}') {
                    braceCount--;
                    if (braceCount === 0) {
                        const jsonStr = html.slice(startIndex, i + 1);
                        try {
                            return JSON.parse(jsonStr);
                        } catch (e) {
                            return null;
                        }
                    }
                }
            }
        }
        return null;
    };

    const signatureDecipherer = {
        cachedDecipher: null,
        fetching: false,
        callbacks: [],
        
        findBaseJs: () => {
            const script = Array.from(document.querySelectorAll('script')).map(s => s.src).find(src => src.includes('/s/player/') && src.includes('/base.js'));
            return script || null;
        },
        
        parseDecipherFunc: (code) => {
            const decipherMatch = code.match(/([a-zA-Z0-9$_]+)=function\([a-zA-Z]\)\{[a-zA-Z]=[a-zA-Z]\.split\(""\);([\s\S]+?);return [a-zA-Z]\.join\(""\)\}/);
            if (!decipherMatch) return null;
            
            const funcBody = decipherMatch[2];
            const objMatch = funcBody.match(/([a-zA-Z0-9$_]+)\.[a-zA-Z0-9$_]+\([a-zA-Z],[0-9]+\)/);
            if (!objMatch) return null;
            const objName = objMatch[1];
            
            const objNameEscaped = objName.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
            const objRegex = new RegExp(`var\\s+${objNameEscaped}\\s*=\\s*\\{([\\s\\S]+?)\\};`);
            const objDefMatch = code.match(objRegex);
            if (!objDefMatch) return null;
            
            const objDef = objDefMatch[1];
            const methods = {};
            const methodMatches = objDef.matchAll(/([a-zA-Z0-9$_]+)\s*:\s*function\(([^)]+)\)\{([^}]+)\}/g);
            for (const match of methodMatches) {
                const name = match[1];
                const body = match[3];
                if (body.includes('reverse')) {
                    methods[name] = 'reverse';
                } else if (body.includes('splice')) {
                    methods[name] = 'slice';
                } else {
                    methods[name] = 'swap';
                }
            }
            
            const steps = [];
            const stepMatches = funcBody.matchAll(/([a-zA-Z0-9$_]+)\.([a-zA-Z0-9$_]+)\([a-zA-Z],([0-9]+)\)/g);
            for (const match of stepMatches) {
                const mName = match[2];
                const param = parseInt(match[3], 10);
                const action = methods[mName];
                if (action) {
                    steps.push({ action, param });
                }
            }
            
            return steps;
        },
        
        buildDecipherFunc: (steps) => {
            return (sig) => {
                const arr = sig.split('');
                steps.forEach(step => {
                    if (step.action === 'reverse') {
                        arr.reverse();
                    } else if (step.action === 'slice') {
                        arr.splice(0, step.param);
                    } else if (step.action === 'swap') {
                        const temp = arr[0];
                        arr[0] = arr[step.param % arr.length];
                        arr[step.param % arr.length] = temp;
                    }
                });
                return arr.join('');
            };
        },
        
        decipher: (sig, callback) => {
            if (signatureDecipherer.cachedDecipher) {
                try {
                    callback(signatureDecipherer.cachedDecipher(sig));
                } catch (e) {
                    callback(null);
                }
                return;
            }
            
            const baseJs = signatureDecipherer.findBaseJs();
            if (!baseJs) {
                callback(null);
                return;
            }

            const cachedUrl = getSetting('ksmo_youtube_base_url', null);
            const cachedSteps = getSetting('ksmo_youtube_decipher_steps', null);
            if (cachedUrl === baseJs && cachedSteps) {
                try {
                    const steps = JSON.parse(cachedSteps);
                    const dec = signatureDecipherer.buildDecipherFunc(steps);
                    signatureDecipherer.cachedDecipher = dec;
                    callback(dec(sig));
                    return;
                } catch (e) {}
            }
            
            signatureDecipherer.callbacks.push({ sig, callback });
            if (signatureDecipherer.fetching) return;
            signatureDecipherer.fetching = true;
            
            GM_xmlhttpRequest({
                method: 'GET',
                url: baseJs,
                onload: (resp) => {
                    signatureDecipherer.fetching = false;
                    try {
                        const steps = signatureDecipherer.parseDecipherFunc(resp.responseText);
                        if (steps && steps.length > 0) {
                            setSetting('ksmo_youtube_base_url', baseJs);
                            setSetting('ksmo_youtube_decipher_steps', JSON.stringify(steps));
                            const dec = signatureDecipherer.buildDecipherFunc(steps);
                            signatureDecipherer.cachedDecipher = dec;
                            while (signatureDecipherer.callbacks.length) {
                                const item = signatureDecipherer.callbacks.shift();
                                try {
                                    item.callback(dec(item.sig));
                                } catch (e) {
                                    item.callback(null);
                                }
                            }
                        } else {
                            while (signatureDecipherer.callbacks.length) {
                                signatureDecipherer.callbacks.shift().callback(null);
                            }
                        }
                    } catch (e) {
                        while (signatureDecipherer.callbacks.length) {
                            signatureDecipherer.callbacks.shift().callback(null);
                        }
                    }
                },
                onerror: () => {
                    signatureDecipherer.fetching = false;
                    while (signatureDecipherer.callbacks.length) {
                        signatureDecipherer.callbacks.shift().callback(null);
                    }
                }
            });
        }
    };

    const decipherUrl = (cipher, callback) => {
        let parsed = new URLSearchParams(cipher);
        let url = parsed.get('url');
        let s = parsed.get('s');
        let sp = parsed.get('sp') || 'sig';

        if (!url || !s) {
            const urlMatch = cipher.match(/url=([^&]+)/);
            const sMatch = cipher.match(/s=([^&]+)/);
            const spMatch = cipher.match(/sp=([^&]+)/);
            if (urlMatch) url = decodeURIComponent(urlMatch[1].replace(/\+/g, ' '));
            if (sMatch) s = decodeURIComponent(sMatch[1].replace(/\+/g, ' '));
            if (spMatch) sp = decodeURIComponent(spMatch[1].replace(/\+/g, ' '));
        }

        if (!url || !s) {
            callback(null);
            return;
        }

        signatureDecipherer.decipher(s, (decipheredS) => {
            if (decipheredS) {
                try {
                    const u = new URL(url);
                    u.searchParams.set(sp, decipheredS);
                    callback(u.toString());
                } catch (e) {
                    callback(null);
                }
            } else {
                callback(null);
            }
        });
    };

    const fetchEmbedPlayerResponse = (videoId, callback) => {
        if (cacheManager.hasEmbed(videoId)) {
            callback(cacheManager.getEmbed(videoId));
            return;
        }
        if (typeof GM_xmlhttpRequest === 'undefined') {
            callback(null);
            return;
        }
        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://www.youtube.com/embed/${videoId}`,
            onload: (resp) => {
                const data = extractJsonFromHtml(resp.responseText, 'ytInitialPlayerResponse');
                if (data) {
                    cacheManager.setEmbed(videoId, data);
                    callback(data);
                } else {
                    callback(null);
                }
            },
            onerror: () => callback(null)
        });
    };

    const twitterExtractor = () => {
        const source = document.querySelector('[data-testid="videoPlayer"] video source');
        let url = source?.src;
        if (!url) {
            const video = document.querySelector('video') || document.querySelector('[data-testid="videoPlayer"] video');
            url = video?.src;
        }
        if (!url || url.startsWith('blob:')) return null;

        return {
            title: document.title || 'Twitter Video',
            formats: [{ quality: 'Source', url, type: 'video/mp4', label: 'Twitter Video' }]
        };
    };

    const extractors = {
        'youtube.com': () => {
            try {
                const myWin = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
                const player = document.querySelector('#movie_player') || myWin.movie_player;
                let response = player?.getPlayerResponse?.() || myWin.ytInitialPlayerResponse;

                if (!response) {
                    const scripts = Array.from(document.querySelectorAll('script'));
                    for (const script of scripts) {
                        const data = extractJsonFromHtml(script.textContent, 'ytInitialPlayerResponse');
                        if (data) {
                            response = data;
                            break;
                        }
                    }
                }

                if (!response) return null;

                const title = response.videoDetails?.title || document.title || 'YouTube Video';
                const streaming = response.streamingData;
                if (!streaming) return null;

                const formats = [];
                const muxed = streaming.formats || [];
                const adaptive = streaming.adaptiveFormats || [];

                muxed.forEach(f => {
                    let url = f.url;
                    let cipher = null;
                    if (!url && f.signatureCipher) {
                        cipher = f.signatureCipher;
                    }
                    formats.push({
                        quality: f.qualityLabel || `${f.width}p`,
                        url: url || '',
                        cipher: cipher,
                        type: f.mimeType?.split(';')[0] || 'video/mp4',
                        label: cipher ? `Muxed [${utils.getCodec(f.mimeType)}] (Deciphering...)` : `Muxed [${utils.getCodec(f.mimeType)}]`
                    });
                });

                adaptive.forEach(f => {
                    let url = f.url;
                    let cipher = null;
                    if (!url && f.signatureCipher) {
                        cipher = f.signatureCipher;
                    }
                    const isVideo = f.mimeType?.includes('video');
                    formats.push({
                        quality: f.qualityLabel || `${f.width}p`,
                        url: url || '',
                        cipher: cipher,
                        type: f.mimeType?.split(';')[0] || 'video/mp4',
                        label: cipher ? (isVideo ? `Adaptive [${utils.getCodec(f.mimeType)}] (Deciphering...)` : `Audio Only [${utils.getCodec(f.mimeType)}] (Deciphering...)`) : (isVideo ? `Adaptive [${utils.getCodec(f.mimeType)}] (No Audio)` : `Audio Only [${utils.getCodec(f.mimeType)}]`)
                    });
                });

                const captionTracks = response.captions?.playerCaptionsTracklistRenderer?.captionTracks || [];
                captionTracks.forEach(track => {
                    if (track.baseUrl) {
                        formats.push({
                            quality: `${track.name.simpleText || track.name.runs?.[0]?.text || 'Subtitles'}`,
                            url: track.baseUrl,
                            type: 'text/srt',
                            label: 'Subtitle Track',
                            isSubtitle: true
                        });
                    }
                });

                return { title, formats };
            } catch (e) {
                return null;
            }
        },

        'tiktok.com': () => {
            const video = document.querySelector('video');
            let url = video?.src;
            let title = document.title || 'TikTok Video';

            const myWin = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
            if (myWin.__INITIAL_STATE__) {
                const data = myWin.__INITIAL_STATE__;
                url = data.videoData?.playAddr || url;
                title = data.videoData?.desc || title;
            }

            if (!url) {
                const container = document.querySelector('[class*="xgplayer-container"]');
                const v = container?.querySelector('video');
                url = v?.src;
            }

            if (!url) return null;
            return {
                title,
                formats: [{ quality: 'Source', url, type: 'video/mp4', label: 'TikTok Video' }]
            };
        },

        'instagram.com': () => {
            const video = document.querySelector('video');
            let url = video?.src;
            if (!url) {
                const preload = document.querySelector('link[rel="preload"][as="video"]');
                url = preload?.href;
            }
            if (!url) return null;

            const isBlob = url.startsWith('blob:');
            const article = video?.closest('article');
            const title = article?.querySelector('h1')?.textContent || document.title || 'Instagram Video';
            
            return {
                title,
                formats: [{
                    quality: 'Source',
                    url: isBlob ? '' : url,
                    type: 'video/mp4',
                    label: isBlob ? 'HLS Stream (Direct Download Unsupported)' : 'Instagram Video',
                    isHLS: isBlob
                }]
            };
        },

        'x.com': twitterExtractor,
        'twitter.com': twitterExtractor,

        'facebook.com': () => {
            const video = document.querySelector('video');
            if (!video || !video.src || video.src.startsWith('blob:')) return null;
            return {
                title: document.title || 'Facebook Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'Facebook Video' }]
            };
        },

        'threads.net': () => {
            const video = document.querySelector('video');
            if (!video || !video.src || video.src.startsWith('blob:')) return null;
            return {
                title: document.title || 'Threads Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'Threads Video' }]
            };
        },

        'douyin.com': () => {
            const video = document.querySelector('video');
            if (!video || !video.src) return null;
            return {
                title: document.title || 'Douyin Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'Douyin Video' }]
            };
        },

        'xiaohongshu.com': () => {
            const video = document.querySelector('video');
            if (!video || !video.src) return null;
            return {
                title: document.title || 'XiaoHongShu Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'XiaoHongShu Video' }]
            };
        },

        'ted.com': () => {
            const video = document.querySelector('video');
            if (!video || !video.src) return null;
            return {
                title: document.title || 'TED Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'TED Video' }]
            };
        },

        'snapchat.com': () => {
            const video = document.querySelector('video');
            if (!video || !video.src) return null;
            return {
                title: document.title || 'Snapchat Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'Snapchat Video' }]
            };
        },

        'pinterest.com': () => {
            const video = document.querySelector('video');
            if (!video || !video.src) return null;
            return {
                title: document.title || 'Pinterest Video',
                formats: [{ quality: 'Source', url: video.src, type: 'video/mp4', label: 'Pinterest Video' }]
            };
        },

        'spotify.com': () => {
            const url = window.location.href;
            const match = url.match(/track\/([a-zA-Z0-9]+)/);
            if (match) {
                return {
                    title: document.title || 'Spotify Track',
                    formats: [],
                    spotifyId: match[1]
                };
            }
            return null;
        },

        'generic': () => {
            const video = document.querySelector('video');
            if (!video || !video.src || video.src.startsWith('blob:')) return null;
            const isHLS = video.src.includes('.m3u8') || video.src.includes('m3u8');
            return {
                title: document.title || 'Video',
                formats: [{
                    quality: 'Source',
                    url: isHLS ? '' : video.src,
                    type: 'video/mp4',
                    label: isHLS ? 'HLS Stream (Direct Download Unsupported)' : 'HTML5 Video',
                    isHLS: isHLS
                }]
            };
        }
    };

    const YOUTUBE_PLAYER_SELECTORS = [
        '#movie_player video',
        '.html5-video-player video',
        'ytd-watch-flexy video',
        'video[src*="googlevideo.com"]',
        'video[src*="blob:"]'
    ];

    const KNOWN_VIDEO_SITES = [
        'youtube.com', 'tiktok.com', 'instagram.com', 'twitter.com',
        'x.com', 'facebook.com', 'threads.net', 'douyin.com',
        'xiaohongshu.com', 'ted.com', 'snapchat.com', 'pinterest.com', 'spotify.com'
    ];

    const findAnyVideo = () => {
        if (window.location.hostname.includes('youtube.com')) {
            for (const selector of YOUTUBE_PLAYER_SELECTORS) {
                const v = document.querySelector(selector);
                if (v) return v;
            }
        }
        const queue = [document.body];
        const visitedNodes = new Set();
        while (queue.length > 0) {
            const node = queue.shift();
            if (!node || visitedNodes.has(node)) continue;
            visitedNodes.add(node);

            const video = node.querySelector?.('video');
            if (video) return video;

            const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
            while (walker.nextNode()) {
                const el = walker.currentNode;
                if (el.shadowRoot && !visitedNodes.has(el.shadowRoot)) {
                    queue.push(el.shadowRoot);
                }
            }
        }
        return null;
    };

    function getExtractor() {
        const host = window.location.hostname;
        for (const domain of Object.keys(extractors)) {
            if (domain !== 'generic' && host.includes(domain)) {
                return extractors[domain];
            }
        }
        return extractors['generic'];
    }

    function getPlayerContainer(video) {
        const specificPlayer =
            video.closest('[data-uia="video-player"]') ||
            video.closest('.watch-video') ||
            video.closest('[class*="watch-video"]') ||
            video.closest('[class*="nf-player"]') ||
            video.closest('.html5-video-player') ||
            video.closest('.video-player') ||
            video.closest('[class*="video-player"]') ||
            video.closest('[class*="stream-player"]') ||
            video.closest('[data-testid*="player"]') ||
            video.closest('[role="main"]') ||
            video.closest('[id*="player"]');

        const forbidden = ['BODY', 'HTML', 'MAIN', 'ARTICLE', 'SECTION'];
        if (specificPlayer && !forbidden.includes(specificPlayer.tagName)) return specificPlayer;

        let current = video.parentElement;
        const keywords = ['player', 'video-player', 'movie_player', 'video-container', 'video-wrapper', 'player-container', 'vjs-control-bar', 'stream-player'];

        while (current && !forbidden.includes(current.tagName)) {
            const id = current.id || '';
            const classes = Array.from(current.classList).join(' ').toLowerCase();

            if (keywords.some(k => id.toLowerCase().includes(k) || classes.includes(k))) {
                return current;
            }
            current = current.parentElement;
        }

        return video.parentElement && !forbidden.includes(video.parentElement.tagName) ? video.parentElement : video;
    }

    const containerListeners = new WeakMap();
    const attachedVideos = new WeakSet();

    function injectYouTubeControlButton() {
        if (document.getElementById('ksmo-yt-control-btn')) return;
        const player = document.querySelector('.html5-video-player');
        if (!player) return;
        const rightControls = player.querySelector('.ytp-right-controls');
        if (!rightControls) return;

        const btn = document.createElement('button');
        btn.id = 'ksmo-yt-control-btn';
        btn.className = 'ytp-button';
        btn.setAttribute('aria-label', 'Download Video');
        btn.setAttribute('title', 'Download Video (Ctrl+Alt+Shift+D)');
        btn.style.display = 'inline-flex';
        btn.style.alignItems = 'center';
        btn.style.justifyContent = 'center';
        btn.style.verticalAlign = 'top';
        btn.style.cursor = 'pointer';

        btn.innerHTML = `
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="width: 20px; height: 20px; margin: auto;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        `;

        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            openDownloadModal();
        });

        rightControls.prepend(btn);
    }

    function attachTriggerButton(video) {
        if (video.dataset.ksmoDownloaderAttached) return;
        if (video.offsetWidth < 50 || video.offsetHeight < 50) return;

        const container = getPlayerContainer(video);
        if (container.querySelector('.ksmo-dl-trigger')) {
            video.dataset.ksmoDownloaderAttached = 'true';
            return;
        }

        if (getSetting(SETTINGS_KEYS.showButton, true) === false) {
            video.dataset.ksmoDownloaderAttached = 'true';
            return;
        }

        if (!video.isConnected) {
            return;
        }

        video.dataset.ksmoDownloaderAttached = 'true';
        attachedVideos.add(video);

        const trigger = document.createElement('div');
        trigger.className = 'ksmo-dl-trigger';
        trigger.setAttribute('aria-label', 'Open video download options');
        trigger.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        `;

        const computedStyle = window.getComputedStyle(container);
        if (computedStyle.position === 'static') {
            container.style.position = 'relative';
        }

        container.appendChild(trigger);

        let hideTimeout;
        const showTrigger = () => {
            clearTimeout(hideTimeout);
            trigger.classList.add('visible');
        };

        const hideTrigger = () => {
            hideTimeout = setTimeout(() => {
                trigger.classList.remove('visible');
            }, 1200);
        };

        if (containerListeners.has(container)) {
            containerListeners.get(container).abort();
        }
        const abController = new AbortController();
        containerListeners.set(container, abController);

        container.addEventListener('mouseenter', showTrigger, { capture: true, passive: true, signal: abController.signal });
        container.addEventListener('mousemove', showTrigger, { capture: true, passive: true, signal: abController.signal });
        container.addEventListener('mouseleave', hideTrigger, { capture: true, passive: true, signal: abController.signal });
        container.addEventListener('touchstart', showTrigger, { capture: true, passive: true, signal: abController.signal });

        trigger.addEventListener('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            openDownloadModal();
        });
    }

    let activeModalBackdrop = null;

    function renderQualityRow(f, info, progressContainer, fill, percentText, list) {
        const row = document.createElement('div');
        row.className = 'ksmo-quality-row';

        const qInfo = document.createElement('div');
        qInfo.className = 'ksmo-quality-info';

        const name = document.createElement('div');
        name.className = 'ksmo-quality-name';
        name.textContent = f.quality;

        const label = document.createElement('div');
        label.className = 'ksmo-quality-label';
        label.textContent = f.label;

        qInfo.appendChild(name);
        qInfo.appendChild(label);

        if (!f.isSubtitle && f.url) {
            fetchFileSize(f.url, (bytes) => {
                if (bytes) {
                    label.textContent = `${f.label} • ${formatBytes(bytes)}`;
                }
            });
        }

        const dlBtn = document.createElement('button');
        dlBtn.className = 'ksmo-dl-btn';
        dlBtn.setAttribute('aria-label', `Download video format ${f.quality}`);
        dlBtn.textContent = 'Download';

        if (f.isHLS) {
            dlBtn.textContent = 'Fallback';
            dlBtn.addEventListener('click', () => {
                showToast('HLS Stream detected. Redirecting to fallback...', 3000);
                setTimeout(() => {
                    triggerFallbackRedirect(window.location.href);
                }, 1000);
            });
        } else {
            if (f.cipher && !f.url) {
                dlBtn.disabled = true;
                dlBtn.style.opacity = '0.5';
                dlBtn.textContent = 'Decrypting';
                
                let completed = false;
                const timeoutId = setTimeout(() => {
                    if (!completed) {
                        completed = true;
                        dlBtn.disabled = false;
                        dlBtn.style.opacity = '1';
                        dlBtn.textContent = 'Fallback';
                        dlBtn.addEventListener('click', () => {
                            showToast('YouTube signature cipher decryption failed. Redirecting to fallback...', 3000);
                            setTimeout(() => {
                                triggerFallbackRedirect(window.location.href);
                            }, 1000);
                        }, { once: true });
                        f.label = f.label.replace('(Deciphering...)', '(Cipher Blocked)');
                        label.textContent = f.label;
                    }
                }, 10000);

                decipherUrl(f.cipher, (decryptedUrl) => {
                    if (completed) return;
                    completed = true;
                    clearTimeout(timeoutId);
                    
                    if (decryptedUrl) {
                        f.url = decryptedUrl;
                        f.cipher = null;
                        dlBtn.disabled = false;
                        dlBtn.style.opacity = '1';
                        dlBtn.textContent = 'Download';
                        
                        const cleanLabel = f.label.replace(' (Deciphering...)', '');
                        f.label = cleanLabel;
                        label.textContent = cleanLabel;
                        
                        fetchFileSize(decryptedUrl, (bytes) => {
                            if (bytes) {
                                label.textContent = `${cleanLabel} • ${formatBytes(bytes)}`;
                            }
                        });
                    } else {
                        dlBtn.disabled = false;
                        dlBtn.style.opacity = '1';
                        dlBtn.textContent = 'Fallback';
                        dlBtn.addEventListener('click', () => {
                            showToast('YouTube signature cipher decryption failed. Redirecting to fallback...', 3000);
                            setTimeout(() => {
                                triggerFallbackRedirect(window.location.href);
                            }, 1000);
                        }, { once: true });
                        f.label = f.label.replace('(Deciphering...)', '(Cipher Blocked)');
                        label.textContent = f.label;
                    }
                });
            }

            dlBtn.addEventListener('click', () => {
                if (!f.url) return;
                progressContainer.style.display = 'flex';
                fill.style.width = '0%';
                percentText.textContent = '0%';

                const sanitizedTitle = info.title.replace(/[\\/:*?"<>|]/g, '_');
                
                if (f.isSubtitle) {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: f.url,
                        onload: (resp) => {
                            const srt = convertXmlToSrt(resp.responseText);
                            const blob = new Blob([srt], { type: 'text/plain' });
                            const blobUrl = URL.createObjectURL(blob);
                            const a = document.createElement('a');
                            a.href = blobUrl;
                            a.download = `${sanitizedTitle}_${f.quality}.srt`;
                            document.body.appendChild(a);
                            a.click();
                            a.remove();
                            URL.revokeObjectURL(blobUrl);
                            progressContainer.style.display = 'none';
                            showToast('Subtitles downloaded successfully');
                        },
                        onerror: () => {
                            progressContainer.style.display = 'none';
                            showToast('Subtitles download failed');
                        }
                    });
                    return;
                }

                const extension = f.type.includes('video') ? 'mp4' : 'mp3';
                const filename = `${sanitizedTitle}_${f.quality}.${extension}`;

                const pName = progressContainer.querySelector('.ksmo-progress-name');
                if (pName) {
                    pName.textContent = filename.length > 32 ? filename.substring(0, 30) + '...' : filename;
                }

                DownloadQueue.add(
                    f.url,
                    filename,
                    (percent) => {
                        fill.style.width = `${percent}%`;
                        percentText.textContent = `${percent}%`;
                    },
                    () => {
                        progressContainer.style.display = 'none';
                        showToast('Download completed successfully');
                    },
                    (err) => {
                        progressContainer.style.display = 'none';
                        showToast('Direct download failed. Click "Fallback" below to use external tool.', 5000);
                        if (getSetting(SETTINGS_KEYS.autoFallback, false)) {
                            if (confirm('Direct download failed. Redirect to external downloader service?')) {
                                triggerFallbackRedirect(window.location.href);
                            }
                        }
                    }
                );
            });
        }

        row.appendChild(qInfo);
        row.appendChild(dlBtn);
        list.appendChild(row);
    }

    const exportPlaylist = () => {
        const selectors = [
            'ytd-playlist-video-renderer a#video-title',
            'ytd-playlist-video-renderer a.yt-simple-endpoint',
            'ytd-playlist-panel-video-renderer a.yt-simple-endpoint',
            'a[href*="/watch?v="][href*="&list="]'
        ];
        
        let links = [];
        for (const selector of selectors) {
            const found = Array.from(document.querySelectorAll(selector))
                .map(a => a.href)
                .filter(href => href && href.includes('/watch?v=') && href.includes('&list='));
            if (found.length > 0) {
                links = found;
                break;
            }
        }
        
        const parsedLinks = links.map(href => {
            const url = new URL(href);
            return `https://www.youtube.com/watch?v=${url.searchParams.get('v')}`;
        });

        const uniqueLinks = [...new Set(parsedLinks)];
        if (uniqueLinks.length > 0) {
            const blob = new Blob([uniqueLinks.join('\n')], { type: 'text/plain' });
            const blobUrl = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = blobUrl;
            a.download = `playlist_${new URLSearchParams(window.location.search).get('list') || 'export'}.txt`;
            a.click();
            URL.revokeObjectURL(blobUrl);
            showToast(`Exported ${uniqueLinks.length} playlist links.`);
        } else {
            showToast('No playlist links found on page.');
        }
    };

    function openDownloadModal() {
        if (activeModalBackdrop) {
            activeModalBackdrop.remove();
        }

        const extractor = getExtractor();
        const info = extractor();

        const backdrop = document.createElement('div');
        backdrop.className = 'ksmo-modal-backdrop';

        const modal = document.createElement('div');
        modal.className = 'ksmo-modal';

        const header = document.createElement('div');
        header.className = 'ksmo-modal-header';
        
        const title = document.createElement('div');
        title.className = 'ksmo-modal-title';
        title.textContent = info ? info.title : (window.location.hostname.includes('spotify.com') ? 'Spotify Track' : 'Web Video');

        const headerActions = document.createElement('div');
        headerActions.className = 'ksmo-header-actions';

        const minBtn = document.createElement('button');
        minBtn.className = 'ksmo-modal-min';
        minBtn.setAttribute('aria-label', 'Minimize download manager');
        minBtn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>
        `;

        const closeBtn = document.createElement('button');
        closeBtn.className = 'ksmo-modal-close';
        closeBtn.setAttribute('aria-label', 'Close download manager');
        closeBtn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
        `;

        headerActions.appendChild(minBtn);
        headerActions.appendChild(closeBtn);
        header.appendChild(title);
        header.appendChild(headerActions);
        modal.appendChild(header);

        const progressContainer = document.createElement('div');
        progressContainer.className = 'ksmo-progress-container';
        progressContainer.innerHTML = `
            <div class="ksmo-progress-name" style="font-size: 11px; color: #a29bfe; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"></div>
            <div class="ksmo-progress-bar"><div class="ksmo-progress-fill"></div></div>
            <div class="ksmo-progress-text">
                <span class="ksmo-progress-percent">0%</span>
                <span class="ksmo-progress-cancel" style="cursor: pointer; text-decoration: underline;">Cancel</span>
            </div>
        `;
        modal.appendChild(progressContainer);

        const cancelBtn = progressContainer.querySelector('.ksmo-progress-cancel');
        const fill = progressContainer.querySelector('.ksmo-progress-fill');
        const percentText = progressContainer.querySelector('.ksmo-progress-percent');

        cancelBtn.addEventListener('click', () => {
            DownloadQueue.clear();
            progressContainer.style.display = 'none';
            showToast('All downloads canceled');
        });

        const list = document.createElement('div');
        list.className = 'ksmo-quality-list';

        if (info && info.formats && info.formats.length > 0) {
            info.formats.forEach(f => {
                renderQualityRow(f, info, progressContainer, fill, percentText, list);
            });
        } else if (window.location.hostname.includes('spotify.com')) {
            const spotifyMessage = document.createElement('div');
            spotifyMessage.style.textAlign = 'center';
            spotifyMessage.style.padding = '20px 0';
            spotifyMessage.style.color = '#a29bfe';
            spotifyMessage.textContent = 'Spotify tracks require external conversion. Directing to spotifydown.com below.';
            list.appendChild(spotifyMessage);
        } else {
            const emptyMessage = document.createElement('div');
            emptyMessage.style.textAlign = 'center';
            emptyMessage.style.padding = '20px 0';
            emptyMessage.style.color = '#94a3b8';
            emptyMessage.textContent = 'No direct download formats found. Use fallback tools below.';
            list.appendChild(emptyMessage);
        }

        modal.appendChild(list);

        if (window.location.hostname.includes('youtube.com')) {
            const urlParams = new URLSearchParams(window.location.search);
            let videoId = urlParams.get('v');
            if (!videoId && window.location.pathname.includes('/shorts/')) {
                const match = window.location.pathname.match(/\/shorts\/([a-zA-Z0-9_-]+)/);
                if (match) videoId = match[1];
            }
            if (!videoId && window.location.pathname.includes('/embed/')) {
                const match = window.location.pathname.match(/\/embed\/([a-zA-Z0-9_-]+)/);
                if (match) videoId = match[1];
            }

            if (videoId) {
                fetchEmbedPlayerResponse(videoId, (data) => {
                    if (!data || !data.streamingData) {
                        const hasProtected = Array.from(list.querySelectorAll('.ksmo-quality-row')).some(row => {
                            const lbl = row.querySelector('.ksmo-quality-label');
                            return lbl && (lbl.textContent.includes('Signature Protected') || lbl.textContent.includes('Cipher Blocked'));
                        });
                        if (hasProtected) {
                            showToast('YouTube is blocking direct downloads. Use fallback.', 6000);
                        }
                        return;
                    }
                    const embedFormats = [];
                    const muxed = data.streamingData.formats || [];
                    const adaptive = data.streamingData.adaptiveFormats || [];

                    const parseFormat = (f, isMuxed) => {
                        let url = f.url;
                        let cipher = null;
                        if (!url && f.signatureCipher) {
                            cipher = f.signatureCipher;
                        }
                        if (url || cipher) {
                            const isVideo = f.mimeType?.includes('video');
                            embedFormats.push({
                                quality: f.qualityLabel || `${f.width}p`,
                                url: url || '',
                                cipher: cipher,
                                type: f.mimeType?.split(';')[0] || 'video/mp4',
                                label: cipher ? (isMuxed ? `Muxed [${utils.getCodec(f.mimeType)}] (Deciphering...)` : (isVideo ? `Adaptive [${utils.getCodec(f.mimeType)}] (Deciphering...)` : `Audio Only [${utils.getCodec(f.mimeType)}] (Deciphering...)`)) : (isMuxed ? `Muxed [${utils.getCodec(f.mimeType)}]` : (isVideo ? `Adaptive [${utils.getCodec(f.mimeType)}] (No Audio)` : `Audio Only [${utils.getCodec(f.mimeType)}]`))
                            });
                        }
                    };

                    muxed.forEach(f => parseFormat(f, true));
                    adaptive.forEach(f => parseFormat(f, false));

                    if (embedFormats.length > 0) {
                        const existingRows = list.querySelectorAll('.ksmo-quality-row');
                        existingRows.forEach(row => {
                            const lbl = row.querySelector('.ksmo-quality-label');
                            if (lbl && (lbl.textContent.includes('Signature Protected') || lbl.textContent.includes('Cipher Blocked'))) {
                                row.remove();
                            }
                        });
                        embedFormats.forEach(f => {
                            renderQualityRow(f, info || { title: document.title }, progressContainer, fill, percentText, list);
                        });
                    } else {
                        const hasProtected = Array.from(list.querySelectorAll('.ksmo-quality-row')).some(row => {
                            const lbl = row.querySelector('.ksmo-quality-label');
                            return lbl && (lbl.textContent.includes('Signature Protected') || lbl.textContent.includes('Cipher Blocked'));
                        });
                        if (hasProtected) {
                            showToast('YouTube is blocking direct downloads. Use fallback.', 6000);
                        }
                    }
                });
            }
        }

        const warningLabel = document.createElement('div');
        warningLabel.className = 'ksmo-warning-label';
        warningLabel.textContent = '⚠️ External services may contain ads and are not controlled by this script.';
        modal.appendChild(warningLabel);

        const footer = document.createElement('div');
        footer.className = 'ksmo-modal-footer';

        const footerRow = document.createElement('div');
        footerRow.className = 'ksmo-footer-row';

        const serviceSelect = document.createElement('select');
        serviceSelect.className = 'ksmo-select';
        serviceSelect.setAttribute('aria-label', 'Select fallback service');

        const available = getAvailableFallbacks();
        const saved = getSetting(getFallbackServiceSettingKey(), available[0]);
        const defaultService = available.includes(saved) ? saved : available[0];

        available.forEach(service => {
            const opt = document.createElement('option');
            opt.value = service;
            opt.textContent = service.toUpperCase();
            if (service === defaultService) {
                opt.selected = true;
            }
            serviceSelect.appendChild(opt);
        });

        serviceSelect.addEventListener('change', () => {
            setSetting(getFallbackServiceSettingKey(), serviceSelect.value);
        });

        const fallbackBtn = document.createElement('button');
        fallbackBtn.className = 'ksmo-secondary-btn';
        fallbackBtn.setAttribute('aria-label', 'Use external download service');
        fallbackBtn.textContent = 'Fallback';

        fallbackBtn.addEventListener('click', () => {
            if (confirm('Redirect to external downloader service?')) {
                triggerFallbackRedirect(window.location.href);
            }
        });

        const copyLinkBtn = document.createElement('button');
        copyLinkBtn.className = 'ksmo-secondary-btn';
        copyLinkBtn.setAttribute('aria-label', 'Copy download link to clipboard');
        copyLinkBtn.textContent = 'Copy Link';

        copyLinkBtn.addEventListener('click', () => {
            const link = info && info.formats && info.formats.length > 0 ? info.formats[0].url : window.location.href;
            navigator.clipboard.writeText(link).then(() => {
                showToast('Link copied to clipboard');
            }).catch(() => {
                showToast('Failed to copy link');
            });
        });

        footerRow.appendChild(serviceSelect);
        footerRow.appendChild(fallbackBtn);
        footerRow.appendChild(copyLinkBtn);
        
        if (window.location.hostname.includes('youtube.com') && window.location.search.includes('list=')) {
            const playlistBtn = document.createElement('button');
            playlistBtn.className = 'ksmo-secondary-btn';
            playlistBtn.textContent = 'Export Playlist';
            playlistBtn.addEventListener('click', exportPlaylist);
            footerRow.appendChild(playlistBtn);
        }

        footer.appendChild(footerRow);
        modal.appendChild(footer);

        backdrop.appendChild(modal);
        document.body.appendChild(backdrop);
        activeModalBackdrop = backdrop;

        setTimeout(() => backdrop.classList.add('show'), 10);

        const dismissModal = () => {
            backdrop.classList.remove('show');
            setTimeout(() => {
                DownloadQueue.clear();
                backdrop.remove();
                activeModalBackdrop = null;
            }, 300);
        };

        minBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            if (backdrop.classList.contains('minimized')) {
                backdrop.classList.remove('minimized');
                minBtn.innerHTML = `
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>
                `;
            } else {
                backdrop.classList.add('minimized');
                minBtn.innerHTML = `
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="12" x2="20" y2="12"/><line x1="12" y1="4" x2="12" y2="20"/></svg>
                `;
            }
        });

        modal.addEventListener('click', (e) => {
            if (backdrop.classList.contains('minimized') && !e.target.closest('.ksmo-modal-close') && !e.target.closest('.ksmo-modal-min') && !e.target.closest('.ksmo-progress-cancel')) {
                backdrop.classList.remove('minimized');
                minBtn.innerHTML = `
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>
                `;
            }
        });

        closeBtn.addEventListener('click', dismissModal);
        backdrop.addEventListener('click', (e) => {
            if (e.target === backdrop) dismissModal();
        });
    }

    function triggerFallbackRedirect(targetUrl) {
        const available = getAvailableFallbacks();
        const key = getFallbackServiceSettingKey();
        const saved = getSetting(key, available[0]);
        const service = available.includes(saved) ? saved : available[0];
        const resolver = FALLBACK_SERVICES[service] || FALLBACK_SERVICES[available[0]];
        const finalUrl = resolver(targetUrl);
        if (typeof GM_openInTab !== 'undefined') {
            GM_openInTab(finalUrl, { active: true });
        } else {
            window.open(finalUrl, '_blank');
        }
    }

    let visited = new WeakSet();
    let isScanning = false;

    function scanAndAttach(targetNode = document.body) {
        if (isScanning) return;
        isScanning = true;

        let foundVideo = false;

        try {
            const nodesToProcess = Array.isArray(targetNode) ? targetNode : [targetNode];
            const queue = [...nodesToProcess];

            while (queue.length > 0) {
                const node = queue.shift();
                if (!node || visited.has(node)) continue;
                visited.add(node);

                if (typeof node.querySelectorAll === 'function') {
                    const videos = node.querySelectorAll('video');
                    videos.forEach(video => {
                        const area = video.offsetWidth * video.offsetHeight;
                        if ((video.offsetWidth > 50 && video.offsetHeight > 50) || video.readyState > 0 || area > 2500) {
                            foundVideo = true;
                            if (!window.location.hostname.includes('youtube.com')) {
                                attachTriggerButton(video);
                            }
                        }
                    });

                    const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
                    while (walker.nextNode()) {
                        const el = walker.currentNode;
                        if (el.shadowRoot && !visited.has(el.shadowRoot)) {
                            queue.push(el.shadowRoot);
                        }
                    }
                }
            }

            if (window.location.hostname.includes('youtube.com')) {
                injectYouTubeControlButton();
                nodesToProcess.forEach(n => {
                    if (typeof n.querySelectorAll === 'function') {
                        YOUTUBE_PLAYER_SELECTORS.forEach(selector => {
                            try {
                                const videos = n.querySelectorAll(selector);
                                if (videos.length > 0) foundVideo = true;
                            } catch (e) {}
                        });
                    }
                });
            }
        } finally {
            isScanning = false;
        }

        if (KNOWN_VIDEO_SITES.some(s => window.location.hostname.includes(s))) {
            foundVideo = true;
        }

        const fab = document.getElementById('ksmo-floating-dl');
        if (fab) {
            if (foundVideo) {
                fab.classList.add('visible');
            } else {
                fab.classList.remove('visible');
            }
        }
    }

    let pendingNodes = [];
    let scanTimeout = null;

    const scanObserver = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    pendingNodes.push(node);
                }
            });
        });

        if (pendingNodes.length > 0) {
            clearTimeout(scanTimeout);
            scanTimeout = setTimeout(() => {
                scanAndAttach(pendingNodes);
                pendingNodes = [];
            }, 100);
        }
    });

    let idleScanInterval = null;
    let visitedResetInterval = null;

    const createFloatingButton = () => {
        if (window.top !== window.self) return;
        if (document.getElementById('ksmo-floating-dl')) return;
        if (getSetting(SETTINGS_KEYS.showButton, true) === false) return;
        
        const fab = document.createElement('div');
        fab.id = 'ksmo-floating-dl';
        fab.className = 'ksmo-floating-dl';
        fab.setAttribute('aria-label', 'Open global video download options');
        fab.title = 'Download Video (Ctrl+Alt+Shift+D)';
        fab.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        `;
        fab.addEventListener('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            openDownloadModal();
        });
        document.body.appendChild(fab);
    };

    const initApp = () => {
        createFloatingButton();
        scanAndAttach(document.body);
        scanObserver.observe(document.body, { childList: true, subtree: true });

        if (idleScanInterval) clearInterval(idleScanInterval);
        idleScanInterval = setInterval(() => {
            if (window.requestIdleCallback) {
                window.requestIdleCallback(() => scanAndAttach(document.body));
            } else {
                scanAndAttach(document.body);
            }
        }, 300000);

        if (visitedResetInterval) clearInterval(visitedResetInterval);
        visitedResetInterval = setInterval(() => {
            visited = new WeakSet();
        }, 300000);
    };

    if (!document.body) {
        new MutationObserver((_, obs) => {
            if (document.body) {
                initApp();
                obs.disconnect();
            }
        }).observe(document.documentElement, { childList: true });
    } else {
        initApp();
    }

    window.addEventListener('keydown', (e) => {
        if (getSetting(SETTINGS_KEYS.enableHotkey, true) === false) return;
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
            return;
        }

        const isD = e.key === 'd' || e.key === 'D' || e.keyCode === 68;
        if (e.ctrlKey && e.altKey && e.shiftKey && isD) {
            e.preventDefault();
            e.stopPropagation();

            const video = findAnyVideo();
            if (!video && !window.location.hostname.includes('spotify.com')) {
                showToast('No video found on this page.');
                return;
            }
            openDownloadModal();
        }

        if (e.key === 'Escape' && activeModalBackdrop) {
            e.preventDefault();
            e.stopPropagation();
            activeModalBackdrop.classList.remove('show');
            setTimeout(() => {
                DownloadQueue.clear();
                activeModalBackdrop?.remove();
                activeModalBackdrop = null;
            }, 300);
        }
    }, true);

    if (typeof GM_registerMenuCommand !== 'undefined') {
        GM_registerMenuCommand('Toggle Floating Button UI', () => {
            const current = getSetting(SETTINGS_KEYS.showButton, true);
            setSetting(SETTINGS_KEYS.showButton, !current);
            showToast(`Floating button ${!current ? 'enabled' : 'disabled'}. Reload the page.`);
        });
        GM_registerMenuCommand('Toggle Auto-Fallback', () => {
            const current = getSetting(SETTINGS_KEYS.autoFallback, false);
            setSetting(SETTINGS_KEYS.autoFallback, !current);
            showToast(`Auto-fallback ${!current ? 'enabled' : 'disabled'}.`);
        });
        GM_registerMenuCommand('Toggle Hotkey (Ctrl+Alt+Shift+D - conflicts with Chrome)', () => {
            const current = getSetting(SETTINGS_KEYS.enableHotkey, true);
            setSetting(SETTINGS_KEYS.enableHotkey, !current);
            showToast(`Keyboard shortcut ${!current ? 'enabled' : 'disabled'}.`);
        });
        GM_registerMenuCommand('Set Custom CORS Proxy', () => {
            const current = getSetting(SETTINGS_KEYS.customProxy, '');
            const proxy = prompt('Enter custom CORS proxy URL prefix (e.g. https://myproxy.com/?url=) or leave empty to clear:', current);
            if (proxy !== null) {
                setSetting(SETTINGS_KEYS.customProxy, proxy.trim());
                showToast(proxy.trim() ? 'Custom CORS proxy configured.' : 'Custom CORS proxy cleared.');
            }
        });
    }

    window.addEventListener('beforeunload', () => {
        scanObserver.disconnect();
        if (idleScanInterval) clearInterval(idleScanInterval);
        if (visitedResetInterval) clearInterval(visitedResetInterval);
        visited = new WeakSet();
    });
})();