Telegram +

Видео, истории и скачивание файлов и другие функции ↴

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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

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

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

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

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

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

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name                Telegram +
// @name:en             Telegram +
// @namespace           by
// @version             2.0
// @author              diorhc
// @description         Видео, истории и скачивание файлов и другие функции ↴
// @description:ar      فيديو، قصص وميزات تنزيل الملفات وغيرها ↴
// @description:az      Video, hekayələr və fayl yükləmə xüsusiyyətləri ↴
// @description:be      Видео, истории и скачивание файлов и другие функции ↴
// @description:bg      Видео, истории и скачивание файлов и другие функции ↴
// @description:zh-CN   视频、故事和文件下载等功能 ↴
// @description:de      Video-, Geschichten- und Dateidownload-Funktionen ↴
// @description:nl      Video-, verhalen- en bestandsdownloadfuncties ↴
// @description:en      Video, stories and file download features ↴
// @description:es      Video, historias y descarga de archivos ↴
// @description:fr      Vidéo, histoires et téléchargement de fichiers ↴
// @description:hi      वीडियो, कहानियाँ और फ़ाइल डाउनलोड सुविधाएँ ↴
// @description:id      Video, cerita, dan fitur unduh file ↴
// @description:it      Video, storie e download di file ↴
// @description:ja      ビデオ、ストーリー、ファイルのダウンロード機能 ↴
// @description:kk      Бейне, әңгімелер және файлдарды жүктеу мүмкіндіктері ↴
// @description:ko      비디오, 스토리 및 파일 다운로드 기능 ↴
// @description:ky      Видео, окуялар жана файлдарды жүктөө мүмкүнчүлүктөрү ↴
// @description:pl      Wideo, historie i funkcje pobierania plików ↴
// @description:pt      Vídeo, histórias e funcionalidades de download de arquivos ↴
// @description:tr      Video, hikayeler ve dosya indirme özellikleri ↴
// @description:zh-TW   視頻、故事和文件下載功能 ↴
// @description:uk      Відео, історії та функції завантаження файлів ↴
// @description:uz      Video, hikoyalar va fayl yuklab olish funksiyalari ↴
// @description:vi      Video, câu chuyện và các tính năng tải xuống tệp ↴
// @run-at              document-idle
// @match               https://web.telegram.org/*
// @match               https://webk.telegram.org/*
// @match               https://webz.telegram.org/*
// @icon                https://www.google.com/s2/favicons?sz=64&domain=telegram.org
// @license             MIT
// @grant               unsafeWindow
// @grant               GM_setValue
// @grant               GM_getValue
// @grant               GM_download
// @homepageURL         https://github.com/diorhc/TGP
// @supportURL          https://github.com/diorhc/TGP/discussions
// ==/UserScript==

var PAGE_WINDOW = (() => {
  try {
    if (typeof unsafeWindow < "u" && unsafeWindow)
      return unsafeWindow.wrappedJSObject || unsafeWindow;
  } catch {
  }
  return window;
})();
(() => {
  "use strict";
  const START_FLAG = "__TELEGRAM_PLUS_STARTED__";
  if (window[START_FLAG]) {
    console.warn("[Tel Download] Script already initialized");
    return;
  }
  window[START_FLAG] = !0;
  const hashCode = (text) => {
    let hash = 0;
    for (let index = 0; index < text.length; index += 1)
      hash = (hash << 5) - hash + text.charCodeAt(index), hash |= 0;
    return hash >>> 0;
  }, DANGEROUS_JSON_KEYS =  new Set(["__proto__", "constructor", "prototype"]), safeJsonReviver = (key, value) => {
    if (!DANGEROUS_JSON_KEYS.has(key))
      return value;
  }, safeJsonParse = (raw, fallback = null) => {
    if (typeof raw != "string") return fallback;
    try {
      return JSON.parse(raw, safeJsonReviver);
    } catch {
      return fallback;
    }
  }, isPlainObject = (value) => {
    if (!value || typeof value != "object" || Array.isArray(value)) return !1;
    const prototype = Object.getPrototypeOf(value);
    return prototype === Object.prototype || prototype === null;
  }, SUPPORTED_MIMES = {
    video: {
      "video/mp4": "mp4",
      "video/webm": "webm",
      "video/ogg": "ogv",
      "video/quicktime": "mov",
      "video/x-matroska": "mkv",
      "video/mpeg": "mpeg",
      "video/x-msvideo": "avi"
    },
    audio: {
      "audio/ogg": "ogg",
      "audio/mpeg": "mp3",
      "audio/mp4": "m4a",
      "audio/x-m4a": "m4a",
      "audio/wav": "wav",
      "audio/webm": "weba",
      "audio/aac": "aac",
      "audio/flac": "flac"
    },
    image: {
      "image/jpeg": "jpg",
      "image/png": "png",
      "image/gif": "gif",
      "image/webp": "webp",
      "image/svg+xml": "svg",
      "image/bmp": "bmp"
    }
  }, DEFAULT_SETTINGS = {
    enableNotifications: !0,
    enableKeyboardShortcuts: !0,
    // Conservative default: ad blocking can interfere with Telegram's virtual DOM updates.
    enableAdBlocking: !1,
    enableExperimentalStreamCapture: !1,
    autoDownloadQuality: "original",
    downloadLocation: "browser",
    uiLanguage: "auto"
  }, ALLOWED_DOWNLOAD_LOCATIONS =  new Set(["browser", "picker", "tab", "auto"]), ALLOWED_UI_LANGUAGES =  new Set([
    "auto",
    "ar",
    "az",
    "be",
    "bg",
    "cn",
    "de",
    "du",
    "en",
    "es",
    "fr",
    "hi",
    "id",
    "it",
    "jp",
    "kk",
    "kr",
    "ky",
    "pl",
    "pt",
    "ru",
    "tr",
    "tw",
    "uk",
    "uz",
    "vi"
  ]), DEFAULT_LOCALE_ALIAS_MAP = {
    ko: "kr",
    ja: "jp",
    zh: "cn",
    "zh-cn": "cn",
    "zh-sg": "cn",
    "zh-tw": "tw",
    "zh-hk": "tw",
    nl: "du"
  }, normalizeSettings = (rawSettings = {}) => {
    const candidate = { ...DEFAULT_SETTINGS, ...rawSettings };
    return {
      ...candidate,
      enableNotifications: !!candidate.enableNotifications,
      enableKeyboardShortcuts: !!candidate.enableKeyboardShortcuts,
      enableAdBlocking: !!candidate.enableAdBlocking,
      enableExperimentalStreamCapture: !!candidate.enableExperimentalStreamCapture,
      downloadLocation: ALLOWED_DOWNLOAD_LOCATIONS.has(candidate.downloadLocation) ? candidate.downloadLocation : DEFAULT_SETTINGS.downloadLocation,
      uiLanguage: ALLOWED_UI_LANGUAGES.has(String(candidate.uiLanguage || "").toLowerCase()) ? String(candidate.uiLanguage).toLowerCase() : DEFAULT_SETTINGS.uiLanguage
    };
  }, getExtensionFromMime = (mimeType, defaultExt = "bin") => {
    const mime = String(mimeType || "").split(";")[0].trim().toLowerCase();
    for (const category of Object.values(SUPPORTED_MIMES))
      if (category[mime]) return category[mime];
    const parts = mime.split("/");
    return parts.length === 2 && parts[1] ? parts[1].replace(/^x-/, "") : defaultExt;
  }, isValidMimeType = (mimeType, category) => {
    const mime = String(mimeType || "").split(";")[0].trim().toLowerCase();
    return SUPPORTED_MIMES[category] && SUPPORTED_MIMES[category][mime] ? !0 : mime.startsWith(`${category}/`);
  }, extractFileName = (url, defaultExt = "mp4") => {
    try {
      const metadata = JSON.parse(decodeURIComponent(url.split("/").pop()));
      if (metadata.fileName) return metadata.fileName;
    } catch {
    }
    try {
      const lastPart = new URL(url).pathname.split("/").pop();
      if (lastPart && lastPart.includes("."))
        try {
          return decodeURIComponent(lastPart);
        } catch {
          return lastPart;
        }
    } catch {
    }
    return `telegram_${( new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}_${hashCode(url).toString(36)}.${defaultExt}`;
  }, shouldRetryDownloadError = (error) => {
    if (!error) return !1;
    const name = String(error.name || ""), message = String(error.message || "").toLowerCase();
    return name === "AbortError" ? !1 : name === "TypeError" ? message.includes("failed to fetch") || message.includes("load failed") || message.includes("network") : !!(name === "NetworkError" || message.includes("unexpected response: 429") || /unexpected response: 5\d\d/.test(message));
  }, getRetryDelayMs = (attempt, baseDelay = 1e3, maxDelay = 3e4) => {
    const normalizedAttempt = Number.isFinite(attempt) ? Math.max(1, Math.floor(attempt)) : 1, normalizedBase = Number.isFinite(baseDelay) ? Math.max(0, baseDelay) : 1e3, normalizedMax = Number.isFinite(maxDelay) ? Math.max(0, maxDelay) : 3e4, delay = normalizedBase * Math.pow(2, normalizedAttempt - 1);
    return Math.min(delay, normalizedMax);
  }, getRetryPlan = ({
    retryCount,
    error,
    maxRetries = 3,
    baseDelay = 1e3,
    maxDelay = 3e4
  } = {}) => {
    const nextRetryCount = (Number.isFinite(retryCount) ? retryCount : 0) + 1, normalizedMaxRetries = Number.isFinite(maxRetries) ? Math.max(0, maxRetries) : 3;
    return !shouldRetryDownloadError(error) || nextRetryCount > normalizedMaxRetries ? {
      action: "fail",
      retryCount: nextRetryCount,
      delay: 0
    } : {
      action: "retry",
      retryCount: nextRetryCount,
      delay: getRetryDelayMs(nextRetryCount, baseDelay, maxDelay)
    };
  }, getCancelPlan = ({ inPendingQueue = !1, hasAbortController = !1 } = {}) => inPendingQueue ? "remove-pending" : hasAbortController ? "abort-active" : "noop", canManualRetry = (status) => !["queued", "active", "retrying"].includes(status), getQueueSlots = (activeCount, maxActive = 2) => {
    const current = Number.isFinite(activeCount) ? Math.max(0, activeCount) : 0, limit = Number.isFinite(maxActive) ? Math.max(0, maxActive) : 0;
    return Math.max(0, limit - current);
  }, planPendingSelection = (pendingStatuses, slots) => {
    const selectedIndexes = [], maxToTake = Number.isFinite(slots) ? Math.max(0, Math.floor(slots)) : 0;
    if (!Array.isArray(pendingStatuses) || maxToTake === 0) return selectedIndexes;
    for (let index = 0; index < pendingStatuses.length && !(selectedIndexes.length >= maxToTake); index += 1)
      pendingStatuses[index] === "queued" && selectedIndexes.push(index);
    return selectedIndexes;
  }, getQueueActivationIndexes = ({ pendingStatuses, activeCount, maxActive = 2 } = {}) => {
    const slots = getQueueSlots(activeCount, maxActive);
    return slots === 0 ? [] : planPendingSelection(pendingStatuses, slots);
  }, detectBrowserLocaleCode = ({
    browserLocale,
    dictionary,
    aliasMap = DEFAULT_LOCALE_ALIAS_MAP,
    fallbackLocale = "en"
  } = {}) => {
    const raw = String(browserLocale || "").toLowerCase(), exact = aliasMap[raw];
    if (exact) return exact;
    const base = raw.split(/[-_]/)[0];
    return aliasMap[base] || (dictionary?.[base] ? base : fallbackLocale);
  }, normalizeLocaleCode = ({
    requestedLocale,
    browserLocale,
    dictionary,
    aliasMap = DEFAULT_LOCALE_ALIAS_MAP,
    fallbackLocale = "en"
  } = {}) => {
    const requested = String(requestedLocale || "").toLowerCase();
    if (!requested || requested === "auto")
      return detectBrowserLocaleCode({
        browserLocale,
        dictionary,
        aliasMap,
        fallbackLocale
      });
    const mapped = aliasMap[requested] || requested;
    if (dictionary?.[mapped]) return mapped;
    const base = mapped.split(/[-_]/)[0];
    return dictionary?.[base] ? base : fallbackLocale;
  }, translateDictionaryKey = ({
    dictionary,
    locale,
    key,
    fallback = "",
    fallbackLocale = "en"
  } = {}) => key ? dictionary?.[locale]?.[key] ?? dictionary?.[fallbackLocale]?.[key] ?? fallback : fallback, logger = {
    info: (message, file = "") => console.log(`[Tel Download]${file ? ` ${file}:` : ""} ${message}`),
    error: (message, file = "") => console.error(`[Tel Download]${file ? ` ${file}:` : ""} ${message}`),
    warn: (message, file = "") => console.warn(`[Tel Download]${file ? ` ${file}:` : ""} ${message}`)
  }, DOWNLOAD_ICON = "", FORWARD_ICON = "", contentRangeRegex = /^bytes (\d+)-(\d+)\/(\d+)$/, REFRESH_DELAY = 500, MAX_NOTIFICATIONS = 5, MAX_ACTIVE_DOWNLOADS = 2, MAX_RETRIES = 3, RETRY_DELAY_BASE = 1e3, EAGER_DOWNLOAD_LIMIT = 25 * 1024 * 1024, SETTINGS_KEY = "tel_downloader_settings", SETTINGS_SCHEMA_VERSION = 2, PROGRESS_KEY = "tg_video_progress", PROGRESS_CONTAINER_ID = "tel-downloader-progress-bar-container", TELEGRAM_SELECTORS = Object.freeze({
    webKMediaViewer: ".media-viewer-whole",
    webZMediaViewerRoot: "#MediaViewer",
    webZActiveSlide: "#MediaViewer .MediaViewerSlide--active",
    webZActions: "#MediaViewer .MediaViewerActions",
    webZMessageMeta: "#MediaViewer .MessageMeta",
    webZProfileInfo: "#MediaViewer .ProfileInfo",
    storiesViewer: "#stories-viewer",
    storyViewer: "#StoryViewer"
  }), IS_SAFARI = (() => {
    const userAgent = navigator.userAgent;
    return /Safari\//.test(userAgent) && !/Chrome|Chromium|CriOS|Edg|OPR|YaBrowser|SamsungBrowser/.test(userAgent);
  })(), storageAdapter =  (() => {
    const memory =  new Map(), getFromLocalStorage = (key) => {
      try {
        return typeof localStorage > "u" ? null : localStorage.getItem(key);
      } catch {
        return null;
      }
    }, setToLocalStorage = (key, value) => {
      try {
        return typeof localStorage > "u" ? !1 : (localStorage.setItem(key, value), !0);
      } catch {
        return !1;
      }
    }, getFromGM = (key) => {
      try {
        if (typeof GM_getValue != "function") return null;
        const value = GM_getValue(key, null);
        return value === null || typeof value > "u" ? null : String(value);
      } catch {
        return null;
      }
    }, setToGM = (key, value) => {
      try {
        return typeof GM_setValue != "function" ? !1 : (GM_setValue(key, value), !0);
      } catch {
        return !1;
      }
    }, getFromMemory = (key) => memory.has(key) ? memory.get(key) : null, setToMemory = (key, value) => (memory.set(key, value), !0);
    return {
      getItem(key) {
        const localValue = getFromLocalStorage(key);
        if (localValue !== null) return localValue;
        const gmValue = getFromGM(key);
        return gmValue !== null ? gmValue : getFromMemory(key);
      },
      setItem(key, value) {
        return setToLocalStorage(key, value) ? "localStorage" : setToGM(key, value) ? "gm" : (setToMemory(key, value), "memory");
      },
      detectAvailable() {
        if (setToLocalStorage("__tel_probe__", "1")) {
          try {
            localStorage.removeItem("__tel_probe__");
          } catch {
          }
          return "localStorage";
        }
        return typeof GM_getValue == "function" && typeof GM_setValue == "function" ? "gm" : "memory";
      }
    };
  })(), storageCache = (() => {
    let _settings = null, _progress = null, _flushTimer = null;
    const _dirty =  new Set(), flushNow = () => {
      if (_dirty.has("settings") && _settings !== null) {
        try {
          storageAdapter.setItem(SETTINGS_KEY, JSON.stringify(_settings));
        } catch (e) {
          logger.error(`storageCache flush settings: ${e.message}`);
          return;
        }
        _dirty.delete("settings");
      }
      if (_dirty.has("progress") && _progress !== null) {
        try {
          storageAdapter.setItem(PROGRESS_KEY, JSON.stringify(_progress));
        } catch (e) {
          logger.error(`storageCache flush progress: ${e.message}`);
          return;
        }
        _dirty.delete("progress");
      }
      _flushTimer = null;
    }, scheduleFlush = () => {
      _flushTimer || (_flushTimer = setTimeout(flushNow, 2e3));
    };
    return window.addEventListener("beforeunload", flushNow), {
      getSettings() {
        if (_settings !== null) return _settings;
        try {
          const raw = storageAdapter.getItem(SETTINGS_KEY);
          if (!raw)
            return _settings = { ...DEFAULT_SETTINGS, _schemaVersion: SETTINGS_SCHEMA_VERSION }, _settings;
          const parsed = safeJsonParse(raw, null);
          if (!isPlainObject(parsed))
            return _settings = { ...DEFAULT_SETTINGS, _schemaVersion: SETTINGS_SCHEMA_VERSION }, _settings;
          const normalized = normalizeSettings(parsed);
          Number(parsed?._schemaVersion || 0) < SETTINGS_SCHEMA_VERSION && (normalized.enableAdBlocking = !1, normalized._schemaVersion = SETTINGS_SCHEMA_VERSION, storageAdapter.setItem(SETTINGS_KEY, JSON.stringify(normalized)), logger.info("Settings migrated to safe defaults")), _settings = normalized;
        } catch {
          _settings = { ...DEFAULT_SETTINGS, _schemaVersion: SETTINGS_SCHEMA_VERSION };
        }
        return _settings;
      },
      setSettings(s) {
        _settings = normalizeSettings(s), _dirty.add("settings"), scheduleFlush();
      },
      getProgress() {
        if (_progress !== null) return _progress;
        try {
          const raw = storageAdapter.getItem(PROGRESS_KEY), parsed = raw ? safeJsonParse(raw, {}) : {};
          _progress = isPlainObject(parsed) ? parsed : {};
        } catch {
          _progress = {};
        }
        return _progress;
      },
      setProgress(p) {
        _progress = p, _dirty.add("progress"), scheduleFlush();
      },
      flush: flushNow
    };
  })();
  function loadSettings() {
    return storageCache.getSettings();
  }
  function saveSettings(settings) {
    storageCache.setSettings(settings), logger.info("Settings saved");
  }
  const state = {
    settings: loadSettings(),
    currentHref: location.href,
    activeVideoBindings:  new Map(),
    themeIsDark: document.documentElement.classList.contains("night") || document.documentElement.classList.contains("theme-dark")
  };
  logger.info(`Storage backend detected: ${storageAdapter.detectAvailable()}`);
  const i18n = (() => {
    const remoteLocalesBaseUrl = "https://raw.githubusercontent.com/diorhc/TGP/main/locales", localeCachePrefix = "tel_i18n_cache_v1_", localeLabels = {
      auto: "Auto",
      ar: "العربية",
      az: "Azərbaycanca",
      be: "Беларуская",
      bg: "Български",
      cn: "简体中文",
      de: "Deutsch",
      du: "Nederlands",
      en: "English",
      es: "Español",
      fr: "Français",
      hi: "हिन्दी",
      id: "Bahasa Indonesia",
      it: "Italiano",
      jp: "日本語",
      kk: "Қазақша",
      kr: "한국어",
      ky: "Кыргызча",
      pl: "Polski",
      pt: "Português",
      ru: "Русский",
      tr: "Türkçe",
      tw: "繁體中文",
      uk: "Українська",
      uz: "Oʻzbekcha",
      vi: "Tiếng Việt"
    }, supportedLocaleCodes = Object.keys(localeLabels).filter((code) => code !== "auto"), knownLocales = supportedLocaleCodes.reduce((acc, code) => (acc[code] = {}, acc), {}), aliasMap = DEFAULT_LOCALE_ALIAS_MAP, dictionary = {
      ...knownLocales
    }, loadedLocales = new Set(
      Object.entries(dictionary).filter(([, value]) => isPlainObject(value) && Object.keys(value).length > 0).map(([code]) => code)
    ), inFlightLocaleLoads =  new Map(), canUseStorage = () => {
      try {
        return typeof localStorage < "u";
      } catch {
        return !1;
      }
    }, readLocaleCache = (code) => {
      if (!canUseStorage()) return null;
      const cacheKey = `${localeCachePrefix}${code}`, cached = safeJsonParse(localStorage.getItem(cacheKey), null);
      return !isPlainObject(cached) || !isPlainObject(cached.data) || Date.now() - Number(cached.updatedAt || 0) > 6048e5 ? null : cached.data;
    }, writeLocaleCache = (code, data) => {
      if (!canUseStorage() || !isPlainObject(data)) return;
      const cacheKey = `${localeCachePrefix}${code}`;
      try {
        localStorage.setItem(
          cacheKey,
          JSON.stringify({
            updatedAt: Date.now(),
            data
          })
        );
      } catch {
      }
    }, mergeLocaleDictionary = (code, data) => isPlainObject(data) ? (dictionary[code] = {
      ...isPlainObject(dictionary[code]) ? dictionary[code] : {},
      ...data
    }, loadedLocales.add(code), !0) : !1, fetchLocaleFromGithub = async (code) => {
      const response = await fetch(`${remoteLocalesBaseUrl}/${encodeURIComponent(code)}.json`, {
        cache: "no-store"
      });
      if (!response.ok)
        throw new Error(`HTTP ${response.status}`);
      const payload = await response.json();
      if (!isPlainObject(payload))
        throw new Error("Locale payload is not an object");
      return payload;
    }, loadLocaleDictionary = async (code, { forceRemote = !1 } = {}) => {
      const normalizedCode = String(code || "").toLowerCase();
      if (!knownLocales[normalizedCode]) return !1;
      if (!forceRemote && loadedLocales.has(normalizedCode)) return !0;
      const existingRequest = inFlightLocaleLoads.get(normalizedCode);
      if (existingRequest) return existingRequest;
      const request = (async () => {
        try {
          if (!forceRemote) {
            const cached = readLocaleCache(normalizedCode);
            if (cached && mergeLocaleDictionary(normalizedCode, cached))
              return !0;
          }
          const remoteData = await fetchLocaleFromGithub(normalizedCode), merged = mergeLocaleDictionary(normalizedCode, remoteData);
          return merged && writeLocaleCache(normalizedCode, remoteData), merged;
        } catch (error) {
          return typeof logger < "u" && logger.warn(`[i18n] failed to load '${normalizedCode}' from GitHub: ${error.message}`), !1;
        } finally {
          inFlightLocaleLoads.delete(normalizedCode);
        }
      })();
      return inFlightLocaleLoads.set(normalizedCode, request), request;
    };
    let locale = "en", initPromise = null;
    const detectBrowserLocale = () => detectBrowserLocaleCode({
      browserLocale: navigator.language || navigator.userLanguage,
      dictionary: knownLocales,
      aliasMap,
      fallbackLocale: "en"
    }), normalizeLocale = (value) => normalizeLocaleCode({
      requestedLocale: value,
      browserLocale: navigator.language || navigator.userLanguage,
      dictionary: knownLocales,
      aliasMap,
      fallbackLocale: "en"
    }), ensureLocaleLoaded = async (targetLocale) => {
      await loadLocaleDictionary("en"), targetLocale !== "en" && await loadLocaleDictionary(targetLocale);
    }, init2 = () => initPromise || (initPromise = ensureLocaleLoaded(locale).then(() => locale), initPromise), setLocale = (nextLocale) => (locale = normalizeLocale(nextLocale), ensureLocaleLoaded(locale), isDevMode() && validateLocale(locale), locale), getLocale = () => locale, t = (key, fallback = "") => translateDictionaryKey({
      dictionary,
      locale,
      key,
      fallback,
      fallbackLocale: "en"
    }), getLanguageOptions = () => ["auto", ...supportedLocaleCodes.slice().sort()].map((code) => ({
      value: code,
      label: localeLabels[code] || code
    })), isDevMode = () => {
      try {
        return localStorage.getItem("tel_devmode") === "1";
      } catch {
        return !1;
      }
    }, validateLocale = (targetLocale) => {
      const base = dictionary.en, current = dictionary[targetLocale];
      if (!base || !current || typeof logger > "u") return;
      const missingKeys = Object.keys(base).filter((key) => !(key in current)), extraKeys = Object.keys(current).filter((key) => !(key in base));
      missingKeys.length > 0 && logger.warn(`[i18n] Locale '${targetLocale}' is missing keys: ${missingKeys.join(", ")}`), extraKeys.length > 0 && logger.warn(`[i18n] Locale '${targetLocale}' has extra keys: ${extraKeys.join(", ")}`);
    };
    return setLocale(state.settings.uiLanguage), init2(), {
      getLocale,
      setLocale,
      t,
      getLanguageOptions,
      validateLocale,
      init: init2,
      reloadLocale: (code) => loadLocaleDictionary(normalizeLocale(code), { forceRemote: !0 })
    };
  })(), onDomReady = (callback) => {
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", callback, { once: !0 });
      return;
    }
    callback();
  }, appendToRoot = (node, preferredParent = document.body) => {
    const parent = preferredParent || document.body || document.documentElement;
    return parent ? (parent.appendChild(node), !0) : !1;
  }, applyStyles = (element, ...styles) => (Object.assign(element.style, ...styles.filter(Boolean)), element), createElement = (tagName, options = {}) => {
    const element = document.createElement(tagName), {
      className,
      text,
      htmlFor,
      type,
      value,
      checked,
      title,
      role,
      ariaLabel,
      tabIndex,
      style,
      attributes
    } = options;
    return className && (element.className = className), typeof text == "string" && (element.textContent = text), typeof htmlFor == "string" && (element.htmlFor = htmlFor), typeof type == "string" && (element.type = type), typeof value == "string" && (element.value = value), typeof checked == "boolean" && (element.checked = checked), typeof title == "string" && (element.title = title), typeof role == "string" && element.setAttribute("role", role), typeof ariaLabel == "string" && element.setAttribute("aria-label", ariaLabel), typeof tabIndex == "number" && (element.tabIndex = tabIndex), style && applyStyles(element, style), attributes && Object.entries(attributes).forEach(([key, attributeValue]) => {
      element.setAttribute(key, attributeValue);
    }), element;
  }, debounce = (func, wait) => {
    let timeout;
    return function(...args) {
      clearTimeout(timeout), timeout = setTimeout(() => func(...args), wait);
    };
  }, randomId = (prefix = "task") => `${prefix}_${Math.random().toString(36).slice(2, 10)}_${Date.now()}`, getTheme = () => state.themeIsDark, triggerAnchorDownload = (href, fileName) => {
    const anchor = document.createElement("a");
    return anchor.href = href, fileName && (anchor.download = fileName), anchor.rel = "noopener", anchor.style.display = "none", appendToRoot(anchor) ? (anchor.click(), anchor.remove(), !0) : !1;
  }, openInNewTab = (href) => {
    const anchor = document.createElement("a");
    return anchor.href = href, anchor.rel = "noopener", anchor.target = "_blank", anchor.style.display = "none", appendToRoot(anchor) ? (anchor.click(), anchor.remove(), !0) : !!window.open(href, "_blank", "noopener");
  }, openWithFallback = (href, fileName, strategy) => strategy === "tab" ? openInNewTab(href) ? "tab" : triggerAnchorDownload(href, fileName) ? "browser" : "failed" : triggerAnchorDownload(href, fileName) ? "browser" : openInNewTab(href) ? "tab" : "failed", setButtonIconContent = (button, iconChar, className = "tgico", extras = []) => {
    button.replaceChildren();
    const iconSpan = createElement("span", {
      className,
      text: iconChar
    });
    return button.appendChild(iconSpan), extras.forEach((node) => {
      node instanceof HTMLElement && button.appendChild(node);
    }), iconSpan;
  }, styleFactory = {
    glassPanel(isDark) {
      return {
        backgroundColor: isDark ? "rgba(18,20,24,0.75)" : "rgba(255,255,255,0.75)",
        color: isDark ? "#eaeaea" : "var(--primary-text-color, #000)",
        border: isDark ? "1px solid rgba(255,255,255,0.04)" : "1px solid rgba(255,255,255,0.6)",
        boxShadow: isDark ? "0 12px 48px rgba(0,0,0,0.6)" : "0 12px 48px rgba(16,24,40,0.12)",
        backdropFilter: "blur(12px) saturate(130%)",
        WebkitBackdropFilter: "blur(12px) saturate(130%)"
      };
    },
    toast(type, isDark) {
      const colors = {
        info: "#2196F3",
        error: "#f44336",
        success: "#4CAF50",
        warning: "#FF9800"
      };
      return {
        backgroundColor: isDark ? "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.7)",
        color: isDark ? "#eaeaea" : "#111",
        padding: "12px 16px",
        borderRadius: "12px",
        border: isDark ? "1px solid rgba(255,255,255,0.06)" : "1px solid rgba(255,255,255,0.6)",
        boxShadow: isDark ? "0 6px 24px rgba(0,0,0,0.6)" : "0 6px 24px rgba(16,24,40,0.12)",
        fontSize: "14px",
        fontWeight: "600",
        backdropFilter: "blur(10px) saturate(140%)",
        WebkitBackdropFilter: "blur(10px) saturate(140%)",
        animation: "tel-slideIn 360ms cubic-bezier(.2,.9,.2,1)",
        cursor: "pointer",
        wordWrap: "break-word",
        display: "flex",
        alignItems: "center",
        gap: "12px",
        overflow: "hidden",
        position: "relative",
        accentColor: colors[type] || colors.info
      };
    },
    modalOverlay(zIndex = "10002") {
      return {
        position: "fixed",
        top: "0",
        left: "0",
        width: "100%",
        height: "100%",
        backgroundColor: "rgba(10, 12, 16, 0.55)",
        zIndex,
        display: "none",
        justifyContent: "center",
        alignItems: "center",
        backdropFilter: "blur(6px)"
      };
    },
    progressContainer() {
      return {
        position: "fixed",
        right: "0",
        bottom: "7%",
        zIndex: location.pathname.startsWith("/k/") ? "4" : "1600",
        display: "flex",
        flexDirection: "column",
        gap: "8px",
        padding: "8px",
        maxHeight: "70vh",
        overflowY: "auto"
      };
    },
    progressCard(isDark) {
      return {
        width: "20rem",
        padding: "0.7rem",
        borderRadius: "12px",
        ...styleFactory.glassPanel(isDark)
      };
    },
    progressBar(isDark) {
      return {
        backgroundColor: isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.06)",
        position: "relative",
        width: "100%",
        height: "1.6rem",
        borderRadius: "1.2rem",
        overflow: "hidden",
        border: isDark ? "1px solid rgba(255,255,255,0.02)" : "1px solid rgba(0,0,0,0.04)"
      };
    },
    progressLabel(isDark) {
      return {
        position: "absolute",
        zIndex: "5",
        left: "50%",
        top: "50%",
        transform: "translate(-50%, -50%)",
        margin: "0",
        color: isDark ? "#fff" : "#111",
        fontWeight: "700",
        fontSize: "0.9rem",
        whiteSpace: "nowrap"
      };
    },
    progressFill(color) {
      return {
        position: "absolute",
        height: "100%",
        width: "0%",
        background: color,
        boxShadow: "0 4px 18px rgba(96,147,181,0.18)"
      };
    },
    keyboardOverlay() {
      return {
        position: "fixed",
        top: "50%",
        left: "50%",
        transform: "translate(-50%, -50%)",
        backgroundColor: "rgba(0, 0, 0, 0.7)",
        color: "white",
        padding: "10px 20px",
        borderRadius: "5px",
        fontSize: "18px",
        opacity: "0",
        transition: "opacity 0.3s ease",
        zIndex: "2147483647",
        pointerEvents: "none"
      };
    }
  }, notificationFactory =  (() => {
    const ensureStyles = () => {
      if (document.getElementById("tel-notification-styles")) return;
      const style = createElement("style", {
        attributes: { id: "tel-notification-styles" },
        text: `
        @keyframes tel-slideIn {
          from { transform: translateX(16px) scale(.98); opacity: 0; }
          to { transform: translateX(0) scale(1); opacity: 1; }
        }
        @keyframes tel-slideOut {
          from { transform: translateX(0) scale(1); opacity: 1; }
          to { transform: translateX(10px) scale(.98); opacity: 0; }
        }
        #tel-notification-container > div {
          will-change: transform, opacity;
        }
      `
      });
      document.head.appendChild(style);
    }, ensureContainer = () => {
      let container = document.getElementById("tel-notification-container");
      return container || (container = createElement("div", {
        attributes: { id: "tel-notification-container" },
        style: {
          position: "fixed",
          top: "20px",
          right: "20px",
          zIndex: "10001",
          display: "flex",
          flexDirection: "column",
          gap: "10px",
          maxWidth: "400px"
        }
      }), appendToRoot(container), container);
    };
    return { show: (message, type = "info", duration = 3e3) => {
      try {
        ensureStyles();
        const container = ensureContainer();
        for (; container.children.length >= MAX_NOTIFICATIONS; )
          container.firstElementChild?.remove();
        const notification = createElement("div", {
          role: type === "error" ? "alert" : "status",
          tabIndex: 0
        });
        notification.dataset.telNotificationType = type;
        const styles = styleFactory.toast(type, getTheme());
        applyStyles(notification, styles), notification.setAttribute("aria-live", type === "error" ? "assertive" : "polite"), notification.setAttribute("aria-atomic", "true");
        const accent = createElement("span", {
          style: {
            width: "6px",
            height: "100%",
            borderRadius: "4px",
            flex: "0 0 6px",
            background: styles.accentColor,
            boxShadow: "inset 0 0 6px rgba(0,0,0,0.08)"
          }
        }), text = createElement("span", { text: message });
        notification.append(accent, text), notification.addEventListener("click", () => notification.remove()), notification.addEventListener("keydown", (event) => {
          ["Enter", " ", "Escape"].includes(event.key) && (event.preventDefault(), notification.remove());
        }), container.appendChild(notification), setTimeout(() => {
          notification.style.animation = "tel-slideOut 0.3s ease-out", setTimeout(() => notification.remove(), 300);
        }, duration);
      } catch (error) {
        logger.error(`Failed to show notification: ${error.message}`);
      }
    }, refreshTheme: () => {
      const container = document.getElementById("tel-notification-container");
      container && container.querySelectorAll(":scope > div").forEach((notification) => {
        const type = notification.dataset.telNotificationType || "info", styles = styleFactory.toast(type, getTheme());
        applyStyles(notification, styles);
        const accent = notification.firstElementChild;
        accent instanceof HTMLElement && (accent.style.background = styles.accentColor);
      });
    } };
  })(), SETTINGS_PANEL_CSS = `
  .tel-settings-shell {
    width: min(860px, 96vw);
    color: #f4f5f7;
    overflow: visible;
  }
  .tel-settings-main {
    flex: 1;
    min-width: 0;
  }
  .tel-settings-toolbar {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 14px;
  }
  .tel-settings-toolbar .tel-settings-topbar-left {
    display: flex;
    align-items: center;
    gap: 14px;
  }
  .tel-settings-toolbar .tel-settings-topbar-right {
    display: flex;
    align-items: center;
    gap: 14px;
    justify-content: flex-end;
  }
  .tel-settings-toolbar-center {
    text-align: center;
    font-size: 12px;
    font-weight: 700;
    color: rgba(255,255,255,0.55);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    pointer-events: none;
    white-space: nowrap;
  }
  .tel-settings-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    width: 100%;
    box-sizing: border-box;
    padding: 0 0 18px;
  }
  .tel-settings-title-pill,
  .tel-settings-save-btn,
  .tel-settings-action-btn {
    border-radius: 999px;
    border: 1px solid rgba(255,255,255,0.26);
    color: #fff;
    background: rgba(255,255,255,0.06);
    font-weight: 600;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
  }
  .tel-settings-title-pill {
    padding: 10px 16px;
    font-size: 14px;
  }
  .tel-settings-topbar-left,
  .tel-settings-topbar-right {
    display: flex;
    align-items: center;
    gap: 14px;
  }
  .tel-settings-save-btn {
    font-size: 18px;
    padding: 12px 24px;
    background: linear-gradient(180deg, rgba(121,20,77,0.82) 0%, rgba(92,16,59,0.88) 100%);
    cursor: pointer;
  }
  .tel-settings-action-btn {
    width: 44px;
    height: 44px;
    padding: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .tel-settings-action-btn svg,
  .tel-settings-sidebar-btn svg {
    width: 20px;
    height: 20px;
    display: block;
  }
  .tel-settings-sidebar {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 12px 10px;
    border-radius: 22px;
    border: 1px solid rgba(255,255,255,0.2);
    background: linear-gradient(180deg, rgba(36,56,84,0.68) 0%, rgba(20,30,48,0.74) 100%);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 12px 32px rgba(0,0,0,0.26);
    align-self: center;
  }
  .tel-settings-sidebar-btn {
    width: 44px;
    height: 44px;
    border-radius: 14px;
    border: 1px solid rgba(255,255,255,0.22);
    background: rgba(255,255,255,0.06);
    color: rgba(255,255,255,0.86);
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background .2s ease, border-color .2s ease, transform .15s ease;
  }
  .tel-settings-sidebar-btn:hover {
    transform: translateY(-1px);
    border-color: rgba(255,255,255,0.34);
    background: rgba(255,255,255,0.1);
  }
  .tel-settings-sidebar-btn.is-active {
    border-color: rgba(255,255,255,0.48);
    background: linear-gradient(180deg, rgba(106,145,191,0.42) 0%, rgba(70,105,149,0.4) 100%);
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.22);
  }
  .tel-settings-close-btn {
    border: 1px solid rgba(255,255,255,0.3);
    background: rgba(255,255,255,0.08);
    color: #fff;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    cursor: pointer;
    font-size: 23px;
    line-height: 1;
    flex-shrink: 0;
  }
  .tel-settings-content {
    flex: 1;
    width: 100%;
  }
  .tel-settings-view-host {
    flex: 1;
    min-width: 0;
  }
  .tel-settings-view {
    width: 100%;
  }
  .tel-settings-view-card {
    border-radius: 22px;
    border: 1px solid rgba(255,255,255,0.18);
    background: linear-gradient(180deg, rgba(43,48,71,0.92) 0%, rgba(31,35,49,0.92) 100%);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), 0 16px 48px rgba(0,0,0,0.28);
    padding: 18px 20px;
  }
  .tel-settings-content-row {
    display: flex;
    align-items: stretch;
    gap: 14px;
  }
  .tel-settings-center-column {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
  }
  .tel-settings-actions-rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    min-height: 100%;
    padding-top: 9%;
  }
  .tel-settings-list {
    flex: 1;
    min-width: 0;
    border-radius: 22px;
    border: 1px solid rgba(255,255,255,0.18);
    background: linear-gradient(180deg, rgba(43,48,71,0.92) 0%, rgba(31,35,49,0.92) 100%);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), 0 16px 48px rgba(0,0,0,0.28);
    overflow: hidden;
  }
  .tel-settings-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 18px 20px;
    cursor: pointer;
    user-select: none;
    transition: background .25s cubic-bezier(.4,0,.2,1), transform .25s cubic-bezier(.4,0,.2,1), box-shadow .25s cubic-bezier(.4,0,.2,1);
  }
  .tel-settings-row--toggle:hover {
    background: rgba(255,255,255,0.04);
    transform: translateX(6px);
    box-shadow: 0 2px 8px rgba(0,0,0,0.12);
  }
  .tel-settings-row + .tel-settings-row {
    border-top: 1px solid rgba(255,255,255,0.08);
  }
  .tel-settings-row-title {
    margin: 0;
    font-size: 21px;
    font-weight: 600;
    color: #fff;
  }
  .tel-settings-row-desc {
    margin: 4px 0 0;
    font-size: 14px;
    color: rgba(227,231,240,0.78);
    line-height: 1.4;
  }
  .tel-settings-row-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
  }
  .tel-settings-check {
    width: 20px;
    height: 20px;
    min-width: 20px;
    min-height: 20px;
    margin: 0;
    border-radius: 999px;
    border: 2px solid rgba(255,255,255,0.32);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    transition: all 250ms cubic-bezier(.4,0,.23,1);
    position: relative;
    flex-shrink: 0;
    color: #f5f7fb;
    box-sizing: border-box;
  }
  .tel-settings-row--toggle:hover .tel-settings-check {
    background: rgba(255,255,255,0.08);
    transform: scale(1.1);
  }
  .tel-settings-check::before {
    content: "";
    width: 5px;
    height: 2px;
    background: currentColor;
    position: absolute;
    transform: rotate(45deg);
    top: 6px;
    left: 3px;
    transition: width 100ms ease 50ms, opacity 50ms;
    transform-origin: 0% 0%;
    opacity: 0;
  }
  .tel-settings-check::after {
    content: "";
    width: 0;
    height: 2px;
    background: currentColor;
    position: absolute;
    transform: rotate(305deg);
    top: 12px;
    left: 7px;
    transition: width 100ms ease, opacity 50ms;
    transform-origin: 0% 0%;
    opacity: 0;
  }
  .tel-settings-check.is-on {
    transform: scale(1.15);
  }
  .tel-settings-check.is-on::before {
    width: 9px;
    opacity: 1;
    transition: width 150ms ease 100ms, opacity 150ms ease 100ms;
  }
  .tel-settings-check.is-on::after {
    width: 16px;
    opacity: 1;
    transition: width 150ms ease 250ms, opacity 150ms ease 250ms;
  }
  .tel-settings-hidden-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
  }
  .tel-settings-row:focus-visible .tel-settings-check,
  .tel-settings-report-debug:focus-visible .tel-settings-check {
    outline: 2px solid rgba(255,255,255,0.9);
    outline-offset: 2px;
  }
  .tel-settings-select-wrap { margin-top: 10px; }
  .tel-settings-select {
    width: 100%;
    border-radius: 14px;
    border: 1px solid rgba(255,255,255,0.24);
    background: rgba(18,23,35,0.82);
    color: #fff;
    font-size: 15px;
    padding: 12px 14px;
  }
  .tel-settings-info-row {
    cursor: default !important;
  }
  .tel-settings-save-btn:focus-visible,
  .tel-settings-action-btn:focus-visible,
  .tel-settings-secondary-btn:focus-visible,
  .tel-settings-close-btn:focus-visible,
  .tel-settings-sidebar-btn:focus-visible,
  .tel-settings-row:focus-visible,
  .tel-settings-select:focus-visible {
    outline: 2px solid rgba(255,255,255,0.9);
    outline-offset: 2px;
  }
  .tel-settings-secondary-btn {
    border-radius: 999px;
    border: 1px solid rgba(255,255,255,0.22);
    background: rgba(255,255,255,0.08);
    color: #fff;
    padding: 10px 16px;
    cursor: pointer;
    font-weight: 600;
  }
  .tel-settings-link-btn,
  .tel-settings-update-btn,
  .tel-settings-update-icon-btn {
    border-radius: 999px;
    border: 1px solid rgba(255,255,255,0.22);
    background: rgba(255,255,255,0.08);
    color: #fff;
    padding: 10px 16px;
    cursor: pointer;
    font-weight: 600;
    text-decoration: none;
  }
  .tel-settings-update-icon-btn {
    width: 44px;
    height: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .tel-settings-update-btn {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
  }
  .tel-settings-update-btn svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
  }
  .tel-settings-about-view,
  .tel-settings-report-view {
    display: grid;
    gap: 14px;
  }
  .tel-settings-about-view {
    align-content: start;
  }
  .tel-settings-about-hero {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 16px;
    padding: 8px 2px 2px;
  }
  .tel-settings-about-kicker {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    opacity: 0.95;
  }
  .tel-settings-about-kicker .app-icon {
    width: 90px;
    height: 90px;
    color: #fff;
  }
  .tel-settings-about-title {
    margin: 0;
    font-size: 46px;
    line-height: 1;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: transparent;
    -webkit-text-stroke: 1px;
    -webkit-text-stroke-color: #fff;
  }
  .tel-settings-about-title:hover {
    color: #ff4d6d;
    -webkit-text-stroke: 1px;
    -webkit-text-stroke-color: transparent;
  }
  .tel-settings-about-status-surface {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px;
    border-radius: 14px;
    border: 1px solid rgba(255,255,255,0.16);
    background: linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%);
  }
  .tel-settings-about-update-panel {
    display: grid;
    gap: 12px;
  }
  .tel-settings-about-meta,
  .tel-settings-about-update-row,
  .tel-settings-about-links,
  .tel-settings-report-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    justify-content: center;
  }
  .tel-settings-about-meta {
    justify-content: flex-start;
    align-items: center;
    margin-top: 0;
  }
  .tel-settings-about-version-row {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .tel-settings-about-version-label {
    font-size: 15px;
    font-weight: 600;
  }
  .tel-settings-about-version-badge {
    border-radius: 999px;
    border: 1px solid rgba(255,255,255,0.28);
    background: rgba(255,255,255,0.1);
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    padding: 3px 10px;
    letter-spacing: 0.04em;
  }
  .tel-settings-about-status {
    color: #5fe58b;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    border-radius: 999px;
    border: 1px solid rgba(95,229,139,0.45);
    background: rgba(95,229,139,0.16);
    padding: 6px 12px;
    white-space: nowrap;
  }
  .tel-settings-about-card {
    display: grid;
    gap: 14px;
  }
  .tel-settings-card-title {
    margin: 0;
    font-size: 18px;
    line-height: 1.3;
  }
  .tel-settings-about-footer,
  .tel-settings-helper-text {
    margin: 0;
    color: rgba(227,231,240,0.78);
    font-size: 13px;
    line-height: 1.5;
    justify-self: center;
  }
  .tel-settings-about-links {
    margin-top: 2px;
  }
  .tel-settings-about-update-row {
    flex-wrap: nowrap;
  }
  .tel-settings-about-update-row .tel-settings-update-btn {
    min-height: 44px;
  }
  .tel-settings-form-input,
  .tel-settings-form-textarea {
    width: 100%;
    border-radius: 14px;
    border: 1px solid rgba(255,255,255,0.18);
    background: rgba(18,23,35,0.82);
    color: #fff;
    padding: 12px 14px;
    font-size: 15px;
    box-sizing: border-box;
  }
  .tel-settings-form-textarea {
    resize: vertical;
    min-height: 150px;
  }
  .tel-settings-report-card {
    display: grid;
    gap: 10px;
  }
  .tel-settings-report-debug {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    user-select: none;
    margin-top: 4px;
    justify-content: center;
  }
  @media (max-width: 700px) {
    .tel-settings-shell { width: min(96vw, 650px); }
    .tel-settings-sidebar { display: none; }
    .tel-settings-row-title { font-size: 18px; }
    .tel-settings-row-desc { font-size: 13px; }
    .tel-settings-save-btn { font-size: 16px; padding: 10px 16px; }
    .tel-settings-content-row { gap: 10px; }
    .tel-settings-actions-rail { gap: 10px; }
    .tel-settings-close-btn { width: 40px; height: 40px; }
    .tel-settings-action-btn { width: 40px; height: 40px; }
    .tel-settings-about-title { font-size: 34px; }
    .tel-settings-about-kicker .app-icon { width: 64px; height: 64px; }
    .tel-settings-about-status-surface { flex-direction: column; align-items: flex-start; }
    .tel-settings-about-update-row { width: 100%; }
  }
`, settingsBuilder = {
    ensureStyles() {
      if (document.getElementById("tel-settings-styles")) return;
      const style = createElement("style", {
        attributes: { id: "tel-settings-styles" },
        text: SETTINGS_PANEL_CSS
      });
      document.head.appendChild(style);
    },
    createToggleCard(id, title, description, checked) {
      const row = createElement("div", {
        className: "tel-settings-row tel-settings-row--toggle",
        attributes: { tabindex: "0" }
      }), textWrap = createElement("div");
      textWrap.append(
        createElement("p", {
          className: "tel-settings-row-title",
          text: title
        }),
        createElement("p", {
          className: "tel-settings-row-desc",
          text: description
        })
      );
      const controls = createElement("div", {
        className: "tel-settings-row-controls"
      }), check = createElement("span", {
        className: "tel-settings-check",
        attributes: { "aria-hidden": "true" }
      }), input = createElement("input", {
        type: "checkbox",
        checked,
        className: "tel-settings-hidden-input",
        attributes: { id, "aria-label": title }
      }), toggle = () => {
        input.checked = !input.checked, input.dispatchEvent(new Event("change", { bubbles: !0 }));
      };
      return row.setAttribute("role", "switch"), row.setAttribute("aria-checked", input.checked ? "true" : "false"), input.addEventListener("change", () => {
        check.classList.toggle("is-on", input.checked), row.setAttribute("aria-checked", input.checked ? "true" : "false");
      }), check.classList.toggle("is-on", input.checked), row.addEventListener("click", (event) => {
        event.target instanceof Element && event.target.closest("button, a, select, input") || toggle();
      }), row.addEventListener("keydown", (event) => {
        (event.key === "Enter" || event.key === " ") && (event.preventDefault(), toggle());
      }), controls.append(check, input), row.append(textWrap, controls), { row, input };
    },
    createSelectCard(id, title, description, value, options) {
      const row = createElement("div", { className: "tel-settings-row" }), textWrap = createElement("div", { style: { flex: "1" } });
      textWrap.append(
        createElement("p", {
          className: "tel-settings-row-title",
          text: title
        }),
        createElement("p", {
          className: "tel-settings-row-desc",
          text: description
        })
      );
      const selectWrap = createElement("div", {
        className: "tel-settings-select-wrap"
      }), select = createElement("select", {
        className: "tel-settings-select",
        attributes: { id }
      });
      return options.forEach((option) => {
        const optionNode = createElement("option", {
          text: option.label,
          value: option.value
        });
        option.value === value && (optionNode.selected = !0), select.appendChild(optionNode);
      }), selectWrap.appendChild(select), textWrap.appendChild(selectWrap), row.append(textWrap, createElement("div", { className: "tel-settings-row-controls" })), { row, select };
    },
    createIcon(paths, viewBox = "0 0 24 24") {
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      return svg.setAttribute("viewBox", viewBox), svg.setAttribute("aria-hidden", "true"), svg.setAttribute("focusable", "false"), paths.forEach((pathDefinition) => {
        const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        Object.entries(pathDefinition).forEach(([key, value]) => {
          path.setAttribute(key, value);
        }), svg.appendChild(path);
      }), svg;
    }
  }, settingsModule =  (() => {
    let settingsPanel = null, lastFocusedElement = null;
    const viewState = {
      current: "settings"
    }, hide = () => {
      const overlay = settingsPanel || document.getElementById("tel-settings-overlay");
      if (overlay && overlay.remove(), settingsPanel = null, lastFocusedElement && typeof lastFocusedElement.focus == "function")
        try {
          lastFocusedElement.focus({ preventScroll: !0 });
        } catch {
          lastFocusedElement.focus();
        }
      lastFocusedElement = null;
    }, createPanel = () => {
      const t = i18n.t;
      settingsBuilder.ensureStyles();
      const overlay = createElement("div", {
        attributes: { id: "tel-settings-overlay" },
        style: styleFactory.modalOverlay()
      }), panel = createElement("div", { className: "tel-settings-shell" }), main = createElement("div", { className: "tel-settings-main" }), left = createElement("div", {
        className: "tel-settings-topbar-left"
      }), right = createElement("div", {
        className: "tel-settings-topbar-right"
      });
      left.append(
        createElement("span", {
          className: "tel-settings-title-pill",
          text: t("settings.title", "Settings")
        })
      );
      const saveButton = createElement("button", {
        type: "button",
        className: "tel-settings-save-btn",
        text: t("settings.save", "Save changes")
      }), closeButton = createElement("button", {
        type: "button",
        className: "tel-settings-close-btn",
        text: "×",
        ariaLabel: t("settings.close", "Close settings")
      });
      closeButton.addEventListener("click", hide);
      const content = createElement("div", {
        className: "tel-settings-content"
      }), toolbar = createElement("div", { className: "tel-settings-toolbar" }), toolbarCenter = createElement("div", { className: "tel-settings-toolbar-center" }), contentRow = createElement("div", {
        className: "tel-settings-content-row"
      }), sidebar = createElement("div", { className: "tel-settings-sidebar" }), centerColumn = createElement("div", { className: "tel-settings-center-column" }), homeList = createElement("div", { className: "tel-settings-list" }), advancedList = createElement("div", { className: "tel-settings-list" }), actionsRail = createElement("div", { className: "tel-settings-actions-rail" }), viewHost = createElement("div", { className: "tel-settings-view-host" }), homeView = createElement("div", {
        className: "tel-settings-view tel-settings-settings-view"
      }), advancedView = createElement("div", {
        className: "tel-settings-view tel-settings-advanced-view"
      }), notifications = settingsBuilder.createToggleCard(
        "tel-setting-notifications",
        t("settings.notifications.title", "Notifications"),
        t("settings.notifications.desc", "Show download status, errors and successful saves"),
        state.settings.enableNotifications
      ), keyboard = settingsBuilder.createToggleCard(
        "tel-setting-keyboard",
        t("settings.keyboard.title", "Keyboard shortcuts"),
        t("settings.keyboard.desc", "Video control with arrows, M, P and Home in WebK/WebZ"),
        state.settings.enableKeyboardShortcuts
      ), adblock = settingsBuilder.createToggleCard(
        "tel-setting-adblock",
        t("settings.adblock.title", "Ad blocking"),
        t("settings.adblock.desc", "Hide sponsored blocks and ad inserts"),
        state.settings.enableAdBlocking
      ), streamCapture = settingsBuilder.createToggleCard(
        "tel-setting-stream-capture",
        t("settings.streamCapture.title", "Experimental stream capture"),
        t(
          "settings.streamCapture.desc",
          "For blob/MSE videos, try MediaRecorder capture on click (best effort)"
        ),
        state.settings.enableExperimentalStreamCapture
      ), pickerSupported = downloadCompatibility.isSavePromptSupported(), pickerLabel = t("settings.downloadMode.picker", "Ask where to save"), downloadMode = settingsBuilder.createSelectCard(
        "tel-setting-download-mode",
        t("settings.downloadMode.title", "Download strategy"),
        t("settings.downloadMode.desc", "Browser mode, system picker or graceful fallback"),
        state.settings.downloadLocation,
        [
          {
            value: "browser",
            label: t("settings.downloadMode.browser", "Through browser")
          },
          {
            value: "picker",
            label: pickerSupported ? pickerLabel : `${pickerLabel} ${t("settings.downloadMode.pickerUnsupportedSuffix", "(Chrome/Edge only)")}`
          },
          {
            value: "tab",
            label: t("settings.downloadMode.tab", "Open in new tab")
          },
          {
            value: "auto",
            label: t("settings.downloadMode.auto", "Auto-select with fallback")
          }
        ]
      ), language = settingsBuilder.createSelectCard(
        "tel-setting-language",
        t("settings.language.title", "Language"),
        t("settings.language.desc", "Choose interface language"),
        state.settings.uiLanguage,
        i18n.getLanguageOptions().map((option) => ({
          value: option.value,
          label: option.value === "auto" ? t("settings.language.auto", "Automatic (browser)") : option.label
        }))
      ), applyAndRefreshSettings = (nextSettings, options = {}) => {
        const { successMessage = "", closePanel = !0 } = options;
        state.settings = normalizeSettings(nextSettings), saveSettings(state.settings), i18n.setLocale(state.settings.uiLanguage), typeof uiModule < "u" && typeof uiModule.refresh == "function" && uiModule.refresh(), typeof progressFactory < "u" && typeof progressFactory.refreshTexts == "function" && progressFactory.refreshTexts(), typeof observersModule < "u" && typeof observersModule.refreshAdBlocking == "function" && observersModule.refreshAdBlocking(), state.settings.enableNotifications && successMessage && showNotification(successMessage, "success"), closePanel && hide();
      }, collectToggleSettings = () => ({
        ...state.settings,
        enableNotifications: notifications.input.checked,
        enableKeyboardShortcuts: keyboard.input.checked,
        enableAdBlocking: adblock.input.checked,
        enableExperimentalStreamCapture: streamCapture.input.checked
      }), collectAllSettings = () => ({
        ...collectToggleSettings(),
        downloadLocation: downloadMode.select.value,
        uiLanguage: language.select.value
      }), persistToggleSettings = () => {
        applyAndRefreshSettings(collectToggleSettings(), { closePanel: !1 });
      }, importInput = createElement("input", {
        type: "file",
        attributes: { accept: "application/json,.json" },
        style: { display: "none" }
      }), createSettingsBackupName = () => `telegram-plus-settings-${( new Date()).toISOString().replace(/[:.]/g, "-")}.json`, exportSettings = () => {
        try {
          const payload = {
            schemaVersion: 1,
            exportedAt: ( new Date()).toISOString(),
            settings: normalizeSettings(state.settings)
          }, blob = new Blob([JSON.stringify(payload, null, 2)], {
            type: "application/json"
          }), link = createElement("a", {
            attributes: {
              href: URL.createObjectURL(blob),
              download: createSettingsBackupName()
            },
            style: { display: "none" }
          });
          document.body.appendChild(link), link.click(), setTimeout(() => {
            URL.revokeObjectURL(link.href), link.remove();
          }, 0), state.settings.enableNotifications && showNotification(t("settings.exported", "Settings exported"), "success");
        } catch (error) {
          showNotification(
            `${t("settings.importError", "Settings import/export error")}: ${error.message}`,
            "error"
          );
        }
      }, importSettings = async (event) => {
        const [file] = Array.from(event.target.files || []);
        if (event.target.value = "", !!file)
          try {
            const content2 = await file.text(), parsed = safeJsonParse(content2, null), imported = isPlainObject(parsed) && parsed.settings ? parsed.settings : parsed;
            if (!isPlainObject(imported))
              throw new Error(t("settings.invalidBackup", "Invalid settings backup format"));
            applyAndRefreshSettings(
              {
                ...state.settings,
                ...imported
              },
              {
                successMessage: t("settings.imported", "Settings imported"),
                closePanel: !0
              }
            );
          } catch (error) {
            showNotification(
              `${t("settings.importError", "Settings import/export error")}: ${error.message}`,
              "error"
            );
          }
      }, exportButton = createElement("button", {
        type: "button",
        className: "tel-settings-action-btn",
        ariaLabel: t("settings.export", "Export settings"),
        title: t("settings.export", "Export settings")
      });
      exportButton.append(
        settingsBuilder.createIcon([
          {
            d: "M4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12",
            fill: "none",
            stroke: "currentColor",
            opacity: "0.5",
            "stroke-width": "1.5",
            "stroke-linecap": "round"
          },
          {
            d: "M12 14L12 4M12 4L15 7M12 4L9 7",
            fill: "none",
            stroke: "currentColor",
            "stroke-linecap": "round",
            "stroke-linejoin": "round",
            "stroke-width": "1.5"
          }
        ])
      ), exportButton.addEventListener("click", exportSettings);
      const importButton = createElement("button", {
        type: "button",
        className: "tel-settings-action-btn",
        ariaLabel: t("settings.import", "Import settings"),
        title: t("settings.import", "Import settings")
      });
      importButton.append(
        settingsBuilder.createIcon([
          {
            d: "M4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12",
            fill: "none",
            stroke: "currentColor",
            opacity: "0.5",
            "stroke-width": "1.5",
            "stroke-linecap": "round"
          },
          {
            d: "M12 4L12 14M12 14L15 11M12 14L9 11",
            fill: "none",
            stroke: "currentColor",
            "stroke-linecap": "round",
            "stroke-linejoin": "round",
            "stroke-width": "1.5"
          }
        ])
      ), importButton.addEventListener("click", () => importInput.click()), importInput.addEventListener("change", importSettings);
      const setView = (viewName) => {
        if (viewState.current = viewName, viewHost.replaceChildren(), sidebar.querySelectorAll(".tel-settings-sidebar-btn").forEach((node) => {
          node.classList.toggle("is-active", node.dataset.view === viewName);
        }), viewName === "about" && typeof updateModule < "u") {
          viewHost.append(updateModule.createView({ t })), right.replaceChildren(saveButton), actionsRail.replaceChildren(closeButton);
          return;
        }
        if (viewName === "report" && typeof reportModule < "u") {
          viewHost.append(reportModule.createView({ t })), right.replaceChildren(saveButton), actionsRail.replaceChildren(closeButton);
          return;
        }
        if (viewName === "advanced") {
          viewHost.append(advancedView), right.replaceChildren(saveButton), actionsRail.replaceChildren(closeButton);
          return;
        }
        viewHost.append(homeView), right.replaceChildren(saveButton, exportButton, importButton), actionsRail.replaceChildren(closeButton, exportButton, importButton);
      }, persistSettings = () => {
        const requestedDownloadLocation = downloadMode.select.value;
        requestedDownloadLocation === "picker" && !downloadCompatibility.isSavePromptSupported() && showNotification(
          t(
            "settings.downloadMode.pickerUnsupported",
            "System picker is unavailable in this browser. Fallback strategy will be used."
          ),
          "info",
          4500
        );
        const nextSettings = collectAllSettings();
        nextSettings.downloadLocation = requestedDownloadLocation, applyAndRefreshSettings(nextSettings, {
          successMessage: t("settings.saved", "Settings saved"),
          closePanel: !0
        });
      };
      [notifications.input, keyboard.input, adblock.input, streamCapture.input].forEach((input) => {
        input.addEventListener("change", persistToggleSettings);
      }), saveButton.addEventListener("click", persistSettings), downloadMode.select.addEventListener("keydown", (event) => {
        event.key === "Enter" && persistSettings();
      }), overlay.addEventListener("click", (event) => {
        event.target === overlay && hide();
      }), panel.addEventListener("keydown", (event) => {
        if (event.key === "Escape") {
          event.preventDefault(), hide();
          return;
        }
        if (event.key !== "Tab") return;
        const focusables = Array.from(
          panel.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
          )
        ).filter((node) => !node.disabled && node.offsetParent !== null);
        if (focusables.length === 0) return;
        const first = focusables[0], last = focusables[focusables.length - 1], active = document.activeElement;
        if (event.shiftKey && active === first) {
          event.preventDefault(), last.focus();
          return;
        }
        !event.shiftKey && active === last && (event.preventDefault(), first.focus());
      }), homeList.append(
        notifications.row,
        keyboard.row,
        adblock.row,
        streamCapture.row,
        downloadMode.row
      );
      const shortcutsWrap = createElement("div");
      shortcutsWrap.append(
        createElement("p", {
          className: "tel-settings-row-title",
          text: t("settings.keyboard.title", "Keyboard shortcuts")
        }),
        createElement("p", {
          className: "tel-settings-row-desc",
          text: "← → seek · ↑ ↓ volume · M mute · P PiP · Home start"
        })
      );
      const shortcutsRow = createElement("div", {
        className: "tel-settings-row tel-settings-info-row"
      });
      shortcutsRow.append(
        shortcutsWrap,
        createElement("div", { className: "tel-settings-row-controls" })
      );
      const diagnosticsWrap = createElement("div");
      diagnosticsWrap.append(
        createElement("p", {
          className: "tel-settings-row-title",
          text: t("settings.diagnostics.title", "Diagnostics")
        }),
        createElement("p", {
          className: "tel-settings-row-desc",
          text: t("settings.diagnostics.desc", "Copy runtime report for debugging and issue tickets")
        })
      );
      const diagnosticsButton = createElement("button", {
        type: "button",
        className: "tel-settings-secondary-btn",
        text: t("settings.diagnostics.copy", "Copy diagnostics report")
      });
      diagnosticsButton.addEventListener("click", async () => {
        try {
          if (typeof reportModule?.copyDiagnostics == "function") {
            await reportModule.copyDiagnostics(t), showNotification(t("settings.diagnostics.copied", "Diagnostics copied"), "success");
            return;
          }
          throw new Error("Report module is unavailable");
        } catch (error) {
          showNotification(
            `${t("settings.diagnostics.copyError", "Failed to copy diagnostics")}: ${error.message}`,
            "error"
          );
        }
      });
      const diagnosticsControls = createElement("div", { className: "tel-settings-row-controls" });
      diagnosticsControls.append(diagnosticsButton);
      const diagnosticsRow = createElement("div", {
        className: "tel-settings-row tel-settings-info-row"
      });
      diagnosticsRow.append(diagnosticsWrap, diagnosticsControls), advancedList.append(language.row, shortcutsRow, diagnosticsRow), homeView.append(homeList), advancedView.append(advancedList);
      const homeSidebarIcon = settingsBuilder.createIcon([
        {
          d: "M2 12.2039C2 9.91549 2 8.77128 2.5192 7.82274C3.0384 6.87421 3.98695 6.28551 5.88403 5.10813L7.88403 3.86687C9.88939 2.62229 10.8921 2 12 2C13.1079 2 14.1106 2.62229 16.116 3.86687L18.116 5.10812C20.0131 6.28551 20.9616 6.87421 21.4808 7.82274C22 8.77128 22 9.91549 22 12.2039V13.725C22 17.6258 22 19.5763 20.8284 20.7881C19.6569 22 17.7712 22 14 22H10C6.22876 22 4.34315 22 3.17157 20.7881C2 19.5763 2 17.6258 2 13.725V12.2039Z",
          stroke: "currentColor",
          "stroke-width": "1.5",
          opacity: "0.5",
          fill: "none"
        },
        {
          d: "M15 18H9",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          fill: "none"
        }
      ]);
      homeSidebarIcon.setAttribute("fill", "none");
      const advancedSidebarIcon = settingsBuilder.createIcon([
        {
          d: "M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12",
          stroke: "currentColor",
          "stroke-width": "1.5",
          opacity: "0.5",
          fill: "none"
        },
        {
          d: "M2 14C2 11.1997 2 9.79961 2.54497 8.73005C3.02433 7.78924 3.78924 7.02433 4.73005 6.54497C5.79961 6 7.19974 6 10 6H14C16.8003 6 18.2004 6 19.27 6.54497C20.2108 7.02433 20.9757 7.78924 21.455 8.73005C22 9.79961 22 11.1997 22 14C22 16.8003 22 18.2004 21.455 19.27C20.9757 20.2108 20.2108 20.9757 19.27 21.455C18.2004 22 16.8003 22 14 22H10C7.19974 22 5.79961 22 4.73005 21.455C3.78924 20.9757 3.02433 20.2108 2.54497 19.27C2 18.2004 2 16.8003 2 14Z",
          stroke: "currentColor",
          "stroke-width": "1.5",
          fill: "none"
        },
        {
          d: "M9.5 14.4L10.9286 16L14.5 12",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          "stroke-linejoin": "round",
          fill: "none"
        }
      ]);
      advancedSidebarIcon.setAttribute("fill", "none");
      const reportSidebarIcon = settingsBuilder.createIcon([
        {
          d: "M4 6V19C4 20.6569 5.34315 22 7 22H17C18.6569 22 20 20.6569 20 19V9C20 7.34315 18.6569 6 17 6H4ZM4 6V5",
          stroke: "currentColor",
          "stroke-width": "1.5",
          fill: "none"
        },
        {
          d: "M18 6.00002V6.75002H18.75V6.00002H18ZM15.7172 2.32614L15.6111 1.58368L15.7172 2.32614ZM4.91959 3.86865L4.81353 3.12619H4.81353L4.91959 3.86865ZM5.07107 6.75002H18V5.25002H5.07107V6.75002ZM18.75 6.00002V4.30604H17.25V6.00002H18.75ZM15.6111 1.58368L4.81353 3.12619L5.02566 4.61111L15.8232 3.0686L15.6111 1.58368ZM4.81353 3.12619C3.91638 3.25435 3.25 4.0227 3.25 4.92895H4.75C4.75 4.76917 4.86749 4.63371 5.02566 4.61111L4.81353 3.12619ZM18.75 4.30604C18.75 2.63253 17.2678 1.34701 15.6111 1.58368L15.8232 3.0686C16.5763 2.96103 17.25 3.54535 17.25 4.30604H18.75ZM5.07107 5.25002C4.89375 5.25002 4.75 5.10627 4.75 4.92895H3.25C3.25 5.9347 4.06532 6.75002 5.07107 6.75002V5.25002Z",
          fill: "currentColor"
        },
        {
          d: "M8 12H16",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          opacity: "0.5",
          fill: "none"
        },
        {
          d: "M8 15.5H13.5",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          opacity: "0.5",
          fill: "none"
        }
      ]);
      reportSidebarIcon.setAttribute("fill", "none");
      const aboutSidebarIcon = settingsBuilder.createIcon([
        {
          d: "M15.5 9L15.6716 9.17157C17.0049 10.5049 17.6716 11.1716 17.6716 12C17.6716 12.8284 17.0049 13.4951 15.6716 14.8284L15.5 15",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          fill: "none"
        },
        {
          d: "M13.2942 7.17041L12.0001 12L10.706 16.8297",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          fill: "none"
        },
        {
          d: "M8.49994 9L8.32837 9.17157C6.99504 10.5049 6.32837 11.1716 6.32837 12C6.32837 12.8284 6.99504 13.4951 8.32837 14.8284L8.49994 15",
          stroke: "currentColor",
          "stroke-width": "1.5",
          "stroke-linecap": "round",
          fill: "none"
        },
        {
          d: "M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12Z",
          stroke: "currentColor",
          "stroke-width": "1.5",
          opacity: "0.5",
          fill: "none"
        }
      ]);
      aboutSidebarIcon.setAttribute("fill", "none");
      const sidebarItems = [
        {
          iconNode: homeSidebarIcon,
          title: t("settings.nav.home", "Settings"),
          view: "home",
          target: notifications.row
        },
        {
          iconNode: advancedSidebarIcon,
          title: t("settings.nav.advanced", "Advanced"),
          view: "advanced",
          target: null
        },
        {
          iconNode: reportSidebarIcon,
          title: t("settings.nav.report", "Report"),
          view: "report",
          target: null
        },
        {
          iconNode: aboutSidebarIcon,
          title: t("settings.nav.about", "About"),
          view: "about",
          target: null
        }
      ];
      return sidebarItems.forEach((item, index) => {
        const button = createElement("button", {
          type: "button",
          className: `tel-settings-sidebar-btn${index === 0 ? " is-active" : ""}`,
          ariaLabel: item.title,
          title: item.title,
          attributes: { "data-view": item.view }
        });
        button.append(item.iconNode), button.addEventListener("click", () => {
          setView(item.view), toolbarCenter.textContent = item.title, item.view === "home" && item.target && item.target.scrollIntoView({ behavior: "smooth", block: "center" });
        }), sidebar.append(button);
      }), toolbarCenter.textContent = sidebarItems[0].title, toolbar.append(left, toolbarCenter, right), centerColumn.append(toolbar, viewHost), contentRow.append(sidebar, centerColumn, actionsRail), content.append(contentRow, importInput), main.append(content), panel.append(main), overlay.appendChild(panel), setView("home"), appendToRoot(overlay), overlay;
    };
    return {
      ensurePanel() {
        return (!settingsPanel || !document.body.contains(settingsPanel)) && (settingsPanel = createPanel()), settingsPanel;
      },
      show() {
        lastFocusedElement || (lastFocusedElement = document.activeElement);
        const overlay = this.ensurePanel();
        overlay.style.display = "flex";
        const closeButton = overlay.querySelector(".tel-settings-close-btn");
        closeButton instanceof HTMLElement && closeButton.focus();
      },
      hide
    };
  })(), reportModule = (() => {
    const REPO_URL = "https://github.com/diorhc/TGP", ISSUES_URL = `${REPO_URL}/issues/new`, DISCUSSIONS_URL = `${REPO_URL}/discussions/new?category=ideas`, getScriptVersion = () => typeof GM_info < "u" && GM_info?.script?.version ? String(GM_info.script.version) : "2.1.0", copyText = async (text) => {
      if (navigator.clipboard?.writeText) {
        await navigator.clipboard.writeText(text);
        return;
      }
      const textarea = createElement("textarea", {
        style: {
          position: "fixed",
          left: "-9999px",
          top: "0"
        },
        text
      });
      appendToRoot(textarea), textarea.focus(), textarea.select(), document.execCommand("copy"), textarea.remove();
    }, launchMailto = (subject, body) => {
      const anchor = document.createElement("a");
      anchor.href = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`, anchor.style.display = "none", appendToRoot(anchor) && (anchor.click(), anchor.remove());
    }, buildReportText = ({ type, title, email, details, includeDebug, t }) => {
      const typeLabel = type === "feature" ? t("report.typeFeature", "Idea") : type === "bug" ? t("report.typeBug", "Bug") : t("report.typeOther", "Other"), lines = [
        `${t("report.kind", "Type")}: ${typeLabel}`,
        `${t("report.title", "Title")}: ${title || "-"}`,
        `${t("report.email", "Email")}: ${email || "-"}`,
        "",
        t("report.description", "Description"),
        details || "-"
      ];
      return includeDebug && lines.push(
        "",
        "---",
        t("report.debug", "Debug information"),
        `Version: ${getScriptVersion()}`,
        `URL: ${state.currentHref || location.href}`,
        `Theme: ${state.themeIsDark ? "dark" : "light"}`,
        `User Agent: ${navigator.userAgent}`,
        `Settings: ${JSON.stringify(normalizeSettings(state.settings), null, 2)}`
      ), lines.join(`
`);
    }, buildDiagnosticsText = (t = i18n.t) => {
      const queueDiagnostics = typeof queueManager < "u" && typeof queueManager.getDiagnosticsSnapshot == "function" ? queueManager.getDiagnosticsSnapshot() : null, payload = {
        generatedAt: ( new Date()).toISOString(),
        version: getScriptVersion(),
        url: state.currentHref || location.href,
        theme: state.themeIsDark ? "dark" : "light",
        userAgent: navigator.userAgent,
        downloadStrategy: state.settings.downloadLocation,
        notificationsEnabled: !!state.settings.enableNotifications,
        nativePickerSupported: typeof downloadCompatibility < "u" && typeof downloadCompatibility.isPickerSupported == "function" ? downloadCompatibility.isPickerSupported() : !1,
        savePromptSupported: typeof downloadCompatibility < "u" && typeof downloadCompatibility.isSavePromptSupported == "function" ? downloadCompatibility.isSavePromptSupported() : !1,
        queue: queueDiagnostics
      };
      return [t("report.debug", "Debug information"), "---", JSON.stringify(payload, null, 2)].join(
        `
`
      );
    };
    return {
      createView: ({ t }) => {
        const view = createElement("section", {
          className: "tel-settings-view tel-settings-report-view"
        }), card = createElement("div", {
          className: "tel-settings-view-card tel-settings-report-card"
        }), typeInput = createElement("select", {
          className: "tel-settings-select"
        });
        [
          { value: "bug", label: t("report.typeBug", "Bug") },
          { value: "feature", label: t("report.typeFeature", "Idea") },
          { value: "other", label: t("report.typeOther", "Other") }
        ].forEach((option) => {
          typeInput.append(
            createElement("option", {
              value: option.value,
              text: option.label
            })
          );
        });
        const titleInput = createElement("input", {
          className: "tel-settings-form-input",
          attributes: {
            type: "text",
            maxlength: "120",
            placeholder: t("report.titlePlaceholder", "Short one-line title")
          }
        }), emailInput = createElement("input", {
          className: "tel-settings-form-input",
          attributes: {
            type: "email",
            placeholder: t("report.emailPlaceholder", "Your email (optional)")
          }
        }), detailsInput = createElement("textarea", {
          className: "tel-settings-form-textarea",
          attributes: {
            rows: "7",
            placeholder: t(
              "report.detailsPlaceholder",
              "Describe the problem, reproduction steps, expected and actual result"
            )
          }
        }), debugToggle = createElement("input", {
          type: "checkbox",
          className: "tel-settings-hidden-input",
          checked: !0,
          attributes: {
            id: "tel-report-debug-toggle",
            "aria-label": t(
              "report.includeDebug",
              "Include debug information (version, URL, settings)"
            )
          }
        }), debugCheck = createElement("span", {
          className: "tel-settings-check",
          attributes: { "aria-hidden": "true" }
        }), debugRow = createElement("div", {
          className: "tel-settings-report-debug",
          role: "switch",
          attributes: { tabindex: "0" }
        }), syncDebug = () => {
          debugCheck.classList.toggle("is-on", debugToggle.checked), debugRow.setAttribute("aria-checked", debugToggle.checked ? "true" : "false");
        }, toggleDebug = () => {
          debugToggle.checked = !debugToggle.checked, debugToggle.dispatchEvent(new Event("change", { bubbles: !0 }));
        };
        syncDebug(), debugToggle.addEventListener("change", syncDebug), debugRow.addEventListener("click", (event) => {
          event.target instanceof Element && event.target.closest("button, a, select, input") || toggleDebug();
        }), debugRow.addEventListener("keydown", (event) => {
          (event.key === "Enter" || event.key === " ") && (event.preventDefault(), toggleDebug());
        }), debugRow.append(
          debugCheck,
          createElement("span", {
            text: t("report.includeDebug", "Include debug information (version, URL, settings)")
          }),
          debugToggle
        );
        const actionRow = createElement("div", { className: "tel-settings-report-actions" }), githubButton = createElement("button", {
          type: "button",
          className: "tel-settings-secondary-btn",
          text: t("report.openGithub", "Open issue on GitHub")
        }), copyButton = createElement("button", {
          type: "button",
          className: "tel-settings-secondary-btn",
          text: t("report.copy", "Copy report")
        }), emailButton = createElement("button", {
          type: "button",
          className: "tel-settings-secondary-btn",
          text: t("report.emailAction", "Prepare email")
        }), getPayload = () => ({
          type: typeInput.value,
          title: titleInput.value.trim(),
          email: emailInput.value.trim(),
          details: detailsInput.value.trim(),
          includeDebug: debugToggle.checked,
          t
        });
        return githubButton.addEventListener("click", () => {
          const payload = getPayload(), title = payload.title || t("report.defaultTitle", "YTP report"), body = buildReportText(payload), targetUrl = payload.type === "feature" ? `${DISCUSSIONS_URL}&title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}` : `${ISSUES_URL}?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
          openInNewTab(targetUrl);
        }), copyButton.addEventListener("click", async () => {
          try {
            await copyText(buildReportText(getPayload())), showNotification(t("report.copied", "Report copied"), "success");
          } catch (error) {
            showNotification(`${t("report.copyError", "Copy failed")}: ${error.message}`, "error");
          }
        }), emailButton.addEventListener("click", () => {
          const payload = getPayload(), subject = payload.title || t("report.defaultTitle", "YTP report");
          launchMailto(subject, buildReportText(payload));
        }), actionRow.append(githubButton, copyButton, emailButton), card.append(
          typeInput,
          titleInput,
          emailInput,
          detailsInput,
          debugRow,
          actionRow,
          createElement("p", {
            className: "tel-settings-helper-text",
            text: t(
              "report.footer",
              "Do not include passwords, tokens or private personal data in the report."
            )
          })
        ), view.append(card), view;
      },
      buildDiagnosticsText,
      copyDiagnostics: async (t = i18n.t) => {
        await copyText(buildDiagnosticsText(t));
      }
    };
  })(), updateModule = (() => {
    const REPO_URL = "https://github.com/diorhc/TGP", DISCUSSIONS_URL = `${REPO_URL}/discussions`, GREASYFORK_URL = "https://greasyfork.org/en/scripts/537433-telegram-plus", UPDATE_URL = "https://update.greasyfork.org/scripts/537433/Telegram%20%2B.user.js", getScriptVersion = () => typeof GM_info < "u" && GM_info?.script?.version ? String(GM_info.script.version) : "2.1.0", createLinkButton = (label, href, className = "tel-settings-link-btn") => {
      const button = createElement("button", {
        type: "button",
        className,
        text: label
      });
      return button.addEventListener("click", () => openInNewTab(href)), button;
    }, makePathIcon = (d) => {
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      svg.setAttribute("viewBox", "0 0 24 24"), svg.setAttribute("fill", "none"), svg.setAttribute("aria-hidden", "true");
      const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
      return path.setAttribute("d", d), path.setAttribute("stroke", "currentColor"), path.setAttribute("stroke-width", "1.5"), path.setAttribute("stroke-linecap", "round"), path.setAttribute("stroke-linejoin", "round"), svg.appendChild(path), svg;
    }, createKickerIcon = () => {
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      svg.setAttribute("class", "app-icon"), svg.setAttribute("width", "90"), svg.setAttribute("height", "90"), svg.setAttribute("viewBox", "0 0 64 64"), svg.setAttribute("version", "1.1"), svg.setAttribute("aria-hidden", "true");
      const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
      return path.setAttribute(
        "d",
        "m23.24,4.62c-0.85,0.45 -2.19,2.12 -4.12,5.13c-1.54,2.41 -2.71,4.49 -3.81,6.8c-0.55,1.14 -1.05,2.2 -1.13,2.35c-0.08,0.16 -0.78,0.7 -1.66,1.28c-1.38,0.91 -1.8,1.29 -1.4,1.28c0.08,0 0.67,-0.35 1.31,-0.77c0.64,-0.42 1.19,-0.76 1.2,-0.74c0.02,0.02 -0.1,0.31 -0.25,0.66c-1.03,2.25 -1.84,5.05 -1.84,6.37c0.01,1.89 0.84,2.67 2.86,2.67c1.08,0 1.94,-0.31 3.66,-1.29c1.84,-1.06 3.03,-1.93 4.18,-3.09c1.69,-1.7 2.91,-3.4 3.28,-4.59c0.59,-1.9 -0.1,-3.08 -2.02,-3.44c-0.87,-0.16 -2.85,-0.14 -3.75,0.06c-1.78,0.38 -2.74,0.76 -2.5,1c0.03,0.03 0.5,-0.1 1.05,-0.28c1.49,-0.48 2.34,-0.59 3.88,-0.53c1.64,0.07 2.09,0.19 2.69,0.75l0.46,0.43l0,0.87c0,0.74 -0.05,0.98 -0.35,1.6c-0.69,1.45 -2.69,3.81 -4.37,5.14c-0.93,0.74 -2.88,1.94 -4.07,2.5c-1.64,0.77 -3.56,0.72 -4.21,-0.11c-0.39,-0.5 -0.5,-1.02 -0.44,-2.11c0.05,-0.85 0.16,-1.32 0.67,-2.86c0.34,-1.01 0.86,-2.38 1.15,-3.04c0.52,-1.18 0.55,-1.22 1.6,-2.14c4.19,-3.65 8.42,-9.4 9.02,-12.26c0.2,-0.94 0.13,-1.46 -0.21,-1.7c-0.31,-0.22 -0.38,-0.21 -0.89,0.06m0.19,0.26c-0.92,0.41 -3.15,3.44 -5.59,7.6c-1.05,1.79 -3.12,5.85 -3.02,5.95c0.07,0.07 1.63,-1.33 2.58,-2.34c1.57,-1.65 3.73,-4.39 4.88,-6.17c1.31,-2.03 2.06,-4.11 1.77,-4.89c-0.13,-0.34 -0.16,-0.35 -0.62,-0.15m11.69,13.32c-0.3,0.6 -1.19,2.54 -1.98,4.32c-1.6,3.62 -1.67,3.71 -2.99,4.34c-1.13,0.54 -2.31,0.85 -3.54,0.92c-0.99,0.06 -1.08,0.04 -1.38,-0.19c-0.28,-0.22 -0.31,-0.31 -0.26,-0.7c0.03,-0.25 0.64,-1.63 1.35,-3.08c1.16,-2.36 2.52,-5.61 2.52,-6.01c0,-0.49 -0.36,0.19 -1.17,2.22c-0.51,1.26 -1.37,3.16 -1.93,4.24c-0.55,1.08 -1.04,2.17 -1.09,2.43c-0.1,0.59 0.07,1.03 0.49,1.28c0.78,0.46 3.3,0.06 5.13,-0.81l0.93,-0.45l-0.66,1.25c-0.7,1.33 -3.36,6.07 -4.31,7.67c-2.02,3.41 -3.96,5.32 -6.33,6.21c-2.57,0.96 -4.92,0.74 -6.14,-0.58c-0.81,-0.88 -0.82,-1.71 -0.04,-3.22c1.22,-2.36 6.52,-6.15 10.48,-7.49c0.52,-0.18 0.95,-0.39 0.95,-0.46c0,-0.21 -0.19,-0.18 -1.24,0.2c-1.19,0.43 -3.12,1.37 -4.34,2.11c-2.61,1.59 -5.44,4.09 -6.13,5.43c-1.15,2.2 -0.73,3.61 1.4,4.6c0.59,0.28 0.75,0.3 2.04,0.3c1.67,0 2.42,-0.18 3.88,-0.89c1.87,-0.92 3.17,-2.13 4.72,-4.41c0.98,-1.44 4.66,-7.88 5.91,-10.33c0.25,-0.49 0.68,-1.19 0.96,-1.56c0.28,-0.37 0.76,-1.15 1.06,-1.73c0.82,-1.59 2.58,-6.10 2.58,-6.6c0,-0.06 -0.07,-0.1 -0.17,-0.1c-0.10,0 -0.39,0.44 -0.71,1.09m-1.34,3.7c-0.93,2.08 -1.09,2.48 -0.87,2.2c0.19,-0.24 1.66,-3.65 1.6,-3.71c-0.02,-0.02 -0.35,0.66 -0.73,1.51"
      ), path.setAttribute("fill", "none"), path.setAttribute("fill-rule", "evenodd"), path.setAttribute("stroke", "currentColor"), svg.appendChild(path), svg;
    };
    return { createView: ({ t }) => {
      const view = createElement("section", {
        className: "tel-settings-view tel-settings-about-view"
      }), hero = createElement("div", { className: "tel-settings-about-hero" }), kicker = createElement("div", { className: "tel-settings-about-kicker" });
      kicker.append(createKickerIcon()), hero.append(
        kicker,
        createElement("h1", {
          className: "tel-settings-about-title",
          text: t("about.title", "Telegram +")
        })
      );
      const card = createElement("div", {
        className: "tel-settings-view-card tel-settings-about-card"
      }), statusSurface = createElement("div", { className: "tel-settings-about-status-surface" }), meta = createElement("div", { className: "tel-settings-about-meta" }), versionRow = createElement("div", { className: "tel-settings-about-version-row" });
      versionRow.append(
        createElement("span", {
          className: "tel-settings-about-version-label",
          text: t("about.currentVersion", "Current version")
        }),
        createElement("span", {
          className: "tel-settings-about-version-badge",
          text: getScriptVersion()
        })
      );
      const status = createElement("span", {
        className: "tel-settings-about-status",
        text: t("about.status", "● Auto-update")
      });
      meta.append(versionRow), statusSurface.append(meta, status);
      const updateRow = createElement("div", { className: "tel-settings-about-update-row" }), checkBtn = createElement("button", {
        type: "button",
        className: "tel-settings-update-btn",
        text: t("about.checkUpdates", "Check updates")
      });
      checkBtn.prepend(
        makePathIcon(
          "M21 12C21 16.9706 16.9706 21 12 21C9.69494 21 7.59227 20.1334 6 18.7083L3 16M3 12C3 7.02944 7.02944 3 12 3C14.3051 3 16.4077 3.86656 18 5.29168L21 8M3 16V22M21 8V2"
        )
      ), checkBtn.addEventListener("click", () => openInNewTab(GREASYFORK_URL)), updateRow.append(
        checkBtn,
        createLinkButton("↗", UPDATE_URL, "tel-settings-update-icon-btn")
      );
      const updatePanel = createElement("div", { className: "tel-settings-about-update-panel" });
      updatePanel.append(statusSurface, updateRow);
      const links = createElement("div", { className: "tel-settings-about-links" });
      links.append(
        createLinkButton("GitHub", REPO_URL),
        createLinkButton("Discussions", DISCUSSIONS_URL),
        createLinkButton("GreasyFork", GREASYFORK_URL)
      );
      const footer = createElement("p", {
        className: "tel-settings-about-footer",
        text: "Made with ❤️ by diorhc · License: MIT"
      });
      return card.append(
        hero,
        createElement("h2", {
          className: "tel-settings-card-title",
          text: t(
            "about.descriptionTitle",
            "Extended Telegram Web experience with downloads, shortcuts and utility tools"
          )
        }),
        updatePanel,
        links,
        footer
      ), view.append(card), view;
    } };
  })(), progressFactory =  (() => {
    const t = (key, fallback) => i18n.t(key, fallback);
    let onCancel = () => {
    }, onRetry = () => {
    }, onReorder = () => {
    }, onExportLogs = () => {
    }, isCollapsed = !1;
    const getList = (container) => container?.querySelector('[data-role="progress-list"]'), syncVisibility = (container) => {
      const list = getList(container);
      list && (list.style.display = isCollapsed ? "none" : "flex");
    }, updateSummary = () => {
      const container = document.getElementById(PROGRESS_CONTAINER_ID);
      if (!container) return;
      const countNode = container.querySelector('[data-role="progress-count"]'), toggleNode = container.querySelector('[data-role="progress-toggle"]'), cards = Array.from(container.querySelectorAll('[id^="tel-downloader-progress-"]')), total = cards.length, inQueue = cards.filter((card) => {
        const status = card.dataset.state || "queued";
        return ["queued", "active", "retrying"].includes(status);
      }).length;
      if (countNode && (countNode.textContent = `${inQueue}/${total}`), toggleNode && (toggleNode.textContent = isCollapsed ? "▾" : "▴", toggleNode.setAttribute(
        "aria-label",
        isCollapsed ? t("progress.expand", "Expand download queue") : t("progress.collapse", "Collapse download queue")
      )), total === 0) {
        container.style.display = "none", isCollapsed = !1, syncVisibility(container), toggleNode && (toggleNode.textContent = "▴");
        return;
      }
      container.style.display = "flex", syncVisibility(container);
    }, ensureContainer = () => {
      let container = document.getElementById(PROGRESS_CONTAINER_ID);
      if (container) return container;
      container = createElement("div", {
        attributes: {
          id: PROGRESS_CONTAINER_ID,
          role: "region",
          "aria-live": "polite",
          "aria-atomic": "false"
        },
        style: styleFactory.progressContainer()
      });
      const header = createElement("div", {
        style: {
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          gap: "8px",
          padding: "8px 10px",
          borderRadius: "12px",
          ...styleFactory.glassPanel(getTheme())
        }
      });
      header.setAttribute("data-role", "progress-header");
      const title = createElement("span", {
        text: t("progress.queueTitle", "Downloads"),
        style: {
          fontSize: "13px",
          fontWeight: "700"
        }
      }), right = createElement("div", {
        style: {
          display: "flex",
          alignItems: "center",
          gap: "8px"
        }
      }), count = createElement("span", {
        text: "0/0",
        style: {
          fontSize: "12px",
          fontWeight: "600",
          opacity: "0.85"
        },
        attributes: { "data-role": "progress-count" }
      }), exportButton = createElement("button", {
        type: "button",
        text: "CSV",
        attributes: { "data-role": "progress-export" },
        style: {
          border: "none",
          background: "transparent",
          color: getTheme() ? "#eaeaea" : "#111",
          cursor: "pointer",
          fontSize: "11px",
          lineHeight: "1",
          padding: "2px 4px",
          fontWeight: "700",
          display: "none"
        },
        ariaLabel: t("progress.exportLog", "Export download log")
      }), devModeEnabled = (() => {
        try {
          return localStorage.getItem("tel_devmode") === "1";
        } catch {
          return !1;
        }
      })();
      exportButton.style.display = devModeEnabled ? "inline-flex" : "none", exportButton.addEventListener("click", () => onExportLogs());
      const toggle = createElement("button", {
        type: "button",
        text: "▴",
        attributes: { "data-role": "progress-toggle" },
        style: {
          border: "none",
          background: "transparent",
          color: getTheme() ? "#eaeaea" : "#111",
          cursor: "pointer",
          fontSize: "14px",
          lineHeight: "1",
          padding: "2px 4px"
        },
        ariaLabel: t("progress.collapse", "Collapse download queue")
      });
      toggle.addEventListener("click", () => {
        isCollapsed = !isCollapsed, updateSummary();
      }), right.append(exportButton, count, toggle), header.append(title, right);
      const list = createElement("div", {
        attributes: { "data-role": "progress-list" },
        style: {
          display: "flex",
          flexDirection: "column",
          gap: "8px"
        }
      });
      return container.append(header, list), container.style.display = "none", appendToRoot(container), updateSummary(), container;
    }, getCard = (taskId) => document.getElementById(`tel-downloader-progress-${taskId}`), createCard = (taskId, fileName) => {
      const isDark = getTheme(), card = createElement("div", {
        attributes: { id: `tel-downloader-progress-${taskId}` },
        style: styleFactory.progressCard(isDark)
      });
      card.dataset.taskId = taskId, card.draggable = !1, card.addEventListener("dragstart", (event) => {
        if (card.dataset.state !== "queued") {
          event.preventDefault();
          return;
        }
        event.dataTransfer.effectAllowed = "move", event.dataTransfer.setData("text/plain", taskId), card.style.opacity = "0.6";
      }), card.addEventListener("dragend", () => {
        card.style.opacity = "1";
      }), card.addEventListener("dragover", (event) => {
        card.dataset.state === "queued" && (event.preventDefault(), event.dataTransfer.dropEffect = "move");
      }), card.addEventListener("drop", (event) => {
        if (event.preventDefault(), card.dataset.state !== "queued") return;
        const sourceTaskId = event.dataTransfer.getData("text/plain");
        !sourceTaskId || sourceTaskId === taskId || onReorder(sourceTaskId, taskId);
      });
      const top = createElement("div", {
        style: {
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          gap: "10px",
          marginBottom: "8px"
        }
      }), title = createElement("p", {
        className: "filename",
        text: fileName,
        style: {
          margin: "0",
          color: isDark ? "#eaeaea" : "#111",
          fontWeight: "600",
          overflow: "hidden",
          textOverflow: "ellipsis",
          whiteSpace: "nowrap",
          flex: "1"
        }
      }), close = createElement("button", {
        type: "button",
        text: "×",
        ariaLabel: `Cancel download ${fileName}`,
        style: {
          border: "none",
          background: "transparent",
          color: isDark ? "#eaeaea" : "#111",
          cursor: "pointer",
          fontSize: "20px",
          lineHeight: "1",
          padding: "0"
        }
      });
      close.addEventListener("click", () => onCancel(taskId)), top.append(title, close);
      const bar = createElement("div", {
        className: "progress",
        role: "progressbar",
        ariaLabel: `Downloading ${fileName}`,
        style: styleFactory.progressBar(isDark)
      });
      bar.setAttribute("aria-valuemin", "0"), bar.setAttribute("aria-valuemax", "100"), bar.setAttribute("aria-valuenow", "0");
      const label = createElement("p", {
        className: "progress-label",
        text: t("progress.queued", "Queued"),
        style: styleFactory.progressLabel(isDark)
      }), fill = createElement("div", {
        className: "progress-fill",
        style: styleFactory.progressFill(
          "linear-gradient(90deg, rgba(96,147,181,1) 0%, rgba(102,201,173,1) 100%)"
        )
      });
      bar.append(label, fill);
      const message = createElement("div", {
        className: "task-message",
        style: {
          marginTop: "8px",
          fontSize: "12px",
          opacity: "0.8",
          minHeight: "16px"
        }
      });
      return card.append(top, bar, message), card;
    }, ensureTask = (taskId, fileName) => {
      const container = ensureContainer(), list = getList(container);
      let card = getCard(taskId);
      return card || (card = createCard(taskId, fileName), list?.appendChild(card), updateSummary()), card;
    }, updateState = (taskId, updateFn) => {
      const card = getCard(taskId);
      card && (updateFn(card), updateSummary());
    }, refreshCardTexts = (card) => {
      if (!card) return;
      const label = card.querySelector(".progress-label"), message = card.querySelector(".task-message"), state2 = card.dataset.state || "queued";
      if (state2 === "queued") {
        const queuedLabel = t("progress.queued", "Queued"), position = Number(card.dataset.queuePosition || "0");
        label && (label.textContent = position > 0 ? `${queuedLabel} #${position}` : queuedLabel), message && message.replaceChildren(
          createElement("span", {
            text: t("progress.waitingSlot", "Waiting for a free slot")
          })
        );
        return;
      }
      if (state2 === "active") {
        message && (message.textContent = t("progress.downloading", "Downloading"));
        return;
      }
      if (state2 === "retrying") {
        const attempt = card.dataset.retryAttempt || "1", delaySec = card.dataset.retryDelaySec || "1";
        label && (label.textContent = `${t("progress.retrying", "Retry")} ${attempt}/${MAX_RETRIES}`), message && (message.textContent = `${t("progress.retryingIn", "Retrying in")} ${delaySec}s`);
        return;
      }
      if (state2 === "completed") {
        label && (label.textContent = t("progress.completed", "Completed")), message && (message.textContent = t("progress.saved", "Saved successfully"));
        return;
      }
      if (state2 === "aborted") {
        label && (label.textContent = t("progress.aborted", "Aborted")), message && (message.textContent = t("progress.cancelledByUser", "Cancelled by user"));
        return;
      }
      if (state2 === "failed") {
        label && (label.textContent = t("progress.failed", "Failed"));
        const retryLink = message?.querySelector("button");
        retryLink && (retryLink.textContent = t("progress.retryButton", "Retry"));
      }
    };
    return {
      configure({ cancelHandler, retryHandler, reorderHandler, exportLogsHandler }) {
        onCancel = cancelHandler, onRetry = retryHandler, onReorder = reorderHandler || (() => {
        }), onExportLogs = exportLogsHandler || (() => {
        });
      },
      ensureContainer,
      ensureTask,
      setQueued: (taskId, fileName, position) => {
        ensureTask(taskId, fileName), updateState(taskId, (card) => {
          card.dataset.state = "queued", card.dataset.queuePosition = String(position || 0), delete card.dataset.retryAttempt, delete card.dataset.retryDelaySec;
          const label = card.querySelector(".progress-label"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message"), title = card.querySelector(".filename");
          if (card.draggable = !0, title && (title.textContent = fileName), label) {
            const queuedLabel = t("progress.queued", "Queued");
            label.textContent = position > 0 ? `${queuedLabel} #${position}` : queuedLabel;
          }
          fill && (fill.style.width = "100%", fill.style.background = "linear-gradient(90deg, #b0bec5 0%, #cfd8dc 100%)"), message && message.replaceChildren(
            createElement("span", {
              text: t("progress.waitingSlot", "Waiting for a free slot")
            })
          );
        });
      },
      setActive: (taskId, fileName) => {
        ensureTask(taskId, fileName), updateState(taskId, (card) => {
          card.dataset.state = "active", card.dataset.queuePosition = "0", delete card.dataset.retryAttempt, delete card.dataset.retryDelaySec;
          const title = card.querySelector(".filename"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message");
          card.draggable = !1, title && (title.textContent = fileName), fill && (fill.style.width = "0%", fill.style.background = "linear-gradient(90deg, rgba(96,147,181,1) 0%, rgba(102,201,173,1) 100%)"), message && (message.textContent = t("progress.downloading", "Downloading"));
        });
      },
      update: (taskId, fileName, percent, speedText = "") => {
        updateState(taskId, (card) => {
          const title = card.querySelector(".filename"), bar = card.querySelector(".progress"), label = card.querySelector(".progress-label"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message");
          title && (title.textContent = fileName), label && (label.textContent = `${percent}%${speedText}`), fill && (fill.style.width = `${percent}%`), bar && (bar.setAttribute("aria-valuenow", String(percent)), speedText && bar.setAttribute("aria-valuetext", `${percent}% ${speedText}`)), message && (message.textContent = t("progress.downloading", "Downloading"));
        });
      },
      setRetrying: (taskId, fileName, attempt, delay) => {
        updateState(taskId, (card) => {
          card.dataset.state = "retrying", card.dataset.retryAttempt = String(attempt || 1), card.dataset.retryDelaySec = String(Math.ceil(delay / 1e3));
          const title = card.querySelector(".filename"), label = card.querySelector(".progress-label"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message");
          card.draggable = !1, title && (title.textContent = fileName), label && (label.textContent = `${t("progress.retrying", "Retry")} ${attempt}/${MAX_RETRIES}`), fill && (fill.style.width = "100%", fill.style.background = "linear-gradient(90deg, #ffb74d 0%, #ffa726 100%)"), message && (message.textContent = `${t("progress.retryingIn", "Retrying in")} ${Math.ceil(delay / 1e3)}s`);
        });
      },
      setCompleted: (taskId) => {
        updateState(taskId, (card) => {
          card.dataset.state = "completed", delete card.dataset.retryAttempt, delete card.dataset.retryDelaySec;
          const label = card.querySelector(".progress-label"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message");
          card.draggable = !1, label && (label.textContent = t("progress.completed", "Completed")), fill && (fill.style.width = "100%", fill.style.background = "#B6C649"), message && (message.textContent = t("progress.saved", "Saved successfully")), setTimeout(() => {
            card.remove(), updateSummary();
          }, 5e3);
        });
      },
      setAborted: (taskId) => {
        updateState(taskId, (card) => {
          card.dataset.state = "aborted", delete card.dataset.retryAttempt, delete card.dataset.retryDelaySec;
          const label = card.querySelector(".progress-label"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message");
          card.draggable = !1, label && (label.textContent = t("progress.aborted", "Aborted")), fill && (fill.style.width = "100%", fill.style.background = "#D16666"), message && (message.textContent = t("progress.cancelledByUser", "Cancelled by user")), setTimeout(() => {
            card.remove(), updateSummary();
          }, 6e3);
        });
      },
      setFailed: (taskId, errorMessage) => {
        updateState(taskId, (card) => {
          card.dataset.state = "failed", delete card.dataset.retryAttempt, delete card.dataset.retryDelaySec;
          const label = card.querySelector(".progress-label"), fill = card.querySelector(".progress-fill"), message = card.querySelector(".task-message");
          if (card.draggable = !0, label && (label.textContent = t("progress.failed", "Failed")), fill && (fill.style.width = "100%", fill.style.background = "#D16666"), message) {
            const retryLink = createElement("button", {
              type: "button",
              text: t("progress.retryButton", "Retry"),
              style: {
                border: "none",
                background: "transparent",
                color: "#2196F3",
                cursor: "pointer",
                padding: "0",
                marginLeft: "6px"
              }
            });
            retryLink.addEventListener("click", () => onRetry(taskId)), message.replaceChildren(
              createElement("span", {
                text: errorMessage || "Download failed"
              }),
              retryLink
            );
          }
        });
      },
      refreshTexts() {
        ensureContainer().querySelectorAll('[id^="tel-downloader-progress-"]').forEach((card) => refreshCardTexts(card)), updateSummary();
      },
      refreshTheme() {
        const container = document.getElementById(PROGRESS_CONTAINER_ID);
        if (!container) return;
        const header = container.querySelector('[data-role="progress-header"]');
        header && applyStyles(header, styleFactory.glassPanel(getTheme()));
        const toggle = container.querySelector('[data-role="progress-toggle"]');
        toggle instanceof HTMLElement && (toggle.style.color = getTheme() ? "#eaeaea" : "#111");
        const exportButton = container.querySelector('[data-role="progress-export"]');
        exportButton instanceof HTMLElement && (exportButton.style.color = getTheme() ? "#eaeaea" : "#111"), container.querySelectorAll('[id^="tel-downloader-progress-"]').forEach((card) => {
          applyStyles(card, styleFactory.progressCard(getTheme()));
          const title = card.querySelector(".filename"), close = card.querySelector("button"), label = card.querySelector(".progress-label"), bar = card.querySelector(".progress");
          title instanceof HTMLElement && (title.style.color = getTheme() ? "#eaeaea" : "#111"), close instanceof HTMLElement && (close.style.color = getTheme() ? "#eaeaea" : "#111"), label instanceof HTMLElement && (label.style.color = getTheme() ? "#fff" : "#111"), bar instanceof HTMLElement && applyStyles(bar, styleFactory.progressBar(getTheme()));
        });
        const exportButtonNode = container.querySelector('[data-role="progress-export"]');
        if (exportButtonNode instanceof HTMLElement) {
          const devMode = (() => {
            try {
              return localStorage.getItem("tel_devmode") === "1";
            } catch {
              return !1;
            }
          })();
          exportButtonNode.style.display = devMode ? "inline-flex" : "none";
        }
      }
    };
  })(), showNotification = (message, type = "info", duration = 3e3) => {
    notificationFactory.show(message, type, duration);
  }, showNotificationIfEnabled = (message, type = "info", duration = 3e3) => {
    (type === "error" || state.settings.enableNotifications) && showNotification(message, type, duration);
  }, downloadCompatibility =  (() => {
    const anchorSupportsDownload = () => "download" in HTMLAnchorElement.prototype, canUseGmDownload = () => typeof GM_download == "function", saveWithGmDownload = (url, fileName, shouldPrompt = !1) => canUseGmDownload() ? new Promise((resolve, reject) => {
      let settled = !1;
      const finish = (result) => {
        settled || (settled = !0, resolve(result));
      }, fail = (errorLike) => {
        if (settled) return;
        settled = !0;
        const message = errorLike?.error || errorLike?.details || errorLike?.message || "GM_download failed";
        reject(new Error(String(message)));
      };
      try {
        !GM_download({
          url,
          name: fileName,
          saveAs: !!shouldPrompt,
          onload: () => finish(!0),
          onerror: fail,
          ontimeout: fail,
          onabort: fail
        }) && !shouldPrompt && setTimeout(() => finish(!1), 500);
      } catch (error) {
        fail(error);
      }
    }) : Promise.resolve(!1), getPickerFunction = () => !PAGE_WINDOW || typeof PAGE_WINDOW.showSaveFilePicker != "function" ? null : PAGE_WINDOW.showSaveFilePicker.bind(PAGE_WINDOW), canUseFileSystemPicker = () => {
      if (!getPickerFunction()) return !1;
      try {
        return PAGE_WINDOW.self === PAGE_WINDOW.top;
      } catch {
        return !1;
      }
    }, canUseSavePromptDialog = () => canUseFileSystemPicker() || canUseGmDownload(), resolveStrategy = (preferred = state.settings.downloadLocation) => {
      switch (preferred) {
        case "picker":
          return canUseFileSystemPicker() ? "picker" : canUseGmDownload() ? "gm-picker" : anchorSupportsDownload() ? "browser" : "tab";
        case "tab":
          return "tab";
        case "auto":
          return canUseFileSystemPicker() ? "picker" : IS_SAFARI ? "tab" : anchorSupportsDownload() || IS_SAFARI ? "browser" : "tab";
        default:
          return IS_SAFARI ? "tab" : anchorSupportsDownload() ? "browser" : "tab";
      }
    }, saveWithPicker = async (blob, fileName) => {
      const showPicker = getPickerFunction();
      if (!showPicker) throw new Error("File picker is not available");
      const writable = await (await showPicker({
        suggestedName: fileName
      })).createWritable();
      await writable.write(blob), await writable.close();
    }, openStreamingPicker = (suggestedName) => {
      const showPicker = getPickerFunction();
      return showPicker ? showPicker({ suggestedName }).then((handle) => handle.createWritable()) : Promise.resolve(null);
    };
    return {
      saveBlob: async (blob, fileName, preferredStrategy) => {
        const strategy = resolveStrategy(preferredStrategy);
        if (strategy === "picker")
          try {
            return await saveWithPicker(blob, fileName), "picker";
          } catch (err) {
            if (err?.name === "AbortError") throw err;
            logger.warn(`Native picker failed, falling back: ${err.message}`);
          }
        const blobUrl = URL.createObjectURL(blob);
        try {
          if (strategy === "gm-picker" && canUseGmDownload())
            try {
              if (await saveWithGmDownload(blobUrl, fileName, !0)) return "gm-picker";
              if (logger.warn(
                "GM_download(saveAs:true) did not surface a dialog; opening blob in a new tab."
              ), openInNewTab(blobUrl)) return "tab";
            } catch (err) {
              if (err?.message?.toLowerCase().includes("abort")) throw err;
              logger.warn(`GM_download(saveAs:true) failed: ${err.message}`);
            }
          const normalized = strategy === "gm-picker" ? "browser" : strategy, fallbackStrategy = IS_SAFARI && normalized !== "picker" ? "tab" : normalized;
          return openWithFallback(
            blobUrl,
            fallbackStrategy === "tab" ? void 0 : fileName,
            fallbackStrategy === "tab" ? "tab" : "browser"
          );
        } finally {
          setTimeout(() => URL.revokeObjectURL(blobUrl), IS_SAFARI ? 3e4 : 6e3);
        }
      },
      saveUrl: async (url, fileName, preferredStrategy) => {
        const strategy = resolveStrategy(preferredStrategy);
        if (strategy === "picker") {
          const response = await fetch(url);
          if (!response.ok) throw new Error(`Failed to fetch file: ${response.status}`);
          const blob = await response.blob();
          return await saveWithPicker(blob, fileName), "picker";
        }
        if (strategy === "gm-picker" && canUseGmDownload())
          try {
            if (await saveWithGmDownload(url, fileName, !0)) return "gm-picker";
            if (logger.warn("GM_download(saveAs:true) did not surface a dialog; opening URL in a new tab."), openInNewTab(url)) return "tab";
          } catch (err) {
            if (err?.message?.toLowerCase().includes("abort")) throw err;
            logger.warn(`GM_download(saveAs:true) failed: ${err.message}`);
          }
        const finalStrategy = strategy === "gm-picker" ? "browser" : strategy;
        return openWithFallback(url, finalStrategy === "browser" ? fileName : void 0, finalStrategy);
      },
      resolveStrategy,
      acquireWritable: (suggestedName) => resolveStrategy() !== "picker" || !canUseFileSystemPicker() ? Promise.resolve(null) : openStreamingPicker(suggestedName).catch((err) => {
        if (err?.name === "AbortError") throw err;
        return logger.warn(`Picker open failed, falling back: ${err.message}`), null;
      }),
      canUseStreamingPicker: () => resolveStrategy() === "picker" && canUseFileSystemPicker(),
      isPickerSupported: () => canUseFileSystemPicker(),
      isSavePromptSupported: () => canUseSavePromptDialog()
    };
  })(), queueManager = (() => {
    const tasks =  new Map(), pending = [], active =  new Set(), eventLog = [], MAX_LOG_ENTRIES = 500, pushEventLog = (event, task, details = "") => {
      eventLog.push({
        at: ( new Date()).toISOString(),
        event,
        taskId: task?.id || "",
        fileName: task?.fileName || "",
        mediaKind: task?.mediaKind || "",
        details
      }), eventLog.length > MAX_LOG_ENTRIES && eventLog.splice(0, eventLog.length - MAX_LOG_ENTRIES);
    }, exportLogsCsv = () => {
      if (eventLog.length === 0) {
        showNotification(i18n.t("progress.noLog", "No download events yet"), "info");
        return;
      }
      const escapeCsv = (value) => `"${String(value || "").replace(/"/g, '""')}"`, csv = `${[
        ["at", "event", "taskId", "fileName", "mediaKind", "details"],
        ...eventLog.map((entry) => [
          entry.at,
          entry.event,
          entry.taskId,
          entry.fileName,
          entry.mediaKind,
          entry.details
        ])
      ].map((row) => row.map(escapeCsv).join(",")).join(`
`)}
`, blob = new Blob([csv], { type: "text/csv;charset=utf-8" }), blobUrl = URL.createObjectURL(blob), stamp = ( new Date()).toISOString().replace(/[:.]/g, "-");
      try {
        triggerAnchorDownload(blobUrl, `telegram-plus-download-log-${stamp}.csv`), showNotification(i18n.t("progress.logExported", "Download log exported"), "success");
      } finally {
        setTimeout(() => URL.revokeObjectURL(blobUrl), 3e3);
      }
    }, hasRetryTimers = () => Array.from(tasks.values()).some((task) => !!task?.retryTimer), getDiagnosticsSnapshot = () => {
      const statusCounts = Array.from(tasks.values()).reduce((acc, task) => {
        const status = task?.status || "unknown";
        return acc[status] = (acc[status] || 0) + 1, acc;
      }, {});
      return {
        pendingCount: pending.length,
        activeCount: active.size,
        totalTasks: tasks.size,
        maxActiveDownloads: MAX_ACTIVE_DOWNLOADS,
        statusCounts,
        recentEvents: eventLog.slice(-20)
      };
    }, isQueueIdle = () => active.size === 0 && pending.length === 0 && !hasRetryTimers(), updateQueuedPositions = () => {
      pending.forEach((task, index) => {
        progressFactory.setQueued(task.id, task.fileName, index + 1);
      });
    }, reorder = (sourceTaskId, targetTaskId) => {
      const sourceIndex = pending.findIndex((task) => task.id === sourceTaskId), targetIndex = pending.findIndex((task) => task.id === targetTaskId);
      if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex) return;
      const [moved] = pending.splice(sourceIndex, 1);
      pending.splice(targetIndex, 0, moved), pushEventLog("reordered", moved, `before:${targetTaskId}`), updateQueuedPositions();
    }, finalizeTask = (task, removeTask = !1) => {
      active.delete(task.id), updateQueuedPositions(), processQueue(), removeTask && (task.retryTimer && (clearTimeout(task.retryTimer), task.retryTimer = null), tasks.delete(task.id));
    }, scheduleRetry = (task, error) => {
      const retryPlan = getRetryPlan({
        retryCount: task.retryCount,
        error,
        maxRetries: MAX_RETRIES,
        baseDelay: RETRY_DELAY_BASE
      });
      if (task.retryCount = retryPlan.retryCount, retryPlan.action === "fail") {
        task.status = "failed", pushEventLog("failed", task, error?.message || "retry exhausted"), progressFactory.setFailed(
          task.id,
          error.message || i18n.t("notification.downloadFailed", "Download failed")
        ), showNotificationIfEnabled(
          `${i18n.t("notification.downloadFailed", "Download failed")}: ${task.fileName}`,
          "error"
        ), finalizeTask(task);
        return;
      }
      const delay = retryPlan.delay;
      task.status = "retrying", pushEventLog("retrying", task, `attempt:${task.retryCount} delayMs:${delay}`), progressFactory.setRetrying(task.id, task.fileName, task.retryCount, delay), finalizeTask(task), task.retryTimer = setTimeout(() => {
        task.retryTimer = null, task.status = "queued", pending.unshift(task), updateQueuedPositions(), processQueue();
      }, delay);
    }, processQueue = () => {
      if (pending.length === 0) return;
      const activationIndexes = getQueueActivationIndexes({
        pendingStatuses: pending.map((task) => task.status),
        activeCount: active.size,
        maxActive: MAX_ACTIVE_DOWNLOADS
      });
      if (activationIndexes.length === 0) return;
      const tasksToStart = [];
      for (let index = activationIndexes.length - 1; index >= 0; index -= 1) {
        const pendingIndex = activationIndexes[index], [task] = pending.splice(pendingIndex, 1);
        task && tasksToStart.unshift(task);
      }
      tasksToStart.forEach((task) => {
        !task || task.status !== "queued" || (active.add(task.id), task.status = "active", pushEventLog("started", task), task.abortController = new AbortController(), progressFactory.setActive(task.id, task.fileName), updateQueuedPositions(), task.execute(task).then(() => {
          task.status = "completed", pushEventLog("completed", task), progressFactory.setCompleted(task.id), finalizeTask(task, !0), isQueueIdle() && showNotificationIfEnabled(
            `${i18n.t("notification.downloadCompleted", "Download completed")}: ${task.fileName}`,
            "success"
          );
        }).catch((error) => {
          if (error?.name === "AbortError" || task.status === "aborted") {
            task.status = "aborted", pushEventLog("aborted", task, "abort signal"), progressFactory.setAborted(task.id), finalizeTask(task, !0);
            return;
          }
          logger.error(error.message || String(error), task.fileName), scheduleRetry(task, error);
        }));
      });
    }, enqueue = (task) => (task.retryCount = task.retryCount || 0, task.status = "queued", pushEventLog("queued", task), tasks.set(task.id, task), progressFactory.ensureTask(task.id, task.fileName), pending.push(task), updateQueuedPositions(), processQueue(), task.id), cancel = (taskId) => {
      const task = tasks.get(taskId);
      if (!task) return;
      task.retryTimer && (clearTimeout(task.retryTimer), task.retryTimer = null);
      const pendingIndex = pending.findIndex((item) => item.id === taskId), cancelPlan = getCancelPlan({
        inPendingQueue: pendingIndex >= 0,
        hasAbortController: !!task.abortController
      });
      if (cancelPlan === "remove-pending") {
        pending.splice(pendingIndex, 1), task.status = "aborted", pushEventLog("aborted", task, "cancelled while queued"), progressFactory.setAborted(task.id), task.clearResume?.(), tasks.delete(task.id), updateQueuedPositions();
        return;
      }
      cancelPlan === "abort-active" && (task.status = "aborted", pushEventLog("aborted", task, "cancelled while active"), task.clearResume?.(), task.abortController.abort());
    }, retry = (taskId) => {
      const task = tasks.get(taskId);
      task && canManualRetry(task.status) && (task.retryTimer && (clearTimeout(task.retryTimer), task.retryTimer = null), task.retryCount = 0, task.status = "queued", pushEventLog("manual-retry", task), pending.unshift(task), updateQueuedPositions(), processQueue());
    };
    return progressFactory.configure({
      cancelHandler: cancel,
      retryHandler: retry,
      reorderHandler: reorder,
      exportLogsHandler: exportLogsCsv
    }), {
      enqueue,
      cancel,
      retry,
      isIdle: isQueueIdle,
      exportLogsCsv,
      getDiagnosticsSnapshot
    };
  })(), downloadsModule = (() => {
    const normalizeDownloadUrl = (url) => {
      const raw = String(url || "").trim();
      if (!raw) return null;
      if (/^(?:blob:|data:)/i.test(raw))
        return raw;
      try {
        const parsed = new URL(raw, location.href), path = parsed.pathname || "/";
        return parsed.origin === location.origin && (path === "/" || path === "/k" || path === "/k/" || path === "/z" || path === "/z/") || !/^https?:$/.test(parsed.protocol) ? null : parsed.href;
      } catch {
        return null;
      }
    }, requireDownloadUrl = (url, mediaKind) => {
      const normalizedUrl = normalizeDownloadUrl(url);
      if (!normalizedUrl)
        throw new Error(`Invalid ${mediaKind} URL`);
      if (/^blob:/i.test(normalizedUrl) && (mediaKind === "video" || mediaKind === "audio"))
        throw new Error(
          i18n.t(
            "notification.streamBlobUnsupported",
            "This media is streamed via blob URL. Full download is not available in this browser."
          )
        );
      return normalizedUrl;
    }, resumeStorage =  (() => {
      const DB_NAME = "tel_downloader_resume", STORE_NAME = "chunks";
      let dbPromise = null, prunePromise = null;
      const isSupported = () => typeof indexedDB < "u", openDb = () => isSupported() ? dbPromise || (dbPromise = new Promise((resolve, reject) => {
        const request = indexedDB.open(DB_NAME, 1);
        request.onupgradeneeded = () => {
          const db = request.result;
          db.objectStoreNames.contains(STORE_NAME) || db.createObjectStore(STORE_NAME, { keyPath: "key" });
        }, request.onsuccess = () => resolve(request.result), request.onerror = () => reject(request.error || new Error("Failed to open resume DB"));
      }).catch((error) => (logger.warn(`Resume DB unavailable: ${error.message}`), null)), dbPromise) : Promise.resolve(null), withStore = async (mode, work) => {
        const db = await openDb();
        return db ? new Promise((resolve, reject) => {
          const store = db.transaction(STORE_NAME, mode).objectStore(STORE_NAME);
          let request;
          try {
            request = work(store);
          } catch (error) {
            reject(error);
            return;
          }
          request.onsuccess = () => resolve(request.result), request.onerror = () => reject(request.error || new Error("Resume DB request failed"));
        }).catch((error) => (logger.warn(`Resume DB operation failed: ${error.message}`), null)) : null;
      }, updatedAtMs = (record) => Number(record?.updatedAt || 0), isFreshRecord = (record, now) => now - updatedAtMs(record) <= 864e5, byUpdatedAtDesc = (left, right) => updatedAtMs(right) - updatedAtMs(left), hasResumeKey = (record) => !!record?.key, hasResumeIdentity = (record) => hasResumeKey(record) && !!record?.url && !!record?.mediaKind, normalizeRecordForSave = (record) => ({
        ...record,
        updatedAt: Number(record?.updatedAt || Date.now())
      }), prune = async () => prunePromise || (prunePromise = (async () => {
        const records = await withStore("readonly", (store) => store.getAll());
        if (!Array.isArray(records) || records.length === 0) return;
        const now = Date.now(), staleKeys = records.filter((record) => !isFreshRecord(record, now)).map((record) => record.key).filter(Boolean);
        for (const key of staleKeys)
          await withStore("readwrite", (store) => store.delete(key));
        const freshRecords = records.filter((record) => hasResumeKey(record) && isFreshRecord(record, now)).sort(byUpdatedAtDesc);
        if (freshRecords.length > 50) {
          const extra = freshRecords.slice(50);
          for (const record of extra)
            await withStore("readwrite", (store) => store.delete(record.key));
        }
      })().finally(() => {
        prunePromise = null;
      }), prunePromise);
      return {
        async load(key) {
          if (!key) return null;
          const record = await withStore("readonly", (store) => store.get(key));
          if (!record) return null;
          const now = Date.now();
          return isFreshRecord(record, now) ? record : (await withStore("readwrite", (store) => store.delete(key)), null);
        },
        async save(record) {
          hasResumeKey(record) && (await withStore("readwrite", (store) => store.put(normalizeRecordForSave(record))), prune());
        },
        async remove(key) {
          key && await withStore("readwrite", (store) => store.delete(key));
        },
        async list() {
          const records = await withStore("readonly", (store) => store.getAll());
          if (!Array.isArray(records)) return [];
          const now = Date.now();
          return records.filter((record) => hasResumeIdentity(record) && isFreshRecord(record, now)).sort(byUpdatedAtDesc);
        }
      };
    })(), mediaDefaults = {
      video: {
        defaultExtension: "mp4",
        fallbackMime: "video/mp4",
        idPrefix: "video"
      },
      audio: {
        defaultExtension: "ogg",
        fallbackMime: "audio/ogg",
        idPrefix: "audio"
      },
      image: {
        defaultExtension: "jpg",
        fallbackMime: "image/jpeg",
        idPrefix: "image"
      },
      gif: {
        defaultExtension: "gif",
        fallbackMime: "image/gif",
        idPrefix: "gif"
      },
      sticker: {
        defaultExtension: "webp",
        fallbackMime: "image/webp",
        idPrefix: "sticker"
      }
    }, accelerateDownloadResponse = async (res) => {
      if (!res || !res.ok || !res.body) return res;
      const contentType = res.headers.get("Content-Type") || "", contentLength = Number(res.headers.get("Content-Length"));
      if (!((/^video\//.test(contentType) || /^audio\//.test(contentType) || contentType === "application/octet-stream") && Number.isFinite(contentLength) && contentLength > 0 && contentLength <= EAGER_DOWNLOAD_LIMIT)) return res;
      const blob = await res.blob(), headers = new Headers();
      return res.headers.forEach((value, key) => headers.append(key, value)), new Response(blob, {
        status: res.status,
        statusText: res.statusText,
        headers
      });
    }, fetchDownloadPart = (url, offset, signal) => fetch(url, {
      method: "GET",
      headers: { Range: `bytes=${offset}-` },
      signal
    }).then(accelerateDownloadResponse), assertExpectedMimeType = (contentType, mediaKind, label = "MIME type") => {
      if (!isValidMimeType(contentType, mediaKind))
        throw new Error(`Unexpected ${label}: ${contentType.split(";")[0]}`);
    }, abortWritableSafely = async (writableHandle) => {
      if (writableHandle)
        try {
          await writableHandle.abort();
        } catch {
        }
    }, executeQueuedDownload = async (task) => {
      const writable = task.writable || null;
      if (task.writable = null, !writable && !task.resumeState && task.resumeKey) {
        const persisted = await resumeStorage.load(task.resumeKey);
        persisted && persisted.url === task.url && persisted.mediaKind === task.mediaKind && (task.resumeState = {
          nextOffset: persisted.nextOffset,
          totalSize: persisted.totalSize,
          chunks: Array.isArray(persisted.chunks) ? persisted.chunks : []
        }, persisted.fileName && (task.fileName = persisted.fileName));
      }
      const resumeState = !writable && task.resumeState ? task.resumeState : { nextOffset: 0, totalSize: null, chunks: [] };
      let nextOffset = Number(resumeState.nextOffset) || 0, totalSize = Number.isFinite(resumeState.totalSize) ? resumeState.totalSize : null, lastUpdateTime = Date.now(), lastBytes = nextOffset;
      const blobs = writable ? null : Array.isArray(resumeState.chunks) ? resumeState.chunks : [];
      let currentFileName = task.fileName;
      const persistResumeState = async () => {
        writable || (task.resumeState = {
          nextOffset,
          totalSize,
          chunks: blobs
        }, await resumeStorage.save({
          key: task.resumeKey,
          url: task.url,
          mediaKind: task.mediaKind,
          fileName: task.fileName,
          nextOffset,
          totalSize,
          chunks: blobs,
          updatedAt: Date.now()
        }));
      }, chunkLoop = async () => {
        for (; ; ) {
          const response = await fetchDownloadPart(task.url, nextOffset, task.abortController.signal);
          if (![200, 206].includes(response.status))
            throw new Error(`Unexpected response: ${response.status}`);
          const contentType = response.headers.get("Content-Type");
          if (!contentType) throw new Error("Missing Content-Type header");
          assertExpectedMimeType(contentType, task.mediaKind);
          const extension = getExtensionFromMime(contentType, task.defaultExtension);
          currentFileName = currentFileName.replace(/\.\w+$/, `.${extension}`), task.fileName = currentFileName;
          const blob = await response.blob(), contentRange = response.headers.get("Content-Range");
          if (contentRange) {
            const match = contentRange.match(contentRangeRegex);
            if (!match) throw new Error("Invalid Content-Range header");
            const start = Number(match[1]), end = Number(match[2]), size = Number(match[3]);
            if (start !== nextOffset) throw new Error("Chunk offset mismatch");
            if (totalSize && size !== totalSize) throw new Error("File size changed");
            nextOffset = end + 1, totalSize = size;
          } else if (response.status === 200) {
            const contentLength = Number(response.headers.get("Content-Length"));
            totalSize = Number.isFinite(contentLength) && contentLength > 0 ? contentLength : blob.size, nextOffset > 0 && blobs && (blobs.length = 0), nextOffset = blob.size;
          } else
            throw new Error("Missing Content-Range header");
          const now = Date.now(), timeDiff = (now - lastUpdateTime) / 1e3;
          if (timeDiff > 0) {
            const bytesDiff = nextOffset - lastBytes;
            lastBytes = nextOffset, lastUpdateTime = now;
            const speedMBNum = bytesDiff / timeDiff / (1024 * 1024), speedMB = speedMBNum.toFixed(2), percent = totalSize ? Number((nextOffset * 100 / totalSize).toFixed(0)) : 0;
            progressFactory.update(
              task.id,
              currentFileName,
              percent,
              speedMBNum > 0 ? ` (${speedMB} MB/s)` : ""
            );
          }
          if (writable ? await writable.write(blob) : (blobs.push(blob), await persistResumeState()), nextOffset >= totalSize) break;
        }
      };
      try {
        if (await chunkLoop(), writable)
          await writable.close(), logger.info("Saved using strategy: picker (streaming)", currentFileName);
        else {
          const strategy = await downloadCompatibility.saveBlob(
            new Blob(blobs, { type: task.fallbackMime }),
            currentFileName,
            state.settings.downloadLocation
          );
          logger.info(`Saved using strategy: ${strategy}`, currentFileName);
        }
        task.resumeState = null, await resumeStorage.remove(task.resumeKey);
      } catch (err) {
        throw await abortWritableSafely(writable), err;
      }
    }, createQueueTask = ({ url, mediaKind, defaultExtension, fallbackMime, idPrefix }) => ({
      id: randomId(idPrefix),
      resumeKey: `${idPrefix}:${hashCode(url).toString(36)}`,
      url,
      mediaKind,
      defaultExtension,
      fallbackMime,
      fileName: extractFileName(url, defaultExtension),
      retryCount: 0,
      abortController: null,
      retryTimer: null,
      writable: null,
      resumeState: null,
      clearResume() {
        this.resumeState = null, resumeStorage.remove(this.resumeKey);
      },
      execute: executeQueuedDownload
    }), restorePendingDownloads = async () => {
      const records = await resumeStorage.list();
      if (records.length === 0) return;
      const promptTemplate = i18n.t(
        "progress.resumePrompt",
        "Found {count} unfinished download(s). Resume now?"
      ), promptText = String(promptTemplate).replace("{count}", String(records.length));
      window.confirm(promptText) && records.forEach((record) => {
        const defaults = mediaDefaults[record.mediaKind] || mediaDefaults.video, task = createQueueTask({
          url: record.url,
          mediaKind: record.mediaKind,
          defaultExtension: defaults.defaultExtension,
          fallbackMime: defaults.fallbackMime,
          idPrefix: defaults.idPrefix
        });
        task.resumeKey = record.key, task.fileName = record.fileName || task.fileName, task.resumeState = {
          nextOffset: Number(record.nextOffset || 0),
          totalSize: Number.isFinite(record.totalSize) ? record.totalSize : null,
          chunks: Array.isArray(record.chunks) ? record.chunks : []
        }, queueManager.enqueue(task);
      });
    }, enqueueWithPicker = (task) => {
      downloadCompatibility.acquireWritable(task.fileName).then((writable) => {
        task.writable = writable, queueManager.enqueue(task);
      }).catch((err) => {
        if (err?.name === "AbortError") {
          logger.info("Picker cancelled by user", task.fileName);
          return;
        }
        logger.error(err.message || String(err), task.fileName), queueManager.enqueue(task);
      });
    }, createSingleVisualTask = ({
      url,
      idPrefix,
      defaultExtension,
      fallbackMime,
      expectedKind = "image"
    }) => ({
      id: randomId(idPrefix),
      url,
      mediaKind: expectedKind,
      defaultExtension,
      fallbackMime,
      fileName: extractFileName(url, defaultExtension),
      retryCount: 0,
      abortController: null,
      retryTimer: null,
      writable: null,
      resumeState: null,
      clearResume() {
        this.resumeState = null;
      },
      async execute(task) {
        const response = await fetch(task.url, {
          signal: task.abortController.signal
        });
        if (!response.ok)
          throw new Error(`${idPrefix} response: ${response.status}`);
        const contentType = response.headers.get("Content-Type") || fallbackMime;
        assertExpectedMimeType(contentType, expectedKind, `${idPrefix} MIME type`), task.fileName = task.fileName.replace(
          /\.\w+$/,
          `.${getExtensionFromMime(contentType, defaultExtension)}`
        );
        const blob = await response.blob();
        if (task.writable)
          try {
            await task.writable.write(blob), await task.writable.close(), logger.info("Saved using strategy: picker", task.fileName);
            return;
          } catch (err) {
            if (await abortWritableSafely(task.writable), task.writable = null, err?.name === "AbortError") throw err;
            logger.warn(`Picker write failed, using fallback: ${err.message}`);
          }
        const strategy = await downloadCompatibility.saveBlob(
          blob,
          task.fileName,
          state.settings.downloadLocation
        );
        logger.info(`Saved using strategy: ${strategy}`, task.fileName);
      }
    }), getCaptureStreamFunction = (videoElement) => videoElement && typeof videoElement.captureStream == "function" ? () => videoElement.captureStream() : videoElement && typeof videoElement.mozCaptureStream == "function" ? () => videoElement.mozCaptureStream() : null, pickRecorderMimeType = () => typeof MediaRecorder > "u" || typeof MediaRecorder.isTypeSupported != "function" ? "" : [
      "video/webm;codecs=vp9,opus",
      "video/webm;codecs=vp8,opus",
      "video/webm",
      "video/mp4"
    ].find((mime) => MediaRecorder.isTypeSupported(mime)) || "", captureStreamedVideo = async (videoElement, options = {}) => {
      if (!(videoElement instanceof HTMLVideoElement) || typeof MediaRecorder > "u") return !1;
      const getStream = getCaptureStreamFunction(videoElement);
      if (!getStream) return !1;
      let stream;
      try {
        stream = getStream();
      } catch {
        return !1;
      }
      if (!stream) return !1;
      const stopTracks = () => {
        try {
          stream.getTracks().forEach((track) => track.stop());
        } catch {
        }
      }, mimeType = pickRecorderMimeType();
      let recorder;
      try {
        recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
      } catch {
        return stopTracks(), !1;
      }
      const maxDurationMs = Math.min(Math.max(Number(options.maxDurationMs || 12e3), 3e3), 3e4), chunks = [];
      return new Promise((resolve) => {
        let timeoutId = null;
        const finalize = async () => {
          if (timeoutId && (clearTimeout(timeoutId), timeoutId = null), chunks.length === 0) {
            stopTracks(), resolve(!1);
            return;
          }
          try {
            const outputType = recorder.mimeType || "video/webm", blob = new Blob(chunks, { type: outputType }), fileName = `telegram-stream-${Date.now()}.webm`, strategy = await downloadCompatibility.saveBlob(
              blob,
              fileName,
              state.settings.downloadLocation
            );
            logger.info(`Saved stream capture using strategy: ${strategy}`, fileName), stopTracks(), resolve(strategy !== "failed");
          } catch (error) {
            logger.warn(`Failed to save stream capture: ${error.message}`), stopTracks(), resolve(!1);
          }
        };
        recorder.ondataavailable = (event) => {
          event.data && event.data.size > 0 && chunks.push(event.data);
        }, recorder.onerror = () => {
          if (recorder.state !== "inactive")
            try {
              recorder.stop();
            } catch {
            }
        }, recorder.onstop = () => {
          finalize();
        };
        try {
          recorder.start();
        } catch {
          stopTracks(), resolve(!1);
          return;
        }
        timeoutId = setTimeout(() => {
          if (recorder.state !== "inactive")
            try {
              recorder.stop();
            } catch {
              stopTracks(), resolve(!1);
            }
        }, maxDurationMs);
      });
    }, createQueuedDownloadHandler = ({ idPrefix, mediaKind, defaultExtension, fallbackMime }) => (url) => {
      const task = createQueueTask({
        url: requireDownloadUrl(url, idPrefix),
        mediaKind,
        defaultExtension,
        fallbackMime,
        idPrefix
      });
      enqueueWithPicker(task);
    }, createVisualDownloadHandler = ({
      idPrefix,
      defaultExtension,
      fallbackMime,
      expectedKind = "image"
    }) => (url) => {
      const task = createSingleVisualTask({
        url: requireDownloadUrl(url, idPrefix),
        idPrefix,
        defaultExtension,
        fallbackMime,
        expectedKind
      });
      enqueueWithPicker(task);
    }, downloadVideo = createQueuedDownloadHandler({
      idPrefix: "video",
      mediaKind: "video",
      defaultExtension: "mp4",
      fallbackMime: "video/mp4"
    }), downloadAudio = createQueuedDownloadHandler({
      idPrefix: "audio",
      mediaKind: "audio",
      defaultExtension: "ogg",
      fallbackMime: "audio/ogg"
    }), downloadImage = createVisualDownloadHandler({
      idPrefix: "image",
      defaultExtension: "jpg",
      fallbackMime: "image/jpeg",
      expectedKind: "image"
    }), downloadGif = createVisualDownloadHandler({
      idPrefix: "gif",
      defaultExtension: "gif",
      fallbackMime: "image/gif",
      expectedKind: "image"
    }), downloadSticker = createVisualDownloadHandler({
      idPrefix: "sticker",
      defaultExtension: "webp",
      fallbackMime: "image/webp",
      expectedKind: "image"
    });
    return {
      downloadVideo,
      downloadAudio,
      downloadImage,
      downloadGif,
      downloadSticker,
      captureStreamedVideo,
      restorePendingDownloads
    };
  })(), uiModule = {
    refresh() {
      uiMediaButtons.refresh(), uiLauncher.refresh();
    }
  }, uiLauncher =  (() => {
    const settingsLabel = () => i18n.t("settings.title", "Settings"), getSettingsLauncherStyles = (isDark) => ({
      position: "fixed",
      right: "14px",
      bottom: "24px",
      width: "42px",
      height: "42px",
      borderRadius: "999px",
      border: isDark ? "1px solid rgba(255,255,255,0.15)" : "1px solid rgba(0,0,0,0.12)",
      background: isDark ? "rgba(28, 34, 44, 0.78)" : "rgba(255,255,255,0.85)",
      color: isDark ? "#f2f4f8" : "#20242c",
      boxShadow: isDark ? "0 8px 24px rgba(0,0,0,0.45)" : "0 8px 24px rgba(0,0,0,0.18)",
      backdropFilter: "blur(8px) saturate(140%)",
      WebkitBackdropFilter: "blur(8px) saturate(140%)",
      cursor: "pointer",
      zIndex: "1599",
      fontSize: "21px",
      lineHeight: "1",
      display: "inline-flex",
      alignItems: "center",
      justifyContent: "center",
      userSelect: "none",
      transition: "transform .15s ease, box-shadow .15s ease, background .2s ease"
    }), ensureSettingsLauncher = () => {
      let launcher = document.getElementById("tel-settings-launcher");
      launcher instanceof HTMLButtonElement || (launcher = createElement("button", {
        type: "button",
        text: "⚙",
        attributes: { id: "tel-settings-launcher" },
        ariaLabel: settingsLabel(),
        title: settingsLabel()
      }), launcher.addEventListener("click", (event) => {
        event.preventDefault(), event.stopPropagation(), settingsModule.show();
      }), launcher.addEventListener("mouseenter", () => {
        launcher.style.transform = "translateY(-1px)";
      }), launcher.addEventListener("mouseleave", () => {
        launcher.style.transform = "translateY(0)";
      }), appendToRoot(launcher)), applyStyles(launcher, getSettingsLauncherStyles(getTheme())), launcher.setAttribute("aria-label", settingsLabel()), launcher.title = settingsLabel();
    };
    return {
      refresh() {
        ensureSettingsLauncher();
      }
    };
  })(), getText = (...selectors) => {
    for (const selector of selectors) {
      const value = document.querySelector(selector)?.textContent?.trim();
      if (value) return value;
    }
    return "";
  }, getActiveMediaContext = () => {
    const candidates = [
      {
        root: document.querySelector(TELEGRAM_SELECTORS.webKMediaViewer),
        videoSelector: "video",
        titleSelectors: [".media-viewer-name .peer-title"],
        metaSelectors: [".media-viewer-date"],
        prefix: "webk-media"
      },
      {
        root: document.querySelector(TELEGRAM_SELECTORS.webZActiveSlide),
        videoSelector: "video",
        titleSelectors: [
          `${TELEGRAM_SELECTORS.webZProfileInfo} .fullName`,
          `${TELEGRAM_SELECTORS.webZMediaViewerRoot} .fullName`,
          `${TELEGRAM_SELECTORS.webZMediaViewerRoot} .Title`
        ],
        metaSelectors: [
          TELEGRAM_SELECTORS.webZMessageMeta,
          `${TELEGRAM_SELECTORS.webZMediaViewerRoot} .subtitle`
        ],
        prefix: "webz-media"
      },
      {
        root: document.querySelector(TELEGRAM_SELECTORS.storiesViewer),
        videoSelector: "video.media-video",
        titleSelectors: [`${TELEGRAM_SELECTORS.storiesViewer} [class*='ViewerStoryTitle']`],
        metaSelectors: [],
        prefix: "webk-story"
      },
      {
        root: document.querySelector(TELEGRAM_SELECTORS.storyViewer),
        videoSelector: "video",
        titleSelectors: [
          `${TELEGRAM_SELECTORS.storyViewer} .fullName`,
          `${TELEGRAM_SELECTORS.storyViewer} .Title`
        ],
        metaSelectors: [],
        prefix: "webz-story"
      }
    ];
    for (const candidate of candidates) {
      const video = candidate.root?.querySelector(candidate.videoSelector);
      if (!video) continue;
      const sourceUrl = video.currentSrc || video.src;
      if (!sourceUrl) continue;
      const title = getText(...candidate.titleSelectors), meta = getText(...candidate.metaSelectors), storageSource = title || meta ? `${candidate.prefix}:${title}:${meta}` : `${candidate.prefix}:${location.pathname}:${sourceUrl}`;
      return {
        video,
        key: `${candidate.prefix}:${hashCode(storageSource).toString(36)}`
      };
    }
    const fallbackVideos = [
      {
        selector: `${TELEGRAM_SELECTORS.webKMediaViewer} video`,
        prefix: "webk-fallback"
      },
      {
        selector: `${TELEGRAM_SELECTORS.webZMediaViewerRoot} video`,
        prefix: "webz-fallback"
      },
      {
        selector: `${TELEGRAM_SELECTORS.storiesViewer} video`,
        prefix: "story-fallback"
      },
      {
        selector: `${TELEGRAM_SELECTORS.storyViewer} video`,
        prefix: "story-fallback-z"
      }
    ];
    for (const fallback of fallbackVideos) {
      const video = document.querySelector(fallback.selector);
      if (!(video instanceof HTMLVideoElement)) continue;
      const sourceUrl = video.currentSrc || video.src;
      if (!sourceUrl) continue;
      const keySource = `${fallback.prefix}:${location.pathname}:${sourceUrl}`;
      return {
        video,
        key: `${fallback.prefix}:${hashCode(keySource).toString(36)}`
      };
    }
    return null;
  }, getCandidateMediaUrl = (mediaElement) => {
    if (!(mediaElement instanceof HTMLMediaElement)) return null;
    const candidates = [
      mediaElement.currentSrc,
      mediaElement.getAttribute("src"),
      mediaElement.querySelector("source")?.getAttribute("src")
    ];
    for (const candidate of candidates) {
      const raw = String(candidate || "").trim();
      if (raw)
        try {
          const parsed = new URL(raw, location.href), path = parsed.pathname || "/";
          if (!(parsed.origin === location.origin && (path === "/" || path === "/k" || path === "/k/" || path === "/z" || path === "/z/"))) return parsed.href;
        } catch {
        }
    }
    return null;
  }, isBlobMediaUrl = (url) => /^blob:/i.test(String(url || "").trim()), isMseLikeVideo = (mediaElement, resolvedUrl = "") => {
    if (!(mediaElement instanceof HTMLVideoElement)) return !1;
    if (isBlobMediaUrl(resolvedUrl)) return !0;
    try {
      if (typeof MediaSource < "u" && mediaElement.srcObject && mediaElement.srcObject instanceof MediaSource)
        return !0;
    } catch {
    }
    return !1;
  }, uiMediaButtons = (() => {
    const downloadLabel = () => i18n.t("ui.download", "Download"), openSourceLabel = () => i18n.t("ui.openSource", "Open source/message"), WEBK_BUTTON_VARIANTS = Object.freeze({
      pinned: "btn-icon tgico-download tel-download-pinned",
      story: "btn-icon rp tel-download tel-download-story",
      ckin: "btn-icon default__button tgico-download tel-download tel-download-ckin",
      media: "btn-icon tgico-download tel-download tel-download-media"
    }), WEBZ_BUTTON_VARIANTS = Object.freeze({
      story: "Button TkphaPyQ tiny translucent-white round tel-download tel-download-webz-story",
      media: "Button smaller translucent-white round tel-download tel-download-webz-media",
      video: "Button smaller translucent-white round tel-download tel-download-webz-video"
    }), resolveMediaButtonTitle = (mediaElement, resolvedUrl) => {
      if (!isMseLikeVideo(mediaElement, resolvedUrl)) return downloadLabel();
      const streamHint = state?.settings?.enableExperimentalStreamCapture ? "" : " · Shift+Click = capture";
      return `${openSourceLabel()}${streamHint}`;
    }, resolveSourceMessageUrl = (contextRoot, fallbackUrl = "") => {
      const roots = [
        contextRoot,
        contextRoot?.closest?.(TELEGRAM_SELECTORS.webKMediaViewer),
        document
      ].filter(Boolean), selectors = [
        ".media-viewer-date a[href]",
        ".media-viewer-name a[href]",
        `${TELEGRAM_SELECTORS.webZMessageMeta} a[href]`,
        `${TELEGRAM_SELECTORS.webZProfileInfo} a[href]`,
        `${TELEGRAM_SELECTORS.storiesViewer} a[href]`,
        `${TELEGRAM_SELECTORS.storyViewer} a[href]`,
        "a[href*='t.me/']",
        "a[href*='telegram.org']"
      ];
      for (const root of roots)
        for (const selector of selectors) {
          const anchor = root.querySelector(selector);
          if (!anchor) continue;
          const href = String(anchor.getAttribute("href") || "").trim();
          if (!(!href || href.startsWith("javascript:")))
            try {
              return new URL(href, location.href).href;
            } catch {
            }
        }
      const fallback = String(fallbackUrl || "").trim();
      return fallback && !isBlobMediaUrl(fallback) ? fallback : location.href;
    }, openSourceMessage = (contextRoot, fallbackUrl = "") => {
      const targetUrl = resolveSourceMessageUrl(contextRoot, fallbackUrl);
      return openInNewTab(targetUrl) ? !0 : (showNotificationIfEnabled(
        i18n.t(
          "notification.streamSourceNotFound",
          "Unable to open source/message link from this media viewer."
        ),
        "error"
      ), !1);
    }, handleVideoAction = async ({ videoElement, contextRoot, clickEvent }) => {
      const videoUrl = getCandidateMediaUrl(videoElement);
      if (videoUrl) {
        if (isMseLikeVideo(videoElement, videoUrl)) {
          const captureEnabled = !!state?.settings?.enableExperimentalStreamCapture;
          if ((!!clickEvent?.shiftKey || captureEnabled) && await downloadsModule.captureStreamedVideo(videoElement, {
            maxDurationMs: 12e3
          })) {
            showNotificationIfEnabled(
              i18n.t(
                "notification.streamCaptureSaved",
                "Stream fragment was captured and saved (experimental)."
              ),
              "success"
            );
            return;
          }
          openSourceMessage(contextRoot, videoUrl);
          return;
        }
        downloadsModule.downloadVideo(videoUrl);
      }
    }, downloadVisualByUrl = (url) => {
      const normalized = String(url || "").toLowerCase();
      if (normalized) {
        if (/\.(webp|tgs)(\?|$)/.test(normalized) || normalized.includes("sticker")) {
          downloadsModule.downloadSticker(url);
          return;
        }
        if (/\.gif(\?|$)/.test(normalized) || normalized.includes("gif")) {
          downloadsModule.downloadGif(url);
          return;
        }
        downloadsModule.downloadImage(url);
      }
    }, safeInvokeButtonAction = (label, handler) => {
      const handleError = (error) => {
        logger.error(`${label}: ${error?.message || error}`);
        const message = i18n.t("notification.downloadFailed", "Download failed");
        showNotificationIfEnabled(`${message}${error?.message ? `: ${error.message}` : ""}`, "error");
      };
      try {
        const maybePromise = handler();
        maybePromise && typeof maybePromise.then == "function" && maybePromise.catch(handleError);
      } catch (error) {
        handleError(error);
      }
    }, createWebZActionButton = ({ variant, title, onClick }) => {
      const button = createElement("button", {
        className: WEBZ_BUTTON_VARIANTS[variant] || WEBZ_BUTTON_VARIANTS.media,
        type: "button",
        title,
        ariaLabel: title
      });
      return button.appendChild(createElement("i", { className: "icon icon-download" })), button.addEventListener("click", (event) => {
        event.stopPropagation(), safeInvokeButtonAction(title, () => onClick(event));
      }), button;
    }, createWebKActionButton = ({ variant, title, onClick, extras = [] }) => {
      const button = createElement("button", {
        className: WEBK_BUTTON_VARIANTS[variant] || WEBK_BUTTON_VARIANTS.media,
        type: "button",
        title,
        ariaLabel: title
      });
      return setButtonIconContent(button, DOWNLOAD_ICON, "tgico button-icon", extras), button.addEventListener("click", (event) => {
        event.stopPropagation(), safeInvokeButtonAction(title, () => onClick(event));
      }), button;
    }, injectPinnedAudioButton = () => {
      const pinnedAudio = document.querySelector(".pinned-audio"), utils = pinnedAudio?.querySelector(".pinned-container-wrapper-utils"), dataMid = pinnedAudio?.getAttribute("data-mid");
      if (!utils || !dataMid) return;
      const existingButton = utils.querySelector(".tel-download-pinned");
      if (existingButton?.dataset.mid === dataMid) return;
      existingButton?.remove();
      const audioElement = Array.from(document.querySelectorAll("audio-element")).find(
        (item) => item.getAttribute("data-mid") === dataMid
      ), link = audioElement?.audio?.getAttribute("src");
      if (!link) return;
      const button = createWebKActionButton({
        variant: "pinned",
        title: downloadLabel(),
        onClick: () => {
          audioElement.audio instanceof HTMLAudioElement ? downloadsModule.downloadAudio(link) : downloadsModule.downloadVideo(link);
        }
      });
      button.dataset.mid = dataMid, utils.appendChild(button);
    }, injectWebKStoriesButton = () => {
      const stories = document.getElementById("stories-viewer"), header = stories?.querySelector("[class^='_ViewerStoryHeaderRight']");
      if (!header || header.querySelector(".tel-download-story")) return;
      const ripple = createElement("div", { className: "c-ripple" }), storyVideo = stories.querySelector("video.media-video"), storyVideoUrl = getCandidateMediaUrl(storyVideo), title = resolveMediaButtonTitle(storyVideo, storyVideoUrl), button = createWebKActionButton({
        variant: "story",
        title,
        extras: [ripple],
        onClick: (event) => {
          const video = stories.querySelector("video.media-video");
          if (video)
            return handleVideoAction({
              videoElement: video,
              contextRoot: stories,
              clickEvent: event
            });
          const imageUrl = stories.querySelector("img.media-photo")?.src;
          imageUrl && downloadVisualByUrl(imageUrl);
        }
      });
      header.prepend(button);
    }, injectWebZStoriesButton = () => {
      const stories = document.getElementById("StoryViewer"), header = stories?.querySelector(".GrsJNw3y") || stories?.querySelector(".DropdownMenu")?.parentNode;
      if (!header || header.querySelector(".tel-download-webz-story")) return;
      const storyVideo = stories.querySelector("video"), storyVideoUrl = getCandidateMediaUrl(storyVideo), title = resolveMediaButtonTitle(storyVideo, storyVideoUrl), button = createWebZActionButton({
        variant: "story",
        title,
        onClick: (event) => {
          const video = stories.querySelector("video");
          if (video)
            return handleVideoAction({
              videoElement: video,
              contextRoot: stories,
              clickEvent: event
            });
          const images = Array.from(stories.querySelectorAll("img")), imageUrl = images[images.length - 1]?.src;
          imageUrl && downloadVisualByUrl(imageUrl);
        }
      });
      header.insertBefore(button, header.querySelector("button") || null);
    }, suppressBuiltInDownloadButtons = (buttonsRoot) => {
      buttonsRoot.querySelectorAll("button.btn-icon").forEach((button) => {
        button.classList.contains("tel-download") || (button.textContent === FORWARD_ICON && button.classList.add("tgico-forward"), button.textContent === DOWNLOAD_ICON && (button.classList.add("hide"), button.setAttribute("aria-hidden", "true"), button.style.display = "none"));
      });
    }, injectWebKMediaViewerButton = () => {
      const mediaContainer = document.querySelector(TELEGRAM_SELECTORS.webKMediaViewer), aspecter = mediaContainer?.querySelector(".media-viewer-movers .media-viewer-aspecter"), buttons = mediaContainer?.querySelector(".media-viewer-topbar .media-viewer-buttons");
      if (!aspecter || !buttons) return;
      if (suppressBuiltInDownloadButtons(buttons), aspecter.querySelector(".ckin__player")) {
        const controls = aspecter.querySelector(
          ".default__controls.ckin__controls .bottom-controls .right-controls"
        );
        if (!controls || controls.querySelector(".tel-download-ckin")) return;
        const ckinVideo = aspecter.querySelector("video"), ckinVideoUrl = getCandidateMediaUrl(ckinVideo), title2 = resolveMediaButtonTitle(ckinVideo, ckinVideoUrl), button2 = createWebKActionButton({
          variant: "ckin",
          title: title2,
          onClick: (event) => {
            const video = aspecter.querySelector("video");
            if (video)
              return handleVideoAction({
                videoElement: video,
                contextRoot: mediaContainer,
                clickEvent: event
              });
          }
        });
        controls.prepend(button2);
        return;
      }
      if (buttons.querySelector(".tel-download-media")) return;
      const mediaVideo = aspecter.querySelector("video"), mediaVideoUrl = getCandidateMediaUrl(mediaVideo), title = resolveMediaButtonTitle(mediaVideo, mediaVideoUrl), button = createWebKActionButton({
        variant: "media",
        title,
        onClick: (event) => {
          const video = aspecter.querySelector("video");
          if (video)
            return handleVideoAction({
              videoElement: video,
              contextRoot: mediaContainer,
              clickEvent: event
            });
          const image = aspecter.querySelector("img.thumbnail");
          image?.src && downloadVisualByUrl(image.src);
        }
      });
      buttons.prepend(button);
    }, injectWebZMediaViewerButton = () => {
      const slide = document.querySelector(TELEGRAM_SELECTORS.webZActiveSlide), actions = document.querySelector(TELEGRAM_SELECTORS.webZActions);
      if (!slide || !actions) return;
      const nativeButtons = Array.from(actions.querySelectorAll('button[title="Download"]')).filter(
        (button) => !button.classList.contains("tel-download")
      ), existingButton = actions.querySelector("button.tel-download-webz-media");
      if (nativeButtons.length > 0) {
        existingButton?.remove();
        return;
      }
      const videoPlayer = slide.querySelector(".MediaViewerContent > .VideoPlayer"), videoElement = videoPlayer?.querySelector("video"), videoUrl = getCandidateMediaUrl(videoElement), image = slide.querySelector(".MediaViewerContent > div > img"), targetUrl = videoUrl || image?.src;
      if (!targetUrl) return;
      const title = resolveMediaButtonTitle(videoElement, videoUrl), onClick = (event) => {
        if (videoElement)
          return handleVideoAction({
            videoElement,
            contextRoot: slide,
            clickEvent: event
          });
        image?.src && downloadVisualByUrl(image.src);
      };
      if (existingButton)
        existingButton.dataset.telDownloadUrl !== targetUrl && (existingButton.dataset.telDownloadUrl = targetUrl, existingButton.title = title, existingButton.setAttribute("aria-label", title), existingButton.onclick = (event) => safeInvokeButtonAction(title, () => onClick(event)));
      else {
        const button = createWebZActionButton({
          variant: "media",
          title,
          onClick
        });
        button.dataset.telDownloadUrl = targetUrl, actions.prepend(button);
      }
      const controls = videoPlayer?.querySelector(".VideoPlayerControls .buttons");
      if (controls && !controls.querySelector(".tel-download-webz-video")) {
        const controlButton = createWebZActionButton({
          variant: "video",
          title,
          onClick: (event) => {
            const currentVideo = videoPlayer?.querySelector("video");
            if (currentVideo)
              return handleVideoAction({
                videoElement: currentVideo,
                contextRoot: slide,
                clickEvent: event
              });
          }
        });
        controls.querySelector(".spacer")?.after(controlButton);
      }
    };
    return {
      refresh() {
        injectPinnedAudioButton(), injectWebKStoriesButton(), injectWebZStoriesButton(), injectWebKMediaViewerButton(), injectWebZMediaViewerButton();
      }
    };
  })(), keyboardModule = (() => {
    let feedbackNode = null, fadeFrame = null;
    const ACTIVE_VIDEO_SELECTORS = [
      `${TELEGRAM_SELECTORS.webKMediaViewer} video`,
      `${TELEGRAM_SELECTORS.webZMediaViewerRoot} video`,
      `${TELEGRAM_SELECTORS.storiesViewer} video`,
      `${TELEGRAM_SELECTORS.storyViewer} video`
    ], isEditableTarget = (target) => target instanceof HTMLElement ? ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName) || target.isContentEditable : !1, isSettingsShortcut = (event) => !!(event.shiftKey && event.code === "Slash" || event.key === "?" || event.ctrlKey && event.key === "," || event.metaKey && event.key === "," || event.ctrlKey && event.shiftKey && event.code === "KeyS" || event.altKey && event.code === "KeyS"), ensureStyles = () => {
      if (document.getElementById("video-control-styles")) return;
      const style = createElement("style", {
        attributes: { id: "video-control-styles" },
        text: `
        @keyframes tel-notification-pulse {
          0% { transform: translate(-50%, -50%) scale(0.95); }
          50% { transform: translate(-50%, -50%) scale(1.05); }
          100% { transform: translate(-50%, -50%) scale(1); }
        }
        .tel-notification-pulse {
          animation: tel-notification-pulse 0.3s ease-in-out;
        }
        .tel-video-control-notification {
          font-weight: bold;
          text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
          backdrop-filter: blur(16px) saturate(180%);
          -webkit-backdrop-filter: blur(16px) saturate(180%);
          background: rgba(32, 38, 57, 0.55);
          border-radius: 16px;
          border: 1px solid rgba(255,255,255,0.18);
          box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
          color: #fff;
          font-family: 'Segoe UI', 'Roboto', 'Arial', sans-serif;
          font-size: 1.1em;
          letter-spacing: 0.01em;
          transition: opacity 0.3s, background 0.3s;
          padding: 18px 32px;
          min-width: 120px;
          max-width: 90vw;
          text-align: center;
          user-select: none;
        }
      `
      });
      document.head.appendChild(style);
    }, ensureNode = () => {
      const host = document.fullscreenElement || document.webkitFullscreenElement || null || document.body || document.documentElement;
      return feedbackNode && feedbackNode.isConnected ? (host && feedbackNode.parentElement !== host && host.appendChild(feedbackNode), feedbackNode) : (feedbackNode = createElement("div", {
        className: "tel-video-control-notification",
        style: styleFactory.keyboardOverlay()
      }), appendToRoot(feedbackNode, host), feedbackNode);
    }, showFeedback = (message) => {
      ensureStyles();
      const node = ensureNode();
      node.textContent = message, node.style.opacity = "1", node.classList.add("tel-notification-pulse"), fadeFrame && cancelAnimationFrame(fadeFrame);
      let start = null;
      const fade = (timestamp) => {
        if (start === null && (start = timestamp), timestamp - start > 1500) {
          node.style.opacity = "0", node.classList.remove("tel-notification-pulse");
          return;
        }
        fadeFrame = requestAnimationFrame(fade);
      };
      fadeFrame = requestAnimationFrame(fade);
    }, togglePictureInPicture = async (video) => {
      if (!(video instanceof HTMLVideoElement))
        throw new Error(i18n.t("ui.pip.unsupported", "Picture-in-Picture is not supported"));
      if (document.pictureInPictureElement && document.exitPictureInPicture)
        return await document.exitPictureInPicture(), i18n.t("ui.pip.exited", "Exited PiP");
      if (video.disablePictureInPicture)
        try {
          video.disablePictureInPicture = !1;
        } catch {
        }
      if (typeof video.requestPictureInPicture == "function")
        try {
          return await video.requestPictureInPicture(), i18n.t("ui.pip.entered", "Entered PiP");
        } catch (error) {
          if (typeof video.webkitSetPresentationMode != "function")
            throw error;
        }
      if (typeof video.webkitSetPresentationMode == "function") {
        const nextMode = video.webkitPresentationMode === "picture-in-picture" ? "inline" : "picture-in-picture";
        return video.webkitSetPresentationMode(nextMode), nextMode === "inline" ? i18n.t("ui.pip.exited", "Exited PiP") : i18n.t("ui.pip.entered", "Entered PiP");
      }
      throw new Error(i18n.t("ui.pip.unsupported", "Picture-in-Picture is not supported"));
    };
    return {
      bind() {
        const syncFeedbackHost = () => {
          feedbackNode && ensureNode();
        }, handleSettingsShortcut = (event) => {
          isSettingsShortcut(event) && (isEditableTarget(event.target) || (event.preventDefault(), event.stopPropagation(), settingsModule.show()));
        }, handleSettingsClose = (event) => {
          if (event.key !== "Escape" || isEditableTarget(event.target)) return;
          const panel = document.getElementById("tel-settings-overlay");
          !panel || panel.style.display === "none" || (event.preventDefault(), settingsModule.hide());
        };
        document.addEventListener("keydown", handleSettingsShortcut, !0), document.addEventListener("keydown", handleSettingsClose, !0), document.addEventListener("fullscreenchange", syncFeedbackHost, !0), document.addEventListener("webkitfullscreenchange", syncFeedbackHost, !0), document.addEventListener("keydown", (event) => {
          if (!state.settings.enableKeyboardShortcuts || isEditableTarget(event.target)) return;
          const media = getActiveMediaContext(), fallbackVideo = ACTIVE_VIDEO_SELECTORS.map(
            (selector) => document.querySelector(selector)
          ).find((candidate) => candidate instanceof HTMLVideoElement), video = media?.video || fallbackVideo;
          if (video) {
            switch (event.code) {
              case "ArrowRight":
                event.preventDefault(), video.currentTime = Math.min(video.duration, video.currentTime + 5), showFeedback(`(${Math.floor(video.currentTime)}s)`);
                break;
              case "ArrowLeft":
                event.preventDefault(), video.currentTime = Math.max(0, video.currentTime - 5), showFeedback(`(${Math.floor(video.currentTime)}s)`);
                break;
              case "ArrowUp":
                event.preventDefault(), video.volume = Math.min(1, video.volume + 0.1), showFeedback(`${Math.round(video.volume * 100)}%`);
                break;
              case "ArrowDown":
                event.preventDefault(), video.volume = Math.max(0, video.volume - 0.1), showFeedback(`${Math.round(video.volume * 100)}%`);
                break;
              case "KeyM":
                event.preventDefault(), video.muted = !video.muted, showFeedback(
                  video.muted ? i18n.t("ui.muted", "Muted") : i18n.t("ui.unmuted", "Unmuted")
                );
                break;
              case "KeyP":
                event.preventDefault(), togglePictureInPicture(video).then(showFeedback).catch((error) => {
                  const fallbackMessage = i18n.t(
                    "ui.pip.unsupported",
                    "Picture-in-Picture is not supported"
                  ), message = String(error?.message || fallbackMessage);
                  showFeedback(message), logger.error(message);
                });
                break;
              case "Home":
                event.preventDefault(), video.currentTime = 0, showFeedback("(0s)");
                break;
              default:
                return;
            }
            event.stopPropagation();
          }
        });
      }
    };
  })(), observersModule = (() => {
    const adSelectors = [
      '[class*="Sponsored"]',
      '[class*="sponsored"]',
      '[class*="sponsor"]',
      '[class*="Promo"]',
      '[class*="promo"]',
      '[data-testid="sponsored-message"]',
      '[data-testid*="sponsored"]',
      '[data-testid*="sponsor"]',
      '[data-testid="ad-banner"]',
      '[data-testid*="ad-"]',
      '[data-sponsored="true"]',
      '[aria-label*="Sponsored"]',
      '[aria-label*="sponsored"]'
    ], adLabelRegex = /\b(ad|sponsored)\b/i, adCtaRegex = /\b(open website|learn more|visit site|install|shop now)\b/i, adContainerSelector = [
      '[data-testid*="message"]',
      '[class*="Message"]',
      '[class*="message"]',
      "article",
      ".ListItem"
    ].join(","), uiRootSelectors = [
      TELEGRAM_SELECTORS.webKMediaViewer,
      TELEGRAM_SELECTORS.webZMediaViewerRoot,
      TELEGRAM_SELECTORS.storiesViewer,
      TELEGRAM_SELECTORS.storyViewer,
      ".pinned-audio",
      "#MiddleColumn",
      "#column-center"
    ], adRootSelectors = [
      "#LeftColumn",
      "#MiddleColumn",
      "#column-left",
      "#column-center",
      "#Main"
    ], scopedObservers = {
      ui:  new Map(),
      ads:  new Map()
    }, disconnectScopedObservers = (registry) => {
      registry.forEach((observer) => observer.disconnect()), registry.clear();
    }, loadProgress = () => storageCache.getProgress(), saveProgress = (progress) => storageCache.setProgress(progress);
    let refreshAdCleanup = () => {
    };
    const normalizeText = (value) => String(value || "").replace(/\s+/g, " ").trim(), isLikelyInternalHost = (hostname) => {
      const host = String(hostname || "").toLowerCase();
      return !!(!host || host === location.hostname || host === "t.me" || host.endsWith(".t.me") || host === "telegram.me" || host.endsWith(".telegram.me") || host === "telegram.org" || host.endsWith(".telegram.org"));
    }, isExternalLinkAnchor = (anchor) => {
      if (!(anchor instanceof HTMLAnchorElement)) return !1;
      const href = String(anchor.getAttribute("href") || "").trim();
      if (!href || href.startsWith("javascript:")) return !1;
      try {
        const parsed = new URL(href, location.href);
        return /^https?:$/i.test(parsed.protocol) ? !isLikelyInternalHost(parsed.hostname) : !1;
      } catch {
        return !1;
      }
    }, resolveAdContainer = (element) => element instanceof Element ? element.closest(adContainerSelector) || element : null, looksLikeSponsoredCard = (element) => {
      if (!(element instanceof HTMLElement)) return !1;
      const text = normalizeText(element.innerText || element.textContent || "").slice(0, 1600);
      return !text || !adLabelRegex.test(text) ? !1 : adCtaRegex.test(text) ? !0 : Array.from(element.querySelectorAll("a[href]")).some(
        (anchor) => isExternalLinkAnchor(anchor)
      );
    }, hideAdElement = (element) => {
      element instanceof HTMLElement && element.dataset.telAdHidden !== "1" && (element.dataset.telAdHidden = "1", element.style.setProperty("display", "none", "important"), element.style.setProperty("visibility", "hidden", "important"), element.style.setProperty("pointer-events", "none", "important"), element.style.setProperty("height", "0", "important"), element.style.setProperty("min-height", "0", "important"), element.setAttribute("aria-hidden", "true"));
    }, removeAds = (root = document) => {
      if (state.settings.enableAdBlocking)
        try {
          adSelectors.forEach((selector) => {
            root.querySelectorAll(selector).forEach((element) => {
              const container = resolveAdContainer(element);
              container && hideAdElement(container);
            });
          });
          const heuristicRoots = [];
          (root instanceof Element || root instanceof Document) && heuristicRoots.push(root), heuristicRoots.forEach((scopeRoot) => {
            Array.from(scopeRoot.querySelectorAll("a[href]")).forEach((anchor) => {
              if (!isExternalLinkAnchor(anchor)) return;
              const container = resolveAdContainer(anchor);
              !container || !looksLikeSponsoredCard(container) || hideAdElement(container);
            });
          });
        } catch (error) {
          logger.error(`Error removing ads: ${error.message}`);
        }
    }, syncVideoProgress = () => {
      const activeMedia = getActiveMediaContext(), video = activeMedia?.video;
      if (!video) return;
      const key = activeMedia.key, store = loadProgress();
      if (store[key] && !video.dataset.telProgressRestored && (video.currentTime = store[key], video.dataset.telProgressRestored = "1"), video.dataset.telProgressBound === key) return;
      video.dataset.telProgressBound = key, state.activeVideoBindings.get(key)?.();
      let lastPersistedSecond = -1;
      const persistCurrentTime = () => {
        if (!Number.isFinite(video.currentTime) || video.ended) return;
        const roundedSecond = Math.floor(video.currentTime);
        if (roundedSecond === lastPersistedSecond) return;
        lastPersistedSecond = roundedSecond;
        const nextStore = loadProgress();
        nextStore[key] = video.currentTime, saveProgress(nextStore);
      }, handleTimeUpdate = () => {
        video.paused || video.ended || Math.floor(video.currentTime) % 2 !== 0 || persistCurrentTime();
      }, handlePause = () => {
        persistCurrentTime();
      }, handleEnded = () => {
        cleanup();
        const nextStore = loadProgress();
        delete nextStore[key], saveProgress(nextStore);
      }, cleanup = () => {
        video.removeEventListener("timeupdate", handleTimeUpdate), video.removeEventListener("pause", handlePause), video.removeEventListener("ended", handleEnded), state.activeVideoBindings.get(key) === cleanup && state.activeVideoBindings.delete(key);
      };
      video.addEventListener("timeupdate", handleTimeUpdate), video.addEventListener("pause", handlePause), video.addEventListener("ended", handleEnded), state.activeVideoBindings.set(key, cleanup);
    }, syncScopedObservers = (selectors, registry, callback) => {
      const nextRoots = selectors.map((selector) => document.querySelector(selector)).filter(Boolean), nextRootSet = new Set(nextRoots);
      Array.from(registry.keys()).forEach((root) => {
        nextRootSet.has(root) || (registry.get(root)?.disconnect(), registry.delete(root));
      }), nextRoots.forEach((root) => {
        if (registry.has(root)) return;
        const observer = new MutationObserver(callback);
        observer.observe(root, { childList: !0, subtree: !0 }), registry.set(root, observer);
      });
    };
    return {
      bind() {
        const scheduleUiRefresh = debounce(() => {
          uiModule.refresh(), syncVideoProgress();
        }, REFRESH_DELAY);
        let refreshQueued = !1;
        const queueUiRefresh = () => {
          refreshQueued || (refreshQueued = !0, requestAnimationFrame(() => {
            refreshQueued = !1, scheduleUiRefresh();
          }));
        }, scheduleAdCleanup = debounce(() => {
          removeAds();
        }, REFRESH_DELAY), syncObservers = () => {
          if (syncScopedObservers(uiRootSelectors, scopedObservers.ui, () => {
            location.href !== state.currentHref && (state.currentHref = location.href), queueUiRefresh();
          }), !state.settings.enableAdBlocking) {
            disconnectScopedObservers(scopedObservers.ads);
            return;
          }
          syncScopedObservers(adRootSelectors, scopedObservers.ads, (mutations) => {
            let hasAddedNodes = !1;
            mutations.forEach((mutation) => {
              mutation.addedNodes.forEach((node) => {
                node.nodeType === Node.ELEMENT_NODE && (hasAddedNodes = !0, removeAds(node));
              });
            }), hasAddedNodes && scheduleAdCleanup();
          });
        };
        refreshAdCleanup = () => {
          syncObservers(), scheduleAdCleanup();
        };
        const discoveryObserver = new MutationObserver(
          debounce(() => {
            location.href !== state.currentHref && (state.currentHref = location.href, queueUiRefresh()), syncObservers();
          }, REFRESH_DELAY)
        );
        discoveryObserver.observe(document.body, {
          childList: !0,
          subtree: !0
        });
        const clickRouteProbe = debounce(() => {
          location.href !== state.currentHref && (state.currentHref = location.href, queueUiRefresh(), syncObservers());
        }, 120);
        window.addEventListener("click", clickRouteProbe, !0), window.addEventListener("popstate", queueUiRefresh), window.addEventListener("hashchange", queueUiRefresh), document.addEventListener("visibilitychange", () => {
          document.hidden || (queueUiRefresh(), scheduleAdCleanup(), syncObservers());
        }), window.addEventListener("beforeunload", () => {
          discoveryObserver.disconnect(), disconnectScopedObservers(scopedObservers.ui), disconnectScopedObservers(scopedObservers.ads), state.activeVideoBindings.forEach((cleanup) => cleanup()), state.activeVideoBindings.clear(), window.removeEventListener("click", clickRouteProbe, !0), refreshAdCleanup = () => {
          };
        }), syncObservers(), queueUiRefresh(), scheduleAdCleanup();
      },
      refreshAdBlocking() {
        refreshAdCleanup();
      }
    };
  })(), themeObserver = new MutationObserver(() => {
    const nextTheme = document.documentElement.classList.contains("night") || document.documentElement.classList.contains("theme-dark");
    nextTheme !== state.themeIsDark && (state.themeIsDark = nextTheme, notificationFactory.refreshTheme(), progressFactory.refreshTheme(), logger.info(`Theme changed to: ${state.themeIsDark ? "dark" : "light"}`));
  });
  themeObserver.observe(document.documentElement, {
    attributes: !0,
    attributeFilter: ["class"]
  }), window.addEventListener("beforeunload", () => themeObserver.disconnect());
  const safariSmokeMatrix = () => {
    const lines = ["browser", "picker", "tab", "auto"].map((pref) => {
      const resolved = downloadCompatibility.resolveStrategy(pref);
      return `  ${pref.padEnd(7)} → ${resolved}`;
    });
    logger.info(`Strategy matrix on ${IS_SAFARI ? "Safari" : "non-Safari"}:
${lines.join(`
`)}`);
  }, selfTest = (() => {
    const assert = (condition, label) => {
      if (!condition) throw new Error(`FAIL: ${label}`);
      logger.info(`PASS: ${label}`);
    }, tests = {
      normalizeSettings() {
        const r1 = normalizeSettings({});
        assert(r1.enableNotifications === !0, "normalizeSettings: default enableNotifications"), assert(r1.downloadLocation === "browser", "normalizeSettings: default downloadLocation");
        const r2 = normalizeSettings({
          enableNotifications: 0,
          downloadLocation: "bad"
        });
        assert(r2.enableNotifications === !1, "normalizeSettings: coerce to boolean"), assert(r2.downloadLocation === "browser", "normalizeSettings: reject invalid location");
        const r3 = normalizeSettings({ downloadLocation: "picker" });
        assert(r3.downloadLocation === "picker", "normalizeSettings: accept valid location");
      },
      extractFileName() {
        const r1 = extractFileName("https://example.com/file.mp4?q=1", "mp4");
        assert(typeof r1 == "string" && r1.length > 0, "extractFileName: returns non-empty string");
        const plain = extractFileName("https://example.com/stream/someid", "mp4");
        assert(plain.endsWith(".mp4"), "extractFileName: appends extension for plain URL");
      },
      resolveStrategy() {
        const valid =  new Set(["browser", "picker", "tab"]);
        for (const pref of ["browser", "picker", "tab", "auto"]) {
          const result = downloadCompatibility.resolveStrategy(pref);
          assert(
            valid.has(result),
            `resolveStrategy("${pref}") returns valid strategy (got "${result}")`
          );
        }
      },
      storageCache() {
        const saved = storageCache.getSettings();
        assert(
          typeof saved == "object" && saved !== null,
          "storageCache.getSettings returns object"
        );
        const p = storageCache.getProgress();
        assert(typeof p == "object" && p !== null, "storageCache.getProgress returns object");
      }
    }, run = () => {
      logger.info("─── Self-test start ───");
      let passed = 0, failed = 0;
      for (const [name, fn] of Object.entries(tests))
        try {
          fn(), passed++;
        } catch (err) {
          logger.error(`[selfTest] ${name}: ${err.message}`), failed++;
        }
      return logger.info(`─── Self-test end: ${passed} passed, ${failed} failed ───`), safariSmokeMatrix(), failed === 0;
    };
    return window.__TEL_SELFTEST__ = run, { run };
  })();
  onDomReady(() => {
    const runSafe = (label, action) => {
      try {
        action();
      } catch (error) {
        logger.error(`[init] ${label}: ${error.message}`);
      }
    }, isDevModeEnabled = () => {
      try {
        return localStorage.getItem("tel_devmode") === "1";
      } catch {
        return !1;
      }
    };
    runSafe("progress container", () => progressFactory.ensureContainer()), runSafe("restore downloads", () => {
      downloadsModule.restorePendingDownloads().catch((error) => {
        logger.error(`[init] restore downloads: ${error.message}`);
      });
    }), runSafe("ui refresh", () => uiModule.refresh()), runSafe("keyboard bind", () => keyboardModule.bind()), runSafe("observer bind", () => observersModule.bind()), isDevModeEnabled() && runSafe("self-test", () => selfTest.run()), logger.info(i18n.t("log.init", "Completed script setup. Press ? to open settings."));
  });
})();