YouTube +

Sekmeli Görünüm YouTube ve İndir ve diğer özellikler ↴

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name                YouTube +
// @name:ar             YouTube +
// @name:az             YouTube +
// @name:be             YouTube +
// @name:bg             YouTube +
// @name:zh-CN          YouTube +
// @name:de             YouTube +
// @name:nl             YouTube +
// @name:en             YouTube +
// @name:es             YouTube +
// @name:fr             YouTube +
// @name:hi             YouTube +
// @name:id             YouTube +
// @name:it             YouTube +
// @name:ja             YouTube +
// @name:kk             YouTube +
// @name:ko             YouTube +
// @name:ky             YouTube +
// @name:pl             YouTube +
// @name:pt             YouTube +
// @name:tr             YouTube +
// @name:zh-TW          YouTube +
// @name:uk             YouTube +
// @name:uz             YouTube +
// @name:vi             YouTube +
// @namespace           by
// @version             2.5.1
// @author              diorhc
// @description         Вкладки для информации, комментариев, видео, плейлиста и скачивание видео и другие функции ↴
// @description:ar      Tabview YouTube and download and other features ↴
// @description:az      Tabview YouTube və yükləmə və digər xüsusiyyətlər ↴
// @description:be      Tabview YouTube і загрузка і іншыя функцыі ↴
// @description:bg      Tabview YouTube и изтегляне и други функции ↴
// @description:zh-CN   标签视图 YouTube、下载及其他功能 ↴
// @description:de      Tabview YouTube und Download und andere Funktionen ↴
// @description:nl      Tabview YouTube en Download en andere functies ↴
// @description:en      Tabview YouTube and Download and others features ↴
// @description:es      Vista de pestañas de YouTube, descarga y otras funciones ↴
// @description:fr      Tabview YouTube et Télécharger et autres fonctionnalités ↴
// @description:hi      YouTube टैब व्यू, डाउनलोड और अन्य सुविधाएँ ↴
// @description:id      Tampilan tab YouTube, unduh, dan fitur lainnya ↴
// @description:it      Vista a schede per YouTube, download e altre funzionalità ↴
// @description:ja      タブビューYouTubeとダウンロードおよびその他の機能 ↴
// @description:kk      Tabview YouTube және жүктеу және басқа функциялар ↴
// @description:ko      Tabview YouTube 및 다운로드 및 기타 기능 ↴
// @description:ky      Tabview YouTube жана жүктөө жана башка функциялар ↴
// @description:pl      Widok kart YouTube, pobieranie i inne funkcje ↴
// @description:pt      Visualização em abas do YouTube, download e outros recursos ↴
// @description:tr      Sekmeli Görünüm YouTube ve İndir ve diğer özellikler ↴
// @description:zh-TW   標籤檢視 YouTube 及下載及其他功能 ↴
// @description:uk      Перегляд вкладок YouTube, завантаження та інші функції ↴
// @description:uz      YouTube uchun tabview va yuklab olish va boshqa xususiyatlar ↴
// @description:vi      Chế độ tab cho YouTube, tải xuống và các tính năng khác ↴
// @match               https://*.youtube.com/*
// @match               https://music.youtube.com/*
// @match               https://studio.youtube.com/*
// @match               *://myactivity.google.com/*
// @include             *://www.youtube.com/feed/history/*
// @include             https://www.youtube.com
// @include             *://*.youtube.com/**
// @exclude             *://accounts.youtube.com/*
// @exclude             *://www.youtube.com/live_chat_replay*
// @exclude             *://www.youtube.com/persist_identity*
// @exclude             /^https?://\w+\.youtube\.com\/live_chat.*$/
// @exclude             /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @icon                https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @license             MIT
// @require             https://cdn.jsdelivr.net/npm/@preact/[email protected]/dist/signals-core.min.js
// @require             https://cdn.jsdelivr.net/npm/[email protected]/dist/browser-id3-writer.min.js
// @require             https://cdn.jsdelivr.net/npm/[email protected]/dist/preact.min.js
// @require             https://cdn.jsdelivr.net/npm/[email protected]/hooks/dist/hooks.umd.js
// @require             https://cdn.jsdelivr.net/npm/@preact/[email protected]/dist/signals.min.js
// @require             https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
// @grant               GM_addStyle
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               GM_addValueChangeListener
// @grant               GM_xmlhttpRequest
// @grant               unsafeWindow
// @grant               GM_addElement
// @connect             api.livecounts.io
// @connect             livecounts.io
// @connect             cnv.cx
// @connect             mp3yt.is
// @connect             returnyoutubedislikeapi.com
// @connect             translate.googleapis.com
// @connect             ldpccocxlrdsyejfhrvc.supabase.co
// @connect             raw.githubusercontent.com
// @connect             cdn.jsdelivr.net
// @connect             greasyfork.org
// @connect             update.greasyfork.org
// @connect             youtube.com
// @connect             www.youtube.com
// @connect             m.youtube.com
// @connect             googlevideo.com
// @connect             i.ytimg.com
// @connect             ytimg.com
// @connect             yt3.ggpht.com
// @connect             yt3.googleusercontent.com
// @connect             fonts.googleapis.com
// @connect             www.gstatic.com
// @connect             self
// @run-at              document-start
// @noframes
// @homepageURL         https://github.com/diorhc/YTP
// @supportURL          https://github.com/diorhc/YTP/discussions
// ==/UserScript==
!(function() {
"use strict";
if (window._ytpDefaults) {
return;
}
const TIMEOUTS = Object.freeze({
SHORT_UI: 80,
CHAT_URL_CHANGED: 136,
LONG_OPERATION: 4e3
});
window._ytpDefaults = Object.freeze({
debounce: (fn, ms, options = {}) => {
let timeout = null;
const debounced = function(...args) {
null !== timeout && clearTimeout(timeout);
options.leading && null === timeout && fn.call(this, ...args);
timeout = setTimeout(() => {
options.leading || fn.call(this, ...args);
timeout = null;
}, ms);
};
debounced.cancel = () => {
null !== timeout && clearTimeout(timeout);
timeout = null;
};
return debounced;
},
throttle: (fn, limit) => {
let inThrottle = !1;
return function(...args) {
if (!inThrottle) {
fn.call(this, ...args);
inThrottle = !0;
setTimeout(() => {
inThrottle = !1;
}, limit);
}
};
},
SETTINGS_KEY: "youtube_plus_settings",
SVG_NS: "http://www.w3.org/2000/svg",
TIMEOUTS,
createHTML: html => "function" == typeof window._ytplusCreateHTML ? window._ytplusCreateHTML(html) : "string" == typeof html ? html : String(html ?? ""),
setSafeHTML: (element, html, sanitize = !0) => {
if (!(element instanceof HTMLElement)) {
return;
}
if (window.YouTubeSafeDOM?.setHTML) {
window.YouTubeSafeDOM.setHTML(element, html, {
sanitize
});
return;
}
if (window.YouTubeSecurityUtils?.setInnerHTMLSafe) {
window.YouTubeSecurityUtils.setInnerHTMLSafe(element, html, sanitize);
return;
}
const safeText = String(html || "");
element.replaceChildren(document.createTextNode(safeText));
},
t: key => key || ""
});
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout;
const LOG_LEVELS = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
const logBuffer = [];
const rateLimitMap = new Map;
let currentLevel = (function isDevMode() {
try {
if ("undefined" != typeof window) {
if (window.__ytpDevMode) {
return !0;
}
const settings = localStorage.getItem("youtube_plus_settings");
if (settings) {
const parsed = JSON.parse(settings);
if (parsed.debugMode) {
return !0;
}
}
}
} catch (e) {}
return !1;
})() ? "debug" : "warn";
function log(level, module, message, data) {
if (LOG_LEVELS[level] > LOG_LEVELS[currentLevel]) {
return;
}
if (!(function checkRateLimit(module) {
const now = Date.now();
const entry = rateLimitMap.get(module);
if (!entry || now > entry.resetTime) {
rateLimitMap.set(module, {
count: 1,
resetTime: now + 6e4
});
return !0;
}
if (entry.count >= 60) {
return !1;
}
entry.count++;
return !0;
})(module)) {
return;
}
const formatted = (function formatMessage(level, module, message) {
return `[YouTube+][${module}][${level.toUpperCase()}] ${message}`;
})(level, module, message);
const entry = {
timestamp: Date.now(),
level,
module,
message,
data: void 0 !== data ? data : void 0
};
logBuffer.push(entry);
logBuffer.length > 200 && logBuffer.splice(0, logBuffer.length - 200);
"error" === level ? void 0 !== data ? window.console.error(formatted, data) : window.console.error(formatted) : ("warn" === level || "debug" === currentLevel) && (void 0 !== data ? window.console.warn(formatted, data) : window.console.warn(formatted));
}
const logger = {
error(module, message, data) {
log("error", module, message, data);
},
warn(module, message, data) {
log("warn", module, message, data);
},
info(module, message, data) {
log("info", module, message, data);
},
debug(module, message, data) {
log("debug", module, message, data);
},
setLevel(level) {
void 0 !== LOG_LEVELS[level] && (currentLevel = level);
},
getLevel: () => currentLevel,
getRecent(count = 50, filterLevel) {
let entries = logBuffer;
filterLevel && (entries = entries.filter(e => e.level === filterLevel));
return entries.slice(-count);
},
export: () => JSON.stringify(logBuffer, null, 2),
clear() {
logBuffer.length = 0;
rateLimitMap.clear();
},
getStats() {
const byLevel = {
error: 0,
warn: 0,
info: 0,
debug: 0
};
const byModule = {};
for (const entry of logBuffer) {
byLevel[entry.level]++;
byModule[entry.module] = (byModule[entry.module] || 0) + 1;
}
return {
totalEntries: logBuffer.length,
byLevel,
byModule,
currentLevel
};
},
createLogger: moduleName => ({
error(message, data) {
log("error", moduleName, message, data);
},
warn(message, data) {
log("warn", moduleName, message, data);
},
info(message, data) {
log("info", moduleName, message, data);
},
debug(message, data) {
log("debug", moduleName, message, data);
}
})
};
const ErrorSeverity_LOW = "low", ErrorSeverity_MEDIUM = "medium", ErrorSeverity_HIGH = "high", ErrorSeverity_CRITICAL = "critical";
const errorBoundaryConfig = {
maxErrors: 10,
errorWindow: 6e4,
enableLogging: !0,
enableRecovery: !0,
storageKey: "youtube_plus_errors"
};
const errorState = {
errors: [],
errorCount: 0,
lastErrorTime: 0,
isRecovering: !1
};
const categorizeSeverity = error => {
const message = error.message?.toLowerCase() || "";
return message.includes("cannot read") || message.includes("undefined") || message.includes("null") ? ErrorSeverity_MEDIUM : message.includes("network") || message.includes("fetch") || message.includes("timeout") ? ErrorSeverity_LOW : message.includes("syntax") || message.includes("reference") || message.includes("type") ? ErrorSeverity_HIGH : message.includes("security") || message.includes("csp") ? ErrorSeverity_CRITICAL : ErrorSeverity_MEDIUM;
};
const getErrorRate = () => {
const now = Date.now();
const oneMinuteAgo = now - 6e4;
return errorState.errors.filter(e => new Date(e.timestamp).getTime() > oneMinuteAgo).length;
};
const isErrorRateExceeded = () => {
const now = Date.now();
const windowStart = now - errorBoundaryConfig.errorWindow;
const recentErrors = errorState.errors.filter(e => new Date(e.timestamp).getTime() > windowStart);
return recentErrors.length >= errorBoundaryConfig.maxErrors;
};
const showErrorNotification = error => {
try {
const Y = window.YouTubeUtils;
if (!Y || !Y.NotificationManager || "function" != typeof Y.NotificationManager.show) {
return;
}
const severity = categorizeSeverity(error);
let message = "An error occurred";
let duration = 3e3;
if (severity === ErrorSeverity_LOW) {
message = "A minor issue occurred. Functionality should continue normally.";
duration = 2e3;
} else if (severity === ErrorSeverity_MEDIUM) {
message = "An error occurred. Some features may not work correctly.";
duration = 3e3;
} else if (severity === ErrorSeverity_HIGH) {
message = "A serious error occurred. Please refresh the page if issues persist.";
duration = 5e3;
} else if (severity === ErrorSeverity_CRITICAL) {
message = "A critical error occurred. YouTube+ may not function properly. Please report this issue.";
duration = 7e3;
}
Y.NotificationManager.show(message, {
duration,
type: "error"
});
} catch (notificationError) {
window.console.error("[YouTube+][ErrorBoundary] Failed to show error notification", notificationError);
}
};
const attemptRecovery = (error, context) => {
if (!errorBoundaryConfig.enableRecovery || errorState.isRecovering) {
return;
}
const severity = categorizeSeverity(error);
if (severity !== ErrorSeverity_CRITICAL) {
errorState.isRecovering = !0;
try {
severity !== ErrorSeverity_LOW && getErrorRate() <= 5 && showErrorNotification(error);
window.YouTubePlusErrorRecovery?.attemptRecovery && window.YouTubePlusErrorRecovery.attemptRecovery(error, context);
setTimeout_(() => {
errorState.isRecovering = !1;
}, 5e3);
} catch (recoveryError) {
window.console.error("[YouTube+][ErrorBoundary] Recovery attempt failed", recoveryError);
errorState.isRecovering = !1;
}
} else {
showErrorNotification(error);
}
};
const logBoundaryError = (error, context = {}) => {
if (!errorBoundaryConfig.enableLogging) {
return;
}
const normalizedError = error instanceof Error ? error : new Error(String(error));
const fallbackMessage = normalizedError.message?.trim() || "";
if (!fallbackMessage && !normalizedError.stack && !context.filename) {
return;
}
const displayMessage = fallbackMessage || (context.filename ? `Error in ${context.filename}:${context.lineno}` : "Unknown error");
const errorInfo = {
timestamp: (new Date).toISOString(),
message: displayMessage,
stack: normalizedError.stack,
severity: categorizeSeverity(normalizedError),
context: {
url: window.location.href,
userAgent: navigator.userAgent,
...context
}
};
logger.error("ErrorBoundary", displayMessage, errorInfo);
errorState.errors.push(errorInfo);
errorState.errors.length > 50 && errorState.errors.shift();
try {
const stored = JSON.parse(localStorage.getItem(errorBoundaryConfig.storageKey) || "[]");
stored.push(errorInfo);
stored.length > 20 && stored.shift();
localStorage.setItem(errorBoundaryConfig.storageKey, JSON.stringify(stored));
} catch (e) {}
};
const withErrorBoundary = (fn, context = "unknown") => function(...args) {
try {
return fn.call(this, ...args);
} catch (error) {
const normalizedError = error instanceof Error ? error : new Error(String(error));
logBoundaryError(normalizedError, {
module: context,
args
});
attemptRecovery(normalizedError, {
module: context
});
return null;
}
};
const withAsyncErrorBoundary = (fn, context = "unknown") => async function(...args) {
try {
return await fn.call(this, ...args);
} catch (error) {
const normalizedError = error instanceof Error ? error : new Error(String(error));
logBoundaryError(normalizedError, {
module: context,
args
});
attemptRecovery(normalizedError, {
module: context
});
return null;
}
};
const getErrorStats = () => ({
totalErrors: errorState.errorCount,
recentErrors: errorState.errors.length,
lastErrorTime: errorState.lastErrorTime,
isRecovering: errorState.isRecovering,
errorsByType: errorState.errors.reduce((acc, e) => {
acc[e.severity] = (acc[e.severity] || 0) + 1;
return acc;
}, {})
});
const clearErrors = () => {
errorState.errors = [];
errorState.errorCount = 0;
errorState.lastErrorTime = 0;
try {
localStorage.removeItem(errorBoundaryConfig.storageKey);
} catch (e) {}
};
const handleError = event => {
const error = event.error || new Error(event.message);
const message = (error.message || event.message || "").trim();
if (message.includes("ResizeObserver loop")) {
return !1;
}
const source = event.filename || "";
const isCrossOriginSource = source && !source.startsWith(window.location.origin) && !/YouTube\+/.test(source);
if (!message && isCrossOriginSource) {
return !1;
}
if (!message || "(no message)" === message && isCrossOriginSource) {
return !1;
}
errorState.errorCount++;
errorState.lastErrorTime = Date.now();
logBoundaryError(error, {
type: "uncaught",
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
if (isErrorRateExceeded()) {
logger.error("ErrorBoundary", "Error rate exceeded");
return !1;
}
attemptRecovery(error, {
type: "uncaught"
});
return !1;
};
const handleUnhandledRejection = event => {
const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
errorState.errorCount++;
errorState.lastErrorTime = Date.now();
logBoundaryError(error, {
type: "unhandledRejection",
promise: event.promise
});
isErrorRateExceeded() ? logger.error("ErrorBoundary", "Promise rejection rate exceeded") : attemptRecovery(error, {
type: "unhandledRejection"
});
};
logger.withErrorBoundary = withErrorBoundary;
logger.withAsyncErrorBoundary = withAsyncErrorBoundary;
logger.getErrorStats = getErrorStats;
logger.clearErrors = clearErrors;
logger.logError = logBoundaryError;
logger.getErrorRate = getErrorRate;
logger.config = errorBoundaryConfig;
if ("undefined" != typeof window) {
window.addEventListener("error", handleError, !0);
window.addEventListener("unhandledrejection", handleUnhandledRejection, !0);
window.YouTubePlusLogger = logger;
window.YouTubeErrorBoundary = {
withErrorBoundary,
withAsyncErrorBoundary,
getErrorStats,
clearErrors,
logError: logBoundaryError,
getErrorRate,
config: errorBoundaryConfig
};
}
})();

!(function() {
"use strict";
const modules = new Map;
const pendingCallbacks = new Map;
class LazyLoader {
constructor() {
this.modules = new Map;
this.loadedModules = new Set;
this.stats = {
totalModules: 0,
loadedModules: 0,
blockedModules: 0
};
this.isIdle = !1;
this.isReadyLoading = !1;
this.idleCallbackId = null;
this.navListenerAttached = !1;
this.navRetryScheduled = !1;
this.readyListenerAttached = !1;
}
resetReloadableModules() {
let resetCount = 0;
for (const [name, module] of this.modules.entries()) {
if (module.reloadOnNavigate && module.loaded) {
module.loaded = !1;
this.loadedModules.delete(name) && (this.stats.loadedModules = Math.max(0, this.stats.loadedModules - 1));
resetCount++;
}
}
return resetCount;
}
async retryBlockedModules() {
let loaded = 0;
for (const [name, module] of this.modules.entries()) {
const shouldRetry = module.reloadOnNavigate ? !module.shouldLoad || module.shouldLoad() : !!module.shouldLoad && module.shouldLoad();
if (!module.loaded && shouldRetry) {
try {
const ok = await this.load(name);
ok && loaded++;
} catch (e) {}
}
}
return loaded;
}
attachNavRetry() {
if (this.navListenerAttached || "undefined" == typeof window) {
return;
}
this.navListenerAttached = !0;
const schedule = () => {
if (this.navRetryScheduled) {
return;
}
this.navRetryScheduled = !0;
const run = () => {
this.navRetryScheduled = !1;
this.resetReloadableModules();
this.retryBlockedModules().catch(() => {});
for (const [name, module] of this.modules.entries()) {
if (module.loaded && module.onNavigate) {
try {
module.onNavigate();
} catch (err) {
window.YouTubeUtils?.logger?.warn?.(`[LazyLoader] onNavigate("${name}") threw`, err);
}
}
}
try {
window.dispatchEvent(new CustomEvent("ytp:nav-refresh"));
} catch (err) {}
try {
document.querySelector(".ytp-plus-settings-modal") && document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-opened", {
bubbles: !0,
detail: {
__ytpLazyReplay: !0
}
}));
} catch (err) {}
};
"function" == typeof requestIdleCallback ? requestIdleCallback(run, {
timeout: 1500
}) : setTimeout(run, 250);
};
try {
window.addEventListener("yt-navigate-finish", schedule, {
passive: !0
});
document.addEventListener("yt-page-data-updated", schedule, {
passive: !0
});
window.addEventListener("popstate", schedule, {
passive: !0
});
document.addEventListener("youtube-plus-settings-modal-opened", event => {
(event => {
try {
return Boolean(event?.detail?.__ytpLazyReplay);
} catch (e) {
return !1;
}
})(event) || schedule();
});
} catch (e) {}
}
register(name, fn, options = {}) {
if (this.modules.has(name)) {
window.YouTubeUtils?.logger?.warn?.(`[LazyLoader] Module "${name}" already registered`);
return;
}
const moduleConfig = {
fn,
priority: options.priority || 0,
delay: options.delay || 0,
dependencies: options.dependencies || [],
shouldLoad: "function" == typeof options.shouldLoad ? options.shouldLoad : null,
reloadOnNavigate: !0 === options.reloadOnNavigate,
loadOnReady: !1 !== options.loadOnReady,
loaded: !1,
onNavigate: "function" == typeof options.onNavigate ? options.onNavigate : null
};
this.modules.set(name, moduleConfig);
this.stats.totalModules++;
window.YouTubeUtils?.logger?.debug?.(`[LazyLoader] Registered module "${name}" (priority: ${moduleConfig.priority})`);
}
async load(name) {
const module = this.modules.get(name);
if (!module) {
window.YouTubeUtils?.logger?.warn?.(`[LazyLoader] Module "${name}" not found`);
return !1;
}
if (module.loaded) {
return !0;
}
if (module.shouldLoad && !module.shouldLoad()) {
this.stats.blockedModules++;
this.attachNavRetry();
return !1;
}
for (const dep of module.dependencies) {
this.loadedModules.has(dep) || await this.load(dep);
}
module.delay > 0 && await new Promise(resolve => setTimeout(resolve, module.delay));
try {
await module.fn();
module.loaded = !0;
this.loadedModules.add(name);
this.stats.loadedModules++;
return !0;
} catch (error) {
window.console.error(`[LazyLoader] Failed to load module "${name}":`, error);
window.YouTubeUtils?.logger?.error?.(`[LazyLoader] Module "${name}" load failed`, error);
return !1;
}
}
async loadAll() {
const sortedModules = Array.from(this.modules.entries()).sort((a, b) => b[1].priority - a[1].priority);
let loadedCount = 0;
for (const [name, module] of sortedModules) {
if (!module.loaded) {
const success = await this.load(name);
success && loadedCount++;
}
}
return loadedCount;
}
loadOnReady() {
if (this.isReadyLoading) {
return;
}
this.attachNavRetry();
const loadModules = () => {
if (!this.isReadyLoading) {
this.isReadyLoading = !0;
this.loadAll().catch(() => {});
}
};
if ("undefined" == typeof document || "loading" !== document.readyState) {
loadModules();
return;
}
if (this.readyListenerAttached) {
return;
}
this.readyListenerAttached = !0;
const onReadyStateChange = () => {
if ("loading" !== document.readyState) {
document.removeEventListener("readystatechange", onReadyStateChange);
loadModules();
}
};
document.addEventListener("readystatechange", onReadyStateChange, {
passive: !0
});
window.addEventListener("yt-navigate-finish", () => {
document.removeEventListener("readystatechange", onReadyStateChange);
loadModules();
}, {
passive: !0,
once: !0
});
}
loadOnIdle(timeout = 2e3) {
if (this.isIdle) {
return;
}
this.isIdle = !0;
const loadModules = async () => {
await this.loadAll();
};
this.attachNavRetry();
this.idleCallbackId = "undefined" != typeof requestIdleCallback ? requestIdleCallback(loadModules, {
timeout
}) : setTimeout(loadModules, timeout);
}
cancelIdleLoading() {
if (this.isIdle) {
void 0 !== window.cancelIdleCallback && this.idleCallbackId ? window.cancelIdleCallback(this.idleCallbackId) : this.idleCallbackId && clearTimeout(this.idleCallbackId);
this.isIdle = !1;
this.idleCallbackId = null;
}
}
cancelReadyLoading() {
this.isReadyLoading = !1;
this.readyListenerAttached = !1;
}
isLoaded(name) {
return this.loadedModules.has(name);
}
getStats() {
return {
...this.stats,
loadingPercentage: this.stats.totalModules > 0 ? this.stats.loadedModules / this.stats.totalModules * 100 : 0,
unloadedModules: this.stats.totalModules - this.stats.loadedModules,
blockedModules: this.stats.blockedModules
};
}
clear() {
this.cancelIdleLoading();
this.cancelReadyLoading();
this.modules.clear();
this.loadedModules.clear();
this.stats = {
totalModules: 0,
loadedModules: 0,
blockedModules: 0
};
this.navListenerAttached = !1;
this.navRetryScheduled = !1;
}
}
const lazyLoader = new LazyLoader;
const lazyLoaderApi = {
LazyLoader,
register: (name, fn, options) => lazyLoader.register(name, fn, options),
load: name => lazyLoader.load(name),
loadAll: () => lazyLoader.loadAll(),
loadOnIdle: timeout => lazyLoader.loadOnIdle(timeout),
loadOnReady: () => lazyLoader.loadOnReady(),
isLoaded: name => lazyLoader.isLoaded(name),
getStats: () => lazyLoader.getStats(),
clear: () => lazyLoader.clear(),
retryBlockedModules: () => lazyLoader.retryBlockedModules(),
attachNavRetry: () => lazyLoader.attachNavRetry()
};
const registry = {
register(name, moduleExport) {
if (!name || "string" != typeof name) {
window.console.warn("[YouTube+ Registry] Invalid module name:", name);
return;
}
modules.set(name, moduleExport);
const windowAliases = {
utils: "YouTubeUtils",
domCache: "YouTubeDOMCache",
errorBoundary: "YouTubeErrorBoundary",
performance: "YouTubePerformance",
i18n: "YouTubePlusI18n",
lazyLoader: "YouTubePlusLazyLoader",
eventDelegation: "YouTubePlusEventDelegation",
security: "YouTubeSecurityUtils",
settings: "YouTubePlusSettingsHelpers",
modalHandlers: "YouTubePlusModalHandlers",
stats: "YouTubeStats",
download: "YouTubePlusDownload",
music: "YouTubeMusic",
voting: "YouTubePlus",
logger: "YouTubePlusLogger"
};
windowAliases[name] && "undefined" != typeof window && (window[windowAliases[name]] = moduleExport);
const pending = pendingCallbacks.get(name);
if (pending) {
for (const cb of pending) {
try {
cb(moduleExport);
} catch (e) {
window.console.error(`[YouTube+ Registry] Callback error for "${name}":`, e);
}
}
pendingCallbacks.delete(name);
}
},
get: name => modules.get(name),
has: name => modules.has(name),
onReady(name, callback) {
if (modules.has(name)) {
try {
callback(modules.get(name));
} catch (e) {
window.console.error(`[YouTube+ Registry] onReady callback error for "${name}":`, e);
}
} else {
pendingCallbacks.has(name) || pendingCallbacks.set(name, new Set);
pendingCallbacks.get(name)?.add(callback);
}
},
list: () => Array.from(modules.keys()),
getStats: () => ({
totalModules: modules.size,
moduleNames: Array.from(modules.keys()),
pendingCallbacks: Array.from(pendingCallbacks.keys())
}),
unregister(name) {
modules.delete(name);
},
clear() {
modules.clear();
pendingCallbacks.clear();
lazyLoader.clear();
},
lazyLoader: lazyLoaderApi
};
if ("undefined" != typeof window) {
window.YouTubePlusLazyLoader = lazyLoaderApi;
window.YouTubePlusRegistry = registry;
}
})();

!(function() {
"use strict";
const $ = (sel, ctx) => {
const cache = window.YouTubeDOMCache;
return cache && "function" == typeof cache.querySelector ? cache.querySelector(sel, ctx) : cache && "function" == typeof cache.get && !ctx ? cache.get(sel) : (ctx || document).querySelector(sel);
};
const $$ = (sel, ctx) => {
const cache = window.YouTubeDOMCache;
return cache && "function" == typeof cache.querySelectorAll ? cache.querySelectorAll(sel, ctx) : cache && "function" == typeof cache.getAll && !ctx ? cache.getAll(sel) : Array.from((ctx || document).querySelectorAll(sel));
};
const byId = id => {
const cache = window.YouTubeDOMCache;
return cache && "function" == typeof cache.getElementById ? cache.getElementById(id) : document.getElementById(id);
};
const t = (key, params = {}) => {
if (window.YouTubePlusI18n?.t) {
return window.YouTubePlusI18n.t(key, params);
}
if (!key) {
return "";
}
let result = String(key);
for (const [k, v] of Object.entries(params || {})) {
const token = `{${k}}`;
result = result.split(token).join(String(v));
}
return result;
};
const getLanguage = () => {
if (window.YouTubePlusI18n?.getLanguage) {
return window.YouTubePlusI18n.getLanguage();
}
const htmlLang = document.documentElement?.lang || navigator.language || "en";
return String(htmlLang || "en").toLowerCase();
};
const setSafeHTML = (element, html, sanitize = !0) => {
element instanceof HTMLElement && (window.YouTubeSafeDOM?.setHTML ? window.YouTubeSafeDOM.setHTML(element, html, {
sanitize
}) : window._ytpDefaults?.setSafeHTML?.(element, html, sanitize));
};
const SETTINGS_KEY = "youtube_plus_settings";
const isStudioPage = () => {
try {
const host = String(location.hostname || "").toLowerCase();
return "studio.youtube.com" === host || host.endsWith(".studio.youtube.com");
} catch (e) {
return !1;
}
};
const loadFeatureEnabled = (featureKey, defaultValue = !0) => {
try {
const settings = localStorage.getItem(SETTINGS_KEY);
if (settings) {
const parsed = JSON.parse(settings);
return !1 !== parsed[featureKey];
}
} catch (e) {}
return defaultValue;
};
const getPathname = urlLike => {
try {
return urlLike ? new URL(urlLike, window.location.origin).pathname || "" : window.location.pathname || "";
} catch (e) {
return "";
}
};
const isWatchPage = urlLike => "/watch" === getPathname(urlLike);
const isShortsPage = urlLike => getPathname(urlLike).startsWith("/shorts");
const isChannelPage = urlLike => {
const pathname = getPathname(urlLike);
return pathname.startsWith("/@") || pathname.startsWith("/channel/") || pathname.startsWith("/c/");
};
const formatTime = seconds => {
if (!Number.isFinite(seconds) || seconds < 0) {
return "0:00";
}
const totalSeconds = Math.floor(seconds);
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor(totalSeconds % 3600 / 60);
const s = totalSeconds % 60;
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}` : `${m}:${String(s).padStart(2, "0")}`;
};
const onDomReady = cb => {
"function" == typeof cb && ("loading" === document.readyState ? document.addEventListener("DOMContentLoaded", cb, {
once: !0
}) : cb());
};
const logError = (module, message, error) => {
try {
const errorDetails = {
module,
message,
error: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack
} : error,
timestamp: (new Date).toISOString(),
userAgent: "undefined" != typeof navigator ? navigator.userAgent : "unknown",
url: "undefined" != typeof window ? window.location.href : "unknown"
};
window.console.error(`[YouTube+][${module}] ${message}:`, error);
window.console.warn("[YouTube+] Error details:", errorDetails);
} catch (loggingError) {
window.console.error("[YouTube+] Error logging failed:", loggingError);
}
};
const debounce = (fn, ms, options = {}) => {
let timeout = null;
let lastArgs = null;
let lastThis = null;
let isDestroyed = !1;
const debounced = function(...args) {
if (!isDestroyed) {
lastArgs = args;
lastThis = this;
null !== timeout && clearTimeout(timeout);
if (options.leading && null === timeout) {
try {
fn.apply(this, args);
} catch (e) {
window.console.error("[YouTube+] Debounced function error:", e);
}
}
timeout = setTimeout(() => {
if (!isDestroyed && !options.leading) {
try {
fn.apply(lastThis, lastArgs);
} catch (e) {
window.console.error("[YouTube+] Debounced function error:", e);
}
}
timeout = null;
lastArgs = null;
lastThis = null;
}, ms);
}
};
debounced.cancel = () => {
null !== timeout && clearTimeout(timeout);
timeout = null;
lastArgs = null;
lastThis = null;
};
debounced.destroy = () => {
debounced.cancel();
isDestroyed = !0;
};
return debounced;
};
const throttle = (fn, limit) => {
let inThrottle = !1;
let lastResult;
return function(...args) {
if (!inThrottle) {
lastResult = fn.apply(this, args);
inThrottle = !0;
setTimeout(() => inThrottle = !1, limit);
}
return lastResult;
};
};
const StyleManager = (function() {
const styles = new Map;
let element = null;
const ensureElement = () => {
if (element?.isConnected) {
return element;
}
const existing = document.getElementById("youtube-plus-styles");
if (existing) {
element = existing;
return element;
}
if (!document.head && !document.documentElement) {
return null;
}
const created = document.createElement("style");
created.id = "youtube-plus-styles";
created.type = "text/css";
(document.head || document.documentElement).appendChild(created);
element = created;
return element;
};
const render = () => {
try {
const host = ensureElement();
if (!host) {
document.addEventListener("DOMContentLoaded", () => {
const lateHost = ensureElement();
lateHost && (lateHost.textContent = Array.from(styles.values()).join("\n\n"));
}, {
once: !0
});
return;
}
host.textContent = Array.from(styles.values()).join("\n\n");
} catch (e) {
logError("StyleManager", "render failed", e);
}
};
return {
styles,
add(id, css) {
try {
if ("string" != typeof id || !id) {
return;
}
if ("string" != typeof css) {
return;
}
styles.set(id, css);
render();
} catch (e) {
logError("StyleManager", "add failed", e);
}
},
remove(id) {
try {
styles.delete(id);
render();
} catch (e) {
logError("StyleManager", "remove failed", e);
}
},
clear() {
try {
styles.clear();
if (element) {
element.remove();
element = null;
}
} catch (e) {
logError("StyleManager", "clear failed", e);
}
}
};
})();
const cleanupManager = (function() {
const observers = new Set;
const listeners = new Map;
const listenerStats = {
registeredTotal: 0
};
const intervals = new Set;
const timeouts = new Set;
const animationFrames = new Set;
const callbacks = new Set;
const elementObservers = new WeakMap;
return {
registerObserver(o, el) {
try {
o && observers.add(o);
if (el && "object" == typeof el) {
try {
let set = elementObservers.get(el);
if (!set) {
set = new Set;
elementObservers.set(el, set);
}
set.add(o);
} catch (e) {}
}
} catch (e) {}
return o;
},
registerListener(target, ev, fn, opts) {
try {
target.addEventListener(ev, fn, opts);
const key = Symbol();
listeners.set(key, {
target,
ev,
fn,
opts
});
listenerStats.registeredTotal++;
return key;
} catch (e) {
logError("cleanupManager", "registerListener failed", e);
return null;
}
},
getListenerStats() {
try {
return {
active: listeners.size,
registeredTotal: listenerStats.registeredTotal
};
} catch (e) {
return {
active: 0,
registeredTotal: 0
};
}
},
registerInterval(id) {
intervals.add(id);
return id;
},
registerTimeout(id) {
timeouts.add(id);
return id;
},
registerAnimationFrame(id) {
animationFrames.add(id);
return id;
},
register(cb) {
"function" == typeof cb && callbacks.add(cb);
},
cleanup() {
try {
for (const cb of callbacks) {
try {
cb();
} catch (e) {
logError("cleanupManager", "callback failed", e);
}
}
callbacks.clear();
for (const o of observers) {
try {
o && "function" == typeof o.disconnect && o.disconnect();
} catch (e) {}
}
observers.clear();
for (const keyEntry of listeners.values()) {
try {
keyEntry.target.removeEventListener(keyEntry.ev, keyEntry.fn, keyEntry.opts);
} catch (e) {}
}
listeners.clear();
for (const id of intervals) {
clearInterval(id);
}
intervals.clear();
for (const id of timeouts) {
clearTimeout(id);
}
timeouts.clear();
for (const id of animationFrames) {
cancelAnimationFrame(id);
}
animationFrames.clear();
} catch (e) {
logError("cleanupManager", "cleanup failed", e);
}
},
observers,
elementObservers,
disconnectForElement(el) {
try {
const set = elementObservers.get(el);
if (!set) {
return;
}
for (const o of set) {
try {
o && "function" == typeof o.disconnect && o.disconnect();
observers.delete(o);
} catch (e) {}
}
elementObservers.delete(el);
} catch (e) {
logError("cleanupManager", "disconnectForElement failed", e);
}
},
disconnectObserver(o) {
try {
if (!o) {
return;
}
try {
"function" == typeof o.disconnect && o.disconnect();
} catch (e) {}
observers.delete(o);
} catch (e) {
logError("cleanupManager", "disconnectObserver failed", e);
}
},
listeners,
intervals,
timeouts,
animationFrames
};
})();
const createElement = (tag, props = {}, children = []) => {
try {
const element = document.createElement(tag);
Object.entries(props).forEach(([k, v]) => {
"className" === k ? element.className = String(v) : "style" === k && "object" == typeof v ? Object.assign(element.style, v) : "dataset" === k && "object" == typeof v ? Object.assign(element.dataset, v) : k.startsWith("on") && "function" == typeof v ? element.addEventListener(k.slice(2), v) : element.setAttribute(k, String(v));
});
children.forEach(c => {
"string" == typeof c ? element.appendChild(document.createTextNode(c)) : c instanceof Node && element.appendChild(c);
});
return element;
} catch (e) {
logError("createElement", "failed", e);
return document.createElement("div");
}
};
const waitForElement = (selector, timeout = 5e3, parent = document) => new Promise((resolve, reject) => {
if (!selector || "string" != typeof selector) {
return reject(new Error("Invalid selector"));
}
let subId = null;
let fallbackTimer = null;
let timeoutTimer = null;
const finalize = () => {
if (subId && window.YouTubeMutationCoordinator?.unsubscribe) {
try {
window.YouTubeMutationCoordinator.unsubscribe(subId);
} catch (e) {}
}
subId = null;
if (fallbackTimer) {
clearInterval(fallbackTimer);
fallbackTimer = null;
}
if (timeoutTimer) {
clearTimeout(timeoutTimer);
timeoutTimer = null;
}
};
const tryResolve = () => {
const el = parent.querySelector(selector);
if (!el) {
return !1;
}
finalize();
resolve(el);
return !0;
};
try {
if (tryResolve()) {
return;
}
} catch (e) {
return reject(e);
}
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.watchTarget && parent instanceof Node) {
subId = `utils::waitForElement::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`;
coordinator.watchTarget(subId, parent, () => {
try {
tryResolve();
} catch (e) {
finalize();
reject(e);
}
}, {
childList: !0,
attributes: !1,
subtree: !0
});
} else {
fallbackTimer = setInterval(() => {
try {
tryResolve();
} catch (e) {
finalize();
reject(e);
}
}, 120);
}
timeoutTimer = setTimeout(() => {
finalize();
reject(new Error("timeout"));
}, timeout);
cleanupManager.registerTimeout(timeoutTimer);
});
const sanitizeHTML = html => {
if ("string" != typeof html) {
return "";
}
if (window.YouTubeSafeDOM?.sanitizeHTML) {
return window.YouTubeSafeDOM.sanitizeHTML(html);
}
html.length > 1e6 && (html = html.substring(0, 1e6));
const map = {
"<": "&lt;",
">": "&gt;",
"&": "&amp;",
'"': "&quot;",
"'": "&#39;",
"/": "&#x2F;",
"`": "&#x60;",
"=": "&#x3D;"
};
return html.replace(/[<>&"'\/`=]/g, char => map[char] || char);
};
const escapeHTMLAttribute = str => {
if ("string" != typeof str) {
return "";
}
const map = {
"<": "&lt;",
">": "&gt;",
"&": "&amp;",
'"': "&quot;",
"'": "&#39;",
"/": "&#x2F;",
"`": "&#x60;",
"=": "&#x3D;",
"\n": "&#10;",
"\r": "&#13;",
"\t": "&#9;"
};
return str.replace(/[<>&"'\/`=\n\r\t]/g, char => map[char] || char);
};
const isValidURL = url => {
if ("string" != typeof url) {
return !1;
}
if (url.length > 2048) {
return !1;
}
if (/^\s|\s$/.test(url)) {
return !1;
}
try {
const parsed = new URL(url);
return !![ "http:", "https:" ].includes(parsed.protocol);
} catch (e) {
return !1;
}
};
const safeMerge = (target, source) => {
if (!source || "object" != typeof source) {
return target;
}
if (!target || "object" != typeof target) {
return target;
}
const dangerousKeys = [ "__proto__", "constructor", "prototype" ];
for (const key in source) {
if (!Object.prototype.hasOwnProperty.call(source, key)) {
continue;
}
if (dangerousKeys.includes(key)) {
window.console.warn(`[YouTube+][Security] Blocked attempt to set dangerous key: ${key}`);
continue;
}
const value = source[key];
target[key] = value && "object" == typeof value && !Array.isArray(value) ? safeMerge(target[key] || {}, value) : value;
}
return target;
};
const validateVideoId = videoId => "string" != typeof videoId ? null : /^[a-zA-Z0-9_-]{11}$/.test(videoId) ? videoId : null;
const validatePlaylistId = playlistId => "string" != typeof playlistId || !/^[a-zA-Z0-9_-]+$/.test(playlistId) || playlistId.length < 2 || playlistId.length > 50 ? null : playlistId;
const validateChannelId = channelId => "string" != typeof channelId ? null : /^UC[a-zA-Z0-9_-]{22}$/.test(channelId) || /^@[\w-]{3,30}$/.test(channelId) ? channelId : null;
const validateNumber = (value, min = -Infinity, max = Infinity, defaultValue = 0) => {
const num = Number(value);
return Number.isNaN(num) || !Number.isFinite(num) ? defaultValue : Math.max(min, Math.min(max, num));
};
const retryWithBackoff = async (fn, maxRetries = 3, baseDelay = 1e3) => {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (i < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
};
const storage = {
get(key, def = null) {
if ("string" != typeof key || !/^[a-zA-Z0-9_\-\.]+$/.test(key)) {
logError("storage", "Invalid key format", new Error(`Invalid key: ${key}`));
return def;
}
try {
const v = localStorage.getItem(key);
if (null === v) {
return def;
}
if (v.length > 5242880) {
logError("storage", "Stored value too large", new Error(`Key: ${key}`));
return def;
}
return JSON.parse(v);
} catch (e) {
logError("storage", "Failed to parse stored value", e);
return def;
}
},
set(key, val) {
if ("string" != typeof key || !/^[a-zA-Z0-9_\-\.]+$/.test(key)) {
logError("storage", "Invalid key format", new Error(`Invalid key: ${key}`));
return !1;
}
try {
const serialized = JSON.stringify(val);
if (serialized.length > 5242880) {
logError("storage", "Value too large to store", new Error(`Key: ${key}`));
return !1;
}
localStorage.setItem(key, serialized);
return !0;
} catch (e) {
logError("storage", "Failed to store value", e);
return !1;
}
},
remove(key) {
try {
localStorage.removeItem(key);
} catch (e) {
logError("storage", "Failed to remove value", e);
}
},
clear() {
try {
localStorage.clear();
} catch (e) {
logError("storage", "Failed to clear storage", e);
}
},
has(key) {
try {
return null !== localStorage.getItem(key);
} catch (e) {
return !1;
}
}
};
const ScrollManager = (() => {
const listeners = new WeakMap;
return {
addScrollListener: (element, callback, options = {}) => {
try {
const {debounce: debounceMs = 0, throttle: throttleMs = 0, runInitial = !1} = options;
let handler = callback;
debounceMs > 0 && (handler = debounce(handler, debounceMs));
throttleMs > 0 && (handler = throttle(handler, throttleMs));
listeners.has(element) || listeners.set(element, new Set);
listeners.get(element).add(handler);
element.addEventListener("scroll", handler, {
passive: !0
});
if (runInitial) {
try {
callback();
} catch (err) {
logError("ScrollManager", "Initial callback error", err);
}
}
return () => {
try {
element.removeEventListener("scroll", handler);
const set = listeners.get(element);
if (set) {
set.delete(handler);
0 === set.size && listeners.delete(element);
}
} catch (err) {
logError("ScrollManager", "Cleanup error", err);
}
};
} catch (err) {
logError("ScrollManager", "addScrollListener error", err);
return () => {};
}
},
removeAllListeners: element => {
try {
const set = listeners.get(element);
if (!set) {
return;
}
set.forEach(handler => {
try {
element.removeEventListener("scroll", handler);
} catch (e) {}
});
listeners.delete(element);
} catch (err) {
logError("ScrollManager", "removeAllListeners error", err);
}
},
scrollToTop: (element, options = {}) => {
const {duration = 300, easing = "ease-out"} = options;
try {
if ("scrollBehavior" in (document.documentElement.style || {})) {
element.scrollTo({
top: 0,
behavior: "smooth"
});
return;
}
const start = element.scrollTop;
const startTime = performance.now();
const scroll = currentTime => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = "ease-out" === easing ? (t => t * (2 - t))(progress) : progress;
element.scrollTop = start * (1 - easedProgress);
progress < 1 && requestAnimationFrame(scroll);
};
requestAnimationFrame(scroll);
} catch (err) {
logError("ScrollManager", "scrollToTop error", err);
}
}
};
})();
if ("undefined" != typeof window && !window.__ytp_history_wrapped) {
window.__ytp_history_wrapped = !0;
const _origPush = history.pushState;
const _origReplace = history.replaceState;
history.pushState = function() {
const result = _origPush.apply(this, arguments);
try {
window.dispatchEvent(new CustomEvent("ytp-history-navigate", {
detail: {
type: "pushState"
}
}));
} catch (e) {
window.console.warn("[YouTube+] pushState event error:", e);
}
return result;
};
history.replaceState = function() {
const result = _origReplace.apply(this, arguments);
try {
window.dispatchEvent(new CustomEvent("ytp-history-navigate", {
detail: {
type: "replaceState"
}
}));
} catch (e) {
window.console.warn("[YouTube+] replaceState event error:", e);
}
return result;
};
}
const createRetryScheduler = opts => {
const {check, maxAttempts = 20, interval = 250, onGiveUp, label} = opts;
let attempts = 0;
let timerId = null;
let stopped = !1;
const _label = label || "retry";
const _hasPerfApi = "undefined" != typeof performance && "function" == typeof performance.mark;
const tick = () => {
if (!stopped) {
attempts++;
if (_hasPerfApi) {
try {
performance.mark(`ytp:${_label}:attempt:${attempts}`);
} catch (e) {}
}
try {
if (check()) {
stopped = !0;
if (_hasPerfApi) {
try {
performance.mark(`ytp:${_label}:success`);
} catch (e) {}
}
return;
}
} catch (e) {
logError("RetryScheduler", "check error", e);
}
if (attempts >= maxAttempts) {
stopped = !0;
if (_hasPerfApi) {
try {
performance.mark(`ytp:${_label}:giveup`);
} catch (e) {}
}
if ("function" == typeof onGiveUp) {
try {
onGiveUp();
} catch (e) {}
}
} else {
timerId = setTimeout(tick, interval);
}
}
};
timerId = setTimeout(tick, 0);
return {
stop() {
stopped = !0;
timerId && clearTimeout(timerId);
timerId = null;
}
};
};
const createVisibilityAwareInterval = (callback, delay, options = {}) => {
const label = options.label || "visibility-interval";
let intervalId = null;
let stopped = !1;
const tick = () => {
if (!(stopped || "undefined" != typeof document && document.hidden)) {
try {
callback();
} catch (e) {
logError("VisibilityInterval", `${label} tick failed`, e);
}
}
};
const pause = () => {
if (null !== intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};
const resume = () => {
if (!(stopped || null !== intervalId || "undefined" != typeof document && document.hidden)) {
intervalId = setInterval(tick, delay);
"function" == typeof cleanupManager?.registerInterval && cleanupManager.registerInterval(intervalId);
}
};
const visibilityHandler = () => {
"undefined" != typeof document && (document.hidden ? pause() : resume());
};
"function" == typeof cleanupManager?.registerListener && "undefined" != typeof document ? cleanupManager.registerListener(document, "visibilitychange", visibilityHandler, {
passive: !0
}) : "undefined" != typeof document && document.addEventListener("visibilitychange", visibilityHandler, {
passive: !0
});
resume();
return {
stop() {
stopped = !0;
pause();
},
pause,
resume,
get active() {
return null !== intervalId;
}
};
};
const ObserverRegistry = (() => {
let _active = 0;
let _peak = 0;
let _created = 0;
let _disconnected = 0;
return {
track() {
_active++;
_created++;
_active > _peak && (_peak = _active);
},
untrack() {
_active = Math.max(0, _active - 1);
_disconnected++;
},
getStats: () => ({
active: _active,
peak: _peak,
created: _created,
disconnected: _disconnected
}),
reset() {
_active = 0;
_peak = 0;
_created = 0;
_disconnected = 0;
},
dump() {
const stats = {
active: _active,
peak: _peak,
created: _created,
disconnected: _disconnected
};
const cmStats = cleanupManager ? {
observers: cleanupManager.observers?.size ?? "n/a",
intervals: cleanupManager.intervals?.size ?? "n/a",
timeouts: cleanupManager.timeouts?.size ?? "n/a",
listeners: "function" == typeof cleanupManager.getListenerStats ? cleanupManager.getListenerStats() : "n/a"
} : null;
window.console.warn("[YouTube+ Diagnostics] ObserverRegistry:", stats);
cmStats && window.console.warn("[YouTube+ Diagnostics] CleanupManager:", cmStats);
return {
observers: stats,
cleanup: cmStats
};
}
};
})();
const createFeatureToggle = (featureKey, defaultEnabled = !0) => {
let _enabled = loadFeatureEnabled(featureKey, defaultEnabled);
const _listeners = new Set;
return {
isEnabled: () => _enabled,
setEnabled(value) {
const next = !1 !== value;
if (next !== _enabled) {
_enabled = next;
try {
const raw = localStorage.getItem(SETTINGS_KEY);
const settings = raw ? JSON.parse(raw) : {};
settings[featureKey] = _enabled;
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
} catch (e) {}
for (const cb of _listeners) {
try {
cb(_enabled);
} catch (e) {}
}
}
},
onChange(cb) {
_listeners.add(cb);
return () => _listeners.delete(cb);
},
reload() {
_enabled = loadFeatureEnabled(featureKey, defaultEnabled);
}
};
};
if ("undefined" != typeof window) {
const winGlobal = window;
winGlobal.YouTubeUtils || (winGlobal.YouTubeUtils = {});
const U = winGlobal.YouTubeUtils;
U.logError = U.logError || logError;
U.debounce = U.debounce || debounce;
U.throttle = U.throttle || throttle;
U.StyleManager = U.StyleManager || StyleManager;
U.cleanupManager = U.cleanupManager || cleanupManager;
U.ScrollManager = U.ScrollManager || ScrollManager;
U.createElement = U.createElement || createElement;
U.waitForElement = U.waitForElement || waitForElement;
U.storage = U.storage || storage;
U.sanitizeHTML = U.sanitizeHTML || sanitizeHTML;
U.escapeHTMLAttribute = U.escapeHTMLAttribute || escapeHTMLAttribute;
U.safeMerge = U.safeMerge || safeMerge;
U.validateVideoId = U.validateVideoId || validateVideoId;
U.validatePlaylistId = U.validatePlaylistId || validatePlaylistId;
U.validateChannelId = U.validateChannelId || validateChannelId;
U.validateNumber = U.validateNumber || validateNumber;
U.isValidURL = U.isValidURL || isValidURL;
U.logger = U.logger || (() => {
const isDebugEnabled = (() => {
try {
if ("undefined" == typeof window) {
return !1;
}
const cfg = window.YouTubePlusConfig;
return !(!cfg || !cfg.debug) || void 0 !== window.YTP_DEBUG && !!window.YTP_DEBUG;
} catch (e) {
return !1;
}
})();
return {
debug: function(...args) {
isDebugEnabled && window.console?.warn && window.console.warn("[YouTube+][DEBUG]", ...args);
},
info: function(...args) {
isDebugEnabled && window.console?.warn && window.console.warn("[YouTube+][INFO]", ...args);
},
warn: function(...args) {
window.console?.warn && window.console.warn("[YouTube+]", ...args);
},
error: function(...args) {
window.console?.error && window.console.error("[YouTube+]", ...args);
}
};
})();
U.retryWithBackoff = U.retryWithBackoff || retryWithBackoff;
"function" != typeof U.createRetryScheduler && (U.createRetryScheduler = createRetryScheduler);
"function" != typeof U.createVisibilityAwareInterval && (U.createVisibilityAwareInterval = createVisibilityAwareInterval);
U.ObserverRegistry = U.ObserverRegistry || ObserverRegistry;
U.$ = U.$ || $;
U.$$ = U.$$ || $$;
U.byId = U.byId || byId;
U.t = U.t || t;
U.getLanguage = U.getLanguage || getLanguage;
U.setSafeHTML = U.setSafeHTML || setSafeHTML;
U.onDomReady = U.onDomReady || onDomReady;
U.loadFeatureEnabled = U.loadFeatureEnabled || loadFeatureEnabled;
U.isWatchPage = U.isWatchPage || isWatchPage;
U.isShortsPage = U.isShortsPage || isShortsPage;
U.isChannelPage = U.isChannelPage || isChannelPage;
U.formatTime = U.formatTime || formatTime;
U.createFeatureToggle = U.createFeatureToggle || createFeatureToggle;
U.SETTINGS_KEY = U.SETTINGS_KEY || SETTINGS_KEY;
U.isStudioPage = U.isStudioPage || isStudioPage;
window.__ytpDiagnostics || (window.__ytpDiagnostics = function(verbose) {
const obs = ObserverRegistry.getStats();
const cm = {
observers: cleanupManager.observers.size,
listeners: cleanupManager.getListenerStats(),
intervals: cleanupManager.intervals.size,
timeouts: cleanupManager.timeouts.size,
animationFrames: cleanupManager.animationFrames.size
};
let retryMetrics = null;
try {
if ("undefined" != typeof performance && "function" == typeof performance.getEntriesByType) {
const marks = performance.getEntriesByType("mark").filter(m => m.name.startsWith("ytp:"));
const retryLabels = new Set;
const retryData = {};
for (const m of marks) {
const parts = m.name.split(":");
if (parts.length >= 3) {
const label = parts[1];
retryLabels.add(label);
retryData[label] || (retryData[label] = {
attempts: 0,
success: !1,
giveup: !1
});
"attempt" === parts[2] ? retryData[label].attempts++ : "success" === parts[2] ? retryData[label].success = !0 : "giveup" === parts[2] && (retryData[label].giveup = !0);
}
}
retryMetrics = {
totalMarks: marks.length,
schedulers: retryData
};
}
} catch (e) {}
const report = {
observers: obs,
cleanupManager: cm,
retrySchedulers: retryMetrics,
timestamp: (new Date).toISOString()
};
window.console.warn("[YouTube+ Diagnostics] Observers:", obs);
window.console.warn("[YouTube+ Diagnostics] CleanupManager:", cm);
retryMetrics && window.console.warn("[YouTube+ Diagnostics] RetrySchedulers:", retryMetrics);
verbose && window.console.warn("[YouTube+ Diagnostics]", JSON.stringify(report, null, 2));
return report;
});
U.channelStatsHelpers = U.channelStatsHelpers || null;
try {
const w = window;
if (w && !w.__ytp_timers_wrapped) {
const origSetTimeout = w.setTimeout.bind(w);
const origSetInterval = w.setInterval.bind(w);
const origRaf = w.requestAnimationFrame ? w.requestAnimationFrame.bind(w) : null;
w.setTimeout = function(fn, ms, ...args) {
const id = origSetTimeout(fn, ms, ...args);
try {
U.cleanupManager.registerTimeout(id);
} catch (e) {}
return id;
};
w.setInterval = function(fn, ms, ...args) {
const id = origSetInterval(fn, ms, ...args);
try {
U.cleanupManager.registerInterval(id);
} catch (e) {}
return id;
};
origRaf && (w.requestAnimationFrame = function(cb) {
const id = origRaf(cb);
try {
U.cleanupManager.registerAnimationFrame(id);
} catch (e) {}
return id;
});
w.__ytp_timers_wrapped = !0;
}
} catch (e) {
logError("utils", "timer wrapper failed", e);
}
try {
const navCleanupHost = window;
if (!navCleanupHost.__ytp_nav_cleanup_registered) {
navCleanupHost.__ytp_nav_cleanup_registered = !0;
document.addEventListener("yt-navigate-start", () => {
try {
U.cleanupManager.cleanup();
} catch (e) {}
});
}
} catch (e) {}
window.YouTubePlusChannelStatsHelpers || (window.YouTubePlusChannelStatsHelpers = {
async fetchWithRetry(fetchFn, maxRetries = 2, logger = console) {
let attempt = 0;
for (;attempt <= maxRetries; ) {
try {
const res = await fetchFn();
return res;
} catch (err) {
attempt += 1;
if (attempt > maxRetries) {
logger && logger.warn && logger.warn("[ChannelStatsHelpers] fetch failed after retries", err);
return null;
}
await new Promise(r => setTimeout(r, 300 * attempt));
}
}
return null;
},
cacheStats(mapLike, channelId, stats) {
try {
if (!mapLike || "function" != typeof mapLike.set) {
return;
}
mapLike.set(channelId, stats);
} catch (e) {}
},
getCachedStats(mapLike, channelId, cacheDuration = 6e4) {
try {
if (!mapLike || "function" != typeof mapLike.get) {
return null;
}
const s = mapLike.get(channelId);
return s ? s.timestamp && Date.now() - s.timestamp > cacheDuration ? null : s : null;
} catch (e) {
return null;
}
},
extractSubscriberCountFromPage() {
try {
const el = $("yt-formatted-string#subscriber-count") || $('[id*="subscriber-count"]');
if (!el) {
return 0;
}
const txt = el.textContent || "";
const digits = txt.replace(/[^0-9]/g, "");
return digits ? parseInt(digits, 10) : 0;
} catch (e) {
return 0;
}
},
createFallbackStats: (followerCount = 0) => ({
followerCount: followerCount || 0,
bottomOdos: [ 0, 0 ],
error: !0,
timestamp: Date.now()
})
});
}
})();

!(function() {
"use strict";
const styleManager = window.YouTubeUtils?.StyleManager;
styleManager?.add && styleManager.add("yt-plus-design-system", "\n    :root{\n      --yt-accent:#ff0000;\n      --yt-accent-secondary:#1976d2;\n      --yt-accent-secondary-light:#42a5f5;\n      --yt-accent-secondary-ghost:rgba(25,118,210,0.28);\n      --yt-accent-secondary-light-ghost:rgba(66,165,245,0.4);\n      --yt-accent-secondary-shadow:rgba(25,118,210,0.25);\n      --yt-primary-soft:rgba(33,150,243,.12);\n      --yt-primary-soft-hover:rgba(33,150,243,.22);\n      --yt-primary-border:rgba(33,150,243,.25);\n      --yt-primary-text:#2196f3;\n      --yt-surface-soft:rgba(255,255,255,.08);\n      --yt-surface-active:rgba(255,255,255,.12);\n      --yt-surface-active-strong:rgba(255,255,255,.14);\n      --yt-surface-border-strong:rgba(255,255,255,.15);\n      --yt-surface-overlay-soft:rgba(255,255,255,.1);\n      --yt-surface-overlay-subtle:rgba(255,255,255,.04);\n      --yt-surface-overlay-faint:rgba(255,255,255,.02);\n      --yt-surface-overlay-border:rgba(255,255,255,.06);\n      --yt-danger-soft:rgba(255,59,59,0.15);\n      --yt-danger-soft-hover:rgba(255,59,59,0.25);\n      --yt-danger-border:rgba(255,59,59,0.3);\n      --yt-danger-text:#ff5c5c;\n      --yt-danger-ghost:rgba(255,0,0,.12);\n      --yt-danger-shadow:rgba(255,0,0,.3);\n      --yt-danger-shadow-strong:rgba(255,0,0,.35);\n      --yt-danger-card-bg-start:rgba(255,0,0,.28);\n      --yt-danger-card-bg-end:rgba(255,0,0,.16);\n      --yt-danger-card-border:rgba(255,0,0,.45);\n      --yt-danger-card-inset:rgba(255,0,0,.22);\n      --yt-success:#4caf50;\n      --yt-success-soft:rgba(76,175,80,0.2);\n      --yt-success-soft-hover:rgba(76,175,80,.22);\n      --yt-danger:#f44336;\n      --yt-warning:#ffc107;\n      --yt-warning-soft:rgba(255,193,7,0.2);\n      --yt-shadow-soft:rgba(0,0,0,.2);\n      --yt-shadow-soft-strong:rgba(0,0,0,.3);\n      --yt-shadow-inset-soft:rgba(0,0,0,.04);\n      --yt-shadow-inset-strong:rgba(0,0,0,.35);\n      --yt-shadow-flyout:rgba(0,0,0,.5);\n       --yt-shadow-notification:rgba(0,0,0,.3);\n       --yt-shadow-deep-1:rgba(0,0,0,.15);\n       --yt-shadow-deep-2:rgba(0,0,0,.1);\n       --yt-shadow-deep-3:rgba(0,0,0,.06);\n       --yt-shadow-deep-4:rgba(0,0,0,.09);\n       --yt-border-light:rgba(0,0,0,.25);\n       --yt-overlay-strong:rgba(0,0,0,.55);\n       --yt-overlay-deep:rgba(0,0,0,.2);\n       --yt-overlay-faint:rgba(0,0,0,.15);\n       --yt-tab-color-accent:#ff4533;\n       --yt-scrollbar-outline:rgba(127,127,127,.5);\n       --yt-panel-overlay-subtle:rgba(0,0,0,.05);\n      --yt-scrollbar-thumb:rgba(144,144,144,.5);\n      --yt-scrollbar-thumb-hover:rgba(170,170,170,.7);\n      --yt-panel-overlay-weak:rgba(0,0,0,.02);\n      --yt-badge-bg-light:rgba(255,255,255,.1);\n      --yt-badge-bg-dark:rgba(0,0,0,.05);\n      --yt-text-dark-primary:#0f0f0f;\n      --yt-playall-accent-purple:#bf4bcc;\n      --yt-playall-accent-blue:#2b66da;\n      --yt-search-highlight-bg:rgba(255,99,71,.12);\n      --yt-search-highlight-border:rgba(255,99,71,.25);\n      --yt-search-highlight-border-strong:rgba(255,99,71,.4);\n      --yt-search-highlight-faint:rgba(255,99,71,.1);\n      --yt-search-highlight-hover:rgba(255,99,71,.22);\n      --yt-search-highlight-accent:#ff5c5c;\n      --yt-shorts-shadow-deep:rgba(0,0,0,.4);\n      --yt-shorts-overlay-gray:rgba(155,155,155,.15);\n      --yt-shorts-border-light:rgba(255,255,255,.2);\n      --yt-shorts-shadow-blue:rgba(31,38,135,.37);\n      --yt-shorts-feedback-bg-dark:rgba(34,34,34,.7);\n      --yt-shorts-feedback-bg-light:rgba(255,255,255,.95);\n      --yt-shorts-border-dark:rgba(0,0,0,.08);\n      --yt-shorts-help-bg-light:rgba(255,255,255,.98);\n      --yt-shorts-header-bg:rgba(255,255,255,.05);\n      --yt-shorts-header-bg-light:rgba(0,0,0,.04);\n      --yt-shorts-kbd-bg:rgba(255,255,255,.15);\n      --yt-shorts-kbd-border:rgba(255,255,255,.2);\n      --yt-shorts-kbd-bg-light:rgba(0,0,0,.06);\n      --yt-shorts-kbd-hover:rgba(255,255,255,.22);\n      --yt-shorts-text-secondary:rgba(255,255,255,.92);\n      --yt-shorts-footer-bg:rgba(255,255,255,.05);\n      --yt-shorts-footer-bg-light:rgba(0,0,0,.04);\n      --yt-shorts-panel-header:rgba(255,255,255,.1);\n      --yt-shorts-panel-header-bg:rgba(255,255,255,.05);\n      --yt-shorts-feedback-bg:rgba(255,255,255,.15);\n      --yt-shorts-feedback-border:rgba(255,255,255,.2);\n      --yt-shorts-help-bg:rgba(255,255,255,.15);\n      --yt-shorts-help-border:rgba(255,255,255,.2);\n      --yt-shorts-kbd-non-editable:rgba(0,0,0,.08);\n      --yt-muted-text:#666;\n      --yt-success-accent:#10c56a;\n      --yt-success-accent-soft:rgba(16,197,106,0.15);\n      --yt-surface-contrast:#111;\n      --yt-progress-track:#e0e0e0;\n      --yt-progress-fill:#1a73e8;\n      --yt-modal-surface:rgba(20,20,20,.64);\n      --yt-radius-xs:6px;\n      --yt-radius-sm:10px;\n      --yt-radius-md:14px;\n      --yt-radius-lg:20px;\n      --yt-space-sm:8px;\n      --yt-space-md:16px;\n      --yt-space-lg:24px;\n      --yt-transition-fast:all .14s cubic-bezier(.2,.8,.2,1);\n      --yt-transition-default:all .24s cubic-bezier(.2,.8,.2,1);\n      --yt-glass-blur:blur(18px) saturate(180%);\n      --yt-glass-blur-light:blur(12px) saturate(160%);\n      --yt-glass-blur-heavy:blur(24px) saturate(200%);\n      --yt-z-overlay:1000;\n      --yt-z-flyout:20000;\n      --yt-z-modal:100000;\n      --yt-stats-icon-views-bg:rgba(59,130,246,0.15);\n      --yt-stats-icon-views:#3b82f6;\n      --yt-stats-icon-likes-bg:rgba(34,197,94,0.15);\n      --yt-stats-icon-likes:#22c55e;\n      --yt-stats-icon-dislikes-bg:rgba(239,68,68,0.15);\n      --yt-stats-icon-dislikes:#ef4444;\n      --yt-stats-icon-comments-bg:rgba(168,85,247,0.15);\n      --yt-stats-icon-comments:#a855f7;\n      --yt-stats-icon-viewers-bg:rgba(234,179,8,0.15);\n      --yt-stats-icon-viewers:#eab308;\n      --yt-stats-icon-subscribers-bg:rgba(236,72,153,0.15);\n      --yt-stats-icon-subscribers:#ec4899;\n      --yt-stats-icon-videos-bg:rgba(14,165,233,0.15);\n      --yt-stats-icon-videos:#0ea5e9;\n      --yt-stats-card-bg-dark:rgba(255,255,255,0.05);\n      --yt-stats-card-bg-light:rgba(0,0,0,0.03);\n      --yt-stats-card-border-dark:rgba(255,255,255,0.08);\n      --yt-stats-card-border-light:rgba(0,0,0,0.1);\n      --yt-stats-text-secondary-dark:rgba(255,255,255,0.65);\n      --yt-stats-text-secondary-light:rgba(0,0,0,0.6);\n      --yt-stats-text-label:rgba(255,255,255,0.72);\n      --yt-stats-text-exact-dark:rgba(255,255,255,0.5);\n      --yt-stats-text-exact-light:rgba(0,0,0,0.5);\n      --yt-stats-text-value-dark:#fff;\n      --yt-stats-text-value-light:#111;\n      --yt-stats-error:#ff6b6b;\n      --yt-stats-link-color:#0b61d6;\n      --yt-stats-link-hover:#e6f0ff;\n      --yt-stats-link-hover-dark:#0647a6;\n      --yt-stats-loader-text-dark:#fff;\n      --yt-stats-loader-text-light:#666;\n      --yt-stats-shadow-hover:rgba(0,0,0,0.3);\n      --yt-stats-shadow-deep:rgba(0,0,0,0.32);\n      --yt-stats-modal-shadow:rgba(0,0,0,0.45);\n      --yt-stats-bg-overlay-dark:rgba(28,28,28,0.75);\n      --yt-stats-img-border-dark:rgba(255,255,255,0.06);\n      --yt-stats-img-border-light:rgba(0,0,0,0.06);\n      --yt-stats-button-bg-dark:rgba(24,24,24,0.68);\n      --yt-stats-button-border-dark:rgba(255,255,255,0.08);\n      --yt-stats-button-border-light:rgba(0,0,0,0.06);\n      --yt-stats-button-bg-light:rgba(255,255,255,0.12);\n      --yt-stats-author-name-bright:rgba(255,255,255,0.9);\n      --yt-stats-author-name-light:rgba(0,0,0,0.8);\n      --yt-stats-channel-button-bg:rgba(0,0,0,0.4);\n      --yt-stats-channel-button-border:rgba(255,255,255,0.1);\n      --yt-stats-channel-button-hover:rgba(0,0,0,0.6);\n      --yt-stats-channel-button-hover-border:rgba(255,255,255,0.3);\n      --yt-stats-channel-menu-bg:rgba(28,28,28,0.75);\n      --yt-stats-channel-menu-border:rgba(255,255,255,0.08);\n      --yt-stats-channel-menu-item-bg:rgba(255,255,255,0.02);\n      --yt-stats-channel-label-text:#eee;\n      --yt-stats-channel-input-bg:rgba(255,255,255,0.1);\n      --yt-stats-channel-input-hover:rgba(255,255,255,0.15);\n      --yt-stats-channel-select-option-bg:#333;\n      --yt-stats-channel-range-bg:rgba(255,255,255,0.2);\n      --yt-stats-channel-range-thumb:#3ea6ff;\n      --yt-stats-channel-checkbox-border:rgba(255,255,255,0.4);\n      --yt-stats-channel-text-value:#bbb;\n      --yt-stats-channel-text-shadow:rgba(0,0,0,0.3);\n      --yt-stats-link-color:#0b61d6;\n      --yt-stats-link-hover-dark:#0647a6;\n      --yt-stats-positive-indicator:#1ed760;\n      --yt-stats-negative-indicator:#f3727f;\n      --yt-stats-channel-filter-shadow:rgba(0,0,0,0.5);\n      --yt-timecode-panel-bg-dark:rgba(34,34,34,0.75);\n      --yt-timecode-panel-bg-light:rgba(255,255,255,0.95);\n      --yt-timecode-panel-border-dark:rgba(255,255,255,0.12);\n      --yt-timecode-panel-border-light:rgba(0,0,0,0.08);\n      --yt-timecode-panel-color-dark:#fff;\n      --yt-timecode-panel-color-light:#222;\n      --yt-timecode-panel-shadow:rgba(0,0,0,0.45);\n      --yt-timecode-active-bg-start:rgba(255,68,68,0.12);\n      --yt-timecode-active-bg-end:rgba(255,68,68,0.04);\n      --yt-timecode-active-border:#ff6666;\n      --yt-timecode-active-inset:rgba(255,68,68,0.03);\n      --yt-timecode-chapter:#ff4444;\n      --yt-timecode-toggle-active-start:#ff6b6b;\n      --yt-timecode-export-success-bg:rgba(0,220,0,0.8);\n      --yt-update-card-shadow:rgba(6,10,20,0.45);\n      --yt-update-available-dot:#ff4444;\n      --yt-update-available-text:#ff6666;\n      --yt-update-install-bg-start:#ff4500;\n      --yt-update-install-bg-end:#ff6b35;\n      --yt-update-install-shadow:rgba(255,69,0,0.3);\n      --yt-thumbnail-overlay-idle:rgba(0,0,0,0.3);\n      --yt-thumbnail-overlay-hover:rgba(0,0,0,0.7);\n      --yt-thumbnail-overlay-active:rgba(0,0,0,0.9);\n    }\n\n    html[dark],html:not([dark]):not([light]){\n      --yt-bg-primary:rgba(15,15,15,.85);\n      --yt-bg-secondary:rgba(28,28,28,.85);\n      --yt-bg-tertiary:rgba(34,34,34,.85);\n      --yt-text-primary:#fff;\n      --yt-text-secondary:#aaa;\n      --yt-border-color:rgba(255,255,255,.2);\n      --yt-hover-bg:rgba(255,255,255,.1);\n      --yt-shadow:0 4px 12px rgba(0,0,0,.25);\n      --yt-glass-bg:rgba(50,50,50,.5);\n      --yt-glass-border:rgba(255,255,255,.2);\n      --yt-glass-shadow:0 8px 32px rgba(0,0,0,.2);\n      --yt-modal-bg:rgba(0,0,0,.75);\n      --yt-notification-bg:rgba(28,28,28,.9);\n      --yt-panel-bg:rgba(34,34,34,.3);\n      --yt-header-bg:rgba(20,20,20,.6);\n      --yt-input-bg:rgba(255,255,255,.1);\n      --yt-button-bg:rgba(255,255,255,.2);\n      --yt-text-stroke:#fff;\n    }\n\n    html[light]{\n      --yt-bg-primary:rgba(255,255,255,.85);\n      --yt-bg-secondary:rgba(248,248,248,.85);\n      --yt-bg-tertiary:rgba(240,240,240,.85);\n      --yt-text-primary:#030303;\n      --yt-text-secondary:#606060;\n      --yt-border-color:rgba(0,0,0,.2);\n      --yt-hover-bg:rgba(0,0,0,.05);\n      --yt-shadow:0 4px 12px rgba(0,0,0,.15);\n      --yt-glass-bg:rgba(255,255,255,.7);\n      --yt-glass-border:rgba(0,0,0,.1);\n      --yt-glass-shadow:0 8px 32px rgba(0,0,0,.1);\n      --yt-modal-bg:rgba(0,0,0,.5);\n      --yt-notification-bg:rgba(255,255,255,.95);\n      --yt-panel-bg:rgba(255,255,255,.7);\n      --yt-header-bg:rgba(248,248,248,.8);\n      --yt-input-bg:rgba(0,0,0,.05);\n      --yt-button-bg:rgba(0,0,0,.1);\n      --yt-text-stroke:#030303;\n    }\n\n    .ytp-plus-btn{\n      padding:var(--yt-space-sm) var(--yt-space-md);\n      border-radius:18px;\n      border:1px solid var(--yt-glass-border);\n      font-size:14px;\n      font-weight:500;\n      cursor:pointer;\n      color:var(--yt-text-primary);\n      background:var(--yt-button-bg);\n      transition:var(--yt-transition-default);\n    }\n    .ytp-plus-btn:hover{\n      transform:translateY(-1px);\n      box-shadow:var(--yt-shadow);\n      background:var(--yt-hover-bg);\n    }\n    .ytp-plus-btn--primary{\n      background:transparent;\n    }\n    .ytp-plus-btn--primary:hover{\n      background:var(--yt-accent);\n      color:#fff;\n      box-shadow:0 6px 16px rgba(255,0,0,.35);\n    }\n\n    .ytp-plus-panel{\n      background:var(--yt-glass-bg);\n      border:1px solid var(--yt-glass-border);\n      border-radius:var(--yt-radius-md);\n      box-shadow:var(--yt-glass-shadow);\n      color:var(--yt-text-primary);\n    }\n\n    .ytp-plus-modal-overlay{\n      position:fixed;\n      inset:0;\n      background:var(--yt-modal-bg);\n      display:flex;\n      align-items:center;\n      justify-content:center;\n      z-index:var(--yt-z-modal);\n      backdrop-filter:blur(8px) saturate(140%);\n      -webkit-backdrop-filter:blur(8px) saturate(140%);\n      animation:ytEnhanceFadeIn .25s ease-out;\n    }\n\n    .ytp-plus-modal-content{\n      background:var(--yt-glass-bg);\n      border:1.5px solid var(--yt-glass-border);\n      border-radius:24px;\n      color:var(--yt-text-primary);\n      box-shadow:0 12px 40px rgba(0,0,0,.45);\n      backdrop-filter:blur(14px) saturate(140%);\n      -webkit-backdrop-filter:blur(14px) saturate(140%);\n      animation:ytEnhanceScaleIn .28s cubic-bezier(.4,0,.2,1);\n    }\n\n    @keyframes ytEnhanceFadeIn{from{opacity:0;}to{opacity:1;}}\n    @keyframes ytEnhanceScaleIn{from{opacity:0;transform:scale(.92) translateY(10px);}to{opacity:1;transform:scale(1) translateY(0);}}\n    @keyframes fadeInModal{from{opacity:0}to{opacity:1}}\n    @keyframes scaleInModal{from{transform:scale(0.95);opacity:0}to{transform:scale(1);opacity:1}}\n    @keyframes spin{to{transform:rotate(360deg)}}\n    @keyframes dash{0%{stroke-dashoffset:80}50%{stroke-dashoffset:10}100%{stroke-dashoffset:80}}\n    @keyframes slideInFromBottom{from{transform:translateY(100%);opacity:0;}to{transform:translateY(0);opacity:1;}}\n    @keyframes slideOutToBottom{from{transform:translateY(0);opacity:1;}to{transform:translateY(100%);opacity:0;}}\n\n    @keyframes ytp-resume-fadein{from{opacity:0;}to{opacity:1;}}\n\n    @media (prefers-reduced-motion: reduce){\n      *,*::before,*::after{\n        animation:none !important;\n        transition:none !important;\n      }\n    }\n  ");
})();

!(function() {
"use strict";
"undefined" != typeof window && (window.YouTubePlusDesignSystem = {
...window.YouTubePlusDesignSystem || {},
initGlassDropdown: function initGlassDropdown(config) {
const resolveElement = target => target ? "string" == typeof target ? document.querySelector(target) : target : null;
const dropdown = resolveElement(config?.dropdown);
const hiddenSelect = resolveElement(config?.hiddenSelect);
if (!dropdown || !hiddenSelect) {
return () => {};
}
const toggle = dropdown.querySelector(".glass-dropdown__toggle");
const list = dropdown.querySelector(".glass-dropdown__list");
const label = dropdown.querySelector(".glass-dropdown__label");
if (!toggle || !list || !label) {
return () => {};
}
let items = Array.from(list.querySelectorAll(".glass-dropdown__item"));
let idx = items.findIndex(it => "true" === it.getAttribute("aria-selected"));
idx < 0 && (idx = 0);
const closeList = () => {
dropdown.setAttribute("aria-expanded", "false");
list.style.display = "none";
};
const openList = () => {
dropdown.setAttribute("aria-expanded", "true");
list.style.display = "block";
items = Array.from(list.querySelectorAll(".glass-dropdown__item"));
};
closeList();
const selectedItem = items[idx];
if (selectedItem) {
hiddenSelect.value = selectedItem.getAttribute("data-value") || "";
label.textContent = selectedItem.textContent || "";
}
const handleToggleClick = () => {
const expanded = "true" === dropdown.getAttribute("aria-expanded");
expanded ? closeList() : openList();
};
const handleDocumentClick = e => {
const target = e.target;
target && !dropdown.contains(target) && closeList();
};
const handleListClick = e => {
const source = e.target instanceof Element ? e.target : null;
const item = source?.closest(".glass-dropdown__item");
if (!(item instanceof HTMLElement)) {
return;
}
const value = item.getAttribute("data-value") || "";
hiddenSelect.value = value;
list.querySelectorAll(".glass-dropdown__item").forEach(li => li.removeAttribute("aria-selected"));
item.setAttribute("aria-selected", "true");
idx = items.indexOf(item);
label.textContent = item.textContent || "";
hiddenSelect.dispatchEvent(new Event("change", {
bubbles: !0
}));
closeList();
};
const handleKeyDown = e => {
const expanded = "true" === dropdown.getAttribute("aria-expanded");
if ("ArrowDown" === e.key) {
e.preventDefault();
expanded || openList();
idx = Math.min(idx + 1, items.length - 1);
items.forEach(it => it.removeAttribute("aria-selected"));
items[idx]?.setAttribute("aria-selected", "true");
items[idx]?.scrollIntoView({
block: "nearest"
});
} else if ("ArrowUp" === e.key) {
e.preventDefault();
expanded || openList();
idx = Math.max(idx - 1, 0);
items.forEach(it => it.removeAttribute("aria-selected"));
items[idx]?.setAttribute("aria-selected", "true");
items[idx]?.scrollIntoView({
block: "nearest"
});
} else if ("Enter" === e.key || " " === e.key) {
e.preventDefault();
if (!expanded) {
openList();
return;
}
const item = items[idx];
if (!item) {
return;
}
hiddenSelect.value = item.getAttribute("data-value") || "";
hiddenSelect.dispatchEvent(new Event("change", {
bubbles: !0
}));
label.textContent = item.textContent || "";
closeList();
} else {
"Escape" === e.key && closeList();
}
};
toggle.addEventListener("click", handleToggleClick);
list.addEventListener("click", handleListClick);
dropdown.addEventListener("keydown", handleKeyDown);
window.YouTubeUtils?.cleanupManager?.registerListener ? window.YouTubeUtils.cleanupManager.registerListener(document, "click", handleDocumentClick) : document.addEventListener("click", handleDocumentClick);
return () => {
toggle.removeEventListener("click", handleToggleClick);
list.removeEventListener("click", handleListClick);
dropdown.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("click", handleDocumentClick);
};
}
});
})();

!(function() {
"use strict";
const BLOCKED_TAGS = new Set([ "SCRIPT", "IFRAME", "OBJECT", "EMBED", "LINK", "META", "STYLE", "BASE", "FORM" ]);
const URL_ATTRS = new Set([ "href", "src", "xlink:href", "formaction", "action", "poster" ]);
let trustedTypesFacade = null;
function getTrustedTypesPolicy() {
if ("undefined" == typeof window || void 0 === window.trustedTypes) {
return null;
}
const existing = window.YouTubeTrustedTypes;
if (existing?.policy) {
return existing.policy;
}
try {
const policy = window.trustedTypes.createPolicy("youtubeplus#sanitize", {
createHTML: value => "string" != typeof value ? String(value ?? "") : value.replace(/<script\b[\s\S]*?<\/script\s*>/gi, "").replace(/<iframe\b[\s\S]*?<\/iframe\s*>/gi, "").replace(/<object\b[\s\S]*?<\/object\s*>/gi, "").replace(/<embed\b[^>]*\/?>/gi, "").replace(/\s+on[a-z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "").replace(/javascript\s*:/gi, "").replace(/data\s*:\s*text\/html/gi, "blocked:"),
createScriptURL: value => {
if ("string" != typeof value) {
return String(value ?? "");
}
try {
const url = new URL(value, location.origin);
if (url.origin === location.origin) {
return value;
}
if (url.hostname.endsWith(".googleapis.com") || url.hostname.endsWith(".youtube.com")) {
return value;
}
} catch (e) {}
window.console.warn("[YouTube+][Security] Blocked untrusted script URL:", value);
return "about:blank";
},
createScript: value => "string" == typeof value ? value : String(value ?? "")
});
"undefined" != typeof window && window.YouTubeTrustedTypes && (window.YouTubeTrustedTypes.policy = policy);
return policy;
} catch (e) {
return existing?.policy || null;
}
}
function createTrustedHTML(html) {
const normalized = "string" == typeof html ? html : String(html ?? "");
const policy = getTrustedTypesPolicy();
return policy ? policy.createHTML(normalized) : normalized;
}
function createTrustedScriptURL(url) {
const normalized = "string" == typeof url ? url : String(url ?? "");
const policy = getTrustedTypesPolicy();
return policy ? policy.createScriptURL(normalized) : normalized;
}
function createTrustedScript(value) {
const normalized = "string" == typeof value ? value : String(value ?? "");
const policy = getTrustedTypesPolicy();
return policy ? policy.createScript(normalized) : normalized;
}
function createFragment(html) {
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
const safeHTML = (function normalizeHTMLForSink(html) {
return null == html ? "" : "string" == typeof html && "undefined" != typeof window && void 0 !== window.trustedTypes ? createTrustedHTML(html) : html;
})(html);
return range.createContextualFragment(safeHTML);
}
function escapeHTML(input) {
if ("string" != typeof input || 0 === input.length) {
return "";
}
const div = document.createElement("div");
div.textContent = input;
return div.innerHTML;
}
function isSafeUrl(value) {
if ("string" != typeof value) {
return !1;
}
const normalized = value.trim();
if (!normalized) {
return !0;
}
if (normalized.startsWith("#") || normalized.startsWith("/")) {
return !0;
}
const lower = normalized.toLowerCase();
if (lower.startsWith("javascript:")) {
return !1;
}
if (lower.startsWith("vbscript:")) {
return !1;
}
if (lower.startsWith("data:") && !lower.startsWith("data:image/")) {
return !1;
}
try {
const url = new URL(normalized, location.origin);
return [ "http:", "https:", "mailto:", "tel:", "blob:" ].includes(url.protocol);
} catch (e) {
return !1;
}
}
function sanitizeHTML(html) {
if ("string" != typeof html || 0 === html.length) {
return "";
}
const fragment = createFragment(createTrustedHTML(html));
const showElement = window.NodeFilter && window.NodeFilter.SHOW_ELEMENT || 1;
const walker = document.createTreeWalker(fragment, showElement);
const toRemove = [];
for (;walker.nextNode(); ) {
const element = walker.currentNode;
if (BLOCKED_TAGS.has(element.tagName)) {
toRemove.push(element);
continue;
}
const attrs = Array.from(element.attributes);
for (const attr of attrs) {
const name = attr.name.toLowerCase();
const value = attr.value;
name.startsWith("on") || "srcdoc" === name ? element.removeAttribute(attr.name) : URL_ATTRS.has(name) && !isSafeUrl(value) && element.removeAttribute(attr.name);
}
}
for (const el of toRemove) {
el.remove();
}
const container = document.createElement("div");
container.append(fragment);
return container.innerHTML;
}
function createSafeHTML(html) {
const sanitized = sanitizeHTML(html);
return createTrustedHTML(sanitized);
}
function setHTML(element, html, options = {}) {
if (!(element instanceof HTMLElement)) {
return;
}
const shouldSanitize = !1 !== options.sanitize;
const content = shouldSanitize ? createSafeHTML(html) : html;
const fragment = createFragment(content || "");
element.replaceChildren(fragment);
}
function setText(element, text) {
element instanceof HTMLElement && (element.textContent = "string" == typeof text ? text : "");
}
function renderTemplateClone(element, html) {
if (!(element instanceof Element)) {
return;
}
const trusted = createTrustedHTML("string" == typeof html ? html : String(html ?? ""));
const fragment = createFragment(trusted);
element.replaceChildren(fragment);
}
function isValidVideoId(id) {
return !(!id || "string" != typeof id) && /^[a-zA-Z0-9_-]{11}$/.test(id);
}
function isValidChannelId(id) {
return !(!id || "string" != typeof id) && /^UC[a-zA-Z0-9_-]{22}$/.test(id);
}
function isYouTubeUrl(url) {
if (!url || "string" != typeof url) {
return !1;
}
try {
const parsed = new URL(url);
const hostname = parsed.hostname.toLowerCase();
return "www.youtube.com" === hostname || "youtube.com" === hostname || "m.youtube.com" === hostname || "music.youtube.com" === hostname || hostname.endsWith(".youtube.com");
} catch (e) {
return !1;
}
}
function sanitizeText(text) {
return text && "string" == typeof text ? text.replace(/[<>]/g, "").replace(/javascript:/gi, "").replace(/on\w+=/gi, "").trim() : "";
}
function sanitizeAttribute(attrName, attrValue) {
if (!attrName || "string" != typeof attrName) {
return null;
}
if (null == attrValue) {
return "";
}
if (/^on[a-z]/i.test(attrName)) {
window.console.warn(`[Security] Blocked event handler attribute: ${attrName}`);
return null;
}
const valueStr = String(attrValue);
const lowerName = attrName.toLowerCase();
if ("href" === lowerName || "src" === lowerName) {
if (/^javascript:/i.test(valueStr)) {
window.console.warn(`[Security] Blocked javascript protocol in ${attrName}`);
return null;
}
if (valueStr.toLowerCase().startsWith("data:") && !valueStr.toLowerCase().startsWith("data:image/")) {
window.console.warn(`[Security] Blocked non-image data: URI in ${attrName}`);
return null;
}
}
return valueStr;
}
function setAttributeSafe(element, attrName, attrValue) {
if (!(element instanceof HTMLElement)) {
window.console.error("[Security] Invalid element for setAttributeSafe");
return !1;
}
const sanitizedValue = sanitizeAttribute(attrName, attrValue);
if (null === sanitizedValue) {
return !1;
}
try {
element.setAttribute(attrName, sanitizedValue);
return !0;
} catch (e) {
window.console.error("[Security] setAttribute failed:", e);
return !1;
}
}
function validateNumber(value, min = -Infinity, max = Infinity) {
const num = Number(value);
return isNaN(num) || !isFinite(num) || num < min || num > max ? null : num;
}
class RateLimiter {
constructor(maxRequests = 10, timeWindow = 6e4, maxKeys = 100) {
this.maxRequests = maxRequests;
this.timeWindow = timeWindow;
this.maxKeys = maxKeys;
this.requests = new Map;
}
canRequest(key) {
const now = Date.now();
const requests = this.requests.get(key) || [];
const recentRequests = requests.filter(time => now - time < this.timeWindow);
if (recentRequests.length >= this.maxRequests) {
window.console.warn(`[Security] Rate limit exceeded for ${key}. Max ${this.maxRequests} requests per ${this.timeWindow}ms.`);
return !1;
}
recentRequests.push(now);
this.requests.set(key, recentRequests);
if (this.requests.size > this.maxKeys) {
const keysToDelete = this.requests.size - this.maxKeys;
const iter = this.requests.keys();
for (let i = 0; i < keysToDelete; i += 1) {
const oldest = iter.next().value;
oldest !== key && this.requests.delete(oldest);
}
}
return !0;
}
clear() {
this.requests.clear();
}
}
function fetchWithTimeout(url, options = {}, timeout = 1e4) {
return Promise.race([ fetch(url, options), new Promise((_, reject) => setTimeout(() => reject(new Error("Request timeout")), timeout)) ]);
}
function validateJSONSchema(data, schema) {
if (!data || "object" != typeof data) {
return !1;
}
if (!schema || "object" != typeof schema) {
return !0;
}
const schemaObj = schema;
const dataObj = data;
for (const key in schemaObj) {
if (schemaObj[key].required && !(key in dataObj)) {
window.console.warn(`[Security] Missing required field: ${key}`);
return !1;
}
if (key in dataObj && schemaObj[key].type && typeof dataObj[key] !== schemaObj[key].type) {
window.console.warn(`[Security] Invalid type for field ${key}: expected ${schemaObj[key].type}, got ${typeof dataObj[key]}`);
return !1;
}
}
return !0;
}
if ("undefined" != typeof window) {
trustedTypesFacade = {
policyName: "youtubeplus#sanitize",
isSupported: void 0 !== window.trustedTypes,
getPolicy: getTrustedTypesPolicy,
createHTML: createTrustedHTML,
createScriptURL: createTrustedScriptURL,
createScript: createTrustedScript
};
window.YouTubeTrustedTypes = trustedTypesFacade;
window._ytplusCreateHTML = createTrustedHTML;
!(function installDefaultTrustedTypesPolicy() {
if ("undefined" == typeof window || void 0 === window.trustedTypes) {
return;
}
if (window.trustedTypes.defaultPolicy) {
return;
}
const namedPolicy = getTrustedTypesPolicy();
try {
window.trustedTypes.createPolicy("default", {
createHTML: value => {
const safeValue = "string" == typeof value ? value : String(value ?? "");
return namedPolicy ? namedPolicy.createHTML(safeValue) : safeValue;
},
createScriptURL: value => {
const safeValue = "string" == typeof value ? value : String(value ?? "");
return namedPolicy ? namedPolicy.createScriptURL(safeValue) : safeValue;
},
createScript: value => "string" == typeof value ? value : String(value ?? "")
});
} catch (e) {}
})();
window.YouTubeSafeDOM = {
escapeHTML,
sanitizeHTML,
createSafeHTML,
createFragment,
createTrustedHTML,
createTrustedScriptURL,
createTrustedScript,
createTrustedInlineScript: createTrustedScript,
getTrustedTypesPolicy,
setHTML,
renderTemplateClone,
setText,
isSafeUrl,
isValidVideoId,
isValidChannelId,
isYouTubeUrl,
sanitizeText,
sanitizeAttribute,
setAttributeSafe,
validateNumber,
RateLimiter,
fetchWithTimeout,
validateJSONSchema
};
window.YouTubeSecurityUtils = {
isValidVideoId,
isValidChannelId,
isYouTubeUrl,
sanitizeText,
escapeHtml: escapeHTML,
createSafeHTML,
setInnerHTMLSafe(element, html, sanitize = !1) {
setHTML(element, html, {
sanitize
});
},
renderTemplateClone,
setTextContentSafe(element, text) {
setText(element, text || "");
},
sanitizeAttribute,
setAttributeSafe,
validateNumber,
RateLimiter,
fetchWithTimeout,
validateJSONSchema
};
window.YouTubePlusSecurity = window.YouTubeSecurityUtils;
}
"undefined" != typeof module && module.exports && (module.exports = window.YouTubeSafeDOM);
})();

const basicSetTimeout_ = setTimeout;

const YouTubeUtils = (() => {
"use strict";
const Security = window.YouTubePlusSecurity || {};
const Storage = window.YouTubePlusStorage || {};
const logError = (module, message, error) => {
window.console.error(`[YouTube+][${module}] ${message}:`, error);
};
const safeExecute = Security.safeExecute || ((fn, context = "Unknown") => function(...args) {
try {
return fn.call(this, ...args);
} catch (error) {
logError(context, "Execution failed", error);
return null;
}
});
const safeExecuteAsync = Security.safeExecuteAsync || ((fn, context = "Unknown") => async function(...args) {
try {
return await fn.call(this, ...args);
} catch (error) {
logError(context, "Async execution failed", error);
return null;
}
});
const sanitizeHTML = window.YouTubeSafeDOM?.sanitizeHTML || window.YouTubeUtils?.sanitizeHTML || Security.sanitizeHTML || (html => "string" != typeof html ? "" : html.replace(/[<>&"'\/`=]/g, ""));
const isValidURL = url => {
if ("function" == typeof Security.isValidURL) {
try {
return !!Security.isValidURL(url);
} catch (e) {
return !1;
}
}
if ("string" != typeof url) {
return !1;
}
try {
const parsed = new URL(url);
return [ "http:", "https:" ].includes(parsed.protocol);
} catch (e) {
return !1;
}
};
const storage = Storage || {
get: (key, defaultValue = null) => {
try {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : defaultValue;
} catch (e) {
return defaultValue;
}
},
set: (key, value) => {
try {
localStorage.setItem(key, JSON.stringify(value));
return !0;
} catch (e) {
return !1;
}
},
remove: key => {
try {
localStorage.removeItem(key);
return !0;
} catch (e) {
return !1;
}
}
};
const debounce = window._ytpDefaults.debounce;
const throttle = window._ytpDefaults.throttle;
const createElement = (tag, props = {}, children = []) => {
if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
logError("createElement", "Invalid tag name", new Error(`Tag "${tag}" is not allowed`));
return document.createElement("div");
}
const element = document.createElement(tag);
Object.entries(props).forEach(([key, value]) => {
const normalizedKey = String(key || "").toLowerCase();
if ("className" === key) {
element.className = value;
} else if ("style" === key && "object" == typeof value) {
Object.assign(element.style || {}, value);
} else if (key.startsWith("on") && "function" == typeof value) {
element.addEventListener(key.substring(2).toLowerCase(), value);
} else if (normalizedKey.startsWith("on")) {
logError("createElement", `Blocked unsafe event attribute ${key}`, new Error("Event handlers must be functions"));
} else if ("dataset" === key && "object" == typeof value) {
Object.assign(element.dataset || {}, value);
} else if ("innerHTML" === key || "outerHTML" === key) {
logError("createElement", "Direct HTML injection prevented", new Error("Use children array instead"));
} else if ("href" !== normalizedKey && "src" !== normalizedKey && "action" !== normalizedKey || "string" != typeof value) {
try {
element.setAttribute(key, value);
} catch (e) {
logError("createElement", `Failed to set attribute ${key}`, e);
}
} else {
const trimmed = value.trim();
const isProtocolRelative = trimmed.startsWith("//");
const isRelative = trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../");
const isAnchor = trimmed.startsWith("#");
if (!(isRelative || isAnchor || isProtocolRelative || isValidURL(trimmed))) {
logError("createElement", `Blocked unsafe URL for ${key}`, new Error("Only http/https and safe relative URLs are allowed"));
return;
}
try {
element.setAttribute(key, trimmed);
} catch (e) {
logError("createElement", `Failed to set URL attribute ${key}`, e);
}
}
});
children.forEach(child => {
"string" == typeof child ? element.appendChild(document.createTextNode(child)) : child instanceof Node && element.appendChild(child);
});
return element;
};
const selectorCache = new Map;
const cleanupWaitResources = (timeoutId, controller, subId, fallbackTimerId) => {
controller.abort();
if (subId && window.YouTubeMutationCoordinator?.unsubscribe) {
try {
window.YouTubeMutationCoordinator.unsubscribe(subId);
} catch (e) {
logError("waitForElement", "Coordinator unsubscribe failed", e);
}
}
null !== fallbackTimerId && clearInterval(fallbackTimerId);
clearTimeout(timeoutId);
};
const cleanupManager = {
observers: new Set,
listeners: new Map,
intervals: new Set,
timeouts: new Set,
animationFrames: new Set,
cleanupFunctions: new Set,
register: fn => {
"function" == typeof fn && cleanupManager.cleanupFunctions.add(fn);
return fn;
},
unregister: fn => {
cleanupManager.cleanupFunctions.delete(fn);
},
registerObserver: observer => {
cleanupManager.observers.add(observer);
return observer;
},
unregisterObserver: observer => {
if (observer) {
try {
observer.disconnect();
} catch (e) {
logError("Cleanup", "Observer disconnect failed", e);
}
cleanupManager.observers.delete(observer);
}
},
registerListener: (element, event, handler, options) => {
const key = Symbol("listener");
cleanupManager.listeners.set(key, {
element,
event,
handler,
options
});
try {
element.addEventListener(event, handler, options);
} catch (e) {}
return key;
},
unregisterListener: key => {
const listener = cleanupManager.listeners.get(key);
if (listener) {
const {element, event, handler, options} = listener;
try {
element.removeEventListener(event, handler, options);
} catch (e) {
logError("Cleanup", "Listener removal failed", e);
}
cleanupManager.listeners.delete(key);
}
},
registerInterval: id => {
cleanupManager.intervals.add(id);
return id;
},
unregisterInterval: id => {
clearInterval(id);
cleanupManager.intervals.delete(id);
},
registerTimeout: id => {
cleanupManager.timeouts.add(id);
return id;
},
unregisterTimeout: id => {
clearTimeout(id);
cleanupManager.timeouts.delete(id);
},
registerAnimationFrame: id => {
cleanupManager.animationFrames.add(id);
return id;
},
unregisterAnimationFrame: id => {
cancelAnimationFrame(id);
cleanupManager.animationFrames.delete(id);
},
cleanup: () => {
cleanupManager.cleanupFunctions.forEach(fn => {
try {
fn();
} catch (e) {
logError("Cleanup", "Cleanup function failed", e);
}
});
cleanupManager.cleanupFunctions.clear();
cleanupManager.observers.forEach(obs => {
try {
obs.disconnect();
} catch (e) {
logError("Cleanup", "Observer disconnect failed", e);
}
});
cleanupManager.observers.clear();
cleanupManager.listeners.forEach(({element, event, handler, options}) => {
try {
element.removeEventListener(event, handler, options);
} catch (e) {
logError("Cleanup", "Listener removal failed", e);
}
});
cleanupManager.listeners.clear();
cleanupManager.intervals.forEach(id => clearInterval(id));
cleanupManager.intervals.clear();
cleanupManager.timeouts.forEach(id => clearTimeout(id));
cleanupManager.timeouts.clear();
cleanupManager.animationFrames.forEach(id => cancelAnimationFrame(id));
cleanupManager.animationFrames.clear();
}
};
const SettingsManager = {
storageKey: "youtube_plus_all_settings_v2",
defaults: {
speedControl: {
enabled: !0,
currentSpeed: 1
},
screenshot: {
enabled: !0
},
download: {
enabled: !0
},
updateChecker: {
enabled: !0
},
adBlocker: {
enabled: !0
},
pip: {
enabled: !0
},
timecodes: {
enabled: !0
}
},
load() {
const saved = storage.get(this.storageKey);
return saved ? {
...this.defaults,
...saved
} : {
...this.defaults
};
},
save(settings) {
storage.set(this.storageKey, settings);
window.dispatchEvent(new CustomEvent("youtube-plus-settings-changed", {
detail: settings
}));
},
get(path) {
const settings = this.load();
return path.split(".").reduce((obj, key) => obj?.[key], settings);
},
set(path, value) {
const settings = this.load();
const keys = path.split(".");
const last = keys.pop();
const target = keys.reduce((obj, key) => {
obj[key] = obj[key] || {};
return obj[key];
}, settings);
target[last] = value;
this.save(settings);
}
};
const StyleManager = window.YouTubeUtils?.StyleManager || {
styles: new Map,
add() {},
remove() {},
clear() {}
};
const NotificationManager = {
queue: [],
activeNotifications: new Set,
maxVisible: 3,
defaultDuration: 3e3,
show(message, options = {}) {
if (!message || "string" != typeof message) {
logError("NotificationManager", "Invalid message", new Error("Message must be a non-empty string"));
return null;
}
const {duration = this.defaultDuration, position = null, action = null} = options;
this.activeNotifications.forEach(notif => {
notif.dataset.message === message && this.remove(notif);
});
const positions = {
"top-right": {
top: "20px",
right: "20px"
},
"top-left": {
top: "20px",
left: "20px"
},
"bottom-right": {
bottom: "20px",
right: "20px"
},
"bottom-left": {
bottom: "20px",
left: "20px"
}
};
try {
const notification = createElement("div", {
className: "youtube-enhancer-notification",
dataset: {
message
},
style: {
zIndex: "10001",
width: "auto",
display: "flex",
alignItems: "center",
gap: "10px",
...position && positions[position] ? positions[position] : {}
}
});
notification.setAttribute("role", "status");
notification.setAttribute("aria-live", "polite");
notification.setAttribute("aria-atomic", "true");
const messageSpan = createElement("span", {
style: {
flex: "1"
}
}, [ message ]);
notification.appendChild(messageSpan);
if (action && action.text && "function" == typeof action.callback) {
const actionBtn = createElement("button", {
style: {
background: "var(--yt-button-bg)",
border: "1px solid var(--yt-glass-border)",
color: "white",
padding: "4px 12px",
borderRadius: "4px",
cursor: "pointer",
fontSize: "12px",
fontWeight: "600",
transition: "background 0.2s"
},
onClick: () => {
action.callback();
this.remove(notification);
}
}, [ action.text ]);
notification.appendChild(actionBtn);
}
const _notifContainerId = "youtube-enhancer-notification-container";
let _notifContainer = document.getElementById(_notifContainerId);
if (!_notifContainer) {
_notifContainer = createElement("div", {
id: _notifContainerId,
className: "youtube-enhancer-notification-container"
});
try {
const appendRoot = document.body || document.documentElement;
if (!appendRoot) {
return null;
}
appendRoot.appendChild(_notifContainer);
} catch (e) {
const appendRoot = document.body || document.documentElement;
if (appendRoot) {
appendRoot.appendChild(notification);
this.activeNotifications.add(notification);
}
}
}
try {
_notifContainer.insertBefore(notification, _notifContainer.firstChild);
} catch (e) {
const appendRoot = document.body || document.documentElement;
appendRoot && appendRoot.appendChild(notification);
}
try {
notification.style.pointerEvents = "auto";
} catch (e) {}
this.activeNotifications.add(notification);
try {
notification.style.animation = "slideInFromBottom 0.38s ease-out forwards";
} catch (e) {}
if (duration > 0) {
const timeoutId = basicSetTimeout_(() => this.remove(notification), duration);
cleanupManager.registerTimeout(timeoutId);
}
if (this.activeNotifications.size > this.maxVisible) {
const oldest = Array.from(this.activeNotifications)[0];
this.remove(oldest);
}
return notification;
} catch (error) {
logError("NotificationManager", "Failed to show notification", error);
return null;
}
},
remove(notification) {
if (notification && notification.isConnected) {
try {
try {
notification.style.animation = "slideOutToBottom 0.32s ease-in forwards";
const timeoutId = basicSetTimeout_(() => {
try {
notification.remove();
this.activeNotifications.delete(notification);
} catch (e) {
logError("NotificationManager", "Failed to remove notification", e);
}
}, 340);
cleanupManager.registerTimeout(timeoutId);
} catch (e) {
try {
notification.remove();
this.activeNotifications.delete(notification);
} catch (e) {
logError("NotificationManager", "Failed to remove notification (fallback)", e);
}
}
} catch (error) {
logError("NotificationManager", "Failed to animate notification removal", error);
notification.remove();
this.activeNotifications.delete(notification);
}
}
},
clearAll() {
this.activeNotifications.forEach(notif => {
try {
notif.remove();
} catch (e) {
logError("NotificationManager", "Failed to clear notification", e);
}
});
this.activeNotifications.clear();
}
};
window.addEventListener("beforeunload", () => {
cleanupManager.cleanup();
selectorCache.clear();
StyleManager.clear();
NotificationManager.clearAll();
});
const cacheCleanup = () => {
const now = Date.now();
for (const [key, value] of selectorCache.entries()) {
(!value.element?.isConnected || now - value.timestamp > 1e4) && selectorCache.delete(key);
}
};
const cacheCleanupInterval = setInterval(() => {
"function" == typeof requestIdleCallback ? requestIdleCallback(cacheCleanup, {
timeout: 2e3
}) : cacheCleanup();
}, 3e4);
const globalObject = "undefined" != typeof window ? window : globalThis;
const previousCacheCleanupInterval = globalObject.__ytpBasicCacheCleanupIntervalId;
previousCacheCleanupInterval && clearInterval(previousCacheCleanupInterval);
globalObject.__ytpBasicCacheCleanupIntervalId = cacheCleanupInterval;
cleanupManager.registerInterval(cacheCleanupInterval);
cleanupManager.registerListener(window, "unhandledrejection", event => {
logError("Global", "Unhandled promise rejection", event.reason);
event.preventDefault();
});
cleanupManager.registerListener(window, "error", event => {
const message = String(event?.message || "");
const errorMessage = String(event?.error?.message || "");
message.includes("ResizeObserver loop") || errorMessage.includes("ResizeObserver loop") || event.filename && event.filename.includes("youtube") && logError("Global", "Uncaught error", new Error(`${event.message} at ${event.filename}:${event.lineno}:${event.colno}`));
});
return {
logError,
safeExecute,
safeExecuteAsync,
sanitizeHTML,
isValidURL,
storage,
debounce,
throttle,
createElement,
querySelector: (selector, nocache = !1) => {
if (nocache) {
return document.querySelector(selector);
}
const now = Date.now();
const cached = selectorCache.get(selector);
if (cached?.element?.isConnected && now - cached.timestamp < 1e4) {
return cached.element;
}
cached && selectorCache.delete(selector);
const element = document.querySelector(selector);
if (element) {
if (selectorCache.size >= 100) {
const firstKey = selectorCache.keys().next().value;
selectorCache.delete(firstKey);
}
selectorCache.set(selector, {
element,
timestamp: now
});
}
return element;
},
waitForElement: (selector, timeout = 5e3, parent = document.body) => new Promise((resolve, reject) => {
const validationError = ((selector, parent) => selector && "string" == typeof selector ? parent && parent instanceof Element ? null : new Error("Parent must be a valid DOM element") : new Error("Selector must be a non-empty string"))(selector, parent);
if (validationError) {
reject(validationError);
return;
}
const {element, error} = ((parent, selector) => {
try {
const element = parent.querySelector(selector);
return {
element,
error: null
};
} catch (e) {
return {
element: null,
error: new Error(`Invalid selector: ${selector}`)
};
}
})(parent, selector);
if (error) {
reject(error);
return;
}
if (element) {
resolve(element);
return;
}
const controller = new AbortController;
let subId = null;
let fallbackTimerId = null;
const timeoutId = basicSetTimeout_(() => {
cleanupWaitResources(timeoutId, controller, subId, fallbackTimerId);
reject(new Error(`Element ${selector} not found within ${timeout}ms`));
}, timeout);
const settleWithElement = () => {
try {
const foundElement = parent.querySelector(selector);
if (!foundElement) {
return !1;
}
cleanupWaitResources(timeoutId, controller, subId, fallbackTimerId);
resolve(foundElement);
return !0;
} catch (e) {
cleanupWaitResources(timeoutId, controller, subId, fallbackTimerId);
reject(new Error(`Invalid selector: ${selector}`));
return !0;
}
};
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.watchTarget) {
subId = `basic::waitForElement::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`;
coordinator.watchTarget(subId, parent, () => {
settleWithElement();
}, {
childList: !0,
attributes: !1,
subtree: !0
});
} else {
fallbackTimerId = setInterval(() => {
settleWithElement();
}, 120);
}
}),
cleanupManager,
SettingsManager,
StyleManager,
NotificationManager,
clearCache: () => selectorCache.clear(),
isMobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768,
getViewport: () => ({
width: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0),
height: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
}),
retryAsync: async (fn, retries = 3, delay = 1e3) => {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) {
throw error;
}
await new Promise(resolve => {
basicSetTimeout_(resolve, delay * (i + 1));
});
}
}
},
measurePerformance: (label, fn) => function(...args) {
const start = performance.now();
try {
const result = fn.apply(this, args);
const duration = performance.now() - start;
duration > 100 && window.console.warn(`[YouTube+][Performance] ${label} took ${duration.toFixed(2)}ms`);
return result;
} catch (error) {
logError("Performance", `${label} failed`, error);
throw error;
}
},
measurePerformanceAsync: (label, fn) => async function(...args) {
const start = performance.now();
try {
const result = await fn.apply(this, args);
const duration = performance.now() - start;
duration > 100 && window.console.warn(`[YouTube+][Performance] ${label} took ${duration.toFixed(2)}ms`);
return result;
} catch (error) {
logError("Performance", `${label} failed`, error);
throw error;
}
},
t: (key, params = {}) => {
const i18n = window.YouTubePlusI18n;
const translator = i18n?.translate || i18n?.t;
if ("function" == typeof translator) {
try {
return translator(key, params);
} catch (e) {}
}
return window._ytpDefaults.t(key);
}
};
})();

if ("undefined" != typeof window) {
window.YouTubeUtils = window.YouTubeUtils || {};
const existing = window.YouTubeUtils;
try {
for (const k of Object.keys(YouTubeUtils)) {
void 0 === existing[k] && (existing[k] = YouTubeUtils[k]);
}
} catch (e) {
window.console.error("[YouTube+] Failed to merge core utilities:", e);
}
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug("[YouTube+ v2.4.5] Core utilities merged");
window.YouTubePlusDebug = {
version: "2.4.5",
cacheSize: () => YouTubeUtils.cleanupManager.observers.size + YouTubeUtils.cleanupManager.listeners.size + YouTubeUtils.cleanupManager.intervals.size,
clearAll: () => {
YouTubeUtils.cleanupManager.cleanup();
YouTubeUtils.clearCache();
YouTubeUtils.StyleManager.clear();
YouTubeUtils.NotificationManager.clearAll();
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug("[YouTube+] All resources cleared");
},
stats: () => ({
observers: YouTubeUtils.cleanupManager.observers.size,
listeners: YouTubeUtils.cleanupManager.listeners.size,
intervals: YouTubeUtils.cleanupManager.intervals.size,
timeouts: YouTubeUtils.cleanupManager.timeouts.size,
animationFrames: YouTubeUtils.cleanupManager.animationFrames.size,
styles: YouTubeUtils.StyleManager.styles.size,
notifications: YouTubeUtils.NotificationManager.activeNotifications.size
})
};
if (!sessionStorage.getItem("youtube_plus_started")) {
sessionStorage.setItem("youtube_plus_started", "true");
basicSetTimeout_(() => {
YouTubeUtils.NotificationManager && YouTubeUtils.NotificationManager.show("YouTube+ v2.4.5 loaded", {
type: "success",
duration: 2e3,
position: "bottom-right"
});
}, 1e3);
}
}

!(function() {
"use strict";
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const {t} = YouTubeUtils;
const YouTubeEnhancer = {
speedControl: {
currentSpeed: 1,
activeAnimationId: null,
storageKey: "youtube_playback_speed",
availableSpeeds: [ .25, .5, .75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3 ]
},
loopControl: {
enabled: !1,
pointA: null,
pointB: null,
storageKey: "youtube_loop_state",
timeUpdateListener: null
},
_initialized: !1,
settings: {
enableSpeedControl: !0,
speedControlHotkeys: {
decrease: "g",
increase: "h",
reset: "b"
},
enableScreenshot: !0,
enableDownload: !0,
enableZenStyles: !0,
zenStyles: {
thumbnailHover: !0,
immersiveSearch: !0,
hideVoiceSearch: !0,
transparentHeader: !0,
hideSideGuide: !0,
cleanSideGuide: !1,
fixFeedLayout: !0,
sideVideosColumnsEnabled: !1,
sideVideosColumns: 0,
betterCaptions: !0,
playerBlur: !0,
theaterEnhancements: !0,
misc: !0
},
enableEnhanced: !0,
enableTabview: !0,
enableCommentTranslate: !0,
enablePlayAll: !0,
enableResumeTime: !0,
enableZoom: !0,
enableThumbnail: !0,
enablePlaylistSearch: !0,
enableScrollToTopButton: !0,
enableRememberManualQuality: !0,
enableLoop: !0,
loopHotkeys: {
toggleLoop: "r",
setPointA: "k",
setPointB: "l",
resetPoints: "o"
},
downloadSites: {
direct: !0,
externalDownloader: !0,
ytdl: !0
},
downloadSiteCustomization: {
externalDownloader: "undefined" != typeof window && window.YouTubePlusConstants ? window.YouTubePlusConstants.DOWNLOAD_SITES.EXTERNAL_DOWNLOADER : {
name: "SSYouTube",
url: "https://ssyoutube.com/watch?v={videoId}"
}
},
storageKey: window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings",
hideSideGuide: !1
},
_cache: new Map,
getElement(selector, useCache = !0) {
if (useCache && this._cache.has(selector)) {
const element = this._cache.get(selector);
if (element?.isConnected) {
return element;
}
this._cache.delete(selector);
}
const element = document.querySelector(selector);
element && useCache && this._cache.set(selector, element);
return element;
},
loadSettings() {
try {
const saved = localStorage.getItem(this.settings.storageKey);
if (saved) {
const parsed = JSON.parse(saved);
if (window.YouTubeUtils && window.YouTubeUtils.safeMerge) {
window.YouTubeUtils.safeMerge(this.settings, parsed);
} else {
for (const key in parsed) {
Object.prototype.hasOwnProperty.call(parsed, key) && ![ "__proto__", "constructor", "prototype" ].includes(key) && (this.settings[key] = parsed[key]);
}
}
return;
}
try {
if ("undefined" != typeof window && window.YouTubeUtils && YouTubeUtils.SettingsManager) {
const globalSettings = YouTubeUtils.SettingsManager.load();
if (!globalSettings) {
return;
}
const sc = globalSettings.speedControl;
sc && "boolean" == typeof sc.enabled && (this.settings.enableSpeedControl = sc.enabled);
const ss = globalSettings.screenshot;
ss && "boolean" == typeof ss.enabled && (this.settings.enableScreenshot = ss.enabled);
const dl = globalSettings.download;
dl && "boolean" == typeof dl.enabled && (this.settings.enableDownload = dl.enabled);
globalSettings.downloadSites && "object" == typeof globalSettings.downloadSites && (this.settings.downloadSites = {
...this.settings.downloadSites || {},
...globalSettings.downloadSites
});
}
} catch (e) {}
} catch (e) {
window.console.error("Error loading settings:", e);
}
},
init() {
if (!this._initialized) {
this._initialized = !0;
try {
this.loadSettings();
try {
const lh = this.settings.loopHotkeys || {};
let migrated = !1;
if ("l" === lh.setPointA) {
lh.setPointA = "k";
migrated = !0;
}
if ("o" === lh.setPointB) {
lh.setPointB = "l";
migrated = !0;
}
if ("k" === lh.resetPoints) {
lh.resetPoints = "o";
migrated = !0;
}
if (migrated) {
this.settings.loopHotkeys = lh;
try {
this.saveSettings();
} catch (e) {
window.console.warn("[YouTube+] Failed to save migrated loop hotkeys", e);
}
}
} catch (e) {}
this.settings.speedControlHotkeys = this.settings.speedControlHotkeys || {};
this.settings.speedControlHotkeys.decrease = this.normalizeSpeedHotkey(this.settings.speedControlHotkeys.decrease, "g");
this.settings.speedControlHotkeys.increase = this.normalizeSpeedHotkey(this.settings.speedControlHotkeys.increase, "h");
this.settings.speedControlHotkeys.reset = this.normalizeSpeedHotkey(this.settings.speedControlHotkeys.reset, "b");
try {
const savedSpeed = localStorage.getItem(this.speedControl.storageKey);
if (null !== savedSpeed) {
const parsed = Number(savedSpeed);
Number.isFinite(parsed) && parsed > 0 && parsed <= 16 && (this.speedControl.currentSpeed = parsed);
}
} catch (e) {
window.console.warn("[YouTube+] Speed restore error:", e);
}
this.settings.loopHotkeys = this.settings.loopHotkeys || {};
this.settings.loopHotkeys.toggleLoop = this.normalizeSpeedHotkey(this.settings.loopHotkeys.toggleLoop, "r");
this.settings.loopHotkeys.setPointA = this.normalizeSpeedHotkey(this.settings.loopHotkeys.setPointA, "k");
this.settings.loopHotkeys.setPointB = this.normalizeSpeedHotkey(this.settings.loopHotkeys.setPointB, "l");
this.settings.loopHotkeys.resetPoints = this.normalizeSpeedHotkey(this.settings.loopHotkeys.resetPoints, "o");
this.loadLoopState();
} catch (error) {
window.console.warn("[YouTube+][Basic]", "Failed to load settings during init:", error);
}
this.insertStyles();
this.addSettingsButtonToHeader();
this.setupNavigationObserver();
location.href.includes("watch?v=") && this.setupCurrentPage();
YouTubeUtils.cleanupManager.registerListener(document, "visibilitychange", () => {
!document.hidden && location.href.includes("watch?v=") && this.setupCurrentPage();
});
try {
const screenshotKeyHandler = e => {
if (e && e.key && ("s" === e.key || "S" === e.key) && !(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) && !this.isEditableTarget(document.activeElement) && this.settings.enableScreenshot) {
try {
this.captureFrame();
} catch (err) {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Basic", "Keyboard screenshot failed", err);
}
}
};
YouTubeUtils.cleanupManager.registerListener(document, "keydown", screenshotKeyHandler, !0);
} catch (e) {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Basic", "Failed to register screenshot keyboard shortcut", e);
}
try {
const speedHotkeyHandler = e => {
if (!this.settings.enableSpeedControl || !e || !e.key) {
return;
}
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return;
}
if (this.isEditableTarget(document.activeElement)) {
return;
}
const key = String(e.key).toLowerCase();
const decreaseKey = this.normalizeSpeedHotkey(this.settings.speedControlHotkeys?.decrease, "g");
const increaseKey = this.normalizeSpeedHotkey(this.settings.speedControlHotkeys?.increase, "h");
const resetKey = this.normalizeSpeedHotkey(this.settings.speedControlHotkeys?.reset, "b");
if (key === decreaseKey) {
e.preventDefault();
this.adjustSpeedByStep(-1);
} else if (key === increaseKey) {
e.preventDefault();
this.adjustSpeedByStep(1);
} else if (key === resetKey) {
e.preventDefault();
this.changeSpeed(1);
}
};
YouTubeUtils.cleanupManager.registerListener(document, "keydown", speedHotkeyHandler, !0);
} catch (e) {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Basic", "Failed to register speed keyboard shortcuts", e);
}
try {
const loopHotkeyHandler = e => {
if (!this.settings.enableLoop || !e || !e.key) {
return;
}
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return;
}
if (this.isEditableTarget(document.activeElement)) {
return;
}
const key = String(e.key).toLowerCase();
const toggleLoopKey = this.normalizeSpeedHotkey(this.settings.loopHotkeys?.toggleLoop, "r");
const setPointAKey = this.normalizeSpeedHotkey(this.settings.loopHotkeys?.setPointA, "k");
const setPointBKey = this.normalizeSpeedHotkey(this.settings.loopHotkeys?.setPointB, "l");
const resetPointsKey = this.normalizeSpeedHotkey(this.settings.loopHotkeys?.resetPoints, "o");
if (key === toggleLoopKey) {
e.preventDefault();
this.toggleLoop();
} else if (key === setPointAKey) {
e.preventDefault();
this.setLoopPoint("A");
} else if (key === setPointBKey) {
e.preventDefault();
this.setLoopPoint("B");
} else if (key === resetPointsKey) {
e.preventDefault();
this.resetLoopPoints();
}
};
YouTubeUtils.cleanupManager.registerListener(document, "keydown", loopHotkeyHandler, !0);
} catch (e) {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Basic", "Failed to register loop keyboard shortcuts", e);
}
}
},
isEditableTarget(target) {
const active = target;
if (!active) {
return !1;
}
const tag = (active.tagName || "").toLowerCase();
return "input" === tag || "textarea" === tag || "select" === tag || Boolean(active.isContentEditable);
},
normalizeSpeedHotkey(value, fallback) {
const candidate = "string" == typeof value ? value.trim().toLowerCase() : "";
return candidate ? candidate.slice(0, 1) : String(fallback || "").trim().toLowerCase().slice(0, 1) || "g";
},
adjustSpeedByStep(direction) {
const speeds = this.speedControl.availableSpeeds;
if (!Array.isArray(speeds) || !speeds.length) {
return;
}
const current = Number(this.speedControl.currentSpeed);
let closestIndex = 0;
let closestDelta = Number.POSITIVE_INFINITY;
for (let i = 0; i < speeds.length; i += 1) {
const delta = Math.abs(speeds[i] - current);
if (delta < closestDelta) {
closestDelta = delta;
closestIndex = i;
}
}
const step = direction > 0 ? 1 : -1;
const nextIndex = Math.max(0, Math.min(speeds.length - 1, closestIndex + step));
nextIndex !== closestIndex && this.changeSpeed(speeds[nextIndex]);
},
toggleLoop() {
if (!this.settings.enableLoop) {
return;
}
this.loopControl.enabled = !this.loopControl.enabled;
const video = document.querySelector("video");
if (video) {
if (this.loopControl.enabled) {
if (null === this.loopControl.pointA && null === this.loopControl.pointB) {
video.loop = !0;
} else {
video.loop = !1;
this.setupLoopListener(video);
}
YouTubeUtils.NotificationManager.show(t("loopEnabled") || "Loop enabled", {
duration: 1500,
type: "success"
});
} else {
video.loop = !1;
this.removeLoopListener();
YouTubeUtils.NotificationManager.show(t("loopDisabled") || "Loop disabled", {
duration: 1500,
type: "info"
});
}
this.updateLoopProgressBar();
this.saveLoopState();
} else {
this.saveLoopState();
}
},
setLoopPoint(point) {
if (!this.settings.enableLoop) {
return;
}
const video = document.querySelector("video");
if (!video) {
return;
}
const currentTime = video.currentTime;
if ("A" === point) {
this.loopControl.pointA = currentTime;
YouTubeUtils.NotificationManager.show(`${t("loopPointASet") || "Point A set"}: ${this.formatTime(currentTime)}`, {
duration: 1500,
type: "success"
});
} else if ("B" === point) {
this.loopControl.pointB = currentTime;
YouTubeUtils.NotificationManager.show(`${t("loopPointBSet") || "Point B set"}: ${this.formatTime(currentTime)}`, {
duration: 1500,
type: "success"
});
}
if (this.loopControl.enabled && null !== this.loopControl.pointA && null !== this.loopControl.pointB) {
const video = document.querySelector("video");
if (video) {
video.loop = !1;
this.setupLoopListener(video);
}
}
this.updateLoopProgressBar();
this.saveLoopState();
},
resetLoopPoints() {
if (this.settings.enableLoop) {
this.loopControl.pointA = null;
this.loopControl.pointB = null;
if (this.loopControl.enabled) {
const video = document.querySelector("video");
if (video) {
video.loop = !0;
this.removeLoopListener();
}
}
YouTubeUtils.NotificationManager.show(t("loopPointsReset") || "Loop points reset", {
duration: 1500,
type: "info"
});
this.updateLoopProgressBar();
this.saveLoopState();
}
},
setupLoopListener(video) {
this.removeLoopListener();
if (null === this.loopControl.pointA || null === this.loopControl.pointB) {
return;
}
const startTime = Math.min(this.loopControl.pointA, this.loopControl.pointB);
const endTime = Math.max(this.loopControl.pointA, this.loopControl.pointB);
this.loopControl.timeUpdateListener = () => {
this.loopControl.enabled && video.currentTime >= endTime && (video.currentTime = startTime);
};
video.addEventListener("timeupdate", this.loopControl.timeUpdateListener);
},
removeLoopListener() {
if (this.loopControl.timeUpdateListener) {
const video = document.querySelector("video");
video && video.removeEventListener("timeupdate", this.loopControl.timeUpdateListener);
this.loopControl.timeUpdateListener = null;
}
},
updateLoopProgressBar() {
if (null === this.loopControl.pointA && null === this.loopControl.pointB) {
const existingIndicator = document.querySelector(".ytp-plus-loop-indicator");
existingIndicator && existingIndicator.remove();
return;
}
const video = document.querySelector("video");
if (!video || !video.duration) {
return;
}
let progressBar = document.querySelector(".ytp-progress-bar-container") || document.querySelector(".ytp-scrubber-container") || document.querySelector('[role="slider"][aria-label*="video"]') || document.querySelector(".html5-progress-bar");
if (!progressBar) {
const playbackUI = document.querySelector(".html5-video-player");
playbackUI && (progressBar = playbackUI.querySelector('[role="slider"]'));
}
if (!progressBar) {
return;
}
let indicator = document.querySelector(".ytp-plus-loop-indicator");
if (!indicator) {
indicator = document.createElement("div");
indicator.className = "ytp-plus-loop-indicator";
try {
const compStyle = window.getComputedStyle(progressBar);
compStyle && "static" !== compStyle.position || (progressBar.style.position = "relative");
} catch (e) {}
progressBar.appendChild(indicator);
indicator.style.position = "absolute";
indicator.style.top = "0";
indicator.style.height = "100%";
indicator.style.pointerEvents = "none";
indicator.style.zIndex = "1000";
}
if (null !== this.loopControl.pointA && null === this.loopControl.pointB) {
const startPercent = this.loopControl.pointA / video.duration * 100;
indicator.style.left = `${startPercent}%`;
indicator.style.width = "2px";
indicator.style.background = "linear-gradient(90deg,var(--yt-accent-secondary),var(--yt-accent-secondary-light))";
indicator.style.borderLeft = "2px solid var(--yt-accent-secondary)";
indicator.style.borderRight = "2px solid var(--yt-accent-secondary)";
indicator.style.display = "block";
return;
}
if (null !== this.loopControl.pointB && null === this.loopControl.pointA) {
const bPercent = this.loopControl.pointB / video.duration * 100;
indicator.style.left = `${bPercent}%`;
indicator.style.width = "2px";
indicator.style.background = "linear-gradient(90deg,var(--yt-accent-secondary),var(--yt-accent-secondary-light))";
indicator.style.borderLeft = "2px solid var(--yt-accent-secondary)";
indicator.style.borderRight = "2px solid var(--yt-accent-secondary)";
indicator.style.display = "block";
return;
}
const startTime = Math.min(this.loopControl.pointA, this.loopControl.pointB);
const endTime = Math.max(this.loopControl.pointA, this.loopControl.pointB);
const startPercent = startTime / video.duration * 100;
const endPercent = endTime / video.duration * 100;
indicator.style.left = `${startPercent}%`;
indicator.style.width = `${Math.max(.2, endPercent - startPercent)}%`;
indicator.style.background = "linear-gradient(90deg,var(--yt-accent-secondary-ghost) 0%,var(--yt-accent-secondary-light-ghost) 50%,var(--yt-accent-secondary-ghost) 100%)";
indicator.style.borderLeft = "2px solid var(--yt-accent-secondary)";
indicator.style.borderRight = "2px solid var(--yt-accent-secondary)";
indicator.style.display = "block";
},
applyLoopStateToCurrentVideo() {
const video = document.querySelector("video");
if (video) {
this.removeLoopListener();
if (this.settings.enableLoop && this.loopControl.enabled) {
if (null !== this.loopControl.pointA && null !== this.loopControl.pointB) {
video.loop = !1;
this.setupLoopListener(video);
} else {
video.loop = !0;
}
this.updateLoopProgressBar();
} else {
video.loop = !1;
this.updateLoopProgressBar();
}
}
},
saveLoopState() {
try {
const state = {
enabled: this.loopControl.enabled,
pointA: this.loopControl.pointA,
pointB: this.loopControl.pointB
};
localStorage.setItem(this.loopControl.storageKey, JSON.stringify(state));
} catch (e) {
window.console.warn("[YouTube+] Failed to save loop state:", e);
}
},
loadLoopState() {
try {
const saved = localStorage.getItem(this.loopControl.storageKey);
if (saved) {
const state = JSON.parse(saved);
this.loopControl.enabled = Boolean(state?.enabled);
this.loopControl.pointA = "number" == typeof state?.pointA && Number.isFinite(state.pointA) ? state.pointA : null;
this.loopControl.pointB = "number" == typeof state?.pointB && Number.isFinite(state.pointB) ? state.pointB : null;
basicSetTimeout_(() => this.applyLoopStateToCurrentVideo(), 1e3);
}
} catch (e) {
window.console.warn("[YouTube+] Failed to load loop state:", e);
}
},
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, "0")}`;
},
saveSettings() {
localStorage.setItem(this.settings.storageKey, JSON.stringify(this.settings));
this.updatePageBasedOnSettings();
this.refreshDownloadButton();
try {
window.youtubePlus = window.youtubePlus || {};
window.youtubePlus.settings = this.settings;
window.dispatchEvent(new CustomEvent("youtube-plus-settings-updated", {
detail: this.settings
}));
} catch (e) {
window.console.warn("[YouTube+] Settings broadcast error:", e);
}
},
updatePageBasedOnSettings() {
Object.entries({
"ytp-screenshot-button": "enableScreenshot",
"ytp-download-button": "enableDownload",
"speed-control-btn": "enableSpeedControl"
}).forEach(([className, setting]) => {
const button = this.getElement(`.${className}`, !1);
button && (button.style.display = this.settings[setting] ? "" : "none");
});
const speedOptions = document.querySelector(".speed-options");
speedOptions && (speedOptions.style.display = this.settings.enableSpeedControl ? "" : "none");
},
refreshDownloadButton() {
if ("undefined" != typeof window && window.YouTubePlusDownloadButton) {
const manager = window.YouTubePlusDownloadButton.createDownloadButtonManager({
settings: this.settings,
t,
getElement: this.getElement.bind(this),
YouTubeUtils
});
manager.refreshDownloadButton();
}
},
setupCurrentPage() {
this.waitForElement("#player-container-outer .html5-video-player, .ytp-right-controls", 5e3).then(() => {
this.addCustomButtons();
this.setupVideoObserver();
this.applyCurrentSpeed();
this.applyLoopStateToCurrentVideo();
this.updatePageBasedOnSettings();
this.refreshDownloadButton();
}).catch(() => {});
},
insertStyles() {
const injectNonCritical = () => {
if (!document.getElementById("yt-enhancer-nc-styles")) {
const ncEl = document.createElement("style");
ncEl.id = "yt-enhancer-nc-styles";
ncEl.textContent = '\n        .ytp-plus-settings-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.45);display:flex;align-items:center;justify-content:center;z-index:100000;backdrop-filter:blur(8px) saturate(140%);-webkit-backdrop-filter:blur(8px) saturate(140%);animation:ytEnhanceFadeIn .25s ease-out;contain:layout style paint;}\n        .ytp-plus-settings-shell{max-width:45vw;max-height:65vh;display:flex;flex-direction:row;gap:12px;animation:ytEnhanceScaleIn .28s cubic-bezier(.4,0,.2,1);will-change:transform,opacity;}\n        .ytp-plus-settings-sidebar{display:flex;align-items:center;justify-content:center;padding-top:44px;box-sizing:border-box;}\n        .ytp-plus-settings-column{flex:1;min-width:0;display:flex;flex-direction:column;gap:12px;}\n        .ytp-plus-settings-topbar{display:flex;align-items:center;gap:12px;padding:0 2px;}\n        .ytp-plus-settings-title{font-size:14px;font-weight:500;margin:0;padding:var(--yt-space-sm) var(--yt-space-md);border-radius:18px;border:1px solid var(--yt-glass-border);color:var(--yt-text-primary);cursor:default;transition:all .25s cubic-bezier(.4,0,.2,1);white-space:nowrap;}\n        .ytp-plus-settings-active-label{flex:1;font-size:13px;font-weight:600;color:var(--yt-text-secondary);text-align:center;white-space:nowrap;letter-spacing:.03em;text-transform:uppercase;opacity:.75;}\n        .ytp-plus-settings-panel{background:var(--yt-glass-bg);color:var(--yt-text-primary);border-radius:24px;flex:1;min-width:0;min-height:0;overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,0.45);backdrop-filter:blur(14px) saturate(140%);-webkit-backdrop-filter:blur(14px) saturate(140%);border:1.5px solid var(--yt-glass-border);contain:layout style paint;display:flex;}\n        .ytp-plus-settings-side-actions{display:flex;flex-direction:column;gap:10px;padding-top:50px;align-self:flex-start;}\n        .ytp-plus-settings-close{width:40px;height:40px;border-radius:50%;background:var(--yt-surface-soft);border:1px solid var(--yt-surface-active);display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 14px var(--yt-shadow-soft);transition:transform .12s ease,background .12s ease,color .2s;color:var(--yt-text-primary);padding:0;}\n        .ytp-plus-settings-close:hover{transform:translateY(-2px);background:var(--yt-danger-ghost);color:var(--yt-accent);}\n        .ytp-plus-settings-nav{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;width:100%;}\n        .ytp-plus-settings-nav-rail{border:1px solid var(--yt-glass-border);border-radius:24px;background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.03));box-shadow:inset 0 1px 0 rgba(255,255,255,.08);padding:10px 8px;}\n        .ytp-plus-settings-nav-item{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;height:44px;border-radius:14px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);font-size:11px;border:none;color:var(--yt-text-primary);padding:4px 4px;text-align:center;}\n        .ytp-plus-settings-nav-item-label{display:none;}\n        .ytp-plus-settings-nav-item:hover{background:var(--yt-hover-bg);transform:translateY(-1px);}\n        .ytp-plus-settings-nav-item.active{background:var(--yt-surface-active);color:var(--yt-accent);box-shadow:inset 0 0 0 1px var(--yt-surface-active-strong);}\n        .ytp-plus-settings-nav-item svg{width:20px;height:20px;margin-right:0;opacity:.92;transition:opacity .2s,transform .2s;flex-shrink:0;}\n        .ytp-plus-settings-nav-item.active svg{opacity:1;transform:scale(1.1);}\n        .ytp-plus-settings-nav-item:hover svg{transform:scale(1.06);}\n        .ytp-plus-settings-main{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden;}\n        .ytp-plus-settings-content{flex:1;padding:var(--yt-space-md) var(--yt-space-lg);overflow-y:auto;min-height:0;}\n        .ytp-plus-settings-section{margin-bottom:var(--yt-space-lg);}\n        .ytp-plus-settings-section-title{font-size:16px;font-weight:500;margin-bottom:var(--yt-space-md);color:var(--yt-text-primary);}\n        .ytp-plus-settings-section.hidden{display:none !important;}\n        .ytp-plus-settings-item{display:flex;align-items:center;margin-bottom:var(--yt-space-md);padding:14px 18px;background:transparent;transition:all .25s cubic-bezier(.4,0,.2,1);border-radius:var(--yt-radius-md);}\n        .ytp-plus-settings-item:hover{background:var(--yt-hover-bg);transform:translateX(6px);box-shadow:0 2px 8px rgba(0,0,0,.1);}\n        .ytp-plus-settings-item-actions{display:flex;align-items:center;gap:10px;margin-left:auto;}\n        .ytp-plus-submenu-toggle{width:26px;height:26px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:1px solid var(--yt-glass-border);color:var(--yt-text-primary);cursor:pointer;opacity:.9;transition:transform .15s ease,background-color .15s ease,opacity .15s ease;}\n        .ytp-plus-submenu-toggle:hover{background:var(--yt-hover-bg);transform:scale(1.06);}\n        .ytp-plus-submenu-toggle:disabled{opacity:.35;cursor:not-allowed;transform:none;}\n        .ytp-plus-submenu-toggle svg{width:16px;height:16px;transition:transform .15s ease;}\n        .ytp-plus-submenu-toggle[aria-expanded="false"] svg{transform:rotate(-90deg);}\n        .ytp-plus-submenu-toggle[aria-expanded="true"] svg{transform:rotate(0deg);}\n        .ytp-plus-settings-item-label{flex:1;font-size:14px;color:var(--yt-text-primary);}\n        .ytp-plus-settings-item-description{font-size:12px;color:var(--yt-text-secondary);margin-top:4px;}\n        .ytp-plus-settings-checkbox{appearance:none;-webkit-appearance:none;-moz-appearance:none;width:20px;height:20px;min-width:20px;min-height:20px;margin-left:auto;border:2px solid var(--yt-glass-border);border-radius:50%;background:transparent;display:inline-flex;align-items:center;justify-content:center;transition:all 250ms cubic-bezier(.4,0,.23,1);cursor:pointer;position:relative;flex-shrink:0;color:#fff;box-sizing:border-box;}\n        html:not([dark]) .ytp-plus-settings-checkbox{border-color:var(--yt-border-light);color:#222;}\n        .ytp-plus-settings-checkbox:focus-visible{outline:2px solid var(--yt-accent);outline-offset:2px;}\n        .ytp-plus-settings-checkbox:hover{background:var(--yt-hover-bg);transform:scale(1.1);}\n        .ytp-plus-settings-checkbox::before{content:"";width:5px;height:2px;background:var(--yt-text-primary);position:absolute;transform:rotate(45deg);top:6px;left:3px;transition:width 100ms ease 50ms,opacity 50ms;transform-origin:0% 0%;opacity:0;}\n        .ytp-plus-settings-checkbox::after{content:"";width:0;height:2px;background:var(--yt-text-primary);position:absolute;transform:rotate(305deg);top:12px;left:7px;transition:width 100ms ease,opacity 50ms;transform-origin:0% 0%;opacity:0;}\n        .ytp-plus-settings-checkbox:checked{transform:rotate(0deg) scale(1.15);}\n        .ytp-plus-settings-checkbox:checked::before{width:9px;opacity:1;background:#fff;transition:width 150ms ease 100ms,opacity 150ms ease 100ms;}\n        .ytp-plus-settings-checkbox:checked::after{width:16px;opacity:1;background:#fff;transition:width 150ms ease 250ms,opacity 150ms ease 250ms;}\n        .ytp-plus-settings-select{margin-left:auto;flex-shrink:0;background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);border-radius:8px;color:var(--yt-text-primary);font-size:13px;padding:4px 8px;cursor:pointer;outline:none;transition:border-color .2s;}\n        .ytp-plus-settings-select:focus{border-color:var(--yt-accent,#f00);}\n        html:not([dark]) .ytp-plus-settings-select{background:var(--yt-input-bg);border-color:var(--yt-border-color);}\n        .ytp-plus-button{padding:var(--yt-space-sm) var(--yt-space-md);border-radius:18px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);}\n        .ytp-plus-button-primary{background:transparent;border:1px solid var(--yt-glass-border);color:var(--yt-text-primary);}\n        .ytp-plus-button-primary:hover{background:var(--yt-accent);color:#fff;box-shadow:0 6px 16px var(--yt-danger-shadow-strong);transform:translateY(-2px);}\n        .app-icon{fill:var(--yt-text-primary);stroke:var(--yt-text-primary);transition:all .3s;}\n        .about-section-content{display:flex;flex-direction:row;align-items:center;justify-content:center;flex-wrap:nowrap;width:fit-content;max-width:100%;line-height:1;gap:12px;text-align:center;margin:6px auto 12px;}\n        .about-section-content .app-icon{display:block;flex:0 0 auto;margin:0;}\n        @media(max-width:768px){.ytp-plus-settings-shell{max-height:86vh;flex-direction:column;gap:8px;}\n        .ytp-plus-settings-sidebar{width:100%;max-height:70px;overflow-x:auto;padding-top:0;}\n        .ytp-plus-settings-nav{flex-direction:row;}\n        .ytp-plus-settings-nav-rail{max-width:none;border:none;border-radius:0;background:transparent;box-shadow:none;padding:0;flex-direction:row;display:flex;gap:4px;}\n        .ytp-plus-settings-nav-item{width:40px;height:40px;}\n        .ytp-plus-settings-side-actions{flex-direction:row;padding-top:0;align-self:auto;}\n        .ytp-plus-settings-panel{min-height:0;}\n        .ytp-plus-settings-active-label{display:none;}\n        .ytp-plus-settings-item{padding:10px 12px;}}\n        .about-section-content h1{margin:0;white-space:nowrap;font-family:\'Montserrat\',sans-serif;font-size:52px;font-weight:600;line-height:1.05;color:transparent;-webkit-text-stroke-width:1px;-webkit-text-stroke-color:var(--yt-text-stroke);cursor:pointer;transition:color .2s;}\n        .about-section-content h1:hover{color:var(--yt-accent);-webkit-text-stroke-width:1px;-webkit-text-stroke-color:transparent;}\n        .download-options{position:fixed;background:var(--yt-glass-bg);color:var(--yt-text-primary);border-radius:var(--yt-radius-md);width:150px;z-index:2147483647;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);overflow:hidden;opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease;transform:translateY(8px);box-sizing:border-box;}\n        .download-options.visible{opacity:1;pointer-events:auto;transform:translateY(0);backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);}\n        .download-options-list{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;}\n        .download-option-item{cursor:pointer;padding:12px;text-align:center;transition:background .2s,color .2s;width:100%;}\n        .download-option-item:hover{background:var(--yt-hover-bg);color:var(--yt-accent);}\n        .glass-panel{background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);box-shadow:var(--yt-glass-shadow);}\n        .glass-card{background:var(--yt-panel-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);padding:var(--yt-space-md);box-shadow:var(--yt-shadow);}\n        .glass-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--yt-modal-bg);display:flex;align-items:center;justify-content:center;z-index:99999;}\n        .glass-button{background:var(--yt-button-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);padding:var(--yt-space-sm) var(--yt-space-md);color:var(--yt-text-primary);cursor:pointer;transition:all .2s ease;}\n        .glass-button:hover{background:var(--yt-hover-bg);transform:translateY(-1px);box-shadow:var(--yt-shadow);}\n        .download-submenu{margin:4px 0 12px 12px;}\n        .download-submenu-container{display:flex;flex-direction:column;gap:8px;}\n        .style-submenu{margin:4px 0 12px 12px;}\n        .style-submenu-container{display:flex;flex-direction:column;gap:8px;}\n        .speed-submenu{margin:4px 0 12px 12px;}\n        .speed-submenu-container{display:flex;flex-direction:column;gap:8px;}\n        .speed-hotkeys-row{flex-direction:column!important;align-items:stretch!important;gap:6px;}\n        .speed-hotkeys-info{display:flex;flex-direction:column;gap:4px;}\n        .speed-hotkeys-fields{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-top:12px;width:100%;}\n        .speed-hotkey-field{display:flex;flex-direction:column;align-items:center;gap:8px;font-size:12px;color:var(--yt-text-secondary);flex:1;min-width:80px;}\n        .speed-hotkey-field span{text-align:center;width:100%;}\n        .speed-hotkey-input{width:100%;height:36px;border-radius:8px;border:1px solid var(--yt-glass-border);background:var(--yt-glass-bg);color:var(--yt-text-primary);text-align:center;text-transform:uppercase;}\n        .speed-hotkey-input:focus{background:var(--yt-hover-bg);}\n        .loop-submenu-container{display:flex;flex-direction:column;gap:8px;}\n        .loop-hotkeys-row{flex-direction:column!important;align-items:stretch!important;gap:6px;}\n        .loop-hotkeys-info{display:flex;flex-direction:column;gap:4px;}\n        .loop-hotkeys-fields{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-top:12px;width:100%;}\n        .loop-hotkey-field{display:flex;flex-direction:column;align-items:center;gap:8px;font-size:12px;color:var(--yt-text-secondary);flex:1;min-width:80px;}\n        .loop-hotkey-field span{text-align:center;width:100%;}\n        .loop-hotkey-input{width:100%;height:36px;border-radius:8px;border:1px solid var(--yt-glass-border);background:var(--yt-glass-bg);color:var(--yt-text-primary);text-align:center;text-transform:uppercase;}\n        .loop-hotkey-input:focus{background:var(--yt-hover-bg);}\n        .download-site-option{display:flex;flex-direction:column;align-items:stretch;gap:8px;padding:10px;border-radius:var(--yt-radius-md);transition:background .2s;}\n        .download-site-option:hover{background:var(--yt-hover-bg);}\n        .download-site-header{display:flex;flex-direction:row;align-items:center;justify-content:space-between;width:100%;gap:12px;}\n        .download-site-label{flex:1;cursor:pointer;display:flex;flex-direction:column;}\n        .download-site-controls{width:100%;margin-top:4px;padding-top:10px;border-top:1px solid var(--yt-glass-border);}\n        .download-site-input{width:95%;margin-top:8px;padding:8px;background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-sm);color:var(--yt-text-primary);font-size:13px;transition:all .2s;}\n        .download-site-input:focus{border-color:var(--yt-accent);background:var(--yt-hover-bg);}\n        .download-site-input.small{margin-top:6px;font-size:12px;}\n        .download-site-cta{display:flex;flex-direction:row;gap:8px;margin-top:10px;}\n        .download-site-cta .glass-button{flex:1;justify-content:center;font-size:13px;padding:8px 12px;}\n        .download-site-cta .glass-button.danger{background:var(--yt-danger-soft);border-color:var(--yt-danger-border);}\n        .download-site-cta .glass-button.danger:hover{background:var(--yt-danger-soft-hover);}\n        .download-site-option .ytp-plus-settings-checkbox{margin:0;}\n        .download-site-name{font-weight:500;font-size:15px;color:var(--yt-text-primary);}\n        .download-site-desc{font-size:12px;color:var(--yt-text-secondary);margin-top:2px;opacity:0.8;}\n        .ytp-plus-settings-panel select,\n        .ytp-plus-settings-panel select option {background: var(--yt-panel-bg) !important; color: var(--yt-text-primary) !important;}\n        .ytp-plus-settings-panel select {-webkit-appearance: menulist !important; appearance: menulist !important; padding: 6px 8px !important; border-radius: 6px !important; border: 1px solid var(--yt-glass-border) !important;}\n        .ytp-plus-theme-item{display:flex;flex-direction:column;align-items:stretch;gap:12px}\n        .ytp-plus-theme-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}\n        .ytp-plus-theme-card{display:flex;align-items:center;justify-content:center;min-height:44px;border-radius:12px;border:1px solid var(--yt-glass-border);background:var(--yt-panel-bg);color:var(--yt-text-secondary);font-size:13px;font-weight:500;cursor:pointer;transition:all .18s ease}\n        .ytp-plus-theme-card:hover{background:var(--yt-hover-bg);color:var(--yt-text-primary)}\n        .ytp-plus-theme-card.active{color:#fff;background:linear-gradient(180deg,var(--yt-danger-card-bg-start),var(--yt-danger-card-bg-end));border-color:var(--yt-danger-card-border);box-shadow:0 0 0 1px var(--yt-danger-card-inset) inset}\n        @media(max-width:580px){.ytp-plus-theme-grid{grid-template-columns:1fr}}\n        .glass-dropdown{position:relative;display:inline-block;min-width:110px}\n        .glass-dropdown__toggle{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:6px 8px;border-radius:8px;background:linear-gradient(180deg, var(--yt-surface-overlay-subtle), var(--yt-surface-overlay-faint));color:inherit;border:1px solid var(--yt-surface-overlay-border);cursor:pointer}\n        .glass-dropdown__toggle:focus{outline:2px solid var(--yt-surface-overlay-border)}\n        .glass-dropdown__label{font-size:12px}\n        .glass-dropdown__chev{opacity:0.9}\n        .glass-dropdown__list{position:absolute;left:0;right:0;top:calc(100% + 8px);z-index:20000;display:none;margin:0;padding:6px;border-radius:10px;list-style:none;background:var(--yt-header-bg);border:1px solid var(--yt-surface-overlay-border);box-shadow:0 8px 30px var(--yt-shadow-flyout);backdrop-filter:blur(10px) saturate(130%);-webkit-backdrop-filter:blur(10px) saturate(130%);max-height:220px;overflow:auto}\n        .glass-dropdown__item{padding:8px 10px;border-radius:6px;margin:4px 0;cursor:pointer;color:inherit;font-size:13px}\n        .glass-dropdown__item:hover{background:var(--yt-surface-overlay-subtle)}\n        .glass-dropdown__item[aria-selected="true"]{background:linear-gradient(90deg, var(--yt-surface-overlay-subtle), var(--yt-surface-overlay-faint));box-shadow:inset 0 0 0 1px var(--yt-surface-overlay-faint)}\n        .ytp-plus-settings-voting-header{margin-bottom:var(--yt-space-lg);}\n        .ytp-plus-settings-voting-header h3{font-size:18px;font-weight:500;margin:0 0 8px 0;color:var(--yt-text-primary);}\n        .ytp-plus-settings-voting-desc{font-size:13px;color:var(--yt-text-secondary);margin:0;}\n        .ytp-plus-voting{display:flex;flex-direction:column;gap:12px;}\n        .ytp-plus-voting-header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}\n        .ytp-plus-voting-list{display:flex;flex-direction:column;gap:12px;}\n        .ytp-plus-voting-item{display:flex;align-items:flex-start;justify-content:space-between;padding:16px;background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);transition:all .2s ease;gap:12px;}\n        .ytp-plus-voting-item:hover{background:var(--yt-hover-bg);transform:translateX(4px);}\n        .ytp-plus-voting-item-content{flex:1;padding-right:16px;}\n        .ytp-plus-voting-item-title{font-size:14px;font-weight:500;color:var(--yt-text-primary);margin-bottom:4px;}\n        .ytp-plus-voting-item-desc{font-size:12px;color:var(--yt-text-secondary);line-height:1.4;}\n        .ytp-plus-voting-item-status{font-size:11px;min-height:28px;padding:0 10px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:var(--yt-surface-overlay-soft);color:var(--yt-text-secondary);border:1px solid var(--yt-glass-border);line-height:1;}\n        .ytp-plus-voting-item-status.completed{background:var(--yt-success-soft);color:var(--yt-success);}\n        .ytp-plus-voting-item-status.in-progress{background:var(--yt-warning-soft);color:var(--yt-warning);}\n        .ytp-plus-voting-item-votes{display:flex;flex-direction:column;align-items:stretch;gap:8px;min-width:120px;}\n        .ytp-plus-voting-score{display:flex;align-items:baseline;gap:8px;justify-content:center;}\n        .ytp-plus-vote-total{font-size:12px;color:var(--yt-text-secondary);}\n        .ytp-plus-voting-buttons{position:relative;display:flex;justify-content:center;gap:0;border:1px solid var(--yt-glass-border);border-radius:20px;overflow:hidden;}\n        .ytp-plus-voting-buttons-track{position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;transition:background .4s ease;border-radius:20px;pointer-events:none;}\n        .ytp-plus-vote-btn{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:42px;height:32px;border:none;background:transparent;cursor:pointer;transition:color .15s ease,opacity .15s ease;color:var(--yt-text-secondary);opacity:.95}\n        .ytp-plus-vote-btn:first-of-type{border-right:1px solid var(--yt-glass-border)}\n        .ytp-plus-vote-btn:hover{color:var(--yt-text-primary);opacity:1}\n        .ytp-plus-vote-btn.active{color:#fff;opacity:1}\n        .ytp-plus-vote-icon{width:20px;height:20px;fill:currentColor;opacity:.92}\n        .ytp-plus-vote-btn.active .ytp-plus-vote-icon,.ytp-plus-vote-btn:hover .ytp-plus-vote-icon{opacity:1}\n        .ytp-plus-voting-loading,.ytp-plus-voting-empty{text-align:center;padding:24px;color:var(--yt-text-secondary);font-size:13px;}\n        .ytp-plus-voting-add-btn{background:var(--yt-accent);color:#fff;border:none;padding:8px 16px;border-radius:18px;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;}\n        .ytp-plus-voting-add-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px var(--yt-danger-shadow);}\n        .ytp-plus-voting-add-form{margin-top:16px;padding:16px;background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);border-radius:var(--yt-radius-md);}\n        .ytp-plus-voting-add-form input,.ytp-plus-voting-add-form textarea{width:100%;padding:10px 12px;margin-bottom:12px;background:var(--yt-header-bg);border:1px solid var(--yt-glass-border);border-radius:8px;color:var(--yt-text-primary);font-size:13px;box-sizing:border-box;}\n        .ytp-plus-voting-add-form input:focus,.ytp-plus-voting-add-form textarea:focus{border-color:var(--yt-accent);outline:none;}\n        .ytp-plus-voting-add-form textarea{min-height:80px;resize:vertical;}\n        .ytp-plus-voting-form-actions{display:flex;gap:8px;justify-content:flex-end;}\n        .ytp-plus-voting-cancel{background:transparent;border:1px solid var(--yt-glass-border);color:var(--yt-text-primary);padding:8px 16px;border-radius:18px;font-size:13px;cursor:pointer;transition:all .2s ease;}\n        .ytp-plus-voting-cancel:hover{background:var(--yt-hover-bg);}\n        .ytp-plus-voting-submit{background:var(--yt-accent);color:#fff;border:none;padding:8px 16px;border-radius:18px;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;}\n        .ytp-plus-voting-submit:hover{transform:translateY(-2px);box-shadow:0 4px 12px var(--yt-danger-shadow);}\n        @media (max-width: 680px){.ytp-plus-voting-item{flex-direction:column;align-items:stretch}.ytp-plus-voting-item-content{padding-right:0}.ytp-plus-voting-item-votes{min-width:0;width:100%}}\n        .ytp-plus-voting-preview{margin-bottom:20px;}\n        .ytp-plus-ba-container{position:relative;width:100%;height:260px;overflow:hidden;border-radius:var(--yt-radius-md);border:1px solid var(--yt-glass-border);user-select:none;cursor:ew-resize;background:var(--yt-glass-bg);}\n        .ytp-plus-ba-before,.ytp-plus-ba-after{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;}\n        .ytp-plus-ba-before img,.ytp-plus-ba-after img{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;display:block;pointer-events:none;}\n        .ytp-plus-ba-after{clip-path:inset(0 0 0 50%);}\n        .ytp-plus-ba-divider{position:absolute;top:0;left:50%;transform:translateX(-50%);width:8px;height:100%;background:transparent;pointer-events:auto;z-index:3;cursor:ew-resize;transition:left .6s linear}\n        .ytp-plus-ba-divider::after{content:\'\';position:absolute;left:50%;top:0;transform:translateX(-50%);width:2px;height:100%;background:var(--yt-accent,#f00);}        \n        .ytp-plus-ba-divider.autoplay{animation:ytpPlusSlideDivider 6s linear infinite}\n        @keyframes ytpPlusSlideDivider{0%{left:10%}50%{left:90%}100%{left:10%}}\n        .ytp-plus-ba-label{position:absolute;top:10px;padding:4px 10px;border-radius:4px;font-size:12px;font-weight:600;color:#fff;background:var(--yt-overlay-strong);pointer-events:none;z-index:5;}\n        .ytp-plus-ba-label-before{left:10px;}\n        .ytp-plus-ba-label-after{right:10px;}\n        .ytp-plus-vote-bar-section{margin-top:12px;display:flex;flex-direction:column;align-items:center;gap:6px;}\n        .ytp-plus-vote-bar-buttons{position:relative;display:flex;gap:0;border-radius:20px;overflow:hidden;border:1px solid var(--yt-glass-border);}\n        .ytp-plus-vote-bar-track{position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;transition:background .4s ease;background:linear-gradient(to right, var(--yt-success) 50%, var(--yt-danger) 50%);border-radius:20px;}\n        .ytp-plus-vote-bar-btn{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;padding:8px 18px;background:transparent;border:none;color:var(--yt-text-secondary);cursor:pointer;transition:color .15s;font-size:14px;}\n        .ytp-plus-vote-bar-btn:first-of-type{border-right:1px solid var(--yt-glass-border);}\n        .ytp-plus-vote-bar-btn:hover{color:var(--yt-text-primary);}\n        .ytp-plus-vote-bar-btn.active{color:#fff;}\n        .ytp-plus-vote-bar-btn svg{fill:currentColor;width:20px;height:20px;display:block;}\n        .ytp-plus-vote-bar-btn svg path{fill:currentColor;}\n        .ytp-plus-vote-bar-count{font-size:12px;color:var(--yt-text-secondary);}';
(document.head || document.documentElement).appendChild(ncEl);
}
};
this.ensureNonCriticalStyles = injectNonCritical;
document.getElementById("yt-enhancer-main") || YouTubeUtils.StyleManager.add("yt-enhancer-main", ".ytp-screenshot-button,.ytp-cobalt-button,.ytp-pip-button{position:relative;width:44px;height:100%;display:inline-flex;align-items:center;justify-content:center;vertical-align:top;transition:opacity .15s,transform .15s;}\n        .ytp-screenshot-button:hover,.ytp-cobalt-button:hover,.ytp-pip-button:hover{transform:scale(1.1);}\n        .speed-control-btn{width:4em!important;position:relative!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;height:100%!important;vertical-align:top!important;text-align:center!important;border-radius:var(--yt-radius-sm);font-size:13px;color:var(--yt-text-primary);cursor:pointer;user-select:none;font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:color .2s;}\n        .speed-control-btn:hover{color:var(--yt-accent);font-weight:bold;}\n        .speed-options{position:fixed!important;background:var(--yt-glass-bg)!important;color:var(--yt-text-primary)!important;border-radius:var(--yt-radius-md)!important;display:flex!important;flex-direction:column!important;align-items:stretch!important;gap:0!important;transform:translate(-50%,12px)!important;width:92px!important;z-index:2147483647!important;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);overflow:hidden;opacity:0;pointer-events:none!important;transition:opacity .18s ease,transform .18s ease;box-sizing:border-box;}\n        .speed-options.visible{opacity:1;pointer-events:auto!important;transform:translate(-50%,0)!important;backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);}\n        .speed-option-item{cursor:pointer!important;height:28px!important;line-height:28px!important;font-size:12px!important;text-align:center!important;transition:background-color .15s,color .15s;}\n        .speed-option-active,.speed-option-item:hover{color:var(--yt-accent)!important;font-weight:bold!important;background:var(--yt-hover-bg)!important;}\n        #speed-indicator{position:absolute!important;margin:auto!important;top:0!important;right:0!important;bottom:0!important;left:0!important;border-radius:24px!important;font-size:30px!important;background:var(--yt-glass-bg)!important;color:var(--yt-text-primary)!important;z-index:99999!important;width:80px!important;height:80px!important;line-height:80px!important;text-align:center!important;display:none;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);}\n        .youtube-enhancer-notification-container{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:10px;z-index:2147483647;pointer-events:none;max-width:calc(100% - 32px);width:100%;box-sizing:border-box;padding:0 16px;}\n        .youtube-enhancer-notification{position:relative;max-width:700px;width:auto;background:var(--yt-glass-bg);color:var(--yt-text-primary);padding:8px 14px;font-size:13px;border-radius:var(--yt-radius-md);z-index:inherit;transition:opacity .35s,transform .32s;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);font-weight:500;box-sizing:border-box;display:flex;align-items:center;gap:10px;pointer-events:auto;}\n        .ytp-plus-loop-indicator{position:absolute;height:100%;background:linear-gradient(90deg,var(--yt-accent-secondary-ghost) 0%,var(--yt-accent-secondary-light-ghost) 50%,var(--yt-accent-secondary-ghost) 100%);border-left:2px solid var(--yt-accent-secondary);border-right:2px solid var(--yt-accent-secondary);display:none;pointer-events:none;top:0;z-index:1000;box-shadow:inset 0 0 4px var(--yt-accent-secondary-shadow);}\n        .ytp-plus-settings-button{background:transparent;border:none;color:var(--yt-text-secondary);cursor:pointer;padding:var(--yt-space-sm);margin-right:var(--yt-space-sm);border:none;display:flex;align-items:center;justify-content:center;transition:background-color .2s,transform .2s;}\n        .ytp-plus-settings-button svg{width:24px;height:24px;}\n        .ytp-plus-settings-button:hover{transform:rotate(30deg);color:var(--yt-text-secondary);}\n        .ytp-download-button{position:relative!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;height:100%!important;vertical-align:top!important;cursor:pointer!important;}\n        .ytSearchboxComponentInputBox { background: transparent !important; }");
"function" == typeof requestIdleCallback ? requestIdleCallback(injectNonCritical, {
timeout: 5e3
}) : basicSetTimeout_(injectNonCritical, 1e3);
},
addSettingsButtonToHeader() {
this.waitForElement("ytd-masthead #end", 5e3).then(headerEnd => {
if (!this.getElement(".ytp-plus-settings-button")) {
const settingsButton = document.createElement("div");
settingsButton.className = "ytp-plus-settings-button";
settingsButton.setAttribute("title", t("youtubeSettings"));
_setSafeHTML(settingsButton, '\n                <svg width="24" height="24" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round">\n                  <path d="M39.23,26a16.52,16.52,0,0,0,.14-2,16.52,16.52,0,0,0-.14-2l4.33-3.39a1,1,0,0,0,.25-1.31l-4.1-7.11a1,1,0,0,0-1.25-.44l-5.11,2.06a15.68,15.68,0,0,0-3.46-2l-.77-5.43a1,1,0,0,0-1-.86H19.9a1,1,0,0,0-1,.86l-.77,5.43a15.36,15.36,0,0,0-3.46,2L9.54,9.75a1,1,0,0,0-1.25.44L4.19,17.3a1,1,0,0,0,.25,1.31L8.76,22a16.66,16.66,0,0,0-.14,2,16.52,16.52,0,0,0,.14,2L4.44,29.39a1,1,0,0,0-.25,1.31l4.1,7.11a1,1,0,0,0,1.25.44l5.11-2.06a15.68,15.68,0,0,0,3.46,2l.77,5.43a1,1,0,0,0,1,.86h8.2a1,1,0,0,0,1-.86l.77-5.43a15.36,15.36,0,0,0,3.46-2l5.11,2.06a1,1,0,0,0,1.25-.44l4.1-7.11a1,1,0,0,0-.25-1.31ZM24,31.18A7.18,7.18,0,1,1,31.17,24,7.17,7.17,0,0,1,24,31.18Z"/>\n                </svg>\n              ');
settingsButton.addEventListener("click", this.openSettingsModal.bind(this));
const avatarButton = headerEnd.querySelector("ytd-topbar-menu-button-renderer");
avatarButton ? headerEnd.insertBefore(settingsButton, avatarButton) : headerEnd.appendChild(settingsButton);
}
}).catch(() => {});
},
handleModalClickActions(target, modal, handlers, _markDirty, _context, translate) {
const navItem = target.classList && target.classList.contains("ytp-plus-settings-nav-item") ? target : target.closest && target.closest(".ytp-plus-settings-nav-item");
if (navItem) {
handlers.handleSidebarNavigation(navItem, modal);
} else if ("ytp-plus-save-settings" !== target.id && "ytp-plus-save-settings-icon" !== target.id) {
"download-externalDownloader-save" !== target.id ? "download-externalDownloader-reset" === target.id && handlers.handleExternalDownloaderReset(modal, this.settings, this.saveSettings.bind(this), this.showNotification.bind(this), translate) : handlers.handleExternalDownloaderSave(target, this.settings, this.saveSettings.bind(this), this.showNotification.bind(this), translate);
} else {
this.saveSettings();
try {
document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-closed", {
bubbles: !0
}));
} catch (e) {}
modal.remove();
this.showNotification(translate("settingsSaved"));
}
},
createSettingsModal() {
const modal = document.createElement("div");
modal.className = "ytp-plus-settings-modal";
const helpers = window.YouTubePlusSettingsHelpers;
const handlers = window.YouTubePlusModalHandlers;
_setSafeHTML(modal, `\n        <div class="ytp-plus-settings-shell">\n          <div class="ytp-plus-settings-sidebar">${helpers.createSettingsSidebar(t)}</div>\n          <div class="ytp-plus-settings-column">\n            <div class="ytp-plus-settings-topbar">\n              <h2 class="ytp-plus-settings-title">${t("settingsTitle")}</h2>\n              <div class="ytp-plus-settings-active-label" id="ytp-plus-active-section-label"></div>\n              <button class="ytp-plus-button ytp-plus-button-primary" id="ytp-plus-save-settings">${t("saveChanges")}</button>\n            </div>\n            <div class="ytp-plus-settings-panel">${helpers.createMainContent(this.settings, t)}</div>\n          </div>\n          <div class="ytp-plus-settings-side-actions">\n            <button class="ytp-plus-settings-close" id="ytp-plus-close-settings" aria-label="${t("closeButton")}">\n              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 9.50002L9.5 14.5M9.49998 9.5L14.5 14.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg>\n            </button>\n          </div>\n        </div>\n      `);
const _initialNav = modal.querySelector(".ytp-plus-settings-nav-item.active");
const _activeLabel = modal.querySelector("#ytp-plus-active-section-label");
_initialNav && _activeLabel && (_activeLabel.textContent = _initialNav.dataset?.label || "");
const markDirty = () => {};
const context = {
settings: this.settings,
getElement: this.getElement.bind(this),
addDownloadButton: this.addDownloadButton.bind(this),
addSpeedControlButton: this.addSpeedControlButton.bind(this),
refreshDownloadButton: this.refreshDownloadButton.bind(this),
updatePageBasedOnSettings: this.updatePageBasedOnSettings.bind(this)
};
modal.addEventListener("click", e => {
const target = e.target;
const submenuToggleBtn = target.closest(".ytp-plus-submenu-toggle");
if (submenuToggleBtn) {
try {
if (submenuToggleBtn instanceof HTMLElement && "BUTTON" === submenuToggleBtn.tagName && submenuToggleBtn.hasAttribute("disabled")) {
return;
}
const submenuKey = submenuToggleBtn.dataset?.submenu;
if (!submenuKey) {
return;
}
const panel = submenuToggleBtn.closest(".ytp-plus-settings-panel");
if (!panel) {
return;
}
const submenuSelector = "music" === submenuKey ? `.music-submenu[data-submenu="${submenuKey}"]` : "download" === submenuKey ? `.download-submenu[data-submenu="${submenuKey}"]` : "style" === submenuKey ? `.style-submenu[data-submenu="${submenuKey}"]` : "speed" === submenuKey ? `.speed-submenu[data-submenu="${submenuKey}"]` : "loop" === submenuKey ? `.loop-submenu[data-submenu="${submenuKey}"]` : "pip" === submenuKey ? `.pip-submenu[data-submenu="${submenuKey}"]` : "timecode" === submenuKey ? `.timecode-submenu[data-submenu="${submenuKey}"]` : "enhanced" === submenuKey ? `.enhanced-submenu[data-submenu="${submenuKey}"]` : `[data-submenu="${submenuKey}"]`;
const submenuEl = panel.querySelector(submenuSelector);
if (!(submenuEl instanceof HTMLElement)) {
return;
}
const computedDisplay = window.getComputedStyle(submenuEl).display;
const currentlyHidden = "none" === computedDisplay || submenuEl.hidden;
const nextHidden = !currentlyHidden;
submenuEl.style.display = nextHidden ? "none" : "";
submenuToggleBtn.setAttribute("aria-expanded", nextHidden ? "false" : "true");
try {
const submenuStates = JSON.parse(localStorage.getItem("ytp-plus-submenu-states") || "{}");
submenuStates[submenuKey] = !nextHidden;
localStorage.setItem("ytp-plus-submenu-states", JSON.stringify(submenuStates));
} catch (e) {}
} catch (e) {
window.console.warn("[YouTube+] Submenu toggle error:", e);
}
return;
}
const themeCard = target.closest(".ytp-plus-theme-card[data-setting-card][data-value]");
if (themeCard instanceof HTMLElement) {
const setting = themeCard.dataset.settingCard;
const value = themeCard.dataset.value;
if (setting && "string" == typeof value) {
handlers.setSettingByPath(this.settings, setting, value);
const group = themeCard.closest(".ytp-plus-theme-grid");
group && group.querySelectorAll(".ytp-plus-theme-card").forEach(card => {
const isActive = card === themeCard;
card.classList.toggle("active", isActive);
card.setAttribute("aria-checked", isActive ? "true" : "false");
});
handlers.applySettingLive(setting, context);
this.saveSettings();
}
return;
}
if (target !== modal) {
if ("ytp-plus-close-settings" === target.id || "ytp-plus-close-settings-icon" === target.id || target.classList.contains("ytp-plus-settings-close") || target.closest(".ytp-plus-settings-close") || target.closest("#ytp-plus-close-settings") || target.closest("#ytp-plus-close-settings-icon")) {
try {
document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-closed", {
bubbles: !0
}));
} catch (e) {}
modal.remove();
} else {
"open-ytdl-github" === target.id || target.closest("#open-ytdl-github") ? window.open("https://github.com/diorhc/YTDL", "_blank") : "open-ytp-github" === target.id || target.closest("#open-ytp-github") ? window.open("https://github.com/diorhc/YTP", "_blank") : "open-ytp-discussions" === target.id || target.closest("#open-ytp-discussions") ? window.open("https://github.com/diorhc/YTP/discussions", "_blank") : "open-ytp-greasyfork" === target.id || target.closest("#open-ytp-greasyfork") ? window.open("https://greasyfork.org/en/scripts/537017-youtube", "_blank") : this.handleModalClickActions(target, modal, handlers, markDirty, context, t);
}
} else {
try {
document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-closed", {
bubbles: !0
}));
} catch (e) {}
modal.remove();
}
});
modal.addEventListener("change", e => {
const target = e.target;
if ("SELECT" === target.tagName && target.dataset?.setting) {
const setting = target.dataset.setting;
const rawValue = target.value;
const numericValue = Number(rawValue);
const parsedValue = "" !== rawValue && Number.isNaN(numericValue) ? rawValue : numericValue;
handlers.setSettingByPath(this.settings, setting, parsedValue);
handlers.applySettingLive(setting, context);
this.saveSettings();
return;
}
if (!target.classList.contains("ytp-plus-settings-checkbox")) {
return;
}
const {dataset} = target;
const {setting} = dataset;
if (setting) {
if (setting.startsWith("downloadSite_")) {
const key = setting.replace("downloadSite_", "");
handlers.handleDownloadSiteToggle(target, key, this.settings, markDirty, this.saveSettings.bind(this));
return;
}
handlers.isMusicSetting && handlers.isMusicSetting(setting) ? handlers.handleMusicSettingToggle(target, setting, this.showNotification.bind(this), t) : handlers.handleSimpleSettingToggle(target, setting, this.settings, context, markDirty, this.saveSettings.bind(this), modal);
}
});
modal.addEventListener("input", e => {
const target = e.target;
if (target.classList.contains("speed-hotkey-input")) {
const keyType = target.dataset?.speedHotkey;
"decrease" !== keyType && "increase" !== keyType && "reset" !== keyType;
return;
}
if (target.classList.contains("loop-hotkey-input")) {
const keyType = target.dataset?.loopHotkey;
"setPointA" !== keyType && "setPointB" !== keyType && "resetPoints" !== keyType;
return;
}
if (target.classList.contains("download-site-input")) {
const {dataset} = target;
const {site, field} = dataset;
if (!site || !field) {
return;
}
handlers.handleDownloadSiteInput(target, site, field, this.settings, markDirty, t);
}
});
modal.addEventListener("blur", e => {
const target = e.target;
if (target.classList.contains("speed-hotkey-input")) {
const keyType = target.dataset?.speedHotkey;
if ("decrease" !== keyType && "increase" !== keyType && "reset" !== keyType) {
return;
}
const input = target;
const fallback = "decrease" === keyType ? "g" : "increase" === keyType ? "h" : "b";
const normalized = this.normalizeSpeedHotkey(input.value, fallback);
this.settings.speedControlHotkeys = this.settings.speedControlHotkeys || {
decrease: "g",
increase: "h",
reset: "b"
};
this.settings.speedControlHotkeys[keyType] = normalized;
input.value = normalized;
this.saveSettings();
return;
}
if (target.classList.contains("loop-hotkey-input")) {
const keyType = target.dataset?.loopHotkey;
if ("setPointA" !== keyType && "setPointB" !== keyType && "resetPoints" !== keyType) {
return;
}
const input = target;
const fallback = "setPointA" === keyType ? "k" : "setPointB" === keyType ? "l" : "o";
const normalized = this.normalizeSpeedHotkey(input.value, fallback);
this.settings.loopHotkeys = this.settings.loopHotkeys || {
toggleLoop: "r",
setPointA: "k",
setPointB: "l",
resetPoints: "o"
};
this.settings.loopHotkeys[keyType] = normalized;
input.value = normalized;
this.saveSettings();
return;
}
}, !0);
try {
if ("undefined" != typeof window && window.youtubePlusReport && "function" == typeof window.youtubePlusReport.render) {
try {
window.youtubePlusReport.render(modal);
} catch (e) {
YouTubeUtils.logError("Report", "report.render failed", e);
}
}
} catch (e) {
YouTubeUtils.logError("Report", "Failed to initialize report section", e);
}
let submenuStates = {};
try {
submenuStates = JSON.parse(localStorage.getItem("ytp-plus-submenu-states") || "{}");
Object.entries(submenuStates).forEach(([key, expanded]) => {
const toggleBtn = modal.querySelector(`.ytp-plus-submenu-toggle[data-submenu="${key}"]`);
if (toggleBtn instanceof HTMLElement && !toggleBtn.hasAttribute("disabled")) {
const submenuSelector = "music" === key ? `.music-submenu[data-submenu="${key}"]` : "download" === key ? `.download-submenu[data-submenu="${key}"]` : "style" === key ? `.style-submenu[data-submenu="${key}"]` : "speed" === key ? `.speed-submenu[data-submenu="${key}"]` : "pip" === key ? `.pip-submenu[data-submenu="${key}"]` : "timecode" === key ? `.timecode-submenu[data-submenu="${key}"]` : "enhanced" === key ? `.enhanced-submenu[data-submenu="${key}"]` : `[data-submenu="${key}"]`;
const submenuEl = modal.querySelector(submenuSelector);
if (submenuEl instanceof HTMLElement) {
const isExpanded = !!expanded;
submenuEl.style.display = isExpanded ? "" : "none";
toggleBtn.setAttribute("aria-expanded", isExpanded ? "true" : "false");
}
}
});
} catch (e) {}
try {
const advancedSection = modal.querySelector('.ytp-plus-settings-section[data-section="advanced"]');
if (advancedSection instanceof HTMLElement) {
const ensureVisibleWhenEnabled = (key, setting, submenuSelector) => {
if (Object.prototype.hasOwnProperty.call(submenuStates, key)) {
return;
}
const checkbox = advancedSection.querySelector(`.ytp-plus-settings-checkbox[data-setting="${setting}"]`);
const submenu = advancedSection.querySelector(submenuSelector);
const toggleBtn = advancedSection.querySelector(`.ytp-plus-submenu-toggle[data-submenu="${key}"]`);
if (checkbox instanceof Element && checkbox.classList.contains("ytp-plus-settings-checkbox") && checkbox.checked && submenu instanceof HTMLElement) {
submenu.style.display = "";
toggleBtn instanceof HTMLElement && toggleBtn.setAttribute("aria-expanded", "true");
}
};
ensureVisibleWhenEnabled("enhanced", "enableEnhanced", '.enhanced-submenu[data-submenu="enhanced"]');
ensureVisibleWhenEnabled("music", "enableMusic", '.music-submenu[data-submenu="music"]');
}
} catch (e) {}
try {
const savedSection = localStorage.getItem("ytp-plus-active-nav-section");
if (savedSection) {
const navItem = modal.querySelector(`.ytp-plus-settings-nav-item[data-section="${savedSection}"]`);
if (navItem) {
modal.querySelectorAll(".ytp-plus-settings-nav-item").forEach(item => item.classList.remove("active"));
modal.querySelectorAll(".ytp-plus-settings-section").forEach(s => s.classList.add("hidden"));
navItem.classList.add("active");
const targetSection = modal.querySelector(`.ytp-plus-settings-section[data-section="${savedSection}"]`);
targetSection && targetSection.classList.remove("hidden");
}
}
} catch (e) {}
return modal;
},
openSettingsModal() {
const existingModal = this.getElement(".ytp-plus-settings-modal", !1);
if (existingModal) {
try {
document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-closed", {
bubbles: !0
}));
} catch (e) {}
existingModal.remove();
}
"function" == typeof this.ensureNonCriticalStyles && this.ensureNonCriticalStyles();
document.body.appendChild(this.createSettingsModal());
if (window.YouTubePlus?.Voting) {
const votingContainer = document.getElementById("ytp-plus-voting-container");
if (votingContainer) {
window.YouTubePlus.Voting.init();
window.YouTubePlus.Voting.createUI(votingContainer);
window.YouTubePlus.Voting.loadFeatures();
}
const votingSection = document.querySelector('.ytp-plus-settings-section[data-section="voting"]');
votingSection && !votingSection.classList.contains("hidden") && requestAnimationFrame(() => window.YouTubePlus.Voting?.initSlider?.());
}
try {
document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-opened", {
bubbles: !0
}));
} catch (e) {}
},
waitForElement: (selector, timeout = 5e3) => YouTubeUtils.waitForElement(selector, timeout),
addCustomButtons() {
const controls = this.getElement(".ytp-right-controls");
if (controls) {
this.getElement(".ytp-screenshot-button") || this.addScreenshotButton(controls);
this.getElement(".ytp-download-button") || this.addDownloadButton(controls);
this.getElement(".speed-control-btn") || this.addSpeedControlButton(controls);
if (!document.getElementById("speed-indicator")) {
const indicator = document.createElement("div");
indicator.id = "speed-indicator";
const player = document.getElementById("movie_player");
player && player.appendChild(indicator);
}
this.handleFullscreenChange();
}
},
addScreenshotButton(controls) {
const button = document.createElement("button");
button.className = "ytp-button ytp-screenshot-button";
button.setAttribute("title", t("takeScreenshot"));
button.setAttribute("aria-label", t("takeScreenshot"));
_setSafeHTML(button, '\n          <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity="0.5" d="M7.142 18.9706C5.18539 18.8995 3.99998 18.6568 3.17157 17.8284C2 16.6569 2 14.7712 2 11C2 7.22876 2 5.34315 3.17157 4.17157C4.34315 3 6.22876 3 10 3H14C17.7712 3 19.6569 3 20.8284 4.17157C22 5.34315 22 7.22876 22 11C22 14.7712 22 16.6569 20.8284 17.8284C20.0203 18.6366 18.8723 18.8873 17 18.965" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" style="--darkreader-inline-stroke: var(--darkreader-text-ffffff, #cad3f5);" data-darkreader-inline-stroke=""></path> <path d="M9.94955 16.0503C10.8806 15.1192 11.3461 14.6537 11.9209 14.6234C11.9735 14.6206 12.0261 14.6206 12.0787 14.6234C12.6535 14.6537 13.119 15.1192 14.0501 16.0503C16.0759 18.0761 17.0888 19.089 16.8053 19.963C16.7809 20.0381 16.7506 20.1112 16.7147 20.1815C16.2973 21 14.8648 21 11.9998 21C9.13482 21 7.70233 21 7.28489 20.1815C7.249 20.1112 7.21873 20.0381 7.19436 19.963C6.91078 19.089 7.92371 18.0761 9.94955 16.0503Z" stroke="#ffffff" stroke-width="1.5" style="--darkreader-inline-stroke: var(--darkreader-text-ffffff, #cad3f5);" data-darkreader-inline-stroke=""></path></svg>  \n        ');
button.addEventListener("click", this.captureFrame.bind(this));
controls.insertBefore(button, controls.firstChild);
},
addDownloadButton(controls) {
if ("undefined" != typeof window && window.YouTubePlusDownloadButton) {
const manager = window.YouTubePlusDownloadButton.createDownloadButtonManager({
settings: this.settings,
t,
getElement: this.getElement.bind(this),
YouTubeUtils
});
manager.addDownloadButton(controls);
} else {
window.console.warn("[YouTube+] Download button module not loaded");
}
},
addSpeedControlButton(controls) {
if (!this.settings.enableSpeedControl) {
return;
}
const speedBtn = document.createElement("button");
speedBtn.type = "button";
speedBtn.className = "ytp-button speed-control-btn";
speedBtn.setAttribute("aria-label", t("speedControl"));
speedBtn.setAttribute("aria-haspopup", "true");
speedBtn.setAttribute("aria-expanded", "false");
_setSafeHTML(speedBtn, `<span>${this.speedControl.currentSpeed}×</span>`);
const speedOptions = document.createElement("div");
speedOptions.className = "speed-options";
speedOptions.setAttribute("role", "menu");
const selectSpeed = speed => {
this.changeSpeed(speed);
hideDropdown();
};
this.speedControl.availableSpeeds.forEach(speed => {
const option = document.createElement("div");
option.className = "speed-option-item" + (Number(speed) === this.speedControl.currentSpeed ? " speed-option-active" : "");
option.textContent = `${speed}x`;
option.dataset.speed = String(speed);
option.setAttribute("role", "menuitem");
option.tabIndex = 0;
option.addEventListener("click", () => selectSpeed(speed));
option.addEventListener("keydown", event => {
if ("Enter" === event.key || " " === event.key) {
event.preventDefault();
selectSpeed(speed);
}
});
speedOptions.appendChild(option);
});
speedBtn.appendChild(speedOptions);
const existingSpeed = document.querySelector(".speed-options");
existingSpeed && existingSpeed.remove();
try {
document.body.appendChild(speedOptions);
} catch (e) {}
const positionDropdown = () => {
const rect = speedBtn.getBoundingClientRect();
speedOptions.style.left = `${rect.left + rect.width / 2}px`;
speedOptions.style.bottom = window.innerHeight - rect.top + 8 + "px";
};
const hideDropdown = () => {
speedOptions.classList.remove("visible");
speedBtn.setAttribute("aria-expanded", "false");
};
const showDropdown = () => {
positionDropdown();
speedOptions.classList.add("visible");
speedBtn.setAttribute("aria-expanded", "true");
};
let documentClickKey;
documentClickKey = YouTubeUtils.cleanupManager.registerListener(document, "click", event => {
if (speedBtn.isConnected) {
speedOptions.classList.contains("visible") && (speedBtn.contains(event.target) || speedOptions.contains(event.target) || hideDropdown());
} else if (documentClickKey) {
YouTubeUtils.cleanupManager.unregisterListener(documentClickKey);
documentClickKey = void 0;
}
}, !0);
YouTubeUtils.cleanupManager.registerListener(document, "keydown", event => {
if ("Escape" === event.key && speedOptions.classList.contains("visible")) {
hideDropdown();
speedBtn.focus();
}
}, !0);
YouTubeUtils.cleanupManager.registerListener(window, "resize", () => {
speedOptions.classList.contains("visible") && positionDropdown();
});
YouTubeUtils.cleanupManager.registerListener(window, "scroll", () => {
speedOptions.classList.contains("visible") && positionDropdown();
}, !0);
let speedHideTimer;
speedBtn.addEventListener("mouseenter", () => {
clearTimeout(speedHideTimer);
showDropdown();
});
speedBtn.addEventListener("mouseleave", () => {
clearTimeout(speedHideTimer);
speedHideTimer = basicSetTimeout_(hideDropdown, 200);
});
speedOptions.addEventListener("mouseenter", () => {
clearTimeout(speedHideTimer);
showDropdown();
});
speedOptions.addEventListener("mouseleave", () => {
clearTimeout(speedHideTimer);
speedHideTimer = basicSetTimeout_(hideDropdown, 200);
});
speedBtn.addEventListener("keydown", event => {
if ("Enter" === event.key || " " === event.key) {
event.preventDefault();
speedOptions.classList.contains("visible") ? hideDropdown() : showDropdown();
} else {
"Escape" === event.key && hideDropdown();
}
});
controls.insertBefore(speedBtn, controls.firstChild);
},
applyGuideVisibility() {
try {
const enabled = Boolean(YouTubeUtils.storage.get("ytplus.hideGuide", !1));
document.documentElement.classList.toggle("ytp-hide-guide", enabled);
const btn = document.getElementById("ytplus-guide-toggle-btn");
if (btn) {
btn.setAttribute("aria-pressed", String(enabled));
const label = enabled ? "Show side guide" : "Hide side guide";
btn.title = label;
btn.setAttribute("aria-label", label);
}
} catch (e) {
window.console.warn("[YouTube+] applyGuideVisibility failed:", e);
}
},
toggleSideGuide() {
try {
const current = Boolean(YouTubeUtils.storage.get("ytplus.hideGuide", !1));
const next = !current;
YouTubeUtils.storage.set("ytplus.hideGuide", next);
this.applyGuideVisibility();
} catch (e) {
window.console.warn("[YouTube+] toggleSideGuide failed:", e);
}
},
createGuideToggleButton() {
try {
if (document.getElementById("ytplus-guide-toggle-btn")) {
return;
}
const btn = document.createElement("button");
btn.id = "ytplus-guide-toggle-btn";
btn.type = "button";
btn.style.cssText = "position:fixed;right:12px;bottom:12px;z-index:100000;background:var(--yt-spec-call-to-action);color:#fff;border:none;border-radius:8px;padding:8px 10px;box-shadow:0 6px 18px var(--yt-shadow-notification);cursor:pointer;opacity:0.95;font-size:13px;";
btn.setAttribute("aria-pressed", "false");
btn.setAttribute("aria-label", "Hide side guide");
btn.title = "Hide side guide";
btn.textContent = "Toggle Guide";
btn.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
this.toggleSideGuide();
});
btn.addEventListener("keydown", e => {
if ("Enter" === e.key || " " === e.key) {
e.preventDefault();
this.toggleSideGuide();
}
});
document.body.appendChild(btn);
this.applyGuideVisibility();
} catch (e) {
window.console.warn("[YouTube+] createGuideToggleButton failed:", e);
}
},
captureFrame() {
const video = this.getElement("video", !1);
if (!video) {
return;
}
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext("2d");
if (!ctx) {
return;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const videoTitle = document.title.replace(/\s-\sYouTube$/, "").trim();
const link = document.createElement("a");
link.href = canvas.toDataURL("image/png");
link.download = `${videoTitle}.png`;
try {
link.click();
try {
const translated = "function" == typeof t ? t("screenshotSaved") : null;
const message = translated && "screenshotSaved" !== translated ? translated : "Screenshot saved";
this.showNotification(message, 2e3);
} catch (e) {
this.showNotification("Screenshot saved", 2e3);
}
} catch (err) {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Basic", "Screenshot download failed", err);
try {
const translatedFail = "function" == typeof t ? t("screenshotFailed") : null;
const failMsg = translatedFail && "screenshotFailed" !== translatedFail ? translatedFail : "Screenshot failed";
this.showNotification(failMsg, 3e3);
} catch (e) {
this.showNotification("Screenshot failed", 3e3);
}
}
},
showNotification(message, duration = 2e3) {
YouTubeUtils.NotificationManager.show(message, {
duration,
type: "info"
});
},
handleFullscreenChange() {
document.fullscreenElement || document;
document.querySelectorAll(".ytp-screenshot-button, .ytp-cobalt-button").forEach(button => {
button.style.bottom = "0px";
});
},
changeSpeed(speed) {
const numericSpeed = Number(speed);
this.speedControl.currentSpeed = numericSpeed;
localStorage.setItem(this.speedControl.storageKey, String(numericSpeed));
const speedBtn = this.getElement(".speed-control-btn span", !1);
speedBtn && (speedBtn.textContent = `${numericSpeed}×`);
document.querySelectorAll(".speed-option-item").forEach(option => {
option.classList.toggle("speed-option-active", parseFloat(option.dataset?.speed || "0") === numericSpeed);
});
this.applyCurrentSpeed();
this.showSpeedIndicator(numericSpeed);
},
applyCurrentSpeed() {
const videos = window.YouTubeDOMCache && "function" == typeof window.YouTubeDOMCache.getAll ? window.YouTubeDOMCache.getAll("video") : document.querySelectorAll("video");
videos.forEach(video => {
video && video.playbackRate !== this.speedControl.currentSpeed && (video.playbackRate = this.speedControl.currentSpeed);
});
},
setupVideoObserver() {
this._speedInterval && clearInterval(this._speedInterval);
this._speedInterval = null;
if (!this._mouseHoldTracked) {
this._mouseHoldTracked = !0;
this._mouseButtonHeld = !1;
YouTubeUtils.cleanupManager.registerListener(document, "mousedown", e => {
0 === e.button && (this._mouseButtonHeld = !0);
}, {
passive: !0,
capture: !0
});
YouTubeUtils.cleanupManager.registerListener(document, "mouseup", e => {
0 === e.button && (this._mouseButtonHeld = !1);
}, {
passive: !0,
capture: !0
});
}
const applySpeed = () => this.applyCurrentSpeed();
const updateLoopBar = () => this.updateLoopProgressBar();
const applyLoop = () => this.applyLoopStateToCurrentVideo();
const attachSpeedListeners = video => {
if (video._ytpSpeedListenerAttached) {
return;
}
video._ytpSpeedListenerAttached = !0;
video.addEventListener("loadedmetadata", applySpeed);
video.addEventListener("loadedmetadata", updateLoopBar);
video.addEventListener("loadedmetadata", applyLoop);
video.addEventListener("playing", applySpeed);
let _settingRate = !1;
video.addEventListener("ratechange", () => {
if (!(_settingRate || this._mouseButtonHeld && video.playbackRate > this.speedControl.currentSpeed || video.playbackRate === this.speedControl.currentSpeed)) {
_settingRate = !0;
video.playbackRate = this.speedControl.currentSpeed;
_settingRate = !1;
}
});
applySpeed();
};
const mainPlayer = document.querySelector("#movie_player") || document.querySelector("ytd-player");
mainPlayer ? mainPlayer.querySelectorAll("video").forEach(attachSpeedListeners) : document.querySelectorAll("video").forEach(attachSpeedListeners);
const coordinator = window.YouTubeMutationCoordinator;
const onVideoMutations = mutations => {
for (const m of mutations) {
for (const node of m.addedNodes) {
"VIDEO" === node.nodeName && attachSpeedListeners(node);
node instanceof Element && node.querySelectorAll?.("video").forEach(attachSpeedListeners);
}
}
};
const playerRoot = document.querySelector("#movie_player") || document.querySelector("ytd-player") || document.body;
if (playerRoot && coordinator?.watchTarget) {
coordinator.watchTarget("basic::speedVideoElements", playerRoot, onVideoMutations, {
childList: !0,
attributes: !1,
subtree: !0
});
YouTubeUtils.cleanupManager.register(() => {
coordinator.unwatch("basic::speedVideoElements");
});
}
},
setupNavigationObserver() {
let lastUrl = location.href;
YouTubeUtils.cleanupManager.registerListener(document, "fullscreenchange", this.handleFullscreenChange.bind(this));
YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", () => {
location.href.includes("watch?v=") && this.setupCurrentPage();
this.addSettingsButtonToHeader();
});
const checkUrlChange = () => {
if (lastUrl !== location.href) {
lastUrl = location.href;
location.href.includes("watch?v=") && basicSetTimeout_(() => this.setupCurrentPage(), 500);
this.addSettingsButtonToHeader();
}
};
YouTubeUtils.cleanupManager.registerListener(window, "popstate", checkUrlChange);
YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-start", checkUrlChange);
},
showSpeedIndicator(speed) {
const indicator = document.getElementById("speed-indicator");
if (!indicator) {
return;
}
if (this.speedControl.activeAnimationId) {
cancelAnimationFrame(this.speedControl.activeAnimationId);
YouTubeUtils.cleanupManager.unregisterAnimationFrame(this.speedControl.activeAnimationId);
this.speedControl.activeAnimationId = null;
}
indicator.textContent = `${speed}×`;
indicator.style.display = "block";
indicator.style.opacity = "0.8";
const startTime = performance.now();
const fadeOut = timestamp => {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / 1500, 1);
indicator.style.opacity = String(.8 * (1 - progress));
if (progress < 1) {
this.speedControl.activeAnimationId = YouTubeUtils.cleanupManager.registerAnimationFrame(requestAnimationFrame(fadeOut));
} else {
indicator.style.display = "none";
this.speedControl.activeAnimationId = null;
}
};
this.speedControl.activeAnimationId = YouTubeUtils.cleanupManager.registerAnimationFrame(requestAnimationFrame(fadeOut));
}
};
const initFunction = YouTubeEnhancer.init.bind(YouTubeEnhancer);
try {
window.YouTubePlus = window.YouTubePlus || {};
window.YouTubePlus.openSettings = opts => {
try {
if (opts && "string" == typeof opts.section) {
try {
localStorage.setItem("youtube_plus_last_active_section", opts.section);
} catch (e) {}
}
YouTubeEnhancer.openSettingsModal();
return !0;
} catch (e) {
window.console.warn("[YouTube+] openSettings failed:", e);
return !1;
}
};
window.YouTubePlus.closeSettings = () => {
try {
const existing = document.querySelector(".ytp-plus-settings-modal");
if (existing) {
try {
document.dispatchEvent(new CustomEvent("youtube-plus-settings-modal-closed", {
bubbles: !0
}));
} catch (e) {}
existing.remove();
return !0;
}
return !1;
} catch (e) {
window.console.warn("[YouTube+] closeSettings failed:", e);
return !1;
}
};
} catch (e) {
window.console.warn("[YouTube+] Failed to expose public settings API:", e);
}
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", initFunction) : initFunction();
})();

!(function() {
"use strict";
const PerformanceConfig = {
enabled: !0,
sampleRate: .01,
storageKey: "youtube_plus_performance",
metricsRetention: 100,
enableconsoleOutput: !1,
logLevel: "info"
};
const isTestEnv = (() => {
try {
return "undefined" != typeof process && !!process?.env?.JEST_WORKER_ID;
} catch (e) {
return !1;
}
})();
const qs = window.YouTubeUtils?.$ || ((selector, root) => (root || document).querySelector(selector));
const qsAll = window.YouTubeUtils?.$$ || ((selector, root) => Array.from((root || document).querySelectorAll(selector)));
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
PerformanceConfig.sampleRate = isTestEnv ? 1 : (() => {
try {
const cfg = window.YouTubePlusConfig;
const explicit = cfg?.performance?.sampleRate ?? cfg?.performanceSampleRate ?? cfg?.perfSampleRate ?? void 0;
if ("number" == typeof explicit && isFinite(explicit)) {
return Math.min(1, Math.max(0, explicit));
}
} catch (e) {}
return PerformanceConfig.sampleRate;
})();
try {
!isTestEnv && PerformanceConfig.sampleRate < 1 && Math.random() > PerformanceConfig.sampleRate && (PerformanceConfig.enabled = !1);
} catch (e) {}
const metrics = {
timings: new Map,
marks: new Map,
measures: [],
resources: [],
webVitals: {
LCP: null,
CLS: 0,
FID: null,
INP: null,
FCP: null,
TTFB: null
}
};
const mark = name => {
if (PerformanceConfig.enabled) {
try {
"undefined" != typeof performance && performance.mark && performance.mark(name);
metrics.marks.set(name, Date.now());
} catch (e) {
window.console.warn("[YouTube+ Perf] Failed to create mark:", e);
}
}
};
const measure = (name, startMark, endMark) => {
if (!PerformanceConfig.enabled) {
return 0;
}
try {
const startTime = metrics.marks.get(startMark);
if (!startTime) {
return 0;
}
const endTime = endMark ? metrics.marks.get(endMark) ?? Date.now() : Date.now();
const duration = endTime - startTime;
const measureData = {
name,
startMark,
endMark: endMark || "now",
duration,
timestamp: Date.now()
};
metrics.measures.push(measureData);
metrics.measures.length > PerformanceConfig.metricsRetention && metrics.measures.shift();
PerformanceConfig.enableconsoleOutput && window.YouTubeUtils?.logger?.debug?.(`[YouTube+ Perf] ${name}: ${duration.toFixed(2)}ms`);
if ("undefined" != typeof performance && performance.measure) {
try {
performance.measure(name, startMark, endMark);
} catch (e) {}
}
return duration;
} catch (e) {
window.console.warn("[YouTube+ Perf] Failed to measure:", e);
return 0;
}
};
const timeFunction = (name, fn) => PerformanceConfig.enabled ? function(...args) {
const startMark = `${name}-start-${Date.now()}`;
mark(startMark);
try {
const fnCallable = fn;
const result = fnCallable.apply(this, args);
const promiseLike = result;
if (result && "function" == typeof promiseLike.then && "function" == typeof promiseLike.finally) {
return promiseLike.finally(() => {
measure(name, startMark, void 0);
});
}
measure(name, startMark, void 0);
return result;
} catch (error) {
measure(name, startMark, void 0);
throw error;
}
} : fn;
const timeAsyncFunction = (name, fn) => PerformanceConfig.enabled ? async function(...args) {
const startMark = `${name}-start-${Date.now()}`;
mark(startMark);
try {
const fnCallable = fn;
const result = await fnCallable.apply(this, args);
measure(name, startMark, void 0);
return result;
} catch (error) {
measure(name, startMark, void 0);
throw error;
}
} : fn;
const recordMetric = (name, value, metadata = {}) => {
if (!PerformanceConfig.enabled) {
return;
}
const metric = {
name,
value,
timestamp: Date.now(),
...metadata
};
metrics.timings.set(name, metric);
PerformanceConfig.enableconsoleOutput && window.YouTubeUtils?.logger?.debug?.(`[YouTube+ Perf] ${name}: ${value}`, metadata);
};
const getStats = metricName => {
if (metricName) {
const filtered = metrics.measures.filter(m => m.name === metricName);
if (0 === filtered.length) {
return null;
}
const durations = filtered.map(m => m.duration);
return {
name: metricName,
count: durations.length,
min: Math.min(...durations),
max: Math.max(...durations),
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
latest: durations[durations.length - 1]
};
}
const allMetrics = {};
const metricNames = [ ...new Set(metrics.measures.map(m => m.name)) ];
metricNames.forEach(name => {
allMetrics[name] = getStats(name);
});
return {
metrics: allMetrics,
webVitals: {
...metrics.webVitals
},
totalMeasures: metrics.measures.length,
totalMarks: metrics.marks.size,
customMetrics: Object.fromEntries(metrics.timings)
};
};
const getMemoryUsage = () => {
const perf = performance;
if ("undefined" == typeof performance || !perf.memory) {
return null;
}
try {
const memory = perf.memory;
return {
usedJSHeapSize: memory.usedJSHeapSize,
totalJSHeapSize: memory.totalJSHeapSize,
jsHeapSizeLimit: memory.jsHeapSizeLimit,
usedPercent: (memory.usedJSHeapSize / memory.jsHeapSizeLimit * 100).toFixed(2)
};
} catch (e) {
return null;
}
};
const trackMemory = () => {
const memory = getMemoryUsage();
memory && recordMetric("memory-usage", memory.usedJSHeapSize, {
totalJSHeapSize: memory.totalJSHeapSize,
usedPercent: memory.usedPercent
});
};
const checkThresholds = thresholds => {
const violations = [];
const allStats = getStats(void 0);
if (!allStats || !allStats.metrics) {
return violations;
}
Object.entries(thresholds).forEach(([metricName, threshold]) => {
const stat = allStats.metrics[metricName];
stat && stat.avg > threshold && violations.push({
metric: metricName,
threshold,
actual: stat.avg,
exceeded: stat.avg - threshold
});
});
return violations;
};
const exportMetrics = () => {
const data = {
timestamp: (new Date).toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
memory: getMemoryUsage(),
stats: getStats(void 0),
measures: metrics.measures,
customMetrics: Object.fromEntries(metrics.timings),
webVitals: metrics.webVitals
};
return JSON.stringify(data, null, 2);
};
const exportToFile = (filename = "youtube-plus-performance.json") => {
try {
const data = exportMetrics();
if ("undefined" == typeof Blob) {
window.console.warn("[YouTube+ Perf] Blob API not available");
return !1;
}
const blob = new Blob([ data ], {
type: "application/json"
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
return !0;
} catch (e) {
window.console.error("[YouTube+ Perf] Failed to export to file:", e);
return !1;
}
};
const aggregateByPeriod = (periodMs = 6e4) => {
const periods = new Map;
metrics.measures.forEach(measure => {
const periodStart = Math.floor(measure.timestamp / periodMs) * periodMs;
periods.has(periodStart) || periods.set(periodStart, []);
periods.get(periodStart).push(measure);
});
const aggregated = [];
periods.forEach((measures, periodStart) => {
const durations = measures.map(m => m.duration);
aggregated.push({
period: new Date(periodStart).toISOString(),
count: durations.length,
min: Math.min(...durations),
max: Math.max(...durations),
avg: durations.reduce((a, b) => a + b, 0) / durations.length
});
});
return aggregated;
};
const clearMetrics = () => {
metrics.timings.clear();
metrics.marks.clear();
metrics.measures = [];
metrics.resources = [];
metrics.webVitals = {
LCP: null,
CLS: 0,
FID: null,
INP: null,
FCP: null,
TTFB: null
};
try {
localStorage.removeItem(PerformanceConfig.storageKey);
} catch (e) {}
if ("undefined" != typeof performance && performance.clearMarks) {
try {
performance.clearMarks();
performance.clearMeasures();
} catch (e) {}
}
};
const monitorMutations = (element, name) => {
if (!PerformanceConfig.enabled) {
return null;
}
const coordinator = window.YouTubeMutationCoordinator;
if (!coordinator?.watchTarget) {
return null;
}
let mutationCount = 0;
const startTime = Date.now();
const subId = `performance::monitorMutations::${name}::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`;
coordinator.watchTarget(subId, element, mutations => {
mutationCount += mutations.length;
recordMetric(`${name}-mutations`, mutationCount, {
elapsed: Date.now() - startTime
});
}, {
childList: !0,
subtree: !0
});
return {
disconnect: () => coordinator.unwatch(subId),
takeRecords: () => []
};
};
const getPerformanceEntries = type => {
if ("undefined" == typeof performance || !performance.getEntriesByType) {
return [];
}
try {
return performance.getEntriesByType(type);
} catch (e) {
return [];
}
};
const logPageLoadMetrics = () => {
if (PerformanceConfig.enabled) {
try {
const navigation = getPerformanceEntries("navigation")[0];
if (navigation) {
recordMetric("page-load-time", navigation.loadEventEnd - navigation.fetchStart);
recordMetric("dom-content-loaded", navigation.domContentLoadedEventEnd);
recordMetric("dom-interactive", navigation.domInteractive);
}
} catch (e) {
window.console.warn("[YouTube+ Perf] Failed to log page metrics:", e);
}
}
};
if ("undefined" != typeof window) {
"complete" === document.readyState ? logPageLoadMetrics() : window.addEventListener("load", logPageLoadMetrics, {
once: !0
});
PerformanceConfig.enabled && (() => {
if ("undefined" != typeof PerformanceObserver) {
try {
new PerformanceObserver(entryList => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
metrics.webVitals.LCP = lastEntry.startTime;
PerformanceConfig.enableconsoleOutput && window.console.warn(`[YouTube+ Perf] LCP: ${lastEntry.startTime.toFixed(2)}ms`, lastEntry);
}).observe({
type: "largest-contentful-paint",
buffered: !0
});
new PerformanceObserver(entryList => {
for (const entry of entryList.getEntries()) {
const layoutShiftEntry = entry;
layoutShiftEntry.hadRecentInput || (metrics.webVitals.CLS += layoutShiftEntry.value || 0);
}
PerformanceConfig.enableconsoleOutput && "debug" === PerformanceConfig.logLevel && window.console.warn(`[YouTube+ Perf] CLS: ${metrics.webVitals.CLS.toFixed(4)}`);
}).observe({
type: "layout-shift",
buffered: !0
});
new PerformanceObserver(entryList => {
const firstInput = entryList.getEntries()[0];
metrics.webVitals.FID = (firstInput.processingStart || 0) - (firstInput.startTime || 0);
PerformanceConfig.enableconsoleOutput && window.console.warn(`[YouTube+ Perf] FID: ${metrics.webVitals.FID.toFixed(2)}ms`);
}).observe({
type: "first-input",
buffered: !0
});
try {
new PerformanceObserver(entryList => {
const entries = entryList.getEntries();
const maxDuration = Math.max(...entries.map(e => e.duration));
metrics.webVitals.INP = maxDuration;
}).observe({
type: "event",
buffered: !0,
durationThreshold: 16
});
} catch (e) {}
} catch (e) {
window.console.warn("[YouTube+ Perf] Failed to init PerformanceObserver:", e);
}
}
})();
const RAFScheduler = (() => {
let rafId = null;
const callbacks = new Set;
const flush = () => {
rafId = null;
Array.from(callbacks).forEach(cb => {
try {
cb();
} catch (e) {
window.console.error("[RAF] Error:", e);
}
});
callbacks.clear();
};
return {
schedule: callback => {
callbacks.add(callback);
rafId || (rafId = requestAnimationFrame(flush));
return () => callbacks.delete(callback);
},
cancelAll: () => {
rafId && cancelAnimationFrame(rafId);
rafId = null;
callbacks.clear();
}
};
})();
const LazyLoader = (() => {
const observers = new Map;
return {
create: (options = {}) => {
const {root = null, rootMargin = "50px", threshold = .01, onIntersect} = options;
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
onIntersect(entry.target, entry);
observer.unobserve(entry.target);
}
});
}, {
root,
rootMargin,
threshold
});
observers.set(observer, new Set);
return {
observe: el => {
if (el instanceof Element) {
observer.observe(el);
observers.get(observer)?.add(el);
}
},
unobserve: el => {
if (el instanceof Element) {
observer.unobserve(el);
observers.get(observer)?.delete(el);
}
},
disconnect: () => {
observer.disconnect();
observers.delete(observer);
}
};
},
disconnectAll: () => {
observers.forEach((_, o) => o.disconnect());
observers.clear();
}
};
})();
const DOMBatcher = (() => {
const batches = new Map;
return {
batch: (container, elements) => {
batches.has(container) || batches.set(container, []);
batches.get(container)?.push(...elements);
},
flush: () => {
RAFScheduler.schedule(() => {
batches.forEach((elements, container) => {
if (!container.isConnected) {
batches.delete(container);
return;
}
const frag = document.createDocumentFragment();
elements.forEach(el => frag.appendChild(el));
container.appendChild(frag);
});
batches.clear();
});
},
clear: container => batches.delete(container)
};
})();
const ElementCache = (() => {
const cache = new WeakMap;
return {
get: (el, key) => cache.get(el)?.[key],
set: (el, key, val) => {
let data = cache.get(el);
if (!data) {
data = {};
cache.set(el, data);
}
data[key] = val;
},
has: (el, key) => {
const data = cache.get(el);
return !!data && key in data;
},
delete: (el, key) => {
const data = cache.get(el);
data && delete data[key];
}
};
})();
window.YouTubePerformance = {
mark,
measure,
timeFunction,
timeAsyncFunction,
recordMetric,
getStats,
exportMetrics,
exportToFile,
clearMetrics,
monitorMutations,
getPerformanceEntries,
getMemoryUsage,
trackMemory,
checkThresholds,
aggregateByPeriod,
config: PerformanceConfig,
RAFScheduler,
LazyLoader,
DOMBatcher,
ElementCache
};
const yieldToMain = () => new Promise(resolve => {
"scheduler" in window && "function" == typeof window.scheduler?.yield ? window.scheduler.yield().then(resolve) : setTimeout(resolve, 0);
});
const runChunkedTasks = async (tasks, yieldInterval = 50) => {
let lastYield = performance.now();
for (const task of tasks) {
task();
const now = performance.now();
if (now - lastYield > yieldInterval) {
await yieldToMain();
lastYield = performance.now();
}
}
};
const wrapForINP = (handler, options = {}) => {
const {maxBlockTime = 50} = options;
return async function(...args) {
const start = performance.now();
let result;
try {
result = handler.apply(this, args);
result && "function" == typeof result.then && (result = await result);
} finally {
const elapsed = performance.now() - start;
elapsed > maxBlockTime && recordMetric("long-task", elapsed, {
handler: handler.name || "anonymous"
});
}
return result;
};
};
window.YouTubePerformance.yieldToMain = yieldToMain;
window.YouTubePerformance.runChunkedTasks = runChunkedTasks;
window.YouTubePerformance.wrapForINP = wrapForINP;
const injectResourceHints = () => {
const origins = [ "https://www.youtube.com", "https://i.ytimg.com", "https://yt3.ggpht.com", "https://fonts.googleapis.com", "https://www.gstatic.com", "https://play.google.com" ];
const head = document.head;
if (!head) {
return;
}
const existingHrefs = new Set;
head.querySelectorAll('link[rel="preconnect"]').forEach(el => {
existingHrefs.add(el.href);
});
for (const origin of origins) {
if (existingHrefs.has(origin) || existingHrefs.has(origin + "/")) {
continue;
}
const link = document.createElement("link");
link.rel = "preconnect";
link.href = origin;
link.crossOrigin = "anonymous";
head.appendChild(link);
}
};
const boostLCPElement = () => {
const path = location.pathname;
let lcpSelector;
lcpSelector = "/watch" === path || path.startsWith("/shorts/") ? "#movie_player .ytp-cued-thumbnail-overlay-image, #movie_player video, ytd-player #ytd-player .html5-video-container" : "/playlist" === path ? "ytd-playlist-video-renderer:first-child img.yt-core-image" : "ytd-rich-item-renderer:first-child img.yt-core-image, ytd-rich-grid-media img.yt-core-image";
lcpSelector && requestAnimationFrame(() => {
const el = qs(lcpSelector);
if (el && "IMG" === el.tagName) {
el.setAttribute("fetchpriority", "high");
el.setAttribute("loading", "eager");
"lazy" === el.loading && (el.loading = "eager");
}
});
};
const injectContentVisibilityCSS = () => {
const cssId = "ytp-perf-content-visibility";
if (byId(cssId)) {
return;
}
const style = document.createElement("style");
style.id = cssId;
style.textContent = '\n        /* ── YouTube+ LCP Performance Optimizations ── */\n\n        /* Off-screen section rendering deferral */\n        ytd-comments#comments { content-visibility: auto; contain-intrinsic-size: auto 800px; }\n        #secondary ytd-compact-video-renderer:nth-child(n+6) { content-visibility: auto; contain-intrinsic-size: auto 94px; }\n        ytd-watch-next-secondary-results-renderer ytd-item-section-renderer { content-visibility: auto; contain-intrinsic-size: auto 600px; }\n\n        /* Main/browse feed - defer items below first viewport */\n        ytd-rich-grid-renderer #contents > ytd-rich-item-renderer:nth-child(n+9) { content-visibility: auto; contain-intrinsic-size: auto 360px; }\n        ytd-section-list-renderer > #contents > ytd-item-section-renderer:nth-child(n+3) { content-visibility: auto; contain-intrinsic-size: auto 500px; }\n\n        /* Playlist page - defer items beyond visible viewport */\n        ytd-playlist-video-list-renderer #contents > ytd-playlist-video-renderer:nth-child(n+12) { content-visibility: auto; contain-intrinsic-size: auto 90px; }\n\n        /* Note: contain:layout is intentionally omitted here — it breaks position:sticky\n           for chips-wrapper and tabs-container on browse/channel pages. */\n\n        /* Guide sidebar - not needed for LCP */\n        ytd-mini-guide-renderer { content-visibility: auto; contain-intrinsic-size: auto 100vh; }\n        tp-yt-app-drawer#guide { content-visibility: auto; contain-intrinsic-size: 240px 100vh; }\n\n        /* Below-the-fold metadata */\n        ytd-watch-metadata #description { content-visibility: auto; contain-intrinsic-size: auto 120px; }\n        ytd-structured-description-content-renderer { content-visibility: auto; contain-intrinsic-size: auto 200px; }\n\n        /* Shorts shelf on browse pages */\n        ytd-reel-shelf-renderer { content-visibility: auto; contain-intrinsic-size: auto 320px; }\n\n        /* Comments container on main watch - contain:style only, not layout (preserves sticky) */\n        ytd-item-section-renderer#sections { contain: style; }\n\n        /* Reduce paint complexity for non-visible items */\n        ytd-rich-grid-row:nth-child(n+4) { content-visibility: auto; contain-intrinsic-size: auto 240px; }\n\n        /* Engagement panels - safe deferral only when fully hidden */\n        ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"] { content-visibility: auto; contain-intrinsic-size: auto 0px; }\n\n        /* Optimize image decoding */\n        ytd-thumbnail img, yt-image img, .yt-core-image { content-visibility: auto; }\n      ';
(document.head || document.documentElement).appendChild(style);
};
const setupDeferredImageLoading = () => {
const imgObserver = new IntersectionObserver(entries => {
for (const entry of entries) {
if (entry.isIntersecting) {
const img = entry.target;
const dataSrc = img.getAttribute("data-ytp-deferred-src");
if (dataSrc) {
img.src = dataSrc;
img.removeAttribute("data-ytp-deferred-src");
}
imgObserver.unobserve(img);
}
}
}, {
rootMargin: "200px 0px"
});
let imgTimer = null;
const scheduleObserve = () => {
imgTimer || (imgTimer = setTimeout(() => {
imgTimer = null;
(() => {
const belowFold = qsAll("ytd-rich-item-renderer:nth-child(n+5) img[src]:not([data-ytp-img-observed]),ytd-compact-video-renderer:nth-child(n+4) img[src]:not([data-ytp-img-observed])");
belowFold.forEach(img => {
img.setAttribute("data-ytp-img-observed", "1");
});
})();
}, 500));
};
const _pCm = window.YouTubeUtils?.cleanupManager;
_pCm?.registerListener ? _pCm.registerListener(window, "yt-navigate-finish", scheduleObserve, {
passive: !0
}) : window.addEventListener("yt-navigate-finish", scheduleObserve, {
passive: !0
});
"loading" !== document.readyState ? scheduleObserve() : document.addEventListener("DOMContentLoaded", scheduleObserve, {
once: !0
});
};
const SharedMutationManager = (() => {
let observerSubId = null;
const callbacks = new Map;
let scheduled = !1;
const pending = [];
const flush = () => {
scheduled = !1;
const entries = [ ...pending ];
pending.length = 0;
for (const [, {callback, filter}] of callbacks) {
const filtered = filter ? entries.filter(filter) : entries;
if (filtered.length > 0) {
try {
callback(filtered);
} catch (e) {
window.console.warn("[YouTube+ Perf] SharedMutation callback error:", e);
}
}
}
};
return {
register(key, callback, filter) {
callbacks.set(key, {
callback,
filter
});
1 === callbacks.size && (() => {
if (observerSubId) {
return;
}
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
observerSubId = "performance::sharedMutation";
coordinator.subscribeRoot(observerSubId, mutations => {
pending.push(...mutations);
if (!scheduled) {
scheduled = !0;
queueMicrotask(flush);
}
}, {
childList: !0,
attributes: !1,
subtree: !0
});
}
})();
},
unregister(key) {
callbacks.delete(key);
if (0 === callbacks.size && observerSubId) {
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.unsubscribe?.(observerSubId);
observerSubId = null;
}
},
getCallbackCount: () => callbacks.size
};
})();
const IdleScheduler = (() => {
const queue = [];
let running = !1;
const processQueue = deadline => {
for (;queue.length > 0 && (!deadline || deadline.timeRemaining() > 5); ) {
const task = queue.shift();
if (task) {
try {
task.fn();
} catch (e) {
window.console.warn("[YouTube+ Perf] Idle task error:", e);
}
if (!deadline) {
break;
}
}
}
queue.length > 0 ? scheduleNext() : running = !1;
};
const scheduleNext = () => {
"function" == typeof requestIdleCallback ? requestIdleCallback(processQueue, {
timeout: 3e3
}) : setTimeout(() => processQueue(null), 50);
};
return {
schedule(fn, priority = 0) {
queue.push({
fn,
priority
});
queue.sort((a, b) => b.priority - a.priority);
if (!running) {
running = !0;
scheduleNext();
}
},
pending: () => queue.length
};
})();
const initLongTaskMonitor = () => {
if ("undefined" != typeof PerformanceObserver && (!Array.isArray(PerformanceObserver.supportedEntryTypes) || PerformanceObserver.supportedEntryTypes.includes("longtask"))) {
try {
const longTasks = [];
new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
longTasks.push({
duration: entry.duration,
startTime: entry.startTime,
name: entry.name
});
longTasks.length > 50 && longTasks.shift();
}
recordMetric("long-tasks-count", longTasks.length);
const totalBlocking = longTasks.reduce((sum, t) => sum + Math.max(0, t.duration - 50), 0);
recordMetric("total-blocking-time", totalBlocking);
}).observe({
type: "longtask",
buffered: !0
});
} catch (e) {}
}
};
const initNavigationTracking = () => {
const _pCm2 = window.YouTubeUtils?.cleanupManager;
const _addL = (t, ev, fn, o) => {
_pCm2?.registerListener ? _pCm2.registerListener(t, ev, fn, o) : t.addEventListener(ev, fn, o);
};
_addL(window, "yt-navigate-start", () => {
mark("yt-navigate-start");
}, {
passive: !0
});
_addL(window, "yt-navigate-finish", () => {
mark("yt-navigate-finish");
measure("yt-navigation-duration", "yt-navigate-start", void 0);
requestAnimationFrame(() => {
boostLCPElement();
});
}, {
passive: !0
});
};
const initLCPOptimizations = () => {
try {
injectResourceHints();
injectContentVisibilityCSS();
boostLCPElement();
queueMicrotask(() => {
initNavigationTracking();
initLongTaskMonitor();
});
IdleScheduler.schedule(() => setupDeferredImageLoading(), 2);
} catch (e) {
window.console.warn("[YouTube+ Perf] LCP optimization init error:", e);
}
};
initLCPOptimizations();
window.YouTubePerformance.SharedMutationManager = SharedMutationManager;
window.YouTubePerformance.IdleScheduler = IdleScheduler;
window.YouTubePerformance.boostLCPElement = boostLCPElement;
window.YouTubePerformance.injectResourceHints = injectResourceHints;
window.YouTubeUtils?.logger?.debug?.("[YouTube+] Performance monitoring initialized");
}
})();

!(function() {
"use strict";
const OptimizedSelectors = {
player: "#movie_player",
video: "video.video-stream.html5-main-video",
videoAlt: "#movie_player video",
chromeBottom: ".ytp-chrome-bottom",
watchFlexy: "ytd-watch-flexy",
secondary: "#secondary",
rightTabs: "#right-tabs",
playlistPanel: "ytd-playlist-panel-renderer",
tabInfo: "#tab-info",
tabComments: "#tab-comments",
tabVideos: "#tab-videos",
likeButton: "like-button-view-model button",
dislikeButton: "dislike-button-view-model button",
subscribeButton: "#subscribe-button",
shorts: "ytd-shorts",
activeReel: "ytd-reel-video-renderer[is-active]",
masthead: "ytd-masthead",
ytdApp: "ytd-app"
};
function batchQuery(queries) {
return queries.map(({selector, multi = !1, context = document}) => multi ? Array.from(context.querySelectorAll(selector)) : context.querySelector(selector));
}
const globalCache = new class DOMCache {
constructor() {
this.cache = new Map;
this.multiCache = new Map;
this.maxAge = 5e3;
this.nullMaxAge = 1e3;
this.maxSize = 500;
this.cleanupSubId = null;
this.cleanupPending = !1;
this.enabled = !0;
this.stats = {
hits: 0,
misses: 0,
evictions: 0
};
this.contextUids = new WeakMap;
this.uidCounter = 0;
this.observerCallbacks = new Set;
this.sharedObserverSubId = null;
this.sharedObserverPending = !1;
this.startCleanup();
}
getContextUid(ctx) {
if (ctx === document) {
return "doc";
}
let uid = this.contextUids.get(ctx);
if (!uid) {
uid = ++this.uidCounter;
this.contextUids.set(ctx, uid);
}
return uid;
}
querySelector(selector, context = document, skipCache = !1) {
if (!this.enabled || skipCache) {
return context.querySelector(selector);
}
const cacheKey = `${selector}::${this.getContextUid(context)}`;
const cached = this.cache.get(cacheKey);
const now = Date.now();
const ttl = cached && cached.element ? this.maxAge : this.nullMaxAge;
if (cached && now - cached.timestamp < ttl) {
if (!cached.element) {
this.stats.hits++;
return null;
}
if (this.isElementInDOM(cached.element)) {
this.stats.hits++;
return cached.element;
}
}
this.stats.misses++;
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
void 0 !== firstKey && this.cache.delete(firstKey);
this.stats.evictions++;
}
const element = context.querySelector(selector);
this.cache.set(cacheKey, {
element,
timestamp: now
});
return element;
}
querySelectorAll(selector, context = document, skipCache = !1) {
if (!this.enabled || skipCache) {
return Array.from(context.querySelectorAll(selector));
}
const cacheKey = `ALL::${selector}::${this.getContextUid(context)}`;
const cached = this.multiCache.get(cacheKey);
if (cached && this.areElementsValid(cached)) {
return cached;
}
const elements = Array.from(context.querySelectorAll(selector));
this.multiCache.set(cacheKey, elements);
const ttl = elements.length > 0 ? this.maxAge : this.nullMaxAge;
const timeoutId = setTimeout(() => this.multiCache.delete(cacheKey), ttl);
"undefined" != typeof window && window.YouTubeUtils?.cleanupManager?.registerTimeout && window.YouTubeUtils.cleanupManager.registerTimeout(timeoutId);
return elements;
}
getElementById(id) {
if (!this.enabled) {
return document.getElementById(id);
}
const cacheKey = `ID::${id}`;
const cached = this.cache.get(cacheKey);
const now = Date.now();
if (cached && now - cached.timestamp < this.maxAge && cached.element && this.isElementInDOM(cached.element)) {
return cached.element;
}
const element = document.getElementById(id);
this.cache.set(cacheKey, {
element,
timestamp: now
});
return element;
}
isElementInDOM(element) {
return element && document.contains(element);
}
areElementsValid(elements) {
return !elements || 0 === elements.length || this.isElementInDOM(elements[0]) && this.isElementInDOM(elements[elements.length - 1]);
}
invalidate(selector) {
if (selector) {
for (const key of this.cache.keys()) {
key.includes(selector) && this.cache.delete(key);
}
for (const key of this.multiCache.keys()) {
key.includes(selector) && this.multiCache.delete(key);
}
} else {
this.cache.clear();
this.multiCache.clear();
}
}
startCleanup() {
if (this.cleanupSubId) {
return;
}
const cleanupFn = () => {
const now = Date.now();
let deletedCount = 0;
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp > this.maxAge || value.element && !this.isElementInDOM(value.element)) {
this.cache.delete(key);
deletedCount++;
if (deletedCount >= 50) {
break;
}
}
}
};
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
this.cleanupSubId = coordinator.subscribeRoot("dom-cache::cleanup", () => {
if (this.cleanupPending) {
return;
}
this.cleanupPending = !0;
const run = () => {
this.cleanupPending = !1;
cleanupFn();
};
"function" == typeof requestIdleCallback ? requestIdleCallback(run, {
timeout: 1e3
}) : setTimeout(run, 0);
}, {
childList: !0,
attributes: !0,
subtree: !0
});
this.cleanupSubId && window.YouTubeUtils?.cleanupManager?.register && window.YouTubeUtils.cleanupManager.register(() => {
if (this.cleanupSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(this.cleanupSubId);
this.cleanupSubId = null;
}
this.cleanupPending = !1;
});
}
}
destroy() {
if (this.cleanupSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(this.cleanupSubId);
this.cleanupSubId = null;
}
this.cleanupPending = !1;
this.cache.clear();
this.multiCache.clear();
if (this.sharedObserverSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(this.sharedObserverSubId);
this.sharedObserverSubId = null;
}
this.observerCallbacks.clear();
}
getStats() {
return {
size: this.cache.size,
multiSize: this.multiCache.size,
enabled: this.enabled
};
}
initSharedObserver() {
if (this.sharedObserverSubId) {
return;
}
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.subscribeRoot && (this.sharedObserverSubId = coordinator.subscribeRoot("dom-cache::waitForElementShared", () => {
if (0 === this.observerCallbacks.size) {
return;
}
if (this.sharedObserverPending) {
return;
}
this.sharedObserverPending = !0;
const flush = () => {
this.sharedObserverPending = !1;
for (const callback of this.observerCallbacks) {
try {
callback();
} catch (e) {
"undefined" != typeof window && window.YouTubeUtils?.logError && window.YouTubeUtils.logError("DOMCache", "Observer callback error", e);
}
}
};
"function" == typeof requestAnimationFrame ? requestAnimationFrame(flush) : setTimeout(flush, 0);
}));
}
};
const scopedCache = new class ScopedDOMCache {
constructor() {
this.scopedCaches = new Map;
}
getScope(scope) {
this.scopedCaches.has(scope) || this.scopedCaches.set(scope, new WeakMap);
return this.scopedCaches.get(scope);
}
set(scope, element, value) {
this.getScope(scope).set(element, value);
}
get(scope, element) {
return this.getScope(scope).get(element);
}
has(scope, element) {
return this.getScope(scope).has(element);
}
};
function waitForElement(selector, timeout = 5e3, context = document) {
return new Promise(resolve => {
const existing = context.querySelector(selector);
if (existing) {
resolve(existing);
return;
}
const isPlaylistPage = "undefined" != typeof window && window.location && "string" == typeof window.location.pathname && "/playlist" === window.location.pathname;
const ctxNode = context;
if (isPlaylistPage && (ctxNode === document || ctxNode === document.body)) {
const interval = 250;
const start = Date.now();
const timerId = setInterval(() => {
const element = context.querySelector(selector);
if (element) {
clearInterval(timerId);
resolve(element);
} else if (Date.now() - start >= timeout) {
clearInterval(timerId);
resolve(null);
}
}, interval);
return;
}
const ctxNodeShared = context;
const useShared = ctxNodeShared === document || ctxNodeShared instanceof Node && ctxNodeShared === document.body;
if (useShared) {
globalCache.initSharedObserver();
const checkCallback = () => {
const element = context.querySelector(selector);
if (element) {
globalCache.observerCallbacks.delete(checkCallback);
resolve(element);
return !0;
}
return !1;
};
globalCache.observerCallbacks.add(checkCallback);
setTimeout(() => {
globalCache.observerCallbacks.delete(checkCallback);
resolve(null);
}, timeout);
}
const retryFactory = window.YouTubeUtils?.createRetryScheduler;
if ("function" == typeof retryFactory) {
retryFactory({
interval: 120,
maxAttempts: Math.max(1, Math.ceil(timeout / 120)),
check: () => {
const element = context.querySelector(selector);
if (element) {
resolve(element);
return !0;
}
return !1;
}
});
setTimeout(() => resolve(null), timeout);
return;
}
const start = Date.now();
const timerId = setInterval(() => {
const element = context.querySelector(selector);
if (element) {
clearInterval(timerId);
resolve(element);
} else if (Date.now() - start >= timeout) {
clearInterval(timerId);
resolve(null);
}
}, 120);
});
}
if ("undefined" != typeof window) {
window.YouTubeDOMCache = Object.assign(globalCache, {
get: selector => globalCache.querySelector(selector),
getAll: selector => globalCache.querySelectorAll(selector),
waitForElement: (selector, timeout) => waitForElement(selector, timeout)
});
window.YouTubeScopedCache = scopedCache;
window.YouTubeSelectors = OptimizedSelectors;
window.batchQueryDOM = batchQuery;
window.waitForElement = waitForElement;
if (window.YouTubeUtils) {
window.YouTubeUtils.domCache = globalCache;
window.YouTubeUtils.scopedCache = scopedCache;
window.YouTubeUtils.selectors = OptimizedSelectors;
window.YouTubeUtils.batchQuery = batchQuery;
window.YouTubeUtils.waitFor = waitForElement;
}
}
if ("undefined" != typeof window && window.addEventListener) {
window.addEventListener("yt-navigate-finish", () => {
globalCache.invalidate();
});
window.addEventListener("spfdone", () => {
globalCache.invalidate();
});
}
"undefined" != typeof window && window.addEventListener && window.addEventListener("beforeunload", () => {
globalCache.destroy();
});
})();

!(function() {
"use strict";
if ("undefined" == typeof window || window.YouTubeMutationCoordinator) {
return;
}
const rootSubscriptions = new Map;
let rootObserver = null;
let rafScheduled = !1;
let pendingMutations = [];
let currentObserveConfig = null;
const configKey = config => config ? JSON.stringify(config) : "null";
const shouldNotifySelector = (selector, mutations) => {
if (!selector) {
return !0;
}
for (const mutation of mutations) {
const target = mutation.target;
if (target instanceof Element && (target.matches(selector) || target.closest(selector))) {
return !0;
}
for (const node of mutation.addedNodes) {
if (node instanceof Element && (node.matches(selector) || node.querySelector(selector))) {
return !0;
}
}
}
return !1;
};
const filterMutationsForSubscription = (sub, batch) => {
const out = [];
for (const mutation of batch) {
if ("attributes" === mutation.type) {
if (!sub.attributes) {
continue;
}
if (sub.attributeFilter && sub.attributeFilter.length > 0 && mutation.attributeName && !sub.attributeFilter.includes(mutation.attributeName)) {
continue;
}
}
("childList" !== mutation.type || sub.childList) && out.push(mutation);
}
return out;
};
const flush = () => {
rafScheduled = !1;
if (0 === pendingMutations.length) {
return;
}
const batch = pendingMutations;
pendingMutations = [];
for (const sub of rootSubscriptions.values()) {
try {
if (!shouldNotifySelector(sub.selector, batch)) {
continue;
}
const filtered = filterMutationsForSubscription(sub, batch);
filtered.length > 0 && sub.callback(filtered);
} catch (e) {
window.console.error("[MutationCoordinator] subscriber failed:", e);
}
}
};
const refreshObserver = () => {
if (0 !== rootSubscriptions.size) {
(nextConfig => {
const target = document.body || document.documentElement;
if (target) {
rootObserver || (rootObserver = new MutationObserver(mutations => {
pendingMutations.push(...mutations);
if (!rafScheduled) {
rafScheduled = !0;
"function" == typeof requestAnimationFrame ? requestAnimationFrame(flush) : setTimeout(flush, 0);
}
}));
if (configKey(currentObserveConfig) !== configKey(nextConfig)) {
rootObserver.disconnect();
rootObserver.observe(target, nextConfig);
currentObserveConfig = nextConfig;
}
}
})((() => {
let childList = !1;
let attributes = !1;
let hasUnlimitedAttributeFilter = !1;
const attrSet = new Set;
for (const sub of rootSubscriptions.values()) {
childList = childList || sub.childList;
attributes = attributes || sub.attributes;
if (sub.attributes) {
if (sub.attributeFilter && 0 !== sub.attributeFilter.length) {
for (const attr of sub.attributeFilter) {
attrSet.add(attr);
}
} else {
hasUnlimitedAttributeFilter = !0;
}
}
}
return {
childList,
subtree: !0,
attributes,
attributeFilter: attributes && !hasUnlimitedAttributeFilter && attrSet.size > 0 ? [ ...attrSet ] : void 0
};
})());
} else {
if (rootObserver) {
rootObserver.disconnect();
rootObserver = null;
}
currentObserveConfig = null;
pendingMutations = [];
rafScheduled = !1;
}
};
const api = {
subscribeRoot(id, callback, options = {}) {
if (!id || "function" != typeof callback) {
return null;
}
rootSubscriptions.set(id, {
id,
callback,
selector: "string" == typeof options.selector ? options.selector : null,
attributes: !0 === options.attributes,
childList: !1 !== options.childList,
subtree: !1 !== options.subtree,
attributeFilter: Array.isArray(options.attributeFilter) ? options.attributeFilter.filter(a => "string" == typeof a && a.length > 0) : null
});
refreshObserver();
return id;
},
unsubscribe(id) {
if (id) {
rootSubscriptions.delete(id);
refreshObserver();
}
},
watchTarget(id, target, callback, options = {}) {
if (!(id && target instanceof Node && "function" == typeof callback)) {
return null;
}
const normalized = {
attributes: !1 !== options.attributes,
childList: !1 !== options.childList,
subtree: !1 !== options.subtree,
attributeFilter: Array.isArray(options.attributeFilter) ? options.attributeFilter.filter(a => "string" == typeof a && a.length > 0) : null
};
return api.subscribeRoot(id, mutations => {
const filtered = mutations.filter(m => ((target, mutation, options) => {
const allowSubtree = !1 !== options.subtree;
if ("attributes" === mutation.type) {
return !!options.attributes && !(options.attributeFilter && options.attributeFilter.length > 0 && mutation.attributeName && !options.attributeFilter.includes(mutation.attributeName)) && (mutation.target === target || !!allowSubtree && target instanceof Element && target.contains(mutation.target));
}
if ("childList" === mutation.type) {
if (!options.childList) {
return !1;
}
if (mutation.target === target) {
return !0;
}
if (!allowSubtree) {
return !1;
}
if (target instanceof Element && target.contains(mutation.target)) {
return !0;
}
for (const node of mutation.addedNodes) {
if (node === target) {
return !0;
}
if (target instanceof Element && node instanceof Element && target.contains(node)) {
return !0;
}
}
for (const node of mutation.removedNodes) {
if (node === target) {
return !0;
}
}
}
return !1;
})(target, m, normalized));
filtered.length > 0 && callback(filtered);
}, {
selector: "string" == typeof options.selector ? options.selector : null,
attributes: normalized.attributes,
childList: normalized.childList,
subtree: !0,
attributeFilter: normalized.attributeFilter
});
},
unwatch(id) {
api.unsubscribe(id);
},
getStats: () => ({
rootSubscribers: rootSubscriptions.size,
rootObserverActive: !!rootObserver
})
};
window.YouTubeMutationCoordinator = api;
})();

!(function() {
"use strict";
class EventDelegator {
constructor() {
this.delegatedHandlers = new Map;
this.registeredDelegators = new Map;
this.stats = {
totalDelegations: 0,
totalHandlers: 0
};
}
delegate(parent, eventType, selector, handler, options = {}) {
if (!(parent && eventType && selector && handler)) {
window.console.warn("[EventDelegator] Invalid parameters");
return;
}
const parentKey = this._getElementKey(parent);
const delegationKey = `${parentKey}:${eventType}`;
this.delegatedHandlers.has(delegationKey) || this.delegatedHandlers.set(delegationKey, new Map);
const handlersForSelector = this.delegatedHandlers.get(delegationKey);
if (!handlersForSelector) {
return;
}
handlersForSelector.has(selector) || handlersForSelector.set(selector, new Set);
handlersForSelector.get(selector)?.add(handler);
this.stats.totalHandlers++;
this.registeredDelegators.has(parent) || this.registeredDelegators.set(parent, new Map);
const parentDelegators = this.registeredDelegators.get(parent);
if (parentDelegators && !parentDelegators.has(eventType)) {
const delegatedListener = event => {
this._handleDelegatedEvent(parent, eventType, event);
};
parent.addEventListener(eventType, delegatedListener, options);
parentDelegators.set(eventType, delegatedListener);
this.stats.totalDelegations++;
window.YouTubeUtils?.logger?.debug?.(`[EventDelegator] Created delegation on ${parentKey} for ${eventType}`);
}
}
undelegate(parent, eventType, selector, handler) {
const parentKey = this._getElementKey(parent);
const delegationKey = `${parentKey}:${eventType}`;
const handlersForSelector = this.delegatedHandlers.get(delegationKey);
if (!handlersForSelector) {
return;
}
const handlers = handlersForSelector.get(selector);
if (handlers) {
handlers.delete(handler);
this.stats.totalHandlers--;
0 === handlers.size && handlersForSelector.delete(selector);
if (0 === handlersForSelector.size) {
this._removeParentListener(parent, eventType);
this.delegatedHandlers.delete(delegationKey);
}
}
}
_handleDelegatedEvent(parent, eventType, event) {
const parentKey = this._getElementKey(parent);
const delegationKey = `${parentKey}:${eventType}`;
const handlersForSelector = this.delegatedHandlers.get(delegationKey);
if (handlersForSelector) {
for (const [selector, handlers] of handlersForSelector.entries()) {
const evtTarget = event.target;
const target = evtTarget?.closest(selector);
if (target && parent.contains(target)) {
for (const handler of handlers) {
try {
handler.call(target, event, target);
} catch (error) {
window.console.error("[EventDelegator] Handler error:", error);
window.YouTubeUtils?.logger?.error?.("[EventDelegator] Handler error", error);
}
}
}
}
}
}
_removeParentListener(parent, eventType) {
const parentDelegators = this.registeredDelegators.get(parent);
if (!parentDelegators) {
return;
}
const listener = parentDelegators.get(eventType);
if (listener) {
parent.removeEventListener(eventType, listener);
parentDelegators.delete(eventType);
this.stats.totalDelegations--;
}
0 === parentDelegators.size && this.registeredDelegators.delete(parent);
}
_getElementKey(element) {
if (element === document) {
return "document";
}
if (element === window) {
return "window";
}
if (element === document.body) {
return "body";
}
if (!this._elementKeyMap) {
this._elementKeyMap = new WeakMap;
this._elementKeyCounter = 0;
}
const htmlEl = element;
if (htmlEl.id) {
return htmlEl.id;
}
const existing = this._elementKeyMap.get(htmlEl);
if (existing) {
return existing;
}
const newKey = `${htmlEl.tagName || "ELEM"}_${this._elementKeyCounter = (this._elementKeyCounter || 0) + 1}`;
this._elementKeyMap.set(htmlEl, newKey);
return newKey;
}
getStats() {
return {
...this.stats,
uniqueDelegations: this.registeredDelegators.size,
delegationKeys: this.delegatedHandlers.size
};
}
clear() {
for (const [parent, delegators] of this.registeredDelegators.entries()) {
for (const eventType of delegators.keys()) {
try {
parent.removeEventListener(eventType, delegators.get(eventType));
} catch (e) {}
}
}
this.delegatedHandlers.clear();
this.registeredDelegators.clear();
if (this._elementKeyMap) {
this._elementKeyMap = new WeakMap;
this._elementKeyCounter = 0;
}
this.stats = {
totalDelegations: 0,
totalHandlers: 0
};
}
}
const eventDelegator = new EventDelegator;
const on = (parent, eventType, selector, handler, options) => {
eventDelegator.delegate(parent, eventType, selector, handler, options);
};
const off = (parent, eventType, selector, handler) => {
eventDelegator.undelegate(parent, eventType, selector, handler);
};
"undefined" != typeof window && (window.YouTubePlusEventDelegation = {
EventDelegator,
on,
off,
delegate: (parent, eventType, selector, handler, options) => eventDelegator.delegate(parent, eventType, selector, handler, options),
undelegate: (parent, eventType, selector, handler) => eventDelegator.undelegate(parent, eventType, selector, handler),
getStats: () => eventDelegator.getStats(),
clear: () => eventDelegator.clear()
});
})();

const mainLog = (() => {
let rawLogger = null;
try {
const utilsLogger = window?.YouTubeUtils?.logger;
if (utilsLogger && "function" == typeof utilsLogger.warn) {
rawLogger = utilsLogger;
} else {
const createLogger = window?.YouTubePlusLogger?.createLogger;
if ("function" == typeof createLogger) {
const logger = createLogger("main");
logger && "function" == typeof logger.warn && (rawLogger = logger);
}
}
} catch (e) {}
return {
debug: (...args) => {
rawLogger?.debug && rawLogger.debug(...args);
},
info: (...args) => {
rawLogger?.info && rawLogger.info(...args);
},
warn: (...args) => {
rawLogger?.warn && rawLogger.warn(...args);
},
error: (...args) => {
rawLogger?.error && rawLogger.error(...args);
}
};
})();

function createHTML(s) {
const safeDomCreate = window?.YouTubeSafeDOM?.createTrustedHTML;
if ("function" == typeof safeDomCreate) {
return safeDomCreate("string" == typeof s ? s : String(s ?? ""));
}
const sharedCreate = window?._ytplusCreateHTML;
return "function" == typeof sharedCreate ? sharedCreate("string" == typeof s ? s : String(s ?? "")) : "string" == typeof s ? s : String(s ?? "");
}

"undefined" != typeof window && "function" != typeof window._ytplusCreateHTML && (window._ytplusCreateHTML = createHTML);

let trustHTMLErr = null;

try {
createHTML("1");
} catch (e) {
trustHTMLErr = e;
}

if (trustHTMLErr) {
mainLog.error("trustHTMLErr", trustHTMLErr);
throw trustHTMLErr;
}

const executionScript = () => {
const mainLog = (() => {
let rawLogger = null;
try {
const utilsLogger = window?.YouTubeUtils?.logger;
if (utilsLogger && "function" == typeof utilsLogger.warn) {
rawLogger = utilsLogger;
} else {
const createLogger = window?.YouTubePlusLogger?.createLogger;
if ("function" == typeof createLogger) {
const logger = createLogger("main");
logger && "function" == typeof logger.warn && (rawLogger = logger);
}
}
} catch (e) {}
return {
debug: (...args) => {
rawLogger?.debug && rawLogger.debug(...args);
},
info: (...args) => {
rawLogger?.info && rawLogger.info(...args);
},
warn: (...args) => {
rawLogger?.warn && rawLogger.warn(...args);
},
error: (...args) => {
rawLogger?.error && rawLogger.error(...args);
}
};
})();
const YouTubeUtils = window.YouTubeUtils || {};
const createHTML = window._ytplusCreateHTML || (s => s);
try {
if ("undefined" == typeof CustomElementRegistry) {
return;
}
if (Reflect.get(CustomElementRegistry.prototype, "define000")) {
return;
}
if ("function" != typeof CustomElementRegistry.prototype.define) {
return;
}
const HTMLElement_ = HTMLElement;
const qsOne = (elm, selector) => window.YouTubeDOMCache && "function" == typeof window.YouTubeDOMCache.querySelector ? window.YouTubeDOMCache.querySelector(selector, elm) : HTMLElement_.prototype.querySelector.call(elm, selector);
const _qs = selector => window.YouTubeDOMCache && "function" == typeof window.YouTubeDOMCache.get ? window.YouTubeDOMCache.get(selector) : document.querySelector(selector);
function qs(a, b) {
return 1 === arguments.length && "string" == typeof a ? _qs(a) : a instanceof Element && "string" == typeof b ? qsOne(a, b) : null;
}
const qsAll = (selector, context) => {
const ctx = context || document;
return window.YouTubeDOMCache && "function" == typeof window.YouTubeDOMCache.getAll ? window.YouTubeDOMCache.getAll(selector) : Array.from(ctx.querySelectorAll(selector));
};
const defineProperties = (p, o) => {
if (p) {
for (const k of Object.keys(o)) {
if (!o[k]) {
mainLog.warn(`defineProperties ERROR: Property ${k} is undefined`);
delete o[k];
}
}
return Object.defineProperties(p, o);
}
mainLog.warn("defineProperties ERROR: Prototype is undefined");
};
const replaceChildrenPolyfill = function replaceChildren(...new_children) {
for (;this.firstChild; ) {
this.removeChild(this.firstChild);
}
(this instanceof Element || this instanceof DocumentFragment) && this.append(...new_children);
};
const fragReplaceChildrenCompat = (frag, ...nodes) => {
const fn = Reflect.get(frag, "replaceChildren000");
"function" != typeof fn ? frag.replaceChildren(...nodes) : fn.apply(frag, nodes);
};
const pdsBaseDF = Object.getOwnPropertyDescriptors(DocumentFragment.prototype);
pdsBaseDF.replaceChildren ? defineProperties(DocumentFragment.prototype, {
replaceChildren000: pdsBaseDF.replaceChildren
}) : Reflect.set(DocumentFragment.prototype, "replaceChildren000", replaceChildrenPolyfill);
const pdsBaseNode = Object.getOwnPropertyDescriptors(Node.prototype);
pdsBaseNode.appendChild000 || pdsBaseNode.insertBefore000 || defineProperties(Node.prototype, {
appendChild000: pdsBaseNode.appendChild,
insertBefore000: pdsBaseNode.insertBefore
});
const pdsBaseElement = Object.getOwnPropertyDescriptors(Element.prototype);
if (!pdsBaseElement.setAttribute000 && !pdsBaseElement.querySelector000) {
const nPdsElement = {
setAttribute000: pdsBaseElement.setAttribute,
getAttribute000: pdsBaseElement.getAttribute,
hasAttribute000: pdsBaseElement.hasAttribute,
removeAttribute000: pdsBaseElement.removeAttribute,
querySelector000: pdsBaseElement.querySelector
};
pdsBaseElement.replaceChildren ? Reflect.set(nPdsElement, "replaceChildren000", pdsBaseElement.replaceChildren) : Reflect.set(Element.prototype, "replaceChildren000", replaceChildrenPolyfill);
defineProperties(Element.prototype, nPdsElement);
}
Element.prototype.setAttribute111 = function(p, v) {
v = `${v}`;
this.getAttribute000(p) !== v && this.setAttribute000(p, v);
};
Element.prototype.incAttribute111 = function(p) {
let v = +(this.getAttribute000(p) ?? "0") || 0;
v = v > 1e9 ? v + 1 : 9;
this.setAttribute000(p, `${v}`);
return v;
};
Element.prototype.assignChildren111 = function(previousSiblings, node, nextSiblings) {
const nodeList = [];
for (let t = this.firstChild; t instanceof Node; t = t.nextSibling) {
t !== node && nodeList.push(t);
}
inPageRearrange = !0;
if (node.parentNode === this) {
const fm = new DocumentFragment;
nodeList.length > 0 && fragReplaceChildrenCompat(fm, ...nodeList);
if (previousSiblings && previousSiblings.length > 0) {
fragReplaceChildrenCompat(fm, ...previousSiblings);
this.insertBefore000(fm, node);
}
if (nextSiblings && nextSiblings.length > 0) {
fragReplaceChildrenCompat(fm, ...nextSiblings);
this.appendChild000(fm);
}
fragReplaceChildrenCompat(fm);
} else {
previousSiblings || (previousSiblings = []);
nextSiblings || (nextSiblings = []);
this.replaceChildren000(...previousSiblings, node, ...nextSiblings);
}
inPageRearrange = !1;
if (nodeList.length > 0) {
for (const t of nodeList) {
t instanceof Element && !1 === t.isConnected && t.remove();
}
}
nodeList.length = 0;
};
let secondaryInnerHold = 0;
const secondaryInnerFn = cb => {
if (secondaryInnerHold) {
secondaryInnerHold++;
let err, r;
try {
r = cb();
} catch (e) {
err = e;
}
secondaryInnerHold--;
if (err) {
throw err;
}
return r;
}
{
const ea = qs("#secondary-inner");
const eb = qs("secondary-wrapper#secondary-inner-wrapper");
if (ea && eb) {
secondaryInnerHold++;
let err, r;
ea.id = "secondary-inner-";
eb.id = "secondary-inner";
try {
r = cb();
} catch (e) {
err = e;
}
ea.id = "secondary-inner";
eb.id = "secondary-inner-wrapper";
secondaryInnerHold--;
if (err) {
throw err;
}
return r;
}
return cb();
}
};
const DISABLE_FLAGS_SHADYDOM_FREE = !0;
(() => {
const e = "undefined" != typeof unsafeWindow ? unsafeWindow : window;
if (!e._ytConfigHacks) {
let t = 4;
class n extends Set {
add(fn) {
if (t <= 0) {
mainLog.warn("yt.config_ is already applied on the page.");
return this;
}
"function" == typeof fn && super.add(fn);
return this;
}
}
let a = globalThis.Promise, i = e._ytConfigHacks = new n, l = () => {
const t = e.ytcsi.originalYtcsi;
if (t) {
e.ytcsi = t;
l = null;
}
}, c = !1, o = () => {
if (t >= 1) {
const n = (e.yt || 0).config_ || (e.ytcfg || 0).data_ || 0;
if ("string" == typeof n.INNERTUBE_API_KEY && "object" == typeof n.EXPERIMENT_FLAGS) {
for (const a of (--t <= 0 && l && l(), c = !0, i)) {
a(n);
}
}
}
}, f = 1, d = target => !!(target = target || e.ytcsi) && (e.ytcsi = new Proxy(target, {
get: (obj, key) => "originalYtcsi" === key ? obj : (o(), c && --f <= 0 && l && l(), 
obj[key])
}), !0);
d() || Object.defineProperty(e, "ytcsi", {
get() {},
set: t => (t && (delete e.ytcsi, d(t)), !0),
enumerable: !1,
configurable: !0
});
const {addEventListener: s, removeEventListener: y} = Document.prototype;
function r(evt) {
o();
evt && e.removeEventListener("DOMContentLoaded", r, !1);
}
new a(done => {
if ("undefined" != typeof AbortSignal) {
s.call(document, "yt-page-data-fetched", done, {
once: !0
}), s.call(document, "yt-navigate-finish", done, {
once: !0
}), s.call(document, "spfdone", done, {
once: !0
});
} else {
const t = () => {
done(), y.call(document, "yt-page-data-fetched", t, !1), y.call(document, "yt-navigate-finish", t, !1), 
y.call(document, "spfdone", t, !1);
};
s.call(document, "yt-page-data-fetched", t, !1), s.call(document, "yt-navigate-finish", t, !1), 
s.call(document, "spfdone", t, !1);
}
}).then(o), new a(done => {
if ("undefined" != typeof AbortSignal) {
s.call(document, "yt-action", done, {
once: !0,
capture: !0
});
} else {
const t = () => {
done();
y.call(document, "yt-action", t, !0);
};
s.call(document, "yt-action", t, !0);
}
}).then(o), a.resolve().then(() => {
"loading" !== document.readyState ? r() : e.addEventListener("DOMContentLoaded", r, !1);
});
}
})();
let configOnce = !1;
window._ytConfigHacks.add(config_ => {
if (configOnce) {
return;
}
configOnce = !0;
const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS || 0;
const EXPERIMENTS_FORCED_FLAGS = config_.EXPERIMENTS_FORCED_FLAGS || 0;
for (const flags of [ EXPERIMENT_FLAGS, EXPERIMENTS_FORCED_FLAGS ]) {
if (flags) {
flags.web_watch_chat_hide_button_killswitch = !1;
flags.web_watch_theater_chat = !1;
flags.suppress_error_204_logging = !0;
flags.kevlar_watch_grid = !1;
if (DISABLE_FLAGS_SHADYDOM_FREE) {
flags.enable_shadydom_free_scoped_node_methods = !1;
flags.enable_shadydom_free_scoped_query_methods = !1;
flags.enable_shadydom_free_scoped_readonly_properties_batch_one = !1;
flags.enable_shadydom_free_parent_node = !1;
flags.enable_shadydom_free_children = !1;
flags.enable_shadydom_free_last_child = !1;
}
}
}
});
const mWeakRef = "function" == typeof WeakRef ? o => o ? new WeakRef(o) : null : o => o || null;
const kRef = wr => wr ? "object" == typeof wr && "deref" in wr && "function" == typeof wr.deref ? wr.deref() || null : wr : null;
const Promise = globalThis.Promise;
const delayPn = delay => new Promise(fn => setTimeout(fn, delay));
const insp = o => o ? o.polymerController || o.inst || o || 0 : o || 0;
const setTimeout_ = setTimeout.bind(window);
const PromiseExternal = (() => {
let resolve_ = () => {};
let reject_ = () => {};
const h = (resolve, reject) => {
resolve_ = resolve;
reject_ = reject;
};
return class PromiseExternal extends Promise {
constructor(cb = h) {
super(cb);
if (cb === h) {
this.resolve = resolve_;
this.reject = reject_;
}
}
};
})();
var nextBrowserTick = void 0 !== nextBrowserTick && nextBrowserTick.version >= 2 ? nextBrowserTick : (() => {
"use strict";
const e = "undefined" != typeof globalThis ? globalThis : window;
let t = !0;
if (!(function n(s) {
return s ? t = !1 : !(!e.postMessage || e.importScripts || !e.addEventListener) && (e.addEventListener("message", n, !1), 
e.postMessage("$$$", "*"), e.removeEventListener("message", n, !1), t);
})()) {
return void mainLog.warn("Your browser environment cannot use nextBrowserTick");
}
const n = Promise;
let s = null;
const o = new Map, {floor: r, random: i} = Math;
let l;
do {
l = `$$nextBrowserTick$$${(i() + 8).toString().slice(2)}$$`;
} while (l in e);
const a = l, c = a.length + 9;
const messageTargetOrigin = "undefined" != typeof location && "string" == typeof location.origin ? location.origin : "*";
e[a] = 1;
e.addEventListener("message", evt => {
if (0 !== o.size) {
const t = (evt || 0).data;
const origin = String((evt || 0).origin || "");
if (origin && "null" !== origin && origin !== location.origin) {
return;
}
if ("string" == typeof t && t.length === c && evt.source === (evt.target || 1)) {
const fn = o.get(t);
if (fn) {
"p" === t[0] && (s = null);
o.delete(t);
fn();
}
}
}
}, !1);
const d = (t = o) => {
if (t === o) {
if (s) {
return s;
}
let token = null;
do {
token = `p${a}${r(314159265359 * i() + 314159265359).toString(36)}`;
} while (token && o.has(token));
s = new n(resolve => {
token && o.set(token, resolve);
});
token && e.postMessage(token, messageTargetOrigin);
token = null;
return s;
}
{
let n;
do {
n = `f${a}${r(314159265359 * i() + 314159265359).toString(36)}`;
} while (o.has(n));
o.set(n, t);
e.postMessage(n, messageTargetOrigin);
return null;
}
};
return d.version = 2, d;
})();
const isPassiveArgSupport = "function" == typeof IntersectionObserver;
const capturePassive = !isPassiveArgSupport || {
capture: !0,
passive: !0
};
class Attributer {
constructor(list) {
this.list = list;
this.flag = 0;
}
makeString() {
let k = 1;
let s = "";
let i = 0;
for (;this.flag >= k; ) {
this.flag & k && (s += this.list[i]);
i++;
k <<= 1;
}
return s;
}
}
const mLoaded = new Attributer("icp");
const wrSelfMap = new WeakMap;
const elements = new Proxy({
related: null,
comments: null,
infoExpander: null,
flexy: null,
chat: null,
playlist: null
}, {
get: (target, prop) => "string" != typeof prop ? null : kRef(target[prop]),
set(target, prop, value) {
if ("string" != typeof prop) {
return !0;
}
const targetObj = target;
if (value) {
let wr = wrSelfMap.get(value);
if (!wr) {
wr = mWeakRef(value);
wrSelfMap.set(value, wr);
}
targetObj[prop] = wr;
} else {
targetObj[prop] = null;
}
return !0;
}
});
const getMainInfo = () => {
const infoExpander = elements.infoExpander;
if (!infoExpander) {
return null;
}
const mainInfo = infoExpander.matches("[tyt-main-info]") ? infoExpander : infoExpander.querySelector000("[tyt-main-info]");
return mainInfo || null;
};
const TABVIEW_PLACEHOLDER_ATTR = "data-ytp-tabview-placeholder";
const getStoredSettings = () => {
try {
return window.youtubePlus?.settings || JSON.parse(localStorage.getItem(window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings") || "{}");
} catch (e) {
return window.youtubePlus?.settings || {};
}
};
const isTabviewEnabled = (settings = null) => {
const resolved = settings || getStoredSettings();
return !1 !== resolved?.enableTabview;
};
const getTabviewPlaceholder = key => qs(`[${TABVIEW_PLACEHOLDER_ATTR}="${key}"]`);
const ensureTabviewPlaceholder = (key, node) => {
if (!(node instanceof Node)) {
return null;
}
const existing = getTabviewPlaceholder(key);
if (existing) {
return existing;
}
const parent = node.parentNode;
if (!(parent instanceof Element)) {
return null;
}
const placeholder = document.createElement("tabview-placeholder");
placeholder.setAttribute111(TABVIEW_PLACEHOLDER_ATTR, key);
placeholder.setAttribute111("hidden", "");
placeholder.setAttribute111("style", "display:none");
inPageRearrange = !0;
parent.insertBefore000(placeholder, node);
inPageRearrange = !1;
return placeholder;
};
const moveNodeToTabview = (key, node, target) => {
if (node instanceof HTMLElement_ && target instanceof HTMLElement_) {
ensureTabviewPlaceholder(key, node);
target.assignChildren111(null, node, null);
}
};
const restoreNodeFromTabview = (key, node) => {
const placeholder = getTabviewPlaceholder(key);
if (!(placeholder instanceof Element && node instanceof HTMLElement_)) {
return !1;
}
const parent = placeholder.parentElement;
if (!(parent instanceof Element)) {
return !1;
}
inPageRearrange = !0;
parent.insertBefore000(node, placeholder);
placeholder.remove();
inPageRearrange = !1;
return !0;
};
"undefined" != typeof window && window.addEventListener("youtube-plus-settings-updated", e => {
const nextEnabled = isTabviewEnabled(e?.detail || null);
nextEnabled ? (function enableTabviewRuntime() {
isTabviewEnabled() && Promise.resolve().then(eventMap.onceInsertRightTabs).then(() => Promise.resolve(lockSet.refreshSecondaryInnerLock).then(eventMap.refreshSecondaryInner)).then(() => Promise.resolve(lockSet.fixInitialTabStateLock).then(eventMap.fixInitialTabStateFn)).catch(mainLog.warn);
})() : (function disableTabviewRuntime() {
const rightTabs = qs("#right-tabs");
const secondaryInner = qs("#secondary-inner.style-scope.ytd-watch-flexy");
const secondaryWrapper = qs("#secondary-inner-wrapper");
roRightTabs.disconnect();
restoreNodeFromTabview("comments", elements.comments);
restoreNodeFromTabview("info", elements.infoExpander);
restoreNodeFromTabview("related", elements.related);
if (rightTabs instanceof Element) {
inPageRearrange = !0;
rightTabs.remove();
inPageRearrange = !1;
}
if (secondaryInner instanceof Element && secondaryWrapper instanceof Element) {
const fragment = document.createDocumentFragment();
for (const child of [ ...secondaryWrapper.childNodes ]) {
fragment.appendChild(child);
}
inPageRearrange = !0;
secondaryInner.insertBefore000(fragment, secondaryWrapper);
secondaryWrapper.remove();
inPageRearrange = !1;
}
const ytdFlexyElm = elements.flexy;
ytdFlexyElm && ytdFlexyElm.setAttribute111("tyt-tab", "");
isRightTabsInserted = !1;
})();
});
let pageType = null;
function getWord(tag) {
try {
if ("undefined" != typeof window && window.YouTubePlusI18n) {
const translation = window.YouTubePlusI18n.t(`tabs.${tag}`);
if (translation && translation !== `tabs.${tag}`) {
return translation;
}
}
const fallbackWords = {
info: "Info",
videos: "Videos",
playlist: "Playlist"
};
return fallbackWords[tag] || tag;
} catch (error) {
mainLog.warn("[YouTube+][Main] Translation error:", error);
const englishWords = {
info: "Info",
videos: "Videos",
playlist: "Playlist"
};
return englishWords[tag] || tag;
}
}
const svgComments = '<path d="M80 27H12A12 12 90 0 0 0 39v42a12 12 90 0 0 12 12h12v20a2 2 90 0 0 3.4 2L47 93h33a12 \n  12 90 0 0 12-12V39a12 12 90 0 0-12-12zM20 47h26a2 2 90 1 1 0 4H20a2 2 90 1 1 0-4zm52 28H20a2 2 90 1 1 0-4h52a2 2 90 \n  1 1 0 4zm0-12H20a2 2 90 1 1 0-4h52a2 2 90 1 1 0 4zm36-58H40a12 12 90 0 0-12 12v6h52c9 0 16 7 16 16v42h0v4l7 7a2 2 90 \n  0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>'.trim();
const svgVideos = '<path d="M89 10c0-4-3-7-7-7H7c-4 0-7 3-7 7v70c0 4 3 7 7 7h75c4 0 7-3 7-7V10zm-62 2h13v10H27V12zm-9 \n  66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25 \n  25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>'.trim();
const svgInfo = '<path d="M30 0C13.3 0 0 13.3 0 30s13.3 30 30 30 30-13.3 30-30S46.7 0 30 0zm6.2 46.6c-1.5.5-2.6 \n  1-3.6 1.3a10.9 10.9 0 0 1-3.3.5c-1.7 0-3.3-.5-4.3-1.4a4.68 4.68 0 0 1-1.6-3.6c0-.4.2-1 .2-1.5a20.9 20.9 90 0 1 \n  .3-2l2-6.8c.1-.7.3-1.3.4-1.9a8.2 8.2 90 0 0 .3-1.6c0-.8-.3-1.4-.7-1.8s-1-.5-2-.5a4.53 4.53 0 0 0-1.6.3c-.5.2-1 \n  .2-1.3.4l.6-2.1c1.2-.5 2.4-1 3.5-1.3s2.3-.6 3.3-.6c1.9 0 3.3.6 4.3 1.3s1.5 2.1 1.5 3.5c0 .3 0 .9-.1 1.6a10.4 10.4 \n  90 0 1-.4 2.2l-1.9 6.7c-.2.5-.2 1.1-.4 1.8s-.2 1.3-.2 1.6c0 .9.2 1.6.6 1.9s1.1.5 2.1.5a6.1 6.1 90 0 0 1.5-.3 9 9 90 \n  0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>'.trim();
const svgPlayList = '<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>'.trim();
const svgElm = (w, h, vw, vh, p, m) => `<svg${m ? ` class=${m}` : ""} width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`;
const hiddenTabsByUserCSS = 0;
const langWords = {
ar: !0,
be: !0,
bg: !0,
cn: !0,
de: !0,
du: !0,
en: !0,
es: !0,
fr: !0,
hi: !0,
id: !0,
it: !0,
jp: !0,
kk: !0,
kr: !0,
ky: !0,
pl: !0,
pt: !0,
ru: !0,
tr: !0,
tw: !0,
uk: !0,
uz: !0,
vi: !0,
ng: !0
};
function getLangForPage() {
!(function getLang() {
try {
if (window.YouTubePlusI18n && "function" == typeof window.YouTubePlusI18n.getLanguage) {
const detected = window.YouTubePlusI18n.getLanguage();
if (detected && langWords[detected]) {
return detected;
}
}
} catch (e) {}
const htmlLang = ((document || 0).documentElement || 0).lang || "";
return {
de: "du",
"de-de": "du",
"de-at": "du",
"de-ch": "du",
fr: "fr",
"fr-fr": "fr",
"fr-ca": "fr",
"fr-be": "fr",
"fr-ch": "fr",
"zh-hant": "tw",
"zh-hant-hk": "tw",
"zh-hant-tw": "tw",
"zh-tw": "tw",
"zh-hk": "tw",
"zh-hans": "cn",
"zh-hans-cn": "cn",
"zh-cn": "cn",
zh: "cn",
"zh-sg": "cn",
ja: "jp",
"ja-jp": "jp",
ko: "kr",
"ko-kr": "kr",
ru: "ru",
"ru-ru": "ru",
uk: "uk",
"uk-ua": "uk",
be: "be",
"be-by": "be",
bg: "bg",
"bg-bg": "bg",
es: "es",
"es-es": "es",
"es-419": "es",
"es-mx": "es",
pt: "pt",
"pt-pt": "pt",
"pt-br": "pt",
it: "it",
"it-it": "it",
pl: "pl",
"pl-pl": "pl",
nl: "du",
"nl-nl": "du",
"nl-be": "du",
ar: "ar",
"ar-sa": "ar",
"ar-ae": "ar",
"ar-eg": "ar",
hi: "hi",
"hi-in": "hi",
id: "id",
"id-id": "id",
ng: "ng",
"en-ng": "ng",
pcm: "ng",
"pcm-ng": "ng",
tr: "tr",
"tr-tr": "tr",
vi: "vi",
"vi-vn": "vi",
uz: "uz",
"uz-uz": "uz",
kk: "kk",
"kk-kz": "kk",
ky: "ky"
}[htmlLang.toLowerCase()] || "en";
})();
}
const _locks = {};
const lockGet = new Proxy(_locks, {
get: (target, prop) => "string" != typeof prop ? 0 : target[prop] || 0,
set: () => !0
});
const lockSet = new Proxy(_locks, {
get(target, prop) {
if ("string" != typeof prop) {
return 0;
}
target[prop] > 1e9 && (target[prop] = 9);
return target[prop] = (target[prop] || 0) + 1;
},
set: () => !0
});
const videosElementProvidedPromise = new PromiseExternal;
const navigateFinishedPromise = new PromiseExternal;
let isRightTabsInserted = !1;
const rightTabsProvidedPromise = new PromiseExternal;
const infoExpanderElementProvidedPromise = new PromiseExternal;
const pluginsDetected = {};
let pluginDetectDebounceTimer = null;
const pluginDetectObserver = new MutationObserver(mutations => {
pluginDetectDebounceTimer || (pluginDetectDebounceTimer = setTimeout(() => {
pluginDetectDebounceTimer = null;
processPluginDetectMutations(mutations);
}, 50));
});
const processPluginDetectMutations = mutations => {
let changeOnRoot = !1;
const newPlugins = [];
const attributeChangedSet = new Set;
for (const mutation of mutations) {
mutation.target === document && (changeOnRoot = !0);
let detected = "";
switch (mutation.attributeName) {
case "data-ytlstm-new-layout":
case "data-ytlstm-overlay-text-shadow":
case "data-ytlstm-theater-mode":
detected = "external.ytlstm";
attributeChangedSet.add(detected);
}
if (detected && !pluginsDetected[detected]) {
pluginsDetected[detected] = !0;
newPlugins.push(detected);
}
}
elements.flexy && attributeChangedSet.has("external.ytlstm") && elements.flexy.setAttribute("tyt-external-ytlstm", qs("[data-ytlstm-theater-mode]") ? "1" : "0");
changeOnRoot && pluginDetectObserver.observe(document.body, {
attributes: !0,
attributeFilter: [ "data-ytlstm-new-layout", "data-ytlstm-overlay-text-shadow", "data-ytlstm-theater-mode" ]
});
for (const detected of newPlugins) {
const pluginItem = plugin[detected];
pluginItem ? pluginItem.activate() : mainLog.warn(`No Plugin Activator for ${detected}`);
}
};
const pluginAttributeFilter = [ "data-ytlstm-new-layout", "data-ytlstm-overlay-text-shadow", "data-ytlstm-theater-mode" ];
pluginDetectObserver.observe(document.documentElement, {
attributes: !0,
attributeFilter: pluginAttributeFilter
});
document.body && pluginDetectObserver.observe(document.body, {
attributes: !0,
attributeFilter: pluginAttributeFilter
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(pluginDetectObserver);
YouTubeUtils?.ObserverRegistry?.track && YouTubeUtils.ObserverRegistry.track();
navigateFinishedPromise.then(() => {
pluginDetectObserver.observe(document.documentElement, {
attributes: !0,
attributeFilter: pluginAttributeFilter
});
document.body && pluginDetectObserver.observe(document.body, {
attributes: !0,
attributeFilter: pluginAttributeFilter
});
});
const funcCanCollapse = function() {
const content = this.content || this.$.content;
this.canToggle = this.shouldUseNumberOfLines && (this.alwaysCollapsed || this.collapsed || !1 === this.isToggled) ? this.alwaysToggleable || this.isToggled || content && content.offsetHeight < content.scrollHeight : this.alwaysToggleable || this.isToggled || content && content.scrollHeight > this.collapsedHeight;
};
const aoChatAttrChangeFn = async lockId => {
if (lockGet.aoChatAttrAsyncLock !== lockId) {
return;
}
const chatElm = elements.chat;
const ytdFlexyElm = elements.flexy;
if (chatElm && ytdFlexyElm) {
const isChatCollapsed = chatElm.hasAttribute000("collapsed");
isChatCollapsed ? ytdFlexyElm.setAttribute111("tyt-chat-collapsed", "") : ytdFlexyElm.removeAttribute000("tyt-chat-collapsed");
ytdFlexyElm.setAttribute111("tyt-chat", isChatCollapsed ? "-" : "+");
}
};
const aoPlayListAttrChangeFn = async lockId => {
if (lockGet.aoPlayListAttrAsyncLock !== lockId) {
return;
}
const playlistElm = elements.playlist;
const ytdFlexyElm = elements.flexy;
let doAttributeChange = 0;
playlistElm && ytdFlexyElm ? doAttributeChange = playlistElm.closest("[hidden]") || playlistElm.hasAttribute000("collapsed") ? 2 : 1 : ytdFlexyElm && (doAttributeChange = 2);
1 === doAttributeChange && ytdFlexyElm ? "" !== ytdFlexyElm.getAttribute000("tyt-playlist-expanded") && ytdFlexyElm.setAttribute111("tyt-playlist-expanded", "") : 2 === doAttributeChange && ytdFlexyElm && ytdFlexyElm.hasAttribute000("tyt-playlist-expanded") && ytdFlexyElm.removeAttribute000("tyt-playlist-expanded");
};
const aoChat = new MutationObserver(() => {
Promise.resolve(lockSet.aoChatAttrAsyncLock).then(aoChatAttrChangeFn).catch(mainLog.warn);
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(aoChat);
YouTubeUtils?.ObserverRegistry?.track && YouTubeUtils.ObserverRegistry.track();
const aoPlayList = new MutationObserver(() => {
Promise.resolve(lockSet.aoPlayListAttrAsyncLock).then(aoPlayListAttrChangeFn).catch(mainLog.warn);
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(aoPlayList);
YouTubeUtils?.ObserverRegistry?.track && YouTubeUtils.ObserverRegistry.track();
let aoCommentThrottleTimer = null;
let aoCommentPendingMutations = [];
const aoComment = new MutationObserver(async mutations => {
aoCommentPendingMutations.push(...mutations);
aoCommentThrottleTimer || (aoCommentThrottleTimer = setTimeout(() => {
aoCommentThrottleTimer = null;
const allMutations = aoCommentPendingMutations;
aoCommentPendingMutations = [];
processCommentMutations(allMutations);
}, 50));
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(aoComment);
YouTubeUtils?.ObserverRegistry?.track && YouTubeUtils.ObserverRegistry.track();
const processCommentMutations = async mutations => {
const commentsArea = elements.comments;
const ytdFlexyElm = elements.flexy;
if (!commentsArea) {
return;
}
let bfHidden = !1;
let bfCommentsVideoId = !1;
let bfCommentDisabled = !1;
for (const mutation of mutations) {
"hidden" === mutation.attributeName && mutation.target === commentsArea ? bfHidden = !0 : "tyt-comments-video-id" === mutation.attributeName && mutation.target === commentsArea ? bfCommentsVideoId = !0 : "tyt-comments-data-status" === mutation.attributeName && mutation.target === commentsArea && (bfCommentDisabled = !0);
}
if (bfHidden) {
commentsArea.hasAttribute000("hidden") || Promise.resolve(commentsArea).then(eventMap.settingCommentsVideoId).catch(mainLog.warn);
Promise.resolve(lockSet.removeKeepCommentsScrollerLock).then(removeKeepCommentsScroller).catch(mainLog.warn);
}
if ((bfHidden || bfCommentsVideoId || bfCommentDisabled) && ytdFlexyElm) {
const commentsDataStatus = +commentsArea.getAttribute000("tyt-comments-data-status");
2 === commentsDataStatus ? ytdFlexyElm.setAttribute111("tyt-comment-disabled", "") : 1 === commentsDataStatus && ytdFlexyElm.removeAttribute000("tyt-comment-disabled");
Promise.resolve(lockSet.checkCommentsShouldBeHiddenLock).then(eventMap.checkCommentsShouldBeHidden).catch(mainLog.warn);
const lockId = lockSet.rightTabReadyLock01;
await rightTabsProvidedPromise.then();
if (lockGet.rightTabReadyLock01 !== lockId) {
return;
}
if (elements.comments !== commentsArea) {
return;
}
if (!1 === commentsArea.isConnected) {
return;
}
if (commentsArea.closest("#tab-comments")) {
const shouldTabVisible = !commentsArea.closest("[hidden]");
const tabCommentsButton = document.querySelector('[tyt-tab-content="#tab-comments"]');
tabCommentsButton && tabCommentsButton.classList.toggle("tab-btn-hidden", !shouldTabVisible);
}
}
};
const ioComment = new IntersectionObserver(entries => {
requestAnimationFrame(() => {
for (const entry of entries) {
const target = entry.target;
const cnt = insp(target);
if (entry.isIntersecting && target instanceof HTMLElement_ && "function" == typeof cnt.calculateCanCollapse) {
cnt.calculateCanCollapse(!0);
target.setAttribute111("io-intersected", "");
const ytdFlexyElm = elements.flexy;
ytdFlexyElm && !ytdFlexyElm.hasAttribute000("keep-comments-scroller") && ytdFlexyElm.setAttribute111("keep-comments-scroller", "");
} else {
target.hasAttribute000("io-intersected") && target.removeAttribute000("io-intersected");
}
}
});
}, {
threshold: [ 0 ],
rootMargin: "100px"
});
let bFixForResizedTabLater = !1;
let lastRoRightTabsWidth = 0;
let resizeDebounceTimer = null;
const roRightTabs = new ResizeObserver(entries => {
resizeDebounceTimer || (resizeDebounceTimer = setTimeout(() => {
resizeDebounceTimer = null;
const entry = entries[entries.length - 1];
const borderBox = Array.isArray(entry.borderBoxSize) ? entry.borderBoxSize[0] : entry.borderBoxSize;
const width = Math.round(borderBox && borderBox.inlineSize || entry.contentRect.width || 0);
if (lastRoRightTabsWidth !== width) {
lastRoRightTabsWidth = width;
if (2 & ~tabAStatus) {
bFixForResizedTabLater = !0;
} else {
bFixForResizedTabLater = !1;
Promise.resolve(1).then(eventMap.fixForTabDisplay);
}
}
}, 100));
});
let cachedTabLinks = null;
const cachedTabContents = new Map;
const switchToTab = activeLink => {
"string" == typeof activeLink && (activeLink = qs(`a[tyt-tab-content="${activeLink}"]`) || null);
const ytdFlexyElm = elements.flexy;
if (!cachedTabLinks || 0 === cachedTabLinks.length || !cachedTabLinks[0].isConnected) {
cachedTabLinks = qsAll("#material-tabs a[tyt-tab-content]");
cachedTabContents.clear();
}
const links = cachedTabLinks;
for (const link of links) {
let content = cachedTabContents.get(link) || null;
if (!content || !content.isConnected) {
content = qs(link.getAttribute000("tyt-tab-content") ?? "");
content && cachedTabContents.set(link, content);
}
if (link && content) {
if (link !== activeLink) {
link.classList.remove("active");
link.setAttribute("aria-selected", "false");
content.classList.add("tab-content-hidden");
content.hasAttribute000("tyt-hidden") || content.setAttribute111("tyt-hidden", "");
} else {
link.classList.add("active");
link.setAttribute("aria-selected", "true");
content.hasAttribute000("tyt-hidden") && content.removeAttribute000("tyt-hidden");
content.classList.remove("tab-content-hidden");
}
}
}
const switchingTo = activeLink ? activeLink.getAttribute000("tyt-tab-content") : "";
switchingTo && (lastTab = lastPanel = switchingTo);
"" === ytdFlexyElm.getAttribute000("tyt-chat") && ytdFlexyElm.removeAttribute000("tyt-chat");
ytdFlexyElm.setAttribute111("tyt-tab", switchingTo);
if (switchingTo) {
bFixForResizedTabLater = !1;
Promise.resolve(0).then(eventMap.fixForTabDisplay);
}
};
let tabAStatus = 0;
const calculationFn = (r = 0, flag) => {
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) {
return r;
}
if (1 & flag) {
r |= 1;
ytdFlexyElm.hasAttribute000("theater") || (r -= 1);
}
if (2 & flag) {
r |= 2;
ytdFlexyElm.getAttribute000("tyt-tab") || (r -= 2);
}
if (4 & flag) {
r |= 4;
"-" !== ytdFlexyElm.getAttribute000("tyt-chat") && (r -= 4);
}
if (8 & flag) {
r |= 8;
"+" !== ytdFlexyElm.getAttribute000("tyt-chat") && (r -= 8);
}
if (16 & flag) {
r |= 16;
ytdFlexyElm.hasAttribute000("is-two-columns_") || (r -= 16);
}
if (32 & flag) {
r |= 32;
ytdFlexyElm.hasAttribute000("tyt-egm-panel_") || (r -= 32);
}
if (64 & flag) {
r |= 64;
document.fullscreenElement || (r -= 64);
}
if (128 & flag) {
r |= 128;
ytdFlexyElm.hasAttribute000("tyt-playlist-expanded") || (r -= 128);
}
if (4096 & flag) {
r |= 4096;
"1" !== ytdFlexyElm.getAttribute("tyt-external-ytlstm") && (r -= 4096);
}
return r;
};
function isTheater() {
const ytdFlexyElm = elements.flexy;
return ytdFlexyElm && ytdFlexyElm.hasAttribute000("theater");
}
function isZenTheaterOverlayActive() {
try {
const raw = localStorage.getItem(window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings");
if (!raw) {
return !0;
}
const s = JSON.parse(raw);
return !1 !== s?.enableZenStyles && !1 !== s?.zenStyles?.theaterEnhancements;
} catch (e) {
return !0;
}
}
function ytBtnCancelTheater() {
if (!isZenTheaterOverlayActive() && isTheater()) {
const sizeBtn = qs("ytd-watch-flexy #ytd-player button.ytp-size-button");
sizeBtn && sizeBtn.click();
}
}
function getSuitableElement(selector) {
const elements = qsAll(selector);
let j = -1, h = -1;
for (let i = 0, l = elements.length; i < l; i++) {
const d = elements[i].getElementsByTagName("*").length;
if (d > h) {
h = d;
j = i;
}
}
return j >= 0 ? elements[j] : null;
}
function ytBtnExpandChat() {
const dom = getSuitableElement("ytd-live-chat-frame#chat");
const cnt = insp(dom);
if (cnt && "boolean" == typeof cnt.collapsed) {
if ("function" == typeof cnt.setCollapsedState) {
cnt.setCollapsedState({
setLiveChatCollapsedStateAction: {
collapsed: !1
}
});
if (!1 === cnt.collapsed) {
return;
}
}
cnt.collapsed = !1;
if (!1 === cnt.collapsed) {
return;
}
if (!0 === cnt.isHiddenByUser && !0 === cnt.collapsed) {
cnt.isHiddenByUser = !1;
cnt.collapsed = !1;
}
}
let button = qs("ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button");
if (button) {
button = button.querySelector000("div.yt-spec-touch-feedback-shape") || button.querySelector000("ytd-toggle-button-renderer");
button && button.click();
}
}
function ytBtnCollapseChat() {
if (isZenTheaterOverlayActive() && isTheater()) {
return;
}
const dom = getSuitableElement("ytd-live-chat-frame#chat");
const cnt = insp(dom);
if (cnt && "boolean" == typeof cnt.collapsed) {
if ("function" == typeof cnt.setCollapsedState) {
cnt.setCollapsedState({
setLiveChatCollapsedStateAction: {
collapsed: !0
}
});
if (!0 === cnt.collapsed) {
return;
}
}
cnt.collapsed = !0;
if (!0 === cnt.collapsed) {
return;
}
if (!1 === cnt.isHiddenByUser && !1 === cnt.collapsed) {
cnt.isHiddenByUser = !0;
cnt.collapsed = !0;
}
}
let button = qs("ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button");
if (button) {
button = button.querySelector000("div.yt-spec-touch-feedback-shape") || button.querySelector000("ytd-toggle-button-renderer");
button && button.click();
}
}
function ytBtnEgmPanelCore(arr) {
if (!arr) {
return;
}
"length" in arr || (arr = [ arr ]);
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) {
return;
}
let actions = [];
for (const entry of arr) {
if (!entry) {
continue;
}
const panelId = entry.panelId;
const toHide = entry.toHide;
const toShow = entry.toShow;
!0 !== toHide || toShow ? !0 !== toShow || toHide || actions.push({
showEngagementPanelEndpoint: {
panelIdentifier: panelId
}
}) : actions.push({
changeEngagementPanelVisibilityAction: {
targetId: panelId,
visibility: "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"
}
});
if (actions.length > 0) {
const cnt = insp(ytdFlexyElm);
cnt.resolveCommand({
signalServiceEndpoint: {
signal: "CLIENT_SIGNAL",
actions
}
}, {}, !1);
}
actions = [];
}
}
function ytBtnCloseEngagementPanels() {
const actions = [];
for (const panelElm of qsAll("ytd-watch-flexy[tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])")) {
"ENGAGEMENT_PANEL_VISIBILITY_EXPANDED" !== panelElm.getAttribute("visibility") || panelElm.closest("[hidden]") || actions.push({
panelId: panelElm.getAttribute000("target-id"),
toHide: !0
});
}
ytBtnEgmPanelCore(actions);
}
function ytBtnClosePlaylist() {
const cnt = insp(elements.playlist);
cnt && "boolean" == typeof cnt.collapsed && (cnt.collapsed = !0);
}
const updateChatLocation498 = function() {
"ytd-watch-grid" !== this.is && secondaryInnerFn(() => {
this.updatePageMediaQueries();
this.schedulePlayerSizeUpdate_();
});
};
const mirrorNodeWS = new WeakMap;
const dummyNode = document.createElement("noscript");
const __j4836__ = Symbol();
const __j5744__ = Symbol();
const __j5733__ = Symbol();
const monitorDataChangedByDOMMutation = async function() {
const node = kRef(this);
if (!(node instanceof Element)) {
return;
}
const cnt = insp(node);
const __lastChanged__ = cnt[__j5733__];
const val = cnt.data ? cnt.data[__j4836__] || 1 : 0;
if (__lastChanged__ !== val) {
cnt[__j5733__] = val > 0 ? cnt.data[__j4836__] = Date.now() : 0;
await Promise.resolve();
attributeInc(node, "tyt-data-change-counter");
}
};
const moChangeReflection = function(mutations) {
const node = kRef(this);
if (!(node instanceof Element)) {
return;
}
const originElement = kRef(node[__j5744__] || null) || null;
if (!(originElement instanceof Element)) {
return;
}
const cnt = insp(node);
const oriCnt = insp(originElement);
if (mutations) {
let bfDataChangeCounter = !1;
for (const mutation of mutations) {
("tyt-clone-refresh-count" === mutation.attributeName && mutation.target === originElement || "tyt-data-change-counter" === mutation.attributeName && mutation.target === originElement) && (bfDataChangeCounter = !0);
}
if (bfDataChangeCounter && oriCnt.data) {
node.replaceWith(dummyNode);
cnt.data = Object.assign({}, oriCnt.data);
dummyNode.replaceWith(node);
}
}
};
const attributeInc = (elm, prop) => {
let v = (+(elm.getAttribute000(prop) ?? "0") || 0) + 1;
v > 1e9 && (v = 9);
elm.setAttribute000(prop, `${v}`);
return v;
};
const isChannelId = x => "string" == typeof x && 24 === x.length && /UC[-_a-zA-Z0-9+=.]{22}/.test(x);
const infoFix = lockId => {
if (null !== lockId && lockGet.infoFixLock !== lockId) {
return;
}
const infoExpander = elements.infoExpander;
const infoContainer = (infoExpander ? infoExpander.parentElement : null) || qs("#tab-info");
const ytdFlexyElm = elements.flexy;
if (!infoContainer || !ytdFlexyElm) {
return;
}
if (infoExpander) {
const match = infoExpander.matches("#tab-info > [class]") || infoExpander.matches("#tab-info > [tyt-main-info]");
if (!match) {
return;
}
}
const requireElements = [ ...qsAll('ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"] > *, ytd-watch-metadata.ytd-watch-flexy #extra-content > *') ].filter(elm => "string" == typeof elm.is).map(elm => {
const is = elm.is;
let current = elm;
for (;current instanceof HTMLElement_; ) {
const q = [ ...current.querySelectorAll(is) ].filter(e => insp(e).data);
if (q.length >= 1) {
return q[0];
}
current = current.parentElement;
}
}).filter(elm => !!elm && "string" == typeof elm.is);
const source = requireElements.map(entry => {
const inst = insp(entry);
return {
data: inst.data,
tag: inst.is,
elm: entry
};
});
let noscript_ = qs("noscript#aythl");
if (!noscript_) {
const createdNoscript = document.createElement("noscript");
createdNoscript.id = "aythl";
inPageRearrange = !0;
ytdFlexyElm.insertBefore000(createdNoscript, ytdFlexyElm.firstChild);
inPageRearrange = !1;
noscript_ = createdNoscript;
}
if (!noscript_) {
return;
}
const noscript = noscript_;
let requiredUpdate = !1;
const mirrorElmSet = new Set;
const targetParent = infoContainer;
for (const {data, tag, elm: s} of source) {
let mirrorNode = mirrorNodeWS.get(s);
mirrorNode = mirrorNode ? kRef(mirrorNode) : mirrorNode;
if (mirrorNode) {
mirrorNode.parentNode !== targetParent && (requiredUpdate = !0);
} else {
const cnt = insp(s);
const cProto = cnt.constructor.prototype;
const element = document.createElement(tag);
noscript.appendChild(element);
mirrorNode = element;
mirrorNode[__j5744__] = mWeakRef(s);
const nodeWR = mWeakRef(mirrorNode);
new MutationObserver(moChangeReflection.bind(nodeWR)).observe(s, {
attributes: !0,
attributeFilter: [ "tyt-clone-refresh-count", "tyt-data-change-counter" ]
});
s.jy8432 = 1;
if (cProto instanceof Node || cProto._dataChanged496 || "function" != typeof cProto._createPropertyObserver) {
if (!(cProto instanceof Node) && !cProto._dataChanged496 && !0 === cProto.useSignals && insp(s).signalProxy) {
const dataSignal = cnt?.signalProxy?.signalCache?.data;
if (dataSignal && "function" == typeof dataSignal.setWithPath && !dataSignal.setWithPath573 && !dataSignal.controller573) {
dataSignal.controller573 = mWeakRef(cnt);
dataSignal.setWithPath573 = dataSignal.setWithPath;
dataSignal.setWithPath = function() {
const cnt = kRef(this.controller573 || null) || null;
cnt && "function" == typeof cnt._dataChanged496k && Promise.resolve(cnt).then(cnt._dataChanged496k).catch(mainLog.warn);
return this.setWithPath573(...arguments);
};
cProto._dataChanged496 = function() {
const node = this.hostElement || this;
node.jy8432 && attributeInc(node, "tyt-data-change-counter");
};
cProto._dataChanged496k = cnt => cnt._dataChanged496();
}
}
} else {
cProto._dataChanged496 = function() {
const node = this.hostElement || this;
node.jy8432 && attributeInc(node, "tyt-data-change-counter");
};
cProto._createPropertyObserver("data", "_dataChanged496", void 0);
}
cProto._dataChanged496 || new MutationObserver(monitorDataChangedByDOMMutation.bind(mirrorNode[__j5744__])).observe(s, {
attributes: !0,
childList: !0,
subtree: !0
});
mirrorNodeWS.set(s, nodeWR);
requiredUpdate = !0;
}
if (!requiredUpdate) {
const cloneNodeCnt = insp(mirrorNode);
cloneNodeCnt.data !== data && (requiredUpdate = !0);
}
mirrorElmSet.add(mirrorNode);
}
const mirroElmArr = [ ...mirrorElmSet ];
mirrorElmSet.clear();
if (!requiredUpdate) {
let e = infoExpander ? -1 : 0;
for (let n = targetParent.firstChild; n instanceof Node; n = n.nextSibling) {
const target = e < 0 ? infoExpander : mirroElmArr[e];
e++;
if (n !== target) {
requiredUpdate = !0;
break;
}
}
requiredUpdate || e === mirroElmArr.length + 1 || (requiredUpdate = !0);
}
if (requiredUpdate) {
infoExpander ? targetParent.assignChildren111(null, infoExpander, mirroElmArr) : targetParent.replaceChildren000(...mirroElmArr);
for (const mirrorElm of mirroElmArr) {
const j = attributeInc(mirrorElm, "tyt-clone-refresh-count");
const oriElm = kRef(mirrorElm[__j5744__] || null) || null;
oriElm instanceof Element && oriElm.setAttribute111("tyt-clone-refresh-count", j);
}
}
mirroElmArr.length = 0;
source.length = 0;
};
const layoutFix = lockId => {
if (lockGet.layoutFixLock !== lockId) {
return;
}
const secondaryWrapper = qs("#secondary-inner.style-scope.ytd-watch-flexy > secondary-wrapper");
if (secondaryWrapper) {
const secondaryInner = secondaryWrapper.parentElement;
if (!secondaryInner) {
return;
}
const chatContainer = qs("#columns.style-scope.ytd-watch-flexy [tyt-chat-container]");
const hasExtraNodes = () => {
for (let node = secondaryInner.firstChild; node; node = node.nextSibling) {
if (node !== secondaryWrapper && node !== chatContainer && (3 !== node.nodeType || (node.textContent || "").trim())) {
return !0;
}
}
return !1;
};
if (hasExtraNodes() || chatContainer && !chatContainer.closest("secondary-wrapper")) {
const w = [];
const w2 = [];
for (let node = secondaryInner.firstChild; node instanceof Node; node = node.nextSibling) {
if (node === chatContainer && chatContainer) {} else if (node === secondaryWrapper) {
for (let node2 = secondaryWrapper.firstChild; node2 instanceof Node; node2 = node2.nextSibling) {
if (node2 === chatContainer && chatContainer) {} else {
node2 instanceof Element && "right-tabs" === node2.id && chatContainer && w2.push(chatContainer);
w2.push(node2);
}
}
} else {
w.push(node);
}
}
inPageRearrange = !0;
secondaryWrapper.replaceChildren000(...w, ...w2);
inPageRearrange = !1;
const chatElm = elements.chat;
const chatCnt = insp(chatElm);
chatCnt && "function" == typeof chatCnt.urlChanged && secondaryWrapper.contains(chatElm) && ("function" == typeof chatCnt.urlChangedAsync12 ? chatCnt.urlChanged() : setTimeout(() => chatCnt.urlChanged(), 136));
}
}
};
let lastPanel = "";
let lastTab = "";
let egmPanelsDebounceTimer = null;
const aoEgmPanels = new MutationObserver(() => {
egmPanelsDebounceTimer || (egmPanelsDebounceTimer = setTimeout(() => {
egmPanelsDebounceTimer = null;
Promise.resolve(lockSet.updateEgmPanelsLock).then(updateEgmPanels).catch(mainLog.warn);
}, 16));
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(aoEgmPanels);
YouTubeUtils?.ObserverRegistry?.track && YouTubeUtils.ObserverRegistry.track();
const removeKeepCommentsScroller = async lockId => {
if (lockGet.removeKeepCommentsScrollerLock !== lockId) {
return;
}
await Promise.resolve();
if (lockGet.removeKeepCommentsScrollerLock !== lockId) {
return;
}
const ytdFlexyFlm = elements.flexy;
ytdFlexyFlm && ytdFlexyFlm.removeAttribute000("keep-comments-scroller");
};
const egmPanelsCache = new Set;
const updateEgmPanels = async lockId => {
if (lockId !== lockGet.updateEgmPanelsLock) {
return;
}
await navigateFinishedPromise.then().catch(mainLog.warn);
if (lockId !== lockGet.updateEgmPanelsLock) {
return;
}
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) {
return;
}
const newVisiblePanels = [];
const newHiddenPanels = [];
const allVisiblePanels = [];
const panels = egmPanelsCache;
for (const panelElm of panels) {
if (!panelElm.isConnected) {
egmPanelsCache.delete(panelElm);
continue;
}
const visibility = panelElm.getAttribute000("visibility");
if ("ENGAGEMENT_PANEL_VISIBILITY_HIDDEN" === visibility || panelElm.closest("[hidden]")) {
if (panelElm.hasAttribute000("tyt-visible-at")) {
panelElm.removeAttribute000("tyt-visible-at");
newHiddenPanels.push(panelElm);
}
} else if ("ENGAGEMENT_PANEL_VISIBILITY_EXPANDED" === visibility && !panelElm.closest("[hidden]")) {
const visibleAt = panelElm.getAttribute000("tyt-visible-at");
if (!visibleAt) {
panelElm.setAttribute111("tyt-visible-at", Date.now());
newVisiblePanels.push(panelElm);
}
allVisiblePanels.push(panelElm);
}
}
if (newVisiblePanels.length >= 1 && allVisiblePanels.length >= 2) {
const targetVisible = newVisiblePanels[newVisiblePanels.length - 1];
const actions = [];
for (const panelElm of allVisiblePanels) {
panelElm !== targetVisible && actions.push({
panelId: panelElm.getAttribute000("target-id"),
toHide: !0
});
}
actions.length >= 1 && ytBtnEgmPanelCore(actions);
}
allVisiblePanels.length >= 1 ? ytdFlexyElm.setAttribute111("tyt-egm-panel_", "") : ytdFlexyElm.removeAttribute000("tyt-egm-panel_");
newVisiblePanels.length = 0;
newHiddenPanels.length = 0;
allVisiblePanels.length = 0;
};
const checkElementExist = (css, exclude) => {
const elms = window.YouTubeDOMCache ? window.YouTubeDOMCache.querySelectorAll?.(css, document) ?? qsAll(css) : qsAll(css);
for (const p of elms) {
if (!p.closest(exclude)) {
return p;
}
}
return null;
};
let fixInitialTabStateK = 0;
const {handleNavigateFactory} = (() => {
let isLoadStartListened = !1;
const closestFromAnchor = Element.prototype.closest;
const _querySelector = Element.prototype.querySelector;
const findContentsRenderer = renderer => {
const parent = renderer ? renderer.parentNode : null;
const cnt = parent ? insp(parent) : null;
const contents = ((cnt || 0).data || 0).contents || [];
return {
parent,
index: Array.isArray(contents) ? contents.findIndex(c => c === insp(renderer).data) : -1
};
};
function findLcComment(lc) {
if (1 === arguments.length) {
const element = qs(`#tab-comments ytd-comments ytd-comment-renderer #header-author a[href*="lc=${lc}"]`);
if (element) {
const commentRendererElm = closestFromAnchor.call(element, "ytd-comment-renderer");
if (commentRendererElm && lc) {
return {
lc,
commentRendererElm
};
}
}
} else if (0 === arguments.length) {
const element = qs("#tab-comments ytd-comments ytd-comment-renderer > #linked-comment-badge span:not(:empty)");
if (element) {
const commentRendererElm = closestFromAnchor.call(element, "ytd-comment-renderer");
if (commentRendererElm) {
const header = _querySelector.call(commentRendererElm, "#header-author");
if (header) {
const anchor = _querySelector.call(header, 'a[href*="lc="]');
if (anchor) {
const href = anchor.getAttribute("href") || "";
const m = /[&?]lc=([\w_.-]+)/.exec(href);
m && (lc = m[1]);
}
}
}
if (commentRendererElm && lc) {
return {
lc,
commentRendererElm
};
}
}
}
return null;
}
function lcSwapFuncB(targetLcId, currentLcId, _p) {
let done = 0;
try {
const currentResult = findLcComment(currentLcId);
const targetResult = findLcComment(targetLcId);
if (!currentResult || !targetResult) {
return !1;
}
const r1 = currentResult.commentRendererElm;
const r1cnt = insp(r1);
const r2 = targetResult.commentRendererElm;
const r2cnt = insp(r2);
const r1d = r1cnt.data;
const p = Object.assign({}, _p);
r1d.linkedCommentBadge = null;
delete r1d.linkedCommentBadge;
const q = Object.assign({}, r1d);
q.linkedCommentBadge = null;
delete q.linkedCommentBadge;
r1cnt.data = Object.assign({}, q);
r2cnt.data = Object.assign({}, r2cnt.data, {
linkedCommentBadge: p
});
done = 1;
} catch (e) {
mainLog.warn(e);
}
return 1 === done;
}
const loadStartFx = async evt => {
const target = evt ? evt.target : null;
if (!(target instanceof HTMLMediaElement)) {
return;
}
const media = target;
if ("VIDEO" !== media.nodeName && "AUDIO" !== media.nodeName) {
return;
}
const newMedia = media;
const media1 = common.getMediaElement(0);
const media2 = common.getMediaElements(2);
if (media1 instanceof HTMLMediaElement && media2.length > 0) {
if (newMedia !== media1 && !1 === media1.paused) {
(media => !!media && !media.paused && !media.ended && media.readyState > 2)(media1) && Promise.resolve(newMedia).then(video => !1 === video.paused && video.pause()).catch(mainLog.warn);
} else if (newMedia === media1) {
for (const s of media2) {
if (s instanceof HTMLMediaElement && !1 === s.paused) {
Promise.resolve(s).then(mediaElement => !1 === mediaElement.paused && mediaElement.pause()).catch(mainLog.warn);
break;
}
}
} else {
Promise.resolve(media1).then(video1 => !1 === video1.paused && video1.pause()).catch(mainLog.warn);
}
}
};
const getBrowsableEndPoint = req => {
let valid = !1;
let endpoint = req ? req.command : null;
if (endpoint && (endpoint.commandMetadata || 0).webCommandMetadata && endpoint.watchEndpoint) {
const videoId = endpoint.watchEndpoint.videoId;
const url = endpoint.commandMetadata.webCommandMetadata.url;
if ("string" == typeof videoId && "string" == typeof url && url.indexOf("lc=") > 0) {
const m = /^\/watch\?v=([\w_-]+)&lc=([\w_.-]+)$/.exec(url);
if (m && m[1] === videoId) {
const targetLc = findLcComment(m[2]);
const currentLc = targetLc ? findLcComment() : null;
if (targetLc && currentLc) {
const done = targetLc.lc === currentLc.lc || (function lcSwapFuncA(targetLcId, currentLcId) {
let done = 0;
try {
const currentResult = findLcComment(currentLcId);
const targetResult = findLcComment(targetLcId);
if (!currentResult || !targetResult) {
return !1;
}
const r1 = currentResult.commentRendererElm;
const r2 = targetResult.commentRendererElm;
if ("object" == typeof insp(r1).data.linkedCommentBadge && void 0 === insp(r2).data.linkedCommentBadge) {
const p = Object.assign({}, insp(r1).data.linkedCommentBadge);
((p || 0).metadataBadgeRenderer || 0).trackingParams && delete p.metadataBadgeRenderer.trackingParams;
const v1 = findContentsRenderer(r1);
const v2 = findContentsRenderer(r2);
if (v1.parent !== v2.parent || "YTD-COMMENTS" !== v2.parent.nodeName && "YTD-ITEM-SECTION-RENDERER" !== v2.parent.nodeName) {
return !1;
}
if (v2.index >= 0) {
if ("YTD-COMMENT-REPLIES-RENDERER" === v2.parent.nodeName) {
lcSwapFuncB(targetLcId, currentLcId, p) && (done = 1);
done = 1;
} else {
const v2pCnt = insp(v2.parent);
const v2Conents = (v2pCnt.data || 0).contents || [];
v2Conents || mainLog.warn("v2Conents is not found");
v2pCnt.data = Object.assign({}, v2pCnt.data, {
contents: [ v2Conents[v2.index], ...v2Conents.slice(0, v2.index), ...v2Conents.slice(v2.index + 1) ]
});
lcSwapFuncB(targetLcId, currentLcId, p) && (done = 1);
}
}
}
} catch (e) {
mainLog.warn(e);
}
return 1 === done;
})(targetLc.lc, currentLc.lc) ? 1 : 0;
if (1 === done) {
common.xReplaceState(history.state, url);
return;
}
}
}
}
}
if (endpoint && (endpoint.commandMetadata || 0).webCommandMetadata && endpoint.browseEndpoint && isChannelId(endpoint.browseEndpoint.browseId)) {
valid = !0;
} else if (endpoint && (endpoint.browseEndpoint || endpoint.searchEndpoint) && !endpoint.urlEndpoint && !endpoint.watchEndpoint) {
if (endpoint.browseEndpoint && "FEwhat_to_watch" === endpoint.browseEndpoint.browseId) {
const playerMedia = common.getMediaElement(1);
playerMedia && !1 === playerMedia.paused && (valid = !0);
} else if (endpoint.commandMetadata && endpoint.commandMetadata.webCommandMetadata) {
const meta = endpoint.commandMetadata.webCommandMetadata;
meta && meta.url && meta.webPageType && (valid = !0);
}
}
valid || (endpoint = null);
return endpoint;
};
const shouldUseMiniPlayer = () => {
const isSubTypeExist = qs("ytd-page-manager#page-manager > ytd-browse[page-subtype]");
if (isSubTypeExist) {
return !0;
}
const movie_player = qsAll("#movie_player").filter(e => !e.closest("[hidden]"))[0];
if (movie_player) {
const media = qsOne(movie_player, "video[class], audio[class]");
if (media && media.currentTime > 3 && media.duration - media.currentTime > 3 && !1 === media.paused) {
return !0;
}
}
return !1;
};
let u38 = 0;
return {
handleNavigateFactory: handleNavigate => function(req) {
u38 > 1e9 && (u38 = 9);
const t38 = ++u38;
const $arguments = arguments;
let endpoint = null;
(req => {
const command = req ? req.command : null;
if (command) {
if (command && (command.commandMetadata || 0).webCommandMetadata && command.watchEndpoint) {} else if (command && (command.commandMetadata || 0).webCommandMetadata && command.browseEndpoint && isChannelId(command.browseEndpoint.browseId)) {} else if (!command || !command.browseEndpoint && !command.searchEndpoint || command.urlEndpoint || command.watchEndpoint) {
return !1;
}
return !!shouldUseMiniPlayer() && "watch" === pageType;
}
})(req) && (endpoint = getBrowsableEndPoint(req));
if (!endpoint || !shouldUseMiniPlayer()) {
return handleNavigate.apply(this, $arguments);
}
const ytdAppElm = qs("ytd-app");
const ytdAppCnt = insp(ytdAppElm);
let object = null;
try {
object = ytdAppCnt.data.response.currentVideoEndpoint.watchEndpoint || null;
} catch (e) {
object = null;
}
"object" != typeof object && (object = null);
const once = {
once: !0
};
if (null !== object && !("playlistId" in object)) {
let wObject = mWeakRef(object);
const N = 3;
let count = 0;
Object.defineProperty(kRef(wObject) || {}, "playlistId", {
get() {
count++;
count === N && delete this.playlistId;
return "*";
},
set(value) {
delete this.playlistId;
this.playlistId = value;
},
enumerable: !1,
configurable: !0
});
let playlistClearout = null;
let timeoutid = 0;
Promise.race([ new Promise(resolve => {
timeoutid = setTimeout_(resolve, 4e3);
}), new Promise(resolve => {
playlistClearout = () => {
if (0 !== timeoutid) {
clearTimeout(timeoutid);
timeoutid = 0;
}
resolve();
};
document.addEventListener("yt-page-type-changed", playlistClearout, once);
}) ]).then(() => {
if (0 !== timeoutid) {
playlistClearout && document.removeEventListener("yt-page-type-changed", playlistClearout);
timeoutid = 0;
}
playlistClearout = null;
count = N - 1;
const object = kRef(wObject);
wObject = null;
return object ? object.playlistId : null;
}).catch(mainLog.warn);
}
if (!isLoadStartListened) {
isLoadStartListened = !0;
YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "loadstart", loadStartFx, !0) : document.addEventListener("loadstart", loadStartFx, !0);
}
const endpointURL = `${endpoint?.commandMetadata?.webCommandMetadata?.url || ""}`;
endpointURL && endpointURL.endsWith("/about") && /\/channel\/UC[-_a-zA-Z0-9+=.]{22}\/about/.test(endpointURL) && (async t38 => {
let promise = new PromiseExternal;
const f = () => {
promise && promise.resolve();
promise = null;
};
document.addEventListener("yt-navigate-finish", f, !1);
await (promise ? promise.then() : Promise.resolve());
promise = null;
document.removeEventListener("yt-navigate-finish", f, !1);
t38 === u38 && setTimeout(() => {
const currentAbout = qsAll("ytd-about-channel-renderer").filter(e => !e.closest("[hidden]"))[0];
let okay = !1;
if (currentAbout) {
const popupContainer = currentAbout.closest("ytd-popup-container");
if (popupContainer) {
const cnt = insp(popupContainer);
let arr = null;
try {
arr = cnt.handleGetOpenedPopupsAction_();
} catch (e) {}
arr && 0 === arr.length && (okay = !0);
} else {
okay = !1;
}
} else {
okay = !0;
}
if (okay) {
const descriptionModel = [ ...qsAll("yt-description-preview-view-model") ].filter(e => !e.closest("[hidden]"))[0];
if (descriptionModel) {
const button = [ ...descriptionModel.querySelectorAll("button") ].filter(e => !e.closest("[hidden]") && `${e.textContent}`.trim().length > 0)[0];
button && button.click();
}
}
}, 80);
})(t38);
handleNavigate.apply(this, $arguments);
}
};
})();
const common = (() => {
let mediaModeLock = 0;
const _getMediaElement = i => {
if (0 === mediaModeLock) {
const e = qs(".video-stream.html5-main-video") || qs("#movie_player video, #movie_player audio") || qs("body video[src], body audio[src]");
e && ("VIDEO" === e.nodeName ? mediaModeLock = 1 : "AUDIO" === e.nodeName && (mediaModeLock = 2));
}
if (!mediaModeLock) {
return null;
}
if (1 === mediaModeLock) {
switch (i) {
case 1:
return "ytd-player#ytd-player video[src]";

case 2:
return 'ytd-browse[role="main"] video[src]';

default:
return "#movie_player video[src]";
}
} else if (2 === mediaModeLock) {
switch (i) {
case 1:
return "ytd-player#ytd-player audio.video-stream.html5-main-video[src]";

case 2:
return 'ytd-browse[role="main"] audio.video-stream.html5-main-video[src]';

default:
return "#movie_player audio.video-stream.html5-main-video[src]";
}
}
return null;
};
return {
xReplaceState(s, u) {
try {
history.replaceState(s, "", u);
} catch (e) {}
if (s.endpoint) {
try {
const ytdAppElm = qs("ytd-app");
const ytdAppCnt = insp(ytdAppElm);
ytdAppCnt.replaceState(s.endpoint, "", u);
} catch (e) {}
}
},
getMediaElement(i) {
const s = _getMediaElement(i) || "";
if (!s) {
return null;
}
const media = qs(s);
return media instanceof HTMLMediaElement ? media : null;
},
getMediaElements(i) {
const s = _getMediaElement(i) || "";
return s ? qsAll(s).filter(el => el instanceof HTMLMediaElement) : [];
}
};
})();
let inPageRearrange = !1;
let tmpLastVideoId = "";
const getCurrentVideoId = () => {
const ytdFlexyElm = elements.flexy;
const ytdFlexyCnt = insp(ytdFlexyElm);
if (ytdFlexyCnt && "string" == typeof ytdFlexyCnt.videoId && ytdFlexyCnt.videoId) {
return ytdFlexyCnt.videoId;
}
if (ytdFlexyElm && "string" == typeof ytdFlexyElm.videoId && ytdFlexyElm.videoId) {
return ytdFlexyElm.videoId;
}
try {
const params = new URLSearchParams(location.search);
const v = params.get("v");
if (v && /^[\w-]{11}$/.test(v)) {
return v;
}
} catch (e) {}
return "";
};
const fixInlineExpanderDisplay = inlineExpanderCnt => {
try {
inlineExpanderCnt.updateIsAttributedExpanded();
} catch (e) {}
try {
inlineExpanderCnt.updateIsFormattedExpanded();
} catch (e) {}
try {
inlineExpanderCnt.updateTextOnSnippetTypeChange();
} catch (e) {}
try {
inlineExpanderCnt.updateStyles();
} catch (e) {}
};
const setExpand = cnt => {
if ("function" == typeof cnt.set) {
cnt.set("isExpanded", !0);
"function" == typeof cnt.isExpandedChanged && cnt.isExpandedChanged();
} else if (!1 === cnt.isExpanded) {
cnt.isExpanded = !0;
"function" == typeof cnt.isExpandedChanged && cnt.isExpandedChanged();
}
};
const cloneMethods = {
updateTextOnSnippetTypeChange() {
!1 === this.isResetMutation && (this.isResetMutation = !0);
!0 === this.isExpanded && (this.isExpanded = !1);
setExpand(this);
!1 === this.isResetMutation && (this.isResetMutation = !0);
},
collapse() {},
computeExpandButtonOffset: () => 0,
dataChanged() {}
};
const fixInlineExpanderMethods = inlineExpanderCnt => {
if (inlineExpanderCnt && !inlineExpanderCnt.__$$idncjk8487$$__) {
inlineExpanderCnt.__$$idncjk8487$$__ = !0;
inlineExpanderCnt.dataChanged = cloneMethods.dataChanged;
inlineExpanderCnt.updateTextOnSnippetTypeChange = cloneMethods.updateTextOnSnippetTypeChange;
"function" == typeof inlineExpanderCnt.collapse && (inlineExpanderCnt.collapse = cloneMethods.collapse);
"function" == typeof inlineExpanderCnt.computeExpandButtonOffset && (inlineExpanderCnt.computeExpandButtonOffset = cloneMethods.computeExpandButtonOffset);
"boolean" == typeof inlineExpanderCnt.isResetMutation && (inlineExpanderCnt.isResetMutation = !0);
"string" == typeof inlineExpanderCnt.collapseLabel && (inlineExpanderCnt.collapseLabel = "");
fixInlineExpanderDisplay(inlineExpanderCnt);
}
};
const fixInlineExpanderContent = () => {
const mainInfo = getMainInfo();
if (!mainInfo) {
return;
}
const inlineExpanderElm = mainInfo.querySelector("ytd-text-inline-expander");
const inlineExpanderCnt = insp(inlineExpanderElm);
fixInlineExpanderMethods(inlineExpanderCnt);
};
const plugin = {
minibrowser: {
activated: !1,
toUse: !0,
activate() {
if (this.activated) {
return;
}
const isPassiveArgSupport = "function" == typeof IntersectionObserver;
if (!isPassiveArgSupport) {
return;
}
this.activated = !0;
const ytdAppElm = qs("ytd-app");
const ytdAppCnt = insp(ytdAppElm);
if (!ytdAppCnt) {
return;
}
const cProto = ytdAppCnt.constructor.prototype;
if (cProto.handleNavigate && !cProto.handleNavigate.__ma355__) {
cProto.handleNavigate = handleNavigateFactory(cProto.handleNavigate);
cProto.handleNavigate.__ma355__ = 1;
}
}
},
autoExpandInfoDesc: {
activated: !1,
toUse: !1,
mo: null,
promiseReady: new PromiseExternal,
moFn(lockId) {
if (lockGet.autoExpandInfoDescAttrAsyncLock !== lockId) {
return;
}
const mainInfo = getMainInfo();
if (mainInfo) {
switch (((mainInfo || 0).nodeName || "").toLowerCase()) {
case "ytd-expander":
if (mainInfo.hasAttribute000("collapsed")) {
let success = !1;
try {
insp(mainInfo).handleMoreTap(new Event("tap"));
success = !0;
} catch (e) {}
success && mainInfo.setAttribute111("tyt-no-less-btn", "");
}
break;

case "ytd-expandable-video-description-body-renderer":
const inlineExpanderElm = mainInfo.querySelector("ytd-text-inline-expander");
const inlineExpanderCnt = insp(inlineExpanderElm);
inlineExpanderCnt && !1 === inlineExpanderCnt.isExpanded && setExpand(inlineExpanderCnt);
}
}
},
activate() {
if (!this.activated) {
this.moFn = this.moFn.bind(this);
this.mo = new MutationObserver(() => {
Promise.resolve(lockSet.autoExpandInfoDescAttrAsyncLock).then(this.moFn).catch(mainLog.warn);
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(this.mo);
this.activated = !0;
this.promiseReady.resolve();
}
},
async onMainInfoSet(mainInfo) {
await this.promiseReady.then();
if (this.mo) {
"ytd-expander" === mainInfo.nodeName.toLowerCase() ? this.mo.observe(mainInfo, {
attributes: !0,
attributeFilter: [ "collapsed", "attr-8ifv7" ]
}) : this.mo.observe(mainInfo, {
attributes: !0,
attributeFilter: [ "attr-8ifv7" ]
});
mainInfo.incAttribute111("attr-8ifv7");
}
}
},
fullChannelNameOnHover: {
activated: !1,
toUse: !0,
mo: null,
ro: null,
promiseReady: new PromiseExternal,
checkResize: 0,
mouseEnterFn(evt) {
const target = evt ? evt.target : null;
if (!(target instanceof HTMLElement_)) {
return;
}
const metaDataElm = target.closest("ytd-watch-metadata");
if (metaDataElm) {
metaDataElm.classList.remove("tyt-metadata-hover-resized");
this.checkResize = Date.now() + 300;
metaDataElm.classList.add("tyt-metadata-hover");
}
},
mouseLeaveFn(evt) {
const target = evt ? evt.target : null;
if (!(target instanceof HTMLElement_)) {
return;
}
const metaDataElm = target.closest("ytd-watch-metadata");
if (metaDataElm) {
metaDataElm.classList.remove("tyt-metadata-hover-resized");
metaDataElm.classList.remove("tyt-metadata-hover");
}
},
moFn(lockId) {
if (lockGet.fullChannelNameOnHoverAttrAsyncLock !== lockId) {
return;
}
const uploadInfo = qs("#primary.ytd-watch-flexy ytd-watch-metadata #upload-info");
if (!uploadInfo) {
return;
}
const evtOpt = {
passive: !0,
capture: !1
};
uploadInfo.removeEventListener("pointerenter", this.mouseEnterFn, evtOpt);
uploadInfo.removeEventListener("pointerleave", this.mouseLeaveFn, evtOpt);
uploadInfo.addEventListener("pointerenter", this.mouseEnterFn, evtOpt);
uploadInfo.addEventListener("pointerleave", this.mouseLeaveFn, evtOpt);
},
async onNavigateFinish() {
await this.promiseReady.then();
const uploadInfo = qs("#primary.ytd-watch-flexy ytd-watch-metadata #upload-info");
if (uploadInfo && this.mo && this.ro) {
this.mo.observe(uploadInfo, {
attributes: !0,
attributeFilter: [ "hidden", "attr-3wb0k" ]
});
uploadInfo.incAttribute111("attr-3wb0k");
this.ro.observe(uploadInfo);
}
},
activate() {
if (this.activated) {
return;
}
const isPassiveArgSupport = "function" == typeof IntersectionObserver;
if (isPassiveArgSupport) {
this.activated = !0;
this.mouseEnterFn = this.mouseEnterFn.bind(this);
this.mouseLeaveFn = this.mouseLeaveFn.bind(this);
this.moFn = this.moFn.bind(this);
this.mo = new MutationObserver(() => {
Promise.resolve(lockSet.fullChannelNameOnHoverAttrAsyncLock).then(this.moFn).catch(mainLog.warn);
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(this.mo);
this.ro = new ResizeObserver(mutations => {
if (!(Date.now() > this.checkResize)) {
for (const mutation of mutations) {
const uploadInfo = mutation.target;
if (uploadInfo && mutation.contentRect.width > 0 && mutation.contentRect.height > 0) {
const metaDataElm = uploadInfo.closest("ytd-watch-metadata");
metaDataElm && metaDataElm.classList.contains("tyt-metadata-hover") && metaDataElm.classList.add("tyt-metadata-hover-resized");
break;
}
}
}
});
this.promiseReady.resolve();
}
}
},
"external.ytlstm": {
activated: !1,
toUse: !0,
activate() {
if (!this.activated) {
this.activated = !0;
document.documentElement.classList.add("external-ytlstm");
}
}
}
};
sessionStorage.__$$tmp_UseAutoExpandInfoDesc$$__ && (plugin.autoExpandInfoDesc.toUse = !0);
const __attachedSymbol__ = Symbol();
const makeInitAttached = tag => {
const inPageRearrange_ = inPageRearrange;
inPageRearrange = !1;
for (const elm of qsAll(`${tag}`)) {
const cnt = insp(elm) || 0;
"function" != typeof cnt.attached498 || elm[__attachedSymbol__] || Promise.resolve(elm).then(eventMap[`${tag}::attached`]).catch(mainLog.warn);
}
inPageRearrange = inPageRearrange_;
};
const getGeneralChatElement = async () => {
for (let i = 2; i-- > 0; ) {
const t = qs("#columns.style-scope.ytd-watch-flexy ytd-live-chat-frame#chat");
if (t instanceof Element) {
return t;
}
i > 0 && await delayPn(200);
}
return null;
};
const nsTemplateObtain = () => {
let nsTemplate = qs("ytd-watch-flexy noscript[ns-template]");
if (!nsTemplate) {
nsTemplate = document.createElement("noscript");
nsTemplate.setAttribute("ns-template", "");
const flexyHost = qs("ytd-watch-flexy");
flexyHost && flexyHost.appendChild(nsTemplate);
}
return nsTemplate;
};
const isPageDOM = (elm, selector) => !!(elm && elm instanceof Element && elm.nodeName) && (!!elm.closest(selector) && !0 === elm.isConnected);
const invalidFlexyParent = hostElement => {
if (hostElement instanceof HTMLElement) {
const hasFlexyParent = HTMLElement.prototype.closest.call(hostElement, "ytd-watch-flexy");
if (!hasFlexyParent) {
return !0;
}
const currentFlexy = elements.flexy;
if (currentFlexy && currentFlexy !== hasFlexyParent) {
return !0;
}
}
return !1;
};
let headerMutationObserver = null;
let headerMutationTmpNode = null;
const eventMap = {
ceHack: () => {
mLoaded.flag |= 2;
document.documentElement.setAttribute111("tabview-loaded", mLoaded.makeString());
retrieveCE("ytd-watch-flexy").then(eventMap["ytd-watch-flexy::defined"]).catch(mainLog.warn);
retrieveCE("ytd-expander").then(eventMap["ytd-expander::defined"]).catch(mainLog.warn);
retrieveCE("ytd-watch-next-secondary-results-renderer").then(eventMap["ytd-watch-next-secondary-results-renderer::defined"]).catch(mainLog.warn);
retrieveCE("ytd-comments-header-renderer").then(eventMap["ytd-comments-header-renderer::defined"]).catch(mainLog.warn);
retrieveCE("ytd-live-chat-frame").then(eventMap["ytd-live-chat-frame::defined"]).catch(mainLog.warn);
retrieveCE("ytd-comments").then(eventMap["ytd-comments::defined"]).catch(mainLog.warn);
retrieveCE("ytd-engagement-panel-section-list-renderer").then(eventMap["ytd-engagement-panel-section-list-renderer::defined"]).catch(mainLog.warn);
retrieveCE("ytd-watch-metadata").then(eventMap["ytd-watch-metadata::defined"]).catch(mainLog.warn);
retrieveCE("ytd-playlist-panel-renderer").then(eventMap["ytd-playlist-panel-renderer::defined"]).catch(mainLog.warn);
retrieveCE("ytd-expandable-video-description-body-renderer").then(eventMap["ytd-expandable-video-description-body-renderer::defined"]).catch(mainLog.warn);
},
fixForTabDisplay: isResize => {
bFixForResizedTabLater = !1;
const runLowPriority = () => {
for (const element of qsAll("[io-intersected]")) {
const cnt = insp(element);
if (element instanceof HTMLElement_ && "function" == typeof cnt.calculateCanCollapse) {
try {
cnt.calculateCanCollapse(!0);
} catch (e) {}
}
}
};
"function" == typeof requestIdleCallback ? requestIdleCallback(runLowPriority, {
timeout: 100
}) : setTimeout(runLowPriority, 0);
isResize || "#tab-info" !== lastTab || requestAnimationFrame(() => {
for (const element of qsAll("#tab-info ytd-video-description-infocards-section-renderer, #tab-info yt-chip-cloud-renderer, #tab-info ytd-horizontal-card-list-renderer, #tab-info yt-horizontal-list-renderer")) {
const cnt = insp(element);
if (element instanceof HTMLElement_ && "function" == typeof cnt.notifyResize) {
try {
cnt.notifyResize();
} catch (e) {}
}
}
for (const element of qsAll("#tab-info ytd-text-inline-expander")) {
const cnt = insp(element);
element instanceof HTMLElement_ && "function" == typeof cnt.resize && cnt.resize(!1);
fixInlineExpanderDisplay(cnt);
}
});
if (!isResize && "string" == typeof lastTab && lastTab.startsWith("#tab-")) {
const tabContent = qs(".tab-content-cld:not(.tab-content-hidden)");
if (tabContent) {
const renderers = tabContent.querySelectorAll("yt-chip-cloud-renderer");
for (const renderer of renderers) {
const cnt = insp(renderer);
if ("function" == typeof cnt.notifyResize) {
try {
cnt.notifyResize();
} catch (e) {}
}
}
}
}
},
"ytd-watch-flexy::defined": cProto => {
if (!cProto.updateChatLocation498 && "function" == typeof cProto.updateChatLocation && 0 === cProto.updateChatLocation.length) {
cProto.updateChatLocation498 = cProto.updateChatLocation;
cProto.updateChatLocation = updateChatLocation498;
}
if (!cProto.isTwoColumnsChanged498_ && "function" == typeof cProto.isTwoColumnsChanged_ && 2 === cProto.isTwoColumnsChanged_.length) {
cProto.isTwoColumnsChanged498_ = cProto.isTwoColumnsChanged_;
cProto.isTwoColumnsChanged_ = function(arg1, arg2, ...args) {
const r = secondaryInnerFn(() => {
const r = this.isTwoColumnsChanged498_(arg1, arg2, ...args);
return r;
});
return r;
};
}
if (!cProto.defaultTwoColumnLayoutChanged498 && "function" == typeof cProto.defaultTwoColumnLayoutChanged && 0 === cProto.defaultTwoColumnLayoutChanged.length) {
cProto.defaultTwoColumnLayoutChanged498 = cProto.defaultTwoColumnLayoutChanged;
cProto.defaultTwoColumnLayoutChanged = function(...args) {
const r = secondaryInnerFn(() => {
const r = this.defaultTwoColumnLayoutChanged498(...args);
return r;
});
return r;
};
}
if (!cProto.updatePlayerLocation498 && "function" == typeof cProto.updatePlayerLocation && 0 === cProto.updatePlayerLocation.length) {
cProto.updatePlayerLocation498 = cProto.updatePlayerLocation;
cProto.updatePlayerLocation = function(...args) {
const r = secondaryInnerFn(() => {
const r = this.updatePlayerLocation498(...args);
return r;
});
return r;
};
}
if (!cProto.updateCinematicsLocation498 && "function" == typeof cProto.updateCinematicsLocation && 0 === cProto.updateCinematicsLocation.length) {
cProto.updateCinematicsLocation498 = cProto.updateCinematicsLocation;
cProto.updateCinematicsLocation = function(...args) {
const r = secondaryInnerFn(() => {
const r = this.updateCinematicsLocation498(...args);
return r;
});
return r;
};
}
if (!cProto.updatePanelsLocation498 && "function" == typeof cProto.updatePanelsLocation && 0 === cProto.updatePanelsLocation.length) {
cProto.updatePanelsLocation498 = cProto.updatePanelsLocation;
cProto.updatePanelsLocation = function(...args) {
const r = secondaryInnerFn(() => {
const r = this.updatePanelsLocation498(...args);
return r;
});
return r;
};
}
if (!cProto.swatcherooUpdatePanelsLocation498 && "function" == typeof cProto.swatcherooUpdatePanelsLocation && 6 === cProto.swatcherooUpdatePanelsLocation.length) {
cProto.swatcherooUpdatePanelsLocation498 = cProto.swatcherooUpdatePanelsLocation;
cProto.swatcherooUpdatePanelsLocation = function(arg1, arg2, arg3, arg4, arg5, arg6, ...args) {
const r = secondaryInnerFn(() => {
const r = this.swatcherooUpdatePanelsLocation498(arg1, arg2, arg3, arg4, arg5, arg6, ...args);
return r;
});
return r;
};
}
if (!cProto.updateErrorScreenLocation498 && "function" == typeof cProto.updateErrorScreenLocation && 0 === cProto.updateErrorScreenLocation.length) {
cProto.updateErrorScreenLocation498 = cProto.updateErrorScreenLocation;
cProto.updateErrorScreenLocation = function(...args) {
const r = secondaryInnerFn(() => {
const r = this.updateErrorScreenLocation498(...args);
return r;
});
return r;
};
}
if (!cProto.updateFullBleedElementLocations498 && "function" == typeof cProto.updateFullBleedElementLocations && 0 === cProto.updateFullBleedElementLocations.length) {
cProto.updateFullBleedElementLocations498 = cProto.updateFullBleedElementLocations;
cProto.updateFullBleedElementLocations = function(...args) {
const r = secondaryInnerFn(() => {
const r = this.updateFullBleedElementLocations498(...args);
return r;
});
return r;
};
}
},
"ytd-watch-next-secondary-results-renderer::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
const self = this;
inPageRearrange || Promise.resolve(self.hostElement).then(eventMap["ytd-watch-next-secondary-results-renderer::attached"]).catch(mainLog.warn);
return self.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
const self = this;
inPageRearrange || Promise.resolve(self.hostElement).then(eventMap["ytd-watch-next-secondary-results-renderer::detached"]).catch(mainLog.warn);
return self.detached498();
};
}
makeInitAttached("ytd-watch-next-secondary-results-renderer");
},
"ytd-watch-next-secondary-results-renderer::attached": hostElement => {
if (!invalidFlexyParent(hostElement)) {
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (hostElement instanceof HTMLElement_ && hostElement.classList.length > 0 && !hostElement.closest("noscript") && !0 === hostElement.isConnected && hostElement instanceof HTMLElement_ && hostElement.matches("#columns #related ytd-watch-next-secondary-results-renderer") && !hostElement.matches("#right-tabs ytd-watch-next-secondary-results-renderer, [hidden] ytd-watch-next-secondary-results-renderer")) {
elements.related = hostElement.closest("#related");
hostElement.setAttribute111("tyt-videos-list", "");
}
}
},
"ytd-watch-next-secondary-results-renderer::detached": hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected && hostElement.hasAttribute000("tyt-videos-list")) {
elements.related = null;
hostElement.removeAttribute000("tyt-videos-list");
}
},
settingCommentsVideoId: hostElement => {
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest("noscript")) {
return;
}
const cnt = insp(hostElement);
const commentsArea = elements.comments;
if (commentsArea !== hostElement || !0 !== hostElement.isConnected || !0 !== cnt.isAttached || !cnt.data || !1 !== cnt.hidden) {
return;
}
const ytdFlexyElm = elements.flexy;
const ytdFlexyCnt = ytdFlexyElm ? insp(ytdFlexyElm) : null;
ytdFlexyCnt && ytdFlexyCnt.videoId ? hostElement.setAttribute111("tyt-comments-video-id", ytdFlexyCnt.videoId) : hostElement.removeAttribute000("tyt-comments-video-id");
},
checkCommentsShouldBeHidden: lockId => {
if (lockGet.checkCommentsShouldBeHiddenLock !== lockId) {
return;
}
const commentsArea = elements.comments;
const ytdFlexyElm = elements.flexy;
if (commentsArea && ytdFlexyElm && !commentsArea.hasAttribute000("hidden")) {
const ytdFlexyCnt = insp(ytdFlexyElm);
if ("string" == typeof ytdFlexyCnt.videoId) {
const commentsVideoId = commentsArea.getAttribute("tyt-comments-video-id");
commentsVideoId && commentsVideoId !== ytdFlexyCnt.videoId && commentsArea.setAttribute111("hidden", "");
}
}
},
"ytd-comments::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-comments::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-comments::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
cProto._createPropertyObserver("data", "_dataChanged498", void 0);
cProto._dataChanged498 = function() {
Promise.resolve(this.hostElement).then(eventMap["ytd-comments::_dataChanged498"]).catch(mainLog.warn);
};
makeInitAttached("ytd-comments");
},
"ytd-comments::_dataChanged498": hostElement => {
if (!hostElement.hasAttribute000("tyt-comments-area")) {
return;
}
let commentsDataStatus = 0;
const cnt = insp(hostElement);
const data = cnt ? cnt.data : null;
const contents = data ? data.contents : null;
if (data) {
contents && 1 === contents.length && contents[0].messageRenderer && (commentsDataStatus = 2);
contents && contents.length > 1 && contents[0].commentThreadRenderer && (commentsDataStatus = 1);
}
commentsDataStatus ? hostElement.setAttribute111("tyt-comments-data-status", commentsDataStatus) : hostElement.removeAttribute000("tyt-comments-data-status");
Promise.resolve(hostElement).then(eventMap.settingCommentsVideoId).catch(mainLog.warn);
},
"ytd-comments::attached": async hostElement => {
if (invalidFlexyParent(hostElement)) {
return;
}
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest("noscript")) {
return;
}
if (!0 !== hostElement.isConnected) {
return;
}
if (!hostElement || "comments" !== hostElement.id) {
return;
}
elements.comments = hostElement;
Promise.resolve(hostElement).then(eventMap.settingCommentsVideoId).catch(mainLog.warn);
aoComment.observe(hostElement, {
attributes: !0
});
hostElement.setAttribute111("tyt-comments-area", "");
const lockId = lockSet.rightTabReadyLock02;
await rightTabsProvidedPromise.then();
if (lockGet.rightTabReadyLock02 === lockId && elements.comments === hostElement && isTabviewEnabled()) {
if (hostElement && !hostElement.closest("#right-tabs")) {
const tabComments = qs("#tab-comments");
moveNodeToTabview("comments", hostElement, tabComments);
} else {
const shouldTabVisible = elements.comments && elements.comments.closest("#tab-comments") && !elements.comments.closest("[hidden]");
const tabCommentsButton = document.querySelector('[tyt-tab-content="#tab-comments"]');
tabCommentsButton && tabCommentsButton.classList.toggle("tab-btn-hidden", !shouldTabVisible);
Promise.resolve(lockSet.removeKeepCommentsScrollerLock).then(removeKeepCommentsScroller).catch(mainLog.warn);
}
}
},
"ytd-comments::detached": hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected && hostElement.hasAttribute000("tyt-comments-area")) {
hostElement.removeAttribute000("tyt-comments-area");
aoComment.disconnect();
aoComment.takeRecords();
elements.comments = null;
const tabCommentsButton = document.querySelector('[tyt-tab-content="#tab-comments"]');
tabCommentsButton && tabCommentsButton.classList.add("tab-btn-hidden");
Promise.resolve(lockSet.removeKeepCommentsScrollerLock).then(removeKeepCommentsScroller).catch(mainLog.warn);
}
},
"ytd-comments-header-renderer::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-comments-header-renderer::attached"]).catch(mainLog.warn);
Promise.resolve(this.hostElement).then(eventMap["ytd-comments-header-renderer::dataChanged"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-comments-header-renderer::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
if (!cProto.dataChanged498 && "function" == typeof cProto.dataChanged) {
cProto.dataChanged498 = cProto.dataChanged;
cProto.dataChanged = function() {
Promise.resolve(this.hostElement).then(eventMap["ytd-comments-header-renderer::dataChanged"]).catch(mainLog.warn);
return this.dataChanged498();
};
}
makeInitAttached("ytd-comments-header-renderer");
},
"ytd-comments-header-renderer::attached": hostElement => {
if (invalidFlexyParent(hostElement)) {
return;
}
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest("noscript")) {
return;
}
if (!0 !== hostElement.isConnected) {
return;
}
if (!hostElement || !hostElement.classList.contains("ytd-item-section-renderer")) {
return;
}
const targetElement = qs("[tyt-comments-area] ytd-comments-header-renderer");
if (hostElement === targetElement) {
hostElement.setAttribute111("tyt-comments-header-field", "");
} else {
const parentNode = hostElement.parentNode;
parentNode instanceof HTMLElement_ && parentNode.querySelector("[tyt-comments-header-field]") && hostElement.setAttribute111("tyt-comments-header-field", "");
}
},
"ytd-comments-header-renderer::detached": hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected) {
if (hostElement.hasAttribute000("field-of-cm-count")) {
hostElement.removeAttribute000("field-of-cm-count");
const cmCount = qs("#tyt-cm-count");
cmCount && !qs("#tab-comments ytd-comments-header-renderer[field-of-cm-count]") && (cmCount.textContent = "");
}
hostElement.hasAttribute000("tyt-comments-header-field") && hostElement.removeAttribute000("tyt-comments-header-field");
}
},
"ytd-comments-header-renderer::dataChanged": hostElement => {
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest("noscript")) {
return;
}
const ytdFlexyElm = elements.flexy;
let b = !1;
const cnt = insp(hostElement);
(cnt && hostElement.closest("#tab-comments") && qs("#tab-comments ytd-comments-header-renderer") === hostElement || hostElement instanceof HTMLElement_ && hostElement.parentNode instanceof HTMLElement_ && hostElement.parentNode.querySelector("[tyt-comments-header-field]")) && (b = !0);
if (b) {
hostElement.setAttribute111("tyt-comments-header-field", "");
ytdFlexyElm && ytdFlexyElm.removeAttribute000("tyt-comment-disabled");
}
if (hostElement.hasAttribute000("tyt-comments-header-field") && !0 === hostElement.isConnected) {
if (!headerMutationObserver) {
headerMutationObserver = new MutationObserver(eventMap["ytd-comments-header-renderer::deferredCounterUpdate"]);
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(headerMutationObserver);
}
hostElement.parentNode && headerMutationObserver.observe(hostElement.parentNode, {
subtree: !1,
childList: !0
});
headerMutationTmpNode || (headerMutationTmpNode = document.createElementNS("http://www.w3.org/2000/svg", "defs"));
const tmpNode = headerMutationTmpNode;
hostElement.insertAdjacentElement("afterend", tmpNode);
tmpNode.remove();
}
},
"ytd-comments-header-renderer::deferredCounterUpdate": () => {
const nodes = qsAll("#tab-comments ytd-comments-header-renderer[class]");
if (1 === nodes.length) {
const hostElement = nodes[0];
const cnt = insp(hostElement);
const data = cnt.data;
if (!data) {
return;
}
let ez = "";
if (data.commentsCount && data.commentsCount.runs && data.commentsCount.runs.length >= 1) {
let max = -1;
const z = data.commentsCount.runs.map(e => {
const c = e.text.replace(/\D+/g, "").length;
c > max && (max = c);
return [ e.text, c ];
}).filter(a => a[1] === max);
z.length >= 1 && (ez = z[0][0]);
} else if (data.countText && data.countText.runs && data.countText.runs.length >= 1) {
let max = -1;
const z = data.countText.runs.map(e => {
const c = e.text.replace(/\D+/g, "").length;
c > max && (max = c);
return [ e.text, c ];
}).filter(a => a[1] === max);
z.length >= 1 && (ez = z[0][0]);
}
const cmCount = qs("#tyt-cm-count");
if (ez) {
hostElement.setAttribute111("field-of-cm-count", "");
cmCount && (cmCount.textContent = ez.trim());
} else {
hostElement.removeAttribute000("field-of-cm-count");
cmCount && (cmCount.textContent = "");
mainLog.warn("no text for #tyt-cm-count");
}
}
},
"ytd-expander::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-expander::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-expander::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
if (!cProto.calculateCanCollapse498 && "function" == typeof cProto.calculateCanCollapse) {
cProto.calculateCanCollapse498 = cProto.calculateCanCollapse;
cProto.calculateCanCollapse = funcCanCollapse;
}
if (!cProto.childrenChanged498 && "function" == typeof cProto.childrenChanged) {
cProto.childrenChanged498 = cProto.childrenChanged;
cProto.childrenChanged = function() {
Promise.resolve(this.hostElement).then(eventMap["ytd-expander::childrenChanged"]).catch(mainLog.warn);
return this.childrenChanged498();
};
}
makeInitAttached("ytd-expander");
},
"ytd-expander::childrenChanged": hostElement => {
hostElement instanceof Element && hostElement.hasAttribute000("hidden") && hostElement.hasAttribute000("tyt-main-info") && hostElement.firstElementChild && hostElement.removeAttribute("hidden");
},
"ytd-expandable-video-description-body-renderer::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-expandable-video-description-body-renderer::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-expandable-video-description-body-renderer::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
makeInitAttached("ytd-expandable-video-description-body-renderer");
},
"ytd-expandable-video-description-body-renderer::attached": async hostElement => {
if (hostElement instanceof HTMLElement_ && isPageDOM(hostElement, "[tyt-info-renderer]") && !hostElement.matches("[tyt-main-info]")) {
elements.infoExpander = hostElement;
infoExpanderElementProvidedPromise.resolve();
hostElement.setAttribute111("tyt-main-info", "");
plugin.autoExpandInfoDesc.toUse && plugin.autoExpandInfoDesc.onMainInfoSet(hostElement);
const lockId = lockSet.rightTabReadyLock03;
await rightTabsProvidedPromise.then();
if (lockGet.rightTabReadyLock03 !== lockId) {
return;
}
if (elements.infoExpander !== hostElement) {
return;
}
if (!1 === hostElement.isConnected) {
return;
}
elements.infoExpander.classList.add("tyt-main-info");
const infoExpander = elements.infoExpander;
const inlineExpanderElm = infoExpander.querySelector("ytd-text-inline-expander");
if (inlineExpanderElm) {
const mo = new MutationObserver(() => {
const p = qs("#tab-info ytd-text-inline-expander");
sessionStorage.__$$tmp_UseAutoExpandInfoDesc$$__ = p && p.hasAttribute("is-expanded") ? "1" : "";
p && fixInlineExpanderContent();
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(mo);
mo.observe(inlineExpanderElm, {
attributes: !0,
attributeFilter: [ "is-expanded", "attr-6v8qu", "hidden" ],
subtree: !0
});
inlineExpanderElm.incAttribute111("attr-6v8qu");
const cnt = insp(inlineExpanderElm);
cnt && fixInlineExpanderDisplay(cnt);
}
if (infoExpander && !infoExpander.closest("#right-tabs")) {
const tabInfoElm = qs("#tab-info");
tabInfoElm && tabInfoElm.assignChildren111(null, infoExpander, null);
} else {
const tabInfoButton = document.querySelector('[tyt-tab-content="#tab-info"]');
if (tabInfoButton) {
const shouldTabVisible = elements.infoExpander && elements.infoExpander.closest("#tab-info");
tabInfoButton.classList.toggle("tab-btn-hidden", !shouldTabVisible);
}
}
Promise.resolve(lockSet.infoFixLock).then(infoFix).catch(mainLog.warn);
}
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (hostElement instanceof HTMLElement_ && hostElement.classList.length > 0 && !hostElement.closest("noscript") && !0 === hostElement.isConnected) {
if (isPageDOM(hostElement, "#tab-info [tyt-main-info]")) {} else if (!hostElement.closest("#tab-info")) {
const bodyRenderer = hostElement;
let bodyRendererNew = qs("ytd-expandable-video-description-body-renderer[tyt-info-renderer]");
if (!bodyRendererNew) {
bodyRendererNew = document.createElement("ytd-expandable-video-description-body-renderer");
bodyRendererNew.setAttribute("tyt-info-renderer", "");
const nsTemplate = nsTemplateObtain();
nsTemplate && nsTemplate.appendChild(bodyRendererNew);
}
const cnt = insp(bodyRendererNew);
cnt.data = Object.assign({}, insp(bodyRenderer).data);
const inlineExpanderElm = bodyRendererNew.querySelector("ytd-text-inline-expander");
const inlineExpanderCnt = insp(inlineExpanderElm);
fixInlineExpanderMethods(inlineExpanderCnt);
elements.infoExpanderRendererBack = bodyRenderer;
elements.infoExpanderRendererFront = bodyRendererNew;
bodyRenderer.setAttribute("tyt-info-renderer-back", "");
bodyRendererNew.setAttribute("tyt-info-renderer-front", "");
}
}
},
"ytd-expandable-video-description-body-renderer::detached": async hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected && hostElement.hasAttribute000("tyt-main-info")) {
elements.infoExpander = null;
hostElement.removeAttribute000("tyt-main-info");
}
},
"ytd-expander::attached": async hostElement => {
if (!invalidFlexyParent(hostElement)) {
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (hostElement instanceof HTMLElement_ && hostElement.classList.length > 0 && !hostElement.closest("noscript") && !0 === hostElement.isConnected && hostElement instanceof HTMLElement_ && hostElement.matches("[tyt-comments-area] #contents ytd-expander#expander") && !hostElement.matches("[hidden] ytd-expander#expander")) {
hostElement.setAttribute111("tyt-content-comment-entry", "");
ioComment.observe(hostElement);
}
}
},
"ytd-expander::detached": hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected) {
if (hostElement.hasAttribute000("tyt-content-comment-entry")) {
ioComment.unobserve(hostElement);
hostElement.removeAttribute000("tyt-content-comment-entry");
} else if (hostElement.hasAttribute000("tyt-main-info")) {
elements.infoExpander = null;
hostElement.removeAttribute000("tyt-main-info");
}
}
},
"ytd-live-chat-frame::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-live-chat-frame::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-live-chat-frame::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
if ("function" == typeof cProto.urlChanged && !cProto.urlChanged66 && !cProto.urlChangedAsync12 && 0 === cProto.urlChanged.length) {
cProto.urlChanged66 = cProto.urlChanged;
let ath = 0;
cProto.urlChangedAsync12 = async function() {
await this.__urlChangedAsyncT689__;
const t = ath = 1 + (1073741823 & ath);
const chatframe = this.chatframe || (this.$ || 0).chatframe || 0;
if (chatframe instanceof HTMLIFrameElement) {
if (null === chatframe.contentDocument) {
await Promise.resolve("#").catch(mainLog.warn);
if (t !== ath) {
return;
}
}
await new Promise(resolve => setTimeout_(resolve, 1)).catch(mainLog.warn);
if (t !== ath) {
return;
}
const isBlankPage = !this.data || this.collapsed;
const p1 = new Promise(resolve => setTimeout_(resolve, 706)).catch(mainLog.warn);
const p2 = new Promise(resolve => {
new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
const rect = entry.boundingClientRect || 0;
if (isBlankPage || rect.width > 0 && rect.height > 0) {
observer.disconnect();
resolve("#");
break;
}
}
}).observe(chatframe);
}).catch(mainLog.warn);
await Promise.race([ p1, p2 ]);
if (t !== ath) {
return;
}
}
this.urlChanged66();
};
cProto.urlChanged = function() {
const t = this.__urlChangedAsyncT688__ = 1 + (1073741823 & this.__urlChangedAsyncT688__);
nextBrowserTick(() => {
t === this.__urlChangedAsyncT688__ && this.urlChangedAsync12();
});
};
}
makeInitAttached("ytd-live-chat-frame");
},
"ytd-live-chat-frame::attached": async hostElement => {
if (invalidFlexyParent(hostElement)) {
return;
}
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest("noscript")) {
return;
}
if (!0 !== hostElement.isConnected) {
return;
}
if (!hostElement || "chat" !== hostElement.id) {
return;
}
const lockId = lockSet.ytdLiveAttachedLock;
const chatElem = await getGeneralChatElement();
if (lockGet.ytdLiveAttachedLock === lockId) {
if (chatElem === hostElement) {
elements.chat = chatElem;
aoChat.observe(chatElem, {
attributes: !0
});
const isFlexyReady = elements.flexy instanceof Element;
chatElem.setAttribute111("tyt-active-chat-frame", isFlexyReady ? "CF" : "C");
const chatContainer = chatElem ? chatElem.closest("#chat-container") || chatElem : null;
if (chatContainer && !chatContainer.hasAttribute000("tyt-chat-container")) {
for (const p of qsAll("[tyt-chat-container]")) {
p.removeAttribute000("[tyt-chat-container]");
}
chatContainer.setAttribute111("tyt-chat-container", "");
}
const cnt = insp(hostElement);
const q = cnt.__urlChangedAsyncT688__;
const p = cnt.__urlChangedAsyncT689__ = new PromiseExternal;
setTimeout_(() => {
if (p === cnt.__urlChangedAsyncT689__ && !0 === cnt.isAttached && !0 === hostElement.isConnected) {
p.resolve();
q === cnt.__urlChangedAsyncT688__ && cnt.urlChanged();
}
}, 320);
Promise.resolve(lockSet.layoutFixLock).then(layoutFix);
} else {
mainLog.warn("Issue found in ytd-live-chat-frame::attached", chatElem, hostElement);
}
}
},
"ytd-live-chat-frame::detached": hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected && hostElement.hasAttribute000("tyt-active-chat-frame")) {
aoChat.disconnect();
aoChat.takeRecords();
hostElement.removeAttribute000("tyt-active-chat-frame");
elements.chat = null;
const ytdFlexyElm = elements.flexy;
if (ytdFlexyElm) {
ytdFlexyElm.removeAttribute000("tyt-chat-collapsed");
ytdFlexyElm.setAttribute111("tyt-chat", "");
}
}
},
"ytd-engagement-panel-section-list-renderer::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-engagement-panel-section-list-renderer::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-engagement-panel-section-list-renderer::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
makeInitAttached("ytd-engagement-panel-section-list-renderer");
},
"ytd-engagement-panel-section-list-renderer::bindTarget": hostElement => {
if (hostElement.matches("#panels.ytd-watch-flexy > ytd-engagement-panel-section-list-renderer[target-id][visibility]")) {
hostElement.setAttribute111("tyt-egm-panel", "");
egmPanelsCache.add(hostElement);
Promise.resolve(lockSet.updateEgmPanelsLock).then(updateEgmPanels).catch(mainLog.warn);
aoEgmPanels.observe(hostElement, {
attributes: !0,
attributeFilter: [ "visibility", "hidden" ]
});
}
},
"ytd-engagement-panel-section-list-renderer::attached": hostElement => {
if (!invalidFlexyParent(hostElement)) {
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (hostElement instanceof HTMLElement_ && hostElement.classList.length > 0 && !hostElement.closest("noscript") && !0 === hostElement.isConnected && hostElement.matches("#panels.ytd-watch-flexy > ytd-engagement-panel-section-list-renderer")) {
if (hostElement.hasAttribute000("target-id") && hostElement.hasAttribute000("visibility")) {
Promise.resolve(hostElement).then(eventMap["ytd-engagement-panel-section-list-renderer::bindTarget"]).catch(mainLog.warn);
} else {
hostElement.setAttribute000("tyt-egm-panel-jclmd", "");
moEgmPanelReady.observe(hostElement, {
attributes: !0,
attributeFilter: [ "visibility", "target-id" ]
});
}
}
}
},
"ytd-engagement-panel-section-list-renderer::detached": hostElement => {
if (hostElement instanceof HTMLElement_ && !hostElement.closest("noscript") && !1 === hostElement.isConnected) {
if (hostElement.hasAttribute000("tyt-egm-panel")) {
hostElement.removeAttribute000("tyt-egm-panel");
Promise.resolve(lockSet.updateEgmPanelsLock).then(updateEgmPanels).catch(mainLog.warn);
} else if (hostElement.hasAttribute000("tyt-egm-panel-jclmd")) {
hostElement.removeAttribute000("tyt-egm-panel-jclmd");
moEgmPanelReadyClearFn();
}
}
},
"ytd-watch-metadata::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-watch-metadata::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-watch-metadata::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
makeInitAttached("ytd-watch-metadata");
},
"ytd-watch-metadata::attached": hostElement => {
if (!invalidFlexyParent(hostElement)) {
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
hostElement instanceof HTMLElement_ && hostElement.classList.length > 0 && !hostElement.closest("noscript") && !0 === hostElement.isConnected && plugin.fullChannelNameOnHover.activated && plugin.fullChannelNameOnHover.onNavigateFinish();
}
},
"ytd-watch-metadata::detached": hostElement => {
hostElement instanceof HTMLElement_ && hostElement.closest("noscript");
},
"ytd-playlist-panel-renderer::defined": cProto => {
if (!cProto.attached498 && "function" == typeof cProto.attached) {
cProto.attached498 = cProto.attached;
cProto.attached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-playlist-panel-renderer::attached"]).catch(mainLog.warn);
return this.attached498();
};
}
if (!cProto.detached498 && "function" == typeof cProto.detached) {
cProto.detached498 = cProto.detached;
cProto.detached = function() {
inPageRearrange || Promise.resolve(this.hostElement).then(eventMap["ytd-playlist-panel-renderer::detached"]).catch(mainLog.warn);
return this.detached498();
};
}
makeInitAttached("ytd-playlist-panel-renderer");
},
"ytd-playlist-panel-renderer::attached": hostElement => {
if (!invalidFlexyParent(hostElement)) {
hostElement instanceof Element && Reflect.set(hostElement, __attachedSymbol__, !0);
if (hostElement instanceof HTMLElement_ && hostElement.classList.length > 0 && !hostElement.closest("noscript") && !0 === hostElement.isConnected) {
elements.playlist = hostElement;
aoPlayList.observe(hostElement, {
attributes: !0,
attributeFilter: [ "hidden", "collapsed", "attr-1y6nu" ]
});
hostElement.incAttribute111("attr-1y6nu");
}
}
},
"ytd-playlist-panel-renderer::detached": hostElement => {
hostElement instanceof HTMLElement_ && hostElement.closest("noscript");
},
_yt_playerProvided: () => {
mLoaded.flag |= 4;
document.documentElement.setAttribute111("tabview-loaded", mLoaded.makeString());
},
relatedElementProvided: target => {
if (!target.closest("[hidden]")) {
elements.related = target;
videosElementProvidedPromise.resolve();
}
},
onceInfoExpanderElementProvidedPromised: () => {
const ytdFlexyElm = elements.flexy;
ytdFlexyElm && ytdFlexyElm.setAttribute111("hide-default-text-inline-expander", "");
},
refreshSecondaryInner: lockId => {
if (lockGet.refreshSecondaryInnerLock !== lockId) {
return;
}
if (!isTabviewEnabled()) {
return;
}
const ytdFlexyElm = elements.flexy;
ytdFlexyElm && ytdFlexyElm.matches("ytd-watch-flexy[theater][full-bleed-player]:not([full-bleed-no-max-width-columns])") && ytdFlexyElm.setAttribute111("full-bleed-no-max-width-columns", "");
const related = elements.related;
if (related && related.isConnected && !related.closest("#right-tabs #tab-videos")) {
const tabVideos = qs("#tab-videos");
moveNodeToTabview("related", related, tabVideos);
}
const infoExpander = elements.infoExpander;
if (infoExpander && infoExpander.isConnected && !infoExpander.closest("#right-tabs #tab-info")) {
const tabInfo = qs("#tab-info");
moveNodeToTabview("info", infoExpander, tabInfo);
}
const commentsArea = elements.comments;
if (commentsArea) {
const isConnected = commentsArea.isConnected;
if (isConnected && !commentsArea.closest("#right-tabs #tab-comments")) {
const tab = qs("#tab-comments");
moveNodeToTabview("comments", commentsArea, tab);
}
}
},
"yt-navigate-finish": () => {
"function" == typeof shouldActivateMoOverall && (shouldActivateMoOverall() ? activateMoOverall() : deactivateMoOverall());
const ytdAppElm = qs("ytd-page-manager#page-manager.style-scope.ytd-app");
const ytdAppCnt = insp(ytdAppElm);
pageType = ytdAppCnt ? (ytdAppCnt.data || 0).page : null;
if (!qs("ytd-watch-flexy #player")) {
return;
}
const flexyArr = qsAll("ytd-watch-flexy").filter(e => !e.closest("[hidden]") && e.querySelector("#player"));
if (1 === flexyArr.length) {
elements.flexy = flexyArr[0];
if (isRightTabsInserted) {
Promise.resolve(lockSet.refreshSecondaryInnerLock).then(eventMap.refreshSecondaryInner).catch(mainLog.warn);
Promise.resolve(lockSet.removeKeepCommentsScrollerLock).then(removeKeepCommentsScroller).catch(mainLog.warn);
} else {
navigateFinishedPromise.resolve();
plugin.minibrowser.toUse && plugin.minibrowser.activate();
plugin.autoExpandInfoDesc.toUse && plugin.autoExpandInfoDesc.activate();
plugin.fullChannelNameOnHover.toUse && plugin.fullChannelNameOnHover.activate();
}
const chat = elements.chat;
chat instanceof Element && chat.setAttribute111("tyt-active-chat-frame", "CF");
const infoExpander = elements.infoExpander;
infoExpander && infoExpander.closest("#right-tabs") && Promise.resolve(lockSet.infoFixLock).then(infoFix).catch(mainLog.warn);
Promise.resolve(lockSet.layoutFixLock).then(layoutFix);
plugin.fullChannelNameOnHover.activated && plugin.fullChannelNameOnHover.onNavigateFinish();
}
},
onceInsertRightTabs: () => {
const related = elements.related;
let rightTabs = qs("#right-tabs");
if (!qs("#right-tabs") && related) {
if (!isTabviewEnabled()) {
return;
}
getLangForPage();
const docTmp = document.createElement("template");
const parsedTabs = (new DOMParser).parseFromString(String(createHTML((function getTabsHTML() {
const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord("videos")}</span>`;
const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord("info")}</span>`;
const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord("playlist")}</span>`;
const str1 = '\n        <paper-ripple class="style-scope yt-icon-button">\n            <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>\n            <div id="waves" class="style-scope paper-ripple"></div>\n        </paper-ripple>\n        ';
const str_fbtns = '\n    <div class="font-size-right">\n    <div class="font-size-btn font-size-plus" tyt-di="8rdLQ">\n    <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet" \n    stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">\n      <path d="M12 25H38M25 12V38"/>\n    </svg>\n    </div><div class="font-size-btn font-size-minus" tyt-di="8rdLQ">\n    <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"\n    stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">\n      <path d="M12 25h26"/>\n    </svg>\n    </div>\n    </div>\n    '.replace(/[\r\n]+/g, "");
const str_tabs = [ `<a id="tab-btn1" role="tab" aria-selected="false" aria-controls="tab-info" tyt-di="q9Kjc" tyt-tab-content="#tab-info" class="tab-btn${1 & ~hiddenTabsByUserCSS ? "" : " tab-btn-hidden"}">${sTabBtnInfo}${str1}${str_fbtns}</a>`, `<a id="tab-btn3" role="tab" aria-selected="false" aria-controls="tab-comments" tyt-di="q9Kjc" tyt-tab-content="#tab-comments" class="tab-btn${2 & ~hiddenTabsByUserCSS ? "" : " tab-btn-hidden"}">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`, `<a id="tab-btn4" role="tab" aria-selected="false" aria-controls="tab-videos" tyt-di="q9Kjc" tyt-tab-content="#tab-videos" class="tab-btn${4 & ~hiddenTabsByUserCSS ? "" : " tab-btn-hidden"}">${sTabBtnVideos}${str1}${str_fbtns}</a>`, `<a id="tab-btn5" role="tab" aria-selected="false" aria-controls="tab-list" tyt-di="q9Kjc" tyt-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>` ].join("");
const addHTML = `\n        <div id="right-tabs">\n            <tabview-view-pos-thead></tabview-view-pos-thead>\n            <header>\n                <div id="material-tabs" role="tablist">\n                    ${str_tabs}\n                </div>\n            </header>\n            <div class="tab-content">\n                <div id="tab-info" role="tabpanel" aria-labelledby="tab-btn1" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>\n                <div id="tab-comments" role="tabpanel" aria-labelledby="tab-btn3" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>\n                <div id="tab-videos" role="tabpanel" aria-labelledby="tab-btn4" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>\n                <div id="tab-list" role="tabpanel" aria-labelledby="tab-btn5" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>\n            </div>\n        </div>\n        `;
return addHTML;
})())), "text/html");
const parsedNodes = Array.from(parsedTabs.body.childNodes).map(node => document.importNode(node, !0));
docTmp.content.replaceChildren(...parsedNodes);
const newElm = docTmp.content.firstElementChild;
if (null !== newElm) {
inPageRearrange = !0;
related.parentNode.insertBefore000(newElm, related);
inPageRearrange = !1;
}
rightTabs = newElm;
const tabCommentsBtn = rightTabs ? rightTabs.querySelector('[tyt-tab-content="#tab-comments"]') : null;
tabCommentsBtn && tabCommentsBtn.classList.add("tab-btn-hidden");
const secondaryWrapper = document.createElement("secondary-wrapper");
secondaryWrapper.classList.add("tabview-secondary-wrapper");
secondaryWrapper.id = "secondary-inner-wrapper";
const secondaryInner = qs("#secondary-inner.style-scope.ytd-watch-flexy");
if (!secondaryInner) {
return;
}
inPageRearrange = !0;
secondaryWrapper.replaceChildren000(...secondaryInner.childNodes);
secondaryInner.insertBefore000(secondaryWrapper, secondaryInner.firstChild);
inPageRearrange = !1;
const materialTabs = rightTabs ? rightTabs.querySelector("#material-tabs") : null;
materialTabs && materialTabs.addEventListener("click", eventMap["tabs-btn-click"], !0);
const tabsRoot = rightTabs;
materialTabs && tabsRoot && materialTabs.addEventListener("keydown", e => {
const key = e.key;
if ("ArrowLeft" !== key && "ArrowRight" !== key && "Home" !== key && "End" !== key) {
return;
}
const tabs = Array.from(tabsRoot.querySelectorAll("#material-tabs a.tab-btn[tyt-tab-content]:not(.tab-btn-hidden)"));
if (0 === tabs.length) {
return;
}
const idx = tabs.indexOf(document.activeElement);
if (idx < 0) {
return;
}
e.preventDefault();
let next;
next = "ArrowRight" === key ? tabs[(idx + 1) % tabs.length] : "ArrowLeft" === key ? tabs[(idx - 1 + tabs.length) % tabs.length] : "Home" === key ? tabs[0] : tabs[tabs.length - 1];
next.focus();
next.click();
});
inPageRearrange = !0;
rightTabs && !rightTabs.closest("secondary-wrapper") && secondaryWrapper.appendChild000(rightTabs);
inPageRearrange = !1;
}
if (rightTabs) {
isRightTabsInserted = !0;
const ioTabBtns = new IntersectionObserver(entries => {
for (const entry of entries) {
const rect = entry.boundingClientRect;
entry.target.classList.toggle("tab-btn-visible", !(!rect.width || !rect.height));
}
}, {
rootMargin: "0px"
});
for (const btn of qsAll(".tab-btn[tyt-tab-content]")) {
ioTabBtns.observe(btn);
}
if (related && !related.closest("#right-tabs")) {
const tabVideos = qs("#tab-videos");
moveNodeToTabview("related", related, tabVideos);
}
const infoExpander = elements.infoExpander;
if (infoExpander && !infoExpander.closest("#right-tabs")) {
const tabInfo = qs("#tab-info");
moveNodeToTabview("info", infoExpander, tabInfo);
}
const commentsArea = elements.comments;
if (commentsArea && !commentsArea.closest("#right-tabs")) {
const tabComments = qs("#tab-comments");
moveNodeToTabview("comments", commentsArea, tabComments);
}
rightTabsProvidedPromise.resolve();
roRightTabs.disconnect();
roRightTabs.observe(rightTabs);
const ytdFlexyElm = elements.flexy;
const aoFlexy = new MutationObserver(eventMap.aoFlexyFn);
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(aoFlexy);
ytdFlexyElm && aoFlexy.observe(ytdFlexyElm, {
attributes: !0
});
Promise.resolve(lockSet.fixInitialTabStateLock).then(eventMap.fixInitialTabStateFn).catch(mainLog.warn);
ytdFlexyElm.incAttribute111("attr-7qlsy");
}
},
aoFlexyFn: () => {
Promise.resolve(lockSet.checkCommentsShouldBeHiddenLock).then(eventMap.checkCommentsShouldBeHidden).catch(mainLog.warn);
Promise.resolve(lockSet.refreshSecondaryInnerLock).then(eventMap.refreshSecondaryInner).catch(mainLog.warn);
Promise.resolve(lockSet.tabsStatusCorrectionLock).then(eventMap.tabsStatusCorrection).catch(mainLog.warn);
const videoId = getCurrentVideoId();
if (videoId !== tmpLastVideoId) {
tmpLastVideoId = videoId;
Promise.resolve(lockSet.updateOnVideoIdChangedLock).then(eventMap.updateOnVideoIdChanged).catch(mainLog.warn);
}
},
twoColumnChanged10: lockId => {
if (lockId === lockGet.twoColumnChanged10Lock) {
for (const continuation of qsAll("#tab-videos ytd-watch-next-secondary-results-renderer ytd-continuation-item-renderer")) {
if (continuation.closest("[hidden]")) {
continue;
}
const cnt = insp(continuation);
if ("boolean" == typeof cnt.showButton) {
if (!1 === cnt.showButton) {
continue;
}
cnt.showButton = !1;
const behavior = cnt.ytRendererBehavior || cnt;
"function" == typeof behavior.invalidate && behavior.invalidate(!1);
}
}
}
},
tabsStatusCorrection: lockId => {
if (lockId !== lockGet.tabsStatusCorrectionLock) {
return;
}
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) {
return;
}
const p = tabAStatus;
const q = calculationFn(p, 4351);
let resetForPanelDisappeared = !1;
const wasTheaterBeforeFullscreen = !(1 & ~p);
const isEnteringFullscreen = !(64 & p || 64 & ~q);
const isExitingFullscreen = !(64 & ~p || 64 & q);
if (p !== q) {
let actioned = !1;
let special = 0;
plugin["external.ytlstm"].activated ? 64 & q ? isEnteringFullscreen && wasTheaterBeforeFullscreen && setTimeout(() => {
if (isTheater()) {} else {
const sizeBtn = qs("ytd-watch-flexy #ytd-player button.ytp-size-button");
sizeBtn && !isTheater() && sizeBtn.click();
}
}, 300) : 4121 == (4127 & p) && 4117 == (4127 & q) ? special = 3 : 17 & ~q || !qs("[data-ytlstm-theater-mode]") ? 25 & ~q || !qs('[is-two-columns_][theater][tyt-chat="+"]') || (special = 2) : special = 1 : isExitingFullscreen && wasTheaterBeforeFullscreen && setTimeout(() => {
if (!isTheater()) {
const sizeBtn = qs("ytd-watch-flexy #ytd-player button.ytp-size-button");
sizeBtn && sizeBtn.click();
}
}, 300);
if (special) {} else if (128 & p || 128 & ~q) {
if (8 & p || 8 & ~q) {
if ((4 & ~p || 12 & q) && (8 & ~p || 12 & q) || "chat" !== lastPanel) {
if (8 == (12 & p) && 4 == (12 & q) && "chat" === lastPanel) {
lastPanel = lastTab || "";
resetForPanelDisappeared = !0;
} else if (!(128 & ~p || 128 & q || "playlist" !== lastPanel)) {
lastPanel = lastTab || "";
resetForPanelDisappeared = !0;
}
} else {
lastPanel = lastTab || "";
resetForPanelDisappeared = !0;
}
} else {
lastPanel = "chat";
}
} else {
lastPanel = "playlist";
}
tabAStatus = q;
if (special) {
if (1 === special) {
"+" !== ytdFlexyElm.getAttribute("tyt-chat") && ytBtnExpandChat();
ytdFlexyElm.getAttribute("tyt-tab") && switchToTab(null);
} else if (2 === special) {
ytBtnCollapseChat();
} else if (3 === special) {
ytBtnCancelTheater();
lastTab && switchToTab(lastTab);
}
return;
}
let bFixForResizedTab = !1;
2 == (2 ^ q) && bFixForResizedTabLater && (bFixForResizedTab = !0);
16 & ~p || 16 & q || Promise.resolve(lockSet.twoColumnChanged10Lock).then(eventMap.twoColumnChanged10).catch(mainLog.warn);
!(2 & ~p) == !(2 & ~q) || 2 & ~q || (bFixForResizedTab = !0);
if (!(2 & p || 2 & ~q || 128 & ~p || 128 & ~q)) {
lastPanel = lastTab || "";
ytBtnClosePlaylist();
actioned = !0;
}
if (128 == (136 & p) && !(136 & ~q) && "chat" === lastPanel) {
lastPanel = lastTab || "";
ytBtnClosePlaylist();
actioned = !0;
}
if (27 == (255 & p) && 26 == (255 & q)) {
lastPanel = lastTab || "";
ytBtnCollapseChat();
actioned = !0;
}
if (2 == (130 & p) && !(130 & ~q) && "playlist" === lastPanel) {
switchToTab(null);
actioned = !0;
}
if (8 == (136 & p) && !(136 & ~q) && "playlist" === lastPanel) {
lastPanel = lastTab || "";
ytBtnCollapseChat();
actioned = !0;
}
if (17 == (145 & p) && !(145 & ~q)) {
ytBtnCancelTheater();
actioned = !0;
}
if (144 == (145 & p) && !(145 & ~q)) {
lastPanel = lastTab || "";
ytBtnClosePlaylist();
actioned = !0;
}
if (64 & ~q) {
if (64 & ~p || 64 & q) {
if (17 == (59 & p) && 25 == (59 & q)) {
ytBtnCancelTheater();
actioned = !0;
} else if (16 == (49 & p) && 48 == (49 & q) && (10 & q) > 0) {
if (2 & q) {
switchToTab(null);
actioned = !0;
}
if (8 & q) {
ytBtnCollapseChat();
actioned = !0;
}
} else if (24 != (27 & p) || 16 != (27 & q) || 128 & q) {
if (1 & p || 1 & ~q) {
if (1 != (3 & p) || 3 & ~q) {
if (2 != (10 & p) || 10 & ~q) {
if (32 != (40 & p) || 40 & ~q) {
if (32 != (34 & p) || 34 & ~q) {
if (8 != (10 & p) || 10 & ~q) {
if (!(1 & ~p || 33 & q)) {
if ("chat" === lastPanel) {
ytBtnExpandChat();
actioned = !0;
} else if (lastPanel === lastTab && lastTab) {
switchToTab(lastTab);
actioned = !0;
}
}
} else {
ytBtnCollapseChat();
actioned = !0;
}
} else {
ytBtnCloseEngagementPanels();
actioned = !0;
}
} else {
ytBtnCloseEngagementPanels();
actioned = !0;
}
} else {
switchToTab(null);
actioned = !0;
}
} else {
ytBtnCancelTheater();
actioned = !0;
}
} else {
32 & ~q || ytBtnCloseEngagementPanels();
8 != (9 & p) || 9 & ~q || ytBtnCollapseChat();
switchToTab(null);
actioned = !0;
}
} else if (lastTab) {
switchToTab(lastTab);
actioned = !0;
}
} else {
32 & ~q || ytBtnCloseEngagementPanels();
if (!(10 & ~q)) {
if ("chat" === lastPanel) {
switchToTab(null);
actioned = !0;
} else if (lastPanel) {
ytBtnCollapseChat();
actioned = !0;
}
}
}
} else {
actioned = !1;
}
if (!(actioned || 128 & ~q)) {
lastPanel = "playlist";
if (!(2 & ~q)) {
switchToTab(null);
actioned = !0;
}
}
let shouldDoAutoFix = !1;
(2 & ~p || 128 != (130 & q)) && (8 & ~p || 128 != (136 & q)) && (actioned || 16 != (17 & p) || 16 != (123 & q) ? 20 == (255 & q) && (shouldDoAutoFix = !0) : shouldDoAutoFix = !0);
if (shouldDoAutoFix) {
if ("chat" === lastPanel) {
ytBtnExpandChat();
actioned = !0;
} else if ("playlist" === lastPanel) {
!(function ytBtnOpenPlaylist() {
const cnt = insp(elements.playlist);
cnt && "boolean" == typeof cnt.collapsed && (cnt.collapsed = !1);
})();
actioned = !0;
} else if (lastTab) {
switchToTab(lastTab);
actioned = !0;
} else if (resetForPanelDisappeared) {
Promise.resolve(lockSet.fixInitialTabStateLock).then(eventMap.fixInitialTabStateFn).catch(mainLog.warn);
actioned = !0;
}
}
if (bFixForResizedTab) {
bFixForResizedTabLater = !1;
Promise.resolve(0).then(eventMap.fixForTabDisplay).catch(mainLog.warn);
}
if (!(16 & ~p) != !(16 & ~q)) {
Promise.resolve(lockSet.infoFixLock).then(infoFix).catch(mainLog.warn);
Promise.resolve(lockSet.removeKeepCommentsScrollerLock).then(removeKeepCommentsScroller).catch(mainLog.warn);
Promise.resolve(lockSet.layoutFixLock).then(layoutFix).catch(mainLog.warn);
}
}
},
updateOnVideoIdChanged: lockId => {
if (lockId !== lockGet.updateOnVideoIdChangedLock) {
return;
}
const videoId = tmpLastVideoId;
if (!videoId) {
return;
}
const bodyRenderer = elements.infoExpanderRendererBack;
const bodyRendererNew = elements.infoExpanderRendererFront;
bodyRendererNew && bodyRenderer && (insp(bodyRendererNew).data = insp(bodyRenderer).data);
Promise.resolve(lockSet.infoFixLock).then(infoFix).catch(mainLog.warn);
},
fixInitialTabStateFn: async lockId => {
if (lockGet.fixInitialTabStateLock !== lockId) {
return;
}
const delayTime = fixInitialTabStateK > 0 ? 200 : 1;
await delayPn(delayTime);
if (lockGet.fixInitialTabStateLock !== lockId) {
return;
}
const kTab = qs("[tyt-tab]");
const qTab = kTab && "" !== kTab.getAttribute("tyt-tab") ? null : checkElementExist("ytd-watch-flexy[is-two-columns_]", "[hidden]");
if (checkElementExist("ytd-playlist-panel-renderer#playlist", "[hidden], [collapsed]")) {
switchToTab(null);
} else if (checkElementExist("ytd-live-chat-frame#chat", "[hidden], [collapsed]")) {
switchToTab(null);
checkElementExist("ytd-watch-flexy[theater]", "[hidden]") && ytBtnCollapseChat();
} else if (qTab) {
const hasTheater = qTab.hasAttribute("theater");
if (hasTheater) {
switchToTab(null);
} else {
const btn0 = qs(".tab-btn-visible");
switchToTab(btn0 || null);
}
}
fixInitialTabStateK++;
},
"tabs-btn-click": evt => {
const target = evt.target;
if (target instanceof HTMLElement_ && target.classList.contains("tab-btn") && target.hasAttribute000("tyt-tab-content")) {
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
const activeLink = target;
switchToTab(activeLink);
}
}
};
Promise.all([ videosElementProvidedPromise, navigateFinishedPromise ]).then(eventMap.onceInsertRightTabs).catch(mainLog.warn);
Promise.all([ navigateFinishedPromise, infoExpanderElementProvidedPromise ]).then(eventMap.onceInfoExpanderElementProvidedPromised).catch(mainLog.warn);
const isCustomElementsProvided = "undefined" != typeof customElements && "function" == typeof (customElements || 0).whenDefined;
const promiseForCustomYtElementsReady = isCustomElementsProvided ? Promise.resolve(0) : new Promise(callback => {
if ("undefined" == typeof customElements) {
"__CE_registry" in document || Object.defineProperty(document, "__CE_registry", {
get() {},
set(nv) {
if ("object" == typeof nv) {
delete this.__CE_registry;
this.__CE_registry = nv;
this.dispatchEvent(new CustomEvent("ytI-ce-registry-created"));
}
return !0;
},
enumerable: !1,
configurable: !0
});
let eventHandler = () => {
eventHandler && document.removeEventListener("ytI-ce-registry-created", eventHandler, !1);
callback();
eventHandler = null;
};
document.addEventListener("ytI-ce-registry-created", eventHandler, !1);
} else {
callback();
}
});
const retrieveCE = async nodeName => {
try {
isCustomElementsProvided || await promiseForCustomYtElementsReady;
await customElements.whenDefined(nodeName);
const dummy = qs(nodeName) || document.createElement(nodeName);
const cProto = insp(dummy).constructor.prototype;
return cProto;
} catch (e) {
mainLog.warn(e);
}
};
const moOverallRes = {
_yt_playerProvided: () => (window || 0)._yt_player || 0
};
let promiseWaitNext = null;
const moOverall = new MutationObserver(() => {
if (promiseWaitNext) {
promiseWaitNext.resolve();
promiseWaitNext = null;
}
if ("function" == typeof moOverallRes._yt_playerProvided) {
const r = moOverallRes._yt_playerProvided();
if (r) {
moOverallRes._yt_playerProvided = r;
eventMap._yt_playerProvided();
}
}
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(moOverall);
let moOverallActive = !1;
const shouldActivateMoOverall = () => {
try {
return !!qs("ytd-watch-flexy #player");
} catch (e) {
return !1;
}
};
const activateMoOverall = () => {
if (moOverallActive) {
return;
}
const target = document.head || document.documentElement || document;
moOverall.observe(target, {
subtree: !0,
childList: !0
});
moOverallActive = !0;
};
const deactivateMoOverall = () => {
if (moOverallActive) {
moOverall.disconnect();
moOverallActive = !1;
}
};
shouldActivateMoOverall() && activateMoOverall();
const moEgmPanelReady = new MutationObserver(mutations => {
for (const mutation of mutations) {
const target = mutation.target;
if (target instanceof Element && (target.hasAttribute000("tyt-egm-panel-jclmd") && target.hasAttribute000("target-id") && target.hasAttribute000("visibility"))) {
target.removeAttribute000("tyt-egm-panel-jclmd");
moEgmPanelReadyClearFn();
Promise.resolve(target).then(eventMap["ytd-engagement-panel-section-list-renderer::bindTarget"]).catch(mainLog.warn);
}
}
});
YouTubeUtils?.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(moEgmPanelReady);
YouTubeUtils?.ObserverRegistry?.track && YouTubeUtils.ObserverRegistry.track();
const moEgmPanelReadyClearFn = () => {
if (null === qs("[tyt-egm-panel-jclmd]")) {
moEgmPanelReady.takeRecords();
moEgmPanelReady.disconnect();
}
};
YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", eventMap["yt-navigate-finish"], !1) : document.addEventListener("yt-navigate-finish", eventMap["yt-navigate-finish"], !1);
const _animStartHandler = evt => {
const f = eventMap[evt.animationName];
"function" == typeof f && f(evt.target);
};
YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "animationstart", _animStartHandler, capturePassive) : document.addEventListener("animationstart", _animStartHandler, capturePassive);
mLoaded.flag |= 1;
document.documentElement.setAttribute111("tabview-loaded", mLoaded.makeString());
promiseForCustomYtElementsReady.then(eventMap.ceHack).catch(mainLog.warn);
} catch (e) {
mainLog.error("error 0xF491", e);
}
};

const styles = {
main: '\n  @keyframes relatedElementProvided{0%{background-position-x:3px;}100%{background-position-x:4px;}}\n  html[tabview-loaded="icp"] #related.ytd-watch-flexy{animation:relatedElementProvided 1ms linear 0s 1 normal forwards;}\n  html[tabview-loaded="icp"] #right-tabs #related.ytd-watch-flexy,html[tabview-loaded="icp"] [hidden] #related.ytd-watch-flexy,html[tabview-loaded="icp"] #right-tabs ytd-expander#expander,html[tabview-loaded="icp"] [hidden] ytd-expander#expander,html[tabview-loaded="icp"] ytd-comments ytd-expander#expander{animation:initial;}\n  #secondary.ytd-watch-flexy{position:relative;}\n  #secondary-inner.style-scope.ytd-watch-flexy{height:100%;}\n  #secondary-inner secondary-wrapper{display:flex;flex-direction:column;flex-wrap:nowrap;box-sizing:border-box;padding:0;margin:0;border:0;height:100%;max-height:calc(100vh - var(--ytd-toolbar-height,56px));position:absolute;top:0;right:0;left:0;contain:strict;padding:var(--ytd-margin-6x) var(--ytd-margin-6x) var(--ytd-margin-6x) 0;}\n  #right-tabs{position:relative;display:flex;padding:0;margin:0;flex-grow:1;flex-direction:column;}\n  [tyt-tab=""] #right-tabs{flex-grow:0;}\n  [tyt-tab=""] #right-tabs .tab-content{border:0;}\n  #right-tabs .tab-content{flex-grow:1;}\n  ytd-watch-flexy[hide-default-text-inline-expander] #primary.style-scope.ytd-watch-flexy ytd-text-inline-expander{display:none;}\n  ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden{--comment-pre-load-sizing:90px;visibility:collapse;z-index:-1;position:absolute!important;left:2px;top:2px;width:var(--comment-pre-load-sizing)!important;height:var(--comment-pre-load-sizing)!important;display:block!important;pointer-events:none!important;overflow:hidden;contain:strict;border:0;margin:0;padding:0;}\n  ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments>ytd-item-section-renderer#sections{display:block!important;overflow:hidden;height:var(--comment-pre-load-sizing);width:var(--comment-pre-load-sizing);contain:strict;border:0;margin:0;padding:0;}\n  ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments>ytd-item-section-renderer#sections>#contents{display:flex!important;flex-direction:row;gap:60px;overflow:hidden;height:var(--comment-pre-load-sizing);width:var(--comment-pre-load-sizing);contain:strict;border:0;margin:0;padding:0;}\n  ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents{--comment-pre-load-display:none;}\n  ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*:only-of-type,ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*:last-child{--comment-pre-load-display:block;}\n  ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*{display:var(--comment-pre-load-display)!important;}\n  ytd-watch-flexy #tab-comments ytd-comments-header-renderer #title{justify-content:space-between;}\n  ytd-watch-flexy #tab-comments ytd-item-section-renderer #contents{padding-left:0!important;}\n  ytd-watch-flexy #tab-videos ytd-item-section-renderer #contents{padding-left:0!important;}\n  ytd-watch-flexy #tab-comments:not(.tab-content-hidden){pointer-events:auto!important;}\n  ytd-watch-flexy #tab-comments:not(.tab-content-hidden) *{pointer-events:auto!important;}\n  ytd-watch-flexy #tab-comments:not(.tab-content-hidden) button,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) yt-button-renderer,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) a,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) tp-yt-paper-button,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) [role="button"],ytd-watch-flexy #tab-comments:not(.tab-content-hidden) yt-button-shape{pointer-events:auto!important;}\n  ytd-watch-flexy #tab-comments tp-yt-paper-button{white-space:normal;word-break:break-word;max-width:100%;overflow-wrap:break-word;}\n  ytd-watch-flexy #tab-comments:not(.tab-content-hidden) ytd-comment-action-buttons-renderer,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) ytd-button-renderer,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) #action-buttons,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) ytd-menu-renderer,ytd-watch-flexy #tab-comments:not(.tab-content-hidden) yt-dropdown-menu{pointer-events:auto!important;}\n  #right-tabs #material-tabs{position:relative;display:flex;padding:0;border:1px solid var(--ytd-searchbox-legacy-border-color);overflow:hidden;}\n  [tyt-tab] #right-tabs #material-tabs{border-radius:12px;}\n  [tyt-tab^="#"] #right-tabs #material-tabs{border-radius:12px 12px 0 0;}\n  ytd-watch-flexy:not([is-two-columns_]) #right-tabs #material-tabs{outline:0;}\n  #right-tabs #material-tabs a.tab-btn[tyt-tab-content]>*{pointer-events:none;}\n  #right-tabs #material-tabs a.tab-btn[tyt-tab-content]>.font-size-right{pointer-events:initial;display:none;}\n  ytd-watch-flexy #right-tabs .tab-content{padding:0;box-sizing:border-box;display:block;border:1px solid var(--ytd-searchbox-legacy-border-color);border-top:0;position:relative;top:0;display:flex;flex-direction:row;overflow:hidden;border-radius:0 0 12px 12px;}\n  ytd-watch-flexy:not([is-two-columns_]) #right-tabs .tab-content{height:100%;}\n  ytd-watch-flexy #right-tabs .tab-content-cld{box-sizing:border-box;position:relative;display:block;width:100%;overflow:auto;--tab-content-padding:var(--ytd-margin-4x);padding:var(--tab-content-padding);contain:layout paint;will-change:scroll-position;}\n  .tab-content-cld,#right-tabs,.tab-content{transition:none;animation:none;}\n  ytd-watch-flexy #right-tabs .tab-content-cld::-webkit-scrollbar{width:8px;height:8px;}\n  ytd-watch-flexy #right-tabs .tab-content-cld::-webkit-scrollbar-track{background:transparent;}\n  ytd-watch-flexy #right-tabs .tab-content-cld::-webkit-scrollbar-thumb{background:rgba(144,144,144,.5);border-radius:4px;}\n  ytd-watch-flexy #right-tabs .tab-content-cld::-webkit-scrollbar-thumb:hover{background:rgba(170,170,170,.7);}\n  #right-tabs #emojis.ytd-commentbox{inset:auto 0 auto 0;width:auto;}\n  ytd-watch-flexy[is-two-columns_] #right-tabs .tab-content-cld{height:100%;width:100%;contain:size layout paint style;position:absolute;}\n  ytd-watch-flexy #right-tabs .tab-content-cld.tab-content-hidden{display:none;width:100%;contain:size layout paint style;}\n  @supports (color:var(--tabview-tab-btn-define)){\n  ytd-watch-flexy #right-tabs .tab-btn{background:var(--yt-spec-general-background-a);}\n  html{--tyt-tab-btn-flex-grow:1;--tyt-tab-btn-flex-basis:0%;--tyt-tab-bar-color-1-def:#ff4533;--tyt-tab-bar-color-2-def:var(--yt-brand-light-red);--tyt-tab-bar-color-1:var(--main-color,var(--tyt-tab-bar-color-1-def));--tyt-tab-bar-color-2:var(--main-color,var(--tyt-tab-bar-color-2-def));}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]{flex:var(--tyt-tab-btn-flex-grow) 1 var(--tyt-tab-btn-flex-basis);position:relative;display:inline-block;text-decoration:none;text-transform:uppercase;--tyt-tab-btn-color:var(--yt-text-secondary);color:var(--tyt-tab-btn-color);text-align:center;padding:14px 8px 10px;border:0;border-bottom:4px solid transparent;font-weight:500;font-size:12px;line-height:18px;cursor:pointer;transition:border 200ms linear 100ms;background-color:var(--ytd-searchbox-legacy-button-color);text-transform:var(--yt-button-text-transform,inherit);user-select:none!important;overflow:hidden;white-space:nowrap;text-overflow:clip;}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]>svg{height:18px;padding-right:0;vertical-align:bottom;opacity:.5;margin-right:0;color:var(--yt-button-color,inherit);fill:var(--iron-icon-fill-color,currentcolor);stroke:var(--iron-icon-stroke-color,none);pointer-events:none;}\n  ytd-watch-flexy #right-tabs .tab-btn{--tabview-btn-txt-ml:8px;}\n  ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"]{--tabview-btn-txt-ml:0;}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]>svg+span{margin-left:var(--tabview-btn-txt-ml);}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].active{font-weight:500;outline:0;--tyt-tab-btn-color:var(--yt-text-primary);background-color:var(--ytd-searchbox-legacy-button-focus-color);border-bottom:2px var(--tyt-tab-bar-color-2) solid;}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].active svg{opacity:.9;}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]:not(.active):hover{background-color:var(--ytd-searchbox-legacy-button-hover-color);--tyt-tab-btn-color:var(--yt-text-primary);}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]:not(.active):hover svg{opacity:.9;}\n  ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].tab-btn-hidden{display:none;}\n  ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"],ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"]:hover{--tyt-tab-btn-color:var(--yt-spec-icon-disabled);}\n  ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"] span#tyt-cm-count:empty{display:none;}\n  ytd-watch-flexy #right-tabs .tab-btn span#tyt-cm-count:empty::after{display:inline-block;width:4em;text-align:left;font-size:inherit;color:currentColor;transform:scaleX(.8);}}\n  @supports (color:var(--tyt-cm-count-define)){\n  ytd-watch-flexy{--tyt-x-loading-content-letter-spacing:2px;}\n  html{--tabview-text-loading:"Loading";--tabview-text-fetching:"Fetching";--tabview-panel-loading:var(--tabview-text-loading);}\n  ytd-watch-flexy #right-tabs .tab-btn span#tyt-cm-count:empty::after{content:var(--tabview-text-loading);letter-spacing:var(--tyt-x-loading-content-letter-spacing);}}\n  @supports (color:var(--tabview-font-size-btn-define)){\n  .font-size-right{display:inline-flex;flex-direction:column;position:absolute;right:0;top:0;bottom:0;width:16px;padding:4px 0;justify-content:space-evenly;align-content:space-evenly;pointer-events:none;}\n  html body ytd-watch-flexy.style-scope .font-size-btn{user-select:none!important;}\n  .font-size-btn{--tyt-font-size-btn-display:none;display:var(--tyt-font-size-btn-display,none);width:12px;height:12px;color:var(--yt-spec-text-secondary);background-color:var(--yt-spec-badge-chip-background);box-sizing:border-box;cursor:pointer;transform-origin:left top;margin:0;padding:0;position:relative;font-family:\'Menlo\',\'Lucida Console\',\'Monaco\',\'Consolas\',monospace;line-height:100%;font-weight:900;transition:background-color 90ms linear,color 90ms linear;pointer-events:all;}\n  .font-size-btn:hover{background-color:var(--yt-spec-text-primary);color:var(--yt-spec-general-background-a);}\n  @supports (zoom:.5){\n  .tab-btn .font-size-btn{--tyt-font-size-btn-display:none;}\n  .tab-btn.active:hover .font-size-btn{--tyt-font-size-btn-display:inline-block;}\n  body ytd-watch-flexy:not([is-two-columns_]) #columns.ytd-watch-flexy{flex-direction:column;}\n  body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy{display:block;width:100%;box-sizing:border-box;}\n  body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy secondary-wrapper{padding-left:var(--ytd-margin-6x);contain:content;height:initial;}\n  body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy secondary-wrapper #right-tabs{overflow:auto;}\n  [tyt-chat="+"] { --tyt-chat-grow: 1;}\n  [tyt-chat="+"] secondary-wrapper>[tyt-chat-container]{flex-grow:var(--tyt-chat-grow);flex-shrink:0;display:flex;flex-direction:column;}\n  [tyt-chat="+"] secondary-wrapper>[tyt-chat-container]>#chat{flex-grow:var(--tyt-chat-grow);}\n  ytd-watch-flexy[is-two-columns_]:not([theater]):not([full-bleed-player]) #columns.style-scope.ytd-watch-flexy{min-height:calc(100vh - var(--ytd-toolbar-height,56px));}\n  ytd-watch-flexy[is-two-columns_]:not([full-bleed-player]) ytd-live-chat-frame#chat{min-height:initial!important;height:initial!important;}\n  ytd-watch-flexy[tyt-tab^="#"]:not([is-two-columns_]):not([tyt-chat="+"]) #right-tabs{min-height:var(--ytd-watch-flexy-chat-max-height);}\n  body ytd-watch-flexy:not([is-two-columns_]) #chat.ytd-watch-flexy{margin-top:0;}\n  body ytd-watch-flexy:not([is-two-columns_]) ytd-watch-metadata.ytd-watch-flexy{margin-bottom:0;}\n  ytd-watch-metadata.ytd-watch-flexy ytd-metadata-row-container-renderer{display:none;}\n  #tab-info [show-expand-button] #expand-sizer.ytd-text-inline-expander{visibility:initial;}\n  #tab-info #collapse.button.ytd-text-inline-expander {display: none;}\n  #tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#left-arrow-container.ytd-video-description-infocards-section-renderer>#left-arrow,#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#right-arrow-container.ytd-video-description-infocards-section-renderer>#right-arrow{border:6px solid transparent;opacity:.65;}\n  #tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#left-arrow-container.ytd-video-description-infocards-section-renderer>#left-arrow:hover,#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#right-arrow-container.ytd-video-description-infocards-section-renderer>#right-arrow:hover{opacity:1;}\n  #tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>div#left-arrow-container::before{content:\'\';background:transparent;width:40px;display:block;height:40px;position:absolute;left:-20px;top:0;z-index:-1;}\n  #tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>div#right-arrow-container::before{content:\'\';background:transparent;width:40px;display:block;height:40px;position:absolute;right:-20px;top:0;z-index:-1;}\n  body ytd-watch-flexy[is-two-columns_][tyt-egm-panel_] #columns.style-scope.ytd-watch-flexy #panels.style-scope.ytd-watch-flexy{flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}\n  body ytd-watch-flexy[is-two-columns_][tyt-egm-panel_] #columns.style-scope.ytd-watch-flexy #panels.style-scope.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]{height:initial;max-height:initial;min-height:initial;flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}\n  secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] ytd-transcript-renderer:not(:empty),secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] #body.ytd-transcript-renderer:not(:empty),secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] #content.ytd-transcript-renderer:not(:empty){flex-grow:1;height:initial;max-height:initial;min-height:initial;}\n  secondary-wrapper #content.ytd-engagement-panel-section-list-renderer{position:relative;}\n  secondary-wrapper #content.ytd-engagement-panel-section-list-renderer>[panel-target-id]:only-child{contain:style size;}\n  secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-list-renderer.ytd-transcript-search-panel-renderer{flex-grow:1;contain:strict;}\n  secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-renderer.style-scope.ytd-transcript-segment-list-renderer{contain:layout paint style;}\n  secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-renderer.style-scope.ytd-transcript-segment-list-renderer>.segment{contain:layout paint style;}\n  body ytd-watch-flexy[theater] #secondary.ytd-watch-flexy{margin-top:var(--ytd-margin-3x);padding-top:0;}\n  body ytd-watch-flexy[theater] secondary-wrapper{margin-top:0;padding-top:0;}\n  body ytd-watch-flexy[theater] #chat.ytd-watch-flexy{margin-bottom:var(--ytd-margin-2x);}\n  ytd-watch-flexy[theater] #right-tabs .tab-btn[tyt-tab-content]{padding:8px 4px 6px;border-bottom:0 solid transparent;}\n  ytd-watch-flexy[theater] #playlist.ytd-watch-flexy{margin-bottom:var(--ytd-margin-2x);}\n  ytd-watch-flexy[theater] ytd-playlist-panel-renderer[collapsible][collapsed] .header.ytd-playlist-panel-renderer{padding:6px 8px;}\n  #tab-comments ytd-comments#comments [field-of-cm-count]{margin-top:0;}\n  #tab-info>ytd-expandable-video-description-body-renderer{margin-bottom:var(--ytd-margin-3x);}\n  #tab-info [class]:last-child{margin-bottom:0;padding-bottom:0;}\n  #tab-info ytd-rich-metadata-row-renderer ytd-rich-metadata-renderer{max-width:initial;}\n  ytd-watch-flexy[is-two-columns_] secondary-wrapper #chat.ytd-watch-flexy{margin-bottom:var(--ytd-margin-3x);}\n  ytd-watch-flexy[tyt-tab] tp-yt-paper-tooltip{white-space:nowrap;contain:content;}\n  ytd-watch-info-text tp-yt-paper-tooltip.style-scope.ytd-watch-info-text{margin-bottom:-300px;margin-top:-96px;}\n  [hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata{font-size:1.2rem;line-height:1.8rem;}\n  [hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata yt-animated-rolling-number{font-size:inherit;}\n  [hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata #info-container.style-scope.ytd-watch-info-text{align-items:center;}\n  ytd-watch-flexy[hide-default-text-inline-expander]{--tyt-bottom-watch-metadata-margin:6px;}\n  [hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata>#description-inner.ytd-watch-metadata{margin:6px 12px;}\n  [hide-default-text-inline-expander] ytd-watch-metadata[title-headline-xs] h1.ytd-watch-metadata{font-size:1.8rem;}\n  ytd-watch-flexy[is-two-columns_][hide-default-text-inline-expander] #below.style-scope.ytd-watch-flexy ytd-merch-shelf-renderer{padding:0;border:0;margin:0;}\n  ytd-watch-flexy[is-two-columns_][hide-default-text-inline-expander] #below.style-scope.ytd-watch-flexy ytd-watch-metadata.ytd-watch-flexy{margin-bottom:6px;}\n  #tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--horizontal .yt-video-attribute-view-model__link-container .yt-video-attribute-view-model__hero-section{flex-shrink:0;}\n  #tab-info yt-video-attribute-view-model .yt-video-attribute-view-model__overflow-menu{background:var(--yt-emoji-picker-category-background-color);border-radius:99px;}\n  #tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--image-square.yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section{max-height:128px;}\n  #tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section{max-width:128px;}\n  #tab-info ytd-reel-shelf-renderer #items.yt-horizontal-list-renderer ytd-reel-item-renderer.yt-horizontal-list-renderer{max-width:142px;}\n  ytd-watch-info-text#ytd-watch-info-text.style-scope.ytd-watch-metadata #view-count.style-scope.ytd-watch-info-text,ytd-watch-info-text#ytd-watch-info-text.style-scope.ytd-watch-metadata #date-text.style-scope.ytd-watch-info-text{align-items:center;}\n  ytd-watch-info-text:not([detailed]) #info.ytd-watch-info-text a.yt-simple-endpoint.yt-formatted-string{pointer-events:none;}\n  body ytd-app>ytd-popup-container>tp-yt-iron-dropdown>#contentWrapper>[slot="dropdown-content"]{backdrop-filter:none;}\n  #tab-info [tyt-clone-refresh-count]{overflow:visible!important;}\n  #tab-info #items.ytd-horizontal-card-list-renderer yt-video-attribute-view-model.ytd-horizontal-card-list-renderer{contain:layout;}\n  #tab-info #thumbnail-container.ytd-structured-description-channel-lockup-renderer,#tab-info ytd-media-lockup-renderer[is-compact] #thumbnail-container.ytd-media-lockup-renderer{flex-shrink:0;}\n  secondary-wrapper ytd-donation-unavailable-renderer{--ytd-margin-6x:var(--ytd-margin-2x);--ytd-margin-5x:var(--ytd-margin-2x);--ytd-margin-4x:var(--ytd-margin-2x);--ytd-margin-3x:var(--ytd-margin-2x);}\n  [tyt-no-less-btn] #less{display:none;}\n  .tyt-metadata-hover-resized #purchase-button,.tyt-metadata-hover-resized #sponsor-button,.tyt-metadata-hover-resized #analytics-button,.tyt-metadata-hover-resized #subscribe-button{display:none!important;}\n  .tyt-metadata-hover #upload-info{max-width:max-content;min-width:max-content;flex-basis:100vw;flex-shrink:0;}\n  .tyt-info-invisible{display:none;}\n  [tyt-playlist-expanded] secondary-wrapper>ytd-playlist-panel-renderer#playlist{overflow:auto;flex-shrink:1;flex-grow:1;max-height:unset!important;}\n  [tyt-playlist-expanded] secondary-wrapper>ytd-playlist-panel-renderer#playlist>#container{max-height:unset!important;}\n  secondary-wrapper ytd-playlist-panel-renderer{--ytd-margin-6x:var(--ytd-margin-3x);}\n  #tab-info ytd-structured-description-playlist-lockup-renderer[collections] #playlist-thumbnail.style-scope.ytd-structured-description-playlist-lockup-renderer{max-width:100%;}\n  #tab-info ytd-structured-description-playlist-lockup-renderer[collections] #lockup-container.ytd-structured-description-playlist-lockup-renderer{padding:1px;}\n  #tab-info ytd-structured-description-playlist-lockup-renderer[collections] #thumbnail.ytd-structured-description-playlist-lockup-renderer{outline:1px solid rgba(127,127,127,.5);}\n  ytd-live-chat-frame#chat[collapsed] ytd-message-renderer~#show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame{padding:0;}\n  ytd-watch-flexy{--tyt-bottom-watch-metadata-margin:12px;}\n  ytd-watch-flexy[rounded-info-panel],ytd-watch-flexy[rounded-player-large]{--tyt-rounded-a1:12px;}\n  #bottom-row.style-scope.ytd-watch-metadata .item.ytd-watch-metadata{margin-right:var(--tyt-bottom-watch-metadata-margin,12px);margin-top:var(--tyt-bottom-watch-metadata-margin,12px);}\n  #cinematics{contain:layout style size;}\n  ytd-watch-flexy[is-two-columns_]{contain:layout style;}\n  .yt-spec-touch-feedback-shape--touch-response .yt-spec-touch-feedback-shape__fill{background-color:transparent;}\n  /* plugin: external.ytlstm */\n  body[data-ytlstm-theater-mode] #secondary-inner[class] > secondary-wrapper[class]:not(#chat-container):not(#chat) {display: flex !important;}  \n  body[data-ytlstm-theater-mode] secondary-wrapper {all: unset;height: 100vh;}\n  body[data-ytlstm-theater-mode] #right-tabs {display: none;}\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] [tyt-chat="+"] {--tyt-chat-grow: unset;}\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #columns.style-scope.ytd-watch-flexy,\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #secondary.style-scope.ytd-watch-flexy,\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #secondary-inner.style-scope.ytd-watch-flexy,\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] secondary-wrapper,\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #chat-container.style-scope,\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] [tyt-chat-container].style-scope {pointer-events: none;}\n  body[data-ytlstm-theater-mode] [data-ytlstm-chat-over-video] #chat[class] {pointer-events: auto;}\n  .playlist-items.ytd-playlist-panel-renderer {background-color: transparent !important;}\n  @supports (color: var(--tyt-fix-20251124)) { #below ytd-watch-metadata .ytTextCarouselItemViewModelImageType { height: 16px; width: 16px;}\n  #below ytd-watch-metadata yt-text-carousel-item-view-model { column-gap: 6px;}\n  #below ytd-watch-metadata ytd-watch-info-text#ytd-watch-info-text { font-size: inherit; line-height: inherit;}\n  /* Fix: video tab thumbnails (yt-lockup-view-model) too large in side panel */\n  #tab-videos yt-lockup-view-model{max-width:100%;contain:layout paint;}\n  #tab-videos yt-lockup-view-model .yt-lockup-view-model__content-image,#tab-videos yt-lockup-view-model .yt-lockup-view-model__content-image img,#tab-videos yt-lockup-view-model .yt-lockup-view-model__content-image yt-image{max-width:175px;max-height:94px;width:175px;height:auto;object-fit:cover;border-radius:8px;flex-shrink:0;}\n  #tab-videos yt-lockup-view-model .yt-lockup-view-model--horizontal{display:flex;gap:8px;align-items:flex-start;}\n  #tab-videos yt-lockup-view-model .yt-lockup-view-model--horizontal .yt-lockup-view-model__content-image{flex-shrink:0;width:175px;}\n  #tab-videos yt-lockup-view-model .yt-lockup-view-model--horizontal .yt-lockup-view-model__metadata{flex:1;min-width:0;overflow:hidden;}\n  #tab-videos ytd-video-renderer[use-search-ui] #thumbnail.ytd-video-renderer,#tab-videos ytd-compact-video-renderer #thumbnail{max-width:175px;width:175px;flex-shrink:0;}\n  /* ── LCP Performance: safe content-visibility hints (no contain:layout to preserve sticky) ── */\n  ytd-browse[page-subtype="home"] #contents.ytd-rich-grid-renderer>ytd-rich-item-renderer:nth-child(n+9){content-visibility:auto;contain-intrinsic-size:auto 360px;}\n  ytd-playlist-video-list-renderer #contents>ytd-playlist-video-renderer:nth-child(n+10){content-visibility:auto;contain-intrinsic-size:auto 90px;}\n  ytd-watch-next-secondary-results-renderer ytd-compact-video-renderer:nth-child(n+5){content-visibility:auto;contain-intrinsic-size:auto 94px;}\n  /* ── CLS Fix: reserve thumbnail space to prevent layout shifts (home, playlist, search) ── */\n  ytd-thumbnail,ytd-thumbnail a.ytd-thumbnail{display:block;aspect-ratio:16/9;contain:layout paint;}\n  ytd-browse[page-subtype="home"] ytd-thumbnail,ytd-browse[page-subtype="home"] ytd-thumbnail a.ytd-thumbnail{aspect-ratio:16/9;}\n  ytd-playlist-video-renderer ytd-thumbnail,ytd-playlist-video-renderer ytd-thumbnail a.ytd-thumbnail{aspect-ratio:16/9;}\n  ytd-compact-video-renderer ytd-thumbnail,ytd-compact-video-renderer ytd-thumbnail a.ytd-thumbnail{aspect-ratio:16/9;}\n  ytd-rich-item-renderer ytd-thumbnail yt-image{aspect-ratio:16/9;width:100%;height:auto;display:block;}\n  /* ── CLS Fix: stabilise rich-grid rows so columns don\'t shift as items load ── */\n  ytd-browse[page-subtype="home"] ytd-rich-grid-row{contain:layout size;}\n  ytd-browse[page-subtype="home"] ytd-rich-item-renderer{contain:layout paint;min-height:280px;}\n  /* ── Playlist page: reserve row height to prevent CLS on item load ── */\n  ytd-playlist-video-renderer{contain:layout paint;min-height:90px;}\n  ytd-playlist-panel-video-renderer{contain:layout paint;min-height:72px;}\n  /* ── Video page sidebar: stabilise compact-video rows ── */\n  ytd-compact-video-renderer{contain:layout paint;min-height:92px;}\n  /* ── Performance: GPU-composite the fixed/sticky nav to avoid repaint ── */\n  #masthead-container{will-change:transform;}\n  '
};

(async () => {
var nextBrowserTick = void 0 !== nextBrowserTick && nextBrowserTick.version >= 2 ? nextBrowserTick : (() => {
"use strict";
const e = "undefined" != typeof globalThis ? globalThis : window;
let t = !0;
if (!(function n(s) {
return s ? t = !1 : !(!e.postMessage || e.importScripts || !e.addEventListener) && (e.addEventListener("message", n, !1), 
e.postMessage("$$$", "*"), e.removeEventListener("message", n, !1), t);
})()) {
return void mainLog.warn("Your browser environment cannot use nextBrowserTick");
}
const n = globalThis.Promise;
let s = null;
const o = new Map, {floor: r, random: i} = Math;
let l;
do {
l = `$$nextBrowserTick$$${(i() + 8).toString().slice(2)}$$`;
} while (l in e);
const a = l, c = a.length + 9;
const messageTargetOrigin = "undefined" != typeof location && "string" == typeof location.origin ? location.origin : "*";
e[a] = 1;
e.addEventListener("message", evt => {
if (0 !== o.size) {
const t = (evt || 0).data;
const origin = String((evt || 0).origin || "");
if (origin && "null" !== origin && origin !== location.origin) {
return;
}
if ("string" == typeof t && t.length === c && evt.source === (evt.target || 1)) {
const fn = o.get(t);
if (fn) {
"p" === t[0] && (s = null);
o.delete(t);
fn();
}
}
}
}, !1);
const d = (t = o) => {
if (t === o) {
if (s) {
return s;
}
let token = null;
do {
token = `p${a}${r(314159265359 * i() + 314159265359).toString(36)}`;
} while (token && o.has(token));
s = new n(resolve => {
token && o.set(token, resolve);
});
token && e.postMessage(token, messageTargetOrigin);
token = null;
return s;
}
{
let n;
do {
n = `f${a}${r(314159265359 * i() + 314159265359).toString(36)}`;
} while (o.has(n));
o.set(n, t);
e.postMessage(n, messageTargetOrigin);
return null;
}
};
return d.version = 2, d;
})();
const communicationKey = `ck-${Date.now()}-${Math.floor(314159265359 * Math.random() + 314159265359).toString(36)}`;
const Promise = globalThis.Promise;
if (!document.documentElement) {
await Promise.resolve(0);
for (;!document.documentElement; ) {
await new Promise(resolve => nextBrowserTick(resolve)).then().catch(mainLog.warn);
}
}
const textContent = `(${executionScript})("${communicationKey}");\n\n//# sourceURL=debug://tabview-youtube/tabview.execution.js\n`;
let executionInjected = !1;
try {
const script = document.createElement("script");
const nonceSource = document.querySelector("script[nonce]") || document.querySelector("[nonce]") || document.documentElement;
const nonce = nonceSource?.nonce || nonceSource?.getAttribute?.("nonce") || document.documentElement?.getAttribute?.("nonce") || "";
nonce && script.setAttribute("nonce", nonce);
script.textContent = textContent;
(document.head || document.documentElement).appendChild(script);
script.remove();
executionInjected = !0;
} catch (e) {
mainLog.warn("[YouTube+] Script nonce injection failed, falling back:", e);
}
if (!executionInjected) {
try {
const blob = new globalThis.Blob([ textContent ], {
type: "text/javascript"
});
const blobUrl = globalThis.URL.createObjectURL(blob);
const script = document.createElement("script");
script.src = blobUrl;
script.onload = script.onerror = () => {
try {
globalThis.URL.revokeObjectURL(blobUrl);
} catch (e) {}
try {
script.remove();
} catch (e) {}
};
(document.head || document.documentElement).appendChild(script);
executionInjected = !0;
} catch (e) {
mainLog.error("[YouTube+] Failed to inject execution script", e);
throw e;
}
}
const style = document.createElement("style");
const cssContent = `${styles.main.trim()}\n\n/*# sourceURL=debug://tabview-youtube/tabview.main.css */\n`;
const gmAddStyle = "undefined" != typeof window && window.GM_addStyle || null;
if ("function" == typeof gmAddStyle) {
gmAddStyle(cssContent);
} else {
style.textContent = cssContent;
document.documentElement.appendChild(style);
}
const scheduleTabviewI18nTabs = () => {
let attempts = 0;
const tryApply = () => {
if (!(() => {
const container = document.querySelector("#right-tabs");
if (!container) {
return !1;
}
const i18n = "undefined" != typeof window ? window.YouTubePlusI18n : null;
const translate = (key, fallback) => {
if (i18n && "function" == typeof i18n.t) {
const value = i18n.t(key);
if (value && value !== key) {
return value;
}
}
return fallback;
};
const labels = [ {
selector: "#tab-btn1 span",
key: "info",
fallback: "Info"
}, {
selector: "#tab-btn4 span",
key: "videos",
fallback: "Videos"
}, {
selector: "#tab-btn5 span",
key: "playlist",
fallback: "Playlist"
} ];
for (const {selector, key, fallback} of labels) {
const label = container.querySelector(selector);
label && (label.textContent = translate(key, fallback));
}
return !0;
})() && attempts < 20) {
attempts += 1;
setTimeout(tryApply, 250);
}
};
tryApply();
};
const refreshTabviewI18n = () => {
(() => {
const root = document.documentElement;
if (!root) {
return;
}
const i18n = "undefined" != typeof window ? window.YouTubePlusI18n : null;
const translate = (key, fallback) => {
if (i18n && "function" == typeof i18n.t) {
const value = i18n.t(key);
if (value && value !== key) {
return value;
}
}
return fallback;
};
const toCssString = value => {
const text = String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
return `"${text}"`;
};
if (root.style) {
root.style.setProperty("--tabview-text-loading", toCssString(translate("loading", "Loading")));
root.style.setProperty("--tabview-text-fetching", toCssString(translate("fetching", "Fetching")));
}
})();
scheduleTabviewI18nTabs();
};
let tabviewI18nListenerBound = !1;
if ("undefined" != typeof window) {
if (YouTubeUtils?.cleanupManager?.registerListener) {
YouTubeUtils.cleanupManager.registerListener(window, "youtube-plus-i18n-ready", refreshTabviewI18n, {
passive: !0
});
YouTubeUtils.cleanupManager.registerListener(window, "youtube-plus-language-changed", refreshTabviewI18n, {
passive: !0
});
} else {
window.addEventListener("youtube-plus-i18n-ready", refreshTabviewI18n, {
passive: !0
});
window.addEventListener("youtube-plus-language-changed", refreshTabviewI18n, {
passive: !0
});
}
}
(() => {
let attempts = 0;
const tryBind = () => {
const i18n = "undefined" != typeof window ? window.YouTubePlusI18n : null;
if (i18n && "function" == typeof i18n.t) {
refreshTabviewI18n();
if (!tabviewI18nListenerBound && "function" == typeof i18n.onLanguageChange) {
i18n.onLanguageChange(refreshTabviewI18n);
tabviewI18nListenerBound = !0;
}
} else if (attempts < 120) {
attempts += 1;
setTimeout(tryBind, 500);
}
};
tryBind();
})();
scheduleTabviewI18nTabs();
if ("undefined" != typeof window && window.YouTubePlusLazyLoader) {
const {loadOnIdle} = window.YouTubePlusLazyLoader;
loadOnIdle(2e3);
}
!(function injectPerformanceHints() {
try {
const preconnectOrigins = [ "https://i.ytimg.com", "https://yt3.ggpht.com", "https://yt3.googleusercontent.com" ];
preconnectOrigins.forEach(origin => {
if (!document.querySelector(`link[rel="preconnect"][href="${origin}"]`)) {
const link = document.createElement("link");
link.rel = "preconnect";
link.href = origin;
link.crossOrigin = "anonymous";
document.head.appendChild(link);
}
});
const promoteLCPImage = () => {
try {
const firstThumb = document.querySelector("ytd-rich-item-renderer ytd-thumbnail img, ytd-playlist-video-renderer ytd-thumbnail img, ytd-video-renderer ytd-thumbnail img");
if (firstThumb && !firstThumb._ytpLCPPromoted) {
firstThumb.fetchPriority = "high";
firstThumb.loading = "eager";
firstThumb.decoding = "sync";
firstThumb._ytpLCPPromoted = !0;
}
const allThumbs = document.querySelectorAll("ytd-rich-item-renderer ytd-thumbnail img, ytd-playlist-video-renderer ytd-thumbnail img, ytd-compact-video-renderer ytd-thumbnail img");
for (let i = 1; i < allThumbs.length; i++) {
const img = allThumbs[i];
if (!img._ytpDecoding) {
img.decoding = "async";
img._ytpDecoding = !0;
}
}
} catch (e) {}
};
window.addEventListener("yt-navigate-finish", promoteLCPImage, {
passive: !0
});
"loading" !== document.readyState ? promoteLCPImage() : document.addEventListener("DOMContentLoaded", promoteLCPImage, {
once: !0
});
} catch (e) {}
})();
})();

/**
 * YouTube+ Internationalization (i18n) System - v3.2
 * Unified i18n system with integrated loader
 * Supports all major YouTube interface languages
 * @module i18n
 * @version 3.2
 */ !(function() {
"use strict";
const GITHUB_CONFIG_owner = "diorhc", GITHUB_CONFIG_repo = "YTP", GITHUB_CONFIG_branch = "main", GITHUB_CONFIG_basePath = "locales";
const CDN_URLS = {
github: `https://raw.githubusercontent.com/${GITHUB_CONFIG_owner}/${GITHUB_CONFIG_repo}/${GITHUB_CONFIG_branch}/${GITHUB_CONFIG_basePath}`,
jsdelivr: `https://cdn.jsdelivr.net/gh/${GITHUB_CONFIG_owner}/${GITHUB_CONFIG_repo}@${GITHUB_CONFIG_branch}/${GITHUB_CONFIG_basePath}`
};
const AVAILABLE_LANGUAGES = [ "en", "ru", "kr", "fr", "du", "cn", "tw", "jp", "tr", "es", "pt", "de", "it", "pl", "uk", "ar", "hi", "id", "vi", "uz", "kk", "ky", "be", "bg", "az" ];
const LANGUAGE_NAMES = {
en: "English",
ru: "???????",
kr: "???",
fr: "Fran�ais",
du: "Nederlands",
cn: "????",
tw: "????",
jp: "???",
tr: "T�rk�e",
es: "Espa�ol",
pt: "Portugu�s",
de: "Deutsch",
it: "Italiano",
pl: "Polski",
uk: "??????????",
sv: "Svenska",
no: "Norsk",
da: "Dansk",
fi: "Suomi",
cs: "Ce�tina",
sk: "Slovencina",
hu: "Magyar",
ro: "Rom�na",
bg: "?????????",
hr: "Hrvatski",
sr: "??????",
sl: "Sloven�cina",
el: "????????",
lt: "Lietuviu",
lv: "Latvie�u",
et: "Eesti",
mk: "??????????",
sq: "Shqip",
bs: "Bosanski",
is: "�slenska",
ca: "Catal�",
eu: "Euskara",
gl: "Galego",
ar: "???????",
he: "?????",
fa: "?????",
sw: "Kiswahili",
zu: "isiZulu",
af: "Afrikaans",
am: "????",
hi: "??????",
th: "???",
vi: "Ti?ng Vi?t",
id: "Bahasa Indonesia",
ms: "Bahasa Melayu",
bn: "?????",
ta: "?????",
te: "??????",
mr: "?????",
gu: "???????",
kn: "?????",
ml: "??????",
pa: "??????",
fil: "Filipino",
km: "?????????",
lo: "???",
my: "??????",
ne: "??????",
si: "?????",
az: "Az?rbaycanca",
be: "??????????",
hy: "???????",
ka: "???????",
kk: "?????",
ky: "????????",
mn: "??????",
tg: "??????",
uz: "O?zbekcha"
};
const LANGUAGE_FALLBACKS = {
es: "es",
"es-es": "es",
"es-mx": "es",
"es-419": "es",
pt: "pt",
"pt-br": "pt",
"pt-pt": "pt",
de: "de",
"de-de": "de",
"de-at": "de",
"de-ch": "de",
it: "it",
pl: "pl",
uk: "uk",
"uk-ua": "uk",
ar: "ar",
"ar-sa": "ar",
"ar-ae": "ar",
"ar-eg": "ar",
hi: "hi",
"hi-in": "hi",
th: "en",
"th-th": "en",
vi: "vi",
"vi-vn": "vi",
id: "id",
"id-id": "id",
ms: "en",
"ms-my": "en",
sv: "en",
"sv-se": "en",
no: "en",
"nb-no": "en",
"nn-no": "en",
da: "en",
"da-dk": "en",
fi: "en",
"fi-fi": "en",
cs: "en",
"cs-cz": "en",
sk: "en",
"sk-sk": "en",
hu: "en",
"hu-hu": "en",
ro: "en",
"ro-ro": "en",
bg: "bg",
"bg-bg": "bg",
hr: "en",
"hr-hr": "en",
sr: "ru",
"sr-rs": "ru",
sl: "en",
"sl-si": "en",
el: "en",
"el-gr": "en",
he: "en",
"he-il": "en",
iw: "en",
fa: "en",
"fa-ir": "en",
bn: "en",
"bn-in": "en",
ta: "en",
"ta-in": "en",
te: "en",
"te-in": "en",
mr: "en",
"mr-in": "en",
gu: "en",
"gu-in": "en",
kn: "en",
"kn-in": "en",
ml: "en",
"ml-in": "en",
pa: "en",
"pa-in": "en",
fil: "en",
"fil-ph": "en",
tl: "en",
km: "en",
lo: "en",
my: "en",
ne: "en",
si: "en",
sw: "en",
"sw-ke": "en",
zu: "en",
af: "en",
am: "en",
az: "az",
"az-az": "az",
be: "be",
"be-by": "be",
hy: "ru",
ka: "en",
kk: "kk",
"kk-kz": "kk",
ky: "ky",
mn: "ru",
tg: "ru",
uz: "uz",
"uz-uz": "uz",
lt: "en",
"lt-lt": "en",
lv: "en",
"lv-lv": "en",
et: "en",
"et-ee": "en",
mk: "ru",
sq: "en",
bs: "en",
is: "en",
ca: "es",
eu: "es",
gl: "es"
};
const translationsCache = new Map;
const loadingPromises = new Map;
const setTimeout_ = setTimeout.bind(window);
async function fetchJSON(url) {
if ("undefined" != typeof GM_xmlhttpRequest) {
const responseText = await new Promise((resolve, reject) => {
const timeoutId = setTimeout_(() => reject(new Error("i18n request timeout")), 12e3);
GM_xmlhttpRequest({
method: "GET",
url,
timeout: 12e3,
headers: {
Accept: "application/json"
},
onload: response => {
clearTimeout(timeoutId);
response.status >= 200 && response.status < 300 ? resolve(response.responseText || "") : reject(new Error(`HTTP ${response.status}: ${response.statusText || "request failed"}`));
},
onerror: err => {
clearTimeout(timeoutId);
reject(new Error(`Network error: ${String(err)}`));
},
ontimeout: () => {
clearTimeout(timeoutId);
reject(new Error("i18n request timeout"));
}
});
});
return JSON.parse(String(responseText || "{}"));
}
const response = await fetch(url, {
cache: "default",
headers: {
Accept: "application/json"
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
function loadTranslationsFromLoader(lang) {
const languageCode = AVAILABLE_LANGUAGES.includes(lang) ? lang : "en";
if (translationsCache.has(languageCode)) {
return translationsCache.get(languageCode);
}
if (loadingPromises.has(languageCode)) {
return loadingPromises.get(languageCode);
}
const loadPromise = (async () => {
try {
const translations = await (async function fetchTranslation(lang) {
try {
if ("undefined" != typeof window && window.YouTubePlusEmbeddedTranslations) {
const embedded = window.YouTubePlusEmbeddedTranslations[lang];
if (embedded) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][i18n]", `Using embedded translations for ${lang}`);
return embedded;
}
}
} catch (e) {
window.console.warn("[YouTube+][i18n]", "Error reading embedded translations", e);
}
try {
const rawUrl = `${CDN_URLS.github}/${lang}.json`;
return await fetchJSON(rawUrl);
} catch (firstErr) {
try {
const cdnUrl = `${CDN_URLS.jsdelivr}/${lang}.json?_=${Date.now()}`;
window.console.warn("[YouTube+][i18n]", `Raw GitHub fetch failed, trying jsDelivr: ${cdnUrl}`);
return await fetchJSON(cdnUrl);
} catch (err) {
window.console.error("[YouTube+][i18n]", `Failed to fetch translations for ${lang}:`, err, firstErr);
throw err;
}
}
})(languageCode);
try {
const missing = [];
[ "loading", "fetching" ].forEach(k => {
Object.prototype.hasOwnProperty.call(translations, k) || missing.push(k);
});
missing.length > 0 && window.console.warn("[YouTube+][i18n]", `Translations for ${languageCode} missing keys: ${missing.join(", ")} (source may be stale)`);
} catch (e) {}
translationsCache.set(languageCode, translations);
loadingPromises.delete(languageCode);
return translations;
} catch (error) {
loadingPromises.delete(languageCode);
if ("en" !== languageCode) {
return loadTranslationsFromLoader("en");
}
throw error;
}
})();
loadingPromises.set(languageCode, loadPromise);
return loadPromise;
}
let currentLanguage = "en";
let translations = {};
let fallbackTranslationsEn = {};
const translationCache = new Map;
const languageChangeListeners = new Set;
let loadingPromise = null;
function emitI18nEvent(name, detail = {}) {
try {
if ("undefined" == typeof window) {
return;
}
window.dispatchEvent(new CustomEvent(name, {
detail
}));
} catch (e) {
try {
if ("undefined" == typeof window) {
return;
}
window.dispatchEvent(new Event(name));
} catch (e) {}
}
}
const languageMap = {
ko: "kr",
"ko-kr": "kr",
fr: "fr",
"fr-fr": "fr",
"fr-ca": "fr",
"fr-be": "fr",
"fr-ch": "fr",
nl: "du",
"nl-nl": "du",
"nl-be": "du",
zh: "cn",
"zh-cn": "cn",
"zh-hans": "cn",
"zh-sg": "cn",
"zh-tw": "tw",
"zh-hk": "tw",
"zh-hant": "tw",
ja: "jp",
"ja-jp": "jp",
tr: "tr",
"tr-tr": "tr",
ru: "ru",
"ru-ru": "ru",
en: "en",
"en-us": "en",
"en-gb": "en",
"en-au": "en",
"en-ca": "en",
"en-in": "en",
...Object.fromEntries(Object.entries(LANGUAGE_FALLBACKS).map(([key, fallback]) => [ key, fallback ]))
};
function mapToSupportedLanguage(langCode) {
const lower = langCode.toLowerCase();
if (languageMap[lower]) {
return languageMap[lower];
}
if (AVAILABLE_LANGUAGES.includes(lower)) {
return lower;
}
const shortCode = lower.substr(0, 2);
if (languageMap[shortCode]) {
return languageMap[shortCode];
}
if (AVAILABLE_LANGUAGES.includes(shortCode)) {
return shortCode;
}
const fallbacks = LANGUAGE_FALLBACKS;
return fallbacks[lower] ? fallbacks[lower] : fallbacks[shortCode] ? fallbacks[shortCode] : "en";
}
function detectLanguage() {
try {
const ytLang = document.documentElement.lang || window.YouTubeUtils?.$("html")?.getAttribute("lang");
if (ytLang) {
const mapped = mapToSupportedLanguage(ytLang);
return mapped;
}
try {
const urlParams = new URLSearchParams(window.location.search);
const hlParam = urlParams.get("hl");
if (hlParam) {
const mapped = mapToSupportedLanguage(hlParam);
return mapped;
}
} catch {}
try {
const ytConfig = window.ytcfg || window.yt?.config_;
if (ytConfig && "function" == typeof ytConfig.get) {
const hl = ytConfig.get("HL") || ytConfig.get("GAPI_LOCALE");
if (hl) {
const mapped = mapToSupportedLanguage(hl);
return mapped;
}
}
} catch {}
const browserLang = navigator.language || navigator.userLanguage || "en";
const mapped = mapToSupportedLanguage(browserLang);
return mapped;
} catch (error) {
window.console.error("[YouTube+][i18n]", "Error detecting language:", error);
return "en";
}
}
async function loadTranslations() {
if (loadingPromise) {
await loadingPromise;
return !0;
}
loadingPromise = (async () => {
try {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][i18n]", `Loading translations for ${currentLanguage}...`);
translations = await loadTranslationsFromLoader(currentLanguage);
if (!fallbackTranslationsEn || 0 === Object.keys(fallbackTranslationsEn).length) {
try {
const embeddedEn = "undefined" != typeof window && window.YouTubePlusEmbeddedTranslations && window.YouTubePlusEmbeddedTranslations.en;
fallbackTranslationsEn = embeddedEn && "object" == typeof embeddedEn ? embeddedEn : await loadTranslationsFromLoader("en");
} catch (e) {
fallbackTranslationsEn = {};
}
}
translationCache.clear();
window.YouTubeUtils?.logger?.debug?.("[YouTube+][i18n]", `? Loaded ${Object.keys(translations).length} translations for ${currentLanguage}`);
return !0;
} catch (error) {
window.console.error("[YouTube+][i18n]", "Failed to load translations:", error);
if ("en" !== currentLanguage) {
currentLanguage = "en";
return loadTranslations();
}
return !1;
} finally {
loadingPromise = null;
}
})();
return loadingPromise;
}
function translate(key, params = {}) {
const cacheKey = `${key}:${JSON.stringify(params)}`;
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey) ?? key;
}
let text = translations[key];
if (!text) {
const enText = fallbackTranslationsEn ? fallbackTranslationsEn[key] : void 0;
if (enText) {
text = enText;
} else {
Object.keys(translations).length > 0 && Object.keys(fallbackTranslationsEn).length > 0 && window.console.warn("[YouTube+][i18n]", `Missing translation for key: ${key}`);
text = key;
}
}
Object.keys(params).length > 0 && Object.keys(params).forEach(param => {
const token = `{${param}}`;
text = text.split(token).join(String(params[param]));
});
translationCache.set(cacheKey, text);
return text;
}
function getLanguage() {
return currentLanguage;
}
function getAvailableLanguages() {
return AVAILABLE_LANGUAGES;
}
async function initialize() {
try {
currentLanguage = detectLanguage();
window.YouTubeUtils?.logger?.debug?.("[YouTube+][i18n]", `Detected language: ${currentLanguage} (${LANGUAGE_NAMES[currentLanguage] || currentLanguage})`);
await loadTranslations();
emitI18nEvent("youtube-plus-i18n-ready", {
language: currentLanguage
});
} catch (error) {
window.console.error("[YouTube+][i18n]", "Initialization error:", error);
currentLanguage = "en";
}
}
const i18nAPI = {
t: translate,
translate,
getLanguage,
setLanguage: async function setLanguage(lang) {
if (lang === currentLanguage) {
return !0;
}
const oldLang = currentLanguage;
currentLanguage = lang;
try {
const success = await loadTranslations();
if (success) {
languageChangeListeners.forEach(listener => {
try {
listener(currentLanguage, oldLang);
} catch (error) {
window.console.error("[YouTube+][i18n]", "Error in language change listener:", error);
}
});
emitI18nEvent("youtube-plus-language-changed", {
language: currentLanguage,
previousLanguage: oldLang
});
}
return success;
} catch (error) {
window.console.error("[YouTube+][i18n]", "Failed to change language:", error);
currentLanguage = oldLang;
return !1;
}
},
detectLanguage,
getAllTranslations: function getAllTranslations() {
return {
...translations
};
},
getAvailableLanguages,
hasTranslation: function hasTranslation(key) {
return void 0 !== translations[key];
},
addTranslation: function addTranslation(key, value) {
translations[key] = value;
translationCache.clear();
},
addTranslations: function addTranslations(newTranslations) {
Object.assign(translations, newTranslations);
translationCache.clear();
},
onLanguageChange: function onLanguageChange(callback) {
languageChangeListeners.add(callback);
return () => languageChangeListeners.delete(callback);
},
formatNumber: function formatNumber(num, options = {}) {
try {
const lang = getLanguage();
const localeMap = {
ru: "ru-RU",
kr: "ko-KR",
fr: "fr-FR",
du: "nl-NL",
cn: "zh-CN",
tw: "zh-TW",
jp: "ja-JP",
tr: "tr-TR"
};
const locale = localeMap[lang] || "en-US";
return new Intl.NumberFormat(locale, options).format(num);
} catch (error) {
window.console.error("[YouTube+][i18n]", "Error formatting number:", error);
return String(num);
}
},
formatDate: function formatDate(date, options = {}) {
try {
const lang = getLanguage();
const localeMap = {
ru: "ru-RU",
kr: "ko-KR",
fr: "fr-FR",
du: "nl-NL",
cn: "zh-CN",
tw: "zh-TW",
jp: "ja-JP",
tr: "tr-TR"
};
const locale = localeMap[lang] || "en-US";
const dateObj = date instanceof Date ? date : new Date(date);
return new Intl.DateTimeFormat(locale, options).format(dateObj);
} catch (error) {
window.console.error("[YouTube+][i18n]", "Error formatting date:", error);
return String(date);
}
},
pluralize: function pluralize(count, singular, plural, few) {
const lang = getLanguage();
if ("ru" === lang && few) {
const mod10 = count % 10;
const mod100 = count % 100;
return 1 === mod10 && 11 !== mod100 ? singular : mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20) ? few : plural;
}
return 1 === count ? singular : plural;
},
clearCache: function clearCache() {
translationCache.clear();
},
getCacheStats: function getCacheStats() {
return {
size: translationCache.size,
currentLanguage,
availableLanguages: getAvailableLanguages(),
translationsLoaded: Object.keys(translations).length
};
},
isReady: () => null === loadingPromise && Object.keys(translations).length > 0,
loadTranslations,
initialize
};
if ("undefined" != typeof window) {
window.YouTubePlusI18n = i18nAPI;
window.YouTubePlusI18nLoader = {
loadTranslations: loadTranslationsFromLoader,
AVAILABLE_LANGUAGES,
LANGUAGE_NAMES,
CDN_URLS
};
if (window.YouTubeUtils) {
window.YouTubeUtils.i18n = i18nAPI;
window.YouTubeUtils.t = translate;
window.YouTubeUtils.getLanguage = getLanguage;
}
}
"undefined" != typeof module && module.exports && (module.exports = i18nAPI);
initialize().then(() => {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][i18n]", "i18n system initialized successfully");
});
})();

!(function() {
"use strict";
function createNavItem(section, label, icon, active = !1) {
const activeClass = active ? " active" : "";
return `\n    <div class="ytp-plus-settings-nav-item${activeClass}" data-section="${section}" data-label="${label}" title="${label}" aria-label="${label}">\n      ${icon}\n      <span class="ytp-plus-settings-nav-item-label">${label}</span>\n    </div>\n  `;
}
function createSettingsItem(label, description, setting, checked) {
const inputId = `ytp-plus-setting-${setting}`;
return `\n    <div class="ytp-plus-settings-item">\n      <div>\n        <label class="ytp-plus-settings-item-label" for="${inputId}">${label}</label>\n        <div class="ytp-plus-settings-item-description">${description}</div>\n      </div>\n      <input type="checkbox" id="${inputId}" class="ytp-plus-settings-checkbox" data-setting="${setting}" ${checked ? "checked" : ""}>\n    </div>\n  `;
}
function createSettingsSelect(label, description, setting, value, options) {
const inputId = `ytp-plus-setting-${setting}`;
const opts = options.map(o => `<option value="${o.value}"${String(value) === String(o.value) ? " selected" : ""}>${o.label}</option>`).join("");
return `\n    <div class="ytp-plus-settings-item">\n      <div>\n        <label class="ytp-plus-settings-item-label" for="${inputId}">${label}</label>\n        <div class="ytp-plus-settings-item-description">${description}</div>\n      </div>\n      <select id="${inputId}" class="ytp-plus-settings-select" data-setting="${setting}">${opts}</select>\n    </div>\n  `;
}
function createDownloadSiteOption(site) {
const {key, name, description, checked, hasControls, controls} = site;
const inputId = `download-site-${key}`;
return `\n    <div class="download-site-option">\n      <div class="download-site-header">\n        <label for="${inputId}" class="download-site-label">\n          <div class="download-site-name">${name}</div>\n          <div class="download-site-desc">${description}</div>\n        </label>\n        <input type="checkbox" id="${inputId}" class="ytp-plus-settings-checkbox" data-setting="downloadSite_${key}" ${checked ? "checked" : ""}>\n      </div>\n      ${hasControls ? `<div class="download-site-controls" style="display:${checked ? "block" : "none"};">${controls}</div>` : ""}\n    </div>\n  `;
}
function createExternalDownloaderControls(customization, t) {
const name = customization?.name || "SSYouTube";
const url = customization?.url || "https://ssyoutube.com/watch?v={videoId}";
return `\n    <input type="text" placeholder="${t("siteName")}" value="${name}" \n        data-site="externalDownloader" data-field="name" class="download-site-input"\n        aria-label="${t("siteName")}">\n    <input type="text" placeholder="${t("urlTemplate")}" value="${url}" \n      data-site="externalDownloader" data-field="url" class="download-site-input small"\n      aria-label="${t("urlTemplate")}">\n    <div class="download-site-cta">\n      <button class="glass-button" id="download-externalDownloader-save">${t("saveButton")}</button>\n      <button class="glass-button danger" id="download-externalDownloader-reset">${t("resetButton")}</button>\n    </div>\n  `;
}
function tr(t, key, fallback) {
try {
const v = t(key);
if ("string" == typeof v && v && v !== key) {
return v;
}
} catch (e) {}
return fallback;
}
function createStyleSubmenu(settings, t) {
const display = settings.enableZenStyles ? "block" : "none";
const rawSideVideosColumns = Number(settings.zenStyles?.sideVideosColumns);
const sideVideosColumnsValue = Number.isFinite(rawSideVideosColumns) ? Math.max(0, Math.min(2, rawSideVideosColumns)) : 0;
const sideVideosColumnsEnabled = !0 === settings.zenStyles?.sideVideosColumnsEnabled || sideVideosColumnsValue > 0;
const rows = [ {
label: tr(t, "zenStyleThumbnailHoverLabel", "Thumbnail hover preview"),
desc: tr(t, "zenStyleThumbnailHoverDesc", "Enlarge inline preview player on hover"),
key: "zenStyles.thumbnailHover",
value: settings.zenStyles?.thumbnailHover
}, {
label: tr(t, "zenStyleImmersiveSearchLabel", "Immersive search"),
desc: tr(t, "zenStyleImmersiveSearchDesc", "Centered searchbox experience when focused"),
key: "zenStyles.immersiveSearch",
value: settings.zenStyles?.immersiveSearch
}, {
label: tr(t, "zenStyleHideVoiceSearchLabel", "Hide Voice Search"),
desc: tr(t, "zenStyleHideVoiceSearchDesc", "Remove microphone button from the header"),
key: "zenStyles.hideVoiceSearch",
value: settings.zenStyles?.hideVoiceSearch
}, {
label: tr(t, "zenStyleTransparentHeaderLabel", "Transparent Header"),
desc: tr(t, "zenStyleTransparentHeaderDesc", "Make the top header transparent"),
key: "zenStyles.transparentHeader",
value: settings.zenStyles?.transparentHeader
}, {
label: tr(t, "zenStyleHideSideGuideLabel", "Hide Side Guide"),
desc: tr(t, "zenStyleHideSideGuideDesc", "Completely hide the sidebar guide"),
key: "zenStyles.hideSideGuide",
value: settings.zenStyles?.hideSideGuide
}, {
label: tr(t, "zenStyleCleanSideGuideLabel", "Clean Side Guide"),
desc: tr(t, "zenStyleCleanSideGuideDesc", "Remove Premium/Sports/Settings from sidebar"),
key: "zenStyles.cleanSideGuide",
value: settings.zenStyles?.cleanSideGuide
}, {
label: tr(t, "zenStyleFixFeedLayoutLabel", "Fix Feed Layout"),
desc: tr(t, "zenStyleFixFeedLayoutDesc", "Improve video grid layout on home page"),
key: "zenStyles.fixFeedLayout",
value: settings.zenStyles?.fixFeedLayout
}, {
label: tr(t, "zenStyleCompactFeedLabel", "Compact Feed"),
desc: tr(t, "zenStyleCompactFeedDesc", "Reduce feed spacing and show quick actions inline on hover"),
key: "zenStyles.compactFeed",
value: settings.zenStyles?.compactFeed
}, {
label: tr(t, "zenStyleBetterCaptionsLabel", "Better Captions"),
desc: tr(t, "zenStyleBetterCaptionsDesc", "Enhanced subtitle styling with blur backdrop"),
key: "zenStyles.betterCaptions",
value: settings.zenStyles?.betterCaptions
}, {
label: tr(t, "zenStylePlayerBlurLabel", "Player Controls Blur"),
desc: tr(t, "zenStylePlayerBlurDesc", "Add blur effect to player controls"),
key: "zenStyles.playerBlur",
value: settings.zenStyles?.playerBlur
}, {
label: tr(t, "zenStyleTheaterEnhancementsLabel", "Theater Enhancements"),
desc: tr(t, "zenStyleTheaterEnhancementsDesc", "Floating comments panel and improved theater mode"),
key: "zenStyles.theaterEnhancements",
value: settings.zenStyles?.theaterEnhancements
}, {
label: tr(t, "zenStyleMiscLabel", "Misc Enhancements"),
desc: tr(t, "zenStyleMiscDesc", "Compact feed, hover menus, and other minor improvements"),
key: "zenStyles.misc",
value: settings.zenStyles?.misc
} ];
return `\n    <div class="style-submenu" data-submenu="style" style="display:${display};">\n      <div class="glass-card style-submenu-container">\n        ${rows.map(r => createSettingsItem(r.label, r.desc, r.key, r.value)).join("")}\n        ${(function createSettingsToggleWithSelectSubmenu(label, description, toggleSetting, checked, submenuKey, selectLabel, selectDescription, selectSetting, selectValue, options) {
const toggleInputId = `ytp-plus-setting-${toggleSetting}`;
return `\n    <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu">\n      <div>\n        <label class="ytp-plus-settings-item-label" for="${toggleInputId}">${label}</label>\n        <div class="ytp-plus-settings-item-description">${description}</div>\n      </div>\n      <div class="ytp-plus-settings-item-actions">\n        <button\n          type="button"\n          class="ytp-plus-submenu-toggle"\n          data-submenu="${submenuKey}"\n          aria-label="Toggle ${submenuKey} submenu"\n          aria-expanded="${checked ? "true" : "false"}"\n          ${checked ? "" : "disabled"}\n          style="display:${checked ? "inline-flex" : "none"};"\n        >\n          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n            <polyline points="6 9 12 15 18 9"></polyline>\n          </svg>\n        </button>\n        <input type="checkbox" id="${toggleInputId}" class="ytp-plus-settings-checkbox" data-setting="${toggleSetting}" ${checked ? "checked" : ""} aria-label="${label}">\n      </div>\n    </div>\n    <div class="style-side-videos-submenu" data-submenu="${submenuKey}" style="display:${checked ? "block" : "none"};margin-left:12px;margin-bottom:8px;">\n      <div class="glass-card" style="display:flex;flex-direction:column;gap:8px;">\n        ${createSettingsSelect(selectLabel, selectDescription, selectSetting, selectValue, options)}\n      </div>\n    </div>\n  `;
})(tr(t, "zenStyleSideVideosColumnsLabel", "Side Videos Columns"), tr(t, "zenStyleSideVideosColumnsDesc", "Choose how many columns to use for side videos in Zen mode"), "zenStyles.sideVideosColumnsEnabled", sideVideosColumnsEnabled, "style-side-videos", tr(t, "zenStyleSideVideosColumnsLabel", "Side Videos Columns"), tr(t, "zenStyleSideVideosColumnsDesc", "Choose how many columns to use for side videos in Zen mode"), "zenStyles.sideVideosColumns", sideVideosColumnsValue, [ {
value: 0,
label: "Default (Off)"
}, {
value: 1,
label: "1 Column"
}, {
value: 2,
label: "2 Columns"
} ])}\n      </div>\n    </div>\n  `;
}
function createBasicSettingsSection(settings, t) {
const downloadEnabled = !!settings.enableDownload;
const styleEnabled = !1 !== settings.enableZenStyles;
const speedEnabled = !!settings.enableSpeedControl;
return `\n    <div class="ytp-plus-settings-section" data-section="basic">\n      <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu">\n        <div>\n          <label class="ytp-plus-settings-item-label" for="ytp-plus-setting-enableZenStyles">${tr(t, "zenStylesTitle", "Zen styles")}</label>\n          <div class="ytp-plus-settings-item-description">${tr(t, "zenStylesDesc", "Optional UI tweaks and cosmetic improvements")}</div>\n        </div>\n        <div class="ytp-plus-settings-item-actions">\n          <button\n            type="button"\n            class="ytp-plus-submenu-toggle"\n            data-submenu="style"\n            aria-label="Toggle styles submenu"\n            aria-expanded="${styleEnabled ? "true" : "false"}"\n            ${styleEnabled ? "" : "disabled"}\n            style="display:${styleEnabled ? "inline-flex" : "none"};"\n          >\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n              <polyline points="6 9 12 15 18 9"></polyline>\n            </svg>\n          </button>\n          <input type="checkbox" id="ytp-plus-setting-enableZenStyles" class="ytp-plus-settings-checkbox" data-setting="enableZenStyles" ${styleEnabled ? "checked" : ""}>\n        </div>\n      </div>\n      ${createStyleSubmenu(settings, t)}\n      <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu">\n        <div>\n          <label class="ytp-plus-settings-item-label" for="ytp-plus-setting-enableSpeedControl">${t("speedControl")}</label>\n          <div class="ytp-plus-settings-item-description">${t("speedControlDesc")}</div>\n        </div>\n        <div class="ytp-plus-settings-item-actions">\n          <button\n            type="button"\n            class="ytp-plus-submenu-toggle"\n            data-submenu="speed"\n            aria-label="Toggle speed submenu"\n            aria-expanded="${speedEnabled ? "true" : "false"}"\n            ${speedEnabled ? "" : "disabled"}\n            style="display:${speedEnabled ? "inline-flex" : "none"};"\n          >\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n              <polyline points="6 9 12 15 18 9"></polyline>\n            </svg>\n          </button>\n          <input type="checkbox" id="ytp-plus-setting-enableSpeedControl" class="ytp-plus-settings-checkbox" data-setting="enableSpeedControl" ${speedEnabled ? "checked" : ""}>\n        </div>\n      </div>\n      ${(function createSpeedControlSubmenu(settings, t) {
const display = settings.enableSpeedControl ? "block" : "none";
const decrease = (settings.speedControlHotkeys?.decrease || "g").slice(0, 1).toLowerCase();
const increase = (settings.speedControlHotkeys?.increase || "h").slice(0, 1).toLowerCase();
const reset = (settings.speedControlHotkeys?.reset || "b").slice(0, 1).toLowerCase();
return `\n    <div class="speed-submenu" data-submenu="speed" style="display:${display};">\n      <div class="glass-card speed-submenu-container">\n        <div class="ytp-plus-settings-item speed-hotkeys-row">\n          <div class="speed-hotkeys-info">\n            <div class="ytp-plus-settings-item-label">${tr(t, "speedHotkeysTitle", "Keyboard hotkeys")}</div>\n            <div class="ytp-plus-settings-item-description">${tr(t, "speedHotkeysDesc", "Use single-letter shortcuts to decrease/increase/reset playback speed")}</div>\n            <div class="speed-hotkeys-fields">\n              <label class="speed-hotkey-field">                \n                <input\n                  type="text"\n                  class="speed-hotkey-input"\n                  data-speed-hotkey="decrease"\n                  value="${decrease}"\n                  maxlength="1"\n                  autocomplete="off"\n                  spellcheck="false"\n                >\n                <span>${tr(t, "decreaseSpeedHotkey", "Decrease")}</span>\n              </label>\n              <label class="speed-hotkey-field">                \n                <input\n                  type="text"\n                  class="speed-hotkey-input"\n                  data-speed-hotkey="increase"\n                  value="${increase}"\n                  maxlength="1"\n                  autocomplete="off"\n                  spellcheck="false"\n                >\n                <span>${tr(t, "increaseSpeedHotkey", "Increase")}</span>\n              </label>\n              <label class="speed-hotkey-field">                \n                <input\n                  type="text"\n                  class="speed-hotkey-input"\n                  data-speed-hotkey="reset"\n                  value="${reset}"\n                  maxlength="1"\n                  autocomplete="off"\n                  spellcheck="false"\n                >\n                <span>${tr(t, "resetButton", "Reset")}</span>\n              </label>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  `;
})(settings, t)}\n      ${createSettingsItem(t("screenshotButton"), t("screenshotButtonDesc"), "enableScreenshot", settings.enableScreenshot)}\n      <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu">\n        <div>\n          <label class="ytp-plus-settings-item-label" for="ytp-plus-setting-enableDownload">${t("downloadButton")}</label>\n          <div class="ytp-plus-settings-item-description">${t("downloadButtonDesc")}</div>\n        </div>\n        <div class="ytp-plus-settings-item-actions">\n          <button\n            type="button"\n            class="ytp-plus-submenu-toggle"\n            data-submenu="download"\n            aria-label="Toggle download submenu"\n            aria-expanded="${downloadEnabled ? "true" : "false"}"\n            ${downloadEnabled ? "" : "disabled"}\n            style="display:${downloadEnabled ? "inline-flex" : "none"};"\n          >\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n              <polyline points="6 9 12 15 18 9"></polyline>\n            </svg>\n          </button>\n          <input type="checkbox" id="ytp-plus-setting-enableDownload" class="ytp-plus-settings-checkbox" data-setting="enableDownload" ${settings.enableDownload ? "checked" : ""}>\n        </div>\n      </div>\n      ${(function createDownloadSubmenu(settings, t) {
const display = settings.enableDownload ? "block" : "none";
const sites = [ {
key: "externalDownloader",
name: settings.downloadSiteCustomization?.externalDownloader?.name || "SSYouTube",
description: t("customDownloader"),
checked: settings.downloadSites?.externalDownloader,
hasControls: !0,
controls: createExternalDownloaderControls(settings.downloadSiteCustomization?.externalDownloader, t)
}, {
key: "ytdl",
name: t("byYTDL"),
description: t("customDownload"),
checked: settings.downloadSites?.ytdl,
hasControls: !0,
controls: '\n    <div class="download-site-cta one-btn">\n      <button class="glass-button" id="open-ytdl-github">\n        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">\n              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>\n              <polyline points="15,3 21,3 21,9"/>\n              <line x1="10" y1="14" x2="21" y2="3"/>\n            </svg>\n        GitHub\n      </button>\n    </div>\n  '
}, {
key: "direct",
name: t("directDownload"),
description: t("directDownloadDesc"),
checked: settings.downloadSites?.direct,
hasControls: !1
} ];
return `\n    <div class="download-submenu" data-submenu="download" style="display:${display};">\n      <div class="glass-card download-submenu-container">\n        ${sites.map(site => createDownloadSiteOption(site)).join("")}\n      </div>\n    </div>\n  `;
})(settings, t)}\n    </div>\n  `;
}
function getMusicSettings() {
const defaults = {
enableMusic: !0,
immersiveSearchStyles: !0,
hoverStyles: !0,
playerSidebarStyles: !0,
centeredPlayerStyles: !0,
playerBarStyles: !0,
centeredPlayerBarStyles: !0,
miniPlayerStyles: !0,
scrollToTopStyles: !0
};
try {
if ("undefined" != typeof GM_getValue) {
const stored = GM_getValue("youtube-plus-music-settings", null);
if ("string" == typeof stored && stored) {
const parsed = JSON.parse(stored);
if (parsed && "object" == typeof parsed) {
const merged = {
...defaults
};
const mergedSettings = merged;
const parsedSettings = parsed;
"boolean" == typeof parsed.enableMusic && (merged.enableMusic = parsed.enableMusic);
for (const key of Object.keys(defaults)) {
"enableMusic" !== key && "boolean" == typeof parsedSettings[key] && (mergedSettings[key] = parsedSettings[key]);
}
"boolean" == typeof parsed.enableImmersiveSearch && (merged.immersiveSearchStyles = parsed.enableImmersiveSearch);
"boolean" == typeof parsed.enableSidebarHover && (merged.hoverStyles = parsed.enableSidebarHover);
"boolean" == typeof parsed.enableCenteredPlayer && (merged.centeredPlayerStyles = parsed.enableCenteredPlayer);
"boolean" == typeof parsed.enableScrollToTop && (merged.scrollToTopStyles = parsed.enableScrollToTop);
return merged;
}
}
}
} catch (e) {}
try {
const stored = localStorage.getItem("youtube-plus-music-settings");
if (stored) {
const parsed = JSON.parse(stored);
if (parsed && "object" == typeof parsed) {
const merged = {
...defaults
};
const mergedSettings2 = merged;
const parsedSettings2 = parsed;
"boolean" == typeof parsed.enableMusic && (merged.enableMusic = parsed.enableMusic);
for (const key of Object.keys(defaults)) {
"enableMusic" !== key && "boolean" == typeof parsedSettings2[key] && (mergedSettings2[key] = parsedSettings2[key]);
}
"boolean" == typeof parsed.enableImmersiveSearch && (merged.immersiveSearchStyles = parsed.enableImmersiveSearch);
"boolean" == typeof parsed.enableSidebarHover && (merged.hoverStyles = parsed.enableSidebarHover);
"boolean" == typeof parsed.enableCenteredPlayer && (merged.centeredPlayerStyles = parsed.enableCenteredPlayer);
"boolean" == typeof parsed.enableScrollToTop && (merged.scrollToTopStyles = parsed.enableScrollToTop);
const legacyEnabled = !!(parsed.enableMusicStyles || parsed.enableMusicEnhancements || parsed.enableImmersiveSearch || parsed.enableSidebarHover || parsed.enableCenteredPlayer || parsed.enableScrollToTop);
legacyEnabled && "boolean" != typeof parsed.enableMusic && (merged.enableMusic = !0);
return merged;
}
}
} catch (e) {
window.console.warn("[YouTube+] Failed to load music settings:", e);
}
return defaults;
}
function createAdvancedSettingsSection(settings, t) {
const musicSettings = getMusicSettings();
const musicEnabled = !!musicSettings.enableMusic;
const enhancedEnabled = !1 !== settings.enableEnhanced;
const enhancedSettings = {
enableTabview: !1 !== settings.enableTabview,
enableCommentTranslate: !1 !== settings.enableCommentTranslate,
enablePlayAll: !1 !== settings.enablePlayAll,
enableResumeTime: !1 !== settings.enableResumeTime,
enableZoom: !1 !== settings.enableZoom,
enableThumbnail: !1 !== settings.enableThumbnail,
enablePlaylistSearch: !1 !== settings.enablePlaylistSearch,
enableScrollToTopButton: !1 !== settings.enableScrollToTopButton,
enableRememberManualQuality: !1 !== settings.enableRememberManualQuality
};
return `\n    <div class="ytp-plus-settings-section hidden" data-section="advanced">\n      <div class="ytp-plus-settings-group">\n        <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu">\n          <div>\n            <label class="ytp-plus-settings-item-label">${tr(t, "enhancedFeaturesTitle", "Enhanced Features")}</label>\n            <div class="ytp-plus-settings-item-description">${tr(t, "enhancedFeaturesDesc", "Additional productivity features and UI enhancements")}</div>\n          </div>\n          <div class="ytp-plus-settings-item-actions">\n            <button\n              type="button"\n              class="ytp-plus-submenu-toggle"\n              data-submenu="enhanced"\n              aria-label="Toggle enhanced features submenu"\n              aria-expanded="${enhancedEnabled ? "true" : "false"}"\n              ${enhancedEnabled ? "" : "disabled"}\n              style="display:${enhancedEnabled ? "inline-flex" : "none"};"\n            >\n              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                <polyline points="6 9 12 15 18 9"></polyline>\n              </svg>\n            </button>\n            <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableEnhanced" ${enhancedEnabled ? "checked" : ""}>\n          </div>\n        </div>\n\n        <div class="enhanced-submenu" data-submenu="enhanced" style="display:${enhancedEnabled ? "block" : "none"};margin-left:12px;margin-bottom:12px;">\n          <div class="glass-card" style="display:flex;flex-direction:column;gap:8px;">\n            <div class="endscreen-settings-slot"></div>\n            ${createSettingsItem(tr(t, "enableTabviewLabel", "Tabview"), tr(t, "enableTabviewDesc", "Show description, comments and related videos in a tab panel on the right"), "enableTabview", enhancedSettings.enableTabview)}\n            ${createSettingsItem(tr(t, "enableCommentTranslateLabel", "Comment Translate"), tr(t, "enableCommentTranslateDesc", "Add a translate button to each comment"), "enableCommentTranslate", enhancedSettings.enableCommentTranslate)}\n            ${createSettingsItem(tr(t, "enablePlayAllLabel", "Play All Button"), tr(t, "enablePlayAllDesc", "Add Play All button to playlists and channel pages"), "enablePlayAll", enhancedSettings.enablePlayAll)}\n            ${createSettingsItem(tr(t, "enableResumeTimeLabel", "Resume Playback"), tr(t, "enableResumeTimeDesc", "Remember video position and offer to resume"), "enableResumeTime", enhancedSettings.enableResumeTime)}\n            ${createSettingsItem(tr(t, "enableZoomLabel", "Video Zoom"), tr(t, "enableZoomDesc", "Enable zoom and pan controls for video player"), "enableZoom", enhancedSettings.enableZoom)}\n            ${createSettingsItem(tr(t, "thumbnailPreview", "Thumbnail Preview"), tr(t, "thumbnailPreviewDesc", "Add a button to thumbnails/avatars/banners to open the original image"), "enableThumbnail", enhancedSettings.enableThumbnail)}\n            ${createSettingsItem(tr(t, "enablePlaylistSearchLabel", "Playlist Search"), tr(t, "enablePlaylistSearchDesc", "Add search functionality to playlist panels"), "enablePlaylistSearch", enhancedSettings.enablePlaylistSearch)}\n            ${createSettingsItem(tr(t, "scrollToTopButtonLabel", "Scroll to Top"), tr(t, "scrollToTopButtonDesc", "Show scroll-to-top button on pages"), "enableScrollToTopButton", enhancedSettings.enableScrollToTopButton)}\n            ${createSettingsItem(tr(t, "rememberManualQualityLabel", "Remember Manual Quality"), tr(t, "rememberManualQualityDesc", "Keep the last video quality you selected manually when opening the next video"), "enableRememberManualQuality", enhancedSettings.enableRememberManualQuality)}\n            <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu" style="margin-top:4px;">\n              <div>\n                <label class="ytp-plus-settings-item-label">${tr(t, "enableLoopLabel", "Loop")}</label>\n                <div class="ytp-plus-settings-item-description">${tr(t, "enableLoopDesc", "Enable looping of videos and custom segments (A → B)")}</div>\n              </div>\n              <div class="ytp-plus-settings-item-actions">\n                <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableLoop" ${settings.enableLoop ? "checked" : ""}>\n              </div>\n            </div>\n            ${(function createLoopSubmenu(settings, t) {
const display = settings.enableLoop ? "block" : "none";
const setPointA = (settings.loopHotkeys?.setPointA || "k").slice(0, 1).toLowerCase();
const setPointB = (settings.loopHotkeys?.setPointB || "l").slice(0, 1).toLowerCase();
const resetPoints = (settings.loopHotkeys?.resetPoints || "o").slice(0, 1).toLowerCase();
return `\n    <div class="loop-submenu" data-submenu="loop" style="display:${display};margin:0 0 4px 0;">\n      <div class="ytp-plus-settings-item loop-hotkeys-row" style="margin-bottom:0;">\n        <div class="loop-hotkeys-info">\n          <div class="ytp-plus-settings-item-label">${tr(t, "loopSegmentTitle", "Loop A → B")}</div>\n          <div class="ytp-plus-settings-item-description">${tr(t, "loopSegmentDesc", "Repeat a custom segment of the video (A → B)")}</div>\n          <div class="loop-hotkeys-fields" style="margin-top:12px;">\n            <label class="loop-hotkey-field">                \n              <input\n                type="text"\n                class="loop-hotkey-input"\n                data-loop-hotkey="setPointA"\n                value="${setPointA}"\n                maxlength="1"\n                autocomplete="off"\n                spellcheck="false"\n              >\n              <span>${tr(t, "setPointAHotkey", "Set Point A")}</span>\n            </label>\n            <label class="loop-hotkey-field">                \n              <input\n                type="text"\n                class="loop-hotkey-input"\n                data-loop-hotkey="setPointB"\n                value="${setPointB}"\n                maxlength="1"\n                autocomplete="off"\n                spellcheck="false"\n              >\n              <span>${tr(t, "setPointBHotkey", "Set Point B")}</span>\n            </label>\n            <label class="loop-hotkey-field">                \n              <input\n                type="text"\n                class="loop-hotkey-input"\n                data-loop-hotkey="resetPoints"\n                value="${resetPoints}"\n                maxlength="1"\n                autocomplete="off"\n                spellcheck="false"\n              >\n              <span>${tr(t, "resetButton", "Reset")}</span>\n            </label>\n          </div>\n        </div>\n      </div>\n    </div>\n  `;
})(settings, t)}\n          </div>\n        </div>\n\n        <div class="ytp-plus-settings-item ytp-plus-settings-item--with-submenu">\n          <div>\n            <label class="ytp-plus-settings-item-label">${t("youtubeMusicTitle")}</label>\n            <div class="ytp-plus-settings-item-description">${t("youtubeMusicDesc")}</div>\n          </div>\n          <div class="ytp-plus-settings-item-actions">\n            <button\n              type="button"\n              class="ytp-plus-submenu-toggle"\n              data-submenu="music"\n              aria-label="Toggle YouTube Music submenu"\n              aria-expanded="${musicEnabled ? "true" : "false"}"\n              ${musicEnabled ? "" : "disabled"}\n              style="display:${musicEnabled ? "inline-flex" : "none"};"\n            >\n              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                <polyline points="6 9 12 15 18 9"></polyline>\n              </svg>\n            </button>\n            <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableMusic" ${musicSettings.enableMusic ? "checked" : ""}>\n          </div>\n        </div>\n\n        <div class="music-submenu" data-submenu="music" style="display:${musicEnabled ? "block" : "none"};margin-left:12px;margin-bottom:12px;">\n          <div class="glass-card" style="display:flex;flex-direction:column;gap:8px;">\n            ${createSettingsItem(t("immersiveSearchLabel"), t("immersiveSearchDesc"), "immersiveSearchStyles", musicSettings.immersiveSearchStyles)}\n            ${createSettingsItem(t("sidebarHoverLabel"), t("sidebarHoverDesc"), "hoverStyles", musicSettings.hoverStyles)}\n            ${createSettingsItem(t("playerSidebarStylesLabel"), t("playerSidebarStylesDesc"), "playerSidebarStyles", musicSettings.playerSidebarStyles)}\n            ${createSettingsItem(t("centeredPlayerLabel"), t("centeredPlayerDesc"), "centeredPlayerStyles", musicSettings.centeredPlayerStyles)}\n            ${createSettingsItem(t("playerBarStylesLabel"), t("playerBarStylesDesc"), "playerBarStyles", musicSettings.playerBarStyles)}\n            ${createSettingsItem(t("centeredPlayerBarStylesLabel"), t("centeredPlayerBarStylesDesc"), "centeredPlayerBarStyles", musicSettings.centeredPlayerBarStyles)}\n            ${createSettingsItem(t("miniPlayerStylesLabel"), t("miniPlayerStylesDesc"), "miniPlayerStyles", musicSettings.miniPlayerStyles)}\n          </div>\n        </div>\n      </div>\n    </div>\n  `;
}
function createExperimentalSettingsSection(settings, t) {
const themeVariant = "solid" === settings?.zenStyles?.themeVariant ? "solid" : "glass";
return `\n    <div class="ytp-plus-settings-section hidden" data-section="experimental">\n        <div class="ytp-plus-settings-item ytp-plus-theme-item">\n          <div>\n            <label class="ytp-plus-settings-item-label">${tr(t, "zenStyleThemeVariantLabel", "Theme")}</label>\n            <div class="ytp-plus-settings-item-description">${tr(t, "zenStyleThemeVariantDesc", "Choose the visual style. Solid disables blur and uses opaque surfaces for weaker GPUs.")}</div>\n          </div>\n          <div class="ytp-plus-theme-grid" role="radiogroup" aria-label="${tr(t, "zenStyleThemeVariantLabel", "Theme")}">\n            <button\n              type="button"\n              class="ytp-plus-theme-card ${"glass" === themeVariant ? "active" : ""}"\n              role="radio"\n              aria-checked="${"glass" === themeVariant ? "true" : "false"}"\n              data-setting-card="zenStyles.themeVariant"\n              data-value="glass"\n            >\n              <span class="ytp-plus-theme-card-title">${tr(t, "themeVariantGlass", "Glassmorphism")}</span>\n            </button>\n            <button\n              type="button"\n              class="ytp-plus-theme-card ${"solid" === themeVariant ? "active" : ""}"\n              role="radio"\n              aria-checked="${"solid" === themeVariant ? "true" : "false"}"\n              data-setting-card="zenStyles.themeVariant"\n              data-value="solid"\n            >\n              <span class="ytp-plus-theme-card-title">${tr(t, "themeVariantSolid", "Solid")}</span>\n            </button>\n          </div>\n        </div>\n    </div>\n  `;
}
function createVotingSection(_settings, t) {
const previewBefore = tr(t, "votingPreviewBefore", "Before");
const previewAfter = tr(t, "votingPreviewAfter", "After");
return `\n    <div class="ytp-plus-settings-section hidden" data-section="voting">\n      <div class="ytp-plus-settings-voting-header">\n        <h3>${tr(t, "votingTitle", "Feature Requests")}</h3>\n        <p class="ytp-plus-settings-voting-desc">${tr(t, "votingDesc", "Vote for features you want to see in YouTube+")}</p>\n      </div>\n\n      <div class="ytp-plus-voting-preview">\n        <div class="ytp-plus-ba-container">\n          <div class="ytp-plus-ba-before">\n            <img src="https://i.imgur.com/FVW4tdH.jpeg" alt="${previewBefore}" draggable="false" />\n            <span class="ytp-plus-ba-label ytp-plus-ba-label-before">${previewBefore}</span>\n          </div>\n          <div class="ytp-plus-ba-after">\n            <img src="https://i.imgur.com/ljq1KeL.jpeg" alt="${previewAfter}" draggable="false" />\n            <span class="ytp-plus-ba-label ytp-plus-ba-label-after">${previewAfter}</span>\n          </div>\n          <div class="ytp-plus-ba-divider" role="separator" tabindex="0" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"></div>\n        </div>\n\n        <div class="ytp-plus-vote-bar-section" id="ytp-plus-vote-bar-section">\n          <div class="ytp-plus-vote-bar-buttons">\n            <div class="ytp-plus-vote-bar-track" id="ytp-plus-vote-bar-fill"></div>\n            <button class="ytp-plus-vote-bar-btn" id="ytp-plus-vote-bar-up" type="button" aria-label="${tr(t, "like", "Like")}" data-vote="1">\n              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20.9751 12.1852L20.2361 12.0574L20.9751 12.1852ZM20.2696 16.265L19.5306 16.1371L20.2696 16.265ZM6.93776 20.4771L6.19055 20.5417H6.19055L6.93776 20.4771ZM6.1256 11.0844L6.87281 11.0198L6.1256 11.0844ZM13.9949 5.22142L14.7351 5.34269V5.34269L13.9949 5.22142ZM13.3323 9.26598L14.0724 9.38725V9.38725L13.3323 9.26598ZM6.69813 9.67749L6.20854 9.10933H6.20854L6.69813 9.67749ZM8.13687 8.43769L8.62646 9.00585H8.62646L8.13687 8.43769ZM10.518 4.78374L9.79207 4.59542L10.518 4.78374ZM10.9938 2.94989L11.7197 3.13821L11.7197 3.13821L10.9938 2.94989ZM12.6676 2.06435L12.4382 2.77841L12.4382 2.77841L12.6676 2.06435ZM12.8126 2.11093L13.0419 1.39687L13.0419 1.39687L12.8126 2.11093ZM9.86194 6.46262L10.5235 6.81599V6.81599L9.86194 6.46262ZM13.9047 3.24752L13.1787 3.43584V3.43584L13.9047 3.24752ZM11.6742 2.13239L11.3486 1.45675L11.3486 1.45675L11.6742 2.13239ZM20.2361 12.0574L19.5306 16.1371L21.0086 16.3928L21.7142 12.313L20.2361 12.0574ZM13.245 21.25H8.59634V22.75H13.245V21.25ZM7.68497 20.4125L6.87281 11.0198L5.37839 11.149L6.19055 20.5417L7.68497 20.4125ZM19.5306 16.1371C19.0238 19.0677 16.3813 21.25 13.245 21.25V22.75C17.0712 22.75 20.3708 20.081 21.0086 16.3928L19.5306 16.1371ZM13.2548 5.10015L12.5921 9.14472L14.0724 9.38725L14.7351 5.34269L13.2548 5.10015ZM7.18772 10.2456L8.62646 9.00585L7.64728 7.86954L6.20854 9.10933L7.18772 10.2456ZM11.244 4.97206L11.7197 3.13821L10.2678 2.76157L9.79207 4.59542L11.244 4.97206ZM12.4382 2.77841L12.5832 2.82498L13.0419 1.39687L12.897 1.3503L12.4382 2.77841ZM10.5235 6.81599C10.8354 6.23198 11.0777 5.61339 11.244 4.97206L9.79207 4.59542C9.65572 5.12107 9.45698 5.62893 9.20041 6.10924L10.5235 6.81599ZM12.5832 2.82498C12.8896 2.92342 13.1072 3.16009 13.1787 3.43584L14.6306 3.05921C14.4252 2.26719 13.819 1.64648 13.0419 1.39687L12.5832 2.82498ZM11.7197 3.13821C11.7547 3.0032 11.8522 2.87913 11.9998 2.80804L11.3486 1.45675C10.8166 1.71309 10.417 2.18627 10.2678 2.76157L11.7197 3.13821ZM11.9998 2.80804C12.1345 2.74311 12.2931 2.73181 12.4382 2.77841L12.897 1.3503C12.3872 1.18655 11.8312 1.2242 11.3486 1.45675L11.9998 2.80804ZM14.1537 10.9842H19.3348V9.4842H14.1537V10.9842ZM14.7351 5.34269C14.8596 4.58256 14.824 3.80477 14.6306 3.0592L13.1787 3.43584C13.3197 3.97923 13.3456 4.54613 13.2548 5.10016L14.7351 5.34269ZM8.59634 21.25C8.12243 21.25 7.726 20.887 7.68497 20.4125L6.19055 20.5417C6.29851 21.7902 7.34269 22.75 8.59634 22.75V21.25ZM8.62646 9.00585C9.30632 8.42 10.0391 7.72267 10.5235 6.81599L9.20041 6.10924C8.85403 6.75767 8.30249 7.30493 7.64728 7.86954L8.62646 9.00585ZM21.7142 12.313C21.9695 10.8365 20.8341 9.4842 19.3348 9.4842V10.9842C19.9014 10.9842 20.3332 11.4959 20.2361 12.0574L21.7142 12.313ZM12.5921 9.14471C12.4344 10.1076 13.1766 10.9842 14.1537 10.9842V9.4842C14.1038 9.4842 14.0639 9.43901 14.0724 9.38725L12.5921 9.14471ZM6.87281 11.0198C6.84739 10.7258 6.96474 10.4378 7.18772 10.2456L6.20854 9.10933C5.62021 9.61631 5.31148 10.3753 5.37839 11.149L6.87281 11.0198Z" fill="currentColor"></path> <path opacity="0.5" d="M3.9716 21.4709L3.22439 21.5355L3.9716 21.4709ZM3 10.2344L3.74721 10.1698C3.71261 9.76962 3.36893 9.46776 2.96767 9.48507C2.5664 9.50239 2.25 9.83274 2.25 10.2344L3 10.2344ZM4.71881 21.4063L3.74721 10.1698L2.25279 10.299L3.22439 21.5355L4.71881 21.4063ZM3.75 21.5129V10.2344H2.25V21.5129H3.75ZM3.22439 21.5355C3.2112 21.383 3.33146 21.2502 3.48671 21.2502V22.7502C4.21268 22.7502 4.78122 22.1281 4.71881 21.4063L3.22439 21.5355ZM3.48671 21.2502C3.63292 21.2502 3.75 21.3686 3.75 21.5129H2.25C2.25 22.1954 2.80289 22.7502 3.48671 22.7502V21.2502Z" fill="currentColor"></path> </svg>\n            </button>\n            <button class="ytp-plus-vote-bar-btn" id="ytp-plus-vote-bar-down" type="button" aria-label="${tr(t, "dislike", "Dislike")}" data-vote="-1">\n              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20.9751 11.8148L20.2361 11.9426L20.9751 11.8148ZM20.2696 7.73505L19.5306 7.86285L20.2696 7.73505ZM6.93776 3.52293L6.19055 3.45832H6.19055L6.93776 3.52293ZM6.1256 12.9156L6.87281 12.9802L6.1256 12.9156ZM13.9949 18.7786L14.7351 18.6573V18.6573L13.9949 18.7786ZM13.3323 14.734L14.0724 14.6128V14.6128L13.3323 14.734ZM6.69813 14.3225L6.20854 14.8907H6.20854L6.69813 14.3225ZM8.13687 15.5623L8.62646 14.9942H8.62646L8.13687 15.5623ZM10.518 19.2163L9.79207 19.4046L10.518 19.2163ZM10.9938 21.0501L11.7197 20.8618L11.7197 20.8618L10.9938 21.0501ZM12.6676 21.9356L12.4382 21.2216L12.4382 21.2216L12.6676 21.9356ZM12.8126 21.8891L13.0419 22.6031L13.0419 22.6031L12.8126 21.8891ZM9.86194 17.5374L10.5235 17.184V17.184L9.86194 17.5374ZM13.9047 20.7525L13.1787 20.5642V20.5642L13.9047 20.7525ZM11.6742 21.8676L11.3486 22.5433L11.3486 22.5433L11.6742 21.8676ZM20.2361 11.9426L19.5306 7.86285L21.0086 7.60724L21.7142 11.687L20.2361 11.9426ZM13.245 2.75H8.59634V1.25H13.245V2.75ZM7.68497 3.58754L6.87281 12.9802L5.37839 12.851L6.19055 3.45832L7.68497 3.58754ZM19.5306 7.86285C19.0238 4.93226 16.3813 2.75 13.245 2.75V1.25C17.0712 1.25 20.3708 3.91895 21.0086 7.60724L19.5306 7.86285ZM13.2548 18.8998L12.5921 14.8553L14.0724 14.6128L14.7351 18.6573L13.2548 18.8998ZM7.18772 13.7544L8.62646 14.9942L7.64728 16.1305L6.20854 14.8907L7.18772 13.7544ZM11.244 19.0279L11.7197 20.8618L10.2678 21.2384L9.79207 19.4046L11.244 19.0279ZM12.4382 21.2216L12.5832 21.175L13.0419 22.6031L12.897 22.6497L12.4382 21.2216ZM10.5235 17.184C10.8354 17.768 11.0777 18.3866 11.244 19.0279L9.79207 19.4046C9.65572 18.8789 9.45698 18.3711 9.20041 17.8908L10.5235 17.184ZM12.5832 21.175C12.8896 21.0766 13.1072 20.8399 13.1787 20.5642L14.6306 20.9408C14.4252 21.7328 13.819 22.3535 13.0419 22.6031L12.5832 21.175ZM11.7197 20.8618C11.7547 20.9968 11.8522 21.1209 11.9998 21.192L11.3486 22.5433C10.8166 22.2869 10.417 21.8137 10.2678 21.2384L11.7197 20.8618ZM11.9998 21.192C12.1345 21.2569 12.2931 21.2682 12.4382 21.2216L12.897 22.6497C12.3872 22.8135 11.8312 22.7758 11.3486 22.5433L11.9998 21.192ZM14.1537 13.0158H19.3348V14.5158H14.1537V13.0158ZM14.7351 18.6573C14.8596 19.4174 14.824 20.1952 14.6306 20.9408L13.1787 20.5642C13.3197 20.0208 13.3456 19.4539 13.2548 18.8998L14.7351 18.6573ZM8.59634 2.75C8.12243 2.75 7.726 3.11302 7.68497 3.58754L6.19055 3.45832C6.29851 2.20975 7.34269 1.25 8.59634 1.25V2.75ZM8.62646 14.9942C9.30632 15.58 10.0391 16.2773 10.5235 17.184L9.20041 17.8908C8.85403 17.2423 8.30249 16.6951 7.64728 16.1305L8.62646 14.9942ZM21.7142 11.687C21.9695 13.1635 20.8341 14.5158 19.3348 14.5158V13.0158C19.9014 13.0158 20.3332 12.5041 20.2361 11.9426L21.7142 11.687ZM12.5921 14.8553C12.4344 13.8924 13.1766 13.0158 14.1537 13.0158V14.5158C14.1038 14.5158 14.0639 14.561 14.0724 14.6128L12.5921 14.8553ZM6.87281 12.9802C6.84739 13.2742 6.96474 13.5622 7.18772 13.7544L6.20854 14.8907C5.62021 14.3837 5.31148 13.6247 5.37839 12.851L6.87281 12.9802Z" fill="currentColor"></path> <path opacity="0.5" d="M3.9716 2.52911L3.22439 2.4645L3.9716 2.52911ZM3 13.7656L3.74721 13.8302C3.71261 14.2304 3.36893 14.5322 2.96767 14.5149C2.5664 14.4976 2.25 14.1673 2.25 13.7656L3 13.7656ZM4.71881 2.59372L3.74721 13.8302L2.25279 13.701L3.22439 2.4645L4.71881 2.59372ZM3.75 2.48709V13.7656H2.25V2.48709H3.75ZM3.22439 2.4645C3.2112 2.61704 3.33146 2.74983 3.48671 2.74983V1.24983C4.21268 1.24983 4.78122 1.87192 4.71881 2.59372L3.22439 2.4645ZM3.48671 2.74983C3.63292 2.74983 3.75 2.63139 3.75 2.48709H2.25C2.25 1.80457 2.80289 1.24983 3.48671 1.24983V2.74983Z" fill="currentColor"></path> </svg>\n            </button>\n          </div>\n          <div class="ytp-plus-vote-bar-count" id="ytp-plus-vote-bar-count">0</div>\n        </div>\n      </div>\n\n      <div id="ytp-plus-voting-container"></div>\n    </div>\n  `;
}
"undefined" != typeof window && (window.YouTubePlusSettingsHelpers = {
createSettingsSidebar: function createSettingsSidebar(t) {
return `\n    <div class="ytp-plus-settings-nav ytp-plus-settings-nav-rail">\n      ${createNavItem("basic", t("basicTab"), '\n    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path opacity="0.5" 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"></path> <path d="M15 18H9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> </svg>\n  ', !0)}\n      ${createNavItem("advanced", t("advancedTab"), '\n    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path opacity="0.5" 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"></path> <path 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"></path> <path d="M9.5 14.4L10.9286 16L14.5 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </svg>\n  ')}\n      ${createNavItem("experimental", t("experimentalTab"), '\n    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M9.74872 2.49415L18.1594 7.31987M9.74872 2.49415L2.65093 14.7455C1.31093 17.0584 2.10615 20.0159 4.42709 21.3513C6.74803 22.6867 9.7158 21.8942 11.0558 19.5813L12.5511 17.0003L14.1886 14.1738L15.902 11.2163L18.1594 7.31987M9.74872 2.49415L8.91283 2M18.1594 7.31987L19 7.80374" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> <path opacity="0.5" d="M15.9021 11.2164L13.3441 9.74463M14.1887 14.1739L9.98577 11.7557M12.5512 17.0004L9.93848 15.4972" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> <path opacity="0.5" d="M22 14.9166C22 16.0672 21.1046 16.9999 20 16.9999C18.8954 16.9999 18 16.0672 18 14.9166C18 14.1967 18.783 13.2358 19.3691 12.6174C19.7161 12.2512 20.2839 12.2512 20.6309 12.6174C21.217 13.2358 22 14.1967 22 14.9166Z" stroke="currentColor" stroke-width="1.5"></path> </svg>\n  ')}\n      ${createNavItem("voting", tr(t, "votingTab", "Voting"), '\n    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="6" r="4" stroke="currentColor" stroke-width="1.5"></circle> <path opacity="0.5" d="M15 13.3271C14.0736 13.1162 13.0609 13 12 13C7.58172 13 4 15.0147 4 17.5C4 19.9853 4 22 12 22C17.6874 22 19.3315 20.9817 19.8068 19.5" stroke="currentColor" stroke-width="1.5"></path> <circle cx="18" cy="16" r="4" stroke="currentColor" stroke-width="1.5"></circle> <path d="M18 14.6667V17.3333" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M16.6665 16L19.3332 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </svg>\n  ')}\n      ${createNavItem("report", t("reportTab"), '\n    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path 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"></path> <path 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"></path> <path opacity="0.5" d="M8 12H16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> <path opacity="0.5" d="M8 15.5H13.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> </svg>\n  ')}\n      ${createNavItem("about", t("aboutTab"), '\n    <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path 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"></path> <path d="M13.2942 7.17041L12.0001 12L10.706 16.8297" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> <path 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"></path> <path opacity="0.5" 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"></path> </svg>\n  ')}\n    </div>\n  `;
},
createMainContent: function createMainContent(settings, t) {
return `\n    <div class="ytp-plus-settings-main">\n      <div class="ytp-plus-settings-content">\n        ${createBasicSettingsSection(settings, t)}\n        ${createAdvancedSettingsSection(settings, t)}\n        ${createExperimentalSettingsSection(settings, t)}\n        ${createVotingSection(0, t)}\n        <div class="ytp-plus-settings-section hidden" data-section="report"></div>\n        \n    <div class="ytp-plus-settings-section hidden" data-section="about">\n      <div class="about-section-content">\n        <svg class="app-icon" width="90" height="90" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">\n          <path 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" fill="none" fill-rule="evenodd" stroke="currentColor" />\n        </svg>\n        <h1>YouTube +</h1>\n      </div>\n      <div class="ytp-plus-about-actions" style="display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin:16px 0;">\n        <button class="glass-button" id="open-ytp-github" type="button">GitHub</button>\n        <button class="glass-button" id="open-ytp-discussions" type="button">Discussions</button>\n        <button class="glass-button" id="open-ytp-greasyfork" type="button">GreasyFork</button>\n      </div>\n      <div class="ytp-plus-about-footer" style="text-align:center;color:var(--yt-text-secondary);font-size:13px;line-height:1.6;margin-bottom:12px;">        \n        <div>Made with ❤️ by <a href="https://github.com/diorhc" target="_blank" rel="noopener noreferrer" style="color:var(--yt-text-primary);font-style:italic;text-decoration:none;">diorhc</a></div>\n        <div>License: MIT</div>\n      </div>\n    </div>\n  \n      </div>\n    </div>\n  `;
},
createSettingsItem,
createSettingsSelect,
createDownloadSiteOption,
createBasicSettingsSection,
createAdvancedSettingsSection,
createExperimentalSettingsSection,
createVotingSection,
getMusicSettings
});
})();

const qs = window.YouTubeUtils?.$ || document.querySelector.bind(document);

const setSettingByPath = (settings, path, value) => {
if (!settings || "object" != typeof settings) {
return;
}
if (!path || "string" != typeof path) {
return;
}
if (!path.includes(".")) {
settings[path] = value;
return;
}
const keys = path.split(".").filter(Boolean);
if (!keys.length) {
return;
}
const lastKey = keys.pop();
if (!lastKey) {
return;
}
let cur = settings;
for (const k of keys) {
Object.prototype.hasOwnProperty.call(cur, k) && "object" == typeof cur[k] && cur[k] || (cur[k] = {});
cur = cur[k];
}
cur[lastKey] = value;
};

const initializeDownloadSites = settings => {
settings.downloadSites || (settings.downloadSites = {
externalDownloader: !0,
ytdl: !0,
direct: !0
});
if (settings.downloadSites && Object.prototype.hasOwnProperty.call(settings.downloadSites, "y2mate")) {
Object.prototype.hasOwnProperty.call(settings.downloadSites, "externalDownloader") || (settings.downloadSites.externalDownloader = settings.downloadSites.y2mate);
delete settings.downloadSites.y2mate;
}
};

const toggleDownloadSiteControls = checkbox => {
try {
const container = checkbox.closest(".download-site-option");
if (container) {
const controls = container.querySelector(".download-site-controls");
controls && (controls.style.display = checkbox.checked ? "block" : "none");
}
} catch (err) {
window.console.warn("[YouTube+] toggle download-site-controls failed:", err);
}
};

const safelySaveSettings = saveSettings => {
try {
saveSettings();
} catch (err) {
window.console.warn("[YouTube+] autosave downloadSite toggle failed:", err);
}
};

const handleDownloadSiteToggle = (target, key, settings, markDirty, saveSettings) => {
initializeDownloadSites(settings);
const checkbox = target;
settings.downloadSites[key] = checkbox.checked;
try {
markDirty();
} catch (e) {}
toggleDownloadSiteControls(checkbox);
rebuildDownloadDropdown(settings);
safelySaveSettings(saveSettings);
};

const handleDownloadButtonToggle = context => {
const {settings, getElement, addDownloadButton} = context;
const controls = getElement(".ytp-right-controls");
const existing = getElement(".ytp-download-button", !1);
if (settings.enableDownload) {
controls && !existing && addDownloadButton(controls);
} else {
existing && existing.remove();
const dropdown = qs(".download-options");
dropdown && dropdown.remove();
}
};

const handleSpeedControlToggle = context => {
const {settings, getElement, addSpeedControlButton} = context;
const controls = getElement(".ytp-right-controls");
const existing = getElement(".speed-control-btn", !1);
if (settings.enableSpeedControl) {
controls && !existing && addSpeedControlButton(controls);
} else {
existing && existing.remove();
const speedOptions = qs(".speed-options");
speedOptions && speedOptions.remove();
}
};

const updateGlobalSettings = settings => {
"undefined" != typeof window && window.youtubePlus && (window.youtubePlus.settings = window.youtubePlus.settings || settings);
};

const applySettingLive = (setting, context) => {
const {settings, refreshDownloadButton} = context;
try {
context.updatePageBasedOnSettings && context.updatePageBasedOnSettings();
"enableDownload" === setting ? handleDownloadButtonToggle(context) : "enableSpeedControl" === setting && handleSpeedControlToggle(context);
refreshDownloadButton && refreshDownloadButton();
} catch (innerErr) {
window.console.warn("[YouTube+] live apply specific toggle failed:", innerErr);
}
updateGlobalSettings(settings);
};

const handleSimpleSettingToggle = (target, setting, settings, context, markDirty, saveSettings, modal) => {
const checked = target.checked;
setSettingByPath(settings, setting, checked);
if ("zenStyles.sideVideosColumnsEnabled" === setting && checked) {
const currentValue = Number(settings.zenStyles?.sideVideosColumns);
if (!Number.isFinite(currentValue) || currentValue <= 0) {
setSettingByPath(settings, "zenStyles.sideVideosColumns", 1);
const select = modal.querySelector('.style-side-videos-submenu select[data-setting="zenStyles.sideVideosColumns"]');
select instanceof HTMLSelectElement && (select.value = "1");
}
}
try {
markDirty();
} catch (e) {}
try {
applySettingLive(setting, context);
} catch (err) {
window.console.warn("[YouTube+] apply settings live failed:", err);
}
try {
saveSettings();
} catch (err) {
window.console.warn("[YouTube+] autosave simple setting failed:", err);
}
if ("enableDownload" === setting) {
const submenu = modal.querySelector(".download-submenu");
submenu && (submenu.style.display = checked ? "block" : "none");
const toggleBtn = modal.querySelector('.ytp-plus-submenu-toggle[data-submenu="download"]');
if (toggleBtn instanceof HTMLElement) {
if (checked) {
toggleBtn.removeAttribute("disabled");
toggleBtn.setAttribute("aria-expanded", "true");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.setAttribute("aria-expanded", "false");
toggleBtn.style.display = "none";
}
}
}
if ("enableZenStyles" === setting) {
const submenu = modal.querySelector(".style-submenu");
submenu && (submenu.style.display = checked ? "block" : "none");
const toggleBtn = modal.querySelector('.ytp-plus-submenu-toggle[data-submenu="style"]');
if (toggleBtn instanceof HTMLElement) {
if (checked) {
toggleBtn.removeAttribute("disabled");
toggleBtn.setAttribute("aria-expanded", "true");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.setAttribute("aria-expanded", "false");
toggleBtn.style.display = "none";
}
}
}
if ("zenStyles.sideVideosColumnsEnabled" === setting) {
const submenu = modal.querySelector('.style-side-videos-submenu[data-submenu="style-side-videos"]');
submenu instanceof HTMLElement && (submenu.style.display = checked ? "block" : "none");
const toggleBtn = modal.querySelector('.ytp-plus-submenu-toggle[data-submenu="style-side-videos"]');
if (toggleBtn instanceof HTMLElement) {
if (checked) {
toggleBtn.removeAttribute("disabled");
toggleBtn.setAttribute("aria-expanded", "true");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.setAttribute("aria-expanded", "false");
toggleBtn.style.display = "none";
}
}
}
if ("enableSpeedControl" === setting) {
const submenu = modal.querySelector(".speed-submenu");
submenu && (submenu.style.display = checked ? "block" : "none");
const toggleBtn = modal.querySelector('.ytp-plus-submenu-toggle[data-submenu="speed"]');
if (toggleBtn instanceof HTMLElement) {
if (checked) {
toggleBtn.removeAttribute("disabled");
toggleBtn.setAttribute("aria-expanded", "true");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.setAttribute("aria-expanded", "false");
toggleBtn.style.display = "none";
}
}
}
if ("enableEnhanced" === setting) {
const submenu = modal.querySelector(".enhanced-submenu");
submenu && (submenu.style.display = checked ? "block" : "none");
const toggleBtn = modal.querySelector('.ytp-plus-submenu-toggle[data-submenu="enhanced"]');
if (toggleBtn instanceof HTMLElement) {
if (checked) {
toggleBtn.removeAttribute("disabled");
toggleBtn.setAttribute("aria-expanded", "true");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.setAttribute("aria-expanded", "false");
toggleBtn.style.display = "none";
}
}
}
if ("enableLoop" === setting) {
const submenu = modal.querySelector(".loop-submenu");
submenu && (submenu.style.display = checked ? "block" : "none");
const toggleBtn = modal.querySelector('.ytp-plus-submenu-toggle[data-submenu="loop"]');
if (toggleBtn instanceof HTMLElement) {
if (checked) {
toggleBtn.removeAttribute("disabled");
toggleBtn.setAttribute("aria-expanded", "true");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.setAttribute("aria-expanded", "false");
toggleBtn.style.display = "none";
}
}
}
};

const initializeDownloadCustomization = settings => {
settings.downloadSiteCustomization || (settings.downloadSiteCustomization = {
externalDownloader: {
name: "SSYouTube",
url: "https://ssyoutube.com/watch?v={videoId}"
}
});
if (settings.downloadSiteCustomization && Object.prototype.hasOwnProperty.call(settings.downloadSiteCustomization, "y2mate")) {
Object.prototype.hasOwnProperty.call(settings.downloadSiteCustomization, "externalDownloader") || (settings.downloadSiteCustomization.externalDownloader = settings.downloadSiteCustomization.y2mate);
delete settings.downloadSiteCustomization.y2mate;
}
};

const initializeDownloadSite = (settings, site) => {
settings.downloadSiteCustomization[site] || (settings.downloadSiteCustomization[site] = {
name: "",
url: ""
});
};

const getDownloadSiteFallbackName = (site, t) => "externalDownloader" === site ? "SSYouTube" : t("ytdl" === site ? "byYTDL" : "directDownload");

const updateDownloadSiteName = (target, site, t) => {
const nameDisplay = target.closest(".download-site-option")?.querySelector(".download-site-name");
if (nameDisplay) {
const inputValue = target.value;
const fallbackName = getDownloadSiteFallbackName(site, t);
nameDisplay.textContent = inputValue || fallbackName;
}
};

const rebuildDownloadDropdown = settings => {
try {
if ("undefined" != typeof window && window.youtubePlus && "function" == typeof window.youtubePlus.rebuildDownloadDropdown) {
window.youtubePlus.settings = window.youtubePlus.settings || settings;
window.youtubePlus.rebuildDownloadDropdown();
}
} catch (err) {
window.console.warn("[YouTube+] rebuildDownloadDropdown call failed:", err);
}
};

const handleDownloadSiteInput = (target, site, field, settings, markDirty, t) => {
initializeDownloadCustomization(settings);
initializeDownloadSite(settings, site);
settings.downloadSiteCustomization[site][field] = target.value;
try {
markDirty();
} catch (e) {}
"name" === field && updateDownloadSiteName(target, site, t);
rebuildDownloadDropdown(settings);
};

const ensureExternalDownloaderStructure = settings => {
settings.downloadSiteCustomization || (settings.downloadSiteCustomization = {
externalDownloader: {
name: "SSYouTube",
url: "https://ssyoutube.com/watch?v={videoId}"
}
});
settings.downloadSiteCustomization.externalDownloader || (settings.downloadSiteCustomization.externalDownloader = {
name: "",
url: ""
});
};

const readExternalDownloaderInputs = (container, settings) => {
const nameInput = container.querySelector('input.download-site-input[data-site="externalDownloader"][data-field="name"]');
const urlInput = container.querySelector('input.download-site-input[data-site="externalDownloader"][data-field="url"]');
nameInput && (settings.downloadSiteCustomization.externalDownloader.name = nameInput.value);
urlInput && (settings.downloadSiteCustomization.externalDownloader.url = urlInput.value);
};

const triggerRebuildDropdown = () => {
try {
"undefined" != typeof window && window.youtubePlus && "function" == typeof window.youtubePlus.rebuildDownloadDropdown && window.youtubePlus.rebuildDownloadDropdown();
} catch (err) {
window.console.warn("[YouTube+] rebuildDownloadDropdown call failed:", err);
}
};

const handleExternalDownloaderSave = (target, settings, saveSettings, showNotification, t) => {
ensureExternalDownloaderStructure(settings);
const container = target.closest(".download-site-option");
container && readExternalDownloaderInputs(container, settings);
saveSettings();
window.youtubePlus && (window.youtubePlus.settings = window.youtubePlus.settings || settings);
triggerRebuildDropdown();
try {
const msg = t && "function" == typeof t && t("externalDownloaderSettingsSaved") || t("y2mateSettingsSaved");
showNotification(msg);
} catch (e) {
showNotification("Settings saved");
}
};

const resetExternalDownloaderToDefaults = settings => {
ensureExternalDownloaderStructure(settings);
settings.downloadSiteCustomization.externalDownloader = {
name: "SSYouTube",
url: "https://ssyoutube.com/watch?v={videoId}"
};
};

const updateExternalDownloaderModalInputs = (container, settings) => {
const nameInput = container.querySelector('input.download-site-input[data-site="externalDownloader"][data-field="name"]');
const urlInput = container.querySelector('input.download-site-input[data-site="externalDownloader"][data-field="url"]');
const nameDisplay = container.querySelector(".download-site-name");
const edSettings = settings.downloadSiteCustomization.externalDownloader;
nameInput && (nameInput.value = edSettings.name);
urlInput && (urlInput.value = edSettings.url);
nameDisplay && (nameDisplay.textContent = edSettings.name);
};

const handleExternalDownloaderReset = (modal, settings, saveSettings, showNotification, t) => {
resetExternalDownloaderToDefaults(settings);
const container = modal.querySelector(".download-site-option");
container && updateExternalDownloaderModalInputs(container, settings);
saveSettings();
window.youtubePlus && (window.youtubePlus.settings = window.youtubePlus.settings || settings);
triggerRebuildDropdown();
try {
const msg = t && "function" == typeof t && t("externalDownloaderReset") || t("y2mateReset");
showNotification(msg);
} catch (e) {
showNotification("Settings reset");
}
};

const handleSidebarNavigation = (navItem, modal) => {
const section = navItem.dataset?.section;
modal.querySelectorAll(".ytp-plus-settings-nav-item").forEach(item => item.classList.remove("active"));
modal.querySelectorAll(".ytp-plus-settings-section").forEach(s => s.classList.add("hidden"));
navItem.classList.add("active");
const activeLabel = modal.querySelector("#ytp-plus-active-section-label");
activeLabel && (activeLabel.textContent = navItem.dataset?.label || "");
const targetSection = modal.querySelector(`.ytp-plus-settings-section[data-section="${section}"]`);
targetSection && targetSection.classList.remove("hidden");
"voting" === section && window.YouTubePlus?.Voting?.initSlider && requestAnimationFrame(() => window.YouTubePlus.Voting.initSlider());
try {
localStorage.setItem("ytp-plus-active-nav-section", section);
} catch (e) {}
};

const handleMusicSettingToggle = (target, setting, showNotification, t) => {
try {
const defaults = {
enableMusic: !0,
immersiveSearchStyles: !0,
hoverStyles: !0,
playerSidebarStyles: !0,
centeredPlayerStyles: !0,
playerBarStyles: !0,
centeredPlayerBarStyles: !0,
miniPlayerStyles: !0,
scrollToTopStyles: !0
};
const allowedKeys = new Set(Object.keys(defaults));
if (!allowedKeys.has(setting)) {
return;
}
let musicSettings = {
...defaults
};
try {
if ("undefined" != typeof GM_getValue) {
const stored = GM_getValue("youtube-plus-music-settings", null);
if ("string" == typeof stored && stored) {
const parsed = JSON.parse(stored);
parsed && "object" == typeof parsed && (musicSettings = {
...musicSettings,
...parsed
});
}
}
} catch (e) {}
try {
const stored = localStorage.getItem("youtube-plus-music-settings");
if (stored) {
const parsed = JSON.parse(stored);
parsed && "object" == typeof parsed && (musicSettings = {
...musicSettings,
...parsed
});
}
} catch (e) {}
musicSettings[setting] = target.checked;
try {
if ("enableMusic" === setting) {
const enabled = !!musicSettings.enableMusic;
const root = target.closest(".ytp-plus-settings-section") || target.closest(".ytp-plus-settings-panel");
if (root) {
const submenu = root.querySelector('.music-submenu[data-submenu="music"]');
submenu instanceof HTMLElement && (submenu.style.display = enabled ? "block" : "none");
const toggleBtn = root.querySelector('.ytp-plus-submenu-toggle[data-submenu="music"]');
if (toggleBtn instanceof HTMLElement) {
if (enabled) {
toggleBtn.removeAttribute("disabled");
toggleBtn.style.display = "inline-flex";
} else {
toggleBtn.setAttribute("disabled", "");
toggleBtn.style.display = "none";
}
toggleBtn.setAttribute("aria-expanded", enabled ? "true" : "false");
}
}
}
} catch (e) {}
localStorage.setItem("youtube-plus-music-settings", JSON.stringify(musicSettings));
try {
"undefined" != typeof GM_setValue && GM_setValue("youtube-plus-music-settings", JSON.stringify(musicSettings));
} catch (e) {}
if ("undefined" != typeof window && window.YouTubeMusic) {
window.YouTubeMusic.saveSettings && window.YouTubeMusic.saveSettings(musicSettings);
window.YouTubeMusic.applySettingsChanges && window.YouTubeMusic.applySettingsChanges();
}
showNotification && t && showNotification(t("musicSettingsSaved"));
} catch (e) {
window.console.warn("[YouTube+] handleMusicSettingToggle failed");
}
};

const isMusicSetting = setting => "enableMusic" === setting || "immersiveSearchStyles" === setting || "hoverStyles" === setting || "playerSidebarStyles" === setting || "centeredPlayerStyles" === setting || "playerBarStyles" === setting || "centeredPlayerBarStyles" === setting || "miniPlayerStyles" === setting || "scrollToTopStyles" === setting;

if ("undefined" != typeof window) {
const createFocusTrap = container => {
const handler = e => {
if ("Tab" !== e.key) {
return;
}
const focusable = Array.from(container.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter(el => null !== el.offsetParent);
if (0 === focusable.length) {
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
};
container.addEventListener("keydown", handler);
return () => container.removeEventListener("keydown", handler);
};
window.YouTubePlusModalHandlers = {
setSettingByPath,
initializeDownloadSites,
toggleDownloadSiteControls,
safelySaveSettings,
handleDownloadSiteToggle,
handleSimpleSettingToggle,
handleDownloadSiteInput,
handleExternalDownloaderSave,
handleExternalDownloaderReset,
handleSidebarNavigation,
applySettingLive,
handleMusicSettingToggle,
isMusicSetting,
createFocusTrap
};
}

/**
 * YouTube+ Download Module
 * Unified download system with button UI and download functionality
 * @version 3.0
 */ !(function() {
"use strict";
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const createVisibilityAwareInterval = window.YouTubeUtils?.createVisibilityAwareInterval || ((callback, delay) => {
const id = setInterval(() => {
document.hidden || callback();
}, delay);
return {
stop() {
clearInterval(id);
},
pause() {
clearInterval(id);
},
resume() {},
get active() {
return !0;
}
};
});
const setTimeout_ = setTimeout.bind(window);
const _potParamsByVideoId = new Map;
const _potCarriedParamNames = [ "pot", "potc", "c", "cver", "cplayer", "cos", "cosver", "cplatform", "cbr", "cbrver", "xorb", "xobt", "xovt" ];
let _potHooksInstalled = !1;
let _potElicitInflight = !1;
function _pageGlobal() {
return "undefined" != typeof unsafeWindow ? unsafeWindow : window;
}
function _rememberPotFromTimedtextUrl(rawUrl) {
try {
if (!rawUrl || -1 === rawUrl.indexOf("/api/timedtext")) {
return;
}
const u = new URL(rawUrl, "https://www.youtube.com");
const videoId = u.searchParams.get("v");
const pot = u.searchParams.get("pot");
if (!videoId || !pot) {
return;
}
const collected = {};
for (const name of _potCarriedParamNames) {
const value = u.searchParams.get(name);
null != value && "" !== value && (collected[name] = value);
}
_potParamsByVideoId.set(videoId, collected);
} catch (e) {}
}
!(function _installPotHooksOnce() {
if (_potHooksInstalled) {
return;
}
const pg = _pageGlobal();
if (pg && "object" == typeof pg) {
try {
const origFetch = pg.fetch;
"function" == typeof origFetch && (pg.fetch = function patchedFetch(input, _init) {
try {
const inputWithUrl = input && "object" == typeof input ? input : {};
const url = "string" == typeof input ? input : input instanceof URL ? input.toString() : "string" == typeof inputWithUrl.url ? inputWithUrl.url : "";
url && _rememberPotFromTimedtextUrl(url);
} catch (e) {}
return origFetch.call(this, input, _init);
});
const xhrProto = pg.XMLHttpRequest && pg.XMLHttpRequest.prototype;
if (xhrProto && "function" == typeof xhrProto.open) {
const origOpen = xhrProto.open;
const patchedOpen = function(...args) {
try {
const url = args[1];
"string" == typeof url && _rememberPotFromTimedtextUrl(url);
} catch (e) {}
return origOpen.apply(this, args);
};
xhrProto.open = patchedOpen;
}
_potHooksInstalled = !0;
} catch (e) {}
}
})();
function _getVideoIdFromCandidate(url) {
try {
const u = new URL(url, "https://www.youtube.com");
return u.searchParams.get("v") || "";
} catch (e) {
return "";
}
}
const isRelevantRoute = () => {
try {
const path = location.pathname || "";
return "/watch" === path || path.startsWith("/shorts");
} catch (e) {
return !1;
}
};
const $ = sel => window.YouTubeUtils?.$(sel) || document.querySelector(sel);
if (void 0 === YouTubeUtils) {
window.console.error("[YouTube+ Download] YouTubeUtils not found!");
return;
}
const {NotificationManager} = YouTubeUtils;
const t = (key, params = {}) => {
if (window.YouTubeUtils?.t) {
return window.YouTubeUtils.t(key, params);
}
const str = String(key || "");
if (!params || 0 === Object.keys(params).length) {
return str;
}
let result = str;
for (const [k, v] of Object.entries(params)) {
result = result.split(`{${k}}`).join(String(v));
}
return result;
};
const _YouTubePlusLogger = window.YouTubePlusLogger;
const logger = void 0 !== _YouTubePlusLogger && _YouTubePlusLogger ? _YouTubePlusLogger.createLogger("Download") : {
debug: () => {},
info: () => {},
warn: window.console.warn.bind(console),
error: window.console.error.bind(console)
};
const DownloadConfig = {
API: {
KEY_URL: "https://cnv.cx/v2/sanity/key",
CONVERT_URL: "https://cnv.cx/v2/converter"
},
HEADERS: {
"Content-Type": "application/json",
Origin: "https://mp3yt.is",
Accept: "*/*",
"User-Agent": "undefined" != typeof navigator ? navigator.userAgent : ""
},
VIDEO_QUALITIES: [ "144", "240", "360", "480", "720", "1080", "1440", "2160" ],
AUDIO_BITRATES: [ "64", "128", "192", "256", "320" ],
DEFAULTS: {
format: "video",
videoQuality: "1080",
audioBitrate: "320",
embedThumbnail: !0
}
};
function getVideoId() {
try {
const params = new URLSearchParams(window.location.search || "");
const fromQuery = params.get("v");
if (fromQuery) {
return fromQuery;
}
const path = window.location.pathname || "";
const shortsMatch = path.match(/^\/shorts\/([a-zA-Z0-9_-]{11})/);
if (shortsMatch && shortsMatch[1]) {
return shortsMatch[1];
}
const liveMatch = path.match(/^\/live\/([a-zA-Z0-9_-]{11})/);
if (liveMatch && liveMatch[1]) {
return liveMatch[1];
}
const youtuBeMatch = (window.location.href || "").match(/youtu\.be\/([a-zA-Z0-9_-]{11})/);
if (youtuBeMatch && youtuBeMatch[1]) {
return youtuBeMatch[1];
}
} catch (e) {}
return null;
}
function getVideoUrl() {
const videoId = getVideoId();
return videoId ? `https://www.youtube.com/watch?v=${videoId}` : window.location.href;
}
function getVideoTitle() {
try {
const titleElement = $("h1.ytd-video-primary-info-renderer yt-formatted-string") || $("h1.title yt-formatted-string") || $("ytd-watch-metadata h1");
return titleElement ? titleElement.textContent.trim() : "video";
} catch (e) {
return "video";
}
}
function sanitizeFilename(filename) {
return filename.replace(/[<>:"/\\|?*]/g, "").replace(/[\x00-\x1f\x7f\u200b-\u200f\u2028-\u202f\ufeff]/g, "").replace(/\s+/g, " ").trim().substring(0, 200);
}
function formatBytes(bytes) {
if (0 === bytes) {
return "0 B";
}
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${[ "B", "KB", "MB", "GB" ][i]}`;
}
function createGmRequestOptions(options, resolve, reject) {
return {
...options,
onload: response => {
options.onload && options.onload(response);
resolve(response);
},
onerror: error => {
options.onerror && options.onerror(error);
reject(error);
},
ontimeout: () => {
options.ontimeout && options.ontimeout();
reject(new Error("Request timeout"));
}
};
}
const _downloadRateLimiter = (() => {
const RATE_LIMIT_EXEMPT_PATHS = [ "/api/timedtext", "/api/timedtext_ui" ];
const requests = new Map;
const backoffUntil = new Map;
return {
canRequest(url) {
let host = "unknown";
let pathname = "";
try {
const parsed = new URL(url);
host = parsed.hostname;
pathname = parsed.pathname;
} catch (e) {}
if (RATE_LIMIT_EXEMPT_PATHS.some(p => pathname.startsWith(p))) {
return !0;
}
const now = Date.now();
const backoff = backoffUntil.get(host) || 0;
if (now < backoff) {
window.console.warn(`[YouTube+ Download] Rate limit backoff: ${host} blocked for ${Math.ceil((backoff - now) / 1e3)}s more`);
return !1;
}
const recent = (requests.get(host) || []).filter(t => now - t < 6e4);
if (recent.length >= 15) {
const consecutiveHits = Math.min(5, Math.floor(recent.length / 15));
const backoffMs = Math.min(6e4, 2e3 * Math.pow(2, consecutiveHits));
backoffUntil.set(host, now + backoffMs);
window.console.warn(`[YouTube+ Download] Rate limit: ${recent.length}/15 requests to ${host}, backing off ${backoffMs}ms`);
return !1;
}
recent.push(now);
requests.set(host, recent);
return !0;
}
};
})();
function gmXmlHttpRequest(options) {
return new Promise((resolve, reject) => {
if (options.url && !_downloadRateLimiter.canRequest(options.url)) {
reject(new Error("[YouTube+ Download] Rate limit exceeded — request blocked"));
return;
}
if ("undefined" != typeof GM_xmlhttpRequest) {
GM_xmlhttpRequest(createGmRequestOptions(options, resolve, reject));
return;
}
const gmApi = globalThis.GM;
gmApi && "function" == typeof gmApi.xmlHttpRequest ? gmApi.xmlHttpRequest(createGmRequestOptions(options, resolve, reject)) : (async () => {
try {
const responseLike = await (async function executeFetchFallback(options) {
const fetchOpts = {
method: options.method || "GET",
headers: options.headers || {},
body: options.data || options.body || void 0
};
const resp = await fetch(options.url, fetchOpts);
const responseLike = (function buildResponseObject(resp) {
return {
status: resp.status,
statusText: resp.statusText,
finalUrl: resp.url,
headers: {},
responseText: null,
response: null
};
})(resp);
await (async function extractResponseText(resp, responseLike) {
try {
responseLike.responseText = await resp.text();
} catch (e) {
responseLike.responseText = null;
}
})(resp, responseLike);
await (async function extractResponseBlob(resp, responseLike, responseType) {
if ("blob" === responseType) {
try {
responseLike.response = await resp.blob();
} catch (e) {
responseLike.response = null;
}
}
})(resp, responseLike, options.responseType);
options.onload && options.onload(responseLike);
return responseLike;
})(options);
resolve(responseLike);
} catch (err) {
options.onerror && options.onerror(err);
reject(err);
}
})();
});
}
const _playerDataCache = (() => {
const store = new Map;
return {
get(videoId) {
const entry = store.get(videoId);
if (!entry) {
return null;
}
if (Date.now() - entry.ts > 3e5) {
store.delete(videoId);
return null;
}
return entry.data;
},
set(videoId, data) {
if (store.size >= 10) {
const oldestKey = store.keys().next().value;
"string" == typeof oldestKey && store.delete(oldestKey);
}
store.set(videoId, {
data,
ts: Date.now()
});
}
};
})();
async function fetchPlayerData(videoId) {
const cached = _playerDataCache.get(videoId);
if (cached) {
return cached;
}
const response = await gmXmlHttpRequest({
method: "POST",
url: "https://www.youtube.com/youtubei/v1/player",
headers: {
"Content-Type": "application/json",
"User-Agent": DownloadConfig.HEADERS["User-Agent"]
},
data: JSON.stringify({
context: {
client: {
clientName: "WEB",
clientVersion: "2.20240304.00.00"
}
},
videoId
})
});
if (200 !== response.status) {
throw new Error(`Failed to get player data: ${response.status}`);
}
const parsed = JSON.parse(response.responseText);
_playerDataCache.set(videoId, parsed);
return parsed;
}
function extractAvailableVideoQualities(playerData) {
const streamingData = playerData?.streamingData || {};
const combined = [ ...Array.isArray(streamingData.formats) ? streamingData.formats : [], ...Array.isArray(streamingData.adaptiveFormats) ? streamingData.adaptiveFormats : [] ];
const qualitySet = new Set;
combined.forEach(format => {
const mimeType = String(format?.mimeType || "");
if (!mimeType.includes("video/")) {
return;
}
const qualityLabel = String(format?.qualityLabel || "").trim();
const match = qualityLabel.match(/(\d{3,4})p/i);
match && qualitySet.add(match[1]);
});
return Array.from(qualitySet).sort((left, right) => Number(left) - Number(right));
}
function extractBalancedJsonObject(src, startIdx) {
if (startIdx < 0 || startIdx >= src.length || "{" !== src[startIdx]) {
return null;
}
let depth = 0;
let inStr = !1;
let escape = !1;
for (let i = startIdx; i < src.length; i++) {
const ch = src[i];
if (escape) {
escape = !1;
} else if (inStr) {
"\\" === ch ? escape = !0 : '"' === ch && (inStr = !1);
} else if ('"' === ch) {
inStr = !0;
} else if ("{" === ch) {
depth++;
} else if ("}" === ch) {
depth--;
if (0 === depth) {
return src.slice(startIdx, i + 1);
}
}
}
return null;
}
async function fetchPlayerResponseFromWatchHtml(videoId) {
try {
const watchUrl = `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`;
const response = await gmXmlHttpRequest({
method: "GET",
url: watchUrl
});
if (200 !== response.status || !response.responseText) {
return null;
}
const html = String(response.responseText);
const assignRe = /ytInitialPlayerResponse\s*=\s*\{/g;
let match;
for (;null !== (match = assignRe.exec(html)); ) {
const braceIdx = match.index + match[0].length - 1;
const jsonText = extractBalancedJsonObject(html, braceIdx);
if (jsonText) {
try {
return JSON.parse(jsonText);
} catch (e) {}
}
}
return null;
} catch (error) {
logger.warn("Watch HTML subtitle fallback failed:", error);
return null;
}
}
function buildSubtitleUrl(baseUrl) {
const normalized = normalizeSubtitleBaseUrl(baseUrl);
return normalized ? normalized.includes("fmt=") ? normalized : `${normalized}&fmt=srv1` : "";
}
function normalizeSubtitleBaseUrl(baseUrl) {
const raw = String(baseUrl || "").trim();
return raw ? raw.replace(/&amp;/g, "&") : "";
}
async function extractSubtitleBody(response) {
const text = String(response?.responseText || "").trim();
if (text) {
return text;
}
const rawResponse = response?.response;
if ("string" == typeof rawResponse && rawResponse.trim()) {
return rawResponse.trim();
}
const xmlDoc = response?.responseXML;
if (xmlDoc && window.XMLSerializer) {
try {
const serialized = (new window.XMLSerializer).serializeToString(xmlDoc).trim();
if (serialized) {
return serialized;
}
} catch (e) {}
}
if (rawResponse && "object" == typeof rawResponse) {
const rawDocument = rawResponse;
if ("string" == typeof rawDocument?.documentElement?.nodeName && window.XMLSerializer) {
try {
const serialized = (new window.XMLSerializer).serializeToString(rawDocument).trim();
if (serialized) {
return serialized;
}
} catch (e) {}
}
}
if (rawResponse && rawResponse instanceof ArrayBuffer) {
try {
return window.TextDecoder ? new window.TextDecoder("utf-8").decode(rawResponse).trim() : "";
} catch (e) {
return "";
}
}
if (rawResponse && ArrayBuffer.isView(rawResponse)) {
try {
const view = rawResponse;
const sliced = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
if (window.TextDecoder) {
return new window.TextDecoder("utf-8").decode(sliced).trim();
}
} catch (e) {}
}
if ("undefined" != typeof Blob && rawResponse instanceof Blob) {
try {
return (await rawResponse.text()).trim();
} catch (e) {}
}
if (rawResponse && "object" == typeof rawResponse) {
const rawObject = rawResponse;
const maybeText = rawObject.text || rawObject.data || rawObject.content;
if ("string" == typeof maybeText && maybeText.trim()) {
return maybeText.trim();
}
if ("function" == typeof rawObject.text) {
try {
const extracted = await rawObject.text();
if ("string" == typeof extracted && extracted.trim()) {
return extracted.trim();
}
} catch (e) {}
}
}
return "";
}
function parseCaptionTracks(captionTracks) {
return captionTracks.map(track => ({
name: track.name?.simpleText || track.languageCode,
languageCode: track.languageCode,
url: buildSubtitleUrl(track.baseUrl),
baseUrl: normalizeSubtitleBaseUrl(track.baseUrl),
isAutoGenerated: "asr" === track.kind,
trackId: String(track?.vssId || ""),
kind: String(track?.kind || "")
}));
}
function findBestCaptionTrack(captionTracks, criteria = {}) {
const tracks = Array.isArray(captionTracks) ? captionTracks.filter(Boolean) : [];
if (0 === tracks.length) {
return null;
}
const wantedLang = String(criteria.languageCode || "").trim();
const wantedBaseUrl = normalizeSubtitleBaseUrl(criteria.baseUrl || "");
const wantedTrackId = String(criteria.trackId || "").trim();
const wantsAuto = "boolean" == typeof criteria.isAutoGenerated ? criteria.isAutoGenerated : null;
const scoreTrack = track => {
let score = 0;
const trackBaseUrl = normalizeSubtitleBaseUrl(track?.baseUrl || "");
const trackLang = String(track?.languageCode || "");
const trackId = String(track?.vssId || "");
const trackIsAuto = "asr" === track?.kind;
const langPrefix = wantedLang ? wantedLang.split("-")[0] : "";
wantedTrackId && trackId && trackId === wantedTrackId && (score += 120);
wantedBaseUrl && trackBaseUrl && trackBaseUrl === wantedBaseUrl && (score += 100);
wantedLang && trackLang === wantedLang ? score += 40 : langPrefix && trackLang.startsWith(langPrefix) && (score += 20);
null !== wantsAuto && (score += trackIsAuto === wantsAuto ? 15 : -5);
!1 !== track?.isTranslatable && (score += 5);
trackBaseUrl && (score += 2);
return score;
};
return tracks.map(track => ({
track,
score: scoreTrack(track)
})).sort((left, right) => right.score - left.score)[0]?.track || null;
}
function parseTranslationLanguages(translationLanguages, sourceTrack) {
const sourceBaseUrl = normalizeSubtitleBaseUrl(sourceTrack?.baseUrl || "");
const sourceLanguageCode = String(sourceTrack?.languageCode || "");
const sourceTrackId = String(sourceTrack?.vssId || "");
return translationLanguages.map(lang => ({
name: lang.languageName?.simpleText || lang.languageCode,
languageCode: lang.languageCode,
sourceLanguageCode: sourceLanguageCode || "",
baseUrl: sourceBaseUrl,
url: buildSubtitleUrl(sourceBaseUrl),
isAutoGenerated: "asr" === sourceTrack?.kind,
trackId: sourceTrackId,
translateTo: lang.languageCode
}));
}
async function getSubtitles(videoId) {
try {
let data = null;
try {
data = await fetchPlayerData(videoId);
} catch (error) {
logger.warn("Primary subtitle API request failed, trying page fallback:", error);
}
let fallback = (function getCaptionsFromPageFallback() {
try {
const title = getVideoTitle();
const globalContext = _pageGlobal();
const initial = globalContext.ytInitialPlayerResponse;
const initialCaps = initial?.captions?.playerCaptionsTracklistRenderer;
if (initialCaps) {
return {
captions: initialCaps,
videoTitle: initial?.videoDetails?.title || title
};
}
const playerEl = window.YouTubeUtils?.byId?.("movie_player") || document.getElementById("movie_player");
const player = playerEl instanceof HTMLElement ? playerEl : null;
const response = "function" == typeof player?.getPlayerResponse && player.getPlayerResponse() || null;
const respCaps = response?.captions?.playerCaptionsTracklistRenderer;
if (respCaps) {
return {
captions: respCaps,
videoTitle: response?.videoDetails?.title || title
};
}
const ytPlayerResponse = globalContext?.ytplayer?.config?.args?.player_response;
if ("string" == typeof ytPlayerResponse && ytPlayerResponse.length > 0) {
try {
const parsed = JSON.parse(ytPlayerResponse);
const parsedCaps = parsed?.captions?.playerCaptionsTracklistRenderer;
if (parsedCaps) {
return {
captions: parsedCaps,
videoTitle: parsed?.videoDetails?.title || title
};
}
} catch (e) {}
}
} catch (error) {
logger.warn("Subtitle fallback extraction failed:", error);
}
return null;
})();
if (!fallback) {
const parsedFromHtml = await fetchPlayerResponseFromWatchHtml(videoId);
parsedFromHtml?.captions?.playerCaptionsTracklistRenderer && (fallback = {
captions: parsedFromHtml.captions.playerCaptionsTracklistRenderer,
videoTitle: parsedFromHtml?.videoDetails?.title || getVideoTitle()
});
}
const videoTitle = data?.videoDetails?.title || fallback?.videoTitle || "video";
const captions = data?.captions?.playerCaptionsTracklistRenderer || fallback?.captions || null;
if (!captions) {
return (function createEmptySubtitleResult(videoId, videoTitle) {
return {
videoId,
videoTitle,
subtitles: [],
autoTransSubtitles: []
};
})(videoId, videoTitle);
}
const captionTracks = captions.captionTracks || [];
const translationLanguages = captions.translationLanguages || [];
const translationSourceTrack = (function getTranslationSourceTrack(captionTracks) {
return findBestCaptionTrack(captionTracks, {
isAutoGenerated: !0
}) || findBestCaptionTrack(captionTracks) || null;
})(captionTracks);
return {
videoId,
videoTitle,
subtitles: parseCaptionTracks(captionTracks),
autoTransSubtitles: parseTranslationLanguages(translationLanguages, translationSourceTrack)
};
} catch (error) {
logger.error("Error getting subtitles:", error);
return null;
}
}
function parseSubtitleXML(xml) {
const cues = [];
const normalizedXml = String(xml || "").replace(/\uFEFF/g, "");
const domParser = "function" == typeof window.DOMParser ? new window.DOMParser : null;
if (domParser) {
try {
const doc = domParser.parseFromString(normalizedXml, "text/xml");
const rootName = doc.documentElement?.nodeName?.toLowerCase?.() || "";
const hasParserError = "parsererror" === rootName || doc.getElementsByTagName("parsererror").length > 0;
if (!hasParserError) {
const nodes = Array.from(doc.getElementsByTagName("text"));
nodes.forEach(node => {
const start = parseFloat(node.getAttribute("start") || "0");
const duration = parseFloat(node.getAttribute("dur") || "0");
const text = decodeHTMLEntities(String(node.textContent || "").trim());
text && cues.push({
start,
duration,
text
});
});
}
} catch (e) {}
}
if (cues.length > 0) {
return cues;
}
const textTagRegex = /<text\b([^>]*)>([\s\S]*?)<\/text>/gi;
let match;
for (;null !== (match = textTagRegex.exec(normalizedXml)); ) {
const attrs = match[1] || "";
const startRaw = /\bstart\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1] || "0";
const durRaw = /\bdur\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1] || "0";
const start = parseFloat(startRaw || "0");
const duration = parseFloat(durRaw || "0");
let text = match[2] || "";
text = text.replace(/<!\[CDATA\[(.*?)\]\]>/g, "$1");
text = decodeHTMLEntities(text.replace(/<br\s*\/?>/gi, " ").trim());
text = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
text && cues.push({
start,
duration,
text
});
}
const pTagRegex = /<p\b([^>]*)>([\s\S]{0,20000}?)<\/p>/gi;
for (;null !== (match = pTagRegex.exec(normalizedXml)); ) {
const attrs = match[1] || "";
const inner = match[2] || "";
const tMsRaw = /\bt="([^"]+)"/i.exec(attrs)?.[1] || "0";
const dMsRaw = /\bd="([^"]+)"/i.exec(attrs)?.[1] || "0";
const start = Math.max(0, Number(tMsRaw) / 1e3);
const duration = Math.max(0, Number(dMsRaw) / 1e3);
const assembled = inner.includes("<s") ? inner.replace(/<s\b[^>]*>/gi, "").replace(/<\/s>/gi, "").replace(/<br\s*\/?>/gi, " ") : inner;
const text = decodeHTMLEntities(assembled.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim());
text && cues.push({
start,
duration: duration > 0 ? duration : 2,
text
});
}
return cues;
}
const _htmlEntityMap = {
amp: "&",
lt: "<",
gt: ">",
quot: '"',
"#39": "'",
apos: "'",
nbsp: " "
};
function decodeHTMLEntities(text) {
return text.replace(/&(#x?[0-9A-Fa-f]+|[a-zA-Z]+);/g, (match, entity) => {
if (_htmlEntityMap[entity]) {
return _htmlEntityMap[entity];
}
if (entity.startsWith("#") && !entity.startsWith("#x")) {
const num = parseInt(entity.slice(1), 10);
return num > 0 && num < 1114111 ? String.fromCharCode(num) : match;
}
if (entity.startsWith("#x")) {
const num = parseInt(entity.slice(2), 16);
return num > 0 && num < 1114111 ? String.fromCharCode(num) : match;
}
return match;
});
}
function formatSRTTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor(seconds % 3600 / 60);
const secs = Math.floor(seconds % 60);
const milliseconds = Math.floor(seconds % 1 * 1e3);
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")},${String(milliseconds).padStart(3, "0")}`;
}
function parseSubtitleJSON3(jsonText) {
try {
const data = JSON.parse(jsonText);
const events = Array.isArray(data?.events) ? data.events : [];
const cues = [];
events.forEach(event => {
const segs = Array.isArray(event?.segs) ? event.segs : [];
const text = segs.map(seg => String(seg?.utf8 || "")).join("").replace(/\s+/g, " ").trim();
if (!text) {
return;
}
const start = Number(event?.tStartMs || 0) / 1e3;
const duration = Math.max(0, Number(event?.dDurationMs || 0) / 1e3);
cues.push({
start,
duration,
text
});
});
return cues;
} catch (e) {
return [];
}
}
function parseSubtitleVTT(vttText) {
const cues = [];
const blocks = String(vttText || "").replace(/\r/g, "").split(/\n\n+/);
const parseClockTime = value => {
const raw = String(value || "").trim();
if (!raw) {
return 0;
}
const parts = raw.split(":");
if (2 !== parts.length && 3 !== parts.length) {
return 0;
}
if (!parts.every(part => {
if (!part) {
return !1;
}
let dotSeen = !1;
for (let i = 0; i < part.length; i += 1) {
const ch = part[i];
if (!(ch >= "0" && ch <= "9")) {
if ("." !== ch || dotSeen) {
return !1;
}
dotSeen = !0;
}
}
return !0;
})) {
return 0;
}
let h = 0;
let m = 0;
let secPart = "";
if (2 === parts.length) {
m = Number(parts[0]);
secPart = parts[1];
} else {
h = Number(parts[0]);
m = Number(parts[1]);
secPart = parts[2];
}
const dot = secPart.indexOf(".");
const sec = Number(dot >= 0 ? secPart.slice(0, dot) : secPart);
const frac = dot >= 0 ? secPart.slice(dot + 1) : "";
const ms = frac ? Number(frac.padEnd(3, "0").slice(0, 3)) : 0;
return Number.isFinite(h) && Number.isFinite(m) && Number.isFinite(sec) && Number.isFinite(ms) ? 3600 * h + 60 * m + sec + ms / 1e3 : 0;
};
blocks.forEach(block => {
const lines = block.split("\n").map(line => line.trim()).filter(Boolean);
if (0 === lines.length) {
return;
}
if ("WEBVTT" === lines[0]) {
return;
}
const timeIndex = lines.findIndex(line => line.includes("--\x3e"));
if (timeIndex < 0) {
return;
}
const range = lines[timeIndex].split("--\x3e");
if (range.length < 2) {
return;
}
const start = parseClockTime(range[0]);
const end = parseClockTime(range[1].split(" ")[0]);
const duration = Math.max(0, end - start);
const text = lines.slice(timeIndex + 1).join(" ").replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
text && cues.push({
start,
duration,
text
});
});
return cues;
}
function parseSubtitleTTML(ttmlText) {
const cues = [];
const normalizedTtml = String(ttmlText || "").replace(/\uFEFF/g, "");
const pTagRegex = /<p\b([^>]*)>([\s\S]{0,20000}?)<\/p>/gi;
const parseTtmlTime = value => {
const v = String(value || "").trim();
if (!v) {
return 0;
}
const last = v[v.length - 1];
if ("s" === last || "S" === last) {
const n = Number(v.slice(0, -1));
return Number.isFinite(n) ? n : 0;
}
const parts = v.split(":");
if (2 !== parts.length && 3 !== parts.length) {
return 0;
}
if (!parts.every(part => {
if (!part) {
return !1;
}
let dotSeen = !1;
for (let i = 0; i < part.length; i += 1) {
const ch = part[i];
if (!(ch >= "0" && ch <= "9")) {
if ("." !== ch || dotSeen) {
return !1;
}
dotSeen = !0;
}
}
return !0;
})) {
return 0;
}
let h = 0;
let m = 0;
let secPart = "";
if (2 === parts.length) {
m = Number(parts[0]);
secPart = parts[1];
} else {
h = Number(parts[0]);
m = Number(parts[1]);
secPart = parts[2];
}
const dot = secPart.indexOf(".");
const s = Number(dot >= 0 ? secPart.slice(0, dot) : secPart);
const frac = dot >= 0 ? secPart.slice(dot + 1) : "";
const ms = frac ? Number(frac.padEnd(3, "0").slice(0, 3)) : 0;
return Number.isFinite(h) && Number.isFinite(m) && Number.isFinite(s) && Number.isFinite(ms) ? 3600 * h + 60 * m + s + ms / 1e3 : 0;
};
const domParser = "function" == typeof window.DOMParser ? new window.DOMParser : null;
if (domParser) {
try {
const doc = domParser.parseFromString(normalizedTtml, "text/xml");
const rootName = doc.documentElement?.nodeName?.toLowerCase?.() || "";
const hasParserError = "parsererror" === rootName || doc.getElementsByTagName("parsererror").length > 0;
if (!hasParserError) {
const nodes = Array.from(doc.getElementsByTagName("p"));
nodes.forEach(node => {
const start = parseTtmlTime(node.getAttribute("begin") || node.getAttribute("start") || "");
const end = parseTtmlTime(node.getAttribute("end") || "");
const dur = parseTtmlTime(node.getAttribute("dur") || "");
const duration = dur || Math.max(0, end - start);
const text = decodeHTMLEntities(String(node.textContent || "").replace(/\s+/g, " ").trim());
text && cues.push({
start,
duration,
text
});
});
}
} catch (e) {}
}
if (cues.length > 0) {
return cues;
}
let match;
for (;null !== (match = pTagRegex.exec(normalizedTtml)); ) {
const attrs = match[1] || "";
const inner = match[2] || "";
const begin = /\bbegin\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1] || "";
const end = /\bend\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1] || "";
const dur = /\bdur\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1] || "";
const start = parseTtmlTime(begin);
let duration = 0;
dur ? duration = parseTtmlTime(dur) : end && (duration = Math.max(0, parseTtmlTime(end) - start));
const text = decodeHTMLEntities(inner.replace(/<br\s*\/?\s*>/gi, " ").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim());
text && cues.push({
start,
duration,
text
});
}
return cues;
}
function convertToXML(cues) {
const body = cues.map(cue => {
const start = Number(cue?.start || 0);
const duration = Number(cue?.duration || 0);
const text = (function escapeXML(text) {
return String(text || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
})(String(cue?.text || "").trim());
return `<text start="${start.toFixed(3)}" dur="${duration.toFixed(3)}">${text}</text>`;
}).join("");
return `<?xml version="1.0" encoding="utf-8"?><transcript>${body}</transcript>`;
}
function setQueryParam(inputUrl, key, value) {
try {
const url = new URL(inputUrl);
url.searchParams.set(key, value);
return url.toString();
} catch (e) {
const encoded = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
const str = String(inputUrl || "");
const qPos = str.indexOf("?");
const hashPos = str.indexOf("#");
const baseEnd = qPos >= 0 ? qPos : hashPos >= 0 ? hashPos : str.length;
const base = str.slice(0, baseEnd);
const hash = hashPos >= 0 ? str.slice(hashPos) : "";
const queryStart = qPos >= 0 ? qPos + 1 : baseEnd;
const queryEnd = hashPos >= 0 ? hashPos : str.length;
const rawQuery = str.slice(queryStart, queryEnd);
const pairs = rawQuery ? rawQuery.split("&").filter(Boolean) : [];
let replaced = !1;
for (let i = 0; i < pairs.length; i += 1) {
const pair = pairs[i];
const eq = pair.indexOf("=");
const name = eq >= 0 ? pair.slice(0, eq) : pair;
if (decodeURIComponent(name) === key) {
pairs[i] = encoded;
replaced = !0;
}
}
replaced || pairs.push(encoded);
return `${base}?${pairs.join("&")}${hash}`;
}
}
function removeQueryParam(inputUrl, key) {
try {
const url = new URL(inputUrl);
url.searchParams.delete(key);
return url.toString();
} catch (e) {
const str = String(inputUrl || "");
const qPos = str.indexOf("?");
if (qPos < 0) {
return str;
}
const hashPos = str.indexOf("#");
const base = str.slice(0, qPos);
const hash = hashPos >= 0 ? str.slice(hashPos) : "";
const query = str.slice(qPos + 1, hashPos >= 0 ? hashPos : str.length);
const nextPairs = query.split("&").filter(Boolean).filter(pair => {
const eq = pair.indexOf("=");
const name = eq >= 0 ? pair.slice(0, eq) : pair;
return decodeURIComponent(name) !== key;
});
const nextQuery = nextPairs.join("&");
return `${base}${nextQuery ? `?${nextQuery}` : ""}${hash}`;
}
}
function normalizeSubtitleCandidates(urls) {
return Array.from(new Set((urls || []).filter(u => (function isHttpUrl(inputUrl) {
try {
const parsed = new URL(String(inputUrl || ""));
return "http:" === parsed.protocol || "https:" === parsed.protocol;
} catch (e) {
return !1;
}
})(u))));
}
function waitMs(ms) {
return new Promise(resolve => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
}
function buildMinimalRateLimitCandidates(candidates) {
const preferred = [];
for (const candidate of normalizeSubtitleCandidates(candidates)) {
const noFmt = removeQueryParam(candidate, "fmt");
const noTlang = removeQueryParam(noFmt, "tlang");
const noAsr = removeQueryParam(noTlang, "kind");
preferred.push(noAsr);
preferred.push(noTlang);
preferred.push(noFmt);
}
return normalizeSubtitleCandidates(preferred).slice(0, 3);
}
function buildSubtitleCandidates(baseUrl, translateTo) {
if (!baseUrl) {
return [];
}
let resolved = baseUrl;
try {
const url = new URL(resolved);
url.searchParams.delete("tlang");
url.searchParams.delete("fmt");
resolved = url.toString();
} catch (e) {
resolved = resolved.replace(/[&?]tlang=[^&]*/g, "").replace(/[&?]fmt=[^&]*/g, "");
resolved = resolved.replace(/\?&/, "?").replace(/\?$/, "");
}
translateTo && (resolved = setQueryParam(resolved, "tlang", translateTo));
const candidates = [ resolved, setQueryParam(resolved, "fmt", "srv1"), setQueryParam(resolved, "fmt", "json3"), setQueryParam(resolved, "fmt", "srv3"), setQueryParam(resolved, "fmt", "vtt") ];
return normalizeSubtitleCandidates(candidates);
}
function buildDirectSubtitleCandidates(videoId, languageCode, isAutoGenerated, translateTo) {
if (!videoId || !languageCode) {
return [];
}
const common = new URLSearchParams({
v: videoId,
lang: languageCode
});
isAutoGenerated && common.set("kind", "asr");
translateTo && common.set("tlang", translateTo);
const withFmt = (fmt = "") => {
const q = new URLSearchParams(common);
fmt && q.set("fmt", fmt);
return `https://www.youtube.com/api/timedtext?${q.toString()}`;
};
return normalizeSubtitleCandidates([ withFmt(), withFmt("srv1"), withFmt("srv3"), withFmt("json3"), withFmt("vtt") ]);
}
function classifySubtitlePayload(raw) {
const text = String(raw || "").trim();
if (!text || text.length < 10) {
return null;
}
if (text.includes("<!DOCTYPE") || text.includes("<html") || text.includes("</html>") || /^\s*<!DOCTYPE/i.test(text)) {
return null;
}
if (text.startsWith("{")) {
const cues = parseSubtitleJSON3(text);
return cues.length > 0 ? {
text,
kind: "json3"
} : null;
}
if (text.includes("WEBVTT") || text.includes("--\x3e")) {
const cues = parseSubtitleVTT(text);
return cues.length > 0 ? {
text,
kind: "vtt"
} : null;
}
if (text.includes("<transcript") || text.includes("<text")) {
const cues = parseSubtitleXML(text);
if (cues.length > 0) {
return {
text,
kind: "xml"
};
}
}
if (text.includes("<tt") || text.includes("<p ")) {
const cues = parseSubtitleTTML(text);
if (cues.length > 0) {
return {
text,
kind: "ttml"
};
}
}
return text.length > 20 ? {
text,
kind: "raw"
} : null;
}
async function fetchSubtitlePayloadViaPageFetch(candidates) {
try {
const pageGlobal = _pageGlobal();
const pageFetch = pageGlobal?.fetch;
if ("function" != typeof pageFetch) {
return null;
}
for (const candidateUrl of normalizeSubtitleCandidates(candidates)) {
try {
const response = await pageFetch(candidateUrl, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
Accept: "*/*"
}
});
if (!response || !response.ok) {
continue;
}
const raw = await response.text();
const detected = classifySubtitlePayload(raw);
if (detected) {
return detected;
}
} catch (e) {}
}
} catch (e) {}
return null;
}
async function fetchSubtitlePayload(candidates, options = {}) {
let firstNonEmpty = null;
let hadRateLimit = !1;
const minimalMode = !0 === options.minimalMode;
const allProfiles = [ {
method: "GET",
withCredentials: !0,
anonymous: !1,
responseType: "text",
headers: {
Referer: "https://www.youtube.com/",
"Accept-Encoding": "identity",
"Cache-Control": "no-cache",
Pragma: "no-cache"
}
}, {
method: "GET",
withCredentials: !0,
anonymous: !1,
headers: {
Referer: "https://www.youtube.com/",
"Accept-Encoding": "identity"
}
} ];
const profiles = minimalMode ? allProfiles.slice(0, 1) : allProfiles;
if (!minimalMode) {
try {
const candidateVideoId = (candidates || []).map(_getVideoIdFromCandidate).find(Boolean) || "";
candidateVideoId && !_potParamsByVideoId.has(candidateVideoId) && await (async function _tryElicitPotForCurrentVideo() {
if (_potElicitInflight) {
return !1;
}
_potElicitInflight = !0;
try {
const pg = _pageGlobal();
const videoId = (() => {
try {
const params = new URLSearchParams(pg.location?.search || "");
return params.get("v") || "";
} catch (e) {
return "";
}
})();
if (!videoId) {
return !1;
}
if (_potParamsByVideoId.has(videoId)) {
return !0;
}
const playerEl = pg.document?.querySelector?.(".html5-video-player");
if (!playerEl) {
return !1;
}
let priorTrack = null;
try {
priorTrack = "function" == typeof playerEl.getOption ? playerEl.getOption("captions", "track") : null;
} catch (e) {
priorTrack = null;
}
let trackToLoad = null;
try {
const list = "function" == typeof playerEl.getOption && playerEl.getOption("captions", "tracklist", {
includeAsr: !0
}) || [];
Array.isArray(list) && list.length && (trackToLoad = list.find(t => t && "ru" === t.languageCode && "asr" === t.kind) || list[0]);
} catch (e) {
trackToLoad = null;
}
if (trackToLoad && "function" == typeof playerEl.setOption) {
try {
playerEl.setOption("captions", "track", trackToLoad);
} catch (e) {}
} else {
try {
const ccBtn = pg.document?.querySelector?.(".ytp-subtitles-button");
ccBtn && "true" !== ccBtn.getAttribute("aria-pressed") && ccBtn.click();
} catch (e) {}
}
const deadline = Date.now() + 2200;
for (;Date.now() < deadline && !_potParamsByVideoId.has(videoId); ) {
await waitMs(120);
}
try {
if (priorTrack && "function" == typeof playerEl.setOption) {
playerEl.setOption("captions", "track", priorTrack);
} else if ("function" == typeof playerEl.toggleSubtitles) {
const ccBtn = pg.document?.querySelector?.(".ytp-subtitles-button");
ccBtn && "true" === ccBtn.getAttribute("aria-pressed") && ccBtn.click();
}
} catch (e) {}
return _potParamsByVideoId.has(videoId);
} catch (e) {
return !1;
} finally {
_potElicitInflight = !1;
}
})();
} catch (e) {}
}
const baseNormalized = minimalMode ? buildMinimalRateLimitCandidates(candidates) : normalizeSubtitleCandidates(candidates);
const normalizedCandidates = minimalMode ? baseNormalized : (function augmentSubtitleCandidatesWithPot(candidates) {
if (!Array.isArray(candidates) || 0 === candidates.length) {
return candidates || [];
}
const augmented = [];
for (const candidate of candidates) {
try {
const videoId = _getVideoIdFromCandidate(candidate);
const params = videoId ? _potParamsByVideoId.get(videoId) : null;
if (!params) {
continue;
}
const u = new URL(candidate, "https://www.youtube.com");
for (const [k, v] of Object.entries(params)) {
u.searchParams.set(k, v);
}
augmented.push(u.toString());
} catch (e) {}
}
const merged = augmented.concat(candidates);
return Array.from(new Set(merged.filter(u => "string" == typeof u && u.length > 0)));
})(baseNormalized);
if (!minimalMode) {
try {
const earlyPageFetched = await fetchSubtitlePayloadViaPageFetch(normalizedCandidates);
if (earlyPageFetched && "raw" !== earlyPageFetched.kind) {
return {
payload: earlyPageFetched,
hadRateLimit
};
}
earlyPageFetched && !firstNonEmpty && (firstNonEmpty = earlyPageFetched);
} catch (e) {}
}
for (const candidateUrl of normalizedCandidates) {
for (const profile of profiles) {
try {
const response = await gmXmlHttpRequest({
...profile,
url: candidateUrl
});
const status = Number(response?.status || 0);
if (429 === status) {
hadRateLimit = !0;
await waitMs(350);
continue;
}
if (0 !== status && !(status >= 200 && status < 400)) {
continue;
}
const raw = await extractSubtitleBody(response);
const detected = classifySubtitlePayload(raw);
if (detected && "raw" !== detected.kind) {
return {
payload: detected,
hadRateLimit
};
}
!firstNonEmpty && detected && (firstNonEmpty = detected);
} catch (e) {}
}
hadRateLimit && await waitMs(220);
}
if (!firstNonEmpty) {
const pageFetched = await fetchSubtitlePayloadViaPageFetch(normalizedCandidates);
if (pageFetched) {
return {
payload: pageFetched,
hadRateLimit
};
}
}
if (!firstNonEmpty && hadRateLimit && !minimalMode) {
await waitMs(1200);
return fetchSubtitlePayload(normalizedCandidates, {
minimalMode: !0
});
}
return {
payload: firstNonEmpty,
hadRateLimit
};
}
function triggerBlobDownload(blob, filename, revokeDelayMs = 1500) {
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobUrl;
a.download = filename;
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout_(() => URL.revokeObjectURL(blobUrl), Math.max(500, Number(revokeDelayMs) || 1500));
}
async function downloadSubtitle(options = {}) {
const {videoId, url: baseUrl, languageCode, languageName, isAutoGenerated = !1, format = "srt", translateTo = null, trackId = ""} = options;
if (!videoId || !baseUrl && !languageCode) {
throw new Error("Video ID and subtitle source are required");
}
const title = getVideoTitle();
const isFirefox = /firefox/i.test(navigator.userAgent || "");
const translatedCandidates = [ ...buildSubtitleCandidates(baseUrl, translateTo), ...buildDirectSubtitleCandidates(videoId, languageCode, isAutoGenerated, translateTo) ];
const sourceCandidates = [ ...buildSubtitleCandidates(baseUrl, null), ...buildDirectSubtitleCandidates(videoId, languageCode, isAutoGenerated, null) ];
const sourceNoAsrCandidates = [ ...buildSubtitleCandidates(removeQueryParam(String(baseUrl || ""), "tlang"), null), ...buildDirectSubtitleCandidates(videoId, languageCode, !1, null) ];
const primaryCandidates = translateTo ? translatedCandidates : sourceCandidates;
const candidates = isFirefox ? buildMinimalRateLimitCandidates(translateTo ? [ ...primaryCandidates ] : [ ...sourceNoAsrCandidates, ...sourceCandidates ]) : primaryCandidates;
NotificationManager.show(t("subtitleDownloading"), {
duration: 2e3,
type: "info"
});
let sawRateLimit = !1;
try {
let {payload, hadRateLimit} = await fetchSubtitlePayload(candidates, {
minimalMode: isFirefox
});
sawRateLimit = sawRateLimit || hadRateLimit;
!payload && hadRateLimit && await waitMs(900);
if (!payload && isAutoGenerated && !translateTo) {
const langOnlyAttempt = await fetchSubtitlePayload(sourceNoAsrCandidates);
payload = langOnlyAttempt.payload;
hadRateLimit = hadRateLimit || langOnlyAttempt.hadRateLimit;
sawRateLimit = sawRateLimit || langOnlyAttempt.hadRateLimit;
}
if (!payload && hadRateLimit) {
const rateLimitCandidates = buildMinimalRateLimitCandidates(translateTo ? [ ...translatedCandidates ] : [ ...sourceNoAsrCandidates, ...sourceCandidates ]);
const finalAttempt = await fetchSubtitlePayload(rateLimitCandidates, {
minimalMode: !0
});
payload = finalAttempt.payload;
sawRateLimit = sawRateLimit || finalAttempt.hadRateLimit;
}
if (!payload) {
try {
const freshPlayerResponse = await fetchPlayerResponseFromWatchHtml(videoId);
const freshTracks = freshPlayerResponse?.captions?.playerCaptionsTracklistRenderer?.captionTracks || [];
const freshTrack = findBestCaptionTrack(freshTracks, {
languageCode,
isAutoGenerated,
baseUrl,
trackId
});
if (freshTrack?.baseUrl) {
const freshBase = normalizeSubtitleBaseUrl(freshTrack.baseUrl);
const freshCandidates = translateTo ? [ ...buildSubtitleCandidates(freshBase, translateTo), ...buildDirectSubtitleCandidates(videoId, languageCode, isAutoGenerated, translateTo) ] : [ ...buildSubtitleCandidates(freshBase, null), ...buildDirectSubtitleCandidates(videoId, languageCode, isAutoGenerated, null), ...buildDirectSubtitleCandidates(videoId, languageCode, !1, null) ];
const freshAttempt = await fetchSubtitlePayload(freshCandidates);
payload = freshAttempt.payload;
sawRateLimit = sawRateLimit || freshAttempt.hadRateLimit;
}
} catch (e) {}
}
if (!payload) {
if (translateTo) {
throw new Error("Translated subtitle track is unavailable for this video/language");
}
throw new Error("Empty subtitle response");
}
const subtitleText = payload.text;
const subtitleKind = payload.kind;
let cues = [];
if ("xml" === subtitleKind) {
cues = parseSubtitleXML(subtitleText);
} else if ("json3" === subtitleKind) {
cues = parseSubtitleJSON3(subtitleText);
} else if ("vtt" === subtitleKind) {
cues = parseSubtitleVTT(subtitleText);
} else if ("ttml" === subtitleKind) {
cues = parseSubtitleTTML(subtitleText);
} else {
cues = parseSubtitleXML(subtitleText);
0 === cues.length && (cues = parseSubtitleJSON3(subtitleText));
0 === cues.length && (cues = parseSubtitleVTT(subtitleText));
0 === cues.length && (cues = parseSubtitleTTML(subtitleText));
}
let content;
let extension;
if ("xml" === format) {
content = "xml" === subtitleKind ? subtitleText : convertToXML(cues);
extension = "xml";
} else {
if (0 === cues.length) {
throw new Error("No subtitle cues found");
}
if ("srt" === format) {
content = (function convertToSRT(cues) {
let srt = "";
cues.forEach((cue, index) => {
const startTime = formatSRTTime(cue.start);
const endTime = formatSRTTime(cue.start + cue.duration);
const text = cue.text.replace(/\n/g, " ").trim();
srt += `${index + 1}\n`;
srt += `${startTime} --\x3e ${endTime}\n`;
srt += `${text}\n\n`;
});
return srt;
})(cues);
extension = "srt";
} else if ("txt" === format) {
content = (function convertToTXT(cues) {
return cues.map(cue => cue.text.trim()).join("\n");
})(cues);
extension = "txt";
} else {
content = "xml" === subtitleKind ? subtitleText : convertToXML(cues);
extension = "xml";
}
}
const langSuffix = translateTo ? `${languageCode}-${translateTo}` : languageCode;
const filename = sanitizeFilename(`${title} - ${languageName} (${langSuffix}).${extension}`);
const blob = new Blob([ content ], {
type: "text/plain;charset=utf-8"
});
triggerBlobDownload(blob, filename);
NotificationManager.show(t("subtitleDownloaded"), {
duration: 3e3,
type: "success"
});
logger.debug("Subtitle downloaded:", filename);
} catch (error) {
sawRateLimit && "Empty subtitle response" === String(error?.message || "") && (error.message = "YouTube temporarily limited subtitle requests (HTTP 429). Please retry in 20-60 seconds.");
logger.error("Error downloading subtitle:", error);
NotificationManager.show(`${t("subtitleDownloadFailed")} ${error.message}`, {
duration: 5e3,
type: "error"
});
throw error;
}
}
async function downloadVideo(options = {}) {
const {format = DownloadConfig.DEFAULTS.format, quality = DownloadConfig.DEFAULTS.videoQuality, audioBitrate = DownloadConfig.DEFAULTS.audioBitrate, embedThumbnail = DownloadConfig.DEFAULTS.embedThumbnail, onProgress = null} = options;
const videoId = getVideoId();
if (!videoId) {
throw new Error("Video ID not found");
}
const videoUrl = getVideoUrl();
const title = getVideoTitle();
NotificationManager.show(t("startingDownload"), {
duration: 2e3,
type: "info"
});
try {
logger.debug("Fetching API key...");
const keyResponse = await gmXmlHttpRequest({
method: "GET",
url: DownloadConfig.API.KEY_URL,
headers: DownloadConfig.HEADERS
});
if (200 !== keyResponse.status) {
throw new Error(`Failed to get API key: ${keyResponse.status}`);
}
const keyData = JSON.parse(keyResponse.responseText);
if (!keyData || !keyData.key) {
throw new Error("API key not found in response");
}
const {key} = keyData;
logger.debug("API key obtained");
let payload;
if ("video" === format) {
const codec = parseInt(quality, 10) > 1080 ? "vp9" : "h264";
payload = {
link: videoUrl,
format: "mp4",
audioBitrate: "128",
videoQuality: quality,
filenameStyle: "pretty",
vCodec: codec
};
} else {
payload = {
link: videoUrl,
format: "mp3",
audioBitrate,
filenameStyle: "pretty"
};
}
logger.debug("Requesting conversion...", {
format: payload?.format,
videoQuality: payload?.videoQuality,
audioBitrate: payload?.audioBitrate
});
const customHeaders = {
...DownloadConfig.HEADERS,
key
};
const downloadResponse = await gmXmlHttpRequest({
method: "POST",
url: DownloadConfig.API.CONVERT_URL,
headers: customHeaders,
data: JSON.stringify(payload)
});
if (200 !== downloadResponse.status) {
throw new Error(`Conversion failed: ${downloadResponse.status}`);
}
const apiDownloadInfo = JSON.parse(downloadResponse.responseText);
logger.debug("Conversion response received");
if (!apiDownloadInfo.url) {
throw new Error("No download URL received from API");
}
logger.debug("Downloading file from:", (function sanitizeUrlForLog(rawUrl) {
try {
if (!rawUrl || "string" != typeof rawUrl) {
return "";
}
const u = new URL(rawUrl, window.location.origin || "https://www.youtube.com");
const sensitiveParams = [ "v", "videoId", "pot", "potc", "key", "token", "sig", "signature", "oauth", "authorization", "cookie" ];
for (const name of sensitiveParams) {
u.searchParams.has(name) && u.searchParams.set(name, "<redacted>");
}
return `${u.origin}${u.pathname}`;
} catch (e) {
return "<redacted-url>";
}
})(String(apiDownloadInfo.url || "")));
return new Promise((resolve, reject) => {
if ("undefined" != typeof GM_xmlhttpRequest) {
GM_xmlhttpRequest({
method: "GET",
url: apiDownloadInfo.url,
responseType: "blob",
headers: {
"User-Agent": DownloadConfig.HEADERS["User-Agent"],
Referer: "https://mp3yt.is/",
Accept: "*/*"
},
onprogress: progress => {
onProgress && onProgress({
loaded: progress.loaded,
total: progress.total,
percent: progress.total ? Math.round(progress.loaded / progress.total * 100) : 0
});
},
onload: async response => {
if (200 === response.status && response.response) {
let blob = response.response;
if (0 === blob.size) {
reject(new Error(t("zeroBytesError")));
return;
}
window.YouTubeUtils && YouTubeUtils.logger?.debug && YouTubeUtils.logger.debug(`[Download] File downloaded: ${formatBytes(blob.size)}`);
if ("audio" === format && embedThumbnail) {
try {
logger.debug("Embedding album art...");
const thumbnailUrl = `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`;
const albumArt = await (function createSquareAlbumArt(thumbnailUrl) {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.crossOrigin = "anonymous";
img.onload = () => {
const canvas = document.createElement("canvas");
const size = Math.min(img.width, img.height);
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
if (!ctx) {
canvas.width = 0;
canvas.height = 0;
reject(new Error("Failed to get canvas context"));
return;
}
const sx = (img.width - size) / 2;
const sy = (img.height - size) / 2;
ctx.drawImage(img, sx, sy, size, size, 0, 0, size, size);
canvas.toBlob(blob => {
canvas.width = 0;
canvas.height = 0;
blob ? resolve(blob) : reject(new Error("Failed to create blob"));
}, "image/jpeg", .95);
};
img.onerror = () => reject(new Error("Failed to load thumbnail"));
img.src = thumbnailUrl;
});
})(thumbnailUrl);
blob = await (async function embedAlbumArtToMP3(mp3Blob, albumArtBlob, metadata) {
try {
if (void 0 === window.ID3Writer) {
logger.warn("ID3Writer not available, skipping album art embedding");
return mp3Blob;
}
const arrayBuffer = await mp3Blob.arrayBuffer();
const writer = new window.ID3Writer(arrayBuffer);
metadata.title && writer.setFrame("TIT2", metadata.title);
metadata.artist && writer.setFrame("TPE1", [ metadata.artist ]);
metadata.album && writer.setFrame("TALB", metadata.album);
if (albumArtBlob) {
const coverArrayBuffer = await albumArtBlob.arrayBuffer();
writer.setFrame("APIC", {
type: 3,
data: coverArrayBuffer,
description: "Cover"
});
}
writer.addTag();
return new Blob([ writer.arrayBuffer ], {
type: "audio/mpeg"
});
} catch (error) {
logger.error("Error embedding album art:", error);
return mp3Blob;
}
})(blob, albumArt, {
title
});
logger.debug("Album art embedded successfully");
} catch (error) {
logger.error("Failed to embed album art:", error);
}
}
const filename = apiDownloadInfo.filename || `${title}.${"video" === format ? "mp4" : "mp3"}`;
triggerBlobDownload(blob, sanitizeFilename(filename), 2e3);
NotificationManager.show(t("downloadCompleted"), {
duration: 3e3,
type: "success"
});
logger.debug("Download completed:", filename);
resolve();
} else {
reject(new Error(`Download failed: ${response.status}`));
}
},
onerror: () => reject(new Error("Download failed - network error")),
ontimeout: () => reject(new Error("Download timeout"))
});
} else {
logger.warn("GM_xmlhttpRequest not available, opening in new tab");
window.open(apiDownloadInfo.url, "_blank");
resolve();
}
});
} catch (error) {
logger.error("Error:", error);
NotificationManager.show(`${t("downloadFailed")} ${error.message}`, {
duration: 5e3,
type: "error"
});
throw error;
}
}
let _modalElements = null;
function buildModalForm() {
const qualitySelect = document.createElement("div");
qualitySelect.role = "radiogroup";
qualitySelect.value = DownloadConfig.DEFAULTS.videoQuality;
Object.assign(qualitySelect.style || {}, {
display: "flex",
flexWrap: "wrap",
gap: "10px",
padding: "12px 6px",
borderRadius: "10px",
width: "100%",
alignItems: "center",
justifyContent: "center",
background: "transparent"
});
const embedCheckbox = document.createElement("input");
embedCheckbox.type = "checkbox";
embedCheckbox.checked = DownloadConfig.DEFAULTS.embedThumbnail;
const embedLabel = document.createElement("label");
embedLabel.style.fontSize = "13px";
embedLabel.style.display = "flex";
embedLabel.style.alignItems = "center";
embedLabel.style.gap = "6px";
embedLabel.style.color = "var(--yt-text-primary)";
embedLabel.style.display = "none";
embedLabel.appendChild(embedCheckbox);
embedLabel.appendChild(document.createTextNode(t("embedThumbnail")));
const subtitleWrapper = document.createElement("div");
subtitleWrapper.style.display = "none";
const subtitleSelect = (function createSubtitleSelect() {
const subtitleSelect = document.createElement("div");
subtitleSelect.setAttribute("role", "listbox");
subtitleSelect.setAttribute("aria-expanded", "false");
subtitleSelect.setAttribute("aria-label", "Subtitle language");
subtitleSelect.setAttribute("tabindex", "0");
Object.assign(subtitleSelect.style || {}, {
position: "relative",
width: "100%",
marginBottom: "8px",
fontSize: "14px",
color: "var(--yt-text-primary)",
cursor: "pointer"
});
const _ssDisplay = document.createElement("div");
Object.assign(_ssDisplay.style || {}, {
padding: "10px 12px",
borderRadius: "10px",
background: "linear-gradient(135deg, var(--yt-glass-bg), var(--yt-surface-overlay-faint))",
border: "1px solid var(--yt-glass-border)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "8px",
backdropFilter: "blur(6px)",
boxShadow: "0 4px 18px var(--yt-shadow-inset-strong) inset"
});
const _ssLabel = document.createElement("div");
if (_ssLabel.style) {
_ssLabel.style.flex = "1";
_ssLabel.style.overflow = "hidden";
_ssLabel.style.textOverflow = "ellipsis";
_ssLabel.style.whiteSpace = "nowrap";
}
_ssLabel.textContent = t("loading");
const _ssChevron = document.createElement("div");
_ssChevron.textContent = "▾";
_ssChevron.style && (_ssChevron.style.opacity = "0.8");
_ssDisplay.appendChild(_ssLabel);
_ssDisplay.appendChild(_ssChevron);
const _ssList = document.createElement("div");
Object.assign(_ssList.style || {}, {
position: "absolute",
top: "calc(100% + 8px)",
left: "0",
right: "0",
maxHeight: "220px",
overflowY: "auto",
borderRadius: "10px",
background: "linear-gradient(180deg, var(--yt-glass-bg), var(--yt-surface-overlay-faint))",
border: "1px solid var(--yt-glass-border)",
boxShadow: "0 8px 30px var(--yt-shadow-flyout)",
backdropFilter: "blur(8px)",
zIndex: "9999",
display: "none"
});
subtitleSelect.appendChild(_ssDisplay);
subtitleSelect.appendChild(_ssList);
_ssList.addEventListener("click", e => {
const target = e.target;
if (!(target instanceof HTMLElement)) {
return;
}
const item = target.closest("[data-value]");
if (item && _ssList.contains(item)) {
subtitleSelect.value = item.dataset?.value || "";
_ssList.style && (_ssList.style.display = "none");
}
});
_ssList.addEventListener("mouseover", e => {
const target = e.target;
if (!(target instanceof HTMLElement)) {
return;
}
const item = target.closest("[data-value]");
item && _ssList.contains(item) && item.style && (item.style.background = "var(--yt-surface-overlay-faint)");
});
_ssList.addEventListener("mouseout", e => {
const target = e.target;
if (!(target instanceof HTMLElement)) {
return;
}
const item = target.closest("[data-value]");
if (!item || !_ssList.contains(item)) {
return;
}
const related = e.relatedTarget;
related && item.contains(related) || item.style && (item.style.background = "transparent");
});
subtitleSelect._options = [];
subtitleSelect._value = "";
subtitleSelect._disabled = !1;
subtitleSelect.setPlaceholder = text => {
_ssLabel.textContent = text || "";
subtitleSelect._options = [];
_ssList.replaceChildren();
subtitleSelect._value = "";
};
subtitleSelect.setOptions = options => {
subtitleSelect._options = options || [];
_ssList.replaceChildren();
subtitleSelect._options.forEach(opt => {
const item = document.createElement("div");
item.textContent = opt.text;
item.dataset.value = String(opt.value);
Object.assign(item.style || {}, {
padding: "10px 12px",
cursor: "pointer",
borderBottom: "1px solid var(--yt-surface-overlay-faint)",
color: "var(--yt-text-primary)"
});
_ssList.appendChild(item);
});
if (subtitleSelect._options.length > 0) {
subtitleSelect.value = String(subtitleSelect._options[0].value);
} else {
subtitleSelect._value = "";
_ssLabel.textContent = t("noSubtitles");
}
};
Object.defineProperty(subtitleSelect, "value", {
get: () => subtitleSelect._value,
set(v) {
subtitleSelect._value = String(v);
const found = subtitleSelect._options.find(o => String(o.value) === subtitleSelect._value);
_ssLabel.textContent = found ? found.text : "";
}
});
Object.defineProperty(subtitleSelect, "disabled", {
get: () => subtitleSelect._disabled,
set(v) {
subtitleSelect._disabled = !!v;
_ssDisplay.style && (_ssDisplay.style.opacity = subtitleSelect._disabled ? "0.5" : "1");
subtitleSelect.style && (subtitleSelect.style.pointerEvents = subtitleSelect._disabled ? "none" : "auto");
}
});
_ssDisplay.addEventListener("click", () => {
if (subtitleSelect._disabled) {
return;
}
const isOpen = !!_ssList.style && "none" !== _ssList.style.display;
_ssList.style && (_ssList.style.display = isOpen ? "none" : "");
subtitleSelect.setAttribute("aria-expanded", isOpen ? "false" : "true");
});
subtitleSelect.addEventListener("keydown", e => {
if (subtitleSelect._disabled) {
return;
}
const isOpen = !!_ssList.style && "none" !== _ssList.style.display;
if ("Enter" === e.key || " " === e.key) {
e.preventDefault();
_ssList.style && (_ssList.style.display = isOpen ? "none" : "");
subtitleSelect.setAttribute("aria-expanded", isOpen ? "false" : "true");
} else if ("Escape" === e.key && isOpen) {
e.preventDefault();
_ssList.style && (_ssList.style.display = "none");
subtitleSelect.setAttribute("aria-expanded", "false");
} else if ("ArrowDown" === e.key || "ArrowUp" === e.key) {
e.preventDefault();
if (!isOpen) {
_ssList.style && (_ssList.style.display = "");
subtitleSelect.setAttribute("aria-expanded", "true");
}
const opts = subtitleSelect._options;
if (0 === opts.length) {
return;
}
const currentIdx = opts.findIndex(o => String(o.value) === subtitleSelect._value);
const nextIdx = "ArrowDown" === e.key ? Math.min(currentIdx + 1, opts.length - 1) : Math.max(currentIdx - 1, 0);
subtitleSelect.value = String(opts[nextIdx].value);
}
});
const _ac = new AbortController;
document.addEventListener("click", e => {
const target = e.target;
if (!(target instanceof Node && subtitleSelect.contains(target))) {
_ssList.style && (_ssList.style.display = "none");
subtitleSelect.setAttribute("aria-expanded", "false");
}
}, {
signal: _ac.signal
});
subtitleSelect.destroy = () => _ac.abort();
return subtitleSelect;
})();
const formatSelect = document.createElement("div");
formatSelect.role = "radiogroup";
formatSelect.value = "srt";
Object.assign(formatSelect.style || {}, {
display: "flex",
gap: "8px",
padding: "6px 0",
borderRadius: "6px",
width: "100%",
alignItems: "center",
justifyContent: "center",
background: "transparent"
});
[ "srt", "txt", "xml" ].forEach(fmt => {
const btn = document.createElement("button");
btn.type = "button";
btn.setAttribute("role", "radio");
btn.setAttribute("aria-checked", "false");
btn.dataset.value = fmt;
btn.textContent = fmt.toUpperCase();
Object.assign(btn.style || {}, {
padding: "6px 12px",
borderRadius: "999px",
border: "1px solid var(--yt-surface-soft)",
background: "var(--yt-surface-overlay-faint)",
color: "var(--yt-text-primary)",
cursor: "pointer",
fontSize: "13px",
fontWeight: "600"
});
btn.addEventListener("click", () => {
Array.from(formatSelect.children).forEach(c => {
c.style.background = "transparent";
c.style.color = "var(--yt-text-primary)";
c.style.border = "1px solid var(--yt-surface-soft)";
c.setAttribute && c.setAttribute("aria-checked", "false");
});
btn.style.background = "var(--yt-surface-contrast)";
btn.style.color = "var(--yt-success-accent)";
btn.style.border = "1px solid var(--yt-success-accent-soft)";
btn.setAttribute("aria-checked", "true");
formatSelect.value = fmt;
});
formatSelect.appendChild(btn);
});
const _defaultFmtBtn = Array.from(formatSelect.children).find(c => c.dataset?.value === formatSelect.value);
_defaultFmtBtn && _defaultFmtBtn.click();
subtitleWrapper.appendChild(subtitleSelect);
subtitleWrapper.appendChild(formatSelect);
const cancelBtn = document.createElement("button");
cancelBtn.type = "button";
cancelBtn.textContent = t("cancel");
Object.assign(cancelBtn.style || {}, {
padding: "8px 16px",
borderRadius: "8px",
border: "1px solid var(--yt-surface-active)",
background: "transparent",
cursor: "pointer",
fontSize: "14px",
color: "var(--yt-text-primary)"
});
const downloadBtn = document.createElement("button");
downloadBtn.type = "button";
downloadBtn.textContent = t("download");
Object.assign(downloadBtn.style || {}, {
padding: "8px 20px",
borderRadius: "8px",
border: "1px solid var(--yt-surface-active)",
background: "transparent",
color: "var(--yt-text-primary)",
cursor: "pointer",
fontSize: "14px",
fontWeight: "600"
});
const progressWrapper = document.createElement("div");
progressWrapper.style.display = "none";
progressWrapper.style.marginTop = "12px";
const progressBar = document.createElement("div");
Object.assign(progressBar.style || {}, {
width: "100%",
height: "3px",
background: "var(--yt-progress-track)",
borderRadius: "5px",
overflow: "hidden",
marginBottom: "6px"
});
const progressFill = document.createElement("div");
Object.assign(progressFill.style || {}, {
width: "0%",
height: "100%",
background: "var(--yt-progress-fill)",
transition: "width 200ms linear"
});
progressBar.appendChild(progressFill);
const progressText = document.createElement("div");
progressText.style.fontSize = "12px";
progressText.style.color = "var(--yt-muted-text)";
progressWrapper.appendChild(progressBar);
progressWrapper.appendChild(progressText);
return {
qualitySelect,
embedLabel,
subtitleWrapper,
subtitleSelect,
formatSelect,
cancelBtn,
downloadBtn,
progressWrapper,
progressFill,
progressText
};
}
function enableFormControls(formParts) {
try {
formParts.qualitySelect && (formParts.qualitySelect.disabled = !1);
formParts.downloadBtn && (formParts.downloadBtn.disabled = !1);
formParts.cancelBtn && (formParts.cancelBtn.disabled = !1);
if (formParts.downloadBtn) {
formParts.downloadBtn.style.opacity = "1";
formParts.downloadBtn.style.cursor = "pointer";
formParts.downloadBtn.style.pointerEvents = "auto";
}
} catch (e) {
window.console.error("Error enabling form controls:", e);
}
}
function wireModalEvents(formParts, activeFormatGetter, getSubtitlesData) {
formParts.cancelBtn.addEventListener("click", () => closeModal());
formParts.downloadBtn.addEventListener("click", async () => {
if (formParts.downloadBtn.disabled) {
return;
}
!(function disableFormControls(formParts) {
try {
formParts.qualitySelect && (formParts.qualitySelect.disabled = !0);
if (formParts.downloadBtn) {
formParts.downloadBtn.disabled = !0;
formParts.downloadBtn.style.opacity = "0.5";
formParts.downloadBtn.style.cursor = "not-allowed";
}
formParts.cancelBtn && (formParts.cancelBtn.disabled = !0);
} catch (e) {
window.console.error("Error disabling form controls:", e);
}
})(formParts);
!(function initializeProgress(formParts) {
formParts.progressWrapper.style.display = "";
formParts.progressFill.style.width = "0%";
formParts.progressText.textContent = t("starting");
})(formParts);
const format = activeFormatGetter();
try {
"subtitle" === format ? await (async function handleSubtitleDownload(formParts, getSubtitlesData) {
const subtitlesData = getSubtitlesData();
const selectedIndex = parseInt(formParts.subtitleSelect.value, 10);
const subtitle = subtitlesData.all[selectedIndex];
const subtitleFormat = formParts.formatSelect.value;
if (!subtitle) {
throw new Error(t("noSubtitleSelected"));
}
const videoId = getVideoId() || "";
const effectiveLanguageCode = subtitle.sourceLanguageCode || subtitle.languageCode;
const effectiveTranslateTo = subtitle.translateTo || null;
await downloadSubtitle({
videoId,
url: subtitle.baseUrl || subtitle.url,
languageCode: effectiveLanguageCode,
languageName: subtitle.name,
isAutoGenerated: !!subtitle.isAutoGenerated,
format: subtitleFormat,
translateTo: effectiveTranslateTo,
trackId: subtitle.trackId || ""
});
})(formParts, getSubtitlesData) : await (async function handleMediaDownload(formParts, format) {
const opts = {
format,
quality: formParts.qualitySelect.value,
audioBitrate: formParts.qualitySelect.value,
embedThumbnail: "audio" === format,
onProgress: p => {
const loaded = Number(p?.loaded || 0);
const total = Number(p?.total || 0);
const hasTotal = Number.isFinite(total) && total > 0;
let percent = Number(p?.percent || 0);
if (hasTotal) {
percent = Math.max(0, Math.min(100, Math.round(loaded / total * 100)));
formParts.progressFill.style.width = `${percent}%`;
formParts.progressText.textContent = `${percent}% • ${formatBytes(loaded)} / ${formatBytes(total)}`;
return;
}
const pseudoPercent = Math.min(95, Math.max(5, Math.round(4 * Math.log2(loaded + 1))));
formParts.progressFill.style.width = `${pseudoPercent}%`;
formParts.progressText.textContent = `${t("downloading")} • ${formatBytes(loaded)} / —`;
}
};
await downloadVideo(opts);
})(formParts, format);
!(function completeDownload(formParts) {
formParts.progressText.textContent = t("completed");
setTimeout(() => closeModal(), 800);
})(formParts);
} catch (err) {
window.console.error("[Download Error]:", err);
!(function handleDownloadError(formParts, err) {
const errorMsg = err?.message || "Unknown error";
formParts.progressText.textContent = `${t("downloadFailed")} ${errorMsg}`;
formParts.progressText.style.color = "var(--yt-danger-text)";
enableFormControls(formParts);
setTimeout_(() => {
try {
enableFormControls(formParts);
} catch (e) {
window.console.error("Failed to re-enable controls:", e);
}
}, 500);
setTimeout_(() => {
formParts.progressText.style.color = "#fff";
}, 3e3);
})(formParts, err);
} finally {
setTimeout_(() => {
formParts.downloadBtn && !formParts.downloadBtn.disabled || enableFormControls(formParts);
}, 1e3);
}
});
}
function updateQualityOptionsForForm(formParts, activeFormat, subtitlesData) {
if ("subtitle" === activeFormat) {
formParts.qualitySelect.style.display = "none";
formParts.embedLabel.style.display = "none";
formParts.subtitleWrapper.style.display = "block";
!(async function loadSubtitlesForForm(formParts, subtitlesData) {
const videoId = getVideoId();
if (videoId) {
formParts.subtitleSelect.setPlaceholder(t("loading"));
formParts.subtitleSelect.disabled = !0;
try {
const data = await getSubtitles(videoId);
if (!data) {
formParts.subtitleSelect.setPlaceholder(t("noSubtitles"));
return;
}
subtitlesData.original = data.subtitles;
subtitlesData.translated = data.autoTransSubtitles.map(autot => ({
...autot,
url: autot.url || (autot.baseUrl ? buildSubtitleUrl(autot.baseUrl) : ""),
translateTo: autot.languageCode
}));
subtitlesData.all = [ ...subtitlesData.original, ...subtitlesData.translated ];
if (0 === subtitlesData.all.length) {
formParts.subtitleSelect.setPlaceholder(t("noSubtitles"));
return;
}
const opts = subtitlesData.all.map((sub, idx) => ({
value: idx,
text: sub.name + (sub.translateTo ? t("autoTranslateSuffix") : "")
}));
formParts.subtitleSelect.setOptions(opts);
formParts.subtitleSelect.disabled = !1;
} catch (err) {
logger.error("Failed to load subtitles:", err);
formParts.subtitleSelect.setPlaceholder(t("subtitleLoadError"));
}
}
})(formParts, subtitlesData);
return;
}
if ("video" === activeFormat) {
formParts.qualitySelect.style.display = "flex";
formParts.embedLabel.style.display = "none";
formParts.subtitleWrapper.style.display = "none";
const videoId = getVideoId() || "";
const renderToken = String(Date.now()) + Math.random().toString(36).slice(2);
formParts.qualitySelect.dataset.renderToken = renderToken;
formParts.qualitySelect.replaceChildren();
const loadingLabel = document.createElement("div");
loadingLabel.textContent = t("loading");
Object.assign(loadingLabel.style || {}, {
fontSize: "13px",
color: "var(--yt-text-secondary)",
padding: "8px 0"
});
formParts.qualitySelect.appendChild(loadingLabel);
function makeQualityButton(q) {
const btn = document.createElement("button");
btn.type = "button";
btn.setAttribute("role", "radio");
btn.setAttribute("aria-checked", "false");
btn.dataset.value = q;
btn.textContent = `${q}p`;
Object.assign(btn.style || {}, {
display: "inline-flex",
alignItems: "center",
gap: "8px",
padding: "8px 12px",
borderRadius: "999px",
border: "1px solid var(--yt-surface-soft)",
background: "var(--yt-surface-overlay-faint)",
color: "var(--yt-text-primary)",
cursor: "pointer",
fontSize: "13px",
fontWeight: "600"
});
btn.addEventListener("click", () => {
Array.from(formParts.qualitySelect.children).forEach(c => {
if (c.dataset && c.dataset.value) {
c.style.background = "transparent";
c.style.color = "var(--yt-text-primary)";
c.style.border = "1px solid var(--yt-surface-soft)";
c.setAttribute && c.setAttribute("aria-checked", "false");
}
});
btn.style.background = "var(--yt-surface-contrast)";
btn.style.color = "var(--yt-success-accent)";
btn.style.border = "1px solid var(--yt-success-accent-soft)";
btn.setAttribute("aria-checked", "true");
formParts.qualitySelect.value = q;
});
return btn;
}
(async function getAvailableVideoQualities(videoId) {
if (!videoId) {
return DownloadConfig.VIDEO_QUALITIES.slice();
}
try {
const playerData = await fetchPlayerData(videoId);
const actualQualities = extractAvailableVideoQualities(playerData);
if (actualQualities.length > 0) {
return actualQualities;
}
} catch (error) {
logger.warn("Primary player quality fetch failed:", error);
}
try {
const fallbackPlayerData = await fetchPlayerResponseFromWatchHtml(videoId);
const fallbackQualities = extractAvailableVideoQualities(fallbackPlayerData);
if (fallbackQualities.length > 0) {
return fallbackQualities;
}
} catch (error) {
logger.warn("Watch HTML quality fallback failed:", error);
}
return DownloadConfig.VIDEO_QUALITIES.slice();
})(videoId).then(qualities => {
if ("video" !== activeFormat) {
return;
}
if (formParts.qualitySelect.dataset.renderToken !== renderToken) {
return;
}
const availableQualities = Array.isArray(qualities) && qualities.length > 0 ? qualities : DownloadConfig.VIDEO_QUALITIES.slice();
const lowQuals = availableQualities.filter(q => parseInt(q, 10) <= 1080);
const highQuals = availableQualities.filter(q => parseInt(q, 10) > 1080);
const previousQuality = String(formParts.qualitySelect.value || "");
formParts.qualitySelect.replaceChildren();
lowQuals.forEach(q => formParts.qualitySelect.appendChild(makeQualityButton(q)));
if (highQuals.length > 0) {
const labelWrap = document.createElement("div");
Object.assign(labelWrap.style || {}, {
display: "flex",
alignItems: "center",
gap: "12px",
width: "100%",
margin: "8px 0"
});
const lineLeft = document.createElement("div");
lineLeft.style.flex = "1";
lineLeft.style.borderTop = "1px solid var(--yt-surface-overlay-border)";
const label = document.createElement("div");
label.textContent = t("vp9Label");
Object.assign(label.style || {}, {
fontSize: "12px",
color: "var(--yt-text-secondary)",
padding: "0 8px"
});
const lineRight = document.createElement("div");
lineRight.style.flex = "1";
lineRight.style.borderTop = "1px solid var(--yt-surface-overlay-border)";
labelWrap.appendChild(lineLeft);
labelWrap.appendChild(label);
labelWrap.appendChild(lineRight);
formParts.qualitySelect.appendChild(labelWrap);
highQuals.forEach(q => formParts.qualitySelect.appendChild(makeQualityButton(q)));
}
formParts.qualitySelect.value = (function pickDefaultVideoQuality(qualities, preferredQuality) {
if (!Array.isArray(qualities) || 0 === qualities.length) {
return preferredQuality || DownloadConfig.DEFAULTS.videoQuality;
}
if (qualities.includes(preferredQuality)) {
return preferredQuality;
}
if (qualities.includes(DownloadConfig.DEFAULTS.videoQuality)) {
return DownloadConfig.DEFAULTS.videoQuality;
}
const sorted = qualities.slice().sort((left, right) => Number(left) - Number(right));
return sorted[sorted.length - 1] || preferredQuality || DownloadConfig.DEFAULTS.videoQuality;
})(availableQualities, previousQuality);
const defaultBtn = Array.from(formParts.qualitySelect.children).find(c => c.dataset && c.dataset.value === formParts.qualitySelect.value);
defaultBtn && defaultBtn.click();
});
return;
}
formParts.qualitySelect.style.display = "flex";
formParts.embedLabel.style.display = "flex";
formParts.subtitleWrapper.style.display = "none";
formParts.qualitySelect.replaceChildren();
DownloadConfig.AUDIO_BITRATES.forEach(b => {
const btn = document.createElement("button");
btn.type = "button";
btn.setAttribute("role", "radio");
btn.setAttribute("aria-checked", "false");
btn.dataset.value = b;
btn.textContent = `${b} kbps`;
Object.assign(btn.style || {}, {
display: "inline-flex",
alignItems: "center",
gap: "8px",
padding: "8px 12px",
borderRadius: "999px",
border: "1px solid var(--yt-surface-soft)",
background: "var(--yt-surface-overlay-faint)",
color: "var(--yt-text-primary)",
cursor: "pointer",
fontSize: "13px",
fontWeight: "600"
});
btn.addEventListener("click", () => {
Array.from(formParts.qualitySelect.children).forEach(c => {
c.style.background = "transparent";
c.style.color = "var(--yt-text-primary)";
c.style.border = "1px solid var(--yt-surface-soft)";
c.setAttribute && c.setAttribute("aria-checked", "false");
});
btn.style.background = "var(--yt-surface-contrast)";
btn.style.color = "var(--yt-success-accent)";
btn.style.border = "1px solid var(--yt-success-accent-soft)";
btn.setAttribute("aria-checked", "true");
formParts.qualitySelect.value = b;
});
formParts.qualitySelect.appendChild(btn);
});
formParts.qualitySelect.value = DownloadConfig.DEFAULTS.audioBitrate;
const defaultAudioBtn = Array.from(formParts.qualitySelect.children).find(c => c.dataset.value === formParts.qualitySelect.value);
defaultAudioBtn && defaultAudioBtn.click();
formParts.embedLabel.style.display = "none";
}
function createModalUI() {
if (_modalElements) {
return _modalElements;
}
let activeFormat = "video";
const subtitlesData = {
all: [],
original: [],
translated: []
};
const overlay = document.createElement("div");
Object.assign(overlay.style || {}, {
position: "fixed",
inset: "0",
background: "rgba(0,0,0,0.6)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: "999999"
});
const box = document.createElement("div");
Object.assign(box.style || {}, {
width: "420px",
maxWidth: "94%",
background: "var(--yt-modal-surface)",
color: "var(--yt-text-primary)",
borderRadius: "12px",
boxShadow: "0 8px 40px var(--yt-shadow-flyout)",
fontFamily: "Arial, sans-serif",
border: "1px solid var(--yt-surface-overlay-border)",
backdropFilter: "blur(8px)"
});
const formParts = buildModalForm();
const tabContainer = (function createTabButtons(onTabChange) {
const tabContainer = document.createElement("div");
tabContainer.setAttribute("role", "tablist");
Object.assign(tabContainer.style || {}, {
display: "flex",
gap: "8px",
padding: "12px",
justifyContent: "center",
alignItems: "center",
background: "transparent"
});
const videoTab = document.createElement("button");
videoTab.textContent = t("videoTab");
videoTab.dataset.format = "video";
const audioTab = document.createElement("button");
audioTab.textContent = t("audioTab");
audioTab.dataset.format = "audio";
const subTab = document.createElement("button");
subTab.textContent = t("subtitleTab");
subTab.dataset.format = "subtitle";
[ videoTab, audioTab, subTab ].forEach(btn => {
Object.assign(btn.style, {
flex: "initial",
padding: "8px 18px",
border: "1px solid var(--yt-surface-overlay-border)",
background: "transparent",
cursor: "pointer",
fontSize: "13px",
fontWeight: "600",
transition: "all 0.18s ease",
color: "var(--yt-muted-text)",
borderRadius: "999px"
});
btn.type = "button";
btn.setAttribute("role", "tab");
btn.setAttribute("aria-selected", "false");
btn.style.outline = "none";
btn.style.userSelect = "none";
});
function setActive(btn) {
[ videoTab, audioTab, subTab ].forEach(b => {
b.style.background = "transparent";
b.style.color = "var(--yt-muted-text)";
b.style.border = "1px solid var(--yt-surface-overlay-border)";
b.style.boxShadow = "none";
b.setAttribute("aria-selected", "false");
});
Object.assign(btn.style, {
background: "var(--yt-success-accent)",
color: "var(--yt-text-primary)",
border: "1px solid var(--yt-shadow-inset-soft)",
boxShadow: "0 1px 0 var(--yt-shadow-inset-soft) inset"
});
btn.setAttribute("aria-selected", "true");
try {
onTabChange(btn.dataset.format);
} catch (e) {}
}
[ videoTab, audioTab, subTab ].forEach(btn => {
btn.addEventListener("click", () => {
setActive(btn);
try {
btn.blur();
} catch (e) {}
});
});
tabContainer.appendChild(videoTab);
tabContainer.appendChild(audioTab);
tabContainer.appendChild(subTab);
tabContainer.addEventListener("keydown", e => {
if ("ArrowLeft" !== e.key && "ArrowRight" !== e.key) {
return;
}
const tabs = [ videoTab, audioTab, subTab ];
const idx = tabs.indexOf(document.activeElement);
if (idx < 0) {
return;
}
e.preventDefault();
const next = "ArrowRight" === e.key ? tabs[(idx + 1) % tabs.length] : tabs[(idx - 1 + tabs.length) % tabs.length];
next.focus();
next.click();
});
setTimeout(() => setActive(videoTab), 0);
return tabContainer;
})(format => {
activeFormat = format;
updateQualityOptionsForForm(formParts, activeFormat, subtitlesData);
});
const content = document.createElement("div");
content.style.padding = "16px";
content.appendChild(formParts.qualitySelect);
content.appendChild(formParts.embedLabel);
content.appendChild(formParts.subtitleWrapper);
content.appendChild(formParts.progressWrapper);
const btnRow = document.createElement("div");
Object.assign(btnRow.style || {}, {
display: "flex",
gap: "8px",
padding: "16px",
justifyContent: "center"
});
btnRow.appendChild(formParts.cancelBtn);
btnRow.appendChild(formParts.downloadBtn);
box.appendChild(tabContainer);
box.appendChild(content);
box.appendChild(btnRow);
overlay.appendChild(box);
updateQualityOptionsForForm(formParts, activeFormat, subtitlesData);
wireModalEvents(formParts, () => activeFormat, () => subtitlesData);
_modalElements = {
overlay,
box,
...formParts
};
return _modalElements;
}
function openModal() {
const els = createModalUI();
if (els) {
try {
document.body.contains(els.overlay) || document.body.appendChild(els.overlay);
} catch (e) {}
}
}
function closeModal() {
if (_modalElements) {
try {
const ss = _modalElements.overlay?.querySelector('[role="listbox"]');
ss && "function" == typeof ss.destroy && ss.destroy();
_modalElements.overlay && _modalElements.overlay.parentNode && _modalElements.overlay.parentNode.removeChild(_modalElements.overlay);
} catch (e) {}
_modalElements = null;
}
}
const buildUrl = (template, videoId, videoUrl) => (template || "").replace("{videoId}", videoId || "").replace("{videoUrl}", encodeURIComponent(videoUrl || ""));
const positionDropdown = (() => {
let rafId = null;
let pendingButton = null;
let pendingDropdown = null;
const applyPosition = () => {
if (!pendingButton || !pendingDropdown) {
return;
}
const rect = pendingButton.getBoundingClientRect();
const left = Math.max(8, rect.left + rect.width / 2 - 75);
const bottom = Math.max(8, window.innerHeight - rect.top + 12);
pendingDropdown.style.left = `${left}px`;
pendingDropdown.style.bottom = `${bottom}px`;
rafId = null;
pendingButton = null;
pendingDropdown = null;
};
return (button, dropdown) => {
pendingButton = button;
pendingDropdown = dropdown;
null === rafId && (rafId = requestAnimationFrame(applyPosition));
};
})();
const createDownloadActions = (tFn, ytUtils) => {
const handleDirectDownload = async () => {
const api = await (timeout => new Promise(resolve => {
let waited = 0;
if (void 0 !== window.YouTubePlusDownload) {
return resolve(window.YouTubePlusDownload);
}
const id = createVisibilityAwareInterval(() => {
waited += 200;
if (void 0 !== window.YouTubePlusDownload) {
id.stop();
return resolve(window.YouTubePlusDownload);
}
if (waited >= timeout) {
id.stop();
return resolve(void 0);
}
}, 200);
try {
window.YouTubeUtils?.cleanupManager?.register && window.YouTubeUtils.cleanupManager.register(() => id.stop());
} catch (e) {}
}))(2e3);
if (api) {
try {
if ("function" == typeof api.openModal) {
api.openModal();
return;
}
if ("function" == typeof api.downloadVideo) {
await api.downloadVideo({
format: "video",
quality: "1080"
});
return;
}
} catch (err) {
window.console.error("[YouTube+] Direct download invocation failed:", err);
}
ytUtils.NotificationManager.show(tFn("directDownloadModuleNotAvailable"), {
duration: 3e3,
type: "error"
});
} else {
window.console.error("[YouTube+] Direct download module not loaded");
ytUtils.NotificationManager.show(tFn("directDownloadModuleNotAvailable"), {
duration: 3e3,
type: "error"
});
}
};
const handleYTDLDownload = url => {
const videoId = new URLSearchParams(location.search).get("v");
const videoUrl = videoId ? `https://www.youtube.com/watch?v=${videoId}` : location.href;
navigator.clipboard.writeText(videoUrl).then(() => {
ytUtils.NotificationManager.show(tFn("copiedToClipboard"), {
duration: 2e3,
type: "success"
});
}).catch(() => {
(async (text, tFn, notificationMgr) => {
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
notificationMgr.show(tFn("copiedToClipboard"), {
duration: 2e3,
type: "success"
});
return;
}
const ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
Object.assign(ta.style, {
position: "fixed",
left: "-9999px",
top: "-9999px",
opacity: "0"
});
document.body.appendChild(ta);
ta.select();
ta.setSelectionRange(0, text.length);
let copied = !1;
try {
copied = document.execCommand("copy");
} catch (e) {
logger.warn("[Download] execCommand copy not supported");
}
document.body.removeChild(ta);
copied ? notificationMgr.show(tFn("copiedToClipboard"), {
duration: 2e3,
type: "success"
}) : notificationMgr.show(tFn("copyFailed") || "Copy failed", {
duration: 2e3,
type: "error"
});
} catch (e) {
logger.warn("[Download] Clipboard copy failed:", e);
notificationMgr.show(tFn("copyFailed") || "Copy failed", {
duration: 2e3,
type: "error"
});
}
})(videoUrl, tFn, ytUtils.NotificationManager);
});
window.open(url, "_blank");
};
return {
handleDirectDownload,
handleYTDLDownload,
openDownloadSite: (url, isYTDL, isDirect, dropdown, button) => {
dropdown.classList.remove("visible");
button.setAttribute("aria-expanded", "false");
isDirect ? handleDirectDownload() : isYTDL ? handleYTDLDownload(url) : window.open(url, "_blank");
}
};
};
const setupDropdownHoverBehavior = (() => {
let initialized = !1;
const dropdownTimers = new WeakMap;
const setTimer = (element, timerId) => dropdownTimers.set(element, timerId);
const clearTimer = element => {
const timerId = (element => dropdownTimers.get(element))(element);
if (void 0 !== timerId) {
clearTimeout(timerId);
dropdownTimers.delete(element);
}
};
const showDropdown = (button, dropdown) => {
clearTimer(button);
clearTimer(dropdown);
positionDropdown(button, dropdown);
dropdown.classList.add("visible");
button.setAttribute("aria-expanded", "true");
};
const hideDropdown = (button, dropdown) => {
clearTimer(button);
clearTimer(dropdown);
const timerId = setTimeout(() => {
dropdown.classList.remove("visible");
button.setAttribute("aria-expanded", "false");
}, 180);
setTimer(button, timerId);
};
return () => {
(() => {
if (!initialized) {
initialized = !0;
document.addEventListener("mouseenter", e => {
const button = e.target?.closest?.(".ytp-download-button");
if (button) {
const dropdown = $(".download-options");
if (dropdown) {
clearTimer(button);
clearTimer(dropdown);
showDropdown(button, dropdown);
}
return;
}
const dropdown = e.target?.closest?.(".download-options");
if (dropdown) {
const button = $(".ytp-download-button");
if (button) {
clearTimer(button);
clearTimer(dropdown);
showDropdown(button, dropdown);
}
}
}, !0);
document.addEventListener("mouseleave", e => {
const button = e.target?.closest?.(".ytp-download-button");
if (button) {
const dropdown = $(".download-options");
if (dropdown) {
clearTimer(button);
clearTimer(dropdown);
const timerId = setTimeout(() => hideDropdown(button, dropdown), 180);
setTimer(button, timerId);
}
return;
}
const dropdown = e.target?.closest?.(".download-options");
if (dropdown) {
const button = $(".ytp-download-button");
if (button) {
clearTimer(button);
clearTimer(dropdown);
const timerId = setTimeout(() => hideDropdown(button, dropdown), 180);
setTimer(dropdown, timerId);
}
}
}, !0);
document.addEventListener("keydown", e => {
const button = e.target?.closest?.(".ytp-download-button");
if (button && ("Enter" === e.key || " " === e.key)) {
const dropdown = $(".download-options");
if (!dropdown) {
return;
}
dropdown.classList.contains("visible") ? hideDropdown(button, dropdown) : showDropdown(button, dropdown);
}
});
}
})();
};
})();
const createDownloadButtonManager = config => {
const {settings, t: tFn, getElement, YouTubeUtils: ytUtils} = config;
const actions = createDownloadActions(tFn, ytUtils);
const buildDownloadSites = (tFn => (customization, enabledSites, videoId, videoUrl) => {
const baseSites = [ {
key: "externalDownloader",
name: customization?.externalDownloader?.name || "SSYouTube",
url: buildUrl(customization?.externalDownloader?.url || "https://ssyoutube.com/watch?v={videoId}", videoId, videoUrl),
isYTDL: !1,
isDirect: !1
}, {
key: "ytdl",
name: "by YTDL",
url: "http://localhost:5005",
isYTDL: !0,
isDirect: !1
}, {
key: "direct",
name: tFn("directDownload"),
url: "#",
isYTDL: !1,
isDirect: !0
} ];
const downloadSites = baseSites.filter(s => !1 !== enabledSites[s.key]);
return {
baseSites,
downloadSites
};
})(tFn);
const addDownloadButton = controls => {
if (!settings.enableDownload) {
return;
}
try {
const existingBtn = controls.querySelector(".ytp-download-button");
existingBtn && existingBtn.remove();
} catch (e) {}
const videoId = new URLSearchParams(location.search).get("v");
const videoUrl = videoId ? `https://www.youtube.com/watch?v=${videoId}` : location.href;
const customization = settings.downloadSiteCustomization || {
externalDownloader: {
name: "SSYouTube",
url: "https://ssyoutube.com/watch?v={videoId}"
}
};
const enabledSites = settings.downloadSites || {
externalDownloader: !0,
ytdl: !0,
direct: !0
};
const {downloadSites} = buildDownloadSites(customization, enabledSites, videoId, videoUrl);
const button = (tFn => {
const button = document.createElement("div");
button.className = "ytp-button ytp-download-button";
button.setAttribute("title", tFn("downloadOptions"));
button.setAttribute("tabindex", "0");
button.setAttribute("role", "button");
button.setAttribute("aria-haspopup", "true");
button.setAttribute("aria-expanded", "false");
_setSafeHTML(button, '\n      <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity="0.5" d="M3 15C3 17.8284 3 19.2426 3.87868 20.1213C4.75736 21 6.17157 21 9 21H15C17.8284 21 19.2426 21 20.1213 20.1213C21 19.2426 21 17.8284 21 15" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: var(--darkreader-text-ffffff, #cad3f5);" data-darkreader-inline-stroke=""></path> <path d="M12 3V16M12 16L16 11.625M12 16L8 11.625" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: var(--darkreader-text-ffffff, #cad3f5);" data-darkreader-inline-stroke=""></path></svg>\n    ');
return button;
})(tFn);
if (1 === downloadSites.length) {
const singleSite = downloadSites[0];
button.style.cursor = "pointer";
const tempDropdown = document.createElement("div");
button.addEventListener("click", () => actions.openDownloadSite(singleSite.url, singleSite.isYTDL, singleSite.isDirect, tempDropdown, button));
controls.insertBefore(button, controls.firstChild);
return;
}
const dropdown = ((downloadSites, button, openDownloadSiteFn) => {
const options = document.createElement("div");
options.className = "download-options";
options.setAttribute("role", "menu");
const list = document.createElement("div");
list.className = "download-options-list";
downloadSites.forEach(site => {
const opt = document.createElement("div");
opt.className = "download-option-item";
opt.textContent = site.name;
opt.setAttribute("role", "menuitem");
opt.setAttribute("tabindex", "0");
opt.dataset.url = site.url;
opt.dataset.isYtdl = site.isYTDL ? "true" : "false";
opt.dataset.isDirect = site.isDirect ? "true" : "false";
list.appendChild(opt);
});
const handleOptionActivate = item => {
item && openDownloadSiteFn(item.dataset.url, "true" === item.dataset.isYtdl, "true" === item.dataset.isDirect, options, button);
};
list.addEventListener("click", e => {
const item = e.target?.closest?.(".download-option-item");
item && list.contains(item) && handleOptionActivate(item);
});
list.addEventListener("keydown", e => {
const item = e.target?.closest?.(".download-option-item");
item && list.contains(item) && ("Enter" !== e.key && " " !== e.key || handleOptionActivate(item));
});
options.appendChild(list);
return options;
})(downloadSites, button, actions.openDownloadSite);
const existingDownload = $(".download-options");
existingDownload && existingDownload.remove();
try {
document.body.appendChild(dropdown);
} catch (e) {
button.appendChild(dropdown);
}
setupDropdownHoverBehavior(button, dropdown);
try {
if ("undefined" != typeof window) {
window.youtubePlus = window.youtubePlus || {};
window.youtubePlus.downloadButtonManager = window.youtubePlus.downloadButtonManager || {};
window.youtubePlus.downloadButtonManager.addDownloadButton = controlsArg => addDownloadButton(controlsArg);
window.youtubePlus.downloadButtonManager.refreshDownloadButton = () => {
try {
const btn = $(".ytp-download-button");
const dd = $(".download-options");
if (settings.enableDownload && (!btn || !dd)) {
try {
const controlsEl = $(".ytp-right-controls");
controlsEl && addDownloadButton(controlsEl);
} catch (e) {}
}
if (settings.enableDownload) {
btn && btn.style && (btn.style.display = "");
dd && dd.style && (dd.style.display = "");
} else {
btn && btn.style && (btn.style.display = "none");
dd && dd.style && (dd.style.display = "none");
}
} catch (e) {}
};
window.youtubePlus.rebuildDownloadDropdown = () => {
try {
const controlsEl = $(".ytp-right-controls");
if (!controlsEl) {
return;
}
window.youtubePlus.downloadButtonManager.addDownloadButton(controlsEl);
window.youtubePlus.settings = window.youtubePlus.settings || settings;
} catch (e) {
window.console.warn("[YouTube+] rebuildDownloadDropdown failed:", e);
}
};
}
} catch (e) {
window.console.warn("[YouTube+] expose rebuildDownloadDropdown failed:", e);
}
controls.insertBefore(button, controls.firstChild);
};
return {
addDownloadButton,
refreshDownloadButton: () => {
const button = getElement(".ytp-download-button");
let dropdown = $(".download-options");
if (settings.enableDownload && (!button || !dropdown)) {
try {
const controlsEl = $(".ytp-right-controls");
if (controlsEl) {
addDownloadButton(controlsEl);
dropdown = $(".download-options");
}
} catch (e) {
logger && logger.warn && logger.warn("[YouTube+] recreate download button failed:", e);
}
}
if (settings.enableDownload) {
button && button.style && (button.style.display = "");
dropdown && dropdown.style && (dropdown.style.display = "");
} else {
button && button.style && (button.style.display = "none");
dropdown && dropdown.style && (dropdown.style.display = "none");
}
}
};
};
let initialized = !1;
function init() {
if (!initialized) {
initialized = !0;
try {
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug("[YouTube+ Download] Unified module loaded");
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug("[YouTube+ Download] Use window.YouTubePlusDownload.downloadVideo() to download");
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug("[YouTube+ Download] Button manager available");
} catch (e) {}
}
}
if ("undefined" != typeof window) {
window.YouTubePlusDownload = {
downloadVideo,
getSubtitles,
downloadSubtitle,
getVideoId,
getVideoUrl,
getVideoTitle,
sanitizeFilename,
formatBytes,
DownloadConfig,
openModal,
init
};
window.YouTubePlusDownloadButton = {
createDownloadButtonManager
};
}
"undefined" != typeof window && (window.YouTubeDownload = {
init,
openModal,
getVideoId,
getVideoTitle,
version: "3.0"
});
const ensureInit = () => {
isRelevantRoute() && ("function" == typeof requestIdleCallback ? requestIdleCallback(init, {
timeout: 1500
}) : setTimeout(init, 0));
};
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("download", ensureInit, {
priority: 2,
shouldLoad: isRelevantRoute
}) : (cb => {
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", cb, {
once: !0
}) : cb();
})(ensureInit);
"function" == typeof window.YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", ensureInit, {
passive: !0
}) : document.addEventListener("yt-navigate-finish", ensureInit, {
passive: !0
});
})();

const enhancedSetTimeout_ = setTimeout;

const {$, $$, byId} = window.YouTubeUtils || {};

const onDomReady = window.YouTubeUtils?.onDomReady || (cb => {
"loading" !== document.readyState ? cb() : document.addEventListener("DOMContentLoaded", cb, {
once: !0
});
});

!(function() {
"use strict";
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const _getLanguage = window.YouTubeUtils.getLanguage;
const t = window.YouTubeUtils.t;
const config = {
enabled: window.YouTubeUtils?.loadFeatureEnabled?.("enableScrollToTopButton") ?? !0,
storageKey: "youtube_top_button_settings"
};
const _debounce = window.YouTubeUtils.debounce;
let universalScrollHandler = null;
let universalScrollContainer = null;
const universalExtraScrollTargets = new Set;
let universalAttachTimeoutIds = [];
let _musicContainersCache = null;
let _musicContainersCacheTime = 0;
const resolveMusicContainers = () => {
const now = Date.now();
if (_musicContainersCache && now - _musicContainersCacheTime < 5e3) {
return _musicContainersCache;
}
_musicContainersCache = [ $("ytmusic-app-layout #layout"), $("ytmusic-app-layout"), $("ytmusic-browse-response #contents"), $("ytmusic-section-list-renderer") ].filter(Boolean);
_musicContainersCacheTime = now;
return _musicContainersCache;
};
const getUniversalScrollContainer = () => {
try {
const host = window.location.hostname;
const candidates = [];
if ("music.youtube.com" === host) {
const musicContainers = resolveMusicContainers();
candidates.push(...musicContainers);
candidates.push($("ytmusic-tabbed-page #content"), $("ytmusic-app-layout #content"), $("#content"), $("ytmusic-app"));
} else {
"studio.youtube.com" === host && candidates.push($("ytcp-entity-page #scrollable-content"), $("ytcp-app #content"), $("#main-content"), $("#content"), $("#main"), $("ytcp-app"));
}
candidates.push(document.scrollingElement, document.documentElement, document.body);
for (const el of candidates) {
if (el && el.scrollHeight > el.clientHeight + 50) {
return el;
}
}
if ("music.youtube.com" === host || "studio.youtube.com" === host) {
return document.scrollingElement || document.documentElement;
}
} catch (e) {
window.console.warn("[YouTube+] Error detecting scroll container:", e);
}
return document.scrollingElement || document.documentElement;
};
let universalWindowScrollHandler = null;
let musicSideScrollHandler = null;
let musicSideScrollContainer = null;
const getMusicSidePanelContainer = () => {
if ("music.youtube.com" !== window.location.hostname) {
return null;
}
const directSelectors = [ "ytmusic-player-queue #contents", "ytmusic-player-queue", "#side-panel #contents", "#side-panel", 'ytmusic-tab-renderer[page-type="MUSIC_PAGE_TYPE_QUEUE"] #contents', "ytmusic-queue #automix-contents", "ytmusic-queue #contents" ];
for (const sel of directSelectors) {
try {
const el = $(sel);
if (el && el.scrollHeight > el.clientHeight + 30) {
return el;
}
} catch (e) {}
}
const roots = [ $("ytmusic-player-page"), $("ytmusic-app-layout"), $("ytmusic-app") ];
const selectors = [ "#side-panel", "#right-content", "ytmusic-player-queue", "ytmusic-queue", "ytmusic-tab-renderer[selected] #contents", ".side-panel" ];
for (const root of roots) {
if (root) {
for (const sel of selectors) {
try {
const el = root.querySelector(sel);
if (el && el.scrollHeight > el.clientHeight + 30) {
return el;
}
} catch (e) {}
}
}
}
return null;
};
const cleanupTopButtons = () => {
try {
const rightButton = byId("right-tabs-top-button");
rightButton && rightButton.remove();
} catch (e) {}
try {
const playlistButton = byId("playlist-panel-top-button");
playlistButton && playlistButton.remove();
} catch (e) {}
(() => {
try {
const btn = byId("music-side-top-button");
btn && btn.remove();
} catch (e) {}
try {
musicSideScrollHandler && musicSideScrollContainer && musicSideScrollContainer.removeEventListener("scroll", musicSideScrollHandler);
} catch (e) {}
musicSideScrollHandler = null;
musicSideScrollContainer = null;
})();
(() => {
try {
const btn = byId("universal-top-button");
btn && btn.remove();
} catch (e) {}
try {
universalScrollHandler && universalScrollContainer && universalScrollContainer.removeEventListener("scroll", universalScrollHandler);
} catch (e) {}
try {
universalWindowScrollHandler && window.removeEventListener("scroll", universalWindowScrollHandler);
} catch (e) {}
try {
if (universalWindowScrollHandler && universalExtraScrollTargets.size) {
for (const target of universalExtraScrollTargets) {
try {
target.removeEventListener("scroll", universalWindowScrollHandler);
target._ytpScrollAttached && (target._ytpScrollAttached = !1);
} catch (e) {}
}
}
} catch (e) {}
try {
universalAttachTimeoutIds.length && universalAttachTimeoutIds.forEach(id => clearTimeout(id));
} catch (e) {}
universalScrollHandler = null;
universalScrollContainer = null;
universalWindowScrollHandler = null;
universalExtraScrollTargets.clear();
universalAttachTimeoutIds = [];
})();
try {
$$("#right-tabs .tab-content-cld").forEach(tab => {
if (tab && tab._topButtonScrollHandler) {
tab.removeEventListener("scroll", tab._topButtonScrollHandler);
tab._topButtonScrollHandler = null;
}
});
} catch (e) {
window.console.warn("[YouTube+] Error cleaning up tab scroll handlers:", e);
}
try {
const rightTabsEl = byId("right-tabs");
if (rightTabsEl) {
if (rightTabsEl._topButtonScrollHandler) {
rightTabsEl.removeEventListener("scroll", rightTabsEl._topButtonScrollHandler);
rightTabsEl._topButtonScrollHandler = null;
}
if (rightTabsEl._scrollCleanup) {
rightTabsEl._scrollCleanup();
rightTabsEl._scrollCleanup = null;
}
}
} catch (e) {}
try {
const playlistScroll = $("ytd-playlist-panel-renderer #items");
if (playlistScroll && playlistScroll._topButtonScrollHandler) {
playlistScroll.removeEventListener("scroll", playlistScroll._topButtonScrollHandler);
playlistScroll._topButtonScrollHandler = null;
}
} catch (e) {}
};
let tabChangesObserver = null;
let watchInitToken = 0;
let isTabClickListenerAttached = !1;
let tabDelegationHandler = null;
let tabDelegationRegistered = !1;
let tabCheckTimeoutId = null;
let playlistPanelCheckTimeoutId = null;
let musicSidePanelSubId = null;
const isTopButton = el => el && ("right-tabs-top-button" === el.id || "universal-top-button" === el.id || "playlist-panel-top-button" === el.id || "music-side-top-button" === el.id);
const handleTopButtonActivate = button => {
try {
if (!button) {
return;
}
if ("right-tabs-top-button" === button.id) {
const activeTab = $("#right-tabs .tab-content-cld:not(.tab-content-hidden)");
const rightTabsEl = byId("right-tabs");
const scrollTarget = rightTabsEl && rightTabsEl.scrollTop > 0 ? rightTabsEl : activeTab && activeTab.scrollTop > 0 ? activeTab : activeTab || rightTabsEl;
if (scrollTarget) {
"scrollBehavior" in (document.documentElement.style || {}) ? scrollTarget.scrollTo({
top: 0,
behavior: "smooth"
}) : scrollTarget.scrollTop = 0;
button.setAttribute("aria-label", "Scrolled to top");
enhancedSetTimeout_(() => {
button.setAttribute("aria-label", t("scrollToTop"));
}, 1e3);
}
return;
}
if ("universal-top-button" === button.id) {
const host = window.location.hostname;
const isMusic = "music.youtube.com" === host;
const isStudio = "studio.youtube.com" === host;
const target = isMusic || isStudio ? getUniversalScrollContainer() : universalScrollContainer || getUniversalScrollContainer();
const scrollToTop = el => {
"scrollBehavior" in (document.documentElement.style || {}) ? el.scrollTo({
top: 0,
behavior: "smooth"
}) : el.scrollTop = 0;
};
target === window || target === document || target === document.body || target === document.documentElement ? window.scrollTo({
top: 0,
behavior: "smooth"
}) : target && "function" == typeof target.scrollTo && scrollToTop(target);
if (isMusic) {
window.scrollTo({
top: 0,
behavior: "smooth"
});
for (const c of resolveMusicContainers()) {
c && c.scrollTop > 0 && scrollToTop(c);
}
}
return;
}
if ("playlist-panel-top-button" === button.id) {
const playlistPanel = $("ytd-playlist-panel-renderer");
const scrollContainer = playlistPanel ? $("#items", playlistPanel) : null;
scrollContainer && ("scrollBehavior" in (document.documentElement.style || {}) ? scrollContainer.scrollTo({
top: 0,
behavior: "smooth"
}) : scrollContainer.scrollTop = 0);
return;
}
if ("music-side-top-button" === button.id) {
const target = getMusicSidePanelContainer() || musicSideScrollContainer;
target && ("scrollBehavior" in (document.documentElement.style || {}) ? target.scrollTo({
top: 0,
behavior: "smooth"
}) : target.scrollTop = 0);
}
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error scrolling to top:", error);
}
};
const setupTopButtonDelegation = (() => {
let attached = !1;
return () => {
if (attached) {
return;
}
attached = !0;
const delegator = window.YouTubePlusEventDelegation;
if (delegator?.on) {
delegator.on(document, "click", ".top-button", (_ev, target) => {
isTopButton(target) && handleTopButtonActivate(target);
});
delegator.on(document, "keydown", ".top-button", (ev, target) => {
if (isTopButton(target) && ("Enter" === ev.key || " " === ev.key)) {
ev.preventDefault();
handleTopButtonActivate(target);
}
});
} else {
const _cm = window.YouTubeUtils?.cleanupManager;
const _clickHandler = ev => {
const target = ev.target?.closest?.(".top-button");
isTopButton(target) && handleTopButtonActivate(target);
};
const _keyHandler = ev => {
const target = ev.target?.closest?.(".top-button");
if (isTopButton(target) && ("Enter" === ev.key || " " === ev.key)) {
ev.preventDefault();
handleTopButtonActivate(target);
}
};
if (_cm?.registerListener) {
_cm.registerListener(document, "click", _clickHandler, !0);
_cm.registerListener(document, "keydown", _keyHandler, !0);
} else {
document.addEventListener("click", _clickHandler, !0);
document.addEventListener("keydown", _keyHandler, !0);
}
}
};
})();
const clearTimeoutSafe = id => {
id && clearTimeout(id);
return null;
};
const addStyles = () => {
if (byId("custom-styles")) {
return;
}
const style = document.createElement("style");
style.id = "custom-styles";
style.textContent = '\n      :root{--yt-scrollbar-width:8px;--yt-scrollbar-track:transparent;--yt-scrollbar-thumb:rgba(144,144,144,.5);--yt-scrollbar-thumb-hover:rgba(170,170,170,.7);--yt-scrollbar-thumb-active:rgba(190,190,190,.9);}\n      ::-webkit-scrollbar{width:var(--yt-scrollbar-width)!important;height:var(--yt-scrollbar-width)!important;}\n      ::-webkit-scrollbar-track{background:var(--yt-scrollbar-track)!important;border-radius:4px!important;}\n      ::-webkit-scrollbar-thumb{background:var(--yt-scrollbar-thumb)!important;border-radius:4px!important;transition:background .2s!important;}\n      ::-webkit-scrollbar-thumb:hover{background:var(--yt-scrollbar-thumb-hover)!important;}\n      ::-webkit-scrollbar-thumb:active{background:var(--yt-scrollbar-thumb-active)!important;}\n      ::-webkit-scrollbar-corner{background:transparent!important;}\n      html,body,#content,#guide-content,#secondary,#comments,#chat,ytd-comments,ytd-watch-flexy,ytd-browse,ytd-search,ytd-playlist-panel-renderer,#right-tabs,.tab-content-cld,ytmusic-app-layout{scrollbar-width:thin;scrollbar-color:var(--yt-scrollbar-thumb) var(--yt-scrollbar-track);}\n      html[dark]{--yt-scrollbar-thumb:rgba(144,144,144,.4);--yt-scrollbar-thumb-hover:rgba(170,170,170,.6);--yt-scrollbar-thumb-active:rgba(190,190,190,.8);}\n      .top-button{position:fixed;bottom:16px;right:16px;width:40px;height:40px;background:var(--yt-button-bg);color:var(--yt-text-primary);border:1px solid var(--yt-glass-border);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:2100;opacity:0;visibility:hidden;transition:all .3s cubic-bezier(0.4, 0, 0.2, 1);backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);box-shadow:var(--yt-shadow);}\n      .top-button:hover{background:var(--yt-hover-bg);transform:translateY(-2px) scale(1.07);box-shadow:var(--yt-shadow);}\n      .top-button:active{transform:translateY(-1px) scale(1.03);}\n      .top-button:focus{outline:2px solid var(--yt-accent);outline-offset:2px;}\n      .top-button.visible{opacity:1;visibility:visible;}\n      .top-button svg{transition:transform .2s ease;}\n      .top-button:hover svg{transform:translateY(-1px) scale(1.1);}\n      html[dark]{--yt-top-btn-bg:var(--yt-button-bg);--yt-top-btn-color:var(--yt-text-primary);--yt-top-btn-border:var(--yt-glass-border);--yt-top-btn-hover:var(--yt-hover-bg);}\n      html:not([dark]){--yt-top-btn-bg:var(--yt-button-bg);--yt-top-btn-color:var(--yt-text-primary);--yt-top-btn-border:var(--yt-glass-border);--yt-top-btn-hover:var(--yt-hover-bg);}\n      #right-tabs .top-button{position:absolute;z-index:1000;}\n      ytd-watch-flexy:not([tyt-tab^="#"]) #right-tabs .top-button{display:none;}\n      ytd-playlist-panel-renderer .top-button{position:absolute;z-index:1000;}\n      ytd-watch-flexy[flexy] #movie_player, ytd-watch-flexy[flexy] #movie_player .html5-video-container, ytd-watch-flexy[flexy] .html5-main-video{width:100%!important; max-width:100%!important;}\n      ytd-watch-flexy[flexy] .html5-main-video{height:auto!important; max-height:100%!important; object-fit:contain!important; transform:none!important;}\n      ytd-watch-flexy[flexy] #player-container-outer, ytd-watch-flexy[flexy] #movie_player{display:flex!important; align-items:center!important; justify-content:center!important;}\n      /* Return YouTube Dislike button styling */\n      dislike-button-view-model button{min-width:fit-content!important;width:auto!important;}\n      dislike-button-view-model .yt-spec-button-shape-next__button-text-content{display:inline-flex!important;align-items:center!important;justify-content:center!important;}\n      #ytp-plus-dislike-text{display:inline-block!important;visibility:visible!important;opacity:1!important;margin-left:6px!important;font-size:1.4rem!important;line-height:2rem!important;font-weight:500!important;}\n      ytd-segmented-like-dislike-button-renderer dislike-button-view-model button{min-width:fit-content!important;}\n      ytd-segmented-like-dislike-button-renderer .yt-spec-button-shape-next__button-text-content{min-width:2.4rem!important;}\n      /* Shorts-specific dislike button styling */\n      ytd-reel-video-renderer dislike-button-view-model #ytp-plus-dislike-text{font-size:1.2rem!important;line-height:1.8rem!important;margin-left:4px!important;}\n      ytd-reel-video-renderer dislike-button-view-model button{padding:8px 12px!important;min-width:auto!important;}\n      ytd-shorts dislike-button-view-model .yt-spec-button-shape-next__button-text-content{display:inline-flex!important;min-width:auto!important;}\n        ';
(document.head || document.documentElement).appendChild(style);
};
const handleScroll = (scrollContainer, button) => {
try {
if (!button || !scrollContainer) {
return;
}
button.classList.toggle("visible", scrollContainer.scrollTop > 100);
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in handleScroll:", error);
}
};
const setupScrollListener = (() => {
let timeout = null;
return () => {
timeout && clearTimeout(timeout);
timeout = enhancedSetTimeout_(() => {
try {
$$(".tab-content-cld").forEach(tab => {
if (tab._topButtonScrollHandler) {
tab.removeEventListener("scroll", tab._topButtonScrollHandler);
delete tab._topButtonScrollHandler;
}
if (tab._scrollObserver) {
tab._scrollObserver.disconnect();
delete tab._scrollObserver;
}
window.YouTubePlusScrollManager?.removeAllListeners?.(tab);
});
try {
const prevRtEl = byId("right-tabs");
if (prevRtEl) {
if (prevRtEl._topButtonScrollHandler) {
prevRtEl.removeEventListener("scroll", prevRtEl._topButtonScrollHandler);
delete prevRtEl._topButtonScrollHandler;
}
if (prevRtEl._scrollCleanup) {
prevRtEl._scrollCleanup();
delete prevRtEl._scrollCleanup;
}
}
} catch (e) {
window.console.warn("[YouTube+] Error cleaning up right-tabs scroll handler:", e);
}
const activeTab = $("#right-tabs .tab-content-cld:not(.tab-content-hidden)");
const button = byId("right-tabs-top-button");
if (activeTab && button) {
const rightTabsEl = byId("right-tabs");
const rtIsScrollHost = rightTabsEl && rightTabsEl !== activeTab && rightTabsEl.scrollHeight > rightTabsEl.clientHeight + 10;
const scrollTarget = rtIsScrollHost ? rightTabsEl : activeTab;
if (window.YouTubePlusScrollManager) {
const cleanup = window.YouTubePlusScrollManager.addScrollListener(scrollTarget, () => handleScroll(scrollTarget, button), {
debounce: 100,
runInitial: !0
});
scrollTarget._scrollCleanup = cleanup;
} else {
const scrollHandler = _debounce(() => handleScroll(scrollTarget, button), 100);
scrollTarget._topButtonScrollHandler = scrollHandler;
scrollTarget.addEventListener("scroll", scrollHandler, {
passive: !0,
capture: !1
});
handleScroll(scrollTarget, button);
}
}
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in setupScrollListener:", error);
}
}, 100);
};
})();
const createUniversalButton = () => {
try {
setupTopButtonDelegation();
if (byId("universal-top-button")) {
return;
}
if (!config.enabled) {
return;
}
const rawContainer = getUniversalScrollContainer();
const scrollContainer = rawContainer === document.scrollingElement || rawContainer === document.documentElement || rawContainer === document.body ? window : rawContainer;
universalScrollContainer = scrollContainer;
const button = document.createElement("button");
button.id = "universal-top-button";
button.className = "top-button";
button.title = t("scrollToTop");
button.setAttribute("aria-label", t("scrollToTop"));
_setSafeHTML(button, '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>');
const host = window.location.hostname;
"music.youtube.com" !== host && "studio.youtube.com" !== host || (button.style.zIndex = "10000");
document.body.appendChild(button);
const scrollHandler = _debounce(() => {
const offset = scrollContainer === window ? window.scrollY : scrollContainer.scrollTop;
button.classList.toggle("visible", offset > 100);
}, 100);
universalScrollHandler = scrollHandler;
scrollContainer.addEventListener("scroll", scrollHandler, {
passive: !0
});
const initialOffset = scrollContainer === window ? window.scrollY : scrollContainer.scrollTop;
button.classList.toggle("visible", initialOffset > 100);
if ("music.youtube.com" === host || "studio.youtube.com" === host) {
const getMusicContainers = () => {
const base = resolveMusicContainers();
return scrollContainer !== window && scrollContainer instanceof Element && !base.includes(scrollContainer) ? [ ...base, scrollContainer ] : base;
};
const musicScrollCheck = _debounce(() => {
let anyScrolled = window.scrollY > 100;
if (!anyScrolled) {
for (const c of getMusicContainers()) {
if (c.scrollTop > 100) {
anyScrolled = !0;
break;
}
}
}
button.classList.toggle("visible", anyScrolled);
}, 100);
window.addEventListener("scroll", musicScrollCheck, {
passive: !0
});
universalWindowScrollHandler = musicScrollCheck;
const attachMusicScrollListeners = () => {
const targets = [ $("ytmusic-app-layout #layout"), $("ytmusic-app-layout") ];
for (const target of targets) {
if (target && !target._ytpScrollAttached) {
target._ytpScrollAttached = !0;
target.addEventListener("scroll", musicScrollCheck, {
passive: !0
});
universalExtraScrollTargets.add(target);
}
}
};
attachMusicScrollListeners();
universalAttachTimeoutIds.push(enhancedSetTimeout_(attachMusicScrollListeners, 1e3));
universalAttachTimeoutIds.push(enhancedSetTimeout_(attachMusicScrollListeners, 3e3));
}
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error creating universal button:", error);
}
};
const createMusicSidePanelButton = () => {
try {
if ("music.youtube.com" !== window.location.hostname) {
return;
}
setupTopButtonDelegation();
if (byId("music-side-top-button")) {
return;
}
if (!config.enabled) {
return;
}
const panel = getMusicSidePanelContainer();
if (!panel) {
window.YouTubeUtils?.createRetryScheduler?.({
check: () => !(!byId("music-side-top-button") && config.enabled) || !!getMusicSidePanelContainer() && (createMusicSidePanelButton(), 
!0),
maxAttempts: 8,
interval: 500
});
return;
}
const button = document.createElement("button");
button.id = "music-side-top-button";
button.className = "top-button";
button.title = t("scrollToTop");
button.setAttribute("aria-label", t("scrollToTop"));
_setSafeHTML(button, '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>');
panel.style.position = panel.style.position || "relative";
button.style.position = "absolute";
button.style.bottom = "16px";
button.style.right = "16px";
button.style.zIndex = "1000";
panel.appendChild(button);
const scrollHandler = _debounce(() => {
button.classList.toggle("visible", panel.scrollTop > 100);
}, 100);
musicSideScrollContainer = panel;
musicSideScrollHandler = scrollHandler;
panel.addEventListener("scroll", scrollHandler, {
passive: !0
});
button.classList.toggle("visible", panel.scrollTop > 100);
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error creating music side button:", error);
}
};
const dislikeCache = new Map;
let dislikeObserver = null;
let dislikePollTimer = null;
const getVideoIdForDislike = () => {
try {
const urlObj = new URL(window.location.href);
const pathname = urlObj.pathname || "";
if (pathname.startsWith("/shorts/")) {
return pathname.slice(8);
}
if (pathname.startsWith("/clip/")) {
const meta = $("meta[itemprop='videoId'], meta[itemprop='identifier']");
return meta?.getAttribute("content") || null;
}
return urlObj.searchParams.get("v");
} catch (e) {
return null;
}
};
const getDislikeButton = () => {
const isShorts = window.location.pathname.startsWith("/shorts");
if (isShorts) {
return (() => {
const activeReel = $("ytd-reel-video-renderer[is-active]");
if (activeReel) {
const btn = $("dislike-button-view-model", activeReel) || $("like-button-view-model", activeReel)?.parentElement?.querySelector('[aria-label*="islike"]')?.closest("button")?.parentElement || $("#dislike-button", activeReel);
if (btn) {
return btn;
}
}
const shortsContainer = $("ytd-shorts");
if (shortsContainer) {
const btn = $("dislike-button-view-model", shortsContainer) || $("#dislike-button", shortsContainer);
if (btn) {
return btn;
}
}
return $("dislike-button-view-model") || $("#dislike-button") || null;
})();
}
const buttons = $("ytd-menu-renderer.ytd-watch-metadata > div#top-level-buttons-computed") || $("ytd-menu-renderer.ytd-video-primary-info-renderer > div") || $("#menu-container #top-level-buttons-computed") || null;
return (buttons => {
if (!buttons) {
return null;
}
const segmented = buttons.querySelector("ytd-segmented-like-dislike-button-renderer");
if (segmented) {
const dislikeViewModel = segmented.querySelector("dislike-button-view-model") || segmented.querySelector("#segmented-dislike-button") || segmented.children[1];
if (dislikeViewModel) {
return dislikeViewModel;
}
}
const viewModel = buttons.querySelector("dislike-button-view-model");
if (viewModel) {
return viewModel;
}
const dislikeBtn = buttons.querySelector('button[aria-label*="islike"]') || buttons.querySelector('button[aria-label*="Не нравится"]');
return dislikeBtn ? dislikeBtn.closest("dislike-button-view-model") || dislikeBtn.parentElement : buttons.children && buttons.children[1] ? buttons.children[1] : null;
})(buttons);
};
const setDislikeDisplay = (dislikeButton, count) => {
try {
const container = (dislikeButton => {
if (!dislikeButton) {
return null;
}
const existingCustom = dislikeButton.querySelector("#ytp-plus-dislike-text");
if (existingCustom) {
return existingCustom;
}
const textSpan = dislikeButton.querySelector("span.yt-core-attributed-string:not(#ytp-plus-dislike-text)") || dislikeButton.querySelector("#text") || dislikeButton.querySelector("yt-formatted-string") || dislikeButton.querySelector('span[role="text"]:not(#ytp-plus-dislike-text)') || dislikeButton.querySelector(".yt-spec-button-shape-next__button-text-content");
if (textSpan && "ytp-plus-dislike-text" !== textSpan.id) {
textSpan.id = "ytp-plus-dislike-text";
return textSpan;
}
const viewModelHost = dislikeButton.closest("ytDislikeButtonViewModelHost") || dislikeButton;
const buttonShape = viewModelHost.querySelector("button-view-model button") || viewModelHost.querySelector("button[aria-label]") || dislikeButton.querySelector("button") || dislikeButton;
let textContainer = buttonShape.querySelector(".yt-spec-button-shape-next__button-text-content");
const created = document.createElement("span");
created.id = "ytp-plus-dislike-text";
created.setAttribute("role", "text");
created.className = "yt-core-attributed-string yt-core-attributed-string--white-space-no-wrap";
const isShorts = window.location.pathname.startsWith("/shorts");
created.style.cssText = isShorts ? "margin-left: 4px; font-size: 1.2rem; line-height: 1.8rem; font-weight: 500; min-width: 1.5em; display: inline-block; text-align: center;" : "margin-left: 6px; font-size: 1.4rem; line-height: 2rem; font-weight: 500; min-width: 2em; display: inline-block; text-align: center;";
try {
if (textContainer) {
textContainer.appendChild(created);
} else {
textContainer = document.createElement("div");
textContainer.className = "yt-spec-button-shape-next__button-text-content";
textContainer.appendChild(created);
buttonShape.appendChild(textContainer);
}
buttonShape.style.minWidth = "auto";
buttonShape.style.width = "auto";
viewModelHost !== dislikeButton && (viewModelHost.style.minWidth = "auto");
} catch (e) {
window.console.warn("YTP: Failed to create dislike text:", e);
}
return created;
})(dislikeButton);
if (!container) {
return;
}
const formatted = (number => {
try {
return new Intl.NumberFormat(_getLanguage() || "en", {
notation: "compact",
compactDisplay: "short"
}).format(Number(number) || 0);
} catch (e) {
return String(number || 0);
}
})(count);
if (container.innerText !== String(formatted)) {
container.innerText = String(formatted);
container.style.display = "inline-block";
container.style.visibility = "visible";
container.style.opacity = "1";
const buttonShape = container.closest("button") || dislikeButton.querySelector("button");
if (buttonShape) {
buttonShape.style.minWidth = "fit-content";
buttonShape.style.width = "auto";
}
}
} catch (e) {
window.console.warn("YTP: Failed to set dislike display:", e);
}
};
const initReturnDislike = async () => {
try {
if (dislikePollTimer) {
return;
}
const checkButton = async () => {
const btn = getDislikeButton();
if (btn) {
if (dislikePollTimer) {
window.YouTubeMutationCoordinator?.unsubscribe?.(dislikePollTimer);
dislikePollTimer = null;
}
const vid = getVideoIdForDislike();
const val = await (async videoId => {
if (!videoId) {
return 0;
}
const cached = dislikeCache.get(videoId);
if (cached && Date.now() < cached.expiresAt) {
return cached.value;
}
if (dislikeCache.size > 50) {
const now = Date.now();
for (const [key, entry] of dislikeCache) {
now >= entry.expiresAt && dislikeCache.delete(key);
}
if (dislikeCache.size > 50) {
const iter = dislikeCache.keys();
for (;dislikeCache.size > 25; ) {
const next = iter.next();
if (next.done) {
break;
}
dislikeCache.delete(next.value);
}
}
}
try {
if ("undefined" != typeof GM_xmlhttpRequest) {
const text = await new Promise((resolve, reject) => {
const timeoutId = enhancedSetTimeout_(() => reject(new Error("timeout")), 8e3);
GM_xmlhttpRequest({
method: "GET",
url: `https://returnyoutubedislikeapi.com/votes?videoId=${encodeURIComponent(videoId)}`,
timeout: 8e3,
headers: {
Accept: "application/json"
},
onload: r => {
clearTimeout(timeoutId);
r.status >= 200 && r.status < 300 ? resolve(r.responseText) : reject(new Error(`HTTP ${r.status}`));
},
onerror: e => {
clearTimeout(timeoutId);
reject(e || new Error("network"));
},
ontimeout: () => {
clearTimeout(timeoutId);
reject(new Error("timeout"));
}
});
});
const parsed = JSON.parse(text || "{}");
const rawDislikes = parsed && "object" == typeof parsed ? parsed.dislikes : void 0;
const val = Number.isFinite(Number(rawDislikes)) ? Math.max(0, Math.floor(Number(rawDislikes))) : 0;
dislikeCache.set(videoId, {
value: val,
expiresAt: Date.now() + 6e5
});
return val;
}
const controller = new AbortController;
const id = enhancedSetTimeout_(() => controller.abort(), 8e3);
try {
const resp = await fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${encodeURIComponent(videoId)}`, {
method: "GET",
cache: "no-cache",
signal: controller.signal,
headers: {
Accept: "application/json"
}
});
clearTimeout(id);
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}`);
}
const json = await resp.json();
const rawDislikes2 = json && "object" == typeof json ? json.dislikes : void 0;
const val = Number.isFinite(Number(rawDislikes2)) ? Math.max(0, Math.floor(Number(rawDislikes2))) : 0;
dislikeCache.set(videoId, {
value: val,
expiresAt: Date.now() + 6e5
});
return val;
} finally {
clearTimeout(id);
}
} catch (e) {
return 0;
}
})(vid);
setDislikeDisplay(btn, val);
(dislikeButton => {
if (!dislikeButton) {
return;
}
if (dislikeObserver) {
window.YouTubeMutationCoordinator?.unwatch?.(dislikeObserver);
dislikeObserver = null;
}
const existingText = dislikeButton.querySelector("#ytp-plus-dislike-text");
if (existingText?.textContent && "0" !== existingText.textContent) {
return;
}
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.watchTarget) {
dislikeObserver = "enhanced::dislikeObserver";
coordinator.watchTarget(dislikeObserver, dislikeButton, () => {
const vid = getVideoIdForDislike();
const cached = dislikeCache.get(vid);
if (cached) {
const btn = getDislikeButton();
btn && setDislikeDisplay(btn, cached.value);
}
}, {
childList: !0,
subtree: !0,
attributes: !0
});
}
})(btn);
return !0;
}
return !1;
};
if (await checkButton()) {
return;
}
const isShorts = window.location.pathname.startsWith("/shorts");
const maxTime = 1e4;
const startTime = Date.now();
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
const pollSelector = isShorts ? "#shorts-container" : "ytd-watch-flexy #below, #page-manager";
dislikePollTimer = "enhanced::dislikePoll";
coordinator.subscribeRoot(dislikePollTimer, async () => {
if (Date.now() - startTime > maxTime) {
dislikePollTimer && coordinator.unsubscribe(dislikePollTimer);
dislikePollTimer = null;
} else {
await checkButton();
}
}, {
selector: pollSelector,
childList: !0,
attributes: !1,
subtree: !0
});
}
} catch (e) {
window.console.warn("[YouTube+] Failed to initialize Return YouTube Dislike:", e);
}
};
const needsUniversalButton = () => {
const host = window.location.hostname;
if ("music.youtube.com" === host || "studio.youtube.com" === host) {
return !0;
}
if (window.YouTubeUtils?.isWatchPage?.() || window.YouTubeUtils?.isShortsPage?.()) {
return !(window.YouTubeUtils?.loadFeatureEnabled?.("enableTabview") ?? 1);
}
const path = window.location.pathname;
const {search} = window.location;
return "/results" === path && search.includes("search_query=") || "/playlist" === path && search.includes("list="), 
!0;
};
const handleTabButtonClick = e => {
try {
const {target} = e;
const tabButton = target?.closest?.(".tab-btn[tyt-tab-content]");
tabButton && enhancedSetTimeout_(setupScrollListener, 100);
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in click handler:", error);
}
};
const stopWatchEnhancements = () => {
watchInitToken++;
try {
tabCheckTimeoutId && "object" == typeof tabCheckTimeoutId && tabCheckTimeoutId.stop ? tabCheckTimeoutId.stop() : tabCheckTimeoutId = clearTimeoutSafe(tabCheckTimeoutId);
} catch (e) {}
tabCheckTimeoutId = null;
try {
playlistPanelCheckTimeoutId && "object" == typeof playlistPanelCheckTimeoutId && playlistPanelCheckTimeoutId.stop ? playlistPanelCheckTimeoutId.stop() : playlistPanelCheckTimeoutId = clearTimeoutSafe(playlistPanelCheckTimeoutId);
} catch (e) {}
playlistPanelCheckTimeoutId = null;
try {
tabChangesObserver && window.YouTubeMutationCoordinator?.unsubscribe?.(tabChangesObserver);
if (tabChangesObserver) {
try {
window.YouTubeUtils?.ObserverRegistry?.untrack?.();
} catch (e) {}
}
} catch (e) {}
tabChangesObserver = null;
(() => {
try {
if (!isTabClickListenerAttached) {
return;
}
const delegator = window.YouTubePlusEventDelegation;
tabDelegationRegistered && delegator?.off && tabDelegationHandler ? delegator.off(document, "click", ".tab-btn[tyt-tab-content]", tabDelegationHandler) : document.removeEventListener("click", handleTabButtonClick, !0);
tabDelegationHandler = null;
tabDelegationRegistered = !1;
isTabClickListenerAttached = !1;
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error cleaning up events:", error);
}
})();
try {
(() => {
try {
if (dislikePollTimer) {
window.YouTubeMutationCoordinator?.unsubscribe?.(dislikePollTimer);
dislikePollTimer = null;
}
if (dislikeObserver) {
window.YouTubeMutationCoordinator?.unwatch?.(dislikeObserver);
dislikeObserver = null;
}
$$("#ytp-plus-dislike-text").forEach(el => {
try {
el.parentNode && el.parentNode.removeChild(el);
} catch (e) {}
});
dislikeCache.clear();
} catch (e) {
window.console.warn("YTP: Dislike cleanup error:", e);
}
})();
} catch (e) {}
};
const startWatchEnhancements = () => {
if (!config.enabled) {
return;
}
if (!window.YouTubeUtils?.isWatchPage?.()) {
return;
}
const token = ++watchInitToken;
(() => {
try {
if (isTabClickListenerAttached) {
return;
}
const delegator = window.YouTubePlusEventDelegation;
if (delegator?.on) {
tabDelegationHandler = (ev, target) => {
target && enhancedSetTimeout_(setupScrollListener, 100);
};
delegator.on(document, "click", ".tab-btn[tyt-tab-content]", tabDelegationHandler, {
capture: !0
});
tabDelegationRegistered = !0;
} else {
document.addEventListener("click", handleTabButtonClick, !0);
}
isTabClickListenerAttached = !0;
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in setupEvents:", error);
}
})();
const tabScheduler = window.YouTubeUtils?.createRetryScheduler?.({
check: () => {
if (token !== watchInitToken || !window.YouTubeUtils?.isWatchPage?.()) {
return !0;
}
if ($("#right-tabs")) {
(() => {
try {
setupTopButtonDelegation();
const rightTabs = $("#right-tabs");
if (!rightTabs || byId("right-tabs-top-button")) {
return;
}
if (!config.enabled) {
return;
}
const button = document.createElement("button");
button.id = "right-tabs-top-button";
button.className = "top-button";
button.title = t("scrollToTop");
button.setAttribute("aria-label", t("scrollToTop"));
_setSafeHTML(button, '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>');
rightTabs.style.position = "relative";
rightTabs.appendChild(button);
setupScrollListener();
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error creating button:", error);
}
})();
try {
tabChangesObserver && window.YouTubeMutationCoordinator?.unsubscribe?.(tabChangesObserver);
} catch (e) {}
tabChangesObserver = (() => {
try {
const coordinator = window.YouTubeMutationCoordinator;
if (!coordinator?.subscribeRoot) {
return null;
}
const observerId = "enhanced::tabChanges";
coordinator.subscribeRoot(observerId, mutations => {
try {
mutations.some(m => "attributes" === m.type && "class" === m.attributeName && m.target instanceof Element && m.target.classList.contains("tab-content-cld")) && enhancedSetTimeout_(setupScrollListener, 100);
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in mutation observer:", error);
}
}, {
selector: "#right-tabs .tab-content-cld",
attributes: !0,
childList: !1,
subtree: !0,
attributeFilter: [ "class" ]
});
try {
window.YouTubeUtils?.ObserverRegistry?.track?.();
} catch (e) {}
const rightTabs = $("#right-tabs");
if (rightTabs) {
return observerId;
}
try {
window.YouTubeUtils?.ObserverRegistry?.untrack?.();
} catch (e) {}
coordinator.unsubscribe(observerId);
return null;
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in observeTabChanges:", error);
return null;
}
})();
return !0;
}
return !1;
},
maxAttempts: 40,
interval: 250
});
const playlistScheduler = window.YouTubeUtils?.createRetryScheduler?.({
check: () => {
if (token !== watchInitToken || !window.YouTubeUtils?.isWatchPage?.()) {
return !0;
}
try {
const playlistPanel = $("ytd-playlist-panel-renderer");
if (playlistPanel && !byId("playlist-panel-top-button")) {
(() => {
try {
setupTopButtonDelegation();
const playlistPanel = $("ytd-playlist-panel-renderer");
if (!playlistPanel || byId("playlist-panel-top-button")) {
return;
}
if (!config.enabled) {
return;
}
const button = document.createElement("button");
button.id = "playlist-panel-top-button";
button.className = "top-button";
button.title = t("scrollToTop");
button.setAttribute("aria-label", t("scrollToTop"));
_setSafeHTML(button, '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>');
const scrollContainer = $("#items", playlistPanel);
if (!scrollContainer) {
return;
}
playlistPanel.style.position = playlistPanel.style.position || "relative";
button.style.position = "absolute";
button.style.bottom = "16px";
button.style.right = "16px";
button.style.zIndex = "1000";
playlistPanel.appendChild(button);
const scrollHandler = _debounce(() => handleScroll(scrollContainer, button), 100);
scrollContainer._topButtonScrollHandler = scrollHandler;
scrollContainer.addEventListener("scroll", scrollHandler, {
passive: !0
});
handleScroll(scrollContainer, button);
const updateVisibility = () => {
try {
if (!playlistPanel.isConnected || playlistPanel.hidden) {
button.style.display = "none";
return;
}
if (null === playlistPanel.offsetParent && "fixed" !== playlistPanel.style.position) {
button.style.display = "none";
return;
}
const {width, height} = playlistPanel.getBoundingClientRect();
if (width < 40 || height < 40) {
button.style.display = "none";
return;
}
if (!scrollContainer || 0 === scrollContainer.offsetHeight || 0 === scrollContainer.scrollHeight) {
button.style.display = "none";
return;
}
button.style.display = "";
} catch (e) {
try {
button.style.display = "none";
} catch (e) {}
}
};
let ro = null;
try {
if ("undefined" != typeof ResizeObserver) {
ro = new ResizeObserver(updateVisibility);
ro.observe(playlistPanel);
scrollContainer && ro.observe(scrollContainer);
}
} catch (e) {
ro = null;
}
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.watchTarget && coordinator.watchTarget("enhanced::playlistPanelVisibility", playlistPanel, updateVisibility, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "class", "style", "hidden" ]
});
updateVisibility();
try {
window.YouTubeUtils && YouTubeUtils.cleanupManager && ro && YouTubeUtils.cleanupManager.register(() => {
try {
ro.disconnect();
} catch (e) {}
});
} catch (e) {}
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error creating playlist panel button:", error);
}
})();
return !0;
}
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error checking for playlist panel:", error);
}
return !1;
},
maxAttempts: 30,
interval: 300
});
tabCheckTimeoutId = tabScheduler;
playlistPanelCheckTimeoutId = playlistScheduler;
};
const init = () => {
try {
addStyles();
const checkPageType = () => {
try {
needsUniversalButton() && !byId("universal-top-button") && createUniversalButton();
"music.youtube.com" !== window.location.hostname || byId("music-side-top-button") || createMusicSidePanelButton();
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error checking page type:", error);
}
};
const onNavigate = () => {
stopWatchEnhancements();
(() => {
_musicContainersCache = null;
_musicContainersCacheTime = 0;
})();
checkPageType();
if (window.YouTubeUtils?.isWatchPage?.() || window.YouTubeUtils?.isShortsPage?.()) {
const _doInitDislike = () => {
try {
initReturnDislike();
} catch (e) {
window.console.warn("[YouTube+] initReturnDislike error:", e);
}
};
"function" == typeof requestIdleCallback ? requestIdleCallback(_doInitDislike, {
timeout: 3e3
}) : enhancedSetTimeout_(_doInitDislike, 0);
}
startWatchEnhancements();
};
onNavigate();
"function" == typeof window.YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", () => enhancedSetTimeout_(onNavigate, 200), {
passive: !0
}) : window.addEventListener("yt-navigate-finish", () => {
enhancedSetTimeout_(onNavigate, 200);
});
if ("music.youtube.com" === window.location.hostname) {
window.addEventListener("popstate", () => enhancedSetTimeout_(onNavigate, 200));
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
musicSidePanelSubId = "enhanced::musicSidePanel";
coordinator.subscribeRoot(musicSidePanelSubId, () => {
!byId("music-side-top-button") && config.enabled && createMusicSidePanelButton();
}, {
selector: "ytmusic-player-page, ytmusic-app-layout, ytmusic-app, #layout",
childList: !0,
attributes: !1,
subtree: !0
});
}
}
} catch (error) {
window.console.error("[YouTube+][Enhanced] Error in initialization:", error);
}
};
window.addEventListener("youtube-plus-settings-updated", e => {
try {
const nextEnabled = !1 !== e?.detail?.enableScrollToTopButton;
const tabviewEnabled = !1 !== e?.detail?.enableTabview;
const shouldUseUniversalOnWatch = ((window.YouTubeUtils?.isWatchPage?.() ?? !1) || (window.YouTubeUtils?.isShortsPage?.() ?? !1)) && !tabviewEnabled;
config.enabled = nextEnabled;
if (!config.enabled) {
cleanupTopButtons();
stopWatchEnhancements();
return;
}
addStyles();
cleanupTopButtons();
stopWatchEnhancements();
!needsUniversalButton() && !shouldUseUniversalOnWatch || byId("universal-top-button") || createUniversalButton();
"music.youtube.com" !== window.location.hostname || byId("music-side-top-button") || createMusicSidePanelButton();
startWatchEnhancements();
} catch (e) {}
});
onDomReady(() => {
"function" == typeof requestIdleCallback ? requestIdleCallback(init, {
timeout: 4e3
}) : enhancedSetTimeout_(init, 0);
});
})();

!(function() {
"use strict";
let pendingApplyTimeouts = [];
let lastAppliedVideoId = "";
const isVideoPage = () => {
try {
const path = window.location.pathname || "";
return "/watch" === path || path.startsWith("/shorts/");
} catch (e) {
return !1;
}
};
const normalizeQuality = value => {
const normalized = String(value || "").trim().toLowerCase();
return normalized && "unknown" !== normalized ? normalized : "";
};
const getPlayer = () => byId("movie_player");
const clearPendingApplyTimeouts = () => {
pendingApplyTimeouts.forEach(timeoutId => clearTimeout(timeoutId));
pendingApplyTimeouts = [];
};
const storeQuality = quality => {
const normalized = normalizeQuality(quality);
try {
if (!normalized || "auto" === normalized) {
localStorage.removeItem("youtube_plus_manual_playback_quality");
return;
}
localStorage.setItem("youtube_plus_manual_playback_quality", normalized);
} catch (e) {}
};
const applyStoredQualityOnce = () => {
if (!(window.YouTubeUtils?.loadFeatureEnabled?.("enableRememberManualQuality") ?? 1)) {
return !0;
}
if (!isVideoPage()) {
return !0;
}
const preferredQuality = (() => {
try {
return normalizeQuality(localStorage.getItem("youtube_plus_manual_playback_quality"));
} catch (e) {
return "";
}
})();
if (!preferredQuality) {
return !0;
}
const player = getPlayer();
if (!player) {
return !1;
}
const currentVideoId = (() => {
try {
const path = window.location.pathname || "";
if ("/watch" === path) {
return new URLSearchParams(window.location.search || "").get("v") || "";
}
if (path.startsWith("/shorts/")) {
return path.split("/")[2] || "";
}
} catch (e) {}
return "";
})();
if (currentVideoId && lastAppliedVideoId === currentVideoId) {
return !0;
}
try {
const availableQualityLevels = "function" == typeof player.getAvailableQualityLevels ? player.getAvailableQualityLevels().map(normalizeQuality).filter(Boolean) : [];
if (availableQualityLevels.length && !availableQualityLevels.includes(preferredQuality)) {
lastAppliedVideoId = currentVideoId;
return !0;
}
"function" == typeof player.setPlaybackQualityRange && player.setPlaybackQualityRange(preferredQuality, preferredQuality);
"function" == typeof player.setPlaybackQuality && player.setPlaybackQuality(preferredQuality);
lastAppliedVideoId = currentVideoId;
return !0;
} catch (e) {
return !1;
}
};
const scheduleApplyStoredQuality = () => {
clearPendingApplyTimeouts();
if ((window.YouTubeUtils?.loadFeatureEnabled?.("enableRememberManualQuality") ?? 1) && isVideoPage()) {
for (let attempt = 0; attempt < 16; attempt += 1) {
const timeoutId = enhancedSetTimeout_(() => {
applyStoredQualityOnce() && clearPendingApplyTimeouts();
}, 350 * attempt);
pendingApplyTimeouts.push(timeoutId);
}
}
};
document.addEventListener("click", event => {
const target = event.target instanceof HTMLElement ? event.target : null;
const menuItem = target?.closest?.(".ytp-quality-menu .ytp-menuitem, .ytp-panel-menu .ytp-menuitem");
if (!menuItem) {
return;
}
const label = String(menuItem.textContent || "").trim().toLowerCase();
label && /(\bauto\b|\d{3,4}p|\bhd\b|\b4k\b|\b8k\b)/.test(label) && enhancedSetTimeout_(() => {
if (!(window.YouTubeUtils?.loadFeatureEnabled?.("enableRememberManualQuality") ?? 1)) {
return;
}
if (label.includes("auto")) {
storeQuality("auto");
return;
}
const currentQuality = normalizeQuality(getPlayer()?.getPlaybackQuality?.());
currentQuality && storeQuality(currentQuality);
}, 150);
}, !0);
document.addEventListener("loadedmetadata", event => {
const target = event.target;
target instanceof HTMLElement && "VIDEO" === target.tagName && scheduleApplyStoredQuality();
}, !0);
window.addEventListener("youtube-plus-settings-updated", () => {
lastAppliedVideoId = "";
scheduleApplyStoredQuality();
});
window.addEventListener("yt-navigate-finish", () => {
lastAppliedVideoId = "";
scheduleApplyStoredQuality();
}, {
passive: !0
});
onDomReady(scheduleApplyStoredQuality);
})();

!(function() {
try {
const host = "undefined" == typeof location ? "" : location.hostname;
if (!host) {
return;
}
if (!/(^|\.)youtube\.com$/.test(host) && !/\.youtube\.google/.test(host)) {
return;
}
const SETTINGS_KEY = window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings";
const STYLE_ELEMENT_ID = "ytp-zen-features-style";
const NON_CRITICAL_STYLE_ID = "ytp-zen-features-style-noncritical";
const STYLE_MANAGER_KEY = "zen-features-style";
let nonCriticalTimer = null;
const DEFAULTS = {
enableZenStyles: !0,
hideSideGuide: !1,
zenStyles: {
themeVariant: "glass",
thumbnailHover: !0,
immersiveSearch: !0,
hideVoiceSearch: !0,
transparentHeader: !0,
hideSideGuide: !0,
cleanSideGuide: !1,
fixFeedLayout: !0,
sideVideosColumnsEnabled: !1,
sideVideosColumns: 0,
compactFeed: !0,
betterCaptions: !0,
playerBlur: !0,
theaterEnhancements: !0,
misc: !0
}
};
const loadSettings = () => {
let parsed = null;
try {
const raw = localStorage.getItem(SETTINGS_KEY);
raw && (parsed = JSON.parse(raw));
} catch (e) {
window.console.warn("[YouTube+] Zen settings parse error:", e);
}
const merged = {
...DEFAULTS,
...parsed && "object" == typeof parsed ? parsed : null
};
merged.zenStyles = {
...DEFAULTS.zenStyles,
...merged.zenStyles && "object" == typeof merged.zenStyles ? merged.zenStyles : null
};
!0 === merged.hideSideGuide && !0 !== merged.zenStyles.hideSideGuide && (merged.zenStyles.hideSideGuide = !0);
!0 !== merged.zenStyles.sideVideosTwoColumns || null != merged.zenStyles.sideVideosColumns && "" !== merged.zenStyles.sideVideosColumns || (merged.zenStyles.sideVideosColumns = 2);
!0 === merged.zenStyles.sideVideosTwoColumns && (merged.zenStyles.sideVideosColumnsEnabled = !0);
const parsedSideCols = Number(merged.zenStyles.sideVideosColumns);
merged.zenStyles.sideVideosColumns = !Number.isFinite(parsedSideCols) || parsedSideCols < 0 ? 0 : parsedSideCols;
merged.zenStyles.sideVideosColumns = Math.min(2, merged.zenStyles.sideVideosColumns);
"boolean" != typeof merged.zenStyles.sideVideosColumnsEnabled && (merged.zenStyles.sideVideosColumnsEnabled = merged.zenStyles.sideVideosColumns > 0);
return merged;
};
const CSS_BLOCKS = {
thumbnailHover: "\n        /* yt-thumbnail hover */\n        #inline-preview-player {transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) 1s !important; transform: scale(1) !important;}\n        #video-preview-container:has(#inline-preview-player) {transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; border-radius: 1.2em !important; overflow: hidden !important; transform: scale(1) !important;}\n        #video-preview-container:has(#inline-preview-player):hover {transform: scale(1.25) !important; box-shadow: rgba(0,0,0,0.5) 0px 0px 60px !important; transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s !important;}\n        ytd-app #content {opacity: 1 !important; transition: opacity 0.3s ease-in-out !important;}\n        ytd-app:has(#video-preview-container:hover) #content {opacity: 0.5 !important; transition: opacity 4s ease-in-out 1s !important;}\n      ",
immersiveSearch: '\n        /* yt-Immersive search */\n        #page-manager, yt-searchbox {transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.35) !important;}\n        #masthead yt-searchbox button[aria-label="Search"] {display: none !important;}\n        .ytSearchboxComponentInputBox {border-radius: 2em !important;}\n        yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) {position: relative !important; left: 0vw !important; top: -30vh !important; height: 40px !important; max-width: 600px !important; transform: scale(1) !important;}\n        @media only screen and (min-width: 1400px) {yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) { height: 60px !important; max-width: 700px !important; transform: scale(1.1) !important;}}\n        yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) .ytSearchboxComponentInputBox,\n        yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {background-color: var(--yt-bg-primary) !important; box-shadow: black 0 0 30px !important;}\n        @media (prefers-color-scheme: dark) {\n          yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) .ytSearchboxComponentInputBox,\n          yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {background-color: var(--yt-modal-bg) !important;}\n        }\n        yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {margin-top: 10px !important;}\n        @media only screen and (min-width: 1400px) {yt-searchbox:has(.ytSearchboxComponentInputBoxHasFocus) #i0 {margin-top: 30px !important;}}\n        .ytd-masthead #center:has(.ytSearchboxComponentInputBoxHasFocus) {height: 100vh !important; width: 100vw !important; left: 0 !important; top: 0 !important; position: fixed !important; justify-content: center !important; align-items: center !important;}\n        #content:has(.ytSearchboxComponentInputBoxHasFocus) #page-manager {filter: blur(20px) !important; transform: scale(1.05) !important;}\n      ',
hideVoiceSearch: "\n        /* No voice search button */\n        #voice-search-button {display: none !important;}\n      ",
transparentHeader: "\n        /* Transparent header */\n        :root{\n          --yt-spec-base-background: transparent;\n          --ytd-masthead-background: transparent;\n          --yt-spec-brand-background-primary: transparent;\n          --yt-spec-brand-background-solid: transparent;\n          --yt-spec-general-background-a: transparent;\n          --yt-spec-raised-background: transparent;\n          --yt-spec-additive-background: transparent;\n        }\n        #masthead-container,\n        #masthead,\n        ytd-masthead,\n        #background.ytd-masthead,\n        ytd-masthead #background,\n        ytd-masthead #container,\n        ytd-masthead #contentContainer,\n        ytd-masthead #end,\n        ytd-masthead #start,\n        ytd-masthead #center,\n        ytd-masthead #frosted-glass,\n        ytd-masthead #background-content,\n        #frosted-glass,\n        ytd-masthead tp-yt-app-header-layout,\n        ytd-mini-guide-renderer,\n        ytd-topbar-logo-renderer,\n        ytd-app #masthead,\n        ytd-app #masthead-container,\n        tp-yt-app-header-layout #masthead-container {\n          background-color: transparent !important;\n          background: transparent !important;\n          box-shadow: none !important;\n        }\n      ",
hideSideGuide: '\n        /* Hide side guide */\n        ytd-mini-guide-renderer, [theater=""] #contentContainer::after {display: none !important;}\n        tp-yt-app-drawer > #contentContainer:not([opened=""]),\n        #contentContainer:not([opened=""]) #guide-content,\n        ytd-mini-guide-renderer,\n        ytd-mini-guide-entry-renderer {background-color: var(--yt-spec-text-primary-inverse) !important; background: var(--yt-spec-text-primary-inverse) !important;}\n        #content:not(:has(#contentContainer[opened=""])) #page-manager {margin-left: 0 !important;}\n        ytd-app:not([guide-persistent-and-visible=""]) tp-yt-app-drawer > #contentContainer {background-color: var(--yt-spec-text-primary-inverse) !important;}\n        ytd-alert-with-button-renderer {align-items: center !important; justify-content: center !important;}\n      ',
cleanSideGuide: '\n        /* Clean side guide */\n        ytd-guide-section-renderer:has([title="YouTube Premium"]),\n        ytd-guide-renderer #footer {display: none !important;}\n        ytd-guide-section-renderer, ytd-guide-collapsible-section-entry-renderer {border: none !important;}\n      ',
fixFeedLayout: "\n        /* Fix new feed layout */\n        @media only screen and (min-width: 1400px) {\n          ytd-rich-item-renderer[rendered-from-rich-grid] { --ytd-rich-grid-items-per-row: 4 !important; }\n        }\n        @media only screen and (min-width: 1700px) {\n          ytd-rich-item-renderer[rendered-from-rich-grid] { --ytd-rich-grid-items-per-row: 5 !important; }\n        }\n        @media only screen and (min-width: 2180px) {\n          ytd-rich-item-renderer[rendered-from-rich-grid] { --ytd-rich-grid-items-per-row: 6 !important; }\n        }\n        ytd-rich-item-renderer[is-in-first-column] { margin-left: calc(var(--ytd-rich-grid-item-margin) / 2) !important; }\n        #contents { padding-left: calc(var(--ytd-rich-grid-item-margin) / 2 + var(--ytd-rich-grid-gutter-margin)) !important; }\n      ",
betterCaptions: "\n        /* Better captions */\n        .caption-window { backdrop-filter: blur(10px) brightness(70%) !important; border-radius: 1em !important; padding: 1em !important; box-shadow: rgba(0,0,0,0.5) 0 0 20px !important; width: fit-content !important; }\n        .ytp-caption-segment { background: none !important; }\n      ",
playerBlur: "\n        /* Player controls blur */\n        .ytp-left-controls .ytp-play-button,\n        .ytp-left-controls .ytp-volume-area,\n        .ytp-left-controls .ytp-time-display.notranslate > span,\n        .ytp-left-controls .ytp-chapter-container > button,\n        .ytp-left-controls .ytp-prev-button,\n        .ytp-left-controls .ytp-next-button,\n        .ytp-right-controls,\n        .ytp-time-wrapper,\n        .ytPlayerQuickActionButtonsHost,\n        .ytPlayerQuickActionButtonsHostCompactControls,\n        .ytPlayerQuickActionButtonsHostDisableBackdropFilter { backdrop-filter: blur(5px) !important; background-color: rgba(0,0,0,0.4) !important; }\n        .ytp-popup { backdrop-filter: blur(10px) !important; background-color: rgba(0,0,0,0.45) !important; }\n      ",
theaterEnhancements: "\n        /* Zen view comments (from zeninternet) */\n        /* Hide secondary column visually but break containment so fixed children can escape */\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #columns #secondary { display: block !important;width: 0 !important;min-width: 0 !important;max-width: 0 !important;padding: 0 !important;margin: 0 !important;border: 0 !important;overflow: visible !important;pointer-events: none !important;flex: 0 0 0px !important;contain: none !important;\n        }\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #secondary-inner { overflow: visible !important;contain: none !important;position: static !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #secondary-inner secondary-wrapper,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #secondary-inner .tabview-secondary-wrapper { contain: none !important;overflow: visible !important;position: static !important;max-height: none !important;height: auto !important;padding: 0 !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #right-tabs { display: block !important;overflow: visible !important;contain: none !important;position: static !important;width: 0 !important;height: 0 !important;padding: 0 !important;margin: 0 !important;border: 0 !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #right-tabs > header { display: none !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #right-tabs .tab-content { display: block !important;overflow: visible !important;contain: none !important;position: static !important;width: 0 !important;height: 0 !important;padding: 0 !important;margin: 0 !important;border: 0 !important;}\n        /* Break containment on tab-comments so its fixed-position child can escape */\n        /* Extra .tab-content-hidden selector to beat main.js specificity (line 5169) */\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments.tab-content-hidden,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments.tab-content-cld { contain: none !important;overflow: visible !important;position: static !important;display: block !important;visibility: visible !important;width: 0 !important;height: 0 !important;padding: 0 !important;margin: 0 !important;z-index: auto !important;pointer-events: none !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments.tab-content-hidden ytd-comments#comments > ytd-item-section-renderer#sections,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments.tab-content-hidden ytd-comments#comments > ytd-item-section-renderer#sections > #contents,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments.tab-content-hidden ytd-comments#comments #contents { contain: none !important;width: auto !important;height: auto !important;max-height: none !important;overflow: visible !important;visibility: visible !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-comments.tab-content-hidden ytd-comments#comments #contents > * { display: block !important;}\n        /* Hide other tabs content */\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-info,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-videos,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #tab-list { display: none !important;}\n        /* Comments overlay panel */\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) ytd-comments { visibility: visible !important;display: block !important;background-color: var(--yt-live-chat-shimmer-background-color) !important;backdrop-filter: blur(20px) !important;padding: 0 2em !important;border-radius: 2em 0 0 2em !important;max-height: calc(100vh - 120px) !important;overflow-y: auto !important;position: fixed !important;z-index: 2000 !important;top: 3vh !important;right: -42em !important;width: 40em !important;height: 90vh !important;opacity: 0 !important;pointer-events: auto !important;transition: opacity 0.4s ease, right 0.4s ease !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) ytd-comments:hover { opacity: 1 !important;right: 0 !important;}\n        /* Transparent overlay chat — fixed panel */\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) [tyt-chat-container],\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat-container { contain: none !important;overflow: visible !important;position: static !important;display: block !important;pointer-events: none !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat { visibility: visible !important;display: block !important;position: fixed !important;top: 3vh !important;right: 0 !important;width: 400px !important;height: calc(100vh - 120px) !important;max-height: calc(100vh - 120px) !important;z-index: 2001 !important;opacity: 0.85 !important;pointer-events: auto !important;border-radius: 2em 0 0 2em !important;overflow: hidden !important;backdrop-filter: blur(20px) !important;transition: opacity 0.4s ease !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat[collapsed] { visibility: visible !important;display: block !important;position: fixed !important;top: 3vh !important;right: 0 !important;width: 400px !important;height: calc(100vh - 120px) !important;max-height: calc(100vh - 120px) !important;z-index: 2001 !important;opacity: 0.85 !important;pointer-events: auto !important;overflow: hidden !important;border-radius: 2em 0 0 2em !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat[collapsed] > #show-hide-button,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat[collapsed] > .ytd-live-chat-frame#show-hide-button { display: none !important;visibility: hidden !important;opacity: 0 !important;pointer-events: none !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat[collapsed] iframe { display: block !important;visibility: visible !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat iframe { height: 100% !important;width: 100% !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) yt-live-chat-renderer { background: transparent !important;}\n        /* Ambient mode: fix black bars in theater */\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #cinematics-container,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #cinematics { position: absolute !important;top: 0 !important;left: 0 !important;width: 100% !important;height: 100% !important;overflow: hidden !important;pointer-events: none !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #cinematics canvas,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #cinematics video { position: absolute !important;top: 50% !important;left: 50% !important;transform: translate(-50%, -50%) scale(1.2) !important;min-width: 100% !important;min-height: 100% !important;object-fit: cover !important;}\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #player-full-bleed-container { overflow: hidden !important;}\n        ytd-watch-flexy[fullscreen] ytd-live-chat-frame { background-color: var(--app-drawer-content-container-background-color) !important;}\n      ",
misc: "        \n        /* Show video meta on hover */\n        #content #dismissible:hover ytd-video-meta-block { opacity: 1 !important;}\n      ",
compactFeed: "\n      /* Compact feed – reduced spacing, hover menus, inline details */\n        ytd-rich-item-renderer { margin-bottom: 15px !important;}\n        ytd-rich-item-renderer[rendered-from-rich-grid] { --ytd-rich-item-row-usable-width: calc(100% - var(--ytd-rich-grid-gutter-margin) * 1) !important;}\n        ytd-rich-item-renderer #metadata.ytd-video-meta-block { flex-direction: row !important;}\n        ytd-rich-item-renderer #metadata.ytd-video-meta-block #metadata-line span:nth-child(3) { height: 1em !important;margin-left: 1em !important;}\n        ytd-rich-grid-media { border-radius: 1.2em;height: 100% !important;}\n        ytd-rich-grid-media ytd-menu-renderer #button { opacity: 0 !important;transition: opacity 0.3s ease-in-out !important;}\n        ytd-rich-grid-media:hover ytd-menu-renderer #button { opacity: 1 !important;}\n      ",
themeSolid: "\n        html {\n          --yt-glass-blur:none !important;\n          --yt-glass-blur-light:none !important;\n          --yt-glass-blur-heavy:none !important;\n        }\n        html[dark],html:not([dark]):not([light]) {\n          --yt-glass-bg:rgba(24,24,24,.96) !important;\n          --yt-panel-bg:rgba(30,30,30,.98) !important;\n          --yt-header-bg:rgba(22,22,22,.98) !important;\n          --yt-button-bg:rgba(42,42,42,.98) !important;\n          --yt-input-bg:rgba(34,34,34,.98) !important;\n          --yt-glass-shadow:0 10px 28px rgba(0,0,0,.28) !important;\n        }\n        html[light] {\n          --yt-glass-bg:rgba(255,255,255,.98) !important;\n          --yt-panel-bg:rgba(250,250,250,.99) !important;\n          --yt-header-bg:rgba(245,245,245,.99) !important;\n          --yt-button-bg:rgba(236,236,236,.98) !important;\n          --yt-input-bg:rgba(245,245,245,.98) !important;\n          --yt-glass-shadow:0 10px 24px rgba(0,0,0,.12) !important;\n        }\n        .ytp-plus-settings-panel,\n        .ytp-plus-settings-sidebar,\n        .top-button,\n        .download-options.visible,\n        .speed-options.visible,\n        .glass-dropdown__list,\n        .glass-panel,\n        .glass-card,\n        .ytp-plus-comments-sidepanel,\n        .ytp-plus-comments-item,\n        .youtube-enhancer-notification,\n        .stats-modal-content,\n        .settings-menu,\n        #timecode-panel,\n        #shorts-keyboard-feedback,\n        .shortsStats,\n        .ytp-popup,\n        .ytPlayerQuickActionButtonsHost,\n        .ytPlayerQuickActionButtonsHostCompactControls,\n        .ytPlayerQuickActionButtonsHostDisableBackdropFilter,\n        .caption-window,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) ytd-comments,\n        ytd-watch-flexy:is([theater],[full-bleed-player]):not([fullscreen]) #chat {\n          backdrop-filter:none !important;\n          -webkit-backdrop-filter:none !important;\n        }\n      ",
clsPrevention: "\n        /* CLS Prevention - Reserve space for dynamic elements */\n        #ytp-plus-dislike-text { min-width: 1.5em;display: inline-block !important;}\n        /* Contain layout only for our own panels (not YouTube layout elements) */\n        .ytp-plus-stats-panel, .ytp-plus-modal-content { contain: layout style;}\n        /* Reduce CLS from late-loading channel avatars */\n        #owner #avatar { min-width: 40px; min-height: 40px; }\n        /* Reserve space for action buttons to prevent shift */\n        ytd-menu-renderer.ytd-watch-metadata { min-height: 36px; }\n        /* Subscribe button stability */\n        ytd-subscribe-button-renderer { min-width: 90px; }\n      "
};
const buildCriticalCss = settings => {
const z = settings?.zenStyles || {};
let css = CSS_BLOCKS.clsPrevention;
"solid" === (z.themeVariant || "glass") && (css += CSS_BLOCKS.themeSolid);
z.hideSideGuide && (css += CSS_BLOCKS.hideSideGuide);
z.fixFeedLayout && (css += CSS_BLOCKS.fixFeedLayout);
z.theaterEnhancements && (css += CSS_BLOCKS.theaterEnhancements);
return css.trim();
};
const buildNonCriticalCss = settings => {
const z = settings?.zenStyles || {};
const themeVariant = z.themeVariant || "glass";
let css = "";
z.thumbnailHover && (css += CSS_BLOCKS.thumbnailHover);
z.immersiveSearch && (css += CSS_BLOCKS.immersiveSearch);
z.hideVoiceSearch && (css += CSS_BLOCKS.hideVoiceSearch);
z.transparentHeader && (css += CSS_BLOCKS.transparentHeader);
z.cleanSideGuide && (css += CSS_BLOCKS.cleanSideGuide);
const sideColsRaw = Number(z.sideVideosColumns);
const sideCols = Number.isFinite(sideColsRaw) ? Math.max(0, Math.min(2, sideColsRaw)) : 0;
const sideColsEnabled = !0 === z.sideVideosColumnsEnabled;
sideColsEnabled && sideCols > 0 && (css += `\n        /* Side Videos: ${sideCols}-column card grid */\n        ytd-watch-flexy #secondary #related ytd-item-section-renderer #contents,\n        ytd-watch-flexy #secondary #related ytd-watch-next-secondary-results-renderer #items {\n          display: grid !important;\n          grid-template-columns: repeat(${sideCols}, minmax(0, 1fr)) !important;\n          gap: 8px !important;\n          padding: 0 !important;\n          align-items: start !important;\n        }\n        ytd-watch-flexy #secondary #related ytd-compact-video-renderer,\n        ytd-watch-flexy #secondary #related ytd-compact-radio-renderer,\n        ytd-watch-flexy #secondary #related ytd-compact-playlist-renderer,\n        ytd-watch-flexy #secondary #related yt-lockup-view-model {\n          width: 100% !important;\n          min-width: 0 !important;\n          max-width: 100% !important;\n          margin: 0 !important;\n          box-sizing: border-box !important;\n        }\n        ytd-watch-flexy #secondary #related yt-lockup-view-model .ytLockupViewModelHost {\n          display: block !important;\n          width: 100% !important;\n          min-width: 0 !important;\n        }\n        ytd-watch-flexy #secondary #related yt-lockup-view-model .ytLockupViewModelHorizontal,\n        ytd-watch-flexy #secondary #related yt-lockup-view-model .yt-lockup-view-model-wiz {\n          display: flex !important;\n          flex-direction: column !important;\n          align-items: stretch !important;\n          width: 100% !important;\n          min-width: 0 !important;\n          gap: 6px !important;\n        }\n        ytd-watch-flexy #secondary #related yt-lockup-view-model .yt-lockup-view-model-wiz__content-image,\n        ytd-watch-flexy #secondary #related yt-lockup-view-model [class*="LockupContentImage"],\n        ytd-watch-flexy #secondary #related yt-lockup-view-model yt-image {\n          width: 100% !important;\n          max-width: 100% !important;\n          min-width: 0 !important;\n          height: auto !important;\n          flex: 0 0 auto !important;\n        }\n        ytd-watch-flexy #secondary #related yt-lockup-view-model yt-image img {\n          width: 100% !important;\n          height: auto !important;\n          object-fit: cover !important;\n          display: block !important;\n        }\n        ytd-watch-flexy #secondary #related yt-lockup-view-model .yt-lockup-view-model-wiz__text-container {\n          padding: 4px 0 0 0 !important;\n          min-width: 0 !important;\n          width: 100% !important;\n          box-sizing: border-box !important;\n        }\n        ytd-watch-flexy #secondary #related ytd-compact-video-renderer #dismissible {\n          display: flex !important;\n          flex-direction: column !important;\n          gap: 6px !important;\n          width: 100% !important;\n        }\n        ytd-watch-flexy #secondary #related ytd-compact-video-renderer ytd-thumbnail {\n          width: 100% !important;\n          max-width: 100% !important;\n          min-width: 0 !important;\n        }\n        ytd-watch-flexy #secondary #related ytd-compact-video-renderer .details,\n        ytd-watch-flexy #secondary #related ytd-compact-video-renderer #meta {\n          padding-left: 0 !important;\n          min-width: 0 !important;\n          width: 100% !important;\n          box-sizing: border-box !important;\n        }\n      `);
z.betterCaptions && "solid" !== themeVariant && (css += CSS_BLOCKS.betterCaptions);
z.playerBlur && "solid" !== themeVariant && (css += CSS_BLOCKS.playerBlur);
z.compactFeed && (css += CSS_BLOCKS.compactFeed);
z.misc && (css += CSS_BLOCKS.misc);
return css.trim();
};
const removeStyles = () => {
try {
window.YouTubeUtils?.StyleManager?.remove && window.YouTubeUtils.StyleManager.remove(STYLE_MANAGER_KEY);
} catch (e) {}
if (nonCriticalTimer) {
if ("undefined" != typeof window && "function" == typeof window.cancelIdleCallback) {
try {
window.cancelIdleCallback(nonCriticalTimer);
} catch (e) {}
} else {
clearTimeout(nonCriticalTimer);
}
nonCriticalTimer = null;
}
const el = byId(STYLE_ELEMENT_ID);
if (el) {
try {
el.remove();
} catch (e) {}
}
const ncEl = byId(NON_CRITICAL_STYLE_ID);
if (ncEl) {
try {
ncEl.remove();
} catch (e) {}
}
};
const applyNonCriticalStyles = css => {
if (!css) {
const ncEl = byId(NON_CRITICAL_STYLE_ID);
ncEl && ncEl.remove();
return;
}
let ncEl = byId(NON_CRITICAL_STYLE_ID);
if (!ncEl) {
ncEl = document.createElement("style");
ncEl.id = NON_CRITICAL_STYLE_ID;
(document.head || document.documentElement).appendChild(ncEl);
}
ncEl.textContent = css;
};
const applyStyles = (settings, immediateNonCritical = !1) => {
const enabled = !1 !== settings?.enableZenStyles;
if (!enabled) {
removeStyles();
return;
}
const criticalCss = buildCriticalCss(settings);
const nonCriticalCss = buildNonCriticalCss(settings);
if (!criticalCss && !nonCriticalCss) {
removeStyles();
return;
}
try {
if (window.YouTubeUtils?.StyleManager?.add) {
window.YouTubeUtils.StyleManager.add(STYLE_MANAGER_KEY, criticalCss || "");
const el = byId(STYLE_ELEMENT_ID);
el && el.remove();
if (nonCriticalTimer) {
if ("undefined" != typeof window && "function" == typeof window.cancelIdleCallback) {
try {
window.cancelIdleCallback(nonCriticalTimer);
} catch (e) {}
} else {
clearTimeout(nonCriticalTimer);
}
}
if (immediateNonCritical) {
applyNonCriticalStyles(nonCriticalCss);
nonCriticalTimer = null;
} else {
nonCriticalTimer = "function" == typeof requestIdleCallback ? requestIdleCallback(() => applyNonCriticalStyles(nonCriticalCss), {
timeout: 5e3
}) : enhancedSetTimeout_(() => applyNonCriticalStyles(nonCriticalCss), 200);
}
return;
}
} catch (e) {}
let el = byId(STYLE_ELEMENT_ID);
if (!el) {
el = document.createElement("style");
el.id = STYLE_ELEMENT_ID;
(document.head || document.documentElement).appendChild(el);
}
el.textContent = criticalCss || "";
if (nonCriticalTimer) {
if ("undefined" != typeof window && "function" == typeof window.cancelIdleCallback) {
try {
window.cancelIdleCallback(nonCriticalTimer);
} catch (e) {}
} else {
clearTimeout(nonCriticalTimer);
}
}
if (immediateNonCritical) {
applyNonCriticalStyles(nonCriticalCss);
nonCriticalTimer = null;
} else {
nonCriticalTimer = "function" == typeof requestIdleCallback ? requestIdleCallback(() => applyNonCriticalStyles(nonCriticalCss), {
timeout: 5e3
}) : enhancedSetTimeout_(() => applyNonCriticalStyles(nonCriticalCss), 200);
}
};
const applyFromStorage = (immediateNonCritical = !1) => applyStyles(loadSettings(), immediateNonCritical);
applyFromStorage(!0);
try {
const _applySearchboxWillChange = () => {
const sb = $("yt-searchbox");
sb instanceof HTMLElement && (sb.style.willChange = "transform");
};
const _clearSearchboxWillChange = () => {
const sb = $("yt-searchbox");
sb instanceof HTMLElement && (sb.style.willChange = "");
};
document.addEventListener("focusin", e => {
e.target instanceof Element && e.target.closest("yt-searchbox") && _applySearchboxWillChange();
}, {
passive: !0,
capture: !0
});
document.addEventListener("focusout", e => {
e.target instanceof Element && e.target.closest("yt-searchbox") && _clearSearchboxWillChange();
}, {
passive: !0,
capture: !0
});
} catch (e) {}
window.addEventListener("youtube-plus-settings-updated", e => {
try {
applyStyles(e?.detail || loadSettings(), !0);
} catch (e) {
applyFromStorage(!0);
}
});
window.addEventListener("yt-navigate-finish", () => {
applyFromStorage(!0);
}, {
passive: !0
});
} catch (err) {
window.console.error("zen-youtube-features injection failed", err);
}
})();

!(function() {
"use strict";
const host = "undefined" == typeof location ? "" : location.hostname;
if (!host) {
return;
}
if (!/(^|\.)youtube\.com$/.test(host) && !/\.youtube\.google/.test(host)) {
return;
}
const SETTINGS_KEY = window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings";
const isTheaterEnhancementEnabled = () => {
const settings = (() => {
try {
const raw = localStorage.getItem(SETTINGS_KEY);
if (!raw) {
return null;
}
const parsed = JSON.parse(raw);
return parsed && "object" == typeof parsed ? parsed : null;
} catch (e) {
return null;
}
})();
return !settings || !1 !== settings.enableZenStyles && (!settings.zenStyles || !1 !== settings.zenStyles.theaterEnhancements);
};
const clickElement = element => {
if (element) {
try {
element.dispatchEvent(new window.MouseEvent("click", {
bubbles: !0,
cancelable: !0,
view: window
}));
} catch (e) {
try {
element.click();
} catch (e) {}
}
}
};
let expandAttempts = 0;
const runOverlayFixes = () => {
if (!window.YouTubeUtils?.isWatchPage?.()) {
return;
}
if (!isTheaterEnhancementEnabled()) {
return;
}
const flexy = $("ytd-watch-flexy");
if (!flexy || flexy.hasAttribute("fullscreen")) {
return;
}
const isTheaterLike = flexy.hasAttribute("theater") || flexy.hasAttribute("full-bleed-player") || flexy.hasAttribute("theater-requested_");
if (isTheaterLike) {
(() => {
const chat = $("ytd-live-chat-frame#chat");
if (!chat) {
return;
}
if (chat.hasAttribute("collapsed")) {
if (expandAttempts >= 3) {
return;
}
expandAttempts++;
let expanded = !1;
try {
const cnt = chat.polymerController || (void 0 !== chat.__data ? chat : null) || (chat.inst ? chat.inst : null);
if (cnt && "function" == typeof cnt.setCollapsedState) {
cnt.setCollapsedState({
setLiveChatCollapsedStateAction: {
collapsed: !1
}
});
expanded = !1 === cnt.collapsed;
}
if (!expanded && cnt && "boolean" == typeof cnt.collapsed) {
cnt.collapsed = !1;
!0 === cnt.isHiddenByUser && (cnt.isHiddenByUser = !1);
expanded = !1 === cnt.collapsed;
}
} catch (e) {}
if (!expanded) {
const showBtn = chat.querySelector("#show-hide-button div.yt-spec-touch-feedback-shape, #show-hide-button ytd-toggle-button-renderer, #show-hide-button button");
showBtn && clickElement(showBtn);
}
}
const iframe = chat.querySelector("iframe#chatframe");
iframe && !iframe.src && chat.url && (iframe.src = chat.url);
})();
(flexy => {
const commentsTab = $("#tab-comments");
const commentsBtn = $('#material-tabs a[tyt-tab-content="#tab-comments"]');
if (!commentsTab || !commentsBtn || "1" === commentsTab.getAttribute("data-ytp-zen-comments-preloaded")) {
return;
}
flexy && !flexy.hasAttribute("keep-comments-scroller") && flexy.setAttribute("keep-comments-scroller", "");
const activeBtn = $("#material-tabs a[tyt-tab-content].active");
clickElement(commentsBtn);
requestAnimationFrame(() => {
commentsTab.setAttribute("data-ytp-zen-comments-preloaded", "1");
activeBtn && activeBtn !== commentsBtn && activeBtn.isConnected && clickElement(activeBtn);
});
})(flexy);
}
};
let debounceTimer = null;
const scheduleRun = () => {
debounceTimer && clearTimeout(debounceTimer);
debounceTimer = enhancedSetTimeout_(runOverlayFixes, 150);
};
const setupOverlayObservers = () => {
const coordinator = window.YouTubeMutationCoordinator;
if (!coordinator?.watchTarget) {
return;
}
let observedFlexy = null;
const attachFlexyObserver = () => {
const flexy = $("ytd-watch-flexy");
if (flexy && flexy !== observedFlexy) {
coordinator.watchTarget("enhanced::overlayFlexy", flexy, scheduleRun, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "theater", "full-bleed-player", "theater-requested_", "fullscreen" ]
});
observedFlexy = flexy;
}
};
let observedChat = null;
const attachChatObserver = () => {
const chat = $("ytd-live-chat-frame#chat");
if (chat && chat !== observedChat) {
coordinator.watchTarget("enhanced::overlayChat", chat, scheduleRun, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "collapsed" ]
});
observedChat = chat;
}
};
attachFlexyObserver();
attachChatObserver();
window.addEventListener("yt-navigate-finish", () => {
expandAttempts = 0;
enhancedSetTimeout_(() => {
attachFlexyObserver();
attachChatObserver();
scheduleRun();
}, 180);
}, {
passive: !0
});
try {
window.YouTubeUtils?.cleanupManager?.register && window.YouTubeUtils.cleanupManager.register(() => {
coordinator.unwatch("enhanced::overlayFlexy");
coordinator.unwatch("enhanced::overlayChat");
});
} catch (e) {}
};
if ("loading" === document.readyState) {
document.addEventListener("DOMContentLoaded", () => {
scheduleRun();
setupOverlayObservers();
}, {
once: !0
});
} else {
scheduleRun();
setupOverlayObservers();
}
})();

!(function() {
"use strict";
const t = window.YouTubeUtils.t;
const TRANSLATE_BTN_CLASS = "ytp-comment-translate-btn";
const ORIGINAL_ATTR = "data-ytp-original-text";
const SETTINGS_KEY = window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings";
let translateObserver = null;
const LANG_MAP = {
cn: "zh-CN",
tw: "zh-TW",
kr: "ko",
jp: "ja",
ng: "en",
du: "nl",
be: "be",
bg: "bg",
kk: "kk",
ky: "ky",
uz: "uz",
uk: "uk",
"zh-hans": "zh-CN",
"zh-hant": "zh-TW",
"zh-cn": "zh-CN",
"zh-tw": "zh-TW",
"zh-hk": "zh-TW",
iw: "he",
jv: "jw",
"sr-latn": "sr",
"pt-br": "pt",
"pt-pt": "pt",
ar: "ar",
az: "az",
cs: "cs",
da: "da",
de: "de",
el: "el",
en: "en",
es: "es",
fi: "fi",
fr: "fr",
hi: "hi",
hr: "hr",
hu: "hu",
id: "id",
it: "it",
lt: "lt",
lv: "lv",
ms: "ms",
nl: "nl",
no: "no",
pl: "pl",
ro: "ro",
ru: "ru",
sk: "sk",
sl: "sl",
sq: "sq",
sv: "sv",
th: "th",
tr: "tr",
vi: "vi"
};
const toGoogleLang = code => {
if (!code) {
return "en";
}
const lower = code.toLowerCase();
if (LANG_MAP[lower]) {
return LANG_MAP[lower];
}
const base = lower.split("-")[0];
return LANG_MAP[base] || base || "en";
};
const getTranslateLabel = () => t("translateComment") || "Translate";
const getShowOriginalLabel = () => t("showOriginal") || "Show original";
const injectStyles = (() => {
let injected = !1;
return () => {
if (injected) {
return;
}
injected = !0;
const css = `\n        .${TRANSLATE_BTN_CLASS}{\n          display:inline-flex;align-items:center;gap:4px;\n          background:none;border:none;cursor:pointer;\n          color:var(--yt-spec-text-secondary,#aaa);\n          font-size:1.2rem;line-height:1.8rem;font-weight:400;\n          padding:4px 0;margin-top:4px;\n          font-family:'Roboto','Arial',sans-serif;\n          transition:color .2s;\n        }\n        .${TRANSLATE_BTN_CLASS}:hover{color:var(--yt-spec-text-primary,#fff);}\n        .${TRANSLATE_BTN_CLASS}[disabled]{opacity:.5;cursor:wait;}\n        .${TRANSLATE_BTN_CLASS} svg{flex-shrink:0;}\n      `;
try {
if (window.YouTubeUtils?.StyleManager?.add) {
window.YouTubeUtils.StyleManager.add("ytp-comment-translate-styles", css);
return;
}
} catch (e) {}
const style = document.createElement("style");
style.id = "ytp-comment-translate-styles";
style.textContent = css;
(document.head || document.documentElement).appendChild(style);
};
})();
const translateIcon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12.87 15.07l-2.54-2.51.03-.03A17.52 17.52 0 0014.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"/></svg>';
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const isCommentTranslateEnabled = (settings = null) => {
try {
const currentSettings = settings || window.youtubePlus?.settings || (() => {
const parsed = JSON.parse(localStorage.getItem(SETTINGS_KEY) || "{}");
return parsed && "object" == typeof parsed ? parsed : {};
})();
return !1 !== currentSettings?.enableCommentTranslate;
} catch (e) {
return !0;
}
};
const removeTranslateButtons = () => {
$$(`.${TRANSLATE_BTN_CLASS}`).forEach(btn => btn.remove());
$$(`[data-ytp-translated][${ORIGINAL_ATTR}]`).forEach(node => {
const original = node.getAttribute(ORIGINAL_ATTR);
original && (node.textContent = original);
node.removeAttribute("data-ytp-translated");
node.removeAttribute(ORIGINAL_ATTR);
});
};
const stopTranslateObserver = () => {
if (translateObserver) {
window.YouTubeMutationCoordinator?.unwatch?.(translateObserver);
translateObserver = null;
}
};
const addTranslateButton = commentEl => {
if (commentEl.querySelector(`.${TRANSLATE_BTN_CLASS}`)) {
return;
}
const contentEl = commentEl.querySelector("#content-text.ytd-comment-view-model, #content-text.ytd-comment-renderer, yt-attributed-string#content-text, yt-formatted-string#content-text, #content-text");
if (!contentEl) {
return;
}
const text = (contentEl.textContent || "").trim();
if (!text || text.length < 2) {
return;
}
const userLang = (() => {
try {
return toGoogleLang(window.YouTubeUtils.getLanguage());
} catch (e) {}
return toGoogleLang(navigator.language) || "en";
})();
const btn = document.createElement("button");
btn.className = TRANSLATE_BTN_CLASS;
btn.type = "button";
_setSafeHTML(btn, `${translateIcon} ${getTranslateLabel()}`);
btn.setAttribute("aria-label", getTranslateLabel());
btn.addEventListener("click", async e => {
e.preventDefault();
e.stopPropagation();
if (contentEl.hasAttribute("data-ytp-translated")) {
const original = contentEl.getAttribute(ORIGINAL_ATTR);
if (original) {
contentEl.textContent = original;
contentEl.removeAttribute("data-ytp-translated");
_setSafeHTML(btn, `${translateIcon} ${getTranslateLabel()}`);
btn.setAttribute("aria-label", getTranslateLabel());
}
return;
}
btn.disabled = !0;
_setSafeHTML(btn, `${translateIcon} ...`);
const originalText = contentEl.textContent || "";
const translated = await (async (text, targetLang) => {
const controller = new AbortController;
const timerId = enhancedSetTimeout_(() => controller.abort(), 8e3);
try {
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${encodeURIComponent(targetLang)}&dt=t&q=${encodeURIComponent(text)}`;
const resp = await fetch(url, {
signal: controller.signal
});
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}`);
}
const data = await resp.json();
if (Array.isArray(data) && Array.isArray(data[0])) {
return data[0].map(s => s && s[0] || "").join("");
}
} catch (e) {
"AbortError" !== e?.name && window.console.warn("[YouTube+] Translation failed:", e);
} finally {
clearTimeout(timerId);
}
return null;
})(originalText, userLang);
if (translated && translated !== originalText) {
contentEl.setAttribute(ORIGINAL_ATTR, originalText);
contentEl.setAttribute("data-ytp-translated", "true");
contentEl.textContent = translated;
_setSafeHTML(btn, `${translateIcon} ${getShowOriginalLabel()}`);
btn.setAttribute("aria-label", getShowOriginalLabel());
} else {
_setSafeHTML(btn, `${translateIcon} ${getTranslateLabel()}`);
btn.setAttribute("aria-label", getTranslateLabel());
}
btn.disabled = !1;
});
const actionBar = commentEl.querySelector("#action-buttons, ytd-comment-action-buttons-renderer, #toolbar");
actionBar ? actionBar.parentElement.insertBefore(btn, actionBar) : contentEl.after(btn);
};
const processComments = () => {
const commentSelectors = [ "ytd-comment-view-model", "ytd-comment-renderer", "ytd-comment-thread-renderer" ];
for (const sel of commentSelectors) {
$$(sel).forEach(addTranslateButton);
}
};
let processTimeout = null;
const startTranslateFeature = () => {
if (!isCommentTranslateEnabled()) {
stopTranslateObserver();
removeTranslateButtons();
return;
}
injectStyles();
processComments();
if (translateObserver) {
return;
}
const commentsContainer = $("#comments, #tab-comments, #content");
const target = commentsContainer || document.body;
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.watchTarget) {
translateObserver = "enhanced::translateComments";
coordinator.watchTarget(translateObserver, target, mutations => {
let hasNewComments = !1;
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node instanceof Element && (node.matches?.("ytd-comment-view-model, ytd-comment-renderer, ytd-comment-thread-renderer") || node.querySelector?.("ytd-comment-view-model, ytd-comment-renderer, #content-text"))) {
hasNewComments = !0;
break;
}
}
if (hasNewComments) {
break;
}
}
hasNewComments && (() => {
processTimeout && clearTimeout(processTimeout);
processTimeout = enhancedSetTimeout_(processComments, 300);
})();
}, {
childList: !0,
attributes: !1,
subtree: !0
});
}
};
const scheduleInit = () => {
const isVideoPage = "/watch" === location.pathname || location.pathname.startsWith("/shorts/");
isVideoPage && ("function" == typeof requestIdleCallback ? requestIdleCallback(() => startTranslateFeature(), {
timeout: 3e3
}) : enhancedSetTimeout_(startTranslateFeature, 1500));
};
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", scheduleInit, {
once: !0
}) : scheduleInit();
window.addEventListener("yt-navigate-finish", scheduleInit, {
passive: !0
});
window.addEventListener("youtube-plus-settings-updated", e => {
if (isCommentTranslateEnabled(e?.detail)) {
startTranslateFeature();
} else {
stopTranslateObserver();
removeTranslateButtons();
}
});
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
const U = window.YouTubeUtils || {};
const $ = (sel, ctx = document) => U.$(sel, ctx) || ctx.querySelector(sel);
const t = U.t || (key => key || "");
const SKIP_SELECTOR = [ ".ytp-ad-skip-button", ".ytp-ad-skip-button-modern", ".ytp-skip-ad-button", ".videoAdUiSkipButton", "button.ytp-ad-skip-button-modern", ".ytp-ad-skip-button-slot button", ".ytp-ad-skip-button-container button", ".ytp-ad-skip-button-modern .ytp-ad-skip-button-container", ".ytp-skip-ad-button__text", 'button[class*="skip"]', ".ytp-ad-skip-button-modern button", "ytd-button-renderer.ytp-ad-skip-button-renderer button" ].join(",");
/**
   * Ad blocking functionality for YouTube
   * @namespace AdBlocker
   */ const AdBlocker = {
config: {
skipInterval: 1e3,
removeInterval: 3e3,
enableLogging: !1,
maxRetries: 2,
enabled: !0,
storageKey: "youtube_adblocker_settings"
},
state: {
isYouTubeShorts: !1,
isYouTubeMusic: "music.youtube.com" === location.hostname,
lastSkipAttempt: 0,
retryCount: 0,
initialized: !1,
moviePlayerSubId: null,
adSlotSubId: null
},
cache: {
moviePlayer: null,
ytdPlayer: null,
lastCacheTime: 0,
cacheTimeout: 1e4
},
selectors: {
ads: ".ytp-ad-timed-pie-countdown-container,.ytp-ad-survey-questions,.ytp-ad-overlay-container,.ytp-ad-progress,.ytp-ad-progress-list",
elements: "#masthead-ad,ytd-merch-shelf-renderer,.yt-mealbar-promo-renderer,ytmusic-mealbar-promo-renderer,ytmusic-statement-banner-renderer,.ytp-featured-product,ytd-in-feed-ad-layout-renderer,ytd-banner-promo-renderer,ytd-statement-banner-renderer,ytd-brand-video-singleton-renderer,ytd-brand-video-shelf-renderer,ytd-promoted-sparkles-web-renderer,ytd-display-ad-renderer,ytd-promoted-video-renderer,.ytd-mealbar-promo-renderer",
video: "video.html5-main-video",
removal: "ytd-reel-video-renderer .ytd-ad-slot-renderer, ytd-ad-slot-renderer, #player-ads, ytd-in-feed-ad-layout-renderer, ytd-display-ad-renderer, ytd-promoted-sparkles-web-renderer, ytd-promoted-video-renderer, ad-slot-renderer, ytd-player-legacy-desktop-watch-ads-renderer"
},
wrappers: [ "ytd-rich-item-renderer", "ytd-grid-video-renderer", "ytd-compact-video-renderer", "ytd-rich-grid-media", "ytd-rich-shelf-renderer", "ytd-rich-grid-row", "ytd-video-renderer", "ytd-playlist-renderer", "ytd-reel-video-renderer" ],
settings: {
load() {
try {
const saved = localStorage.getItem(AdBlocker.config.storageKey);
if (!saved) {
return;
}
const parsed = JSON.parse(saved);
if ("object" != typeof parsed || null === parsed) {
window.console.warn("[AdBlocker] Invalid settings format");
return;
}
AdBlocker.config.enabled = "boolean" != typeof parsed.enabled || parsed.enabled;
AdBlocker.config.enableLogging = "boolean" == typeof parsed.enableLogging && parsed.enableLogging;
} catch (error) {
window.console.error("[AdBlocker] Error loading settings:", error);
AdBlocker.config.enabled = !0;
AdBlocker.config.enableLogging = !1;
}
},
save() {
try {
const settingsToSave = {
enabled: AdBlocker.config.enabled,
enableLogging: AdBlocker.config.enableLogging
};
localStorage.setItem(AdBlocker.config.storageKey, JSON.stringify(settingsToSave));
} catch (error) {
window.console.error("[AdBlocker] Error saving settings:", error);
}
}
},
getPlayer() {
const now = Date.now();
if (now - AdBlocker.cache.lastCacheTime > AdBlocker.cache.cacheTimeout) {
AdBlocker.cache.moviePlayer = $("#movie_player");
AdBlocker.cache.ytdPlayer = $("#ytd-player");
AdBlocker.cache.lastCacheTime = now;
}
const playerEl = AdBlocker.cache.ytdPlayer;
return {
element: AdBlocker.cache.moviePlayer,
player: playerEl?.getPlayer?.() || playerEl
};
},
skipAd() {
if (!AdBlocker.config.enabled) {
return;
}
const now = Date.now();
if (now - AdBlocker.state.lastSkipAttempt < 300) {
return;
}
AdBlocker.state.lastSkipAttempt = now;
if (location.pathname.startsWith("/shorts/")) {
return;
}
const moviePlayer = $("#movie_player");
const isAdShowing = moviePlayer && (moviePlayer.classList.contains("ad-showing") || moviePlayer.classList.contains("ad-interrupting"));
if (isAdShowing) {
try {
const skipButtons = document.querySelectorAll(SKIP_SELECTOR);
for (const skipButton of skipButtons) {
const rect = skipButton.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
skipButton.click();
AdBlocker.state.retryCount = 0;
return;
}
}
const video = $(AdBlocker.selectors.video);
if (video) {
video.muted = !0;
if (video.duration && isFinite(video.duration) && video.duration > 0) {
try {
video.currentTime = Math.max(video.duration - .1, 0);
} catch (e) {
window.console.warn("[YouTube+] Ad seek error:", e);
}
}
}
const overlaySelectors = [ ".ytp-ad-overlay-close-button", ".ytp-ad-overlay-close-container button", ".ytp-ad-overlay-close-button button", ".ytp-ad-overlay-ad-info-button-container", 'button[id="dismiss-button"]' ];
for (const sel of overlaySelectors) {
const overlayClose = $(sel);
if (overlayClose) {
overlayClose.click();
break;
}
}
AdBlocker.state.retryCount = 0;
} catch (e) {
if (AdBlocker.state.retryCount < AdBlocker.config.maxRetries) {
AdBlocker.state.retryCount++;
setTimeout(AdBlocker.skipAd, 800);
}
}
} else {
AdBlocker.state.retryCount = 0;
}
},
dismissAdBlockerWarning() {
if (AdBlocker.config.enabled) {
try {
const enforcement = document.querySelector("ytd-enforcement-message-view-model");
if (enforcement) {
const btns = enforcement.querySelectorAll("button, tp-yt-paper-button, a.yt-spec-button-shape-next--outline");
for (const btn of btns) {
const btnText = (btn.textContent || "").toLowerCase().trim();
if (btnText.includes("allow") || btnText.includes("dismiss") || btnText.includes("разрешить") || btn.getAttribute("aria-label")?.includes("close")) {
btn.click();
return;
}
}
enforcement.remove();
return;
}
const dialogs = document.querySelectorAll("tp-yt-paper-dialog, ytd-popup-container tp-yt-paper-dialog, yt-dialog-container");
for (const dialog of dialogs) {
const text = (dialog.textContent || "").toLowerCase();
const isAdBlockWarning = text.includes("ad blocker") || text.includes("ad blockers") || text.includes("блокировщик") || text.includes("will be blocked") || text.includes("будет заблокирован") || text.includes("allow") && text.includes("ads") || text.includes("blocker") && text.includes("video");
if (!isAdBlockWarning) {
continue;
}
const dismissBtns = dialog.querySelectorAll('#dismiss-button button, .dismiss-button, button[id*="dismiss"], tp-yt-paper-button, yt-button-renderer button, a[href]');
for (const btn of dismissBtns) {
const btnText = (btn.textContent || "").toLowerCase();
if (btnText.includes("dismiss") || btnText.includes("allow") || btnText.includes("not using") || btnText.includes("report")) {
btn.click();
return;
}
}
dialog.style.display = "none";
dialog.remove();
return;
}
const overlays = document.querySelectorAll("tp-yt-iron-overlay-backdrop, .yt-dialog-overlay");
for (const overlay of overlays) {
"none" !== overlay.style.display && null !== overlay.offsetParent && (overlay.style.display = "none");
}
} catch (e) {}
}
},
addCss() {
if ($("#yt-ab-styles") || !AdBlocker.config.enabled) {
return;
}
const styles = `${AdBlocker.selectors.ads}{display:none!important;}`;
YouTubeUtils.StyleManager.add("yt-ab-styles", styles);
},
removeCss() {
YouTubeUtils.StyleManager.remove("yt-ab-styles");
},
removeElements() {
if (!AdBlocker.config.enabled || AdBlocker.state.isYouTubeMusic) {
return;
}
const remove = () => {
try {
const adElements = document.querySelectorAll(AdBlocker.selectors.elements);
adElements.forEach(el => {
try {
el.remove();
} catch (e) {}
});
} catch (e) {}
const elements = document.querySelectorAll(AdBlocker.selectors.removal);
elements.forEach(el => {
try {
for (const w of AdBlocker.wrappers) {
const wrap = el.closest(w);
if (wrap) {
wrap.remove();
return;
}
}
const reel = el.closest("ytd-reel-video-renderer");
if (reel) {
reel.remove();
return;
}
const container = el.closest("ytd-ad-slot-renderer") || el.closest(".ad-container") || el;
container && container.remove && container.remove();
} catch (e) {
AdBlocker.config.enableLogging && window.console.warn("[AdBlocker] removeElements error", e);
}
});
};
const ric = window.requestIdleCallback;
"function" == typeof ric ? ric(remove, {
timeout: 100
}) : setTimeout(remove, 0);
},
addSettingsUI() {
const section = $('.ytp-plus-settings-section[data-section="basic"]');
if (section && !section.querySelector(".ab-settings")) {
try {
const item = document.createElement("div");
item.className = "ytp-plus-settings-item ab-settings";
((container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
})(item, `\n          <div>\n            <label class="ytp-plus-settings-item-label">${t("adBlocker")}</label>\n            <div class="ytp-plus-settings-item-description">${t("adBlockerDescription")}</div>\n          </div>\n          <input type="checkbox" class="ytp-plus-settings-checkbox" ${AdBlocker.config.enabled ? "checked" : ""}>\n        `);
section.appendChild(item);
const checkbox = item.querySelector("input");
if (!checkbox) {
return;
}
checkbox.addEventListener("change", e => {
const target = e.target;
AdBlocker.config.enabled = target.checked;
AdBlocker.settings.save();
AdBlocker.config.enabled ? AdBlocker.addCss() : AdBlocker.removeCss();
});
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
YouTubeUtils.logError("AdBlocker", "Failed to add settings UI", err);
}
}
},
init() {
if (AdBlocker.state.initialized) {
return;
}
AdBlocker.state.initialized = !0;
AdBlocker.settings.load();
if (AdBlocker.config.enabled) {
AdBlocker.addCss();
AdBlocker.removeElements();
}
let _cachedMoviePlayer = null;
let _mpCacheTs = 0;
const isAdActive = () => {
const mp = (() => {
const now = Date.now();
if (!_cachedMoviePlayer || now - _mpCacheTs > 5e3) {
_cachedMoviePlayer = $("#movie_player");
_mpCacheTs = now;
}
return _cachedMoviePlayer;
})();
return mp && (mp.classList.contains("ad-showing") || mp.classList.contains("ad-interrupting"));
};
const combinedAdCheck = () => {
if (AdBlocker.config.enabled && isAdActive()) {
AdBlocker.skipAd();
AdBlocker.dismissAdBlockerWarning();
}
};
try {
const attachAdObserver = () => {
const coordinator = window.YouTubeMutationCoordinator;
if (!coordinator?.watchTarget) {
return;
}
const moviePlayer = $("#movie_player");
if (moviePlayer) {
AdBlocker.state.moviePlayerSubId && coordinator.unwatch(AdBlocker.state.moviePlayerSubId);
AdBlocker.state.moviePlayerSubId = "adblocker::moviePlayerClass";
coordinator.watchTarget(AdBlocker.state.moviePlayerSubId, moviePlayer, () => {
if (AdBlocker.config.enabled && isAdActive()) {
AdBlocker.skipAd();
AdBlocker.dismissAdBlockerWarning();
}
}, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "class" ]
});
YouTubeUtils.cleanupManager?.register && YouTubeUtils.cleanupManager.register(() => {
if (AdBlocker.state.moviePlayerSubId) {
coordinator.unwatch(AdBlocker.state.moviePlayerSubId);
AdBlocker.state.moviePlayerSubId = null;
}
});
} else {
setTimeout_(attachAdObserver, 1500);
}
};
attachAdObserver();
} catch (e) {
window.console.warn("[YouTube+] Ad observer setup error:", e);
}
try {
const handleVideoPlay = () => {
AdBlocker.config.enabled && setTimeout(combinedAdCheck, 100);
};
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "playing", handleVideoPlay, {
capture: !0,
passive: !0
}) : document.addEventListener("playing", handleVideoPlay, {
capture: !0,
passive: !0
});
} catch (e) {
window.console.warn("[YouTube+] Ad play listener error:", e);
}
const handleNavigation = () => {
AdBlocker.state.isYouTubeShorts = location.pathname.startsWith("/shorts/");
AdBlocker.cache.lastCacheTime = 0;
AdBlocker.config.enabled && AdBlocker.removeElements();
};
const navHandler = () => setTimeout(handleNavigation, 50);
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(window, "ytp-history-navigate", navHandler) : window.addEventListener("ytp-history-navigate", navHandler);
const settingsHandler = () => {
setTimeout(AdBlocker.addSettingsUI, 50);
};
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", settingsHandler) : document.addEventListener("youtube-plus-settings-modal-opened", settingsHandler);
try {
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
AdBlocker.state.adSlotSubId && coordinator.unsubscribe(AdBlocker.state.adSlotSubId);
AdBlocker.state.adSlotSubId = "adblocker::adSlots";
coordinator.subscribeRoot(AdBlocker.state.adSlotSubId, () => {
AdBlocker.removeElements();
}, {
selector: "ytd-ad-slot-renderer, ytd-merch-shelf-renderer, #player-ads, ad-slot-renderer",
childList: !0,
attributes: !1,
subtree: !0
});
YouTubeUtils.cleanupManager?.register && YouTubeUtils.cleanupManager.register(() => {
if (AdBlocker.state.adSlotSubId) {
coordinator.unsubscribe(AdBlocker.state.adSlotSubId);
AdBlocker.state.adSlotSubId = null;
}
});
}
} catch (e) {
AdBlocker.config.enableLogging && window.console.warn("[AdBlocker] Failed to create adSlotObserver", e);
}
YouTubeUtils.cleanupManager.registerListener(document, "click", e => {
const target = e.target;
"basic" === target.dataset?.section && setTimeout(AdBlocker.addSettingsUI, 25);
}, {
passive: !0,
capture: !0
});
if (AdBlocker.config.enabled) {
setTimeout(AdBlocker.skipAd, 200);
setTimeout(AdBlocker.dismissAdBlockerWarning, 500);
}
}
};
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", AdBlocker.init, {
once: !0
}) : AdBlocker.init();
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
const renderTemplateClone = (container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
};
const t = window.YouTubeUtils.t;
const logger = window.YouTubeUtils?.logger || console;
const qs = window.YouTubeUtils?.$ || ((selector, root) => (root || document).querySelector(selector));
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
const pipSettings = {
enabled: !0,
shortcut: {
key: "P",
shiftKey: !0,
altKey: !1,
ctrlKey: !1
},
storageKey: "youtube_pip_settings"
};
const getPageGlobal = () => {
try {
if ("undefined" != typeof unsafeWindow) {
const unwrapped = unsafeWindow?.wrappedJSObject;
return unwrapped && "object" == typeof unwrapped ? unwrapped : unsafeWindow;
}
} catch (e) {}
return window;
};
const createTrustedInlineScript = window.YouTubeSafeDOM?.createTrustedScript || (value => {
const normalized = "string" == typeof value ? value : String(value ?? "");
try {
const sharedPolicy = window.YouTubeTrustedTypes && "function" == typeof window.YouTubeTrustedTypes.getPolicy && window.YouTubeTrustedTypes.getPolicy() || window.YouTubeTrustedTypes?.policy || null;
if (sharedPolicy && "function" == typeof sharedPolicy.createScript) {
return sharedPolicy.createScript(normalized);
}
} catch (e) {}
return normalized;
});
let _cachedVideoRef = null;
let _cachedVideoTs = 0;
const getVideoElement = () => {
try {
const now = Date.now();
if (_cachedVideoRef && now - _cachedVideoTs < 2e3) {
const cached = _cachedVideoRef.deref?.();
if (cached && cached.isConnected) {
return cached;
}
_cachedVideoRef = null;
}
const pageGlobal = getPageGlobal();
const candidate = pageGlobal?.document?.querySelector?.("video.html5-main-video, #movie_player video, video") || "function" == typeof YouTubeUtils?.querySelector && YouTubeUtils.querySelector("video") || qs("video");
if (candidate && candidate.tagName && "video" === candidate.tagName.toLowerCase()) {
const video = candidate;
try {
_cachedVideoRef = new WeakRef(video);
_cachedVideoTs = now;
} catch (e) {}
return video;
}
return null;
} catch (error) {
window.console.error("[PiP] Error getting video element:", error);
return null;
}
};
const requestPictureInPictureCompat = async video => {
if ("function" == typeof video.requestPictureInPicture) {
return video.requestPictureInPicture();
}
const pageGlobal = getPageGlobal();
const pageVideo = pageGlobal?.document?.querySelector?.("video.html5-main-video, #movie_player video, video");
const proto = pageGlobal?.HTMLVideoElement?.prototype;
if (pageVideo && "function" == typeof pageVideo.requestPictureInPicture) {
return pageVideo.requestPictureInPicture();
}
if (pageVideo && proto && "function" == typeof proto.requestPictureInPicture) {
return proto.requestPictureInPicture.call(pageVideo);
}
const player = pageGlobal?.document?.getElementById?.("movie_player") || pageGlobal?.document?.querySelector?.("#movie_player");
if (player && "function" == typeof player.togglePictureInPicture) {
return player.togglePictureInPicture();
}
if (player && "function" == typeof player.requestPictureInPicture) {
return player.requestPictureInPicture();
}
throw new Error("requestPictureInPicture is not available on video element");
};
const ensureFirefoxPagePipBridge = () => {
const pageGlobal = getPageGlobal();
if (!pageGlobal || pageGlobal.__ytplusFirefoxPipBridgeInstalled) {
return !0;
}
const bridgeScript = "(() => {\n  if (window['__ytplusFirefoxPipBridgeInstalled']) return;\n  window['__ytplusFirefoxPipBridgeInstalled'] = true;\n\n  const selectVideo = () =>\n    document['querySelector']('video.html5-main-video, #movie_player video, video');\n\n  const clickNativePipButton = () => {\n    const selectors = [\n      'button.ytp-pip-button',\n      '.ytp-right-controls .ytp-pip-button',\n      '.ytp-right-controls button[aria-keyshortcuts=\"i\"]'\n    ];\n    for (const selector of selectors) {\n      const button = document['querySelector'](selector);\n      if (!(button instanceof HTMLElement)) continue;\n      const disabled =\n        button.getAttribute('aria-disabled') === 'true' ||\n        button.hasAttribute('disabled') ||\n        button.classList.contains('ytp-button-disabled');\n      if (disabled) continue;\n      button.click();\n      return true;\n    }\n    return false;\n  };\n\n  const safeTargetOrigin =\n    typeof location !== 'undefined' && typeof location.origin === 'string' && location.origin\n      ? location.origin\n      : '*';\n\n  window.addEventListener('message', event => {\n    if (event.source !== window) return;\n    // Allow same-origin page messages and Firefox/userscript sandbox-origin messages.\n    // In some Firefox userscript contexts event.origin may be \"null\".\n    const msgOrigin = String(event.origin || '');\n    if (msgOrigin && msgOrigin !== 'null' && msgOrigin !== location.origin) return;\n    if (event.data?.type !== 'ytp-pip-request' || typeof event.data?.id !== 'string') return;\n    if (event.data.id.length > 80) return;\n\n    const respond = success => {\n      window.postMessage({ type: 'ytp-pip-response', id: event.data.id, success }, safeTargetOrigin);\n    };\n\n    Promise.resolve()\n      .then(async () => {\n        const video = selectVideo();\n        if (document.pictureInPictureElement && typeof document.exitPictureInPicture === 'function') {\n          await document.exitPictureInPicture();\n          return true;\n        }\n        if (video && typeof video.requestPictureInPicture === 'function') {\n          await video.requestPictureInPicture();\n          return true;\n        }\n        return clickNativePipButton();\n      })\n      .then(success => respond(Boolean(success)))\n      .catch(() => respond(false));\n  }, true);\n})();\n//# sourceURL=debug://youtube-plus/pip-firefox-bridge.js";
try {
const gmAddElement = globalThis.GM_addElement;
if ("function" == typeof gmAddElement) {
gmAddElement("script", {
textContent: createTrustedInlineScript(bridgeScript)
});
return !0;
}
} catch (e) {}
try {
const nonceSource = qs("script[nonce]") || qs("[nonce]") || document.documentElement;
const nonce = nonceSource?.nonce || nonceSource?.getAttribute?.("nonce") || document.documentElement?.getAttribute?.("nonce") || "";
if (nonce) {
const script = document.createElement("script");
script.setAttribute("nonce", nonce);
script.textContent = createTrustedInlineScript(bridgeScript);
(document.head || document.documentElement).appendChild(script);
script.remove();
if (pageGlobal.__ytplusFirefoxPipBridgeInstalled) {
return !0;
}
}
} catch (e) {}
window.console.warn("[PiP] Firefox PiP bridge could not be installed (CSP). Using button fallback.");
return !1;
};
const syncPipShortcutToPageGlobal = () => {
try {
const pageGlobal = getPageGlobal();
pageGlobal && (pageGlobal.__ytplusPipShortcut = {
key: pipSettings.shortcut.key,
shiftKey: pipSettings.shortcut.shiftKey,
altKey: pipSettings.shortcut.altKey,
ctrlKey: pipSettings.shortcut.ctrlKey,
enabled: pipSettings.enabled
});
} catch (e) {}
};
const requestFirefoxPiPViaBridge = () => new Promise(resolve => {
try {
if (!ensureFirefoxPagePipBridge()) {
resolve(!1);
return;
}
const requestId = `ytp-pip-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
let settled = !1;
const finish = success => {
if (!settled) {
settled = !0;
window.removeEventListener("message", onResponse, !0);
resolve(Boolean(success));
}
};
const onResponse = evt => {
if (evt.source !== window) {
return;
}
const msgOrigin = String(evt.origin || "");
msgOrigin && "null" !== msgOrigin && msgOrigin !== location.origin || "ytp-pip-response" === evt.data?.type && evt.data?.id === requestId && finish(Boolean(evt.data?.success));
};
window.addEventListener("message", onResponse, !0);
const targetOrigin = "undefined" != typeof location && "string" == typeof location.origin && location.origin ? location.origin : "*";
window.postMessage({
type: "ytp-pip-request",
id: requestId
}, targetOrigin);
setTimeout_(function() {
finish(!1);
}, 1200);
} catch (e) {
resolve(!1);
}
});
const clickNativeYouTubePipButton = () => {
const selectors = [ "button.ytp-pip-button", ".ytp-right-controls .ytp-pip-button", '.ytp-right-controls button[aria-keyshortcuts="i"]' ];
const contexts = [];
try {
contexts.push(document);
"undefined" != typeof unsafeWindow && unsafeWindow?.document && contexts.push(unsafeWindow.document);
} catch (e) {}
(() => {
const player = byId("movie_player");
if (!(player instanceof HTMLElement)) {
return;
}
const rect = player.getBoundingClientRect();
const clientX = Math.round(rect.left + rect.width - 32);
const clientY = Math.round(rect.top + rect.height - 32);
[ "mousemove", "mouseover", "mouseenter" ].forEach(type => {
try {
player.dispatchEvent(new MouseEvent(type, {
bubbles: !0,
cancelable: !0,
view: window,
clientX,
clientY
}));
} catch (e) {}
});
})();
for (const ctx of contexts) {
for (const sel of selectors) {
const btn = ctx.querySelector(sel);
if (btn instanceof HTMLElement) {
const disabled = "true" === btn.getAttribute("aria-disabled") || btn.hasAttribute("disabled") || btn.classList.contains("ytp-button-disabled");
if (disabled) {
continue;
}
btn.click();
return !0;
}
}
}
return !1;
};
const setSessionActive = isActive => {
try {
isActive ? sessionStorage.setItem("youtube_plus_pip_session", "true") : sessionStorage.removeItem("youtube_plus_pip_session");
} catch (e) {}
};
const loadSettings = () => {
try {
const saved = localStorage.getItem(pipSettings.storageKey);
if (!saved) {
return;
}
const parsed = JSON.parse(saved);
if ("object" != typeof parsed || null === parsed) {
window.console.warn("[PiP] Invalid settings format");
return;
}
"boolean" == typeof parsed.enabled && (pipSettings.enabled = parsed.enabled);
if (parsed.shortcut && "object" == typeof parsed.shortcut) {
"string" == typeof parsed.shortcut.key && parsed.shortcut.key.length > 0 && (pipSettings.shortcut.key = parsed.shortcut.key);
"boolean" == typeof parsed.shortcut.shiftKey && (pipSettings.shortcut.shiftKey = parsed.shortcut.shiftKey);
"boolean" == typeof parsed.shortcut.altKey && (pipSettings.shortcut.altKey = parsed.shortcut.altKey);
"boolean" == typeof parsed.shortcut.ctrlKey && (pipSettings.shortcut.ctrlKey = parsed.shortcut.ctrlKey);
}
} catch (e) {
window.console.error("[PiP] Error loading settings:", e);
}
};
const saveSettings = () => {
try {
const settingsToSave = {
enabled: pipSettings.enabled,
shortcut: pipSettings.shortcut
};
localStorage.setItem(pipSettings.storageKey, JSON.stringify(settingsToSave));
} catch (e) {
window.console.error("[PiP] Error saving settings:", e);
}
syncPipShortcutToPageGlobal();
};
const getCurrentPiPElement = () => {
const current = document.pictureInPictureElement;
if (current && "object" == typeof current && "tagName" in current) {
const tag = current.tagName;
if ("string" == typeof tag && "video" === tag.toLowerCase()) {
return current;
}
}
if (/firefox/i.test(navigator.userAgent || "")) {
try {
const pageGlobal = getPageGlobal();
const doc = pageGlobal?.document || document;
const pipVideo = doc.querySelector("video[__pip]") || doc.querySelector("video._pip");
if (pipVideo && "video" === pipVideo.tagName?.toLowerCase()) {
return pipVideo;
}
} catch {}
}
return null;
};
const togglePictureInPicture = async video => {
if (pipSettings.enabled && video) {
try {
const isFirefox = /firefox/i.test(navigator.userAgent || "");
if (isFirefox) {
ensureFirefoxPagePipBridge();
if ("function" == typeof document.exitPictureInPicture && document.pictureInPictureElement) {
try {
await document.exitPictureInPicture();
setSessionActive(!1);
return;
} catch (e) {}
}
const toggledByButton = clickNativeYouTubePipButton();
if (toggledByButton) {
setSessionActive(!0);
return;
}
try {
const pageGlobal = getPageGlobal();
const pageVideo = pageGlobal?.document?.querySelector?.("video.html5-main-video, #movie_player video, video");
if (pageVideo && "function" == typeof pageVideo.requestPictureInPicture) {
await pageVideo.requestPictureInPicture();
setSessionActive(!0);
return;
}
const proto = pageGlobal?.HTMLVideoElement?.prototype;
if (pageVideo && proto && "function" == typeof proto.requestPictureInPicture) {
await proto.requestPictureInPicture.call(pageVideo);
setSessionActive(!0);
return;
}
} catch (e) {}
try {
const bridgeSuccess = await requestFirefoxPiPViaBridge();
if (bridgeSuccess) {
setSessionActive(!0);
return;
}
const pageGlobal2 = getPageGlobal();
if (pageGlobal2) {
const pageVid = pageGlobal2.document?.querySelector?.("video");
if (pageVid && "function" == typeof pageVid.requestPictureInPicture) {
pageVid.requestPictureInPicture().catch(() => {});
setSessionActive(!0);
}
}
} catch (e) {
logger.warn("[PiP] All Firefox PiP fallbacks exhausted");
}
return;
}
const currentPiP = getCurrentPiPElement();
if (currentPiP && currentPiP !== video) {
"function" == typeof document.exitPictureInPicture && await document.exitPictureInPicture();
setSessionActive(!1);
}
if (getCurrentPiPElement() === video) {
"function" == typeof document.exitPictureInPicture && await document.exitPictureInPicture();
setSessionActive(!1);
return;
}
const hadDisablePiP = !0 === video.disablePictureInPicture;
if (hadDisablePiP) {
try {
video.disablePictureInPicture = !1;
} catch (e) {}
}
try {
await requestPictureInPictureCompat(video);
} catch (e) {
await (video => new Promise(resolve => {
if (!video || video.readyState >= 1) {
resolve();
return;
}
let done = !1;
const finish = () => {
if (!done) {
done = !0;
video.removeEventListener("loadedmetadata", finish);
resolve();
}
};
video.addEventListener("loadedmetadata", finish, {
once: !0
});
setTimeout(finish, 700);
}))(video);
try {
await requestPictureInPictureCompat(video);
} catch (e) {
const toggledByButton = clickNativeYouTubePipButton();
if (!toggledByButton) {
throw new Error("requestPictureInPicture is not available on video element");
}
}
}
if (hadDisablePiP) {
try {
video.disablePictureInPicture = !0;
} catch (e) {}
}
setSessionActive(!0);
} catch (error) {
window.console.error("[YouTube+][PiP] Failed to toggle Picture-in-Picture:", error);
}
}
};
const addPipSettingsToModal = () => {
const advancedSection = qs('.ytp-plus-settings-section[data-section="advanced"]');
if (!advancedSection || advancedSection.querySelector(".pip-settings-item")) {
return !1;
}
const getSubmenuExpanded = () => {
try {
const raw = localStorage.getItem("ytp-plus-submenu-states");
if (!raw) {
return null;
}
const parsed = JSON.parse(raw);
if (parsed && "boolean" == typeof parsed.pip) {
return parsed.pip;
}
} catch (e) {}
return null;
};
const storedExpanded = getSubmenuExpanded();
const initialExpanded = "boolean" != typeof storedExpanded || storedExpanded;
if (!byId("pip-styles")) {
const styles = "\n          .pip-shortcut-editor { display: flex; align-items: center; gap: 8px; }\n          .pip-shortcut-editor select, #pip-key {background: rgba(34, 34, 34, var(--yt-header-bg-opacity)); color: var(--yt-spec-text-primary); border: 1px solid var(--yt-spec-10-percent-layer); border-radius: var(--yt-radius-sm); padding: 4px;}\n        ";
YouTubeUtils.StyleManager.add("pip-styles", styles);
}
const enableItem = document.createElement("div");
enableItem.className = "ytp-plus-settings-item pip-settings-item ytp-plus-settings-item--with-submenu";
renderTemplateClone(enableItem, `\n        <div>\n          <label class="ytp-plus-settings-item-label" for="pip-enable-checkbox">${t("pipTitle")}</label>\n          <div class="ytp-plus-settings-item-description">${t("pipDescription")}</div>\n        </div>\n        <div class="ytp-plus-settings-item-actions">\n          <button\n            type="button"\n            class="ytp-plus-submenu-toggle"\n            data-submenu="pip"\n            aria-label="Toggle PiP submenu"\n            aria-expanded="${initialExpanded ? "true" : "false"}"\n            ${pipSettings.enabled ? "" : "disabled"}\n            style="display:${pipSettings.enabled ? "inline-flex" : "none"};"\n          >\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n              <polyline points="6 9 12 15 18 9"></polyline>\n            </svg>\n          </button>\n          <input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enablePiP" id="pip-enable-checkbox" ${pipSettings.enabled ? "checked" : ""}>\n        </div>\n      `);
advancedSection.appendChild(enableItem);
const submenuWrap = document.createElement("div");
submenuWrap.className = "pip-submenu";
submenuWrap.dataset.submenu = "pip";
submenuWrap.style.display = pipSettings.enabled && initialExpanded ? "block" : "none";
submenuWrap.style.marginLeft = "12px";
submenuWrap.style.marginBottom = "12px";
const submenuCard = document.createElement("div");
submenuCard.className = "glass-card";
submenuCard.style.display = "flex";
submenuCard.style.flexDirection = "column";
submenuCard.style.gap = "8px";
const shortcutItem = document.createElement("div");
shortcutItem.className = "ytp-plus-settings-item pip-shortcut-item";
shortcutItem.style.display = "flex";
const {ctrlKey, altKey, shiftKey} = pipSettings.shortcut;
const modifierValue = ctrlKey && altKey && shiftKey ? "ctrl+alt+shift" : ctrlKey && altKey ? "ctrl+alt" : ctrlKey && shiftKey ? "ctrl+shift" : altKey && shiftKey ? "alt+shift" : ctrlKey ? "ctrl" : altKey ? "alt" : shiftKey ? "shift" : "none";
renderTemplateClone(shortcutItem, `\n        <div>\n          <label class="ytp-plus-settings-item-label">${t("pipShortcutTitle")}</label>\n          <div class="ytp-plus-settings-item-description">${t("pipShortcutDescription")}</div>\n        </div>\n        <div class="pip-shortcut-editor">\n          \x3c!-- hidden native select kept for compatibility --\x3e\n          <select id="pip-modifier-combo" style="display:none;">\n            ${[ "none", "ctrl", "alt", "shift", "ctrl+alt", "ctrl+shift", "alt+shift", "ctrl+alt+shift" ].map(v => `<option value="${v}" ${v === modifierValue ? "selected" : ""}>${"none" === v ? t("none") : v.replace(/\+/g, "+").split("+").map(k => t(k.toLowerCase())).join("+").split("+").map(k => k.charAt(0).toUpperCase() + k.slice(1)).join("+")}</option>`).join("")}\n          </select>\n\n          <div class="glass-dropdown" id="pip-modifier-dropdown" tabindex="0" role="listbox" aria-expanded="false">\n            <button class="glass-dropdown__toggle" type="button" aria-haspopup="listbox">\n              <span class="glass-dropdown__label">${"none" === modifierValue ? t("none") : modifierValue.replace(/\+/g, "+").split("+").map(k => t(k.toLowerCase())).map(k => k.charAt(0).toUpperCase() + k.slice(1)).join("+")}</span>\n              <svg class="glass-dropdown__chev" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>\n            </button>\n            <ul class="glass-dropdown__list" role="presentation">\n              ${[ "none", "ctrl", "alt", "shift", "ctrl+alt", "ctrl+shift", "alt+shift", "ctrl+alt+shift" ].map(v => {
const label = "none" === v ? t("none") : v.replace(/\+/g, "+").split("+").map(k => t(k.toLowerCase())).map(k => k.charAt(0).toUpperCase() + k.slice(1)).join("+");
const sel = v === modifierValue ? ' aria-selected="true"' : "";
return `<li class="glass-dropdown__item" data-value="${v}" role="option"${sel}>${label}</li>`;
}).join("")}\n            </ul>\n          </div>\n\n          <span>+</span>\n          <input type="text" id="pip-key" value="${pipSettings.shortcut.key}" maxlength="1" style="width: 30px; text-align: center;">\n        </div>\n      `);
submenuCard.appendChild(shortcutItem);
submenuWrap.appendChild(submenuCard);
advancedSection.appendChild(submenuWrap);
setTimeout(() => {
const hidden = byId("pip-modifier-combo");
const dropdown = byId("pip-modifier-dropdown");
if (!(hidden instanceof HTMLSelectElement && dropdown instanceof HTMLElement)) {
return;
}
const toggle = dropdown.querySelector(".glass-dropdown__toggle");
const list = dropdown.querySelector(".glass-dropdown__list");
const label = dropdown.querySelector(".glass-dropdown__label");
if (!(toggle instanceof HTMLElement && list instanceof HTMLElement && label instanceof HTMLElement)) {
return;
}
let items = Array.from(list.querySelectorAll(".glass-dropdown__item"));
let idx = items.findIndex(it => "true" === it.getAttribute("aria-selected"));
idx < 0 && (idx = 0);
const openList = () => {
dropdown.setAttribute("aria-expanded", "true");
list.style.display = "block";
items = Array.from(list.querySelectorAll(".glass-dropdown__item"));
};
const closeList = () => {
dropdown.setAttribute("aria-expanded", "false");
list.style.display = "none";
};
toggle.addEventListener("click", () => {
const expanded = "true" === dropdown.getAttribute("aria-expanded");
expanded ? closeList() : openList();
});
const outsideClickHandler = e => {
const target = e.target instanceof Node ? e.target : null;
target && dropdown.contains(target) || closeList();
};
window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(document, "click", outsideClickHandler) : document.addEventListener("click", outsideClickHandler);
dropdown.addEventListener("keydown", e => {
const expanded = "true" === dropdown.getAttribute("aria-expanded");
if ("ArrowDown" === e.key) {
e.preventDefault();
expanded || openList();
idx = Math.min(idx + 1, items.length - 1);
items.forEach(it => it.removeAttribute("aria-selected"));
items[idx].setAttribute("aria-selected", "true");
items[idx].scrollIntoView({
block: "nearest"
});
} else if ("ArrowUp" === e.key) {
e.preventDefault();
expanded || openList();
idx = Math.max(idx - 1, 0);
items.forEach(it => it.removeAttribute("aria-selected"));
items[idx].setAttribute("aria-selected", "true");
items[idx].scrollIntoView({
block: "nearest"
});
} else if ("Enter" === e.key || " " === e.key) {
e.preventDefault();
if (!expanded) {
openList();
return;
}
const it = items[idx];
if (it) {
hidden.value = it.dataset.value;
hidden.dispatchEvent(new Event("change", {
bubbles: !0
}));
label.textContent = it.textContent;
closeList();
}
} else {
"Escape" === e.key && closeList();
}
});
list.addEventListener("click", e => {
const target = e.target instanceof HTMLElement ? e.target : null;
const it = target ? target.closest(".glass-dropdown__item") : null;
if (!it) {
return;
}
const val = it.dataset.value;
hidden.value = val;
list.querySelectorAll(".glass-dropdown__item").forEach(li => li.removeAttribute("aria-selected"));
it.setAttribute("aria-selected", "true");
label.textContent = it.textContent;
hidden.dispatchEvent(new Event("change", {
bubbles: !0
}));
closeList();
});
}, 0);
const enableCheckbox = byId("pip-enable-checkbox");
if (!(enableCheckbox instanceof HTMLInputElement)) {
return !0;
}
enableCheckbox.addEventListener("change", e => {
const target = e.target;
pipSettings.enabled = target.checked;
const submenuToggle = enableItem.querySelector('.ytp-plus-submenu-toggle[data-submenu="pip"]');
if (submenuToggle instanceof HTMLElement) {
if (pipSettings.enabled) {
const stored = getSubmenuExpanded();
const nextExpanded = "boolean" != typeof stored || stored;
submenuToggle.removeAttribute("disabled");
submenuToggle.style.display = "inline-flex";
submenuToggle.setAttribute("aria-expanded", nextExpanded ? "true" : "false");
submenuWrap.style.display = nextExpanded ? "block" : "none";
} else {
submenuToggle.setAttribute("disabled", "");
submenuToggle.style.display = "none";
submenuWrap.style.display = "none";
}
}
saveSettings();
});
const modifierCombo = byId("pip-modifier-combo");
if (!(modifierCombo instanceof HTMLSelectElement)) {
return !0;
}
modifierCombo.addEventListener("change", e => {
const target = e.target;
const value = target.value;
pipSettings.shortcut.ctrlKey = value.includes("ctrl");
pipSettings.shortcut.altKey = value.includes("alt");
pipSettings.shortcut.shiftKey = value.includes("shift");
saveSettings();
});
const pipKeyInput = byId("pip-key");
if (!(pipKeyInput instanceof HTMLInputElement)) {
return !0;
}
pipKeyInput.addEventListener("input", e => {
const target = e.target;
if (target.value) {
pipSettings.shortcut.key = target.value.toUpperCase();
saveSettings();
}
});
pipKeyInput.addEventListener("keydown", e => e.stopPropagation());
return !0;
};
let pipRuntimeStarted = !1;
const startPipRuntime = () => {
if (pipRuntimeStarted) {
return;
}
pipRuntimeStarted = !0;
loadSettings();
let lastHotkeyTriggerTs = 0;
const pipKeydownHandler = e => {
const evt = e;
const isKeyboardLike = evt && "object" == typeof evt && "string" == typeof evt.key && "string" == typeof evt.code;
if (!isKeyboardLike) {
return;
}
if (!pipSettings.enabled) {
return;
}
if (evt.repeat || evt.isComposing) {
return;
}
if (evt.metaKey) {
return;
}
if ((target => {
const node = target;
if (!node || "object" != typeof node) {
return !1;
}
const tag = String(node.tagName || "").toUpperCase();
return !0 === node.isContentEditable || "INPUT" === tag || "TEXTAREA" === tag || "SELECT" === tag || "function" == typeof node.closest && !!node.closest('input, textarea, select, [contenteditable="true"]');
})(evt.target)) {
return;
}
const {shiftKey, altKey, ctrlKey, key} = pipSettings.shortcut;
const expectedCode = (key => {
const normalized = String(key || "").trim().toUpperCase();
if (!normalized) {
return "";
}
const symbolCodeMap = {
"`": "Backquote",
"~": "Backquote",
"-": "Minus",
_: "Minus",
"=": "Equal",
"+": "Equal",
"[": "BracketLeft",
"{": "BracketLeft",
"]": "BracketRight",
"}": "BracketRight",
"\\": "Backslash",
"|": "Backslash",
";": "Semicolon",
":": "Semicolon",
"'": "Quote",
'"': "Quote",
",": "Comma",
"<": "Comma",
".": "Period",
">": "Period",
"/": "Slash",
"?": "Slash"
};
return Object.prototype.hasOwnProperty.call(symbolCodeMap, normalized) ? symbolCodeMap[normalized] : 1 === normalized.length && /[A-Z]/.test(normalized) ? `Key${normalized}` : 1 === normalized.length && /[0-9]/.test(normalized) ? `Digit${normalized}` : normalized;
})(key);
const eventCode = String(evt.code || "");
const eventKey = String(evt.key || "").toUpperCase();
const keyMatches = eventKey === key.toUpperCase() || expectedCode && eventCode === expectedCode;
const modifiersMatch = evt.shiftKey === shiftKey && evt.altKey === altKey && evt.ctrlKey === ctrlKey;
if (modifiersMatch && keyMatches) {
const now = Date.now();
if (now - lastHotkeyTriggerTs < 250) {
e.preventDefault();
return;
}
lastHotkeyTriggerTs = now;
const isFirefox = /firefox/i.test(navigator.userAgent || "");
if (isFirefox) {
const pageGlobal = getPageGlobal();
const bridgeInstalled = Boolean(pageGlobal?.__ytplusFirefoxPipKeydownBridge);
const pageHandledTs = Number(pageGlobal?.__ytplusPipPageHandled || 0);
if (now - pageHandledTs < 300) {
return;
}
const handled = ((e, allowBrowserFallback) => {
const currentPiP = getCurrentPiPElement();
if (currentPiP && "function" == typeof document.exitPictureInPicture) {
document.exitPictureInPicture();
setSessionActive(!1);
return !0;
}
const video = getVideoElement();
if (!video) {
if (clickNativeYouTubePipButton()) {
setSessionActive(!0);
e.preventDefault();
e.stopPropagation();
"function" == typeof e.stopImmediatePropagation && e.stopImmediatePropagation();
return !0;
}
requestFirefoxPiPViaBridge().then(success => {
success && setSessionActive(!0);
});
if (!allowBrowserFallback) {
e.preventDefault();
e.stopPropagation();
"function" == typeof e.stopImmediatePropagation && e.stopImmediatePropagation();
return !0;
}
return !1;
}
try {
!0 === video.disablePictureInPicture && (video.disablePictureInPicture = !1);
} catch (e) {}
let handled = !1;
const toggledByButtonNow = clickNativeYouTubePipButton();
if (toggledByButtonNow) {
setSessionActive(!0);
handled = !0;
}
if (!handled) {
requestFirefoxPiPViaBridge().then(success => {
success ? setSessionActive(!0) : requestPictureInPictureCompat(video).then(() => setSessionActive(!0)).catch(() => {
clickNativeYouTubePipButton() && setSessionActive(!0);
});
});
handled = !allowBrowserFallback;
}
handled || document.pictureInPictureElement || setTimeout(() => {
if (!document.pictureInPictureElement) {
const toggledByButton = clickNativeYouTubePipButton();
if (toggledByButton) {
setSessionActive(!0);
return;
}
requestFirefoxPiPViaBridge().then(success => {
success && setSessionActive(!0);
});
}
}, 70);
if (!handled && !allowBrowserFallback) {
togglePictureInPicture(video);
handled = !0;
}
if (!handled) {
return !1;
}
e.preventDefault();
e.stopPropagation();
"function" == typeof e.stopImmediatePropagation && e.stopImmediatePropagation();
return !0;
})(evt, !bridgeInstalled && (() => {
const s = pipSettings.shortcut;
const normalizedKey = String(s.key || "").trim();
return !0 === s.ctrlKey && !0 === s.shiftKey && !1 === s.altKey && "]" === normalizedKey;
})());
if (!handled) {
return;
}
} else {
const video = getVideoElement();
video ? togglePictureInPicture(video) : clickNativeYouTubePipButton() || window.console.warn("[PiP] Picture-in-Picture API is unavailable in this browser/context");
}
isFirefox || document.pictureInPictureElement || getVideoElement() || window.console.warn("[PiP] Picture-in-Picture API is unavailable in this browser/context");
e.stopPropagation();
"function" == typeof e.stopImmediatePropagation && e.stopImmediatePropagation();
e.preventDefault();
}
};
YouTubeUtils.cleanupManager.registerListener(document, "keydown", pipKeydownHandler, {
capture: !0
});
YouTubeUtils.cleanupManager.registerListener(window, "keydown", pipKeydownHandler, {
capture: !0
});
/firefox/i.test(navigator.userAgent || "") && (() => {
const pageGlobal = getPageGlobal();
if (!pageGlobal) {
return !1;
}
if (pageGlobal.__ytplusFirefoxPipKeydownBridge) {
return !0;
}
syncPipShortcutToPageGlobal();
const bridgeFlag = "__ytplusFirefoxPipKeydownBridge";
const bridgeScript = `(() => {\n  if (window['${bridgeFlag}']) return;\n  window['${bridgeFlag}'] = true;\n\n  const selectVideo = () =>\n      document['querySelector']('video.html5-main-video, #movie_player video, video');\n\n  const clickPipBtn = () => {\n    const sels = ['button.ytp-pip-button', '.ytp-right-controls .ytp-pip-button',\n                  '.ytp-right-controls button[aria-keyshortcuts="i"]'];\n    for (const sel of sels) {\n      const btn = document['querySelector'](sel);\n      if (!(btn instanceof HTMLElement)) continue;\n      if (btn.getAttribute('aria-disabled') === 'true' || btn.hasAttribute('disabled') ||\n          btn.classList.contains('ytp-button-disabled')) continue;\n      btn.click();\n      return true;\n    }\n    return false;\n  };\n\n  const getShortcut = () => window['__ytplusPipShortcut'] || { key: 'P', shiftKey: true, altKey: false, ctrlKey: false, enabled: true };\n  const keyToCode = key => {\n    const normalized = String(key || '').trim().toUpperCase();\n    if (!normalized) return '';\n    const symbolCodeMap = {\n      '-': 'Minus',\n      _: 'Minus',\n      '=': 'Equal',\n      '+': 'Equal',\n      '[': 'BracketLeft',\n      '{': 'BracketLeft',\n      ']': 'BracketRight',\n      '}': 'BracketRight',\n      ';': 'Semicolon',\n      ':': 'Semicolon',\n      "'": 'Quote',\n      '"': 'Quote',\n      ',': 'Comma',\n      '<': 'Comma',\n      '.': 'Period',\n      '>': 'Period',\n      '/': 'Slash',\n      '?': 'Slash'\n    };\n    if (Object.prototype.hasOwnProperty.call(symbolCodeMap, normalized)) {\n      return symbolCodeMap[normalized];\n    }\n    if (normalized.length === 1 && /[A-Z]/.test(normalized)) return 'Key' + normalized;\n    if (normalized.length === 1 && /[0-9]/.test(normalized)) return 'Digit' + normalized;\n    return normalized;\n  };\n\n  const handlePiPHotkey = function ytplusPipKeydown(e) {\n    const s = getShortcut();\n    if (!s || !s.enabled) return;\n    if (e.repeat || e.isComposing || e.metaKey) return;\n    const expectedCode = keyToCode(String(s.key || ''));\n    const eventCode = String(e.code || '');\n    const eventKey = String(e.key || '').toUpperCase();\n    const keyMatches =\n      eventKey === String(s.key || '').toUpperCase() || (expectedCode && eventCode === expectedCode);\n    const modsMatch = !!e.shiftKey === !!s.shiftKey && !!e.altKey === !!s.altKey && !!e.ctrlKey === !!s.ctrlKey;\n    if (!keyMatches || !modsMatch) return;\n\n    // Check active element is not a text input\n    const ae = document.activeElement;\n    if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA' ||\n               ae.tagName === 'SELECT' || ae.isContentEditable)) return;\n\n    e.preventDefault();\n    e.stopPropagation();\n    if (typeof e.stopImmediatePropagation === 'function') e.stopImmediatePropagation();\n\n    // Signal the userscript handler to not double-toggle\n    window['__ytplusPipPageHandled'] = Date.now();\n\n    if (document.pictureInPictureElement) {\n      document.exitPictureInPicture && document.exitPictureInPicture();\n      return;\n    }\n\n    const video = selectVideo();\n    if (video && typeof video.requestPictureInPicture === 'function') {\n      video.requestPictureInPicture().catch(() => clickPipBtn());\n      return;\n    }\n    clickPipBtn();\n  };\n\n  document.addEventListener('keydown', handlePiPHotkey, true);\n  window.addEventListener('keydown', handlePiPHotkey, true);\n})();`;
const gmAddEl = globalThis.GM_addElement;
if ("function" == typeof gmAddEl) {
try {
gmAddEl("script", {
textContent: createTrustedInlineScript(bridgeScript)
});
if (pageGlobal.__ytplusFirefoxPipKeydownBridge) {
return !0;
}
} catch (e) {}
}
try {
const nonceSource = qs("script[nonce]") || qs("[nonce]") || document.documentElement;
const nonce = nonceSource?.nonce || nonceSource?.getAttribute?.("nonce") || document.documentElement?.getAttribute?.("nonce") || "";
const script = document.createElement("script");
nonce && script.setAttribute("nonce", nonce);
script.textContent = createTrustedInlineScript(bridgeScript);
(document.head || document.documentElement).appendChild(script);
script.remove();
if (pageGlobal.__ytplusFirefoxPipKeydownBridge) {
return !0;
}
} catch (e) {}
})();
try {
const pageGlobal = getPageGlobal();
const pageDocument = pageGlobal?.document;
const pageWindow = pageGlobal;
pageDocument && pageDocument !== document && pageDocument.addEventListener && pageDocument.addEventListener("keydown", pipKeydownHandler, {
capture: !0
});
pageWindow && pageWindow !== window && pageWindow.addEventListener && pageWindow.addEventListener("keydown", pipKeydownHandler, {
capture: !0
});
} catch (e) {
logger.warn("[PiP] Could not register page-context keydown listener (Firefox sandbox)");
}
YouTubeUtils.cleanupManager.registerListener(window, "storage", e => {
e instanceof StorageEvent && e.key === pipSettings.storageKey && loadSettings();
});
window.addEventListener("load", () => {
if (!pipSettings.enabled || !(() => {
try {
return "true" === sessionStorage.getItem("youtube_plus_pip_session");
} catch (e) {
return !1;
}
})() || document.pictureInPictureElement) {
return;
}
const resumePiP = () => {
const video = getVideoElement();
video && togglePictureInPicture(video).catch(() => {
setSessionActive(!1);
});
};
const ensureCleanup = handler => {
if (handler) {
try {
document.removeEventListener("pointerdown", handler, !0);
} catch (e) {}
}
};
const cleanupListeners = () => {
ensureCleanup(pointerListener);
ensureCleanup(keyListener);
};
const pointerListener = () => {
cleanupListeners();
resumePiP();
};
const keyListener = () => {
cleanupListeners();
resumePiP();
};
document.addEventListener("pointerdown", pointerListener, {
once: !0,
capture: !0
});
document.addEventListener("keydown", keyListener, {
once: !0,
capture: !0
});
});
const ensurePipSettings = () => {
if (window.YouTubeUtils?.createRetryScheduler) {
window.YouTubeUtils.createRetryScheduler({
check: () => !0 === addPipSettingsToModal(),
maxAttempts: 20,
interval: 120
});
return;
}
let attempts = 0;
const retry = () => {
attempts += 1;
addPipSettingsToModal() || attempts >= 20 || setTimeout(retry, 120);
};
retry();
};
YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", () => {
setTimeout(ensurePipSettings, 50);
});
YouTubeUtils.cleanupManager.registerListener(document, "leavepictureinpicture", () => {
setSessionActive(!1);
});
YouTubeUtils.cleanupManager.registerListener(document, "click", e => {
if (!(e instanceof MouseEvent)) {
return;
}
const target = e.target;
target.classList && target.classList.contains("ytp-plus-settings-nav-item") && "advanced" === target.dataset?.section && setTimeout(ensurePipSettings, 25);
}, !0);
};
window.YouTubePlusLazyLoader?.register ? window.YouTubePlusLazyLoader.register("pip", startPipRuntime, {
priority: 60,
delay: 0,
shouldLoad: () => (() => {
const path = location.pathname || "";
return "/watch" === path || path.startsWith("/shorts");
})() || (() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()
}) : startPipRuntime();
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout;
const renderTemplateClone = (container, html) => {
if (window.YouTubeSafeDOM?.renderTemplateClone) {
window.YouTubeSafeDOM.renderTemplateClone(container, html);
return;
}
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
const htmlFactory = window._ytplusCreateHTML || (s => s);
template.content.append(range.createContextualFragment(htmlFactory(String(html ?? ""))));
container.replaceChildren(template.content.cloneNode(!0));
};
const $ = window.YouTubeUtils.$;
const $$ = window.YouTubeUtils.$$;
const byId = window.YouTubeUtils.byId;
if ("www.youtube.com" !== window.location.hostname || window.frameElement) {
return;
}
if (window._timecodeModuleInitialized) {
return;
}
window._timecodeModuleInitialized = !0;
const t = window.YouTubeUtils.t;
const config = {
enabled: !0,
autoDetect: !0,
shortcut: {
key: "T",
shiftKey: !0,
altKey: !1,
ctrlKey: !1
},
storageKey: "youtube_timecode_settings",
autoSave: !0,
autoTrackPlayback: !0,
panelPosition: null,
export: !0
};
const state = {
timecodes: new Map,
dom: {},
isReloading: !1,
activeIndex: null,
trackingId: 0,
dragging: !1,
editingIndex: null,
resizeListenerKey: null,
settingsIntegrationStarted: !1
};
let initStarted = !1;
const isRelevantRoute = () => {
try {
return "/watch" === location.pathname;
} catch (e) {
return !1;
}
};
const parseLeadingTimestampToken = input => {
const s = String(input || "");
let i = 0;
const readNumber = () => {
const start = i;
for (;i < s.length && s[i] >= "0" && s[i] <= "9"; ) {
i += 1;
}
return i > start ? s.slice(start, i) : "";
};
const a = readNumber();
if (!a || i >= s.length || ":" !== s[i]) {
return null;
}
i += 1;
const b = readNumber();
if (2 !== b.length) {
return null;
}
if (i < s.length && ":" === s[i]) {
i += 1;
const c = readNumber();
return 2 !== c.length ? null : {
token: `${a}:${b}:${c}`,
length: i
};
}
return {
token: `${a}:${b}`,
length: i
};
};
const stripLeadingTimePrefix = value => {
const input = String(value || "").trimStart();
const parsed = parseLeadingTimestampToken(input);
if (!parsed) {
return input;
}
let rest = input.slice(parsed.length).trimStart();
(rest.startsWith("-") || rest.startsWith("–") || rest.startsWith("—") || rest.startsWith(":")) && (rest = rest.slice(1).trimStart());
return rest;
};
const loadSettings = () => {
try {
const saved = localStorage.getItem(config.storageKey);
if (!saved) {
return;
}
const parsed = JSON.parse(saved);
if ("object" != typeof parsed || null === parsed) {
window.console.warn("[Timecode] Invalid settings format");
return;
}
"boolean" == typeof parsed.enabled && (config.enabled = parsed.enabled);
"boolean" == typeof parsed.autoDetect && (config.autoDetect = parsed.autoDetect);
"boolean" == typeof parsed.autoSave && (config.autoSave = parsed.autoSave);
"boolean" == typeof parsed.autoTrackPlayback && (config.autoTrackPlayback = parsed.autoTrackPlayback);
"boolean" == typeof parsed.export && (config.export = parsed.export);
if (parsed.shortcut && "object" == typeof parsed.shortcut) {
"string" == typeof parsed.shortcut.key && (config.shortcut.key = parsed.shortcut.key);
"boolean" == typeof parsed.shortcut.shiftKey && (config.shortcut.shiftKey = parsed.shortcut.shiftKey);
"boolean" == typeof parsed.shortcut.altKey && (config.shortcut.altKey = parsed.shortcut.altKey);
"boolean" == typeof parsed.shortcut.ctrlKey && (config.shortcut.ctrlKey = parsed.shortcut.ctrlKey);
}
if (parsed.panelPosition && "object" == typeof parsed.panelPosition) {
const {left, top} = parsed.panelPosition;
"number" == typeof left && "number" == typeof top && !isNaN(left) && !isNaN(top) && left >= 0 && top >= 0 && (config.panelPosition = {
left,
top
});
}
} catch (error) {
window.console.error("[Timecode] Error loading settings:", error);
}
};
const saveSettings = () => {
try {
const settingsToSave = {
enabled: config.enabled,
autoDetect: config.autoDetect,
shortcut: config.shortcut,
autoSave: config.autoSave,
autoTrackPlayback: config.autoTrackPlayback,
panelPosition: config.panelPosition,
export: config.export
};
localStorage.setItem(config.storageKey, JSON.stringify(settingsToSave));
} catch (error) {
window.console.error("[Timecode] Error saving settings:", error);
}
};
const clampPanelPosition = (panel, left, top) => {
try {
if (!(panel && panel instanceof HTMLElement)) {
window.console.warn("[Timecode] Invalid panel element");
return {
left: 0,
top: 0
};
}
if ("number" != typeof left || "number" != typeof top || isNaN(left) || isNaN(top)) {
window.console.warn("[Timecode] Invalid position coordinates");
return {
left: 0,
top: 0
};
}
const rect = panel.getBoundingClientRect();
const width = rect.width || panel.offsetWidth || 0;
const height = rect.height || panel.offsetHeight || 0;
const maxLeft = Math.max(0, window.innerWidth - width);
const maxTop = Math.max(0, window.innerHeight - height);
return {
left: Math.min(Math.max(0, left), maxLeft),
top: Math.min(Math.max(0, top), maxTop)
};
} catch (error) {
window.console.error("[Timecode] Error clamping panel position:", error);
return {
left: 0,
top: 0
};
}
};
const savePanelPosition = (left, top) => {
try {
if ("number" != typeof left || "number" != typeof top || isNaN(left) || isNaN(top)) {
window.console.warn("[Timecode] Invalid position coordinates for saving");
return;
}
config.panelPosition = {
left,
top
};
saveSettings();
} catch (error) {
window.console.error("[Timecode] Error saving panel position:", error);
}
};
const applySavedPanelPosition = panel => {
panel && config.panelPosition && requestAnimationFrame(() => {
const {left, top} = clampPanelPosition(panel, config.panelPosition.left, config.panelPosition.top);
panel.style.left = `${left}px`;
panel.style.top = `${top}px`;
panel.style.right = "auto";
});
};
const showNotification = (message, duration = 2e3, type = "info") => {
YouTubeUtils.NotificationManager.show(message, {
duration,
type
});
};
const formatTime = seconds => window.YouTubeUtils?.formatTime?.(seconds) || "0:00";
const removeDuplicateText = text => {
if (!text || text.length < 10) {
return text;
}
let cleaned = text.trim();
cleaned = cleaned.replace(/\s*\.{2,}$/, "").replace(/\s*…$/, "");
const words = cleaned.split(/\s+/);
if (words.length < 4) {
return cleaned;
}
const half = Math.floor(words.length / 2);
if (half >= 2) {
const firstHalf = words.slice(0, half).join(" ");
const secondHalf = words.slice(half, 2 * half).join(" ");
if (firstHalf === secondHalf) {
return firstHalf;
}
}
const minPatternLength = Math.max(2, Math.floor(words.length / 4));
const maxPatternLength = Math.floor(words.length / 2);
for (let len = maxPatternLength; len >= minPatternLength; len--) {
const pattern = words.slice(0, len).join(" ");
const patternWords = words.slice(0, len);
for (let offset = 1; offset <= words.length - len; offset++) {
let matchCount = 0;
let partialWordMatch = !1;
const testWords = words.slice(offset, Math.min(offset + len, words.length));
for (let i = 0; i < patternWords.length; i++) {
const patternWord = patternWords[i];
const testWord = testWords[i];
if (!testWord) {
break;
}
if (patternWord === testWord) {
matchCount++;
} else if (testWord.length >= 3 && patternWord.startsWith(testWord)) {
matchCount += .8;
partialWordMatch = !0;
} else if (patternWord.length >= 3 && testWord.startsWith(patternWord)) {
matchCount += .8;
partialWordMatch = !0;
}
}
const similarity = matchCount / patternWords.length;
const effectiveMatches = Math.floor(matchCount);
if (similarity >= .7 && (effectiveMatches >= 2 || matchCount >= 1.5 && partialWordMatch)) {
return pattern;
}
}
}
return cleaned;
};
const parseTime = timeStr => {
try {
if (!timeStr || "string" != typeof timeStr) {
return null;
}
const str = timeStr.trim();
if (0 === str.length || str.length > 12) {
return null;
}
let match = str.match(/^(\d+):(\d{1,2}):(\d{2})$/);
if (match) {
const [, h, m, s] = match.map(Number);
if (isNaN(h) || isNaN(m) || isNaN(s)) {
return null;
}
if (m >= 60 || s >= 60 || h < 0 || m < 0 || s < 0) {
return null;
}
const total = 3600 * h + 60 * m + s;
return total <= 86400 ? total : null;
}
match = str.match(/^(\d{1,2}):(\d{2})$/);
if (match) {
const [, m, s] = match.map(Number);
return isNaN(m) || isNaN(s) || (m >= 60 || s >= 60 || m < 0 || s < 0) ? null : 60 * m + s;
}
return null;
} catch (error) {
window.console.error("[Timecode] Error parsing time:", error);
return null;
}
};
const extractTimecodes = text => {
try {
if (!text || "string" != typeof text) {
return [];
}
if (text.length > 5e4) {
window.console.warn("[Timecode] Text too long, truncating");
text = text.substring(0, 5e4);
}
const timecodes = [];
const seen = new Set;
const lines = String(text || "").replace(/\r/g, "").split("\n");
const maxIterations = 1e3;
for (let i = 0; i < lines.length && i < maxIterations; i += 1) {
const line = lines[i].trim();
if (!line) {
continue;
}
const timeMatch = parseLeadingTimestampToken(line);
if (!timeMatch) {
continue;
}
const time = parseTime(timeMatch.token);
if (null === time || seen.has(time)) {
continue;
}
seen.add(time);
let label = stripLeadingTimePrefix(line.slice(timeMatch.length));
label = label.trim().replace(/^\d+[\.\)]\s*/, "").replace(/\s+/g, " ").substring(0, 100);
const originalLabel = label;
label = label.replace(/[<>\"']/g, "");
label = removeDuplicateText(label);
originalLabel !== label && label.length > 0 && window.console.warn("[Timecode] Description deduplicated:", originalLabel, "->", label);
timecodes.push({
time,
label: label || "",
originalText: timeMatch.token
});
}
lines.length > maxIterations && window.console.warn("[Timecode] Maximum iterations reached during extraction");
return timecodes.sort((a, b) => a.time - b.time);
} catch (error) {
window.console.error("[Timecode] Error extracting timecodes:", error);
return [];
}
};
const DESCRIPTION_SELECTORS = [ "#description-inline-expander yt-attributed-string", "#description-inline-expander yt-formatted-string", "#description-inline-expander ytd-text-inline-expander", "#description-inline-expander .yt-core-attributed-string", "#description ytd-text-inline-expander", "#description ytd-expandable-video-description-body-renderer", "#description.ytd-watch-metadata yt-formatted-string", "#description.ytd-watch-metadata #description-inline-expander", "#tab-info ytd-expandable-video-description-body-renderer yt-formatted-string", "#tab-info ytd-expandable-video-description-body-renderer yt-attributed-string", "#structured-description ytd-text-inline-expander", "#structured-description yt-formatted-string", 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] yt-formatted-string', 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] yt-attributed-string', "ytd-watch-metadata #description", "ytd-watch-metadata #description-inline-expander", "#description" ];
const DESCRIPTION_SELECTOR_COMBINED = DESCRIPTION_SELECTORS.join(",");
const DESCRIPTION_EXPANDERS = [ "#description-inline-expander yt-button-shape button", "#description-inline-expander tp-yt-paper-button#expand", "#description-inline-expander tp-yt-paper-button[aria-label]", "ytd-watch-metadata #description-inline-expander yt-button-shape button", "ytd-text-inline-expander[collapsed] yt-button-shape button", "ytd-text-inline-expander[collapsed] tp-yt-paper-button#expand", "ytd-expandable-video-description-body-renderer #expand", 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-macro-markers-description-chapters"] #expand' ];
const sleep = (ms = 250) => new Promise(resolve => setTimeout(resolve, ms));
const collectDescriptionText = () => {
const snippets = [];
DESCRIPTION_SELECTORS.forEach(selector => {
$$(selector).forEach(node => {
const text = node?.textContent?.trim();
text && snippets.push(text);
});
});
return snippets.join("\n");
};
const COMMENT_SELECTORS = [ "ytd-comment-thread-renderer #content-text", "ytd-comment-renderer #content-text", "ytd-comment-thread-renderer yt-formatted-string#content-text", "ytd-comment-renderer yt-formatted-string#content-text", "#comments ytd-comment-thread-renderer #content-text" ];
const expandDescriptionIfNeeded = async () => {
for (const selector of DESCRIPTION_EXPANDERS) {
const button = $(selector);
if (!button) {
continue;
}
const ariaExpanded = button.getAttribute("aria-expanded");
if ("true" === ariaExpanded) {
return !1;
}
const ariaLabel = button.getAttribute("aria-label")?.toLowerCase();
if (ariaLabel && ariaLabel.includes("less")) {
return !1;
}
if (null !== button.offsetParent) {
try {
button.click();
await sleep(400);
return !0;
} catch (error) {
window.console.warn("[Timecode] Failed to click expand button:", error);
}
}
}
const inlineExpander = $("ytd-text-inline-expander[collapsed]");
if (inlineExpander) {
try {
inlineExpander.removeAttribute("collapsed");
} catch (error) {
YouTubeUtils.logError("TimecodePanel", "Failed to expand description", error);
}
await sleep(300);
return !0;
}
return !1;
};
const detectTimecodes = async (options = {}) => {
const {force = !1} = options;
if (!config.enabled) {
return [];
}
if (!force && !config.autoDetect) {
return [];
}
const videoId = new URLSearchParams(window.location.search).get("v");
if (!videoId) {
return [];
}
const cacheKey = `detect_${videoId}`;
if (!force && state.timecodes.has(cacheKey)) {
const cached = state.timecodes.get(cacheKey);
if (Array.isArray(cached) && cached.length) {
return cached;
}
state.timecodes.delete(cacheKey);
}
await (async () => {
const initialText = collectDescriptionText();
if (initialText) {
return;
}
for (let attempt = 0; attempt < 3; attempt++) {
try {
await YouTubeUtils.waitForElement(DESCRIPTION_SELECTOR_COMBINED, 1500);
} catch (e) {}
await sleep(200);
const expanded = await expandDescriptionIfNeeded();
await sleep(expanded ? 500 : 200);
const text = collectDescriptionText();
if (text && text.length > initialText.length) {
return;
}
}
})();
const uniqueMap = new Map;
const descriptionText = collectDescriptionText();
if (descriptionText) {
const extracted = extractTimecodes(descriptionText);
extracted.forEach(tc => {
tc.time >= 0 && uniqueMap.set(tc.time.toString(), tc);
});
}
const chapters = getYouTubeChapters();
chapters.forEach(chapter => {
if (chapter.time >= 0) {
const key = chapter.time.toString();
const existing = uniqueMap.get(key);
uniqueMap.set(key, existing && chapter.label && chapter.label.length > existing.label.length ? {
...existing,
label: chapter.label,
isChapter: !0
} : existing ? {
...existing,
isChapter: !0
} : chapter);
}
});
if (0 === uniqueMap.size) {
try {
const commentsText = ((maxComments = 30) => {
try {
const snippets = [];
for (const sel of COMMENT_SELECTORS) {
$$(sel).forEach(node => {
if (snippets.length >= maxComments) {
return;
}
const text = node?.textContent?.trim();
text && snippets.push(text);
});
if (snippets.length >= maxComments) {
break;
}
}
return snippets.join("\n");
} catch (error) {
YouTubeUtils.logError("TimecodePanel", "collectCommentsText failed", error);
return "";
}
})();
if (commentsText) {
const extractedComments = extractTimecodes(commentsText);
extractedComments.forEach(tc => {
tc.time >= 0 && uniqueMap.set(tc.time.toString(), tc);
});
}
} catch (error) {
YouTubeUtils.logError("TimecodePanel", "Comment scanning failed", error);
}
}
const result = Array.from(uniqueMap.values()).sort((a, b) => a.time - b.time);
const hadExistingItems = state.dom.list?.childElementCount > 0;
if (result.length > 0) {
updateTimecodePanel(result);
state.timecodes.set(cacheKey, result);
config.autoSave && saveTimecodesToStorage(result);
} else {
!force && hadExistingItems || updateTimecodePanel([]);
force && state.timecodes.delete(cacheKey);
}
return result;
};
const getYouTubeChapters = () => {
const items = $$([ "ytd-macro-markers-list-item-renderer", "ytd-chapter-renderer", 'ytd-engagement-panel-section-list-renderer[target-id*="description-chapters"] ytd-macro-markers-list-item-renderer', 'ytd-engagement-panel-section-list-renderer[target-id*="description-chapters"] #details', "#structured-description ytd-horizontal-card-list-renderer ytd-macro-markers-list-item-renderer" ].join(", "));
const chapters = new Map;
items.forEach(item => {
const timeSelectors = [ ".time-info", ".timestamp", "#time", 'span[id*="time"]' ];
const titleSelectors = [ ".marker-title", ".chapter-title", "#details", "h4", ".title" ];
let timeText = null;
for (const sel of timeSelectors) {
const el = item.querySelector(sel);
if (el?.textContent) {
timeText = el.textContent;
break;
}
}
let titleText = null;
for (const sel of titleSelectors) {
const el = item.querySelector(sel);
if (el?.textContent) {
titleText = el.textContent;
break;
}
}
if (timeText) {
const time = parseTime(timeText.trim());
if (null !== time) {
let cleanTitle = titleText?.trim().replace(/\s+/g, " ") || "";
cleanTitle && cleanTitle.length > 0 && window.console.warn("[Timecode Debug] Raw chapter title:", cleanTitle);
cleanTitle = stripLeadingTimePrefix(cleanTitle);
const deduplicated = removeDuplicateText(cleanTitle);
cleanTitle !== deduplicated && window.console.warn("[Timecode] Removed duplicate:", cleanTitle, "->", deduplicated);
cleanTitle = deduplicated;
chapters.set(time.toString(), {
time,
label: cleanTitle,
isChapter: !0
});
}
}
});
const result = Array.from(chapters.values()).sort((a, b) => a.time - b.time);
return result;
};
const ensureTimecodePanelSettings = (attempt = 0) => {
const advancedSection = $('.ytp-plus-settings-section[data-section="advanced"]');
if (advancedSection) {
(() => {
const advancedSection = $('.ytp-plus-settings-section[data-section="advanced"]');
if (!advancedSection || advancedSection.querySelector(".timecode-settings-item")) {
return;
}
const getSubmenuExpanded = () => {
try {
const raw = localStorage.getItem("ytp-plus-submenu-states");
if (!raw) {
return null;
}
const parsed = JSON.parse(raw);
if (parsed && "boolean" == typeof parsed.timecode) {
return parsed.timecode;
}
} catch (e) {}
return null;
};
const storedExpanded = getSubmenuExpanded();
const initialExpanded = "boolean" != typeof storedExpanded || storedExpanded;
const {ctrlKey, altKey, shiftKey} = config.shortcut;
const modifierValue = [ ctrlKey && altKey && shiftKey && "ctrl+alt+shift", ctrlKey && altKey && "ctrl+alt", ctrlKey && shiftKey && "ctrl+shift", altKey && shiftKey && "alt+shift", ctrlKey && "ctrl", altKey && "alt", shiftKey && "shift" ].find(Boolean) || "none";
const enableDiv = document.createElement("div");
enableDiv.className = "ytp-plus-settings-item timecode-settings-item ytp-plus-settings-item--with-submenu";
renderTemplateClone(enableDiv, `\n        <div>\n          <label class="ytp-plus-settings-item-label" for="timecode-enable-checkbox">${t("enableTimecode")}</label>\n          <div class="ytp-plus-settings-item-description">${t("enableDescription")}</div>\n        </div>\n        <div class="ytp-plus-settings-item-actions">\n          <button\n            type="button"\n            class="ytp-plus-submenu-toggle"\n            data-submenu="timecode"\n            aria-label="Toggle timecode submenu"\n            aria-expanded="${initialExpanded ? "true" : "false"}"\n            ${config.enabled ? "" : "disabled"}\n            style="display:${config.enabled ? "inline-flex" : "none"};"\n          >\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n              <polyline points="6 9 12 15 18 9"></polyline>\n            </svg>\n          </button>\n          <input type="checkbox" id="timecode-enable-checkbox" class="ytp-plus-settings-checkbox" data-setting="enabled" ${config.enabled ? "checked" : ""}>\n        </div>\n      `);
const submenuWrap = document.createElement("div");
submenuWrap.className = "timecode-submenu";
submenuWrap.dataset.submenu = "timecode";
submenuWrap.style.display = config.enabled && initialExpanded ? "block" : "none";
submenuWrap.style.marginLeft = "12px";
submenuWrap.style.marginBottom = "12px";
const submenuCard = document.createElement("div");
submenuCard.className = "glass-card";
submenuCard.style.display = "flex";
submenuCard.style.flexDirection = "column";
submenuCard.style.gap = "8px";
const shortcutDiv = document.createElement("div");
shortcutDiv.className = "ytp-plus-settings-item timecode-settings-item timecode-shortcut-item";
shortcutDiv.style.display = "flex";
renderTemplateClone(shortcutDiv, `\n        <div>\n          <label class="ytp-plus-settings-item-label">${t("keyboardShortcut")}</label>\n          <div class="ytp-plus-settings-item-description">${t("shortcutDescription")}</div>\n        </div>\n        <div style="display: flex; align-items: center; gap: 8px;">\n          \x3c!-- Hidden native select kept for programmatic compatibility --\x3e\n          <select id="timecode-modifier-combo" style="display:none;">\n            ${[ "none", "ctrl", "alt", "shift", "ctrl+alt", "ctrl+shift", "alt+shift", "ctrl+alt+shift" ].map(v => `<option value="${v}" ${v === modifierValue ? "selected" : ""}>${"none" === v ? "None" : v.split("+").map(k => k.charAt(0).toUpperCase() + k.slice(1)).join("+")}</option>`).join("")}\n          </select>\n\n          <div class="glass-dropdown" id="timecode-modifier-dropdown" tabindex="0" role="listbox" aria-expanded="false">\n            <button class="glass-dropdown__toggle" type="button" aria-haspopup="listbox">\n              <span class="glass-dropdown__label">${"none" === modifierValue ? "None" : modifierValue.split("+").map(k => k.charAt(0).toUpperCase() + k.slice(1)).join("+")}</span>\n              <svg class="glass-dropdown__chev" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>\n            </button>\n            <ul class="glass-dropdown__list" role="presentation">\n              ${[ "none", "ctrl", "alt", "shift", "ctrl+alt", "ctrl+shift", "alt+shift", "ctrl+alt+shift" ].map(v => {
const label = "none" === v ? "None" : v.split("+").map(k => k.charAt(0).toUpperCase() + k.slice(1)).join("+");
const sel = v === modifierValue ? ' aria-selected="true"' : "";
return `<li class="glass-dropdown__item" data-value="${v}" role="option"${sel}>${label}</li>`;
}).join("")}\n            </ul>\n          </div>\n\n          <span style="color:inherit;opacity:0.8;">+</span>\n          <input type="text" id="timecode-key" value="${config.shortcut.key}" maxlength="1" style="width: 30px; text-align: center; background: var(--yt-input-bg); color: var(--yt-text-primary); border: 1px solid var(--yt-border-color); border-radius: 4px; padding: 4px;">\n        </div>\n      `);
submenuCard.appendChild(shortcutDiv);
submenuWrap.appendChild(submenuCard);
advancedSection.append(enableDiv, submenuWrap);
window.YouTubePlusDesignSystem?.initGlassDropdown?.({
dropdown: byId("timecode-modifier-dropdown"),
hiddenSelect: byId("timecode-modifier-combo")
});
advancedSection.addEventListener("change", e => {
const target = e.target;
if (target.matches && target.matches('.ytp-plus-settings-checkbox[data-setting="enabled"]')) {
config.enabled = target.checked;
const submenuToggle = enableDiv.querySelector('.ytp-plus-submenu-toggle[data-submenu="timecode"]');
if (submenuToggle instanceof HTMLElement) {
if (config.enabled) {
const stored = getSubmenuExpanded();
const nextExpanded = "boolean" != typeof stored || stored;
submenuToggle.removeAttribute("disabled");
submenuToggle.style.display = "inline-flex";
submenuToggle.setAttribute("aria-expanded", nextExpanded ? "true" : "false");
submenuWrap.style.display = nextExpanded ? "block" : "none";
} else {
submenuToggle.setAttribute("disabled", "");
submenuToggle.style.display = "none";
submenuWrap.style.display = "none";
}
}
toggleTimecodePanel(config.enabled);
saveSettings();
}
});
byId("timecode-modifier-combo")?.addEventListener("change", e => {
const target = e.target;
const value = target.value;
config.shortcut.ctrlKey = value.includes("ctrl");
config.shortcut.altKey = value.includes("alt");
config.shortcut.shiftKey = value.includes("shift");
saveSettings();
});
byId("timecode-key")?.addEventListener("input", e => {
const target = e.target;
if (target.value) {
config.shortcut.key = target.value.toUpperCase();
saveSettings();
}
});
})();
!$(".timecode-settings-item") && attempt < 20 && setTimeout(() => ensureTimecodePanelSettings(attempt + 1), 80);
} else {
attempt < 20 && setTimeout(() => ensureTimecodePanelSettings(attempt + 1), 80);
}
};
const handlePanelClick = e => {
const {target} = e;
const item = target.closest(".timecode-item");
const reloadButton = target.closest ? target.closest("#timecode-reload") : "timecode-reload" === target.id ? target : null;
if (reloadButton) {
e.preventDefault();
(async (buttonOverride = null) => {
const button = buttonOverride || state.dom.reloadButton || byId("timecode-reload");
if (!state.isReloading && config.enabled) {
state.isReloading = !0;
if (button) {
button.disabled = !0;
button.classList.add("loading");
}
try {
const result = await detectTimecodes({
force: !0
});
if (Array.isArray(result) && result.length) {
showNotification(t("foundTimecodes").replace("{count}", String(result.length)));
} else {
updateTimecodePanel([]);
showNotification(t("noTimecodesFound"));
}
} catch (error) {
YouTubeUtils.logError("TimecodePanel", "Reload failed", error);
showNotification(t("reloadError"));
} finally {
if (button) {
button.disabled = !1;
button.classList.remove("loading");
}
state.isReloading = !1;
}
}
})(reloadButton);
return;
}
const closeButton = target.closest ? target.closest("#timecode-close") : "timecode-close" === target.id ? target : null;
if (closeButton) {
toggleTimecodePanel(!1);
} else if ("timecode-add-btn" === target.id) {
const video = YouTubeUtils.querySelector ? YouTubeUtils.querySelector("video") : $("video");
video && showTimecodeForm(video.currentTime);
} else if ("timecode-track-toggle" === target.id) {
config.autoTrackPlayback = !config.autoTrackPlayback;
target.textContent = t(config.autoTrackPlayback ? "tracking" : "track");
target.classList.toggle("active", config.autoTrackPlayback);
state.dom.panel.classList.toggle("auto-tracking", config.autoTrackPlayback);
saveSettings();
config.autoTrackPlayback && startTracking();
} else if ("timecode-export-btn" === target.id) {
exportTimecodes();
} else if ("timecode-form-cancel" === target.id) {
hideTimecodeForm();
} else if ("timecode-form-save" === target.id) {
saveTimecodeForm();
} else if (target.classList.contains("timecode-action")) {
e.stopPropagation();
const action = target.dataset.action;
const index = parseInt(target.closest(".timecode-item").dataset.index, 10);
"edit" === action ? editTimecode(index) : "delete" === action && deleteTimecode(index);
} else if (item && !target.closest(".timecode-actions")) {
const time = parseFloat(item.dataset.time);
const video = $("video");
if (video && !isNaN(time)) {
video.currentTime = time;
video.paused && video.play();
updateActiveItem(item);
}
}
};
const editTimecode = index => {
const timecodes = getCurrentTimecodes();
if (index < 0 || index >= timecodes.length) {
return;
}
const timecode = timecodes[index];
state.editingIndex = index;
const item = state.dom.list.querySelector(`.timecode-item[data-index="${index}"]`);
if (item) {
item.classList.add("editing");
state.dom.list.querySelectorAll(".timecode-item.editing").forEach(el => {
el !== item && el.classList.remove("editing");
});
}
showTimecodeForm(timecode.time, timecode.label);
};
const deleteTimecode = index => {
const timecodes = getCurrentTimecodes();
if (index < 0 || index >= timecodes.length) {
return;
}
const timecode = timecodes[index];
if (!timecode.isChapter || timecode.isUserAdded) {
if (confirm(t("confirmDelete").replace("{label}", timecode.label))) {
timecodes.splice(index, 1);
updateTimecodePanel(timecodes);
saveTimecodesToStorage(timecodes);
showNotification(t("timecodeDeleted"));
}
} else {
showNotification(t("cannotDeleteChapter"));
}
};
const showTimecodeForm = (currentTime, existingLabel = "") => {
const {form, timeInput, labelInput} = state.dom;
form.classList.add("visible");
timeInput.value = formatTime(currentTime);
labelInput.value = existingLabel;
requestAnimationFrame(() => labelInput.focus());
};
const hideTimecodeForm = () => {
state.dom.form.classList.remove("visible");
state.editingIndex = null;
state.dom.list?.querySelectorAll(".timecode-item.editing").forEach(el => {
el.classList.remove("editing");
});
};
const saveTimecodeForm = () => {
const {timeInput, labelInput} = state.dom;
const timeValue = timeInput.value.trim();
const labelValue = labelInput.value.trim();
const time = parseTime(timeValue);
if (null === time) {
showNotification(t("invalidTimeFormat"));
return;
}
const timecodes = getCurrentTimecodes();
const newTimecode = {
time,
label: labelValue || "",
isUserAdded: !0,
isChapter: !1
};
if (null !== state.editingIndex) {
const oldTimecode = timecodes[state.editingIndex];
if (oldTimecode.isChapter && !oldTimecode.isUserAdded) {
showNotification(t("cannotEditChapter"));
hideTimecodeForm();
return;
}
timecodes[state.editingIndex] = {
...oldTimecode,
...newTimecode
};
showNotification(t("timecodeUpdated"));
} else {
timecodes.push(newTimecode);
showNotification(t("timecodeAdded"));
}
const sorted = timecodes.sort((a, b) => a.time - b.time);
updateTimecodePanel(sorted);
saveTimecodesToStorage(sorted);
hideTimecodeForm();
};
const exportTimecodes = () => {
const timecodes = getCurrentTimecodes();
if (!timecodes.length) {
showNotification(t("noTimecodesToExport"));
return;
}
const exportBtn = state.dom.panel?.querySelector("#timecode-export-btn");
if (exportBtn) {
exportBtn.textContent = t("copied");
exportBtn.style.backgroundColor = "var(--yt-timecode-export-success-bg)";
setTimeout_(function() {
exportBtn.textContent = t("export");
exportBtn.style.backgroundColor = "";
}, 2e3);
}
const videoTitle = document.title.replace(/\s-\sYouTube$/, "");
let content = `${videoTitle}\n\nTimecodes:\n`;
timecodes.forEach(tc => {
const label = tc.label?.trim();
content += label ? `${formatTime(tc.time)} - ${label}\n` : `${formatTime(tc.time)}\n`;
});
navigator.clipboard?.writeText && navigator.clipboard.writeText(content).then(() => {
showNotification(t("timecodesCopied"));
});
};
const updateTimecodePanel = timecodes => {
const {list, empty} = state.dom;
if (!list || !empty) {
return;
}
const isEmpty = !timecodes.length;
empty.style.display = isEmpty ? "flex" : "none";
list.style.display = isEmpty ? "none" : "block";
isEmpty ? list.replaceChildren() : renderTemplateClone(list, timecodes.map((tc, i) => {
const timeStr = formatTime(tc.time);
let rawLabel = tc.label?.trim() || "";
rawLabel = stripLeadingTimePrefix(rawLabel);
const beforeDedup = rawLabel;
rawLabel = removeDuplicateText(rawLabel);
beforeDedup !== rawLabel && rawLabel.length > 0 && window.console.warn("[Timecode] Display deduplicated:", beforeDedup, "->", rawLabel);
const normalizedTime = timeStr.replace(/^0+:/, "");
const normalizedLabel = rawLabel.replace(/^0+:/, "");
const hasCustomLabel = rawLabel && rawLabel !== timeStr && normalizedLabel !== normalizedTime && rawLabel !== tc.originalText && rawLabel.length > 0;
const displayLabel = hasCustomLabel ? rawLabel : "";
const escapeMap = {
"<": "&lt;",
">": "&gt;",
"&": "&amp;",
'"': "&quot;",
"'": "&#39;"
};
const safeLabel = displayLabel.replace(/[<>&"']/g, c => escapeMap[c]);
const isEditable = !tc.isChapter || tc.isUserAdded;
return `\n          <div class="timecode-item ${tc.isChapter ? "has-chapter" : ""}" data-time="${tc.time}" data-index="${i}">\n            <div class="timecode-time">${timeStr}</div>\n            ${safeLabel ? `<div class="timecode-label" title="${safeLabel}">${safeLabel}</div>` : ""}\n            <div class="timecode-progress"></div>\n            ${isEditable ? `\n              <div class="timecode-actions">\n                <button class="timecode-action edit" data-action="edit" title="${t("edit")}">✎</button>\n                <button class="timecode-action delete" data-action="delete" title="${t("delete")}">✕</button>\n              </div>\n            ` : ""}\n          </div>\n        `;
}).join(""));
};
const updateActiveItem = activeItem => {
const items = state.dom.list?.querySelectorAll(".timecode-item");
if (items) {
items.forEach(item => item.classList.remove("active", "pulse"));
if (activeItem) {
activeItem.classList.add("active", "pulse");
setTimeout(() => activeItem.classList.remove("pulse"), 800);
}
}
};
const startTracking = () => {
if (state.trackingId) {
return;
}
const track = () => {
try {
const video = $("video");
const {panel, currentTime, list} = state.dom;
if (!video || !panel || panel.classList.contains("hidden") || !config.autoTrackPlayback) {
if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
return;
}
currentTime && !isNaN(video.currentTime) && (currentTime.textContent = formatTime(video.currentTime));
const items = list?.querySelectorAll(".timecode-item");
if (items?.length) {
let activeIndex = -1;
let nextIndex = -1;
for (let i = 0; i < items.length; i++) {
const timeData = items[i].dataset.time;
if (!timeData) {
continue;
}
const time = parseFloat(timeData);
isNaN(time) || (video.currentTime >= time ? activeIndex = i : -1 === nextIndex && (nextIndex = i));
}
if (state.activeIndex !== activeIndex) {
null !== state.activeIndex && state.activeIndex >= 0 && items[state.activeIndex] && items[state.activeIndex].classList.remove("active");
if (activeIndex >= 0 && items[activeIndex]) {
items[activeIndex].classList.add("active");
try {
items[activeIndex].scrollIntoView({
behavior: "smooth",
block: "center"
});
} catch (e) {
items[activeIndex].scrollIntoView(!1);
}
}
state.activeIndex = activeIndex;
}
if (activeIndex >= 0 && nextIndex >= 0 && items[activeIndex]) {
const currentTimeData = items[activeIndex].dataset.time;
const nextTimeData = items[nextIndex].dataset.time;
if (currentTimeData && nextTimeData) {
const current = parseFloat(currentTimeData);
const next = parseFloat(nextTimeData);
if (!isNaN(current) && !isNaN(next) && next > current) {
const progress = (video.currentTime - current) / (next - current) * 100;
const progressEl = items[activeIndex].querySelector(".timecode-progress");
if (progressEl) {
const clampedProgress = Math.min(100, Math.max(0, progress));
progressEl.style.width = `${clampedProgress}%`;
}
}
}
}
}
config.autoTrackPlayback && (state.trackingId = requestAnimationFrame(track));
} catch (error) {
window.console.warn("Timecode tracking error:", error);
if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
}
};
state.trackingId = requestAnimationFrame(track);
};
const makeDraggable = panel => {
const header = panel.querySelector("#timecode-header");
if (!header) {
return;
}
let startX;
let startY;
let startLeft;
let startTop;
YouTubeUtils.cleanupManager.registerListener(header, "mousedown", e => {
if (0 !== e.button) {
return;
}
state.dragging = !0;
startX = e.clientX;
startY = e.clientY;
const rect = panel.getBoundingClientRect();
panel.style.left || (panel.style.left = `${rect.left}px`);
panel.style.top || (panel.style.top = `${rect.top}px`);
panel.style.right = "auto";
startLeft = parseFloat(panel.style.left) || rect.left;
startTop = parseFloat(panel.style.top) || rect.top;
const handleMove = event => {
if (!state.dragging) {
return;
}
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
const {left, top} = clampPanelPosition(panel, startLeft + deltaX, startTop + deltaY);
panel.style.left = `${left}px`;
panel.style.top = `${top}px`;
panel.style.right = "auto";
};
const handleUp = () => {
if (!state.dragging) {
return;
}
state.dragging = !1;
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleUp);
const rectAfter = panel.getBoundingClientRect();
const {left, top} = clampPanelPosition(panel, rectAfter.left, rectAfter.top);
panel.style.left = `${left}px`;
panel.style.top = `${top}px`;
panel.style.right = "auto";
savePanelPosition(left, top);
};
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleUp);
});
};
const saveTimecodesToStorage = timecodes => {
const videoId = new URLSearchParams(window.location.search).get("v");
if (videoId) {
try {
const minimal = timecodes.map(tc => ({
t: tc.time,
l: tc.label?.trim() || "",
c: tc.isChapter || !1,
u: tc.isUserAdded || !1
}));
localStorage.setItem(`yt_tc_${videoId}`, JSON.stringify(minimal));
} catch (e) {}
}
};
const loadTimecodesFromStorage = () => {
const videoId = new URLSearchParams(window.location.search).get("v");
if (!videoId) {
return null;
}
try {
const data = localStorage.getItem(`yt_tc_${videoId}`);
return data ? JSON.parse(data).map(tc => ({
time: tc.t,
label: tc.l,
isChapter: tc.c,
isUserAdded: tc.u || !1
})).sort((a, b) => a.time - b.time) : null;
} catch (e) {
return null;
}
};
const getCurrentTimecodes = () => {
const items = state.dom.list?.querySelectorAll(".timecode-item");
return items ? Array.from(items).map(item => {
const time = parseFloat(item.dataset.time);
const labelEl = item.querySelector(".timecode-label");
const label = labelEl?.textContent?.trim() || "";
return {
time,
label,
isChapter: item.classList.contains("has-chapter"),
isUserAdded: !item.classList.contains("has-chapter") || !1
};
}).sort((a, b) => a.time - b.time) : [];
};
const toggleTimecodePanel = show => {
$$("#timecode-panel").forEach(panel => {
panel !== state.dom.panel && panel.remove();
});
const panel = state.dom.panel || (() => {
if (state.dom.panel) {
return state.dom.panel;
}
$$("#timecode-panel").forEach(p => p.remove());
const panel = document.createElement("div");
panel.id = "timecode-panel";
panel.className = config.enabled ? "" : "hidden";
config.autoTrackPlayback && panel.classList.add("auto-tracking");
renderTemplateClone(panel, `\n        <div id="timecode-header">\n          <h3 id="timecode-title">\n            <div id="timecode-tracking-indicator"></div>\n            ${t("timecodes")}\n            <span id="timecode-current-time"></span>\n          </h3>\n          <div id="timecode-header-controls">\n            <button id="timecode-reload" title="${t("reload")}" aria-label="${t("reload")}">\n              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                <path d="M21.5 2v6h-6M2.5 22v-6h6"/>\n                <path d="M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1"/>\n              </svg>\n            </button>\n            <button id="timecode-close" title="${t("close")}" aria-label="${t("close")}">\n              <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                <path d="M6 6L18 18M18 6L6 18"/>\n              </svg>\n            </button>\n          </div>\n        </div>\n        <div id="timecode-list"></div>\n        <div id="timecode-empty">\n          <div>${t("noTimecodesFound")}</div>\n          <div style="margin-top:5px;font-size:12px">${t("clickToAdd")}</div>\n        </div>\n        <div id="timecode-form">\n          <input type="text" id="timecode-form-time" placeholder="${t("timePlaceholder")}">\n          <input type="text" id="timecode-form-label" placeholder="${t("labelPlaceholder")}">\n          <div id="timecode-form-buttons">\n            <button type="button" id="timecode-form-cancel">${t("cancel")}</button>\n            <button type="button" id="timecode-form-save" class="save">${t("save")}</button>\n          </div>\n        </div>\n        <div id="timecode-actions">\n          <button id="timecode-add-btn">${t("add")}</button>\n          <button id="timecode-export-btn" ${config.export ? "" : 'style="display:none"'}>${t("export")}</button>\n          <button id="timecode-track-toggle" class="${config.autoTrackPlayback ? "active" : ""}">${t(config.autoTrackPlayback ? "tracking" : "track")}</button>\n        </div>\n      `);
state.dom = {
panel,
list: panel.querySelector("#timecode-list"),
empty: panel.querySelector("#timecode-empty"),
form: panel.querySelector("#timecode-form"),
timeInput: panel.querySelector("#timecode-form-time"),
labelInput: panel.querySelector("#timecode-form-label"),
currentTime: panel.querySelector("#timecode-current-time"),
trackToggle: panel.querySelector("#timecode-track-toggle"),
reloadButton: panel.querySelector("#timecode-reload")
};
panel.addEventListener("click", handlePanelClick);
makeDraggable(panel);
document.body.appendChild(panel);
applySavedPanelPosition(panel);
return panel;
})();
void 0 === show && (show = panel.classList.contains("hidden"));
panel.classList.toggle("hidden", !show);
if (show) {
applySavedPanelPosition(panel);
const saved = loadTimecodesFromStorage();
saved?.length ? updateTimecodePanel(saved) : config.autoDetect && detectTimecodes().catch(err => window.console.error("[Timecode] Detection failed:", err));
config.autoTrackPlayback && startTracking();
} else if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
};
const cleanup = () => {
(() => {
if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
})();
if (state.dom.panel) {
state.dom.panel.remove();
state.dom.panel = null;
}
};
const init = () => {
if (initStarted) {
return;
}
if (!isRelevantRoute()) {
return;
}
const appRoot = "function" == typeof YouTubeUtils?.querySelector && YouTubeUtils.querySelector("ytd-app") || $("ytd-app");
if (!appRoot) {
(() => {
const retryScheduler = YouTubeUtils.createRetryScheduler;
if ("function" == typeof retryScheduler) {
retryScheduler({
label: "timecode-init",
interval: 120,
maxAttempts: 30,
check: () => {
const root = "function" == typeof YouTubeUtils.querySelector && YouTubeUtils.querySelector("ytd-app") || $("ytd-app");
if (!root) {
return !1;
}
init();
return !0;
}
});
return;
}
const rafId = requestAnimationFrame(init);
YouTubeUtils.cleanupManager?.registerAnimationFrame?.(rafId);
})();
return;
}
initStarted = !0;
loadSettings();
(() => {
if (byId("timecode-panel-styles")) {
return;
}
YouTubeUtils.StyleManager.add("timecode-panel-styles", "\n      html[dark],body[dark]{--yt-timecode-panel-bg:var(--yt-timecode-panel-bg-dark);--yt-timecode-panel-border:var(--yt-timecode-panel-border-dark);--yt-timecode-panel-color:var(--yt-timecode-panel-color-dark)}\n      html:not([dark]){--yt-timecode-panel-bg:var(--yt-timecode-panel-bg-light);--yt-timecode-panel-border:var(--yt-timecode-panel-border-light);--yt-timecode-panel-color:var(--yt-timecode-panel-color-light)}\n      #timecode-panel{position:fixed;right:20px;top:80px;background:var(--yt-timecode-panel-bg);border-radius:16px;box-shadow:0 12px 40px var(--yt-timecode-panel-shadow);width:320px;max-height:70vh;z-index:10000;color:var(--yt-timecode-panel-color);backdrop-filter:blur(14px) saturate(140%);-webkit-backdrop-filter:blur(14px) saturate(140%);border:1.5px solid var(--yt-timecode-panel-border);transition:transform .28s cubic-bezier(.4,0,.2,1),opacity .28s;overflow:hidden;display:flex;flex-direction:column}\n        #timecode-panel.hidden{transform:translateX(300px);opacity:0;pointer-events:none}\n      #timecode-panel.auto-tracking{box-shadow:0 12px 48px var(--yt-danger-ghost);border-color:var(--yt-danger-border)}\n      #timecode-header{display:flex;justify-content:space-between;align-items:center;padding:14px;border-bottom:1px solid var(--yt-surface-overlay-subtle);background:linear-gradient(180deg, var(--yt-surface-overlay-faint), transparent);cursor:move}\n        #timecode-title{font-weight:600;margin:0;font-size:15px;user-select:none;display:flex;align-items:center;gap:8px}\n      #timecode-tracking-indicator{width:8px;height:8px;background:var(--yt-accent);border-radius:50%;opacity:0;transition:opacity .3s}\n        #timecode-panel.auto-tracking #timecode-tracking-indicator{opacity:1}\n      #timecode-current-time{font-family:monospace;font-size:12px;padding:2px 6px;background:var(--yt-danger-border);border-radius:3px;margin-left:auto}\n        #timecode-header-controls{display:flex;align-items:center;gap:6px}\n        #timecode-reload,#timecode-close{background:transparent;border:none;color:inherit;cursor:pointer;width:28px;height:28px;padding:0;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:background .18s,color .18s}\n      #timecode-header-controls svg{width:16px;height:16px;display:block;flex-shrink:0}\n      #timecode-header-controls svg path{vector-effect:non-scaling-stroke}\n      #timecode-reload:hover,#timecode-close:hover{background:var(--yt-surface-overlay-subtle)}\n        #timecode-reload.loading{animation:spin .8s linear infinite}\n      #timecode-list{overflow-y:auto;padding:8px 0;max-height:calc(70vh - 80px);scrollbar-width:thin;scrollbar-color:var(--yt-scrollbar-outline) transparent}\n        #timecode-list::-webkit-scrollbar{width:6px}\n      #timecode-list::-webkit-scrollbar-thumb{background:var(--yt-scrollbar-outline);border-radius:3px}\n        .timecode-item{padding:10px 14px;display:flex;align-items:center;cursor:pointer;transition:background-color .16s,transform .12s;border-left:3px solid transparent;position:relative;border-radius:8px;margin:6px 10px}\n      .timecode-item:hover{background:var(--yt-surface-overlay-subtle);transform:translateY(-2px)}\n        .timecode-item:hover .timecode-actions{opacity:1}\n      .timecode-item.active{background:linear-gradient(90deg, var(--yt-timecode-active-bg-start), var(--yt-timecode-active-bg-end));border-left-color:var(--yt-timecode-active-border);box-shadow:inset 0 0 0 1px var(--yt-timecode-active-inset)}\n        .timecode-item.active.pulse{animation:timecodePulse .8s ease-out}\n      .timecode-item.editing{background:linear-gradient(90deg, var(--yt-warning-soft), var(--yt-panel-overlay-weak));border-left-color:var(--yt-warning)}\n        .timecode-item.editing .timecode-actions{opacity:1}\n        @keyframes timecodePulse{0%{transform:scale(1)}50%{transform:scale(1.02)}100%{transform:scale(1)}}\n        /* spin keyframe defined in shared-keyframes (basic.js) */\n      .timecode-time{font-family:monospace;margin-right:10px;color:var(--yt-text-secondary);font-size:13px;min-width:45px;flex-shrink:0}\n        .timecode-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:13px;flex:1;margin-left:4px}\n        .timecode-item:not(:has(.timecode-label)) .timecode-time{flex:1;text-align:left}\n      .timecode-item.has-chapter .timecode-time{color:var(--yt-timecode-chapter)}\n      .timecode-progress{width:0;height:2px;background:var(--yt-timecode-chapter);position:absolute;bottom:0;left:0;transition:width .3s;opacity:.8}\n      .timecode-actions{position:absolute;right:8px;top:50%;transform:translateY(-50%);display:flex;gap:4px;opacity:0;transition:opacity .2s;background:var(--yt-overlay-strong);border-radius:4px;padding:2px}\n      .timecode-action{background:none;border:none;color:var(--yt-text-secondary);cursor:pointer;padding:4px;font-size:12px;border-radius:2px;transition:color .2s,background-color .2s}\n      .timecode-action:hover{color:var(--yt-text-primary);background:var(--yt-button-bg)}\n      .timecode-action.edit:hover{color:var(--yt-warning)}\n      .timecode-action.delete:hover{color:var(--yt-timecode-chapter)}\n      #timecode-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center;color:var(--yt-text-secondary);font-size:13px}\n      #timecode-form{padding:12px;border-top:1px solid var(--yt-surface-overlay-subtle);display:none}\n        #timecode-form.visible{display:block}\n      #timecode-form input{width:100%;margin-bottom:8px;padding:8px;background:var(--yt-input-bg);border:1px solid var(--yt-glass-border);border-radius:4px;color:var(--yt-text-primary);font-size:13px}\n      #timecode-form input::placeholder{color:var(--yt-text-secondary)}\n        #timecode-form-buttons{display:flex;gap:8px;justify-content:flex-end}\n        #timecode-form-buttons button{padding:6px 12px;border:none;border-radius:4px;cursor:pointer;font-size:12px;transition:background-color .2s}\n      #timecode-form-cancel{background:var(--yt-button-bg);color:var(--yt-text-primary)}\n      #timecode-form-cancel:hover{background:var(--yt-hover-bg)}\n      #timecode-form-save{background:var(--yt-timecode-chapter);color:var(--yt-text-primary)}\n      #timecode-form-save:hover{background:var(--yt-timecode-active-border)}\n      #timecode-actions{padding:10px;border-top:1px solid var(--yt-surface-overlay-subtle);display:flex;gap:8px;background:linear-gradient(180deg,transparent,var(--yt-panel-overlay-subtle))}\n      #timecode-actions button{padding:8px 12px;border:none;border-radius:8px;cursor:pointer;font-size:13px;transition:background .18s;color:inherit;background:var(--yt-surface-overlay-faint)}\n      #timecode-actions button:hover{background:var(--yt-surface-overlay-subtle)}\n      #timecode-track-toggle.active{background:linear-gradient(90deg,var(--yt-timecode-toggle-active-start),var(--yt-timecode-chapter));color:var(--yt-text-primary)}\n      ");
})();
(() => {
const keydownHandler = e => {
if (!config.enabled) {
return;
}
const target = e.target;
if (target.matches && target.matches("input, textarea, [contenteditable]")) {
return;
}
const {key, shiftKey, altKey, ctrlKey} = config.shortcut;
if (e.key.toUpperCase() === key && e.shiftKey === shiftKey && e.altKey === altKey && e.ctrlKey === ctrlKey) {
e.preventDefault();
toggleTimecodePanel();
}
};
window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(document, "keydown", keydownHandler) : document.addEventListener("keydown", keydownHandler);
})();
(() => {
let currentVideoId = new URLSearchParams(window.location.search).get("v");
const handleNavigationChange = () => {
const newVideoId = new URLSearchParams(window.location.search).get("v");
if (newVideoId !== currentVideoId && "/watch" === window.location.pathname) {
currentVideoId = newVideoId;
state.activeIndex = null;
state.editingIndex = null;
state.timecodes.clear();
if (config.enabled && state.dom.panel && !state.dom.panel.classList.contains("hidden")) {
const saved = loadTimecodesFromStorage();
saved?.length ? updateTimecodePanel(saved) : config.autoDetect && setTimeout(() => detectTimecodes().catch(err => window.console.error("[Timecode] Detection failed:", err)), 500);
config.autoTrackPlayback && startTracking();
}
}
};
window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", handleNavigationChange) : document.addEventListener("yt-navigate-finish", handleNavigationChange);
})();
let modalObserverSubId = null;
let modalObserverTimeout = null;
const settingsModalHandler = () => {
const modal = $(".ytp-plus-settings-modal");
if (modal) {
(modalEl => {
if (!(modalEl && modalEl instanceof Element)) {
return;
}
const coordinator = window.YouTubeMutationCoordinator;
if (modalObserverSubId && coordinator?.unsubscribe) {
try {
coordinator.unsubscribe(modalObserverSubId);
} catch (e) {}
modalObserverSubId = null;
}
if (coordinator?.watchTarget) {
modalObserverSubId = `timecode::settingsModal::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`;
coordinator.watchTarget(modalObserverSubId, modalEl, () => {
modalObserverTimeout || (modalObserverTimeout = setTimeout(() => {
modalObserverTimeout = null;
$('.ytp-plus-settings-section[data-section="advanced"]:not(.hidden)') && !$(".timecode-settings-item") && setTimeout(() => ensureTimecodePanelSettings(), 50);
}, 30));
}, {
childList: !0,
subtree: !0,
attributes: !0,
attributeFilter: [ "class" ]
});
YouTubeUtils.cleanupManager?.register && YouTubeUtils.cleanupManager.register(() => {
if (modalObserverSubId && coordinator?.unsubscribe) {
coordinator.unsubscribe(modalObserverSubId);
modalObserverSubId = null;
}
});
}
})(modal);
setTimeout(() => ensureTimecodePanelSettings(), 100);
}
};
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", settingsModalHandler) : document.addEventListener("youtube-plus-settings-modal-opened", settingsModalHandler);
YouTubeUtils.cleanupManager.registerListener(document, "click", e => {
const target = e.target;
const navItem = target?.closest?.(".ytp-plus-settings-nav-item");
"advanced" === navItem?.dataset?.section && setTimeout(() => ensureTimecodePanelSettings(), 50);
}, !0);
if (config.enabled && !state.resizeListenerKey) {
const onResize = YouTubeUtils.throttle(() => {
if (!state.dom.panel) {
return;
}
const rect = state.dom.panel.getBoundingClientRect();
const {left, top} = clampPanelPosition(state.dom.panel, rect.left, rect.top);
state.dom.panel.style.left = `${left}px`;
state.dom.panel.style.top = `${top}px`;
state.dom.panel.style.right = "auto";
savePanelPosition(left, top);
}, 200);
state.resizeListenerKey = YouTubeUtils.cleanupManager.registerListener(window, "resize", onResize);
}
};
const handleNavigate = () => {
(() => {
if (state.settingsIntegrationStarted) {
return;
}
state.settingsIntegrationStarted = !0;
loadSettings();
const settingsModalHandler = () => {
setTimeout(() => ensureTimecodePanelSettings(), 100);
};
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", settingsModalHandler) : document.addEventListener("youtube-plus-settings-modal-opened", settingsModalHandler);
const clickHandler = e => {
const target = e.target;
const navItem = target?.closest?.(".ytp-plus-settings-nav-item");
"advanced" === navItem?.dataset?.section && setTimeout(() => ensureTimecodePanelSettings(), 50);
};
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "click", clickHandler, !0) : document.addEventListener("click", clickHandler, !0);
})();
isRelevantRoute() ? init() : initStarted && cleanup();
};
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("timecode", handleNavigate, {
priority: 1,
shouldLoad: () => isRelevantRoute() || (() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()
}) : "loading" === document.readyState ? document.addEventListener("DOMContentLoaded", handleNavigate, {
once: !0
}) : handleNavigate();
"function" == typeof window.YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", handleNavigate, {
passive: !0
}) : document.addEventListener("yt-navigate-finish", handleNavigate, {
passive: !0
});
window.addEventListener("beforeunload", cleanup);
})();

!(function() {
"use strict";
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
let featureEnabled = !0;
const setFeatureEnabled = nextEnabled => {
featureEnabled = !1 !== nextEnabled;
if (featureEnabled) {
ensureInit();
handleNavigation();
} else {
cleanup();
}
};
featureEnabled = window.YouTubeUtils?.loadFeatureEnabled?.("enablePlaylistSearch") ?? !0;
if (window._playlistSearchInitialized) {
return;
}
window._playlistSearchInitialized = !0;
const qs = window.YouTubeUtils.$;
const qsAll = window.YouTubeUtils.$$;
const byId = window.YouTubeUtils.byId;
const t = window.YouTubeUtils.t;
const shouldRunOnThisPage = () => window.location.hostname.endsWith("youtube.com") && "music.youtube.com" !== window.location.hostname && ("/watch" === window.location.pathname || "/playlist" === window.location.pathname);
const isPlaylistPage = () => "/playlist" === window.location.pathname;
const isRelevantRoute = () => {
if (!shouldRunOnThisPage()) {
return !1;
}
try {
const params = new URLSearchParams(window.location.search);
return params.has("list");
} catch (e) {
return !1;
}
};
const debounce = window.YouTubeUtils.debounce;
const throttle = window.YouTubeUtils.throttle || window._ytpDefaults?.throttle;
const config = {
enabled: !0,
storageKey: "youtube_playlist_search_settings",
searchDebounceMs: 150,
observerThrottleMs: 300,
maxPlaylistItems: 1e4,
maxQueryLength: 300,
deleteDelay: 250
};
const state = {
searchInput: null,
searchResults: null,
originalItems: [],
currentPlaylistId: null,
observerFallbackTimerId: null,
rafId: null,
itemsCache: new Map,
itemsContainer: null,
itemSelector: null,
itemTagName: null,
playlistPanel: null,
isPlaylistPage: !1,
isDeleting: !1,
deleteMode: !1,
selectedItems: new Set,
rootSubId: null
};
const inputDebouncers = new WeakMap;
const setupInputDelegation = (() => {
let attached = !1;
return () => {
if (attached) {
return;
}
attached = !0;
const handleFocus = input => {
input.style.borderColor = "var(--yt-spec-call-to-action)";
};
const handleBlur = input => {
input.style.borderColor = "var(--yt-spec-10-percent-layer)";
};
const handleInput = input => {
let debounced = inputDebouncers.get(input);
if (!debounced) {
debounced = debounce(value => {
if (value.length > config.maxQueryLength) {
const truncated = value.substring(0, config.maxQueryLength);
input.value = truncated;
filterPlaylistItems(truncated);
return;
}
filterPlaylistItems(value);
}, config.searchDebounceMs);
inputDebouncers.set(input, debounced);
}
debounced(input.value || "");
};
const delegator = window.YouTubePlusEventDelegation;
if (delegator?.on) {
delegator.on(document, "focusin", ".ytplus-playlist-search-input", (ev, target) => {
target && handleFocus(target);
});
delegator.on(document, "focusout", ".ytplus-playlist-search-input", (ev, target) => {
target && handleBlur(target);
});
delegator.on(document, "input", ".ytplus-playlist-search-input", (ev, target) => {
target && handleInput(target);
});
} else {
document.addEventListener("focusin", ev => {
const source = ev.target instanceof Element ? ev.target : null;
const target = source?.closest(".ytplus-playlist-search-input");
target && handleFocus(target);
}, !0);
document.addEventListener("focusout", ev => {
const source = ev.target instanceof Element ? ev.target : null;
const target = source?.closest(".ytplus-playlist-search-input");
target && handleBlur(target);
}, !0);
document.addEventListener("input", ev => {
const source = ev.target instanceof Element ? ev.target : null;
const target = source?.closest(".ytplus-playlist-search-input");
target && handleInput(target);
}, !0);
}
};
})();
const getCurrentPlaylistId = () => {
try {
const urlParams = new URLSearchParams(window.location.search);
const listId = urlParams.get("list");
return listId && /^[a-zA-Z0-9_-]+$/.test(listId) ? listId : null;
} catch (error) {
window.console.warn("[Playlist Search] Failed to get playlist ID:", error);
return null;
}
};
const getPlaylistContext = () => {
if (isPlaylistPage()) {
const panel = qs("ytd-playlist-video-list-renderer");
if (!panel) {
return null;
}
const itemsContainer = panel.querySelector("#contents") || panel.querySelector("ytd-playlist-video-list-renderer #contents");
return {
panel,
itemsContainer,
itemSelector: "ytd-playlist-video-renderer",
itemTagName: "YTD-PLAYLIST-VIDEO-RENDERER",
isPlaylistPage: !0
};
}
if (!0 === window.YouTubeUtils?.isWatchPage?.()) {
const panel = qs("ytd-playlist-panel-renderer");
if (!panel) {
return null;
}
const itemsContainer = panel.querySelector("#items") || panel.querySelector(".playlist-items.style-scope.ytd-playlist-panel-renderer") || panel.querySelector(".playlist-items");
return {
panel,
itemsContainer,
itemSelector: "ytd-playlist-panel-video-renderer",
itemTagName: "YTD-PLAYLIST-PANEL-VIDEO-RENDERER",
isPlaylistPage: !1
};
}
return null;
};
const addSearchUI = () => {
if (!config.enabled) {
return;
}
if (!shouldRunOnThisPage()) {
return;
}
const playlistId = getCurrentPlaylistId();
if (!playlistId) {
return;
}
const context = getPlaylistContext();
if (!context) {
return;
}
const {panel: playlistPanel, itemsContainer, itemSelector, itemTagName} = context;
if (playlistPanel.querySelector(".ytplus-playlist-search")) {
return;
}
state.currentPlaylistId = playlistId;
state.itemsContainer = itemsContainer || null;
state.itemSelector = itemSelector;
state.itemTagName = itemTagName;
state.playlistPanel = playlistPanel;
state.isPlaylistPage = context.isPlaylistPage;
const searchContainer = document.createElement("div");
searchContainer.className = "ytplus-playlist-search";
searchContainer.style.cssText = "\n      padding: 8px 16px;\n      background: transparent;\n      border-bottom: 1px solid var(--yt-spec-10-percent-layer);\n      z-index: 50;\n      width: 94%;\n    ";
setTimeout(() => {
try {
if (!state.isPlaylistPage) {
searchContainer.style.position = "sticky";
searchContainer.style.top = "0";
searchContainer.style.zIndex = "1";
searchContainer.style.background = "transparent";
return;
}
const panel = state.playlistPanel || getPlaylistContext()?.panel || null;
const topOffset = state.isPlaylistPage ? 84 : 8;
let scrollAncestor = panel;
for (;scrollAncestor && scrollAncestor !== document.body; ) {
const style = window.getComputedStyle(scrollAncestor);
const overflowY = style.overflowY;
if (("auto" === overflowY || "scroll" === overflowY) && scrollAncestor.scrollHeight > scrollAncestor.clientHeight) {
break;
}
scrollAncestor = scrollAncestor.parentElement;
}
if (scrollAncestor && scrollAncestor !== document.body) {
searchContainer.style.position = "sticky";
searchContainer.style.top = `${topOffset}px`;
searchContainer.style.background = "var(--yt-spec-badge-chip-background)";
searchContainer.style.backdropFilter = "blur(6px)";
searchContainer.style.boxShadow = "var(--yt-shadow)";
} else if (panel) {
const rect = panel.getBoundingClientRect();
searchContainer.style.position = "fixed";
searchContainer.style.top = `${topOffset}px`;
searchContainer.style.left = `${rect.left}px`;
searchContainer.style.width = `${rect.width}px`;
searchContainer.style.background = "var(--yt-spec-badge-chip-background)";
searchContainer.style.backdropFilter = "blur(6px)";
searchContainer.style.boxShadow = "0 6px 20px rgba(0,0,0,0.4)";
searchContainer.style.zIndex = "9999";
const recompute = debounce(() => {
const r = panel.getBoundingClientRect();
searchContainer.style.left = `${r.left}px`;
searchContainer.style.width = `${r.width}px`;
}, 120);
window.addEventListener("resize", recompute, {
passive: !0
});
window.addEventListener("scroll", recompute, {
passive: !0
});
} else {
searchContainer.style.position = "sticky";
searchContainer.style.top = `${topOffset}px`;
searchContainer.style.background = "var(--yt-spec-badge-chip-background)";
}
} catch (e) {}
}, 100);
const searchInput = document.createElement("input");
searchInput.type = "text";
const playlistName = ((playlistPanel, listId) => {
try {
const sel = [ "ytd-playlist-header-renderer #title", "ytd-playlist-header-renderer .title", ".title", "h3 a", "#header-title", "#title", ".playlist-title", "h1.title" ];
for (const s of sel) {
const el = playlistPanel.querySelector(s) || qs(s);
if (el && el.textContent && el.textContent.trim()) {
const title = el.textContent.trim();
return title.length > 100 ? title.substring(0, 100) + "..." : title;
}
}
const meta = qs('meta[name="title"]') || qs('meta[property="og:title"]');
if (meta && meta.content) {
const title = meta.content.trim();
return title.length > 100 ? title.substring(0, 100) + "..." : title;
}
} catch (error) {
window.console.warn("[Playlist Search] Failed to get display name:", error);
}
return listId && "string" == typeof listId ? listId.substring(0, 50) : "playlist";
})(playlistPanel, playlistId);
const placeholderKey = state.isPlaylistPage ? "searchPlaceholderPlaylistPage" : "searchPlaceholder";
searchInput.placeholder = t(placeholderKey, {
playlist: playlistName
});
searchInput.className = "ytplus-playlist-search-input";
searchInput.style.cssText = "\n      width: 93%;\n      padding: 8px 16px;\n      border: 1px solid var(--yt-spec-10-percent-layer);\n      border-radius: 20px;\n      background: var(--yt-spec-badge-chip-background);\n      color: var(--yt-text-primary);\n      font-size: 14px;\n      font-family: 'Roboto', Arial, sans-serif;\n      outline: none;\n      transition: border-color 0.2s;\n    ";
setupInputDelegation();
searchContainer.appendChild(searchInput);
state.searchInput = searchInput;
if (itemsContainer) {
const firstVideo = itemsContainer.querySelector(itemSelector);
firstVideo && firstVideo.parentElement === itemsContainer ? itemsContainer.insertBefore(searchContainer, firstVideo) : itemsContainer.appendChild(searchContainer);
} else {
playlistPanel.firstChild ? playlistPanel.insertBefore(searchContainer, playlistPanel.firstChild) : playlistPanel.appendChild(searchContainer);
}
collectOriginalItems();
addDeleteUI(searchContainer);
setupPlaylistObserver();
};
const setupPlaylistObserver = () => {
if (state.rootSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(state.rootSubId);
state.rootSubId = null;
}
if (state.observerFallbackTimerId) {
clearInterval(state.observerFallbackTimerId);
state.observerFallbackTimerId = null;
}
const playlistPanel = state.playlistPanel || getPlaylistContext()?.panel;
if (!playlistPanel || !state.itemTagName || !state.itemSelector) {
return;
}
let lastUpdateCount = state.originalItems.length;
let updateScheduled = !1;
const itemTagName = state.itemTagName;
const itemSelector = state.itemSelector;
const itemsRoot = state.itemsContainer || playlistPanel;
const handleMutations = throttle(mutations => {
if (updateScheduled) {
return;
}
const hasRelevantChange = mutations.some(mutation => {
if ("childList" !== mutation.type) {
return !1;
}
if (0 === mutation.addedNodes.length && 0 === mutation.removedNodes.length) {
return !1;
}
for (let i = 0; i < mutation.addedNodes.length; i++) {
const node = mutation.addedNodes[i];
if (1 === node.nodeType) {
const element = node;
if (element.tagName === itemTagName) {
return !0;
}
}
}
for (let i = 0; i < mutation.removedNodes.length; i++) {
const node = mutation.removedNodes[i];
if (1 === node.nodeType) {
const element = node;
if (element.tagName === itemTagName) {
return !0;
}
}
}
return !1;
});
if (hasRelevantChange) {
updateScheduled = !0;
requestAnimationFrame(() => {
const currentCount = lastUpdateCount;
const newItems = itemsRoot ? itemsRoot.querySelectorAll(itemSelector) : [];
if (newItems.length !== currentCount) {
lastUpdateCount = newItems.length;
collectOriginalItems();
state.searchInput && state.searchInput.value && filterPlaylistItems(state.searchInput.value);
}
updateScheduled = !1;
});
}
}, config.observerThrottleMs);
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.subscribeRoot ? state.rootSubId = coordinator.subscribeRoot("playlist-search-items", handleMutations, {
selector: itemSelector
}) : state.observerFallbackTimerId = setInterval(() => {
try {
const currentItemsRoot = state.itemsContainer || state.playlistPanel || playlistPanel;
if (!currentItemsRoot || !state.itemSelector) {
return;
}
const currentItems = currentItemsRoot.querySelectorAll(state.itemSelector);
if (currentItems.length !== state.originalItems.length) {
collectOriginalItems();
state.searchInput && state.searchInput.value && filterPlaylistItems(state.searchInput.value);
}
} catch (e) {}
}, config.observerThrottleMs);
};
const collectOriginalItems = () => {
const itemsRoot = state.itemsContainer || state.playlistPanel;
if (!itemsRoot || !state.itemSelector) {
return;
}
const items = itemsRoot.querySelectorAll(state.itemSelector);
items.length > config.maxPlaylistItems && window.console.warn(`[Playlist Search] Playlist has ${items.length} items, limiting to ${config.maxPlaylistItems}`);
const currentVideoIds = new Set;
const itemsArray = Array.from(items).slice(0, config.maxPlaylistItems);
state.originalItems = itemsArray.map((item, index) => {
const videoId = item.getAttribute("video-id") || `item-${index}`;
currentVideoIds.add(videoId);
if (state.itemsCache.has(videoId)) {
const cached = state.itemsCache.get(videoId);
if (cached && cached.element === item) {
return cached;
}
}
const titleEl = item.querySelector("#video-title") || item.querySelector("a#video-title");
const bylineEl = item.querySelector("#byline") || item.querySelector("#channel-name") || item.querySelector("ytd-channel-name a");
const title = titleEl?.textContent || "";
const channel = bylineEl?.textContent || "";
const itemData = {
element: item,
videoId,
titleOriginal: title,
channelOriginal: channel,
title: title.trim().toLowerCase(),
channel: channel.trim().toLowerCase()
};
state.itemsCache.set(videoId, itemData);
return itemData;
});
for (const [videoId] of state.itemsCache) {
currentVideoIds.has(videoId) || state.itemsCache.delete(videoId);
}
};
const filterPlaylistItems = query => {
state.rafId && cancelAnimationFrame(state.rafId);
if (query && "string" != typeof query) {
window.console.warn("[Playlist Search] Invalid query type");
return;
}
query && query.length > config.maxQueryLength && (query = query.substring(0, config.maxQueryLength));
if (!query || "" === query.trim()) {
state.rafId = requestAnimationFrame(() => {
state.originalItems.forEach(item => {
item.element.style.display = "";
});
state.rafId = null;
});
return;
}
const searchTerm = query.toLowerCase().trim();
let visibleCount = 0;
state.rafId = requestAnimationFrame(() => {
const updates = [];
state.originalItems.forEach(item => {
const matches = item.title.includes(searchTerm) || item.channel.includes(searchTerm);
if (matches) {
"none" === item.element.style.display && updates.push({
element: item.element,
display: ""
});
visibleCount++;
} else {
"none" !== item.element.style.display && updates.push({
element: item.element,
display: "none"
});
}
});
updates.forEach(update => {
update.element.style.display = update.display;
});
updateResultsCount(visibleCount, state.originalItems.length);
state.rafId = null;
});
};
const updateResultsCount = (visible, total) => {
window.YouTubeUtils?.logger?.debug?.(`[Playlist Search] Showing ${visible} of ${total} videos`);
};
const logError = (context, error) => {
const errorObj = error instanceof Error ? error : new Error(String(error));
window.YouTubeErrorBoundary ? window.YouTubeErrorBoundary.logError(errorObj, {
context
}) : window.console.error(`[YouTube+][PlaylistSearch] ${context}:`, error);
};
const withErrorBoundary = (fn, context) => window.YouTubeErrorBoundary?.withErrorBoundary ? window.YouTubeErrorBoundary.withErrorBoundary(fn, "PlaylistSearch") : (...args) => {
try {
return fn(...args);
} catch (e) {
logError(context, e);
return null;
}
};
const toggleDeleteMode = withErrorBoundary(() => {
state.deleteMode = !state.deleteMode;
state.selectedItems.clear();
const container = state.playlistPanel || getPlaylistContext()?.panel;
if (!container) {
return;
}
const toggleBtn = container.querySelector(".ytplus-playlist-delete-toggle");
const deleteBar = container.querySelector(".ytplus-playlist-delete-bar");
if (state.deleteMode) {
if (toggleBtn) {
const tb = toggleBtn;
tb.classList.add("active");
tb.setAttribute("aria-pressed", "true");
tb.title = t("playlistDeleteModeExit");
}
if (deleteBar) {
const db = deleteBar;
db.style && (db.style.display = "");
}
addCheckboxesToItems();
} else {
if (toggleBtn) {
const tb = toggleBtn;
tb.classList.remove("active");
tb.setAttribute("aria-pressed", "false");
tb.title = t("playlistDeleteMode");
}
if (deleteBar) {
const db = deleteBar;
db.style && (db.style.display = "none");
}
removeCheckboxesFromItems();
}
updateDeleteBarState();
}, "toggleDeleteMode");
const addCheckboxesToItems = withErrorBoundary(() => {
const itemsRoot = state.itemsContainer || state.playlistPanel;
if (!itemsRoot || !state.itemSelector) {
return;
}
const items = itemsRoot.querySelectorAll(state.itemSelector);
items.forEach((item, idx) => {
if (item.querySelector(".ytplus-playlist-item-checkbox")) {
return;
}
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.className = "ytplus-playlist-item-checkbox ytp-plus-settings-checkbox";
checkbox.setAttribute("aria-label", t("playlistSelectVideo"));
checkbox.dataset.index = String(idx);
checkbox.style.cssText = "\n        position: absolute;\n        top: 8px;\n        left: 8px;\n        z-index: 2;\n        cursor: pointer;\n      ";
checkbox.addEventListener("change", () => {
const videoId = item.getAttribute("video-id") || `item-${idx}`;
checkbox.checked ? state.selectedItems.add(videoId) : state.selectedItems.delete(videoId);
updateDeleteBarState();
});
checkbox.addEventListener("click", e => e.stopPropagation());
item.style.position = "relative";
item.insertBefore(checkbox, item.firstChild);
});
}, "addCheckboxesToItems");
const removeCheckboxesFromItems = withErrorBoundary(() => {
const itemsRoot = state.itemsContainer || state.playlistPanel;
if (itemsRoot) {
itemsRoot.querySelectorAll(".ytplus-playlist-item-checkbox").forEach(cb => cb.remove());
state.selectedItems.clear();
}
}, "removeCheckboxesFromItems");
const updateDeleteBarState = withErrorBoundary(() => {
const container = state.playlistPanel || getPlaylistContext()?.panel;
if (!container) {
return;
}
const deleteBtn = container.querySelector(".ytplus-playlist-delete-selected");
const countSpan = container.querySelector(".ytplus-playlist-selected-count");
if (deleteBtn) {
deleteBtn.disabled = 0 === state.selectedItems.size;
deleteBtn.style.opacity = state.selectedItems.size > 0 ? "1" : "0.5";
}
countSpan && (countSpan.textContent = t("playlistSelectedCount", {
count: state.selectedItems.size
}));
}, "updateDeleteBarState");
const selectAllItems = withErrorBoundary(() => {
const itemsRoot = state.itemsContainer || state.playlistPanel;
if (itemsRoot && state.itemSelector) {
itemsRoot.querySelectorAll(".ytplus-playlist-item-checkbox").forEach(cb => {
const item = cb.closest(state.itemSelector);
if (item && "none" !== item.style.display) {
cb.checked = !0;
const videoId = item.getAttribute("video-id") || `item-${cb.dataset.index}`;
state.selectedItems.add(videoId);
}
});
updateDeleteBarState();
}
}, "selectAllItems");
const clearAllItems = withErrorBoundary(() => {
const itemsRoot = state.itemsContainer || state.playlistPanel;
if (itemsRoot) {
itemsRoot.querySelectorAll(".ytplus-playlist-item-checkbox").forEach(cb => {
cb.checked = !1;
});
state.selectedItems.clear();
updateDeleteBarState();
}
}, "clearAllItems");
const removeItemViaMenu = item => new Promise(resolve => {
try {
const menuBtn = item.querySelector("button#button[aria-label]") || item.querySelector("yt-icon-button#button") || item.querySelector("ytd-menu-renderer button") || item.querySelector('[aria-haspopup="menu"]') || item.querySelector("button.yt-icon-button");
if (!menuBtn) {
window.console.warn("[Playlist Search] Could not find menu button for item");
resolve(!1);
return;
}
menuBtn.click();
setTimeout(() => {
try {
const menuItems = qsAll("tp-yt-paper-listbox ytd-menu-service-item-renderer, ytd-menu-popup-renderer ytd-menu-service-item-renderer, tp-yt-iron-dropdown ytd-menu-service-item-renderer");
let removeOption = null;
for (const mi of menuItems) {
const text = (mi.textContent || "").toLowerCase();
if (text.includes("remove") || text.includes("удалить") || text.includes("supprimer") || text.includes("entfernen") || text.includes("eliminar") || text.includes("rimuovi") || text.includes("kaldır") || text.includes("削除") || text.includes("삭제") || text.includes("移除") || text.includes("oʻchirish") || text.includes("жою") || text.includes("өчүрүү") || text.includes("выдаліць") || text.includes("премахване") || text.includes("xóa")) {
removeOption = mi;
break;
}
}
if (removeOption) {
removeOption.click();
setTimeout(() => {
document.body.click();
resolve(!0);
}, 100);
} else {
document.body.click();
window.console.warn('[Playlist Search] Could not find "Remove" option in menu');
resolve(!1);
}
} catch (err) {
document.body.click();
logError("removeItemViaMenu:findOption", err);
resolve(!1);
}
}, 350);
} catch (err) {
logError("removeItemViaMenu", err);
resolve(!1);
}
});
const deleteSelectedItems = withErrorBoundary(async () => {
if (state.isDeleting || 0 === state.selectedItems.size) {
return;
}
const count = state.selectedItems.size;
const confirmed = confirm(t("playlistDeleteConfirm", {
count
}));
if (!confirmed) {
return;
}
state.isDeleting = !0;
const itemsRoot = state.itemsContainer || state.playlistPanel;
if (!itemsRoot || !state.itemSelector) {
state.isDeleting = !1;
return;
}
const allItems = Array.from(itemsRoot.querySelectorAll(state.itemSelector));
const toDelete = allItems.filter((item, idx) => {
const videoId = item.getAttribute("video-id") || `item-${idx}`;
return state.selectedItems.has(videoId);
});
let successCount = 0;
let failCount = 0;
for (const item of toDelete) {
const result = await removeItemViaMenu(item);
result ? successCount++ : failCount++;
await new Promise(r => setTimeout(r, config.deleteDelay));
}
state.isDeleting = !1;
state.selectedItems.clear();
setTimeout(() => {
collectOriginalItems();
state.deleteMode && addCheckboxesToItems();
updateDeleteBarState();
}, 500);
const msg = failCount > 0 ? t("playlistDeletePartial", {
success: successCount,
fail: failCount
}) : t("playlistDeleteSuccess", {
count: successCount
});
window.YouTubeUtils?.logger?.debug?.(`[Playlist Search] ${msg}`);
}, "deleteSelectedItems");
const addDeleteUI = searchContainer => {
if (!searchContainer || searchContainer.querySelector(".ytplus-playlist-delete-toggle")) {
return;
}
addDeleteStyles();
const toggleBtn = document.createElement("button");
toggleBtn.type = "button";
toggleBtn.className = "ytplus-playlist-delete-toggle";
toggleBtn.setAttribute("aria-pressed", "false");
toggleBtn.setAttribute("aria-label", t("playlistDeleteMode"));
toggleBtn.title = t("playlistDeleteMode");
((container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
})(toggleBtn, '\n      <svg width="18" height="18" viewBox="0 0 24 24" fill="none"\n           stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n        <polyline points="3 6 5 6 21 6"/>\n        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>\n        <line x1="10" y1="11" x2="10" y2="17"/>\n        <line x1="14" y1="11" x2="14" y2="17"/>\n      </svg>\n    ');
toggleBtn.style.cssText = "\n      background: transparent;\n      border: 1px solid var(--yt-spec-10-percent-layer);\n      border-radius: 50%;\n      width: 36px;\n      height: 36px;\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      color: var(--yt-spec-text-secondary);\n      transition: all 0.2s;\n      vertical-align: middle;\n      margin-left: 6px;\n      flex-shrink: 0;\n    ";
toggleBtn.addEventListener("click", toggleDeleteMode);
const inputWrapper = document.createElement("div");
inputWrapper.style.cssText = "display:flex;align-items:center;gap:6px;";
const searchInput = searchContainer.querySelector(".ytplus-playlist-search-input");
if (searchInput) {
searchInput.style.width = "";
searchInput.style.flex = "1";
searchInput.parentNode && searchInput.parentNode.insertBefore(inputWrapper, searchInput);
inputWrapper.appendChild(searchInput);
inputWrapper.appendChild(toggleBtn);
}
const deleteBar = document.createElement("div");
deleteBar.className = "ytplus-playlist-delete-bar";
deleteBar.style.cssText = "\n      display: none;\n      padding: 6px 0 0;\n      gap: 8px;\n      align-items: center;\n      flex-wrap: wrap;\n    ";
deleteBar.style.display = "none";
const countSpan = document.createElement("span");
countSpan.className = "ytplus-playlist-selected-count";
countSpan.style.cssText = "\n      font-size: 12px;\n      color: var(--yt-spec-text-secondary);\n      margin-right: auto;\n    ";
countSpan.textContent = t("playlistSelectedCount", {
count: 0
});
const createBtn = (label, cls, onClick) => {
const btn = document.createElement("button");
btn.type = "button";
btn.textContent = label;
btn.className = cls;
btn.style.cssText = "\n        padding: 5px 12px;\n        border-radius: 16px;\n        border: 1px solid var(--yt-spec-10-percent-layer);\n        cursor: pointer;\n        font-size: 12px;\n        font-weight: 500;\n        background: var(--yt-spec-badge-chip-background);\n        color: var(--yt-spec-text-primary);\n        transition: all 0.2s;\n      ";
btn.addEventListener("click", onClick);
return btn;
};
const selectAllBtn = createBtn(t("selectAll"), "ytplus-playlist-select-all", selectAllItems);
const clearAllBtn = createBtn(t("clearAll"), "ytplus-playlist-clear-all", clearAllItems);
const deleteBtn = createBtn(t("deleteSelected"), "ytplus-playlist-delete-selected", deleteSelectedItems);
deleteBtn.disabled = !0;
deleteBtn.style.opacity = "0.5";
deleteBtn.style.background = "var(--yt-search-highlight-bg)";
deleteBtn.style.borderColor = "var(--yt-search-highlight-border)";
deleteBtn.style.color = "var(--yt-search-highlight-accent)";
deleteBar.append(countSpan, selectAllBtn, clearAllBtn, deleteBtn);
searchContainer.appendChild(deleteBar);
};
const addDeleteStyles = () => {
if (byId("ytplus-playlist-delete-styles")) {
return;
}
const css = "\n      .ytplus-playlist-delete-toggle.active {\n        color: var(--yt-search-highlight-accent) !important;\n        border-color: var(--yt-search-highlight-border-strong) !important;\n        background: var(--yt-search-highlight-faint) !important;\n      }\n      .ytplus-playlist-delete-toggle:hover {\n        color: var(--yt-spec-text-primary);\n        border-color: var(--yt-spec-text-secondary);\n      }\n      .ytplus-playlist-delete-bar {\n        display: flex;\n      }\n      .ytplus-playlist-delete-selected:not(:disabled):hover {\n        background: var(--yt-search-highlight-hover) !important;\n      }\n      .ytplus-playlist-select-all:hover,\n      .ytplus-playlist-clear-all:hover {\n        background: var(--yt-spec-10-percent-layer) !important;\n      }\n      .ytplus-playlist-item-checkbox {\n        opacity: 0.85;\n        transition: opacity 0.15s;\n      }\n      .ytplus-playlist-item-checkbox:hover {\n        opacity: 1;\n      }\n      /* Checkbox base styles inherited from basic.js .ytp-plus-settings-checkbox — no need to duplicate */\n    ";
try {
if (window.YouTubeUtils?.StyleManager) {
window.YouTubeUtils.StyleManager.add("ytplus-playlist-delete-styles", css);
return;
}
} catch (e) {}
const style = document.createElement("style");
style.id = "ytplus-playlist-delete-styles";
style.textContent = css;
(document.head || document.documentElement).appendChild(style);
};
const cleanup = () => {
if (state.deleteMode) {
removeCheckboxesFromItems();
state.deleteMode = !1;
}
state.isDeleting = !1;
state.selectedItems.clear();
const searchUI = qs(".ytplus-playlist-search");
searchUI && searchUI.remove();
if (state.rootSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(state.rootSubId);
state.rootSubId = null;
}
if (state.observerFallbackTimerId) {
clearInterval(state.observerFallbackTimerId);
state.observerFallbackTimerId = null;
}
if (state.rafId) {
cancelAnimationFrame(state.rafId);
state.rafId = null;
}
state.itemsCache.clear();
state.searchInput = null;
state.originalItems = [];
state.currentPlaylistId = null;
state.itemsContainer = null;
state.itemSelector = null;
state.itemTagName = null;
state.playlistPanel = null;
state.isPlaylistPage = !1;
};
const handleNavigation = debounce(() => {
if (!featureEnabled) {
cleanup();
return;
}
if (!shouldRunOnThisPage()) {
cleanup();
return;
}
const newPlaylistId = getCurrentPlaylistId();
if (newPlaylistId !== state.currentPlaylistId || !qs(".ytplus-playlist-search")) {
cleanup();
if (newPlaylistId) {
const waitFor = window.YouTubeUtils.waitFor || window.YouTubeUtils.waitForElement;
"function" == typeof waitFor ? waitFor(isPlaylistPage() ? "ytd-playlist-video-list-renderer #contents" : "ytd-playlist-panel-renderer #items", 1500).finally(addSearchUI) : requestAnimationFrame(addSearchUI);
}
}
}, 250);
let initialized = !1;
const ensureInit = () => {
if (initialized || !featureEnabled || !isRelevantRoute()) {
return;
}
initialized = !0;
const run = () => {
(() => {
try {
const globalSettings = localStorage.getItem(window.YouTubeUtils?.SETTINGS_KEY || "youtube_plus_settings");
if (globalSettings) {
const parsedGlobal = JSON.parse(globalSettings);
"boolean" == typeof parsedGlobal.enablePlaylistSearch && (config.enabled = parsedGlobal.enablePlaylistSearch);
}
const saved = localStorage.getItem(config.storageKey);
if (saved) {
const parsed = JSON.parse(saved);
window.YouTubeUtils && window.YouTubeUtils.safeMerge ? window.YouTubeUtils.safeMerge(config, parsed) : "boolean" == typeof parsed.enabled && (config.enabled = parsed.enabled);
}
} catch (error) {
window.console.warn("[Playlist Search] Failed to load settings:", error);
}
})();
featureEnabled && !1 !== config.enabled && addSearchUI();
};
"function" == typeof window.requestIdleCallback ? window.requestIdleCallback(run, {
timeout: 1500
}) : setTimeout(run, 0);
};
const handleNavigate = () => {
if (isRelevantRoute()) {
ensureInit();
handleNavigation();
} else {
cleanup();
}
};
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("playlist-search", ensureInit, {
priority: 1,
shouldLoad: isRelevantRoute
}) : (cb => {
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", cb, {
once: !0
}) : cb();
})(ensureInit);
if (window.YouTubeUtils?.cleanupManager && "function" == typeof window.YouTubeUtils.cleanupManager.registerListener) {
YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", handleNavigate, {
passive: !0
});
YouTubeUtils.cleanupManager.registerListener(window, "beforeunload", cleanup, {
passive: !0
});
} else {
document.addEventListener("yt-navigate-finish", handleNavigate);
window.addEventListener("beforeunload", cleanup);
}
window.addEventListener("youtube-plus-settings-updated", e => {
try {
const detail = e.detail;
const nextEnabled = !1 !== detail?.enablePlaylistSearch;
if (nextEnabled === featureEnabled) {
return;
}
setFeatureEnabled(nextEnabled);
} catch (e) {
setFeatureEnabled(window.YouTubeUtils?.loadFeatureEnabled?.("enablePlaylistSearch") ?? !0);
}
});
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytplusCreateHTML || (s => s);
const renderTemplateClone = (container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
};
const qs = window.YouTubeUtils?.$ || document.querySelector.bind(document);
const qsAll = window.YouTubeUtils?.$$ || (sel => Array.from(document.querySelectorAll(String(sel || ""))));
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
const t = window.YouTubeUtils.t;
const isAllowedHost = (host, domain) => {
const normalizedHost = String(host || "").toLowerCase();
const normalizedDomain = String(domain || "").toLowerCase();
return normalizedHost === normalizedDomain || normalizedHost.endsWith(`.${normalizedDomain}`);
};
function loadEnableThumbnail() {
return window.YouTubeUtils?.loadFeatureEnabled?.("enableThumbnail") ?? !0;
}
let thumbnailFeatureEnabled = loadEnableThumbnail();
const isEnabled = () => thumbnailFeatureEnabled;
const isRelevantRoute = () => {
try {
const host = window.location.hostname || "";
if (!isAllowedHost(host, "youtube.com") || "music.youtube.com" === host) {
return !1;
}
const path = window.location.pathname || "";
return "/watch" === path || path.startsWith("/shorts") || path.startsWith("/channel/") || path.startsWith("/@");
} catch (e) {
return !1;
}
};
let started = !1;
let startScheduled = !1;
let mutationObserverSubId = null;
let modalCleanupSubId = null;
let urlChangeCleanup = null;
let thumbnailStylesInjected = !1;
function parseAndValidateUrl(url) {
try {
const parsedUrl = new URL(url);
return (function hasValidProtocol(parsedUrl) {
if ("https:" !== parsedUrl.protocol) {
window.console.warn("[YouTube+][Thumbnail]", "Only HTTPS URLs are allowed");
return !1;
}
return !0;
})(parsedUrl) && (function hasValidDomain(parsedUrl) {
const {hostname} = parsedUrl;
if (!isAllowedHost(hostname, "ytimg.com") && !isAllowedHost(hostname, "youtube.com")) {
window.console.warn("[YouTube+][Thumbnail]", "Only YouTube image domains are allowed");
return !1;
}
return !0;
})(parsedUrl) ? parsedUrl : null;
} catch (error) {
window.console.error("[YouTube+][Thumbnail]", "Invalid URL:", error);
return null;
}
}
function cleanupImageElement(img) {
try {
img.onload = null;
img.onerror = null;
img.src = "";
img.parentNode && img.parentNode.removeChild(img);
} catch (e) {}
}
async function checkImageExists(url) {
try {
if (!(function isValidUrlString(url) {
if (!url || "string" != typeof url) {
window.console.warn("[YouTube+][Thumbnail]", "Invalid URL provided");
return !1;
}
return !0;
})(url)) {
return !1;
}
const parsedUrl = parseAndValidateUrl(url);
if (!parsedUrl) {
return !1;
}
const headResult = await (async function checkViaHeadRequest(url) {
const controller = new AbortController;
const timeoutId = setTimeout_(() => controller.abort(), 5e3);
try {
const response = await fetch(url, {
method: "HEAD",
signal: controller.signal
}).catch(() => null);
clearTimeout(timeoutId);
return !response || response.ok;
} catch (e) {
clearTimeout(timeoutId);
return !1;
}
})(url);
return null !== headResult ? headResult : await (function checkViaImageLoad(url) {
return new Promise(resolve => {
const img = document.createElement("img");
img.style.display = "none";
const timeout = setTimeout_(() => {
cleanupImageElement(img);
resolve(!1);
}, 3e3);
window.YouTubeUtils?.cleanupManager?.registerTimeout && window.YouTubeUtils.cleanupManager.registerTimeout(timeout);
img.onload = () => {
clearTimeout(timeout);
cleanupImageElement(img);
resolve(!0);
};
img.onerror = () => {
clearTimeout(timeout);
cleanupImageElement(img);
resolve(!1);
};
document.body.appendChild(img);
img.src = url;
});
})(url);
} catch (error) {
window.console.error("[YouTube+][Thumbnail]", "Error checking image:", error);
return !1;
}
}
function replaceWithSpinner(overlayElement, originalSvg) {
const spinner = (function createSpinner() {
const spinner = document.createElementNS("http://www.w3.org/2000/svg", "svg");
spinner.setAttribute("xmlns", "http://www.w3.org/2000/svg");
spinner.setAttribute("width", "16");
spinner.setAttribute("height", "16");
spinner.setAttribute("viewBox", "0 0 24 24");
spinner.setAttribute("fill", "none");
spinner.setAttribute("stroke", "white");
spinner.setAttribute("stroke-width", "2");
spinner.setAttribute("stroke-linecap", "round");
spinner.setAttribute("stroke-linejoin", "round");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M21 12a9 9 0 1 1-6.219-8.56");
spinner.appendChild(path);
spinner.style.animation = "spin 1s linear infinite";
return spinner;
})();
overlayElement.replaceChild(spinner, originalSvg);
return spinner;
}
async function openThumbnail(videoId, isShorts, overlayElement) {
try {
if (!(function isValidVideoId(videoId) {
return !(!videoId || "string" != typeof videoId || !/^[a-zA-Z0-9_-]{11}$/.test(videoId));
})(videoId)) {
window.console.error("[YouTube+][Thumbnail]", "Invalid video ID:", videoId);
return;
}
if (!(function isValidOverlayElement(overlayElement) {
return !!(overlayElement && overlayElement instanceof HTMLElement);
})(overlayElement)) {
window.console.error("[YouTube+][Thumbnail]", "Invalid overlay element");
return;
}
const originalSvg = overlayElement.querySelector("svg");
if (!originalSvg) {
window.console.warn("[YouTube+][Thumbnail]", "No SVG found in overlay element");
return;
}
const spinner = replaceWithSpinner(overlayElement, originalSvg);
try {
await (async function loadAndShowThumbnail(videoId, isShorts) {
const urls = isShorts ? (function getShortsThumbnailUrls(videoId) {
return {
primary: `https://i.ytimg.com/vi/${videoId}/oardefault.jpg`,
fallback: `https://i.ytimg.com/vi/${videoId}/oar2.jpg`
};
})(videoId) : (function getVideoThumbnailUrls(videoId) {
return {
primary: `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`,
fallback: `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`
};
})(videoId);
const isPrimaryAvailable = await checkImageExists(urls.primary);
showImageModal(isPrimaryAvailable ? urls.primary : urls.fallback);
})(videoId, isShorts);
} finally {
!(function restoreOriginalSvg(overlayElement, spinner, originalSvg) {
try {
spinner && spinner.parentNode && overlayElement.replaceChild(originalSvg, spinner);
} catch (restoreError) {
window.console.error("[YouTube+][Thumbnail]", "Error restoring original SVG:", restoreError);
spinner && spinner.parentNode && spinner.parentNode.removeChild(spinner);
}
})(overlayElement, spinner, originalSvg);
}
} catch (error) {
window.console.error("[YouTube+][Thumbnail]", "Error opening thumbnail:", error);
}
}
function createDownloadButton(img) {
const downloadBtn = document.createElement("button");
downloadBtn.className = "thumbnail-modal-download thumbnail-modal-action-btn ytp-plus-settings-close";
renderTemplateClone(downloadBtn, '\n            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity="0.5" d="M3 15C3 17.8284 3 19.2426 3.87868 20.1213C4.75736 21 6.17157 21 9 21H15C17.8284 21 19.2426 21 20.1213 20.1213C21 19.2426 21 17.8284 21 15" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: var(--darkreader-text-ffffff, #cad3f5);" data-darkreader-inline-stroke=""></path> <path d="M12 3V16M12 16L16 11.625M12 16L8 11.625" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="--darkreader-inline-stroke: var(--darkreader-text-ffffff, #cad3f5);" data-darkreader-inline-stroke=""></path></svg>\n        ');
downloadBtn.title = t("download");
downloadBtn.setAttribute("aria-label", t("download"));
downloadBtn.addEventListener("click", async e => {
e.preventDefault();
e.stopPropagation();
try {
await (async function downloadImageAsBlob(imgSrc) {
const controller = new AbortController;
const timerId = setTimeout_(() => controller.abort(), 15e3);
let response;
try {
response = await fetch(imgSrc, {
signal: controller.signal
});
} finally {
clearTimeout(timerId);
}
if (!response.ok) {
throw new Error("Network response was not ok");
}
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobUrl;
try {
const urlObj = new URL(imgSrc);
const segments = urlObj.pathname.split("/");
a.download = segments[segments.length - 1] || "thumbnail.jpg";
} catch (e) {
a.download = "thumbnail.jpg";
}
document.body.appendChild(a);
a.click();
a.remove();
setTimeout_(() => URL.revokeObjectURL(blobUrl), 1500);
})(img.src);
} catch (e) {
window.open(img.src, "_blank");
}
});
return downloadBtn;
}
function showImageModal(url) {
try {
if (!isEnabled()) {
return;
}
if (!(function validateModalUrl(url) {
if (!url || "string" != typeof url) {
window.console.error("[YouTube+][Thumbnail]", "Invalid URL provided to modal");
return !1;
}
try {
const parsedUrl = new URL(url);
if ("https:" !== parsedUrl.protocol) {
window.console.error("[YouTube+][Thumbnail]", "Only HTTPS URLs are allowed");
return !1;
}
const allowedDomains = [ "ytimg.com", "youtube.com", "ggpht.com", "googleusercontent.com" ];
if (!allowedDomains.some(d => isAllowedHost(parsedUrl.hostname, d))) {
window.console.error("[YouTube+][Thumbnail]", "Image domain not allowed:", parsedUrl.hostname);
return !1;
}
return !0;
} catch (urlError) {
window.console.error("[YouTube+][Thumbnail]", "Invalid URL format:", urlError);
return !1;
}
})(url)) {
return;
}
qsAll(".thumbnail-modal-overlay").forEach(m => m.remove());
const overlay = document.createElement("div");
overlay.className = "thumbnail-modal-overlay ytp-plus-modal-overlay";
overlay.setAttribute("role", "dialog");
overlay.setAttribute("aria-modal", "true");
overlay.setAttribute("aria-label", "Thumbnail preview");
const content = document.createElement("div");
content.className = "thumbnail-modal-content ytp-plus-modal-content";
const img = (function createModalImage(url) {
const img = document.createElement("img");
img.className = "thumbnail-modal-img";
img.src = url;
img.alt = t("thumbnailPreview");
img.title = "";
img.style.cursor = "pointer";
img.addEventListener("click", () => window.open(img.src, "_blank"));
return img;
})(url);
const optionsDiv = document.createElement("div");
optionsDiv.className = "thumbnail-modal-options";
const closeBtn = (function createCloseButton(overlay) {
const closeBtn = document.createElement("button");
closeBtn.className = "thumbnail-modal-close thumbnail-modal-action-btn ytp-plus-settings-close";
closeBtn.setAttribute("data-shared-close-button", "ytp-plus-close-settings");
renderTemplateClone(closeBtn, '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 9.50002L9.5 14.5M9.49998 9.5L14.5 14.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg>');
closeBtn.title = t("closeButton") || t("close");
closeBtn.setAttribute("aria-label", t("closeButton") || t("close"));
closeBtn.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
overlay.remove();
});
return closeBtn;
})(overlay);
const newTabBtn = (function createNewTabButton(img) {
const newTabBtn = document.createElement("button");
newTabBtn.className = "thumbnail-modal-open thumbnail-modal-action-btn ytp-plus-settings-close";
renderTemplateClone(newTabBtn, '\n            <svg fill="currentColor" viewBox="0 0 24 24" width="18" height="18" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">\n        <g id="SVGRepo_bgCarrier" stroke-width="0"></g>\n        <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>\n        <g id="SVGRepo_iconCarrier"><path d="M14.293,9.707a1,1,0,0,1,0-1.414L18.586,4H16a1,1,0,0,1,0-2h5a1,1,0,0,1,1,1V8a1,1,0,0,1-2,0V5.414L15.707,9.707a1,1,0,0,1-1.414,0ZM3,22H8a1,1,0,0,0,0-2H5.414l4.293-4.293a1,1,0,0,0-1.414-1.414L4,18.586V16a1,1,0,0,0-2,0v5A1,1,0,0,0,3,22Z"></path></g>\n      </svg>\n        ');
newTabBtn.title = t("clickToOpen");
newTabBtn.setAttribute("aria-label", t("clickToOpen"));
newTabBtn.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
window.open(img.src, "_blank");
});
return newTabBtn;
})(img);
const downloadBtn = createDownloadButton(img);
content.appendChild(img);
content.appendChild(optionsDiv);
const wrapper = document.createElement("div");
wrapper.className = "thumbnail-modal-wrapper";
const actionsDiv = document.createElement("div");
actionsDiv.className = "thumbnail-modal-actions";
actionsDiv.appendChild(closeBtn);
actionsDiv.appendChild(newTabBtn);
actionsDiv.appendChild(downloadBtn);
wrapper.appendChild(content);
wrapper.appendChild(actionsDiv);
overlay.appendChild(wrapper);
overlay.addEventListener("click", ({target}) => {
target === overlay && overlay.remove();
});
!(function setupModalKeyboard(overlay) {
window.addEventListener("keydown", function escHandler(e) {
if ("Escape" === e.key) {
overlay.remove();
window.removeEventListener("keydown", escHandler, !0);
}
}, !0);
})(overlay);
!(function setupImageErrorHandler(img, content) {
img.addEventListener("error", () => {
const err = document.createElement("div");
err.textContent = t("thumbnailLoadFailed");
err.style.color = "white";
content.appendChild(err);
});
})(img, content);
document.body.appendChild(overlay);
requestAnimationFrame(() => {
const focusTarget = overlay.querySelector('button, [tabindex="0"]');
focusTarget && focusTarget.focus();
});
if (window.YouTubePlusModalHandlers && window.YouTubePlusModalHandlers.createFocusTrap) {
const removeTrap = window.YouTubePlusModalHandlers.createFocusTrap(overlay);
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
modalCleanupSubId = "thumbnail::modalCleanup";
coordinator.subscribeRoot(modalCleanupSubId, () => {
if (!overlay.isConnected) {
removeTrap();
coordinator.unsubscribe(modalCleanupSubId);
modalCleanupSubId = null;
}
}, {
childList: !0,
attributes: !1,
subtree: !0
});
}
}
} catch (error) {
window.console.error("[YouTube+][Thumbnail]", "Error showing modal:", error);
}
}
let thumbnailPreviewCurrentVideoId = "";
let thumbnailPreviewClosed = !1;
let thumbnailInsertionAttempts = 0;
function attemptInsertion() {
const player = (function findPlayerElement() {
return qs("#movie_player") || qs("ytd-player");
})();
if (!player) {
thumbnailInsertionAttempts++;
thumbnailInsertionAttempts < 10 ? setTimeout_(attemptInsertion, 500) : thumbnailInsertionAttempts = 0;
return;
}
let overlay = player.querySelector("#thumbnailPreview-player-overlay");
if (!overlay) {
overlay = (function createPlayerThumbnailOverlay(videoId, player) {
const overlay = createThumbnailOverlay(videoId, player);
overlay.id = "thumbnailPreview-player-overlay";
overlay.dataset.videoId = videoId;
overlay.style.cssText = "\n      position: absolute;\n      top: 10%;\n      right: 8px;\n      width: 36px;\n      height: 36px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      border-radius: 6px;\n      cursor: pointer;\n      z-index: 1001;\n      transition: all 0.15s ease;\n      opacity: 0;\n    ";
return overlay;
})(thumbnailPreviewCurrentVideoId, player);
overlay.tabIndex = 0;
overlay.setAttribute("role", "button");
overlay.setAttribute("aria-label", "Show thumbnail preview");
overlay.onmouseenter = () => {
try {
overlay.style.opacity = "0.5";
} catch (e) {}
};
overlay.onmouseleave = () => {
try {
overlay.style.opacity = "0";
} catch (e) {}
};
overlay.onfocus = () => {
try {
overlay.style.opacity = "0.5";
} catch (e) {}
};
overlay.onblur = () => {
try {
overlay.style.opacity = "0";
} catch (e) {}
};
overlay.addEventListener("keydown", e => {
const ke = e;
if (ke && ("Enter" === ke.key || " " === ke.key)) {
ke.preventDefault();
overlay.click();
}
});
const playerElement = player;
"static" === getComputedStyle(playerElement).position && (playerElement.style.position = "relative");
playerElement.appendChild(overlay);
return;
}
if (overlay.dataset.videoId !== thumbnailPreviewCurrentVideoId) {
overlay.remove();
attemptInsertion();
}
thumbnailInsertionAttempts = 0;
}
function addOrUpdateThumbnailImage() {
if (!isEnabled()) {
return;
}
if (!window.YouTubeUtils?.isWatchPage?.(window.location.href)) {
return;
}
const newVideoId = (function getCurrentVideoId() {
return new URLSearchParams(window.location.search).get("v");
})();
if (newVideoId !== thumbnailPreviewCurrentVideoId) {
thumbnailPreviewClosed = !1;
!(function removeOldOverlay() {
const oldOverlay = qs("#thumbnailPreview-player-overlay");
oldOverlay && oldOverlay.remove();
})();
}
if (!(function shouldSkipThumbnailUpdate(newVideoId) {
return !newVideoId || newVideoId === thumbnailPreviewCurrentVideoId || thumbnailPreviewClosed;
})(newVideoId)) {
thumbnailPreviewCurrentVideoId = newVideoId || "";
attemptInsertion();
}
}
function createThumbnailOverlay(videoId, container) {
const overlay = document.createElement("div");
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "16");
svg.setAttribute("height", "16");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "white");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.style.transition = "stroke 0.2s ease";
const mainRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
mainRect.setAttribute("width", "18");
mainRect.setAttribute("height", "18");
mainRect.setAttribute("x", "3");
mainRect.setAttribute("y", "3");
mainRect.setAttribute("rx", "2");
mainRect.setAttribute("ry", "2");
svg.appendChild(mainRect);
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", "9");
circle.setAttribute("cy", "9");
circle.setAttribute("r", "2");
svg.appendChild(circle);
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21");
svg.appendChild(path);
overlay.appendChild(svg);
overlay.style.cssText = "\n        position: absolute;\n        bottom: 8px;\n        left: 8px;\n        background: var(--yt-thumbnail-overlay-idle);\n        width: 28px;\n        height: 28px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border-radius: 4px;\n        cursor: pointer;\n        z-index: 1000;\n        opacity: 0;\n        transition: all 0.2s ease;\n      ";
overlay.onmouseenter = () => {
overlay.style.background = "var(--yt-thumbnail-overlay-hover)";
};
overlay.onmouseleave = () => {
overlay.style.background = "var(--yt-thumbnail-overlay-idle)";
};
overlay.onclick = async e => {
e.preventDefault();
e.stopPropagation();
const isShorts = container.closest("ytm-shorts-lockup-view-model") || container.closest(".shortsLockupViewModelHost") || container.closest('[class*="shortsLockupViewModelHost"]') || container.querySelector('a[href*="/shorts/"]');
await openThumbnail(videoId, !!isShorts, overlay);
};
return overlay;
}
function extractVideoInfo(container) {
const img = container.querySelector('img[src*="ytimg.com"]');
if (!img?.src) {
return {
videoId: null,
thumbnailContainer: null
};
}
const videoId = (function extractVideoId(thumbnailSrc) {
try {
if (!thumbnailSrc || "string" != typeof thumbnailSrc) {
return null;
}
const match = thumbnailSrc.match(/\/vi\/([^\/]+)\//);
const videoId = match ? match[1] : null;
if (videoId && !/^[a-zA-Z0-9_-]{11}$/.test(videoId)) {
window.console.warn("[YouTube+][Thumbnail]", "Invalid video ID format:", videoId);
return null;
}
return videoId;
} catch (error) {
window.console.error("[YouTube+][Thumbnail]", "Error extracting video ID:", error);
return null;
}
})(img.src);
const thumbnailContainer = (function findThumbnailContainerFromImage(img) {
return img.closest("yt-thumbnail-view-model") || img.parentElement;
})(img);
return {
videoId,
thumbnailContainer
};
}
function extractShortsInfo(container) {
const link = container.querySelector('a[href*="/shorts/"]');
if (!link?.href) {
return {
videoId: null,
thumbnailContainer: null
};
}
const videoId = (function extractShortsId(href) {
try {
if (!href || "string" != typeof href) {
return null;
}
const match = href.match(/\/shorts\/([^\/\?]+)/);
const shortsId = match ? match[1] : null;
if (shortsId && !/^[a-zA-Z0-9_-]{11}$/.test(shortsId)) {
window.console.warn("[YouTube+][Thumbnail]", "Invalid shorts ID format:", shortsId);
return null;
}
return shortsId;
} catch (error) {
window.console.error("[YouTube+][Thumbnail]", "Error extracting shorts ID:", error);
return null;
}
})(link.href);
const shortsImg = container.querySelector('img[src*="ytimg.com"]');
const thumbnailContainer = (function findShortsThumbnailContainer(shortsImg) {
return shortsImg ? shortsImg.closest(".ytCoreImageHost") || shortsImg.closest('[class*="ThumbnailContainer"]') || shortsImg.closest('[class*="ImageHost"]') || shortsImg.parentElement : null;
})(shortsImg);
return {
videoId,
thumbnailContainer
};
}
function addThumbnailOverlay(container) {
if (!isEnabled()) {
return;
}
if (container.querySelector(".thumb-overlay")) {
return;
}
let {videoId, thumbnailContainer} = extractVideoInfo(container);
videoId || ({videoId, thumbnailContainer} = extractShortsInfo(container));
if (!videoId || !thumbnailContainer) {
return;
}
!(function ensureRelativePosition(thumbnailContainer) {
"static" === getComputedStyle(thumbnailContainer).position && (thumbnailContainer.style.position = "relative");
})(thumbnailContainer);
const overlay = createThumbnailOverlay(videoId, container);
overlay.className = "thumb-overlay";
thumbnailContainer.appendChild(overlay);
!(function setupOverlayHoverEffects(thumbnailContainer, overlay) {
thumbnailContainer.onmouseenter = () => {
overlay.style.opacity = "1";
};
thumbnailContainer.onmouseleave = () => {
overlay.style.opacity = "0";
};
})(thumbnailContainer, overlay);
}
function addAvatarOverlay(img) {
if (!isEnabled()) {
return;
}
const container = img.parentElement;
if (!container) {
return;
}
if (img.closest(".avatar-btn, #avatar-btn") || container.closest(".avatar-btn, #avatar-btn") || img.closest("button") || container.closest("button") || img.closest(".thumbnail-modal-wrapper") || container.closest(".thumbnail-modal-wrapper")) {
return;
}
if (img.closest("ytm-shorts-lockup-view-model") || container.closest("ytm-shorts-lockup-view-model") || img.closest(".shortsLockupViewModelHost") || container.closest(".shortsLockupViewModelHost") || img.closest('[class*="shortsLockupViewModelHost"]') || container.closest('[class*="shortsLockupViewModelHost"]') || img.closest('[class*="shorts"]') || container.closest('[class*="shorts"]')) {
return;
}
if (container.querySelector(".avatar-overlay")) {
return;
}
"static" === getComputedStyle(container).position && (container.style.position = "relative");
const overlay = (function createAvatarOverlay() {
const overlay = document.createElement("div");
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "16");
svg.setAttribute("height", "16");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "white");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.style.transition = "stroke 0.2s ease";
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", "12");
circle.setAttribute("cy", "8");
circle.setAttribute("r", "5");
svg.appendChild(circle);
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M20 21a8 8 0 0 0-16 0");
svg.appendChild(path);
overlay.appendChild(svg);
overlay.style.cssText = "\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n        background: var(--yt-thumbnail-overlay-hover);\n        width: 28px;\n        height: 28px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border-radius: 50%;\n        cursor: pointer;\n        z-index: 1000;\n        opacity: 0;\n        transition: all 0.2s ease;\n      ";
overlay.onmouseenter = () => {
overlay.style.background = "var(--yt-thumbnail-overlay-active)";
};
overlay.onmouseleave = () => {
overlay.style.background = "var(--yt-thumbnail-overlay-hover)";
};
return overlay;
})();
overlay.className = "avatar-overlay";
overlay.onclick = e => {
e.preventDefault();
e.stopPropagation();
const highResUrl = img.src.replace(/=s\d+-c-k-c0x00ffffff-no-rj.*/, "=s0");
showImageModal(highResUrl);
};
container.appendChild(overlay);
container.onmouseenter = () => {
overlay.style.opacity = "1";
};
container.onmouseleave = () => {
overlay.style.opacity = "0";
};
}
function addBannerOverlay(img) {
if (!isEnabled()) {
return;
}
const container = img.parentElement;
if (container.querySelector(".banner-overlay")) {
return;
}
"static" === getComputedStyle(container).position && (container.style.position = "relative");
const overlay = (function createBannerOverlay() {
const overlay = document.createElement("div");
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "16");
svg.setAttribute("height", "16");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "white");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.style.transition = "stroke 0.2s ease";
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", "3");
rect.setAttribute("y", "3");
rect.setAttribute("width", "18");
rect.setAttribute("height", "18");
rect.setAttribute("rx", "2");
rect.setAttribute("ry", "2");
svg.appendChild(rect);
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", "9");
circle.setAttribute("cy", "9");
circle.setAttribute("r", "2");
svg.appendChild(circle);
const polyline = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
polyline.setAttribute("points", "21,15 16,10 5,21");
svg.appendChild(polyline);
overlay.appendChild(svg);
overlay.style.cssText = "\n        position: absolute;\n        bottom: 8px;\n        left: 8px;\n        background: var(--yt-thumbnail-overlay-hover);\n        width: 28px;\n        height: 28px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border-radius: 4px;\n        cursor: pointer;\n        z-index: 1000;\n        opacity: 0;\n        transition: all 0.2s ease;\n      ";
overlay.onmouseenter = () => {
overlay.style.background = "var(--yt-thumbnail-overlay-active)";
};
overlay.onmouseleave = () => {
overlay.style.background = "var(--yt-thumbnail-overlay-hover)";
};
return overlay;
})();
overlay.className = "banner-overlay";
overlay.onclick = e => {
e.preventDefault();
e.stopPropagation();
const highResUrl = img.src.replace(/=w\d+-.*/, "=s0");
showImageModal(highResUrl);
};
container.appendChild(overlay);
container.onmouseenter = () => {
overlay.style.opacity = "1";
};
container.onmouseleave = () => {
overlay.style.opacity = "0";
};
}
function processAll() {
if (isEnabled()) {
!(function processThumbnails() {
const n1 = qsAll("yt-thumbnail-view-model");
for (let i = 0; i < n1.length; i++) {
addThumbnailOverlay(n1[i]);
}
const n2 = qsAll(".ytd-thumbnail");
for (let i = 0; i < n2.length; i++) {
addThumbnailOverlay(n2[i]);
}
const n3 = qsAll("ytm-shorts-lockup-view-model");
for (let i = 0; i < n3.length; i++) {
addThumbnailOverlay(n3[i]);
}
const n4 = qsAll(".shortsLockupViewModelHost");
for (let i = 0; i < n4.length; i++) {
addThumbnailOverlay(n4[i]);
}
const n5 = qsAll('[class*="shortsLockupViewModelHost"]');
for (let i = 0; i < n5.length; i++) {
addThumbnailOverlay(n5[i]);
}
})();
!(function processAvatars() {
[ "yt-avatar-shape img", "#avatar img", "ytd-channel-avatar-editor img", '.ytd-video-owner-renderer img[src*="yt"]', 'img[src*="yt3.ggpht.com"]', 'img[src*="yt4.ggpht.com"]' ].forEach(selector => {
qsAll(selector).forEach(img => {
if (!img.src) {
return;
}
if (!img.src.includes("yt")) {
return;
}
if (img.closest(".avatar-overlay")) {
return;
}
const isAvatar = img.naturalWidth > 0 && img.naturalWidth === img.naturalHeight;
(isAvatar || img.src.includes("ggpht.com")) && addAvatarOverlay(img);
});
});
})();
!(function processBanners() {
[ "yt-image-banner-view-model img", 'ytd-c4-tabbed-header-renderer img[src*="yt"]', '#channel-header img[src*="banner"]', 'img[src*="banner"]' ].forEach(selector => {
qsAll(selector).forEach(img => {
if (!img.src) {
return;
}
if (img.closest(".banner-overlay")) {
return;
}
const isBanner = (img.src.includes("banner") || img.src.includes("yt")) && img.naturalWidth > 2 * img.naturalHeight;
(isBanner || img.src.includes("banner")) && addBannerOverlay(img);
});
});
})();
addOrUpdateThumbnailImage();
}
}
let processAllTimerId = null;
let lastProcessAllTime = 0;
function scheduleProcessAll(minDelay = 0) {
if (processAllTimerId) {
return;
}
const now = Date.now();
const dueIn = Math.max(minDelay, Math.max(0, 350 - (now - lastProcessAllTime)));
processAllTimerId = setTimeout_(() => {
processAllTimerId = null;
lastProcessAllTime = Date.now();
try {
if (!isEnabled()) {
return;
}
processAll();
} catch (e) {
window.console.error("[YouTube+][Thumbnail]", "processAll failed:", e);
}
}, dueIn);
}
function stop() {
if (started) {
started = !1;
try {
if (processAllTimerId) {
clearTimeout(processAllTimerId);
processAllTimerId = null;
}
} catch (e) {}
!(function teardownMutationObserver() {
if (!mutationObserverSubId) {
return;
}
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.unsubscribe && coordinator.unsubscribe(mutationObserverSubId);
mutationObserverSubId = null;
})();
if (urlChangeCleanup) {
try {
urlChangeCleanup();
} catch (e) {}
urlChangeCleanup = null;
}
!(function removeInjectedUi() {
try {
qsAll(".thumbnail-modal-overlay").forEach(m => m.remove());
} catch (e) {}
try {
qsAll(".thumb-overlay, .avatar-overlay, .banner-overlay").forEach(el => el.remove());
} catch (e) {}
try {
const playerOverlay = qs("#thumbnailPreview-player-overlay");
playerOverlay && playerOverlay.remove();
} catch (e) {}
})();
!(function removeThumbnailStyles() {
try {
window.YouTubeUtils?.StyleManager?.remove && window.YouTubeUtils.StyleManager.remove("thumbnail-viewer-styles");
} catch (e) {}
const el = byId("ytplus-thumbnail-styles");
if (el) {
try {
el.remove();
} catch (e) {}
}
thumbnailStylesInjected = !1;
})();
}
}
function start() {
if (!started && isEnabled()) {
started = !0;
!(function ensureThumbnailStyles() {
if (!thumbnailStylesInjected) {
try {
const css = "\n        .thumbnail-overlay-container { position: absolute; bottom: 8px; left: 8px; z-index: var(--yt-z-overlay); opacity: 0; transition: opacity 0.2s ease; }\n        .thumbnail-overlay-button { width: 28px; height: 28px; background: var(--yt-glass-bg); border: none; border-radius: var(--yt-radius-xs); cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--yt-text-primary); position: relative; box-shadow: var(--yt-glass-shadow); backdrop-filter: var(--yt-glass-blur); -webkit-backdrop-filter: var(--yt-glass-blur); border: 1px solid var(--yt-glass-border); }\n        .thumbnail-overlay-button svg{width:16px;height:16px;display:block;flex:none;}\n        .thumbnail-overlay-button:hover { background: var(--yt-hover-bg); }\n        .thumbnail-dropdown { position: absolute; bottom: 100%; left: 0; background: var(--yt-glass-bg); border-radius: var(--yt-radius-xs); padding: 4px; margin-bottom: 4px; display: none; flex-direction: column; min-width: 140px; box-shadow: var(--yt-glass-shadow); z-index: var(--yt-z-flyout); backdrop-filter: var(--yt-glass-blur); -webkit-backdrop-filter: var(--yt-glass-blur); border: 1px solid var(--yt-glass-border); }\n        .thumbnail-dropdown.show { display: flex !important; }\n        .thumbnail-dropdown-item { background: none; border: none; color: var(--yt-text-primary); padding: 8px 12px; cursor: pointer; border-radius: 4px; font-size: 12px; text-align: left; white-space: nowrap; transition: background-color 0.2s ease; }\n        .thumbnail-dropdown-item:hover { background: var(--yt-hover-bg); }\n        .thumbnailPreview-button { position: absolute; bottom: 10px; left: 5px; background-color: var(--yt-glass-bg); color: var(--yt-text-primary); border: none; border-radius: var(--yt-radius-xs); padding: 3px; font-size: 18px; cursor: pointer; z-index: var(--yt-z-overlay); opacity: 0; transition: opacity 0.3s; display: flex; align-items: center; justify-content: center; box-shadow: var(--yt-glass-shadow); backdrop-filter: var(--yt-glass-blur); -webkit-backdrop-filter: var(--yt-glass-blur); border: 1px solid var(--yt-glass-border); }\n        .thumbnailPreview-button svg{width:16px;height:16px;display:block;flex:none;}\n        .thumbnailPreview-container { position: relative; }\n        .thumbnailPreview-container:hover .thumbnailPreview-button { opacity: 1; }\n        .thumbnail-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: var(--yt-modal-bg); z-index: var(--yt-z-modal); display: flex; align-items: center; justify-content: center; animation: fadeInModal 0.22s cubic-bezier(.4,0,.2,1); backdrop-filter: blur(8px) saturate(140%); -webkit-backdrop-filter: blur(8px) saturate(140%); }\n        .thumbnail-modal-content { background: var(--yt-glass-bg); border-radius: var(--yt-radius-lg); box-shadow: 0 12px 40px var(--yt-timecode-panel-shadow); max-width: 78vw; max-height: 90vh; overflow: auto; position: relative; display: flex; flex-direction: column; align-items: center; animation: scaleInModal 0.22s cubic-bezier(.4,0,.2,1); border: 1.5px solid var(--yt-glass-border); backdrop-filter: blur(14px) saturate(150%); -webkit-backdrop-filter: blur(14px) saturate(150%);}\n        /* Wrapper to place content and action buttons side-by-side */\n        .thumbnail-modal-wrapper { display: flex; align-items: flex-start; gap: 12px; }\n        .thumbnail-modal-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }\n        .thumbnail-modal-action-btn { padding: 0; line-height: 0; }\n        .thumbnail-modal-action-btn svg{width:18px;height:18px;display:block;flex:none;}\n        .thumbnail-modal-close svg{width:36px;height:36px;}\n        .thumbnail-modal-close { }\n        .thumbnail-modal-open { }\n        .thumbnail-modal-img { max-width: 72vw; max-height: 70vh; box-shadow: var(--yt-glass-shadow); background: #222; border: 1px solid var(--yt-glass-border); }\n        .thumbnail-modal-options { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }\n        .thumbnail-modal-option-btn { background: var(--yt-button-bg); color: var(--yt-text-primary); border: none; border-radius: var(--yt-radius-xs); padding: 8px 18px; font-size: 14px; cursor: pointer; transition: background 0.2s,color .2s; margin-bottom: 6px; box-shadow: var(--yt-glass-shadow); backdrop-filter: var(--yt-glass-blur); -webkit-backdrop-filter: var(--yt-glass-blur); border: 1px solid var(--yt-glass-border); }\n        .thumbnail-modal-option-btn:hover { background: var(--yt-hover-bg); color: var(--yt-accent); }\n        .thumbnail-modal-title { font-size: 18px; font-weight: 600; color: var(--yt-text-primary); margin-bottom: 10px; text-align: center; text-shadow: 0 2px 8px var(--yt-shadow-deep-1); }\n        /* fadeInModal, scaleInModal are provided by design-system */\n      ";
if (window.YouTubeUtils && YouTubeUtils.StyleManager && "function" == typeof YouTubeUtils.StyleManager.add) {
YouTubeUtils.StyleManager.add("thumbnail-viewer-styles", css);
} else {
const s = document.createElement("style");
s.id = "ytplus-thumbnail-styles";
s.textContent = css;
(document.head || document.documentElement).appendChild(s);
}
thumbnailStylesInjected = !0;
} catch (e) {
if (!byId("ytplus-thumbnail-styles")) {
const s = document.createElement("style");
s.id = "ytplus-thumbnail-styles";
s.textContent = ".thumbnail-modal-img{max-width:72vw;max-height:70vh;}";
(document.head || document.documentElement).appendChild(s);
}
thumbnailStylesInjected = !0;
}
}
})();
urlChangeCleanup || (urlChangeCleanup = (function setupUrlChangeDetection() {
let currentUrl = location.href;
const onNavChange = () => {
setTimeout_(() => {
if (isEnabled() && location.href !== currentUrl) {
currentUrl = location.href;
scheduleProcessAll(250);
}
}, 100);
};
const ytNavigateHandler = () => {
if (isEnabled()) {
location.href !== currentUrl && (currentUrl = location.href);
scheduleProcessAll(120);
}
};
window.addEventListener("ytp-history-navigate", onNavChange);
window.addEventListener("popstate", onNavChange);
window.addEventListener("yt-navigate-finish", ytNavigateHandler);
return () => {
try {
window.removeEventListener("ytp-history-navigate", onNavChange);
window.removeEventListener("popstate", onNavChange);
window.removeEventListener("yt-navigate-finish", ytNavigateHandler);
} catch (e) {}
};
})());
!(function setupMutationObserver() {
if (mutationObserverSubId) {
return;
}
const startObserving = () => {
const coordinator = window.YouTubeMutationCoordinator;
if (!coordinator?.subscribeRoot) {
return;
}
const target = qs("#content") || qs("#page-manager") || document.body;
mutationObserverSubId = "thumbnail::routeObserver";
coordinator.subscribeRoot(mutationObserverSubId, () => {
scheduleProcessAll(120);
}, {
selector: target instanceof Element ? void 0 : "#content, #page-manager",
childList: !0,
attributes: !1,
subtree: target !== document.body
});
};
document.body ? startObserving() : document.addEventListener("DOMContentLoaded", startObserving);
})();
"function" == typeof requestIdleCallback ? requestIdleCallback(() => scheduleProcessAll(0), {
timeout: 2e3
}) : scheduleProcessAll(400);
setTimeout_(() => scheduleProcessAll(0), 900);
setTimeout_(() => scheduleProcessAll(0), 1800);
try {
window.addEventListener("ytp:nav-refresh", () => {
try {
thumbnailFeatureEnabled && scheduleProcessAll(0);
} catch (e) {}
});
} catch (e) {}
}
}
function startMaybe() {
if (started || startScheduled) {
return;
}
if (!isEnabled()) {
return;
}
if (!isRelevantRoute()) {
return;
}
startScheduled = !0;
const run = () => {
startScheduled = !1;
start();
};
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", () => setTimeout_(run, 100), {
once: !0
}) : setTimeout_(run, 100);
}
function setEnabled(nextEnabled) {
thumbnailFeatureEnabled = !1 !== nextEnabled;
thumbnailFeatureEnabled ? startMaybe() : stop();
}
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("thumbnail", startMaybe, {
priority: 1,
shouldLoad: isRelevantRoute
}) : startMaybe();
window.addEventListener("youtube-plus-settings-updated", e => {
try {
const enabledFromEvent = e.detail?.enableThumbnail;
setEnabled(!1 !== enabledFromEvent);
} catch (e) {
setEnabled(loadEnableThumbnail());
}
});
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
const renderTemplateClone = (container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
};
const t = window.YouTubeUtils.t;
const qs = window.YouTubeUtils?.$ || ((selector, root) => (root || document).querySelector(selector));
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
const config = {
enabled: !0,
get shortcuts() {
return {
seekBackward: {
key: "ArrowLeft",
get description() {
return t("seekBackward");
}
},
seekForward: {
key: "ArrowRight",
get description() {
return t("seekForward");
}
},
volumeUp: {
key: "+",
get description() {
return t("volumeUp");
}
},
volumeDown: {
key: "-",
get description() {
return t("volumeDown");
}
},
mute: {
key: "m",
get description() {
return t("muteUnmute");
}
},
toggleCaptions: {
key: "c",
get description() {
return t("toggleCaptions");
}
},
showHelp: {
key: "?",
get description() {
return t("showHideHelp");
},
editable: !1
}
};
},
storageKey: "youtube_shorts_keyboard_settings"
};
const state = {
helpVisible: !1,
lastAction: null,
actionTimeout: null,
editingShortcut: null,
cachedVideo: null,
downloadButton: null,
downloadObserver: null,
downloadEnsureQueued: !1,
lastVideoCheck: 0,
initialized: !1
};
const getCurrentVideo = (() => {
const selectors = [ "ytd-reel-video-renderer[is-active] video", "#shorts-player video", "video" ];
return () => {
const now = Date.now();
if (state.cachedVideo?.isConnected && now - state.lastVideoCheck < 100) {
return state.cachedVideo;
}
for (const selector of selectors) {
const video = YouTubeUtils.querySelector(selector);
if (video) {
state.cachedVideo = video;
state.lastVideoCheck = now;
return state.cachedVideo;
}
}
state.cachedVideo = null;
return null;
};
})();
const utils = {
isInShortsPage: () => location.pathname.startsWith("/shorts/"),
isInputFocused: () => {
const el = document.activeElement;
return !(!el?.matches?.('input, textarea, [contenteditable="true"]') && !el?.isContentEditable);
},
loadSettings: () => {
try {
const saved = localStorage.getItem(config.storageKey);
if (!saved) {
return;
}
const parsed = JSON.parse(saved);
if ("object" != typeof parsed || null === parsed) {
window.console.warn("[YouTube+][Shorts]", "Invalid settings format");
return;
}
"boolean" == typeof parsed.enabled && (config.enabled = parsed.enabled);
if (parsed.shortcuts && "object" == typeof parsed.shortcuts) {
const defaultShortcuts = utils.getDefaultShortcuts();
for (const [action, shortcut] of Object.entries(parsed.shortcuts)) {
const defaultSc = defaultShortcuts;
if (!defaultSc[action]) {
continue;
}
if (!shortcut || "object" != typeof shortcut) {
continue;
}
const {key: sKey, editable: sEditable} = shortcut;
"string" == typeof sKey && sKey.length > 0 && sKey.length <= 20 && (config.shortcuts[action] = {
key: sKey,
description: defaultSc[action].description,
editable: !1 !== sEditable
});
}
}
} catch (error) {
window.console.error("[YouTube+][Shorts]", "Error loading settings:", error);
}
},
saveSettings: () => {
try {
const settingsToSave = {
enabled: config.enabled,
shortcuts: config.shortcuts
};
localStorage.setItem(config.storageKey, JSON.stringify(settingsToSave));
} catch (error) {
window.console.error("[YouTube+][Shorts]", "Error saving settings:", error);
}
},
getDefaultShortcuts: () => ({
seekBackward: {
key: "ArrowLeft",
get description() {
return t("seekBackward");
}
},
seekForward: {
key: "ArrowRight",
get description() {
return t("seekForward");
}
},
volumeUp: {
key: "+",
get description() {
return t("volumeUp");
}
},
volumeDown: {
key: "-",
get description() {
return t("volumeDown");
}
},
mute: {
key: "m",
get description() {
return t("muteUnmute");
}
},
toggleCaptions: {
key: "c",
get description() {
return t("toggleCaptions");
}
},
showHelp: {
key: "?",
get description() {
return t("showHideHelp");
},
editable: !1
}
})
};
const feedback = (() => {
let element = null;
return {
show: text => {
state.lastAction = text;
clearTimeout(state.actionTimeout ?? void 0);
const el = (() => {
if (element) {
return element;
}
element = document.createElement("div");
element.id = "shorts-keyboard-feedback";
document.body.appendChild(element);
element.style.position = "fixed";
element.style.top = "50%";
element.style.left = "50%";
element.style.transform = "translate(-50%,-50%)";
element.style.background = "var(--yt-shorts-feedback-bg-dark)";
element.style.backdropFilter = "blur(12px) saturate(180%)";
element.style.border = "1px solid var(--yt-shorts-border-light)";
element.style.borderRadius = "20px";
element.style.color = "var(--yt-text-primary,#fff)";
element.style.padding = "18px 32px";
element.style.fontSize = "20px";
element.style.fontWeight = "700";
element.style.zIndex = "10000";
element.style.opacity = "0";
element.style.visibility = "hidden";
element.style.pointerEvents = "none";
element.style.transition = "all .3s cubic-bezier(.4,0,.2,1)";
element.style.textAlign = "center";
element.style.boxShadow = "0 8px 32px 0 var(--yt-shorts-shadow-blue)";
element.style.setProperty("-webkit-backdrop-filter", "blur(12px) saturate(180%)");
return element;
})();
el.textContent = text;
requestAnimationFrame(() => {
el.style.opacity = "1";
el.style.visibility = "visible";
el.style.transform = "translate(-50%,-50%) scale(1.05)";
});
state.actionTimeout = setTimeout_(() => {
el.style.opacity = "0";
el.style.visibility = "hidden";
el.style.transform = "translate(-50%,-50%) scale(0.95)";
}, 1500);
}
};
})();
const actions = {
seekBackward: () => {
const video = getCurrentVideo();
if (video) {
video.currentTime = Math.max(0, video.currentTime - 5);
feedback.show("-5s");
}
},
seekForward: () => {
const video = getCurrentVideo();
if (video) {
video.currentTime = Math.min(video.duration || Infinity, video.currentTime + 5);
feedback.show("+5s");
}
},
toggleCaptions: () => {
try {
const container = qs("ytd-shorts-player-controls, ytd-reel-video-renderer, #shorts-player") || document;
const buttons = container.querySelectorAll("button[aria-label]");
for (const b of buttons) {
const aria = (b.getAttribute("aria-label") || "").toLowerCase();
if ((aria.includes("subtit") || aria.includes("caption") || aria.includes("субтит") || aria.includes("субтитр") || aria.includes("cc")) && null !== b.offsetParent) {
b.click();
break;
}
}
} catch (e) {}
const video = getCurrentVideo();
if (video && video.textTracks && video.textTracks.length) {
const tracks = Array.from(video.textTracks).filter(tr => "subtitles" === tr.kind || "captions" === tr.kind || !tr.kind);
if (tracks.length) {
const anyShowing = tracks.some(tr => "showing" === tr.mode);
tracks.forEach(tr => {
tr.mode = anyShowing ? "hidden" : "showing";
});
feedback.show(t(anyShowing ? "captionsOff" : "captionsOn"));
return;
}
}
feedback.show(t("captionsUnavailable"));
},
volumeUp: () => {
const video = getCurrentVideo();
if (video) {
video.volume = Math.min(1, video.volume + .1);
feedback.show(`${Math.round(100 * video.volume)}%`);
}
},
volumeDown: () => {
const video = getCurrentVideo();
if (video) {
video.volume = Math.max(0, video.volume - .1);
feedback.show(`${Math.round(100 * video.volume)}%`);
}
},
mute: () => {
const video = getCurrentVideo();
try {
const container = qs("ytd-shorts-player-controls, ytd-reel-video-renderer, #shorts-player") || document;
const buttons = container.querySelectorAll("button[aria-label]");
for (const b of buttons) {
const aria = (b.getAttribute("aria-label") || "").toLowerCase();
if ((aria.includes("mute") || aria.includes("unmute") || aria.includes("sound") || aria.includes("volume") || aria.includes("звук") || aria.includes("громк")) && null !== b.offsetParent) {
b.click();
setTimeout(() => {
const v = getCurrentVideo();
v && feedback.show(v.muted ? "🔇" : "🔊");
}, 60);
return;
}
}
} catch (e) {}
if (video) {
video.muted = !video.muted;
feedback.show(video.muted ? "🔇" : "🔊");
}
},
showHelp: () => helpPanel.toggle()
};
const helpPanel = (() => {
let panel = null;
let dragListeners = null;
const create = () => {
if (panel) {
return panel;
}
panel = document.createElement("div");
panel.id = "shorts-keyboard-help";
panel.className = "shorts-help-panel ytp-plus-shorts-overlay";
panel.setAttribute("role", "dialog");
panel.setAttribute("aria-modal", "true");
panel.tabIndex = -1;
const render = () => {
if (!panel) {
return;
}
const p = panel;
renderTemplateClone(p, `\n            <div class="help-topbar">\n              <div class="help-header ytp-plus-settings-title">${t("keyboardShortcuts")}</div>\n              <button class="ytp-plus-settings-close help-close" data-shared-close-button="ytp-plus-close-settings" type="button" aria-label="${t("closeButton")}">\n                  <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n                    <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>\n                  </svg>\n              </button>\n            </div>\n            <div class="help-body">\n              <div class="help-content">\n                ${Object.entries(config.shortcuts).map(([action, shortcut]) => {
const sc = shortcut;
return `<div class="help-item">\n                    <kbd data-action="${action}" ${!1 === sc.editable ? 'class="non-editable"' : ""}>${" " === shortcut.key ? "Space" : shortcut.key}</kbd>\n                    <span>${shortcut.description}</span>\n                  </div>`;
}).join("")}\n              </div>\n              <div class="help-actions">\n                <button class="ytp-plus-button ytp-plus-button-primary reset-all-shortcuts">${t("resetAll")}</button>\n              </div>\n            </div>\n          `);
const helpClose = p.querySelector(".help-close");
helpClose && (helpClose.onclick = () => helpPanel.hide());
const resetBtn = p.querySelector(".reset-all-shortcuts");
resetBtn && (resetBtn.onclick = () => {
if (confirm(t("resetAllConfirm"))) {
const defaultShortcuts = utils.getDefaultShortcuts();
Object.assign(config, {
_shortcuts: defaultShortcuts
});
utils.saveSettings();
feedback.show(t("shortcutsReset"));
render();
}
});
p.querySelectorAll("kbd[data-action]:not(.non-editable)").forEach(kbd => {
const kbdEl = kbd;
kbdEl.onclick = () => {
const act = kbdEl.getAttribute("data-action") || "";
const sc = config.shortcuts;
editShortcut(act, sc[act]?.key || "");
};
});
(panelEl => {
dragListeners?.abort();
dragListeners = new AbortController;
const {signal} = dragListeners;
const dragHandle = panelEl.querySelector(".help-content");
if (!(dragHandle instanceof HTMLElement)) {
return;
}
let dragging = !1;
let offsetX = 0;
let offsetY = 0;
const stopDragging = () => {
if (dragging) {
dragging = !1;
panelEl.classList.remove("is-dragging");
}
};
const eventOptions = !!signal && {
signal
};
dragHandle.addEventListener("pointerdown", ev => {
if (0 !== ev.button) {
return;
}
const target = ev.target instanceof Element ? ev.target : null;
if (target?.closest("button,kbd,a,input,textarea,select,label")) {
return;
}
const rect = panelEl.getBoundingClientRect();
panelEl.style.left = `${rect.left}px`;
panelEl.style.top = `${rect.top}px`;
panelEl.style.transform = "none";
offsetX = ev.clientX - rect.left;
offsetY = ev.clientY - rect.top;
dragging = !0;
panelEl.classList.add("is-dragging");
}, eventOptions);
window.addEventListener("pointermove", ev => {
if (!dragging) {
return;
}
const maxLeft = Math.max(0, window.innerWidth - panelEl.offsetWidth);
const maxTop = Math.max(0, window.innerHeight - panelEl.offsetHeight);
const nextLeft = Math.min(Math.max(0, ev.clientX - offsetX), maxLeft);
const nextTop = Math.min(Math.max(0, ev.clientY - offsetY), maxTop);
panelEl.style.left = `${nextLeft}px`;
panelEl.style.top = `${nextTop}px`;
}, eventOptions);
window.addEventListener("pointerup", stopDragging, eventOptions);
window.addEventListener("blur", stopDragging, eventOptions);
})(p);
};
render();
document.body.appendChild(panel);
return panel;
};
return {
show: () => {
const p = create();
p.classList.add("visible");
state.helpVisible = !0;
p.focus();
},
hide: () => {
if (panel) {
panel.classList.remove("visible");
state.helpVisible = !1;
}
},
toggle: () => state.helpVisible ? helpPanel.hide() : helpPanel.show(),
refresh: () => {
dragListeners?.abort();
if (panel) {
panel.remove();
panel = null;
}
}
};
})();
const editShortcut = (actionKey, currentKey) => {
const dialog = document.createElement("div");
dialog.className = "glass-modal shortcut-edit-dialog ytp-plus-shortcut-modal";
dialog.setAttribute("role", "dialog");
dialog.setAttribute("aria-modal", "true");
const sc = config.shortcuts;
renderTemplateClone(dialog, `\n        <div class="glass-panel shortcut-edit-content">\n          <h4>${t("editShortcut")}: ${sc[actionKey]?.description || actionKey}</h4>\n          <p>${t("pressAnyKey")}</p>\n          <div class="current-shortcut">${t("current")}: <kbd>${" " === currentKey ? "Space" : currentKey}</kbd></div>\n          <button class="ytp-plus-button ytp-plus-button-primary shortcut-cancel" type="button">${t("cancel")}</button>\n        </div>\n      `);
document.body.appendChild(dialog);
state.editingShortcut = actionKey;
const handleKey = e => {
e.preventDefault();
e.stopPropagation();
if ("Escape" === e.key) {
return cleanup();
}
const conflict = Object.keys(config.shortcuts).find(key => key !== actionKey && config.shortcuts[key]?.key === e.key);
if (conflict) {
feedback.show(t("keyAlreadyUsed", {
key: e.key
}));
} else {
config.shortcuts[actionKey].key = e.key;
utils.saveSettings();
feedback.show(t("shortcutUpdated"));
helpPanel.refresh();
cleanup();
}
};
const cleanup = () => {
document.removeEventListener("keydown", handleKey, !0);
dialog.remove();
state.editingShortcut = null;
};
const cancelBtn = dialog.querySelector(".shortcut-cancel");
cancelBtn && (cancelBtn.onclick = cleanup);
dialog.onclick = ev => {
ev && ev.target === dialog && cleanup();
};
YouTubeUtils.cleanupManager.registerListener(document, "keydown", handleKey, !0);
};
const removeShortsDownloadButton = () => {
state.downloadButton && state.downloadButton.isConnected && state.downloadButton.remove();
state.downloadButton = null;
};
const ensureShortsDownloadButton = () => {
if (!isOnShortsPage()) {
removeShortsDownloadButton();
return;
}
const globalSettings = window.youtubePlus?.settings;
if (!1 === globalSettings?.enableDownload) {
removeShortsDownloadButton();
return;
}
const getActiveReel = () => qs("ytd-reel-video-renderer[is-active]") || qs('ytd-reel-video-renderer[is-active="true"]') || qs("#shorts-player ytd-reel-video-renderer");
const actionBar = (() => {
const activeReel = getActiveReel();
const selectors = [ "ytwReelActionBarViewModelHostDesktop", "ytwReelActionBarViewModelHost", '[class*="ytwReelActionBarViewModelHostDesktop"]', '[class*="ytwReelActionBarViewModelHost"]', ".ytwReelActionBarViewModelHostDesktop", ".ytwReelActionBarViewModelHost", "reel-action-bar-view-model", "ytd-reel-player-overlay-renderer #actions", "#actions" ];
const pickFrom = root => {
for (const selector of selectors) {
const nodes = root.querySelectorAll(selector);
for (const node of nodes) {
if (node instanceof HTMLElement && null !== node.offsetParent) {
return node;
}
}
}
return null;
};
if (activeReel instanceof Element) {
const fromActive = pickFrom(activeReel);
if (fromActive) {
return fromActive;
}
}
const fromDocument = pickFrom(document);
if (fromDocument) {
return fromDocument;
}
if (activeReel instanceof Element) {
for (const selector of selectors) {
const candidate = activeReel.querySelector(selector);
if (candidate instanceof HTMLElement) {
return candidate;
}
}
}
return null;
})();
const likeButton = actionBar ? (actionBar => {
if (!(actionBar instanceof Element)) {
return null;
}
const likeSelectors = [ "like-button-view-model", "#like-button", 'button[aria-label*="Like" i]', 'button[aria-label*="Нравится" i]' ];
for (const selector of likeSelectors) {
const node = actionBar.querySelector(selector);
if (node instanceof HTMLElement) {
return node;
}
}
return null;
})(actionBar) : (() => {
const likeSelectors = [ "like-button-view-model", "#like-button", 'button[aria-label*="Like" i]', 'button[aria-label*="Нравится" i]' ];
const activeReel = getActiveReel();
if (activeReel instanceof Element) {
for (const selector of likeSelectors) {
const node = activeReel.querySelector(selector);
if (node instanceof HTMLElement && null !== node.offsetParent) {
return node;
}
}
}
for (const selector of likeSelectors) {
const node = document.querySelector(selector);
if (node instanceof HTMLElement && null !== node.offsetParent) {
return node;
}
}
return null;
})();
const likeAnchor = likeButton && likeButton.closest('like-button-view-model, #like-button, reel-action-view-model, ytw-reel-action-view-model, [class*="ReelActionViewModel"]') || likeButton;
if (!actionBar && !likeAnchor) {
return;
}
if (state.downloadButton?.isConnected && likeAnchor instanceof Element && state.downloadButton.nextElementSibling === likeAnchor) {
return;
}
if (state.downloadButton?.isConnected) {
state.downloadButton.remove();
state.downloadButton = null;
}
const btn = document.createElement("button");
btn.className = "ytp-plus-shorts-download";
btn.type = "button";
btn.setAttribute("aria-label", t("download"));
btn.setAttribute("title", t("downloadOptions") || t("download"));
renderTemplateClone(btn, '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity="0.5" d="M3 15C3 17.8284 3 19.2426 3.87868 20.1213C4.75736 21 6.17157 21 9 21H15C17.8284 21 19.2426 21 20.1213 20.1213C21 19.2426 21 17.8284 21 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 3V16M12 16L16 11.625M12 16L8 11.625" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>');
btn.addEventListener("click", ev => {
ev.preventDefault();
ev.stopPropagation();
"function" != typeof window.YouTubePlusDownload?.openModal ? feedback.show(t("directDownloadModuleNotAvailable") || t("downloadNotAvailable")) : window.YouTubePlusDownload.openModal();
});
if (likeAnchor instanceof Element && likeAnchor.parentElement) {
likeAnchor.insertAdjacentElement("beforebegin", btn);
} else {
if (!actionBar) {
return;
}
actionBar.prepend(btn);
}
state.downloadButton = btn;
};
const handleKeydown = e => {
if (!config.enabled || !utils.isInShortsPage() || utils.isInputFocused() || state.editingShortcut) {
return;
}
let {key} = e;
"NumpadAdd" === e.code ? key = "+" : "NumpadSubtract" === e.code && (key = "-");
const action = Object.keys(config.shortcuts).find(k => config.shortcuts[k]?.key === key);
if (action && actions[action]) {
e.preventDefault();
e.stopPropagation();
actions[action]();
}
};
const isOnShortsPage = () => location.pathname.startsWith("/shorts/");
const init = () => {
if (!isOnShortsPage()) {
return;
}
if (state.initialized) {
return;
}
state.initialized = !0;
utils.loadSettings();
(() => {
if (byId("shorts-keyboard-styles")) {
return;
}
YouTubeUtils.StyleManager.add("shorts-keyboard-styles", '\n                  .shorts-help-panel{position:fixed;top:50%;left:25%;transform:translate(-50%,-50%) scale(.9);z-index:10001;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease,transform .3s ease;width:340px;max-width:95vw;max-height:80vh;overflow:hidden;outline:none;color:var(--yt-text-primary,#fff);padding:14px;display:flex;flex-direction:column;gap:12px;}\n                .shorts-help-panel.visible{opacity:1;visibility:visible;transform:translate(-50%,-50%) scale(1);}\n                  .help-topbar{display:flex;align-items:center;justify-content:space-between;gap:10px;}\n                  .help-header{margin:0;line-height:1.2;}\n                  .help-close{position:static;display:flex;align-items:center;justify-content:center;padding:4px;flex-shrink:0;}\n                  .help-body{display:flex;flex-direction:column;gap:12px;min-height:0;}\n                  .help-content{padding:8px 10px;max-height:400px;overflow-y:auto;cursor:grab;user-select:none;-webkit-user-select:none;touch-action:none;border-radius:12px;background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);}\n                .shorts-help-panel.is-dragging .help-content,.help-content:active{cursor:grabbing;}\n                .help-item{display:flex;align-items:center;margin-bottom:14px;gap:18px;}\n                .help-item kbd{background:var(--yt-shorts-kbd-bg);color:inherit;padding:7px 14px;border-radius:8px;font-family:monospace;font-size:15px;font-weight:700;min-width:60px;text-align:center;border:1.5px solid var(--yt-shorts-kbd-border);cursor:pointer;transition:all .2s;position:relative;}\n                html:not([dark]) .help-item kbd{background:var(--yt-shorts-kbd-bg-light);color:#222;border:1.5px solid var(--yt-shorts-border-dark);}\n                .help-item kbd:hover{background:var(--yt-shorts-kbd-hover);transform:scale(1.07);}\n                .help-item kbd:after{content:"✎";position:absolute;top:-7px;right:-7px;font-size:11px;opacity:0;transition:opacity .2s;}\n                .help-item kbd:hover:after{opacity:.7;}\n                .help-item kbd.non-editable{cursor:default;opacity:.7;}\n                .help-item kbd.non-editable:hover{background:var(--yt-shorts-kbd-bg);transform:none;}\n                .help-item kbd.non-editable:after{display:none;}\n                .help-item span{font-size:15px;color:var(--yt-shorts-text-secondary);}\n                html:not([dark]) .help-item span{color:#222;}\n                html:not([dark]) .shorts-help-panel{color:var(--yt-text-dark-primary,#222);}\n                .help-actions{display:flex;justify-content:flex-end;align-items:center;}\n                .reset-all-shortcuts{display:inline-flex;align-items:center;justify-content:center;gap:var(--yt-space-sm);}\n                .ytp-plus-shorts-download{width:48px;height:48px;border-radius:999px;display:flex;align-items:center;justify-content:center;z-index:1;cursor:pointer;box-shadow:var(--yt-glass-shadow);background:var(--yt-glass-bg);border:1px solid var(--yt-glass-border);backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur);margin:0 auto 10px;align-self:center;color:var(--yt-text-primary);transition:all .3s;}\n                .ytp-plus-shorts-download svg{width:22px;height:22px;display:block;pointer-events:none;}\n                .ytp-plus-shorts-download:hover{background:var(--yt-glass-border);}\n                .shortcut-edit-dialog{z-index:10002;}\n                .shortcut-edit-content{padding:28px 32px;min-width:320px;text-align:center;display:flex;flex-direction:column;gap:var(--yt-space-md);color:inherit;}\n                html:not([dark]) .shortcut-edit-content{color:#222;}\n                .shortcut-edit-content h4{margin:0 0 14px;font-size:17px;font-weight:700;}\n                .shortcut-edit-content p{margin:0 0 18px;font-size:15px;color:rgba(255,255,255,.85);}\n                html:not([dark]) .shortcut-edit-content p{color:#222;}\n                .current-shortcut{margin:18px 0;font-size:15px;}\n                .current-shortcut kbd{background:var(--yt-shorts-kbd-bg);padding:5px 12px;border-radius:6px;font-family:monospace;border:1.5px solid var(--yt-shorts-kbd-border);}\n                html:not([dark]) .current-shortcut kbd{background:var(--yt-shorts-kbd-bg-light);color:#222;border:1.5px solid var(--yt-shorts-border-dark);}\n                .shortcut-cancel{display:inline-flex;align-items:center;justify-content:center;gap:var(--yt-space-sm);}\n                @media(max-width:480px){.shorts-help-panel{width:98vw;max-height:85vh;padding:10px}.help-content{padding:10px 8px}.help-item{gap:10px}.help-item kbd{min-width:44px;font-size:13px;padding:5px 7px}.ytp-plus-shorts-download{width:44px;height:44px;margin-bottom:8px}.shortcut-edit-content{margin:20px;min-width:auto}}\n                #shorts-keyboard-feedback{background:var(--yt-shorts-feedback-bg-dark);color:var(--yt-text-primary,#fff);border:1.5px solid var(--yt-shorts-feedback-bg);border-radius:20px;box-shadow:0 8px 32px 0 var(--yt-shorts-shadow-blue);backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);}\n                html:not([dark]) #shorts-keyboard-feedback{background:var(--yt-shorts-feedback-bg-light);color:var(--yt-text-dark-primary,#222);border:1.5px solid var(--yt-shorts-border-dark);}\n            ');
})();
ensureShortsDownloadButton();
if (!state.downloadObserver) {
state.downloadObserver = new MutationObserver(() => {
if (isOnShortsPage() && !state.downloadEnsureQueued) {
state.downloadEnsureQueued = !0;
requestAnimationFrame(() => {
state.downloadEnsureQueued = !1;
ensureShortsDownloadButton();
});
}
});
state.downloadObserver.observe(document.body, {
childList: !0,
subtree: !0
});
YouTubeUtils.cleanupManager?.registerObserver && YouTubeUtils.cleanupManager.registerObserver(state.downloadObserver);
}
YouTubeUtils.cleanupManager.registerListener(document, "keydown", handleKeydown, !0);
YouTubeUtils.cleanupManager.registerListener(document, "click", ev => {
const tgt = ev.target instanceof Element ? ev.target : null;
state.helpVisible && tgt?.closest && !tgt.closest("#shorts-keyboard-help") && helpPanel.hide();
});
YouTubeUtils.cleanupManager.registerListener(document, "keydown", e => {
if ("Escape" === e.key && state.helpVisible) {
e.preventDefault();
helpPanel.hide();
}
});
};
const observeRoute = () => {
let lastPath = location.pathname;
let isCurrentlyOnShorts = isOnShortsPage();
const syncRouteState = () => {
const currentPath = location.pathname;
if (currentPath === lastPath) {
return;
}
lastPath = currentPath;
const nowOnShorts = isOnShortsPage();
if (nowOnShorts !== isCurrentlyOnShorts) {
isCurrentlyOnShorts = nowOnShorts;
!nowOnShorts && state.initialized ? (() => {
if (state.initialized) {
state.helpVisible && helpPanel.hide();
if (state.actionTimeout) {
clearTimeout(state.actionTimeout);
state.actionTimeout = null;
}
state.cachedVideo = null;
if (state.downloadObserver) {
state.downloadObserver.disconnect();
state.downloadObserver = null;
}
state.downloadEnsureQueued = !1;
removeShortsDownloadButton();
state.initialized = !1;
}
})() : nowOnShorts && !state.initialized && init();
} else {
nowOnShorts && ensureShortsDownloadButton();
}
};
if (YouTubeUtils.cleanupManager?.registerListener) {
YouTubeUtils.cleanupManager.registerListener(window, "yt-navigate-finish", syncRouteState);
YouTubeUtils.cleanupManager.registerListener(window, "popstate", syncRouteState);
} else {
window.addEventListener("yt-navigate-finish", syncRouteState);
window.addEventListener("popstate", syncRouteState);
}
};
let shortsRuntimeStarted = !1;
const startShortsRuntime = () => {
if (!shortsRuntimeStarted) {
shortsRuntimeStarted = !0;
if ("loading" === document.readyState) {
document.addEventListener("DOMContentLoaded", () => {
init();
observeRoute();
}, {
once: !0
});
} else {
init();
observeRoute();
}
isOnShortsPage() && !localStorage.getItem("shorts_keyboard_help_shown") && setTimeout_(() => {
if (isOnShortsPage()) {
feedback.show("Press ? for shortcuts");
localStorage.setItem("shorts_keyboard_help_shown", "true");
}
}, 2e3);
}
};
window.YouTubePlusLazyLoader?.register ? window.YouTubePlusLazyLoader.register("shorts", startShortsRuntime, {
priority: 50,
delay: 0,
shouldLoad: isOnShortsPage
}) : startShortsRuntime();
})();

!(function() {
"use strict";
if (window.__ytpVideoStatsModuleInit) {
return;
}
const initVideoStats = () => {
if (window.__ytpVideoStatsModuleInit) {
return;
}
window.__ytpVideoStatsModuleInit = !0;
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const setTimeout_ = setTimeout.bind(window);
const utils = window.YouTubeUtils;
const $ = utils.$;
const $$ = utils.$$;
const byId = utils.byId;
if (window.YouTubeUtils?.isStudioPage?.()) {
return;
}
let statsInitialized = !1;
const t = window.YouTubeUtils.t;
const escapeHtml = window.YouTubeSafeDOM?.escapeHTML || window.YouTubeSecurityUtils?.escapeHtml || (s => {
const d = document.createElement("div");
d.textContent = s;
return d.innerHTML;
});
const STATS_ICON_ID = "ytp-stats-universal-icon";
const STATS_ICON_SELECTOR = `#${STATS_ICON_ID}, .videoStats[data-ytp-stats-icon="true"], .videoStats`;
const _safeLS_getItem = (k, def = null) => {
try {
return localStorage.getItem(k) ?? def;
} catch (e) {
return def;
}
}, _safeLS_setItem = (k, v) => {
try {
localStorage.setItem(k, v);
return !0;
} catch (e) {
return !1;
}
};
let statsButtonEnabled = "false" !== _safeLS_getItem("youtube_stats_button_enabled");
let previousUrl = location.href;
let isChecking = !1;
let experimentalNavListenerKey = null;
const channelFeatures_hasStreams = !1, channelFeatures_hasShorts = !1;
const rateLimiter = {
requests: new Map,
maxRequests: 10,
maxKeys: 100,
timeWindow: 6e4,
canRequest: key => {
const now = Date.now();
const requests = rateLimiter.requests.get(key) || [];
const recentRequests = requests.filter(time => now - time < rateLimiter.timeWindow);
if (recentRequests.length >= rateLimiter.maxRequests) {
window.console.warn(`[YouTube+][Stats] Rate limit exceeded for ${key}. Max ${rateLimiter.maxRequests} requests per minute.`);
return !1;
}
recentRequests.push(now);
rateLimiter.requests.set(key, recentRequests);
if (rateLimiter.requests.size > rateLimiter.maxKeys) {
const firstKey = rateLimiter.requests.keys().next().value;
"string" == typeof firstKey && rateLimiter.requests.delete(firstKey);
}
return !0;
},
clear: () => {
rateLimiter.requests.clear();
}
};
const isValidVideoId = window.YouTubeSecurityUtils?.isValidVideoId || (id => !!id && /^[a-zA-Z0-9_-]{11}$/.test(id));
function getCurrentVideoUrl() {
try {
const url = window.location.href;
if (!url.includes("youtube.com")) {
return null;
}
const fromParams = (function getVideoIdFromParams() {
const urlParams = new URLSearchParams(window.location.search);
const videoId = urlParams.get("v");
return isValidVideoId(videoId) ? `https://www.youtube.com/watch?v=${videoId}` : null;
})();
return fromParams || (function getVideoIdFromShorts(url) {
const shortsMatch = url.match(/\/shorts\/([^?]+)/);
return shortsMatch && isValidVideoId(shortsMatch[1]) ? `https://www.youtube.com/shorts/${shortsMatch[1]}` : null;
})(url);
} catch (error) {
YouTubeUtils?.logError?.("Stats", "Failed to get video URL", error);
return null;
}
}
function getChannelIdentifier() {
try {
const url = window.location.href;
let identifier = "";
url.includes("/channel/") ? identifier = url.split("/channel/")[1].split("/")[0] : url.includes("/@") && (identifier = url.split("/@")[1].split("/")[0]);
return identifier && /^[a-zA-Z0-9_-]+$/.test(identifier) ? identifier : "";
} catch (error) {
YouTubeUtils?.logError?.("Stats", "Failed to get channel identifier", error);
return "";
}
}
function getTabUrl(tab) {
return tab?.tabRenderer?.endpoint?.commandMetadata?.webCommandMetadata?.url || null;
}
function tabMatches(url, pattern) {
return "string" == typeof url && pattern.test(url);
}
function hasBothContentTypes(hasStreams, hasShorts) {
return hasStreams && hasShorts;
}
function updateContentTypeFlags(tabUrl, flags) {
!flags.hasStreams && (function isStreamsTab(tabUrl) {
return tabMatches(tabUrl, /\/streams$/);
})(tabUrl) && (flags.hasStreams = !0);
!flags.hasShorts && (function isShortsTab(tabUrl) {
return tabMatches(tabUrl, /\/shorts$/);
})(tabUrl) && (flags.hasShorts = !0);
}
async function checkChannelTabs(url) {
if (!isChecking && (function validateYouTubeUrl(url) {
if (!url || "string" != typeof url) {
return !1;
}
try {
const parsedUrl = new URL(url);
if ("www.youtube.com" !== parsedUrl.hostname && "youtube.com" !== parsedUrl.hostname) {
window.console.warn("[YouTube+][Stats] Invalid domain for channel check");
return !1;
}
return !0;
} catch (error) {
YouTubeUtils?.logError?.("Stats", "Invalid URL for channel check", error);
return !1;
}
})(url) && rateLimiter.canRequest("checkChannelTabs")) {
isChecking = !0;
try {
const html = await (async function fetchChannelHtml(url) {
try {
const parsed = new URL(url);
const hostname = parsed.hostname.toLowerCase();
if ("www.youtube.com" !== hostname && "youtube.com" !== hostname && "m.youtube.com" !== hostname) {
window.console.warn("[YouTube+][Stats] Blocked fetch to non-YouTube URL:", hostname);
return null;
}
} catch (e) {
return null;
}
const controller = new AbortController;
const timeoutId = setTimeout_(() => controller.abort(), 1e4);
try {
const response = await fetch(url, {
credentials: "same-origin",
signal: controller.signal,
headers: {
Accept: "text/html"
}
});
clearTimeout(timeoutId);
if (!response.ok) {
window.console.warn(`[YouTube+][Stats] HTTP ${response.status} when checking channel tabs`);
return null;
}
const html = await response.text();
if (html.length > 5e6) {
window.console.warn("[YouTube+][Stats] Response too large, skipping parse");
return null;
}
return html;
} catch (error) {
"AbortError" === error.name && window.console.warn("[YouTube+][Stats] Channel check timed out");
throw error;
}
})(url);
if (!html) {
return;
}
const data = (function extractYouTubeData(html) {
const match = html.match(/var ytInitialData = (.+?);<\/script>/);
if (!match || !match[1]) {
return null;
}
try {
return JSON.parse(match[1]);
} catch (parseError) {
YouTubeUtils?.logError?.("Stats", "Failed to parse ytInitialData", parseError);
return null;
}
})(html);
if (!data) {
return;
}
const flags = (function analyzeChannelTabs(data) {
const tabs = data?.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
const flags = {
hasStreams: !1,
hasShorts: !1
};
for (const tab of tabs) {
const tabUrl = getTabUrl(tab);
if (tabUrl) {
updateContentTypeFlags(tabUrl, flags);
if (hasBothContentTypes(flags.hasStreams, flags.hasShorts)) {
break;
}
}
}
return flags;
})(data);
(flags.hasStreams || flags.hasShorts) && window.console.info("[YouTube+][Stats] Channel tabs:", {
hasStreams: flags.hasStreams,
hasShorts: flags.hasShorts
});
!(function refreshStatsMenu() {
const existingMenu = $(".stats-menu-container");
if (existingMenu) {
existingMenu.remove();
createStatsMenu();
}
})();
} catch (error) {
YouTubeUtils?.logError?.("Stats", "Channel tab check failed", error);
} finally {
isChecking = !1;
}
}
}
const checkUrlChange = YouTubeUtils?.debounce?.(() => {
try {
const currentUrl = location.href;
if (currentUrl !== previousUrl) {
previousUrl = currentUrl;
window.YouTubeUtils?.isChannelPage?.(currentUrl) && setTimeout(() => checkChannelTabs(currentUrl), 500);
}
} catch (error) {
YouTubeUtils?.logError?.("Stats", "URL change check failed", error);
}
}, 300) || function() {
try {
const currentUrl = location.href;
if (currentUrl !== previousUrl) {
previousUrl = currentUrl;
window.YouTubeUtils?.isChannelPage?.(currentUrl) && setTimeout(() => checkChannelTabs(currentUrl), 500);
}
} catch (error) {
window.console.error("[YouTube+][Stats] URL change check failed:", error);
}
};
function insertUniversalIcon() {
if (!statsButtonEnabled) {
return;
}
let masthead = $("ytd-masthead.style-scope");
masthead || (masthead = $("ytd-masthead"));
if (!masthead) {
return;
}
let endElem = $("#end.style-scope.ytd-masthead", masthead);
endElem || (endElem = $("#end", masthead));
const existingIcons = $$(STATS_ICON_SELECTOR);
let statsIcon = byId(STATS_ICON_ID);
!statsIcon && existingIcons.length > 0 && (statsIcon = existingIcons[0]);
existingIcons.length > 1 && existingIcons.forEach(icon => {
if (icon !== statsIcon) {
try {
icon.remove();
} catch (e) {}
}
});
if (statsIcon) {
statsIcon.id = STATS_ICON_ID;
statsIcon.classList.add("videoStats");
statsIcon.setAttribute("data-ytp-stats-icon", "true");
} else {
statsIcon = (function createStatsIcon() {
const icon = document.createElement("div");
icon.className = "videoStats";
icon.id = STATS_ICON_ID;
icon.setAttribute("data-ytp-stats-icon", "true");
_setSafeHTML(icon, '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22 22H2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path> <path opacity="0.5" d="M21 22V14.5C21 13.6716 20.3284 13 19.5 13H16.5C15.6716 13 15 13.6716 15 14.5V22" stroke="currentColor" stroke-width="1.5" " data-darkreader-inline-stroke=""></path> <path d="M15 22V5C15 3.58579 15 2.87868 14.5607 2.43934C14.1213 2 13.4142 2 12 2C10.5858 2 9.87868 2 9.43934 2.43934C9 2.87868 9 3.58579 9 5V22" stroke="currentColor" stroke-width="1.5"></path> <path opacity="0.5" d="M9 22V9.5C9 8.67157 8.32843 8 7.5 8H4.5C3.67157 8 3 8.67157 3 9.5V22" stroke="currentColor" stroke-width="1.5"></path></svg>');
icon.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
const videoUrl = getCurrentVideoUrl();
if (videoUrl) {
const urlParams = new URLSearchParams(new URL(videoUrl).search);
const videoId = urlParams.get("v") || videoUrl.match(/\/shorts\/([^?]+)/)?.[1];
videoId && openStatsModal("video", videoId);
}
});
return icon;
})();
}
endElem ? statsIcon.parentNode === endElem && endElem.firstChild === statsIcon || endElem.insertBefore(statsIcon, endElem.firstChild) : statsIcon.parentNode !== masthead && masthead.appendChild(statsIcon);
}
function createButton(text, svgPath, viewBox, className, onClick) {
const buttonViewModel = document.createElement("button-view-model");
buttonViewModel.className = `yt-spec-button-view-model ${className}-view-model`;
const button = document.createElement("button");
button.className = `yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment ${className}-button`;
button.setAttribute("aria-disabled", "false");
button.setAttribute("aria-label", text);
if (button.style) {
button.style.display = "flex";
button.style.alignItems = "center";
button.style.justifyContent = "center";
button.style.gap = "8px";
}
button.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
onClick();
});
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", viewBox);
if (svg.style) {
svg.style.width = "20px";
svg.style.height = "20px";
svg.style.fill = "currentColor";
}
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", svgPath);
svg.appendChild(path);
const buttonText = document.createElement("div");
buttonText.className = `yt-spec-button-shape-next__button-text-content ${className}-text`;
buttonText.textContent = text;
if (buttonText.style) {
buttonText.style.display = "flex";
buttonText.style.alignItems = "center";
}
const touchFeedback = document.createElement("yt-touch-feedback-shape");
touchFeedback.style && (touchFeedback.style.borderRadius = "inherit");
const touchFeedbackDiv = document.createElement("div");
touchFeedbackDiv.className = "yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response";
touchFeedbackDiv.setAttribute("aria-hidden", "true");
const strokeDiv = document.createElement("div");
strokeDiv.className = "yt-spec-touch-feedback-shape__stroke";
const fillDiv = document.createElement("div");
fillDiv.className = "yt-spec-touch-feedback-shape__fill";
touchFeedbackDiv.appendChild(strokeDiv);
touchFeedbackDiv.appendChild(fillDiv);
touchFeedback.appendChild(touchFeedbackDiv);
button.appendChild(svg);
button.appendChild(buttonText);
button.appendChild(touchFeedback);
buttonViewModel.appendChild(button);
return buttonViewModel;
}
const INNERTUBE_API_KEY_FALLBACK_B64 = "QUl6YVN5QU9fRkoyU2xxVThRNFNURUhMR0NpbHdfWTlfMTFxY1c4";
function getInnerTubeApiKey() {
try {
if (void 0 !== window.ytcfg && "function" == typeof window.ytcfg.get) {
const k = window.ytcfg.get("INNERTUBE_API_KEY");
if (k && "string" == typeof k) {
return k;
}
}
if (window.ytcfg?.data_?.INNERTUBE_API_KEY) {
return window.ytcfg.data_.INNERTUBE_API_KEY;
}
if (window.yt?.config_?.INNERTUBE_API_KEY) {
return window.yt.config_.INNERTUBE_API_KEY;
}
} catch (e) {}
try {
return window.atob(INNERTUBE_API_KEY_FALLBACK_B64);
} catch (e) {
return "";
}
}
const INNERTUBE_CLIENT_VERSION_FALLBACK = "2.20250312.01.00";
function getInnerTubeClientVersion() {
try {
if (void 0 !== window.ytcfg && "function" == typeof window.ytcfg.get) {
const version = window.ytcfg.get("INNERTUBE_CLIENT_VERSION");
if (version && "string" == typeof version) {
return version;
}
}
if (window.ytcfg?.data_?.INNERTUBE_CLIENT_VERSION) {
return window.ytcfg.data_.INNERTUBE_CLIENT_VERSION;
}
if (window.yt?.config_?.INNERTUBE_CLIENT_VERSION) {
return window.yt.config_.INNERTUBE_CLIENT_VERSION;
}
} catch (e) {}
return INNERTUBE_CLIENT_VERSION_FALLBACK;
}
function createInnerTubeRequestBody(videoId) {
return {
context: {
client: {
clientName: "WEB",
clientVersion: getInnerTubeClientVersion(),
hl: "en",
gl: "US"
}
},
videoId
};
}
function extractThumbnailUrl(details) {
const thumbnails = details.thumbnail?.thumbnails;
return thumbnails?.[thumbnails.length - 1]?.url || null;
}
async function fetchVideoStatsInnerTube(videoId) {
if (!videoId) {
return null;
}
try {
const url = `https://www.youtube.com/youtubei/v1/player?key=${getInnerTubeApiKey()}&prettyPrint=false`;
const response = await fetch(url, (function createInnerTubeFetchOptions(videoId) {
return {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-YouTube-Client-Name": "1",
"X-YouTube-Client-Version": getInnerTubeClientVersion()
},
body: JSON.stringify(createInnerTubeRequestBody(videoId))
};
})(videoId));
if (!response.ok) {
window.console.warn("[YouTube+][Stats] InnerTube API failed:", response.status);
return null;
}
const data = await response.json();
const stats = (function parseVideoStatsFromResponse(data) {
const details = data.videoDetails || {};
const microformat = data.microformat?.playerMicroformatRenderer || {};
const ownerProfileUrl = microformat.ownerProfileUrl || microformat.ownerUrls?.[0] || "";
const handleMatch = ownerProfileUrl.match(/\/@([\w.-]+)/);
const authorHandle = handleMatch ? `@${handleMatch[1]}` : null;
return {
videoId: details.videoId,
title: details.title,
author: details.author || null,
authorHandle,
views: details.viewCount ? parseInt(details.viewCount, 10) : null,
likes: null,
thumbnail: extractThumbnailUrl(details),
duration: details.lengthSeconds,
country: null,
monetized: void 0 !== microformat.isFamilySafe,
channelId: details.channelId
};
})(data);
stats.channelId && (stats.country = await (async function fetchChannelCountryFromInnerTube(channelId) {
if (!channelId) {
return null;
}
try {
const url = `https://www.youtube.com/youtubei/v1/browse?key=${getInnerTubeApiKey()}&prettyPrint=false`;
const body = {
browseId: channelId,
context: {
client: {
clientName: "WEB",
clientVersion: getInnerTubeClientVersion(),
hl: "en",
gl: "US"
}
}
};
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-YouTube-Client-Name": "1",
"X-YouTube-Client-Version": getInnerTubeClientVersion()
},
body: JSON.stringify(body)
});
if (!response.ok) {
return null;
}
const data = await response.json();
const country = data?.header?.c4TabbedHeaderRenderer?.country || data?.header?.pageHeaderRenderer?.content?.pageHeaderViewModel?.metadata?.contentMetadataViewModel?.metadataRows?.[0]?.metadataParts?.find?.(p => 2 === p?.text?.content?.length)?.text?.content || (() => {
const mutations = data?.frameworkUpdates?.entityBatchUpdate?.mutations || [];
for (const m of mutations) {
const c = m?.payload?.channelHeaderMetadataEntityViewModel?.country || m?.payload?.channelBasicInfoEntityViewModel?.country;
if (c) {
return c;
}
}
return null;
})();
return country || null;
} catch (e) {
return null;
}
})(stats.channelId));
return stats;
} catch (error) {
window.console.error("[YouTube+][Stats] InnerTube fetch error:", error);
return null;
}
}
async function fetchStats(type, id) {
if (!id) {
return {
ok: !1,
status: 0,
data: null
};
}
try {
if ("video" === type) {
const videoData = await fetchVideoStatsInnerTube(id);
if (!videoData) {
return {
ok: !1,
status: 404,
data: null
};
}
const dislikeData = await (async function fetchDislikesData(videoId) {
if (!videoId) {
return null;
}
await new Promise(resolve => {
"function" == typeof requestIdleCallback ? requestIdleCallback(resolve, {
timeout: 3e3
}) : setTimeout(resolve, 0);
});
try {
const response = await fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${videoId}`);
if (!response.ok) {
return null;
}
const data = await response.json();
return {
likes: data.likes || null,
dislikes: data.dislikes || null,
rating: data.rating || null
};
} catch (error) {
window.console.error("[YouTube+][Stats] Failed to fetch dislikes:", error);
return null;
}
})(id);
if (dislikeData) {
videoData.likes = dislikeData.likes;
videoData.dislikes = dislikeData.dislikes;
videoData.rating = dislikeData.rating;
}
return {
ok: !0,
status: 200,
data: videoData
};
}
const endpoint = `https://api.livecounts.io/youtube-live-subscriber-counter/stats/${id}`;
const response = await fetch(endpoint, {
method: "GET",
headers: {
Accept: "application/json"
}
});
if (!response.ok) {
window.console.warn(`[YouTube+][Stats] Failed to fetch ${type} stats:`, response.status);
return {
ok: !1,
status: response.status,
data: null,
url: endpoint
};
}
const data = await response.json();
return {
ok: !0,
status: response.status,
data,
url: endpoint
};
} catch (error) {
YouTubeUtils?.logError?.("Stats", `Failed to fetch ${type} stats`, error);
return {
ok: !1,
status: 0,
data: null
};
}
}
function getPageVideoStats() {
try {
const helpers = window.YouTubeStatsHelpers || {};
const fallbackHelpers = {
extractViews() {
try {
const el = $("yt-view-count-renderer, #count .view-count");
const text = el && el.textContent ? el.textContent.trim() : "";
const match = text.replace(/[^0-9,\.]/g, "").replace(/,/g, "");
return match ? {
views: Number(match) || null
} : {};
} catch (e) {
return {};
}
},
extractLikes() {
try {
const btn = $("ytd-toggle-button-renderer[is-icon-button] yt-formatted-string") || $("#top-level-buttons-computed ytd-toggle-button-renderer:first-child yt-formatted-string");
const text = btn && btn.textContent ? btn.textContent.trim() : "";
const match = text.replace(/[^0-9,\.]/g, "").replace(/,/g, "");
return match ? {
likes: Number(match) || null
} : {};
} catch (e) {
return {};
}
},
extractDislikes: () => ({}),
extractComments() {
try {
const el = $("#count > ytd-comment-thread-renderer, ytd-comments-header-renderer #count");
const text = el && el.textContent ? el.textContent.trim() : "";
const match = text.replace(/[^0-9,\.]/g, "").replace(/,/g, "");
return match ? {
comments: Number(match) || null
} : {};
} catch (e) {
return {};
}
},
extractSubscribers() {
try {
const el = $("#owner-sub-count, #subscriber-count");
const text = el && el.textContent ? el.textContent.trim() : "";
return text ? {
subscribers: text
} : {};
} catch (e) {
return {};
}
},
extractThumbnail() {
try {
const meta = $('link[rel="image_src"]') || $('meta[property="og:image"]');
const url = meta && (meta.href || meta.content) ? meta.href || meta.content : null;
return url ? {
thumbnail: url
} : {};
} catch (e) {
return {};
}
},
extractTitle() {
try {
const el = $("h1.title yt-formatted-string") || $("h1");
const text = el && el.textContent ? el.textContent.trim() : "";
return text ? {
title: text
} : {};
} catch (e) {
return {};
}
},
extractAuthor() {
try {
const handleEl = $("ytd-video-owner-renderer #channel-handle") || $("ytd-video-owner-renderer yt-formatted-string.ytd-channel-name a") || $("#owner ytd-channel-name a") || $("ytd-video-owner-renderer #owner-name a");
const handleText = handleEl?.textContent?.trim() || "";
const handle = handleText.startsWith("@") ? handleText : null;
const nameEl = $("ytd-video-owner-renderer #channel-name") || $("ytd-video-owner-renderer #owner-name");
const authorName = nameEl?.textContent?.trim() || null;
return handle || authorName ? {
authorHandle: handle,
author: authorName
} : {};
} catch (e) {
return {};
}
}
};
const use = helpers && helpers.extractViews ? helpers : fallbackHelpers;
const result = Object.assign({}, use.extractViews?.() || {}, use.extractLikes?.() || {}, use.extractDislikes?.() || {}, use.extractComments?.() || {}, use.extractSubscribers?.() || {}, use.extractThumbnail?.() || {}, use.extractTitle?.() || {}, use.extractAuthor?.() || {});
return Object.keys(result).length > 0 ? result : null;
} catch (e) {
YouTubeUtils?.logError?.("Stats", "Failed to read page stats", e);
return null;
}
}
function buildValueOnlyCard(value, iconOrClass = "", options = {
showValue: !0,
showIcon: !0
}) {
const {showValue, showIcon} = options;
if (!showValue && !showIcon) {
return "";
}
let displayVal = "";
showValue && (displayVal = value ?? t("unknown"));
let iconContent = "";
let extraClass = "";
showIcon && (iconOrClass && "string" == typeof iconOrClass && iconOrClass.indexOf("<") >= 0 ? iconContent = iconOrClass : iconOrClass && "string" == typeof iconOrClass && (extraClass = ` ${iconOrClass}`));
return `\n      <div class="stats-card">\n        <div class="stats-icon${extraClass}">${iconContent}</div>\n        <div class="stats-info">\n          ${showValue ? `<div class="stats-value">${displayVal}</div>` : ""}\n        </div>\n      </div>\n    `;
}
function buildStatCards(pageStats) {
const cardConfigs = [ {
value: pageStats.views,
key: "views",
icon: "stats-icon-views",
svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>'
}, {
value: pageStats.likes,
key: "likes",
icon: "stats-icon-likes",
svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg>'
}, {
value: pageStats.dislikes,
key: "dislikes",
icon: "stats-icon-dislikes",
svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path></svg>'
}, {
value: pageStats.comments,
key: "comments",
icon: "stats-icon-comments",
svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>'
} ];
return cardConfigs.map(config => (function buildPageStatCard(value, labelKey, iconClass, iconSvg) {
return null == value ? "" : `\n        <div class="stats-card">\n          <div class="stats-icon ${iconClass}">\n            ${iconSvg}\n          </div>\n          <div class="stats-info">\n            <div class="stats-label">${t(labelKey)}</div>\n            <div class="stats-value">${formatNumber(Number(value))}</div>\n            <div class="stats-exact">${(value || 0).toLocaleString()}</div>\n          </div>\n        </div>\n      `;
})(config.value, config.key, config.icon, config.svg)).filter(card => card);
}
function buildThumbnailLayout(titleHtml, thumbUrl, gridHtml, extras) {
const extraCards = (function buildExtraCards(extras) {
const monetizationText = extras.monetization || t("unknown");
const countryText = extras.country || t("unknown");
const durationText = extras.duration || t("unknown");
const extraMonCard = buildValueOnlyCard(monetizationText, "stats-icon-subscribers", {
showValue: !1,
showIcon: !0
});
const extraCountryCard = buildValueOnlyCard(countryText, "stats-icon-views", {
showValue: !1,
showIcon: !0
});
const extraDurationCard = buildValueOnlyCard(durationText, "stats-icon-videos", {
showValue: !0,
showIcon: !1
});
return `${extraMonCard}${extraCountryCard}${extraDurationCard}`;
})(extras);
const leftHtml = `<div class="stats-thumb-left"><img class="stats-thumb-img" src="${thumbUrl}" alt="thumbnail"><div class="stats-thumb-extras">${extraCards}</div></div>`;
return `${titleHtml}<div class="stats-thumb-row">${leftHtml}${gridHtml}</div>`;
}
function formatNumber(num) {
if (!num || isNaN(num)) {
return "0";
}
const absNum = Math.abs(num);
return absNum >= 1e9 ? `${(num / 1e9).toFixed(1)}B` : absNum >= 1e6 ? `${(num / 1e6).toFixed(1)}M` : absNum >= 1e3 ? `${(num / 1e3).toFixed(1)}K` : num.toLocaleString();
}
function makeStatsCard(labelKey, value, exact, iconClass, iconSvg) {
const display = null == value ? t("unknown") : formatNumber(value);
let exactText = "—";
if (null != exact) {
const numExact = Number(exact);
exactText = isNaN(numExact) ? String(exact) : Math.floor(numExact).toLocaleString();
}
return `\n        <div class="stats-card">\n          <div class="stats-icon ${iconClass}">\n            ${iconSvg}\n          </div>\n          <div class="stats-info">\n            <div class="stats-label">${t(labelKey)}</div>\n            <div class="stats-value">${display}</div>\n            <div class="stats-exact">${exactText}</div>\n          </div>\n        </div>\n      `;
}
function getFirstAvailableField(stats, ...fields) {
for (const field of fields) {
if (null != stats?.[field]) {
return stats[field];
}
}
return null;
}
function getThumbnailUrl(stats, id) {
const raw = stats?.thumbnail;
if (raw) {
try {
const parsed = new URL(raw);
const h = parsed.hostname;
if ("https:" === parsed.protocol && ("ytimg.com" === h || h.endsWith(".ytimg.com") || "ggpht.com" === h || h.endsWith(".ggpht.com") || "googleusercontent.com" === h || h.endsWith(".googleusercontent.com") || "youtube.com" === h || h.endsWith(".youtube.com"))) {
return raw;
}
} catch (e) {}
}
return id ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : "";
}
function getVideoExtras(apiStats, pageStats) {
const helpers = window.YouTubeStatsHelpers || {};
const duration = apiStats?.duration ?? pageStats?.duration ?? helpers.getDurationFromSources?.(apiStats, pageStats) ?? null;
const country = apiStats?.country ?? pageStats?.country ?? helpers.getCountryFromSources?.(apiStats, pageStats) ?? null;
let monetization = null;
monetization = null != apiStats?.monetized ? t(!0 === apiStats.monetized ? "yes" : "no") : null != pageStats?.monetized ? t(!0 === pageStats.monetized ? "yes" : "no") : helpers.getMonetizationFromSources?.(apiStats, pageStats, t) ?? null;
return {
duration,
country,
monetization
};
}
function createStatsModalStructure(overlay) {
const container = document.createElement("div");
container.className = "stats-modal-container";
container.setAttribute("role", "dialog");
container.setAttribute("aria-modal", "true");
container.setAttribute("aria-label", t("videoStats") || "Video Statistics");
const content = document.createElement("div");
content.className = "stats-modal-content ytp-plus-modal-content";
const body = document.createElement("div");
body.className = "stats-modal-body";
body.appendChild((function createLoadingSpinner() {
const loader = document.createElement("div");
loader.className = "stats-loader";
_setSafeHTML(loader, `\n      <svg class="stats-spinner" viewBox="0 0 50 50">\n        <circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="4"></circle>\n      </svg>\n      <p>${t("loadingStats")}</p>\n    `);
return loader;
})());
content.appendChild(body);
const wrapper = document.createElement("div");
wrapper.className = "thumbnail-modal-wrapper";
const actionsDiv = document.createElement("div");
actionsDiv.className = "thumbnail-modal-actions";
actionsDiv.appendChild((function createStatsModalCloseButton(overlay) {
const closeBtn = document.createElement("button");
closeBtn.className = "stats-modal-close thumbnail-modal-action-btn ytp-plus-settings-close";
closeBtn.setAttribute("data-shared-close-button", "ytp-plus-close-settings");
_setSafeHTML(closeBtn, '\n            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 9.50002L9.5 14.5M9.49998 9.5L14.5 14.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg>\n        ');
closeBtn.title = t("closeButton") || t("close");
closeBtn.setAttribute("aria-label", t("closeButton") || t("close"));
closeBtn.addEventListener("click", e => {
e.preventDefault();
e.stopPropagation();
overlay.remove();
});
return closeBtn;
})(overlay));
wrapper.appendChild(content);
wrapper.appendChild(actionsDiv);
container.appendChild(wrapper);
return {
body,
container
};
}
function handleFailedFetch(body, result, id) {
const pageStats = getPageVideoStats();
pageStats ? (function renderPageFallback(container, pageStats, id) {
const cards = buildStatCards(pageStats);
const gridHtml = `<div class="stats-grid">${cards.join("")}</div>`;
const title = pageStats && pageStats.title || document.title || "";
const safeTitle = escapeHtml(title);
const titleHtml = safeTitle ? `<div class="stats-thumb-title-centered">${safeTitle}</div>` : "";
const thumbUrl = getThumbnailUrl(pageStats, id);
const extras = getVideoExtras(null, pageStats);
_setSafeHTML(container, thumbUrl ? buildThumbnailLayout(titleHtml, thumbUrl, gridHtml, extras) : `${titleHtml}${gridHtml}`);
})(body, pageStats, id) : (function renderErrorMessage(body, result) {
const statusText = result?.status ? ` (${result.status})` : "";
const endpointHint = result?.url ? `<div style="margin-top:8px;font-size:12px;opacity:0.8;word-break:break-all">${result.url}</div>` : "";
_setSafeHTML(body, `\n        <div class="stats-error">\n          <svg class="stats-error-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n            <circle cx="12" cy="12" r="10"></circle>\n            <line x1="12" y1="8" x2="12" y2="12"></line>\n            <line x1="12" y1="16" x2="12.01" y2="16"></line>\n          </svg>\n          <p>${t("failedToLoadStats")}${statusText}</p>\n          ${endpointHint}\n        </div>\n      `);
})(body, result);
}
function displayStatsBasedOnType(body, type, stats, id) {
if ("video" === type) {
try {
const pageStats = getPageVideoStats();
const merged = (function mergeVideoStats(apiStats, pageStats) {
if (!pageStats) {
return apiStats || {};
}
const getValue = (...fields) => {
for (const field of fields) {
if (null != apiStats?.[field]) {
return apiStats[field];
}
}
for (const field of fields) {
if (null != pageStats?.[field]) {
return pageStats[field];
}
}
return null;
};
return {
...apiStats,
views: getValue("views", "viewCount"),
likes: getValue("likes", "likeCount"),
dislikes: getValue("dislikes"),
comments: getValue("comments", "commentCount"),
thumbnail: getValue("thumbnail"),
title: getValue("title"),
liveViewer: getValue("liveViewer"),
duration: getValue("duration"),
country: getValue("country"),
monetized: getValue("monetized", "isMonetized", "monetization"),
author: getValue("author"),
authorHandle: getValue("authorHandle")
};
})(stats, pageStats);
displayVideoStats(body, merged, id);
} catch (e) {
displayVideoStats(body, stats, id);
}
} else {
!(function displayChannelStats(container, stats) {
const {liveSubscriber, liveViews, liveVideos} = stats;
_setSafeHTML(container, `\n      <div class="stats-grid">\n        <div class="stats-card">\n          <div class="stats-icon stats-icon-subscribers">\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n              <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>\n              <circle cx="9" cy="7" r="4"></circle>\n              <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>\n              <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>\n            </svg>\n          </div>\n          <div class="stats-info">\n            <div class="stats-label">${t("subscribers")}</div>\n            <div class="stats-value">${formatNumber(liveSubscriber)}</div>\n            <div class="stats-exact">${(liveSubscriber || 0).toLocaleString()}</div>\n          </div>\n        </div>\n\n        <div class="stats-card">\n          <div class="stats-icon stats-icon-views">\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n              <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>\n              <circle cx="12" cy="12" r="3"></circle>\n            </svg>\n          </div>\n          <div class="stats-info">\n            <div class="stats-label">${t("totalViews")}</div>\n            <div class="stats-value">${formatNumber(liveViews)}</div>\n            <div class="stats-exact">${(liveViews || 0).toLocaleString()}</div>\n          </div>\n        </div>\n\n        <div class="stats-card">\n          <div class="stats-icon stats-icon-videos">\n            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n              <polygon points="23 7 16 12 23 17 23 7"></polygon>\n              <rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>\n            </svg>\n          </div>\n          <div class="stats-info">\n            <div class="stats-label">${t("totalVideos")}</div>\n            <div class="stats-value">${formatNumber(liveVideos)}</div>\n            <div class="stats-exact">${(liveVideos || 0).toLocaleString()}</div>\n          </div>\n        </div>\n      </div>\n      `);
})(body, stats);
}
}
async function openStatsModal(type, id) {
if (!type || !id) {
window.console.error("[YouTube+][Stats] Invalid parameters for modal");
return;
}
const existingOverlays = $$(".stats-modal-overlay");
for (let i = 0; i < existingOverlays.length; i++) {
try {
existingOverlays[i].remove();
} catch (e) {}
}
const overlay = document.createElement("div");
overlay.className = "stats-modal-overlay ytp-plus-modal-overlay";
const {body, container} = createStatsModalStructure(overlay);
overlay.appendChild(container);
!(function setupModalEventHandlers(overlay) {
const previouslyFocused = document.activeElement;
overlay.addEventListener("click", event => {
const {target} = event;
if (target === overlay) {
overlay.remove();
try {
previouslyFocused && previouslyFocused.focus();
} catch (e) {}
}
});
window.addEventListener("keydown", function escHandler(e) {
if ("Escape" === e.key) {
overlay.remove();
window.removeEventListener("keydown", escHandler, !0);
try {
previouslyFocused && previouslyFocused.focus();
} catch (e) {}
}
}, !0);
requestAnimationFrame(() => {
const focusTarget = overlay.querySelector('button, [tabindex="0"]');
focusTarget && focusTarget.focus();
});
if (window.YouTubePlusModalHandlers && window.YouTubePlusModalHandlers.createFocusTrap) {
const removeTrap = window.YouTubePlusModalHandlers.createFocusTrap(overlay);
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
const trapSubId = `stats::overlayTrap::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`;
coordinator.subscribeRoot(trapSubId, () => {
if (!overlay.isConnected) {
removeTrap();
coordinator.unsubscribe(trapSubId);
}
}, {
selector: null,
childList: !0,
attributes: !1,
subtree: !0
});
}
}
})(overlay);
document.body.appendChild(overlay);
const result = await fetchStats(type, id);
result?.ok ? displayStatsBasedOnType(body, type, result.data, id) : handleFailedFetch(body, result, id);
}
function createMonetizationCard(extras, stats) {
const monetizationValue = extras.monetization || t("unknown");
const isMonetized = extras.monetization === t("yes") || !0 === stats?.monetized;
const monIcon = isMonetized ? '<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path></svg>' : '<svg viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>';
return `<div class="stats-card" style="padding:10px;"><div class="stats-icon stats-icon-subscribers">${monIcon}</div><div class="stats-info"><div class="stats-label" style="font-size:12px;">${t("monetization")}</div><div class="stats-value" style="font-size:16px;">${monetizationValue}</div></div></div>`;
}
function createCountryCard(extras) {
const countryValue = escapeHtml(extras.country || t("unknown"));
const rawCode = extras.country && extras.country !== t("unknown") ? extras.country.toUpperCase() : "";
const countryCode = /^[A-Z]{2}$/.test(rawCode) ? rawCode : "";
if (countryCode) {
const flagUrl = `https://cdn.jsdelivr.net/gh/lipis/[email protected]/flags/4x3/${countryCode.toLowerCase()}.svg`;
return `<div class="stats-card" style="padding:10px;"><div class="stats-icon stats-icon-views" data-fallback-icon="globe"><img class="country-flag" src="${flagUrl}" alt="${countryCode}" width="32" height="24" style="border-radius:4px;"/></div><div class="stats-info"><div class="stats-label" style="font-size:12px;">${t("country")}</div><div class="stats-value" style="font-size:16px;">${countryCode}</div></div></div>`;
}
return `<div class="stats-card" style="padding:10px;"><div class="stats-icon stats-icon-views"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg></div><div class="stats-info"><div class="stats-label" style="font-size:12px;">${t("country")}</div><div class="stats-value" style="font-size:16px;">${countryValue}</div></div></div>`;
}
function createDurationCard(extras) {
const raw = extras?.duration ?? null;
const formatted = (function formatDuration(value) {
if (null == value) {
return null;
}
function pad(n) {
return String(n).padStart(2, "0");
}
function secToHms(sec) {
sec = Math.max(0, Math.floor(Number(sec) || 0));
const h = Math.floor(sec / 3600);
const m = Math.floor(sec % 3600 / 60);
const s = sec % 60;
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
}
if ("number" == typeof value && Number.isFinite(value)) {
return secToHms(value);
}
if ("string" == typeof value) {
const s = value.trim();
if (/^\d+$/.test(s)) {
return secToHms(Number(s));
}
if (s.length > 2 && "P" === s[0].toUpperCase() && "T" === s[1].toUpperCase()) {
let h = 0;
let m = 0;
let sec = 0;
let current = "";
let valid = !0;
for (let i = 2; i < s.length; i += 1) {
const ch = s[i];
if (ch >= "0" && ch <= "9") {
current += ch;
continue;
}
if (!current) {
valid = !1;
break;
}
const n = parseInt(current, 10);
current = "";
if ("H" === ch || "h" === ch) {
h = n;
} else if ("M" === ch || "m" === ch) {
m = n;
} else {
if ("S" !== ch && "s" !== ch) {
valid = !1;
break;
}
sec = n;
}
}
if (valid && "" === current) {
return secToHms(3600 * h + 60 * m + sec);
}
}
const colonParts = s.split(":");
if (2 === colonParts.length || 3 === colonParts.length) {
const allDigits = colonParts.every(part => part.length > 0 && /^\d+$/.test(part));
if (allDigits && colonParts.slice(1).every(part => part.length <= 2)) {
const parts = colonParts.map(p => {
const trimmed = p.replace(/^0+/, "");
return "" === trimmed ? "0" : trimmed;
});
if (2 === parts.length) {
const [mm, ss] = parts;
return `${Number(mm)}:${pad(Number(ss))}`;
}
if (3 === parts.length) {
const [hh, mm, ss] = parts;
return `${Number(hh)}:${pad(Number(mm))}:${pad(Number(ss))}`;
}
}
}
return s || null;
}
return null;
})(raw);
const durationValue = formatted || (raw ? String(raw) : null) || t("unknown");
return `<div class="stats-card" style="padding:10px;"><div class="stats-icon stats-icon-videos"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg></div><div class="stats-info"><div class="stats-label" style="font-size:12px;">${t("duration")}</div><div class="stats-value" style="font-size:16px;">${durationValue}</div></div></div>`;
}
function displayVideoStats(container, stats, id) {
const fields = (function extractVideoFields(stats, id) {
return {
views: getFirstAvailableField(stats, "liveViews", "views", "viewCount"),
likes: getFirstAvailableField(stats, "liveLikes", "likes", "likeCount"),
dislikes: getFirstAvailableField(stats, "dislikes", "liveDislikes", "dislikeCount"),
comments: getFirstAvailableField(stats, "liveComments", "comments", "commentCount"),
liveViewer: getFirstAvailableField(stats, "liveViewer", "live_viewers"),
title: stats?.title || document.title || "",
thumbUrl: getThumbnailUrl(stats, id),
country: getFirstAvailableField(stats, "country"),
monetized: stats?.monetized ?? null,
duration: getFirstAvailableField(stats, "duration"),
author: getFirstAvailableField(stats, "author"),
authorHandle: getFirstAvailableField(stats, "authorHandle")
};
})(stats, id);
const {liveViewer, title, thumbUrl} = fields;
const safeTitle = escapeHtml(title);
const titleHtml = safeTitle ? `<div class="stats-thumb-title-centered">${safeTitle}</div>` : "";
const defs = (function getVideoStatDefinitions(fields) {
const {views, likes, dislikes, comments} = fields;
return [ {
label: "views",
value: views,
exact: views,
iconClass: "stats-icon-views",
iconSvg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>'
}, {
label: "likes",
value: likes,
exact: likes,
iconClass: "stats-icon-likes",
iconSvg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg>'
}, {
label: "dislikes",
value: dislikes,
exact: dislikes,
iconClass: "stats-icon-dislikes",
iconSvg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path></svg>'
}, {
label: "comments",
value: comments,
exact: comments,
iconClass: "stats-icon-comments",
iconSvg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>'
} ];
})(fields);
const viewsDef = defs.find(d => "views" === d.label);
const likesDef = defs.find(d => "likes" === d.label);
const dislikesDef = defs.find(d => "dislikes" === d.label);
const commentsDef = defs.find(d => "comments" === d.label);
const viewsHtml = viewsDef ? makeStatsCard(viewsDef.label, viewsDef.value, viewsDef.exact, viewsDef.iconClass, viewsDef.iconSvg) : "";
const likesHtml = likesDef ? makeStatsCard(likesDef.label, likesDef.value, likesDef.exact, likesDef.iconClass, likesDef.iconSvg) : "";
const dislikesHtml = dislikesDef ? makeStatsCard(dislikesDef.label, dislikesDef.value, dislikesDef.exact, dislikesDef.iconClass, dislikesDef.iconSvg) : "";
const commentsHtml = commentsDef ? makeStatsCard(commentsDef.label, commentsDef.value, commentsDef.exact, commentsDef.iconClass, commentsDef.iconSvg) : "";
const pairHtml = likesHtml || dislikesHtml ? `<div class="stats-card-pair">${likesHtml}${dislikesHtml}</div>` : "";
const {author, authorHandle} = fields;
const safeAuthor = author ? escapeHtml(String(author)) : "";
const safeHandle = authorHandle ? escapeHtml(String(authorHandle)) : "";
const authorBigHtml = safeHandle || safeAuthor ? `<div class="stats-author-big">${safeHandle ? `<a class="stats-author-handle-big" href="https://www.youtube.com/${encodeURIComponent(authorHandle)}" target="_blank" rel="noopener noreferrer">${safeHandle}</a>` : `<span class="stats-author-name-big">${safeAuthor}</span>`}</div>` : "";
const parts = [ viewsHtml, pairHtml, commentsHtml ].filter(Boolean);
const liveViewerCard = (function createLiveViewerCard(liveViewer) {
return null == liveViewer ? "" : makeStatsCard("liveViewers", liveViewer, liveViewer, "stats-icon-viewers", '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>');
})(liveViewer);
liveViewerCard && parts.push(liveViewerCard);
const gridHtml = `<div class="stats-grid">${parts.join("")}</div>`;
const sideColumnHtml = `<div class="stats-side-column">${gridHtml}${authorBigHtml}</div>`;
if (thumbUrl) {
const extras = getVideoExtras(stats, null);
const metaCardsHtml = (function buildMetaCardsHtml(stats, extras) {
const cards = [ createMonetizationCard(extras, stats), createCountryCard(extras), createDurationCard(extras) ];
return cards.filter(Boolean).join("");
})(stats, extras);
const metaExtrasHtml = metaCardsHtml ? `<div class="stats-thumb-extras" style="display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;">${metaCardsHtml}</div>` : "";
const leftHtml = `<div class="stats-thumb-left"><img class="stats-thumb-img" src="${thumbUrl}" alt="thumbnail">${metaExtrasHtml}</div>`;
_setSafeHTML(container, `${titleHtml}<div class="stats-thumb-row">${leftHtml}${sideColumnHtml}</div>`);
} else {
_setSafeHTML(container, `${titleHtml}${sideColumnHtml}`);
}
!(function setupFlagImageErrorHandlers(container) {
const flagImages = $$(".country-flag", container);
const globeIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>';
flagImages.forEach(img => {
img.addEventListener("error", function() {
const iconContainer = this.parentElement;
if (iconContainer && "globe" === iconContainer.dataset.fallbackIcon) {
this.style.display = "none";
_setSafeHTML(iconContainer, globeIcon);
}
}, {
once: !0
});
});
})(container);
}
function createStatsMenu() {
if (!statsButtonEnabled) {
return;
}
if ($(".stats-menu-container")) {
return;
}
const containerDiv = document.createElement("div");
containerDiv.className = "yt-flexible-actions-view-model-wiz__action stats-menu-container";
const mainButtonViewModel = document.createElement("button-view-model");
mainButtonViewModel.className = "yt-spec-button-view-model main-stats-view-model";
const mainButton = document.createElement("button");
mainButton.className = "yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment main-stats-button";
mainButton.setAttribute("aria-disabled", "false");
mainButton.setAttribute("aria-label", t("stats"));
if (mainButton.style) {
mainButton.style.display = "flex";
mainButton.style.alignItems = "center";
mainButton.style.justifyContent = "center";
mainButton.style.gap = "8px";
}
const iconWrap = document.createElement("span");
_setSafeHTML(iconWrap, '<svg viewBox="0 0 512 512" style="width:20px;height:20px;fill:currentColor"><path d="M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z"/></svg>');
const svg = iconWrap.firstElementChild;
const buttonText = document.createElement("div");
buttonText.className = "yt-spec-button-shape-next__button-text-content main-stats-text";
buttonText.textContent = t("stats");
if (buttonText.style) {
buttonText.style.display = "flex";
buttonText.style.alignItems = "center";
}
const touchFeedback = document.createElement("yt-touch-feedback-shape");
touchFeedback.style && (touchFeedback.style.borderRadius = "inherit");
const touchFeedbackDiv = document.createElement("div");
touchFeedbackDiv.className = "yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response";
touchFeedbackDiv.setAttribute("aria-hidden", "true");
const strokeDiv = document.createElement("div");
strokeDiv.className = "yt-spec-touch-feedback-shape__stroke";
const fillDiv = document.createElement("div");
fillDiv.className = "yt-spec-touch-feedback-shape__fill";
touchFeedbackDiv.appendChild(strokeDiv);
touchFeedbackDiv.appendChild(fillDiv);
touchFeedback.appendChild(touchFeedbackDiv);
svg && mainButton.appendChild(svg);
mainButton.appendChild(buttonText);
mainButton.appendChild(touchFeedback);
mainButtonViewModel.appendChild(mainButton);
containerDiv.appendChild(mainButtonViewModel);
const horizontalMenu = document.createElement("div");
horizontalMenu.className = "stats-horizontal-menu";
const channelButtonContainer = document.createElement("div");
channelButtonContainer.className = "stats-menu-button channel-stats-container";
const channelButton = createButton(t("channel"), "M64 48c-8.8 0-16 7.2-16 16l0 288c0 8.8 7.2 16 16 16l512 0c8.8 0 16-7.2 16-16l0-288c0-8.8-7.2-16-16-16L64 48zM0 64C0 28.7 28.7 0 64 0L576 0c35.3 0 64 28.7 64 64l0 288c0 35.3-28.7 64-64 64L64 416c-35.3 0-64-28.7-64-64L0 64zM120 464l400 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-400 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z", "0 0 640 512", "channel-stats", () => {
const channelId = getChannelIdentifier();
channelId && openStatsModal("channel", channelId);
});
channelButtonContainer.appendChild(channelButton);
horizontalMenu.appendChild(channelButtonContainer);
if (channelFeatures_hasStreams) {
const liveButtonContainer = document.createElement("div");
liveButtonContainer.className = "stats-menu-button live-stats-container";
const liveButton = createButton(t("live"), "M99.8 69.4c10.2 8.4 11.6 23.6 3.2 33.8C68.6 144.7 48 197.9 48 256s20.6 111.3 55 152.8c8.4 10.2 7 25.3-3.2 33.8s-25.3 7-33.8-3.2C24.8 389.6 0 325.7 0 256S24.8 122.4 66 72.6c8.4-10.2 23.6-11.6 33.8-3.2zm376.5 0c10.2-8.4 25.3-7 33.8 3.2c41.2 49.8 66 113.8 66 183.4s-24.8 133.6-66 183.4c-8.4 10.2-23.6 11.6-33.8 3.2s-11.6-23.6-3.2-33.8c34.3-41.5 55-94.7 55-152.8s-20.6-111.3-55-152.8c-8.4-10.2-7-25.3 3.2-33.8zM248 256a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zm-61.1-78.5C170 199.2 160 226.4 160 256s10 56.8 26.9 78.5c8.1 10.5 6.3 25.5-4.2 33.7s-25.5 6.3-33.7-4.2c-23.2-29.8-37-67.3-37-108s13.8-78.2 37-108c8.1-10.5 23.2-12.3 33.7-4.2s12.3 23.2 4.2 33.7zM427 148c23.2 29.8 37 67.3 37 108s-13.8 78.2-37 108c-8.1 10.5-23.2 12.3-33.7 4.2s-12.3-23.2-4.2-33.7C406 312.8 416 285.6 416 256s-10-56.8-26.9-78.5c-8.1-10.5-6.3-25.5 4.2-33.7s25.5-6.3 33.7 4.2z", "0 0 576 512", "live-stats", () => {
const channelId = getChannelIdentifier();
channelId && openStatsModal("channel", channelId);
});
liveButtonContainer.appendChild(liveButton);
horizontalMenu.appendChild(liveButtonContainer);
}
if (channelFeatures_hasShorts) {
const shortsButtonContainer = document.createElement("div");
shortsButtonContainer.className = "stats-menu-button shorts-stats-container";
const shortsButton = createButton(t("shorts"), "M80 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16l224 0c8.8 0 16-7.2 16-16l0-384c0-8.8-7.2-16-16-16L80 48zM16 64C16 28.7 44.7 0 80 0L304 0c35.3 0 64 28.7 64 64l0 384c0 35.3-28.7 64-64 64L80 512c-35.3 0-64-28.7-64-64L16 64zM160 400l64 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-64 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z", "0 0 384 512", "shorts-stats", () => {
const channelId = getChannelIdentifier();
channelId && openStatsModal("channel", channelId);
});
shortsButtonContainer.appendChild(shortsButton);
horizontalMenu.appendChild(shortsButtonContainer);
}
containerDiv.appendChild(horizontalMenu);
const actionContainer = (() => {
const modernContainer = $("#owner #top-row #buttons, #top-row #owner #buttons, #buttons.ytd-video-owner-renderer");
if (modernContainer) {
return modernContainer;
}
const joinButton = $(".yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)");
return joinButton?.parentElement ? joinButton.parentElement : $("#subscribe-button + #buttons");
})();
actionContainer && actionContainer.appendChild(containerDiv);
return containerDiv;
}
function checkAndAddMenu() {
if (!statsButtonEnabled) {
return;
}
const actionContainer = $("#owner #top-row #buttons, #top-row #owner #buttons, #buttons.ytd-video-owner-renderer, #subscribe-button + #buttons, .yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)");
const statsMenu = $(".stats-menu-container");
actionContainer && !statsMenu && createStatsMenu();
}
function checkAndInsertIcon() {
statsButtonEnabled && insertUniversalIcon();
}
let ensureSettingsScheduler = null;
function ensureSettingsUI() {
ensureSettingsScheduler && ensureSettingsScheduler.stop();
ensureSettingsScheduler = (function createSafeRetryScheduler(opts) {
const factory = window.YouTubeUtils?.createRetryScheduler;
if ("function" == typeof factory) {
try {
return factory(opts);
} catch (error) {
YouTubeUtils?.logError?.("Stats", "Retry scheduler factory failed", error);
}
}
const {check, maxAttempts = 20, interval = 100} = opts || {};
let attempts = 0;
let timerId = null;
let stopped = !1;
const tick = () => {
if (!stopped) {
attempts += 1;
try {
if ("function" == typeof check && check()) {
stopped = !0;
return;
}
} catch (error) {
YouTubeUtils?.logError?.("Stats", "Fallback retry check failed", error);
}
attempts >= maxAttempts ? stopped = !0 : timerId = setTimeout(tick, interval);
}
};
timerId = setTimeout(tick, 0);
return {
stop() {
stopped = !0;
timerId && clearTimeout(timerId);
timerId = null;
}
};
})({
check: () => (function addSettingsUI() {
const section = $$('.ytp-plus-settings-section[data-section="experimental"]').find(candidate => candidate.isConnected);
if (!section) {
return !1;
}
const duplicateItems = Array.from(section.querySelectorAll(".stats-button-settings-item"));
duplicateItems.length > 1 && duplicateItems.slice(1).forEach(node => node.remove());
const existingItem = duplicateItems[0] || $(".stats-button-settings-item", section);
if (existingItem) {
const label = existingItem.querySelector(".ytp-plus-settings-item-label");
const description = existingItem.querySelector(".ytp-plus-settings-item-description");
label && (label.textContent = t("statisticsButton"));
description && (description.textContent = t("statisticsButtonDescription"));
return !0;
}
const item = document.createElement("div");
item.className = "ytp-plus-settings-item stats-button-settings-item";
_setSafeHTML(item, `\n        <div>\n          <label class="ytp-plus-settings-item-label">${t("statisticsButton")}</label>\n          <div class="ytp-plus-settings-item-description">${t("statisticsButtonDescription")}</div>\n        </div>\n        <input type="checkbox" class="ytp-plus-settings-checkbox" ${statsButtonEnabled ? "checked" : ""}>\n      `);
section.appendChild(item);
item.querySelector("input")?.addEventListener("change", e => {
const {target} = e;
const input = target;
statsButtonEnabled = input.checked;
_safeLS_setItem("youtube_stats_button_enabled", statsButtonEnabled ? "true" : "false");
$$(`${STATS_ICON_SELECTOR}, .stats-menu-container`).forEach(el => el.remove());
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
});
return !0;
})(),
maxAttempts: 50,
interval: 100
});
}
YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", () => {
ensureSettingsUI();
}) : document.addEventListener("youtube-plus-settings-modal-opened", () => {
ensureSettingsUI();
});
const handleExperimentalNavClick = e => {
const {target} = e;
const el = target;
const navItem = el?.closest?.(".ytp-plus-settings-nav-item");
"experimental" === navItem?.dataset?.section && ensureSettingsUI();
};
YouTubeUtils?.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-language-changed", () => {
ensureSettingsUI();
}) : document.addEventListener("youtube-plus-language-changed", () => {
ensureSettingsUI();
});
if (!experimentalNavListenerKey) {
if (YouTubeUtils?.cleanupManager?.registerListener) {
experimentalNavListenerKey = YouTubeUtils.cleanupManager.registerListener(document, "click", handleExperimentalNavClick, !0);
} else {
document.addEventListener("click", handleExperimentalNavClick, !0);
experimentalNavListenerKey = "native-click-listener";
}
}
function init() {
!(function addStyles() {
byId("youtube-enhancer-styles") || YouTubeUtils.StyleManager.add("youtube-enhancer-styles", "\n      .videoStats{width:36px;height:36px;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;margin-left:8px;margin-right:8px;background:none;;border:none;transition:transform .18s ease,background .18s}\n      html[dark] .videoStats{background:none;border:none}html:not([dark]) .videoStats{background:none;border:none}.videoStats:hover{transform:translateY(-2px)}.videoStats svg{width:18px;height:18px;fill:var(--yt-spec-text-primary,#030303)}html[dark] .videoStats svg{fill:var(--yt-text-primary)}html:not([dark]) .videoStats svg{fill:var(--yt-text-primary)}\n      .shortsStats{display:flex;align-items:center;justify-content:center;margin-top:16px;margin-bottom:16px;width:48px;height:48px;border-radius:50%;cursor:pointer;background:var(--yt-stats-button-bg-light);box-shadow:0 12px 30px var(--yt-stats-shadow-deep);backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);border:1.25px solid var(--yt-stats-button-bg-light);transition:transform .22s ease}html[dark] .shortsStats{background:var(--yt-stats-button-bg-dark);border:1.25px solid var(--yt-stats-button-border-dark)}html:not([dark]) .shortsStats{background:var(--yt-stats-button-bg-light);border:1.25px solid var(--yt-stats-button-border-light)}\n      .shortsStats:hover{transform:translateY(-3px)}.shortsStats svg{width:24px;height:24px;fill:var(--yt-text-primary)}html[dark] .shortsStats svg{fill:var(--yt-text-primary)}html:not([dark]) .shortsStats svg{fill:var(--yt-text-primary)}\n        .stats-menu-container{position:relative;display:inline-block}.stats-horizontal-menu{position:absolute;display:flex;left:100%;top:0;height:100%;visibility:hidden;opacity:0;transition:visibility 0s,opacity 0.2s linear;z-index:100}.stats-menu-container:hover .stats-horizontal-menu{visibility:visible;opacity:1}.stats-menu-button{margin-left:8px;white-space:nowrap}\n        /* Modal overlay and container with glassmorphism */\n        .stats-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--yt-modal-bg);z-index:var(--yt-z-modal);display:flex;align-items:center;justify-content:center;animation:fadeInModal .18s;backdrop-filter:var(--yt-glass-blur);-webkit-backdrop-filter:var(--yt-glass-blur)}\n        .stats-modal-container{max-width:1100px;max-height:calc(100vh - 32px);display:flex;flex-direction:column}\n        .stats-modal-content{position:relative;background:var(--yt-glass-bg);border-radius:var(--yt-radius-lg);box-shadow:0 18px 40px var(--yt-stats-modal-shadow);overflow:visible;display:flex;flex-direction:column;animation:scaleInModal .18s;border:1.5px solid var(--yt-glass-border);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%)}\n        /* Fix custom element display for Chrome */\n        button-view-model{display:inline-flex;align-items:center;justify-content:center;}\n        button-view-model.yt-spec-button-view-model{vertical-align:top;}\n        /* Modal body */\n        .stats-modal-body{position:relative;padding:24px 16px 16px;overflow:visible;flex:1;display:flex;flex-direction:column}\n        /* Thumbnail preview */\n        .stats-thumb-title-centered{position:absolute;top:-44px;left:50%;transform:translateX(-50%);z-index:3;display:block;width:fit-content;max-width:min(90%,760px);margin:0;padding:8px 16px;border-radius:18px;border:1px solid var(--yt-glass-border);background:var(--yt-glass-bg);font-size:14px;font-weight:500;color:var(--yt-text-primary);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:default;transition:all .25s cubic-bezier(.4,0,.2,1)}\n        .stats-thumb-row{display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap}\n        .stats-thumb-img{width:36vw;max-width:420px;height:auto;object-fit:cover;border-radius:8px;flex-shrink:0;border:1px solid var(--yt-stats-img-border-dark);max-height:44vh}\n        html:not([dark]) .stats-thumb-img{border:1px solid var(--yt-stats-img-border-light)}\n        /* ensure the grid takes remaining horizontal space */\n        .stats-thumb-row .stats-grid{flex:1;min-width:0}\n        .stats-side-column{flex:1;min-width:280px;display:flex;flex-direction:column}\n        .stats-thumb-left{display:flex;flex-direction:column;align-items:center;gap:8px}\n        .stats-thumb-left .stats-thumb-sub{font-size:13px;color:var(--yt-stats-text-secondary-dark)}\n        html:not([dark]) .stats-thumb-left .stats-thumb-sub{color:var(--yt-stats-text-secondary-light)}\n        /* extras row under thumbnail: inline, single line */\n        .stats-thumb-extras{display:flex;flex-direction:row;gap:10px;align-items:center;margin-top:8px}\n        .stats-thumb-extras .stats-card{padding:8px 10px}\n        .stats-thumb-meta{display:flex;flex-direction:column;justify-content:center}\n        .stats-thumb-sub{font-size:13px;color:var(--yt-stats-text-secondary-dark)}\n        html:not([dark]) .stats-thumb-sub{color:var(--yt-stats-text-secondary-light)}\n        /* Loading state */\n        .stats-loader{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;color:var(--yt-stats-loader-text-dark)}\n        html:not([dark]) .stats-loader{color:var(--yt-stats-loader-text-light)}\n        .stats-spinner{width:60px;height:60px;animation:spin 1s linear infinite;margin-bottom:16px}\n        .stats-spinner circle{stroke-dasharray:80;stroke-dashoffset:60;animation:dash 1.5s ease-in-out infinite}            \n        /* Error state */\n        .stats-error{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;color:var(--yt-stats-error);text-align:center}\n        .stats-error-icon{width:60px;height:60px;margin-bottom:16px;stroke:var(--yt-stats-error)}\n        /* Stats grid */\n        .stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px}            \n        /* Stats card */\n        .stats-card{background:var(--yt-stats-card-bg-dark);border-radius:12px;padding:12px;display:flex;align-items:center;gap:12px;border:1px solid var(--yt-stats-card-border-dark);transition:transform .18s ease,box-shadow .18s ease}\n        html:not([dark]) .stats-card{background:var(--yt-stats-card-bg-light);border:1px solid var(--yt-stats-card-border-light)}\n        .stats-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px var(--yt-stats-shadow-hover)}\n        /* Stats icon */\n        .stats-icon{width:48px;height:48px;border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n        .stats-icon svg{width:24px;height:24px}\n        .stats-icon-views{background:var(--yt-stats-icon-views-bg);color:var(--yt-stats-icon-views)}\n        .stats-icon-likes{background:var(--yt-stats-icon-likes-bg);color:var(--yt-stats-icon-likes)}\n        .stats-icon-dislikes{background:var(--yt-stats-icon-dislikes-bg);color:var(--yt-stats-icon-dislikes)}\n        .stats-icon-comments{background:var(--yt-stats-icon-comments-bg);color:var(--yt-stats-icon-comments)}\n        .stats-icon-viewers{background:var(--yt-stats-icon-viewers-bg);color:var(--yt-stats-icon-viewers)}\n        .stats-icon-subscribers{background:var(--yt-stats-icon-subscribers-bg);color:var(--yt-stats-icon-subscribers)}\n        .stats-icon-videos{background:var(--yt-stats-icon-videos-bg);color:var(--yt-stats-icon-videos)}\n        /* Pair likes/dislikes into a single grid cell */\n        .stats-card-pair{display:flex;gap:8px;align-items:stretch}\n        .stats-card-pair .stats-card{flex:1;margin:0}\n        @media(max-width:480px){.stats-card-pair{flex-direction:column}}            \n        /* Stats info */\n        .stats-info{flex:1;min-width:0}\n        .stats-label{font-size:13px;color:var(--yt-stats-text-label);margin-bottom:4px;font-weight:500}\n        html:not([dark]) .stats-label{color:var(--yt-stats-text-secondary-light)}\n        .stats-value{font-size:20px;font-weight:700;color:var(--yt-stats-text-value-dark);line-height:1.2;margin-bottom:2px}\n        html:not([dark]) .stats-value{color:var(--yt-stats-text-value-light)}\n        .stats-exact{font-size:13px;color:var(--yt-stats-text-exact-dark);font-weight:400}\n        html:not([dark]) .stats-exact{color:var(--yt-stats-text-exact-light)}\n        /* Animations — shared keyframes (fadeInModal, scaleInModal, spin, dash) defined in basic.js */\n        /* Responsive */\n        @media(max-width:768px){.stats-modal-container{width:95vw}.stats-grid{grid-template-columns:1fr}.stats-card{padding:16px}.stats-side-column{min-width:0;width:100%}}\n        /* Centered large author handle (preferred) */\n        .stats-author-big{display:block;text-align:center;margin-top:13px;padding-inline:8px}\n        .stats-author-name-big{display:block;color:var(--yt-stats-author-name-bright);font-weight:600;font-size:16px}\n        .stats-author-handle-big{display:inline-block;color:var(--yt-glass-border);font-weight:700;font-size:20px;text-decoration:none;padding:6px 10px;border-radius:6px}\n        .stats-author-handle-big:hover{color:var(--yt-stats-link-hover);text-decoration:underline}\n        html:not([dark]) .stats-author-name-big{color:var(--yt-stats-author-name-light)}\n        html:not([dark]) .stats-author-handle-big{color:var(--yt-stats-link-color)}\n        html:not([dark]) .stats-author-handle-big:hover{color:var(--yt-stats-link-hover-dark)}\n        ");
})();
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
if (YouTubeUtils?.cleanupManager?.registerListener) {
YouTubeUtils.cleanupManager.registerListener(window, "ytp-history-navigate", checkUrlChange);
YouTubeUtils.cleanupManager.registerListener(window, "popstate", checkUrlChange);
} else {
window.addEventListener("ytp-history-navigate", checkUrlChange);
window.addEventListener("popstate", checkUrlChange);
}
window.YouTubeUtils?.isChannelPage?.(location.href) && checkChannelTabs(location.href);
}
const scheduleInit = () => {
if (statsInitialized || !(() => {
try {
const path = location.pathname || "";
return !("/watch" !== path && !path.startsWith("/shorts")) || (window.YouTubeUtils?.isChannelPage?.(location.href) ?? !1);
} catch (e) {
return !1;
}
})()) {
return;
}
statsInitialized = !0;
const run = () => {
try {
init();
} catch (e) {
statsInitialized = !1;
throw e;
}
};
"function" == typeof requestIdleCallback ? requestIdleCallback(run, {
timeout: 2e3
}) : setTimeout(run, 0);
};
(cb => {
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", cb, {
once: !0
}) : cb();
})(scheduleInit);
const handleNavigate = () => {
scheduleInit();
if (statsInitialized && statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
window.YouTubeUtils?.isChannelPage?.(location.href) && checkChannelTabs(location.href);
}
};
const _cleanupManager = window.YouTubeUtils?.cleanupManager;
_cleanupManager ? _cleanupManager.registerListener(document, "yt-navigate-finish", handleNavigate, {
passive: !0
}) : window.addEventListener("yt-navigate-finish", handleNavigate);
const _navRefreshHandler = () => {
try {
if (statsInitialized && statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
} catch (e) {}
};
_cleanupManager ? _cleanupManager.registerListener(window, "ytp:nav-refresh", _navRefreshHandler, {
passive: !0
}) : window.addEventListener("ytp:nav-refresh", _navRefreshHandler);
const handleAction = event => {
scheduleInit();
if (!statsInitialized || !statsButtonEnabled) {
return;
}
const ev = event;
if (ev.detail && "yt-reload-continuation-items-command" === ev.detail.actionName) {
checkAndInsertIcon();
checkAndAddMenu();
}
};
_cleanupManager ? _cleanupManager.registerListener(document, "yt-action", handleAction, {
passive: !0
}) : document.addEventListener("yt-action", handleAction);
};
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("video-stats", initVideoStats, {
priority: 2,
shouldLoad: () => (() => {
try {
const path = location.pathname || "";
return "/watch" === path || path.startsWith("/shorts") || path.startsWith("/@") || path.startsWith("/channel/") || path.startsWith("/c/");
} catch (e) {
return !1;
}
})() || (() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()
}) : initVideoStats();
})();

!(function() {
"use strict";
if (window.__ytpChannelStatsModuleInit) {
return;
}
const initChannelStats = () => {
if (window.__ytpChannelStatsModuleInit) {
return;
}
window.__ytpChannelStatsModuleInit = !0;
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const createVisibilityAwareInterval = window.YouTubeUtils?.createVisibilityAwareInterval || ((callback, delay) => {
const id = setInterval(() => {
document.hidden || callback();
}, delay);
return {
stop() {
clearInterval(id);
},
pause() {
clearInterval(id);
},
resume() {},
get active() {
return !0;
}
};
});
const setTimeout_ = setTimeout.bind(window);
const $ = window.YouTubeUtils.$;
const $$ = window.YouTubeUtils.$$;
const byId = window.YouTubeUtils.byId;
if (window.YouTubeUtils?.isStudioPage?.()) {
return;
}
const t = window.YouTubeUtils.t;
const _safeLS_getItem = (k, def = null) => {
try {
return localStorage.getItem(k) ?? def;
} catch (e) {
return def;
}
}, _safeLS_setItem = (k, v) => {
try {
localStorage.setItem(k, v);
return !0;
} catch (e) {
return !1;
}
};
const CONFIG = {
OPTIONS: [ "subscribers", "views", "videos" ],
FONT_LINK: "https://fonts.googleapis.com/css2?family=Rubik:wght@400;700&display=swap",
STATS_API_URL: "https://api.livecounts.io/youtube-live-subscriber-counter/stats/",
DEFAULT_UPDATE_INTERVAL: 5e3,
DEFAULT_OVERLAY_OPACITY: .75,
MAX_RETRIES: 3,
CACHE_DURATION: 3e5,
DEBOUNCE_DELAY: 100,
STORAGE_KEY: "youtube_channel_stats_settings"
};
const state = {
overlay: null,
isUpdating: !1,
intervalId: null,
currentChannelName: null,
currentChannelId: null,
enabled: "false" !== _safeLS_getItem(CONFIG.STORAGE_KEY),
updateInterval: parseInt(_safeLS_getItem("youtubeEnhancerInterval") || "", 10) || CONFIG.DEFAULT_UPDATE_INTERVAL,
overlayOpacity: parseFloat(_safeLS_getItem("youtubeEnhancerOpacity") || "") || CONFIG.DEFAULT_OVERLAY_OPACITY,
lastSuccessfulStats: new Map,
previousStats: new Map,
channelIdCache: new Map,
lastChannelIdWarnAt: 0,
previousUrl: location.href,
isChecking: !1,
documentListenerKeys: new Set,
overlayEnsureScheduler: null,
pageObserversAttached: !1,
navigationListenerAttached: !1
};
const boundedCacheSet = (map, key, value) => {
if (map.size >= 50) {
const firstKey = map.keys().next().value;
map.delete(firstKey);
}
map.set(key, value);
};
const utils = {
log: (message, ...args) => {
const yt = window.YouTubeUtils;
yt && yt.logger && yt.logger.debug && yt.logger.debug("[YouTube+][Stats]", message, ...args);
},
warn: (message, ...args) => {
window.console.warn("[YouTube+][Stats]", message, ...args);
},
error: (message, ...args) => {
window.console.error("[YouTube+][Stats]", message, ...args);
},
debounce: window.YouTubeUtils.debounce
};
const {OPTIONS} = CONFIG;
const {FONT_LINK} = CONFIG;
const {STATS_API_URL} = CONFIG;
async function getChannelInfo(url) {
const data = await (async function fetchChannel(url) {
if (state.isChecking) {
return null;
}
state.isChecking = !0;
try {
const response = await fetch(url, {
credentials: "same-origin"
});
if (!response.ok) {
return null;
}
const html = await response.text();
const match = html.match(/var ytInitialData = (.+?);<\/script>/);
return match && match[1] ? JSON.parse(match[1]) : null;
} catch (error) {
utils.warn("Failed to fetch channel data:", error);
return null;
} finally {
state.isChecking = !1;
}
})(url);
if (!data) {
return null;
}
try {
const channelName = data?.metadata?.channelMetadataRenderer?.title || t("unknown");
const channelId = data?.metadata?.channelMetadataRenderer?.externalId || null;
return {
channelName,
channelId
};
} catch (e) {
return null;
}
}
function checkUrlChange() {
const currentUrl = location.href;
if (currentUrl !== state.previousUrl) {
state.previousUrl = currentUrl;
window.YouTubeUtils?.isChannelPage?.(currentUrl) && setTimeout(() => getChannelInfo(currentUrl), 500);
}
}
const _cm2 = window.YouTubeUtils?.cleanupManager;
if (_cm2?.registerListener) {
_cm2.registerListener(window, "yt-navigate-finish", checkUrlChange, {
passive: !0
});
_cm2.registerListener(window, "popstate", checkUrlChange, {
passive: !0
});
} else {
window.addEventListener("yt-navigate-finish", checkUrlChange, {
passive: !0
});
window.addEventListener("popstate", checkUrlChange, {
passive: !0
});
}
function init() {
try {
utils.log("Initializing YouTube Enhancer v1.6");
!(function loadFonts() {
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = FONT_LINK;
(document.head || document.documentElement).appendChild(fontLink);
})();
!(function initializeLocalStorage() {
OPTIONS.forEach(option => {
null === _safeLS_getItem(`show-${option}`) && _safeLS_setItem(`show-${option}`, "true");
});
})();
!(function addStyles() {
const styles = '\n      .channel-banner-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:12px;z-index:9;display:flex;justify-content:space-around;align-items:center;color:var(--yt-text-primary);font-family:var(--yt-stats-font-family,\'Rubik\',sans-serif);font-size:var(--yt-stats-font-size,24px);box-sizing:border-box;transition:background-color .3s ease;backdrop-filter:blur(2px)}        \n      .settings-button{position:absolute;top:12px;right:12px;width:32px;height:32px;border-radius:50%;cursor:pointer;z-index:11;transition:all .2s ease;display:flex;align-items:center;justify-content:center;background:var(--yt-stats-channel-button-bg);backdrop-filter:blur(4px);border:1px solid var(--yt-stats-channel-button-border);opacity:0.7}\n      .channel-banner-overlay:hover .settings-button{opacity:1}\n      .settings-button:hover{transform:rotate(30deg) scale(1.1);opacity:1;background:var(--yt-stats-channel-button-hover);border-color:var(--yt-stats-channel-button-hover-border)}\n      .settings-button svg{width:18px;height:18px;fill:var(--yt-text-primary);filter:drop-shadow(0 1px 2px var(--yt-stats-channel-filter-shadow))}        \n      .settings-menu{position:absolute;top:52px;right:12px;background:var(--yt-stats-channel-menu-bg);padding:16px;border-radius:16px;z-index:12;display:flex;flex-direction:column;gap:12px;backdrop-filter:blur(16px) saturate(180%);border:1px solid var(--yt-stats-channel-menu-border);box-shadow:0 8px 32px rgba(0,0,0,0.6);min-width:320px;opacity:0;visibility:hidden;transform:translateY(-10px) scale(0.98);transition:all 0.2s cubic-bezier(0.2,0,0.2,1);pointer-events:none}\n      .settings-menu.show{opacity:1;visibility:visible;transform:translateY(0) scale(1);pointer-events:auto}        \n      .settings-menu .ytp-plus-settings-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-radius:8px;background:var(--yt-stats-channel-menu-item-bg);}\n      .settings-menu .ytp-plus-settings-item + .ytp-plus-settings-item{margin-top:6px}\n      .settings-menu .ytp-plus-settings-item .ytp-plus-settings-item-label{color:var(--yt-stats-channel-label-text);font-size:14px;font-weight:500}\n      .settings-menu label{color:var(--yt-stats-channel-label-text)!important;font-size:14px!important;font-weight:500!important;margin-bottom:6px!important}        \n      .settings-menu input[type="range"]{-webkit-appearance:none;width:100%!important;height:4px;background:var(--yt-stats-channel-range-bg)!important;border-radius:2px;margin:12px 0 4px 0!important;cursor:pointer}\n      .settings-menu input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;height:16px;width:16px;border-radius:50%;background:var(--yt-stats-channel-range-thumb);margin-top:-6px;box-shadow:0 2px 4px var(--yt-stats-channel-text-shadow);border:2px solid var(--yt-text-primary);transition:transform .1s;cursor:pointer}\n      .settings-menu input[type="range"]::-webkit-slider-thumb:hover{transform:scale(1.2)}        \n      .settings-menu select{width:100%!important;background:var(--yt-stats-channel-input-bg)!important;border:1px solid var(--yt-stats-channel-input-bg)!important;color:var(--yt-text-primary)!important;padding:8px 12px!important;border-radius:6px!important;font-size:13px!important;margin-bottom:12px!important;cursor:pointer;outline:none}\n      .settings-menu select:hover{background:var(--yt-stats-channel-input-hover)!important}\n      .settings-menu select option{background:var(--yt-stats-channel-select-option-bg);color:var(--yt-text-primary)}        \n      /* Don\'t override the shared settings checkbox styling; only target non-shared inputs */\n      .settings-menu input[type="checkbox"]:not(.ytp-plus-settings-checkbox){appearance:none;width:18px!important;height:18px!important;border:2px solid var(--yt-stats-channel-checkbox-border)!important;border-radius:4px!important;background:transparent!important;cursor:pointer;position:relative;margin-right:12px!important;vertical-align:middle;transition:all .2s}\n      .settings-menu input[type="checkbox"]:not(.ytp-plus-settings-checkbox):checked{background:var(--yt-stats-channel-range-thumb)!important;border-color:var(--yt-stats-channel-range-thumb)!important}\n      .settings-menu input[type="checkbox"]:not(.ytp-plus-settings-checkbox):checked::after{content:\'\';position:absolute;left:5px;top:1px;width:4px;height:10px;border:solid var(--yt-text-primary);border-width:0 2px 2px 0;transform:rotate(45deg)}\n      .stat-container{display:flex;flex-direction:column;align-items:center;justify-content:center;visibility:hidden;width:33%;height:100%;padding:0 1rem;text-shadow:0 2px 4px var(--yt-stats-channel-text-shadow)}\n      .number-container{display:flex;align-items:center;justify-content:center;font-weight:700;min-height:3rem}\n      .label-container{display:flex;align-items:center;margin-top:.5rem;font-size:1.2rem;opacity:.9}\n      .label-container svg{width:1.5rem;height:1.5rem;margin-right:.5rem;filter:drop-shadow(0 1px 2px var(--yt-stats-channel-text-shadow))}\n      .difference{font-size:1.8rem;height:2rem;margin-bottom:.5rem;transition:opacity .3s}\n      .spinner-container{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center}\n      .spinner-container .stats-spinner{width:60px;height:60px;animation:spin 1s linear infinite}\n      .spinner-container .stats-spinner circle{stroke-dasharray:80;stroke-dashoffset:60;animation:dash 1.5s ease-in-out infinite}\n      /* @keyframes spin already defined in video stats CSS above */\n      @media(max-width:768px){.channel-banner-overlay{flex-direction:column;padding:8px;min-height:160px}.settings-menu{width:280px!important;right:4px!important;top:48px!important}}\n      .setting-group{margin-bottom:12px}\n      .setting-group:last-child{margin-bottom:0}\n      .setting-value{color:var(--yt-stats-channel-text-value);font-size:12px;margin-top:4px}\n      ';
YouTubeUtils.StyleManager.add("channel-stats-overlay", styles);
})();
if (state.enabled) {
observePageChanges();
addNavigationListener();
if (window.YouTubeUtils?.isChannelPage?.(location.href)) {
getChannelInfo(location.href);
try {
ensureOverlayForCurrentPage();
} catch (e) {
utils.warn("Initial overlay attempt failed:", e);
}
}
}
utils.log("YouTube Enhancer initialized successfully");
ensureSettingsUI();
} catch (error) {
utils.error("Failed to initialize YouTube Enhancer:", error);
}
}
function createSettingsMenu() {
const menu = document.createElement("div");
menu.className = "settings-menu";
if (menu.style) {
menu.style.gap = "15px";
menu.style.width = "360px";
}
menu.setAttribute("tabindex", "-1");
menu.setAttribute("aria-modal", "true");
const displaySection = (function createDisplaySection() {
const displaySection = document.createElement("div");
displaySection.style && (displaySection.style.flex = "1");
const displayLabel = document.createElement("label");
displayLabel.textContent = t("displayOptions");
if (displayLabel.style) {
displayLabel.style.marginBottom = "10px";
displayLabel.style.display = "block";
displayLabel.style.fontSize = "16px";
displayLabel.style.fontWeight = "bold";
}
displaySection.appendChild(displayLabel);
displaySection.addEventListener("change", e => {
const checkbox = e.target;
if (checkbox instanceof HTMLInputElement && "checkbox" === checkbox.type && checkbox.id.startsWith("show-")) {
const option = checkbox.id.replace("show-", "");
_safeLS_setItem(`show-${option}`, String(checkbox.checked));
updateDisplayState();
}
});
OPTIONS.forEach(option => {
const item = document.createElement("div");
item.className = "ytp-plus-settings-item";
const left = document.createElement("div");
const label = document.createElement("label");
label.className = "ytp-plus-settings-item-label";
label.htmlFor = `show-${option}`;
label.textContent = t(option);
left.appendChild(label);
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = `show-${option}`;
checkbox.checked = "false" !== _safeLS_getItem(`show-${option}`);
checkbox.className = "ytp-plus-settings-checkbox";
item.appendChild(left);
item.appendChild(checkbox);
displaySection.appendChild(item);
});
return displaySection;
})();
const controlsSection = (function createControlsSection() {
const controlsSection = document.createElement("div");
controlsSection.style && (controlsSection.style.flex = "1");
controlsSection.addEventListener("input", e => {
const target = e.target;
if (target instanceof HTMLElement && target.classList.contains("font-size-slider")) {
const input = target;
const fontSizeValue = controlsSection.querySelector(".font-size-value");
fontSizeValue && (fontSizeValue.textContent = `${input.value}px`);
_safeLS_setItem("youtubeEnhancerFontSize", input.value);
state.overlay && state.overlay.querySelectorAll(".subscribers-number,.views-number,.videos-number").forEach(el => {
el instanceof HTMLElement && el.style && (el.style.fontSize = `${input.value}px`);
});
}
if (target instanceof HTMLElement && target.classList.contains("interval-slider")) {
const input = target;
const newInterval = 1e3 * parseInt(input.value, 10);
const intervalValue = controlsSection.querySelector(".interval-value");
intervalValue && (intervalValue.textContent = `${input.value}s`);
state.updateInterval = newInterval;
_safeLS_setItem("youtubeEnhancerInterval", String(newInterval));
if (state.intervalId) {
state.intervalId.stop();
state.intervalId = createVisibilityAwareInterval(() => {
updateOverlayContent(state.overlay, state.currentChannelName);
}, newInterval);
}
}
if (target instanceof HTMLElement && target.classList.contains("opacity-slider")) {
const input = target;
const newOpacity = parseInt(input.value, 10) / 100;
const opacityValue = controlsSection.querySelector(".opacity-value");
opacityValue && (opacityValue.textContent = `${input.value}%`);
state.overlayOpacity = newOpacity;
_safeLS_setItem("youtubeEnhancerOpacity", String(newOpacity));
state.overlay && (state.overlay.style.backgroundColor = `rgba(0, 0, 0, ${newOpacity})`);
}
});
const fontLabel = document.createElement("label");
fontLabel.textContent = t("fontFamily");
fontLabel.style.display = "block";
fontLabel.style.marginBottom = "5px";
fontLabel.style.fontSize = "16px";
fontLabel.style.fontWeight = "bold";
const fonts = [ {
name: "Rubik",
value: "Rubik, sans-serif"
}, {
name: "Impact",
value: "Impact, Charcoal, sans-serif"
}, {
name: "Verdana",
value: "Verdana, Geneva, sans-serif"
}, {
name: "Tahoma",
value: "Tahoma, Geneva, sans-serif"
} ];
const savedFont = _safeLS_getItem("youtubeEnhancerFontFamily") || "Rubik, sans-serif";
const savedFontName = fonts.find(f => f.value === savedFont)?.name || "Rubik";
const fontSelect = document.createElement("select");
fontSelect.className = "font-family-select";
fontSelect.style.display = "none";
fonts.forEach(f => {
const opt = document.createElement("option");
opt.value = f.value;
opt.textContent = f.name;
f.value === savedFont && (opt.selected = !0);
fontSelect.appendChild(opt);
});
const fontDropdown = document.createElement("div");
fontDropdown.className = "glass-dropdown";
fontDropdown.id = "stats-font-dropdown";
fontDropdown.tabIndex = 0;
fontDropdown.setAttribute("role", "listbox");
fontDropdown.setAttribute("aria-expanded", "false");
fontDropdown.style.marginBottom = "12px";
_setSafeHTML(fontDropdown, `\n      <button class="glass-dropdown__toggle" type="button" aria-haspopup="listbox">\n        <span class="glass-dropdown__label">${savedFontName}</span>\n        <svg class="glass-dropdown__chev" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>\n      </button>\n      <ul class="glass-dropdown__list" role="presentation">\n        ${fonts.map(f => {
const sel = f.value === savedFont ? ' aria-selected="true"' : "";
return `<li class="glass-dropdown__item" data-value="${f.value}" role="option"${sel}>${f.name}</li>`;
}).join("")}\n      </ul>\n    `);
const initFontDropdown = () => {
const toggle = fontDropdown.querySelector(".glass-dropdown__toggle");
const list = fontDropdown.querySelector(".glass-dropdown__list");
const label = fontDropdown.querySelector(".glass-dropdown__label");
const closeList = () => {
fontDropdown.setAttribute("aria-expanded", "false");
list && (list.style.display = "none");
};
const openList = () => {
fontDropdown.setAttribute("aria-expanded", "true");
list && (list.style.display = "block");
};
closeList();
toggle && toggle.addEventListener("click", e => {
e.stopPropagation();
const expanded = "true" === fontDropdown.getAttribute("aria-expanded");
expanded ? closeList() : openList();
});
const _docClickHandler = e => {
const target = e.target;
target instanceof Node && fontDropdown.contains(target) || closeList();
};
if (window.YouTubeUtils?.cleanupManager?.registerListener) {
window.YouTubeUtils.cleanupManager.registerListener(document, "click", _docClickHandler);
} else {
document.addEventListener("click", _docClickHandler);
state?.documentListenerKeys && state.documentListenerKeys.add("_docClickHandler");
}
list && list.addEventListener("click", e => {
const target = e.target;
if (!(target instanceof HTMLElement)) {
return;
}
const it = target.closest(".glass-dropdown__item");
if (!it) {
return;
}
const val = it.dataset?.value || "";
fontDropdown.querySelectorAll(".glass-dropdown__item").forEach(i => i.removeAttribute("aria-selected"));
it.setAttribute("aria-selected", "true");
label && (label.textContent = it.textContent);
fontSelect.value = val;
closeList();
_safeLS_setItem("youtubeEnhancerFontFamily", val);
state.overlay && state.overlay.querySelectorAll(".subscribers-number,.views-number,.videos-number").forEach(el => {
el instanceof HTMLElement && el.style && (el.style.fontFamily = val);
});
});
};
("function" == typeof queueMicrotask ? queueMicrotask : fn => Promise.resolve().then(fn))(initFontDropdown);
const fontSizeLabel = document.createElement("label");
fontSizeLabel.textContent = t("fontSize");
fontSizeLabel.style.display = "block";
fontSizeLabel.style.marginBottom = "5px";
fontSizeLabel.style.fontSize = "16px";
fontSizeLabel.style.fontWeight = "bold";
const fontSizeSlider = document.createElement("input");
fontSizeSlider.type = "range";
fontSizeSlider.min = "16";
fontSizeSlider.max = "72";
fontSizeSlider.value = _safeLS_getItem("youtubeEnhancerFontSize") || "24";
fontSizeSlider.step = "1";
fontSizeSlider.className = "font-size-slider";
const fontSizeValue = document.createElement("div");
fontSizeValue.className = "font-size-value";
fontSizeValue.textContent = `${fontSizeSlider.value}px`;
fontSizeValue.style.fontSize = "14px";
fontSizeValue.style.marginBottom = "15px";
const intervalLabel = document.createElement("label");
intervalLabel.textContent = t("updateInterval");
intervalLabel.style.display = "block";
intervalLabel.style.marginBottom = "5px";
intervalLabel.style.fontSize = "16px";
intervalLabel.style.fontWeight = "bold";
const intervalSlider = document.createElement("input");
intervalSlider.type = "range";
intervalSlider.min = "2";
intervalSlider.max = "10";
intervalSlider.value = String(state.updateInterval / 1e3);
intervalSlider.step = "1";
intervalSlider.className = "interval-slider";
const intervalValue = document.createElement("div");
intervalValue.className = "interval-value";
intervalValue.textContent = `${intervalSlider.value}s`;
intervalValue.style.marginBottom = "15px";
intervalValue.style.fontSize = "14px";
const opacityLabel = document.createElement("label");
opacityLabel.textContent = t("backgroundOpacity");
opacityLabel.style.display = "block";
opacityLabel.style.marginBottom = "5px";
opacityLabel.style.fontSize = "16px";
opacityLabel.style.fontWeight = "bold";
const opacitySlider = document.createElement("input");
opacitySlider.type = "range";
opacitySlider.min = "50";
opacitySlider.max = "90";
opacitySlider.value = String(100 * state.overlayOpacity);
opacitySlider.step = "5";
opacitySlider.className = "opacity-slider";
const opacityValue = document.createElement("div");
opacityValue.className = "opacity-value";
opacityValue.textContent = `${opacitySlider.value}%`;
opacityValue.style.fontSize = "14px";
controlsSection.appendChild(fontLabel);
controlsSection.appendChild(fontSelect);
controlsSection.appendChild(fontDropdown);
controlsSection.appendChild(fontSizeLabel);
controlsSection.appendChild(fontSizeSlider);
controlsSection.appendChild(fontSizeValue);
controlsSection.appendChild(intervalLabel);
controlsSection.appendChild(intervalSlider);
controlsSection.appendChild(intervalValue);
controlsSection.appendChild(opacityLabel);
controlsSection.appendChild(opacitySlider);
controlsSection.appendChild(opacityValue);
return controlsSection;
})();
menu.appendChild(displaySection);
menu.appendChild(controlsSection);
return menu;
}
function createStatContainer(className, iconPath) {
const container = document.createElement("div");
Object.assign(container.style || {}, {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
visibility: "hidden",
width: "33%",
height: "100%",
padding: "0 1rem"
});
const numberContainer = document.createElement("div");
Object.assign(numberContainer.style || {}, {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center"
});
const differenceElement = document.createElement("div");
differenceElement.classList.add(`${className}-difference`);
Object.assign(differenceElement.style || {}, {
fontSize: "2.5rem",
height: "2.5rem",
marginBottom: "1rem"
});
const digitContainer = (function createNumberContainer() {
const container = document.createElement("div");
Object.assign(container.style || {}, {
display: "flex",
justifyContent: "center",
alignItems: "center",
letterSpacing: "0.025em"
});
return container;
})();
digitContainer.classList.add(`${className}-number`);
Object.assign(digitContainer.style || {}, {
fontSize: `${_safeLS_getItem("youtubeEnhancerFontSize") || "24"}px`,
fontWeight: "bold",
lineHeight: "1",
height: "4rem",
fontFamily: _safeLS_getItem("youtubeEnhancerFontFamily") || "Rubik, sans-serif",
letterSpacing: "0.025em"
});
numberContainer.appendChild(differenceElement);
numberContainer.appendChild(digitContainer);
const labelContainer = document.createElement("div");
Object.assign(labelContainer.style || {}, {
display: "flex",
alignItems: "center",
marginTop: "0.5rem"
});
const icon = (function createSVGIcon(path) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 640 512");
svg.setAttribute("width", "2rem");
svg.setAttribute("height", "2rem");
if (svg.style) {
svg.style.marginRight = "0.5rem";
svg.style.display = "none";
}
const svgPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
svgPath.setAttribute("d", path);
svgPath.setAttribute("fill", "white");
svg.appendChild(svgPath);
return svg;
})(iconPath);
Object.assign(icon.style || {}, {
width: "2rem",
height: "2rem",
marginRight: "0.75rem"
});
const labelElement = document.createElement("div");
labelElement.classList.add(`${className}-label`);
labelElement.style.fontSize = "2rem";
labelContainer.appendChild(icon);
labelContainer.appendChild(labelElement);
container.appendChild(numberContainer);
container.appendChild(labelContainer);
return container;
}
function setupSettingsButton() {
const button = (function createSettingsButton() {
const button = document.createElement("div");
button.className = "settings-button";
_setSafeHTML(button, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="white" d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>');
return button;
})();
button.setAttribute("tabindex", "0");
button.setAttribute("aria-label", t("settingsAriaLabel"));
button.setAttribute("role", "button");
return button;
}
function createOverlay(bannerElement) {
clearExistingOverlay();
if (!bannerElement) {
return null;
}
const overlay = (function createOverlayElement() {
const overlay = document.createElement("div");
overlay.classList.add("channel-banner-overlay");
Object.assign(overlay.style || {}, {
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
backgroundColor: `rgba(0, 0, 0, ${state.overlayOpacity})`,
borderRadius: "15px",
zIndex: "10",
display: "flex",
justifyContent: "space-around",
alignItems: "center",
color: "white",
fontFamily: _safeLS_getItem("youtubeEnhancerFontFamily") || "Rubik, sans-serif",
fontSize: `${_safeLS_getItem("youtubeEnhancerFontSize") || "24"}px`,
boxSizing: "border-box",
transition: "background-color 0.3s ease"
});
return overlay;
})();
!(function applyOverlayAccessibility(overlay) {
overlay.setAttribute("role", "region");
overlay.setAttribute("aria-label", t("overlayAriaLabel"));
overlay.setAttribute("tabindex", "-1");
})(overlay);
!(function applyMobileResponsiveness(overlay) {
if (window.innerWidth <= 768 && overlay.style) {
overlay.style.flexDirection = "column";
overlay.style.padding = "10px";
overlay.style.minHeight = "200px";
}
})(overlay);
const settingsButton = setupSettingsButton();
const settingsMenu = (function setupSettingsMenu() {
const menu = createSettingsMenu();
menu.setAttribute("aria-label", t("settingsMenuAriaLabel"));
menu.setAttribute("role", "dialog");
return menu;
})();
overlay.appendChild(settingsButton);
overlay.appendChild(settingsMenu);
!(function attachMenuEventHandlers(settingsButton, settingsMenu) {
const toggleMenu = show => {
settingsMenu.classList.toggle("show", show);
settingsButton.setAttribute("aria-expanded", String(show));
show && settingsMenu.focus();
};
settingsButton.addEventListener("click", e => {
e.stopPropagation();
toggleMenu(!settingsMenu.classList.contains("show"));
});
settingsButton.addEventListener("keydown", e => {
if ("Enter" === e.key || " " === e.key) {
e.preventDefault();
toggleMenu(!settingsMenu.classList.contains("show"));
}
});
const clickKey = YouTubeUtils.cleanupManager.registerListener(document, "click", e => {
const node = e.target;
settingsMenu.contains(node) || settingsButton.contains(node) || toggleMenu(!1);
});
const keyKey = YouTubeUtils.cleanupManager.registerListener(document, "keydown", e => {
const ke = e;
if ("Escape" === ke.key && settingsMenu.classList.contains("show")) {
toggleMenu(!1);
settingsButton.focus();
}
});
state.documentListenerKeys.add(clickKey);
state.documentListenerKeys.add(keyKey);
})(settingsButton, settingsMenu);
const spinner = (function createSpinner() {
const spinnerContainer = document.createElement("div");
spinnerContainer.style.position = "absolute";
spinnerContainer.style.top = "0";
spinnerContainer.style.left = "0";
spinnerContainer.style.width = "100%";
spinnerContainer.style.height = "100%";
spinnerContainer.style.display = "flex";
spinnerContainer.style.justifyContent = "center";
spinnerContainer.style.alignItems = "center";
spinnerContainer.classList.add("spinner-container");
_setSafeHTML(spinnerContainer, '<svg viewBox="0 0 50 50" class="stats-spinner"><circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="4"></circle></svg>');
return spinnerContainer;
})();
overlay.appendChild(spinner);
!(function addStatContainers(overlay) {
const subscribersElement = createStatContainer("subscribers", "M144 160c-44.2 0-80-35.8-80-80S99.8 0 144 0s80 35.8 80 80s-35.8 80-80 80zm368 0c-44.2 0-80-35.8-80-80s35.8-80 80-80s80 35.8 80 80s-35.8 80-80 80zM0 298.7C0 239.8 47.8 192 106.7 192h42.7c15.9 0 31 3.5 44.6 9.7c-1.3 7.2-1.9 14.7-1.9 22.3c0 38.2 16.8 72.5 43.3 96c-.2 0-.4 0-.7 0H21.3C9.6 320 0 310.4 0 298.7zM405.3 320c-.2 0-.4 0-.7 0c26.6-23.5 43.3-57.8 43.3-96c0-7.6-.7-15-1.9-22.3c13.6-6.3 28.7-9.7 44.6-9.7h42.7C592.2 192 640 239.8 640 298.7c0 11.8-9.6 21.3-21.3 21.3H405.3zM416 224c0 53-43 96-96 96s-96-43-96-96s43-96 96-96s96 43 96 96zM128 485.3C128 411.7 187.7 352 261.3 352H378.7C452.3 352 512 411.7 512 485.3c0 14.7-11.9 26.7-26.7 26.7H154.7c-14.7 0-26.7-11.9-26.7-26.7z");
const viewsElement = createStatContainer("views", "M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z");
const videosElement = createStatContainer("videos", "M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zM559.1 99.8c10.4 5.6 16.9 16.4 16.9 28.2V384c0 11.8-6.5 22.6-16.9 28.2s-23 5-32.9-1.6l-96-64L416 337.1V320 192 174.9l14.2-9.5 96-64c9.8-6.5 22.4-7.2 32.9-1.6z");
overlay.appendChild(subscribersElement);
overlay.appendChild(viewsElement);
overlay.appendChild(videosElement);
})(overlay);
bannerElement.appendChild(overlay);
updateDisplayState();
return overlay;
}
async function fetchChannelStats(channelId) {
const helpers = "undefined" != typeof window && window.YouTubePlusChannelStatsHelpers ? window.YouTubePlusChannelStatsHelpers : null;
if (!helpers) {
utils.error("Channel stats helpers not loaded");
return {
followerCount: 0,
bottomOdos: [ 0, 0 ],
error: !0,
timestamp: Date.now()
};
}
try {
const fetchFn = () => (function fetchWithGM(url, headers = {}) {
const requestHeaders = {
Accept: "application/json",
...headers
};
const gm = window.GM_xmlhttpRequest;
if ("function" == typeof gm) {
return new Promise((resolve, reject) => {
gm({
method: "GET",
url,
headers: requestHeaders,
timeout: 1e4,
onload: response => {
if (response.status >= 200 && response.status < 300) {
try {
resolve(JSON.parse(response.responseText));
} catch (parseError) {
const message = parseError instanceof Error ? parseError.message : String(parseError);
reject(new Error(`Failed to parse response: ${message}`));
}
} else {
reject(new Error(`Failed to fetch: ${response.status}`));
}
},
onerror: error => reject(error),
ontimeout: () => reject(new Error("Request timed out"))
});
});
}
utils.warn("GM_xmlhttpRequest unavailable, falling back to fetch API");
return fetch(url, {
method: "GET",
headers: requestHeaders,
credentials: "omit",
mode: "cors"
}).then(response => {
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
return response.json();
}).catch(error => {
utils.error("Fallback fetch failed:", error);
throw error;
});
})(`${STATS_API_URL}${channelId}`, {
origin: "https://livecounts.io",
referer: "https://livecounts.io/"
});
const stats = await helpers.fetchWithRetry(fetchFn, CONFIG.MAX_RETRIES, utils);
if (stats) {
helpers.cacheStats(state.lastSuccessfulStats, channelId, stats);
return stats;
}
const cachedStats = helpers.getCachedStats(state.lastSuccessfulStats, channelId, CONFIG.CACHE_DURATION, utils);
if (cachedStats) {
return cachedStats;
}
const fallbackCount = helpers.extractSubscriberCountFromPage();
fallbackCount > 0 && utils.log("Extracted fallback subscriber count:", fallbackCount);
return helpers.createFallbackStats(fallbackCount);
} catch (error) {
utils.error("Failed to fetch channel stats:", error);
return helpers.createFallbackStats(0);
}
}
function clearExistingOverlay() {
const existingOverlay = $(".channel-banner-overlay");
if (existingOverlay) {
try {
existingOverlay.remove();
} catch (e) {
window.console.warn("[YouTube+] Failed to remove overlay");
}
}
if (state.intervalId) {
try {
state.intervalId.stop();
} catch (e) {
window.console.warn("[YouTube+] Failed to clear interval");
}
state.intervalId = null;
}
if (state.documentListenerKeys && state.documentListenerKeys.size) {
state.documentListenerKeys.forEach(key => {
try {
YouTubeUtils.cleanupManager.unregisterListener(key);
} catch (e) {
window.console.warn("[YouTube+] Failed to unregister listener");
}
});
state.documentListenerKeys.clear();
}
state.lastSuccessfulStats && state.lastSuccessfulStats.clear();
state.previousStats && state.previousStats.clear();
state.currentChannelId = null;
state.isUpdating = !1;
state.overlay = null;
utils.log("Cleared existing overlay");
}
function createDigitElement() {
const digit = document.createElement("span");
Object.assign(digit.style || {}, {
display: "inline-block",
width: "0.6em",
textAlign: "center",
marginRight: "0.025em",
marginLeft: "0.025em"
});
return digit;
}
function createCommaElement() {
const comma = document.createElement("span");
comma.textContent = ",";
Object.assign(comma.style || {}, {
display: "inline-block",
width: "0.3em",
textAlign: "center"
});
return comma;
}
function updateDigits(container, newValue) {
const newValueStr = newValue.toString();
const digitGroups = (function splitIntoDigitGroups(valueStr) {
const digits = [];
for (let i = valueStr.length - 1; i >= 0; i -= 3) {
const start = Math.max(0, i - 2);
digits.unshift(valueStr.slice(start, i + 1));
}
return digits;
})(newValueStr);
!(function clearContainer(container) {
for (;container.firstChild; ) {
container.removeChild(container.firstChild);
}
})(container);
!(function renderDigitGroups(container, digitGroups) {
for (let i = 0; i < digitGroups.length; i++) {
const group = digitGroups[i];
for (let j = 0; j < group.length; j++) {
const digitElement = createDigitElement();
digitElement.textContent = group[j];
container.appendChild(digitElement);
}
i < digitGroups.length - 1 && container.appendChild(createCommaElement());
}
})(container, digitGroups);
!(function animateDigitChanges(container, digitGroups) {
let elementIndex = 0;
for (let i = 0; i < digitGroups.length; i++) {
const group = digitGroups[i];
for (let j = 0; j < group.length; j++) {
const digitElement = container.children[elementIndex];
const newDigit = parseInt(group[j], 10);
const currentDigit = parseInt(digitElement.textContent || "0", 10);
currentDigit !== newDigit && animateDigit(digitElement, currentDigit, newDigit);
elementIndex++;
}
i < digitGroups.length - 1 && elementIndex++;
}
})(container, digitGroups);
}
function animateDigit(element, start, end) {
const startTime = performance.now();
requestAnimationFrame(function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / 1e3, 1);
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
const current = Math.round(start + (end - start) * easeOutQuart);
element.textContent = String(current);
progress < 1 && requestAnimationFrame(update);
});
}
function updateDisplayState() {
const overlay = $(".channel-banner-overlay");
if (!overlay) {
return;
}
const statContainers = overlay.querySelectorAll('div[style*="width"]');
if (!statContainers.length) {
return;
}
let visibleCount = 0;
const visibleContainers = [];
statContainers.forEach(container => {
const numberContainer = container.querySelector('[class$="-number"]');
if (!numberContainer) {
return;
}
const type = numberContainer.className.replace("-number", "");
const isVisible = "false" !== _safeLS_getItem(`show-${type}`);
if (isVisible) {
container instanceof HTMLElement && container.style && (container.style.display = "flex");
visibleCount++;
visibleContainers.push(container);
} else {
container instanceof HTMLElement && container.style && (container.style.display = "none");
}
});
visibleContainers.forEach(container => {
if (container instanceof HTMLElement && container.style) {
container.style.width = "";
container.style.margin = "";
switch (visibleCount) {
case 1:
container.style.width = "100%";
break;

case 2:
container.style.width = "50%";
break;

case 3:
container.style.width = "33.33%";
break;

default:
container.style.display = "none";
}
}
});
const fontSize = _safeLS_getItem("youtubeEnhancerFontSize") || "24";
const fontFamily = _safeLS_getItem("youtubeEnhancerFontFamily") || "Rubik, sans-serif";
overlay.querySelectorAll(".subscribers-number,.views-number,.videos-number").forEach(el => {
if (el instanceof HTMLElement && el.style) {
el.style.fontSize = `${fontSize}px`;
el.style.fontFamily = fontFamily;
}
});
overlay.style && (overlay.style.display = "flex");
}
function updateStatElement(overlay, channelId, className, value, label) {
const numberContainer = overlay.querySelector(`.${className}-number`);
const differenceElement = overlay.querySelector(`.${className}-difference`);
const labelElement = overlay.querySelector(`.${className}-label`);
numberContainer && updateDigits(numberContainer, value);
if (differenceElement && state.previousStats.has(channelId)) {
const previousValue = (function getPreviousStatValue(channelId, className) {
const prevStats = state.previousStats.get(channelId);
if (!prevStats) {
return null;
}
if ("subscribers" === className) {
return prevStats.followerCount;
}
const index = "views" === className ? 0 : 1;
return prevStats.bottomOdos[index];
})(channelId, className);
null !== previousValue && (function updateDifferenceElement(element, currentValue, previousValue) {
if (!previousValue) {
return;
}
const difference = currentValue - previousValue;
if (0 === difference) {
element.textContent = "";
return;
}
const sign = difference > 0 ? "+" : "";
element.textContent = `${sign}${difference.toLocaleString()}`;
element.style && (element.style.color = difference > 0 ? "#1ed760" : "#f3727f");
setTimeout_(() => {
element.textContent = "";
}, 1e3);
})(differenceElement, value, previousValue);
}
labelElement && (labelElement.textContent = label);
}
async function updateOverlayContent(overlay, channelName) {
if ((function shouldUpdateOverlay(channelName) {
return !state.isUpdating && channelName === state.currentChannelName;
})(channelName) && overlay && overlay.isConnected && "hidden" !== document.visibilityState) {
state.isUpdating = !0;
try {
const channelId = await (async function fetchChannelId(channelName) {
const cacheKey = channelName || state.currentChannelName || window.location.pathname;
if (cacheKey && state.channelIdCache.has(cacheKey)) {
return state.channelIdCache.get(cacheKey);
}
if (state.currentChannelId) {
return state.currentChannelId;
}
if ("string" == typeof channelName && /^UC[\w-]{22}$/.test(channelName)) {
state.currentChannelId = channelName;
cacheKey && boundedCacheSet(state.channelIdCache, cacheKey, channelName);
return channelName;
}
const metaTag = $('meta[itemprop="channelId"]');
if (metaTag && metaTag.content) {
state.currentChannelId = metaTag.content;
cacheKey && boundedCacheSet(state.channelIdCache, cacheKey, metaTag.content);
return metaTag.content;
}
const urlMatch = window.location.href.match(/channel\/(UC[\w-]+)/);
if (urlMatch && urlMatch[1]) {
state.currentChannelId = urlMatch[1];
cacheKey && boundedCacheSet(state.channelIdCache, cacheKey, urlMatch[1]);
return urlMatch[1];
}
const channelInfo = await getChannelInfo(window.location.href);
if (channelInfo && channelInfo.channelId) {
state.currentChannelId = channelInfo.channelId;
cacheKey && boundedCacheSet(state.channelIdCache, cacheKey, channelInfo.channelId);
return channelInfo.channelId;
}
return null;
})(channelName);
if (!channelId) {
const now = Date.now();
if (now - state.lastChannelIdWarnAt > 15e3) {
state.lastChannelIdWarnAt = now;
utils.warn("Skipping overlay update: channel ID is not available yet");
}
return;
}
state.currentChannelId = channelId;
const stats = await fetchChannelStats(channelId);
if (channelName !== state.currentChannelName) {
return;
}
if (stats.error) {
!(function handleStatsError(overlay, stats) {
const containers = overlay.querySelectorAll('[class$="-number"]');
containers.forEach(container => {
container.classList.contains("subscribers-number") && stats.followerCount > 0 ? updateDigits(container, stats.followerCount) : container.textContent = "---";
});
utils.warn("Using fallback stats due to API error");
})(overlay, stats);
return;
}
!(function updateAllStatElements(overlay, channelId, stats) {
updateStatElement(overlay, channelId, "subscribers", stats.followerCount, t("subscribers"));
updateStatElement(overlay, channelId, "views", stats.bottomOdos[0], t("views"));
updateStatElement(overlay, channelId, "videos", stats.bottomOdos[1], t("videos"));
})(overlay, channelId, stats);
if (!state.previousStats.has(channelId)) {
!(function showContent(overlay) {
const spinnerContainer = overlay.querySelector(".spinner-container");
spinnerContainer && spinnerContainer.remove();
const containers = overlay.querySelectorAll('div[style*="visibility: hidden"]');
containers.forEach(container => {
container instanceof HTMLElement && container.style && (container.style.visibility = "visible");
});
const icons = overlay.querySelectorAll('svg[style*="display: none"]');
icons.forEach(icon => {
if (icon instanceof SVGElement || icon instanceof HTMLElement) {
const styled = icon;
styled.style && (styled.style.display = "block");
}
});
})(overlay);
utils.log("Displayed initial stats for channel:", channelName);
}
state.previousStats.set(channelId, stats);
} catch (error) {
utils.error("Failed to update overlay content:", error);
!(function showOverlayError(overlay) {
const containers = overlay.querySelectorAll('[class$="-number"]');
containers.forEach(container => {
container.textContent = "---";
});
})(overlay);
} finally {
state.isUpdating = !1;
}
}
}
function createSafeRetryScheduler(opts) {
const factory = window.YouTubeUtils?.createRetryScheduler;
if ("function" == typeof factory) {
try {
return factory(opts);
} catch (error) {
window.YouTubeUtils?.logError?.("ChannelStats", "Retry scheduler factory failed", error);
}
}
const {check, maxAttempts = 20, interval = 100} = opts || {};
let attempts = 0;
let timerId = null;
let stopped = !1;
const tick = () => {
if (!stopped) {
attempts += 1;
try {
if ("function" == typeof check && check()) {
stopped = !0;
return;
}
} catch (error) {
window.YouTubeUtils?.logError?.("ChannelStats", "Fallback retry check failed", error);
}
attempts >= maxAttempts ? stopped = !0 : timerId = setTimeout(tick, interval);
}
};
timerId = setTimeout(tick, 0);
return {
stop() {
stopped = !0;
timerId && clearTimeout(timerId);
timerId = null;
}
};
}
let ensureSettingsScheduler = null;
function ensureSettingsUI() {
ensureSettingsScheduler && ensureSettingsScheduler.stop();
ensureSettingsScheduler = createSafeRetryScheduler({
check: () => (function addSettingsUI() {
const section = $$('.ytp-plus-settings-section[data-section="experimental"]').find(candidate => candidate.isConnected);
if (!section) {
return !1;
}
const existingItem = section.querySelector(".count-settings-item");
if (existingItem) {
const label = existingItem.querySelector(".ytp-plus-settings-item-label");
const description = existingItem.querySelector(".ytp-plus-settings-item-description");
label && (label.textContent = t("channelStatsTitle"));
description && (description.textContent = t("channelStatsDescription"));
return !0;
}
const item = document.createElement("div");
item.className = "ytp-plus-settings-item count-settings-item";
_setSafeHTML(item, `\n        <div>\n          <label class="ytp-plus-settings-item-label">${t("channelStatsTitle")}</label>\n          <div class="ytp-plus-settings-item-description">${t("channelStatsDescription")}</div>\n        </div>\n        <input type="checkbox" class="ytp-plus-settings-checkbox" ${state.enabled ? "checked" : ""}>\n      `);
section.appendChild(item);
item.querySelector("input")?.addEventListener("change", e => {
const {target} = e;
const input = target;
state.enabled = input.checked;
_safeLS_setItem(CONFIG.STORAGE_KEY, state.enabled ? "true" : "false");
if (state.enabled) {
observePageChanges();
addNavigationListener();
setTimeout_(() => {
const bannerElement = byId("page-header-banner-sizer");
bannerElement instanceof HTMLElement && window.YouTubeUtils?.isChannelPage?.() && addOverlay(bannerElement);
}, 100);
} else {
clearExistingOverlay();
}
});
return !0;
})(),
maxAttempts: 50,
interval: 100
});
}
if (_cm2?.registerListener) {
_cm2.registerListener(document, "youtube-plus-settings-modal-opened", () => {
ensureSettingsUI();
});
} else {
const _handler = () => {
ensureSettingsUI();
};
document.addEventListener("youtube-plus-settings-modal-opened", _handler);
try {
window.YouTubeUtils?.cleanupManager?.register?.(() => document.removeEventListener("youtube-plus-settings-modal-opened", _handler));
} catch (e) {}
}
const experimentalNavClickHandler = e => {
const {target} = e;
const el = target;
const navItem = el?.closest?.(".ytp-plus-settings-nav-item");
"experimental" === navItem?.dataset?.section && ensureSettingsUI();
};
if (_cm2?.registerListener) {
_cm2.registerListener(document, "youtube-plus-language-changed", () => {
ensureSettingsUI();
});
} else {
const _langHandler = () => {
ensureSettingsUI();
};
document.addEventListener("youtube-plus-language-changed", _langHandler);
try {
window.YouTubeUtils?.cleanupManager?.register?.(() => document.removeEventListener("youtube-plus-language-changed", _langHandler));
} catch (e) {}
}
if (_cm2?.registerListener) {
const listenerKey = _cm2.registerListener(document, "click", experimentalNavClickHandler, !0);
state.documentListenerKeys.add(listenerKey);
} else {
document.addEventListener("click", experimentalNavClickHandler, !0);
try {
window.YouTubeUtils?.cleanupManager?.register?.(() => document.removeEventListener("click", experimentalNavClickHandler, !0));
} catch (e) {}
}
function setupUpdateInterval(overlay, channelName) {
state.intervalId && state.intervalId.stop();
const debouncedUpdate = (function createDebouncedUpdate(overlay, channelName) {
let lastUpdateTime = 0;
return () => {
if (!overlay || !overlay.isConnected) {
return;
}
if ("hidden" === document.visibilityState) {
return;
}
const now = Date.now();
if (now - lastUpdateTime >= state.updateInterval - 100) {
updateOverlayContent(overlay, channelName);
lastUpdateTime = now;
}
};
})(overlay, channelName);
state.intervalId = createVisibilityAwareInterval(debouncedUpdate, state.updateInterval);
}
function addOverlay(bannerElement) {
const channelName = (function extractChannelName(pathname) {
return pathname.startsWith("/@") ? pathname.split("/")[1].replace("@", "") : pathname.startsWith("/channel/") || pathname.startsWith("/c/") || pathname.startsWith("/user/") ? pathname.split("/")[2] : null;
})(window.location.pathname);
if (!(function shouldSkipOverlay(channelName, bannerElement) {
return !(channelName && !(channelName === state.currentChannelName && state.overlay?.isConnected && bannerElement && bannerElement.contains(state.overlay)));
})(channelName, bannerElement) && channelName) {
!state.overlay || state.overlay.isConnected && bannerElement.contains(state.overlay) || clearExistingOverlay();
!(function ensureBannerPosition(bannerElement) {
bannerElement && bannerElement.style && !bannerElement.style.position && (bannerElement.style.position = "relative");
})(bannerElement);
state.currentChannelName = channelName;
state.overlay = createOverlay(bannerElement);
if (state.overlay) {
!(function clearUpdateInterval() {
if (state.intervalId) {
state.intervalId.stop();
state.intervalId = null;
}
})();
setupUpdateInterval(state.overlay, channelName);
updateOverlayContent(state.overlay, channelName);
utils.log("Added overlay for channel:", channelName);
}
}
}
function stopOverlayEnsureScheduler() {
state.overlayEnsureScheduler?.stop && state.overlayEnsureScheduler.stop();
state.overlayEnsureScheduler = null;
}
function ensureOverlayForCurrentPage(reset = !0) {
reset && stopOverlayEnsureScheduler();
state.overlayEnsureScheduler = createSafeRetryScheduler({
check: () => {
if (!state.enabled) {
return !0;
}
if (!window.YouTubeUtils?.isChannelPage?.()) {
clearExistingOverlay();
state.currentChannelName = null;
return !0;
}
const bannerElement = (function findBannerElement() {
let bannerElement = byId("page-header-banner-sizer");
if (!(bannerElement instanceof HTMLElement)) {
const explicit = $("#page-header-banner");
explicit instanceof HTMLElement && (bannerElement = explicit);
}
if (!(bannerElement instanceof HTMLElement)) {
return null;
}
const rect = bannerElement.getBoundingClientRect?.();
return rect && (rect.width < 8 || rect.height < 8) ? null : bannerElement;
})();
if (!bannerElement) {
return !1;
}
!(function ensureBannerPositioning(bannerElement) {
bannerElement.style && "relative" !== bannerElement.style.position && (bannerElement.style.position = "relative");
})(bannerElement);
addOverlay(bannerElement);
return !(!state.overlay || !state.overlay.isConnected);
},
maxAttempts: 40,
interval: 150
});
}
function handleBannerUpdate() {
if (window.YouTubeUtils?.isChannelPage?.()) {
ensureOverlayForCurrentPage();
} else {
clearExistingOverlay();
state.currentChannelName = null;
stopOverlayEnsureScheduler();
}
}
function observePageChanges() {
if (!state.enabled || state.pageObserversAttached) {
return;
}
state.pageObserversAttached = !0;
const debouncedBannerUpdate = YouTubeUtils.debounce ? YouTubeUtils.debounce(handleBannerUpdate, 150) : handleBannerUpdate;
if (_cm2?.registerListener) {
_cm2.registerListener(document, "yt-navigate-finish", debouncedBannerUpdate);
_cm2.registerListener(document, "yt-page-data-updated", debouncedBannerUpdate);
} else {
document.addEventListener("yt-navigate-finish", debouncedBannerUpdate);
document.addEventListener("yt-page-data-updated", debouncedBannerUpdate);
}
}
function addNavigationListener() {
if (!state.enabled || state.navigationListenerAttached) {
return;
}
state.navigationListenerAttached = !0;
const _navHandler = () => {
if (window.YouTubeUtils?.isChannelPage?.()) {
ensureOverlayForCurrentPage();
utils.log("Navigated to channel page");
} else {
clearExistingOverlay();
state.currentChannelName = null;
stopOverlayEnsureScheduler();
utils.log("Navigated away from channel page");
}
};
_cm2?.registerListener ? _cm2.registerListener(window, "yt-navigate-finish", _navHandler) : window.addEventListener("yt-navigate-finish", _navHandler);
try {
window.addEventListener("ytp:nav-refresh", () => {
try {
_navHandler();
} catch (e) {}
});
} catch (e) {}
}
function cleanup() {
clearExistingOverlay();
stopOverlayEnsureScheduler();
utils.log("Cleanup completed");
}
_cm2?.registerListener ? _cm2.registerListener(window, "beforeunload", cleanup) : window.addEventListener("beforeunload", cleanup);
"undefined" != typeof window && (window.YouTubeStats = {
init,
cleanup,
version: "2.4.5"
});
$(".ytp-plus-settings-modal") && ensureSettingsUI();
init();
};
document.addEventListener("youtube-plus-settings-modal-opened", initChannelStats, {
once: !1
});
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("channel-stats", initChannelStats, {
priority: 1,
shouldLoad: () => (() => {
try {
const path = location.pathname || "";
return path.startsWith("/@") || path.startsWith("/channel/") || path.startsWith("/c/");
} catch (e) {
return !1;
}
})() || (() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()
}) : "function" == typeof requestIdleCallback ? requestIdleCallback(initChannelStats, {
timeout: 2e3
}) : setTimeout(initChannelStats, 0);
})();

!(function() {
"use strict";
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
const renderTemplateClone = (container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
};
const t = window.YouTubeUtils.t;
const CONFIG = {
selectors: {
deleteButtons: 'div[class^="VfPpkd-Bz112c-"], button[aria-label*="Delete"], button[aria-label*="Удалить"], button[aria-label*="Remove"]',
menuButton: '[aria-haspopup="menu"]'
},
classes: {
checkbox: "comment-checkbox",
checkboxAnchor: "comment-checkbox-anchor",
checkboxFloating: "comment-checkbox-floating",
container: "comment-controls-container",
panel: "comment-controls-panel",
header: "comment-controls-header",
title: "comment-controls-title",
actions: "comment-controls-actions",
button: "comment-controls-button",
buttonDanger: "comment-controls-button--danger",
buttonPrimary: "comment-controls-button--primary",
buttonSuccess: "comment-controls-button--success",
close: "comment-controls-close",
deleteButton: "comment-controls-button-delete"
},
debounceDelay: 100,
deleteDelay: 200,
enabled: !0,
storageKey: "youtube_comment_manager_settings"
};
const state = {
observer: null,
isProcessing: !1,
settingsNavListenerKey: null,
panelCollapsed: !1,
initialized: !1,
settingsIntegrationInitialized: !1,
rootSubId: null,
navSubId: null
};
const COMMENT_HISTORY_URL = (() => {
let lang = "en";
try {
lang = window.YouTubeUtils.getLanguage();
} catch (e) {}
return `https://myactivity.google.com/page?hl=${encodeURIComponent(lang)}&utm_medium=web&utm_source=youtube&page=youtube_comments`;
})();
const isTrustedMyActivityHost = () => {
const host = String(location.hostname || "").toLowerCase();
return "myactivity.google.com" === host || host.endsWith(".myactivity.google.com");
};
const canRunCommentManagerRuntime = (() => {
try {
return isTrustedMyActivityHost();
} catch (e) {
return !1;
}
})();
const settings_load = () => {
try {
const saved = localStorage.getItem(CONFIG.storageKey);
saved && (CONFIG.enabled = JSON.parse(saved).enabled ?? !0);
} catch (e) {}
};
const debounce = window.YouTubeUtils.debounce;
const $ = sel => window.YouTubeUtils?.$(sel) || document.querySelector(sel);
const $$ = sel => window.YouTubeUtils?.$$(sel) || Array.from(document.querySelectorAll(sel));
const withErrorBoundary = (fn, context) => {
if (window.YouTubeErrorBoundary?.withErrorBoundary) {
return window.YouTubeErrorBoundary.withErrorBoundary(fn, "CommentManager");
}
return (...args) => {
try {
return fn(...args);
} catch (e) {
((context, error) => {
const errorObj = error instanceof Error ? error : new Error(String(error));
window.YouTubeErrorBoundary ? window.YouTubeErrorBoundary.logError(errorObj, {
context
}) : window.console.error(`[YouTube+][CommentManager] ${context}:`, error);
})(context, e);
return null;
}
};
};
const addCheckboxes = withErrorBoundary(() => {
if (!CONFIG.enabled || state.isProcessing) {
return;
}
const deleteButtons = $$(CONFIG.selectors.deleteButtons);
deleteButtons.forEach(button => {
const parent = button.parentNode;
if (button.closest(CONFIG.selectors.menuButton) || parent && parent.querySelector && parent.querySelector(`.${CONFIG.classes.checkbox}`)) {
return;
}
const commentElement = button.closest('[class*="comment"]') || button.closest('[role="article"]') || parent;
commentElement && commentElement instanceof Element && (commentElement.hasAttribute("data-comment-text") || commentElement.setAttribute("data-comment-text", (commentElement.textContent || "").toLowerCase()));
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.className = `${CONFIG.classes.checkbox} ytp-plus-settings-checkbox`;
checkbox.setAttribute("aria-label", t("selectComment"));
checkbox.addEventListener("change", updateDeleteButtonState);
checkbox.addEventListener("click", e => e.stopPropagation());
const dateElement = commentElement && commentElement.querySelector ? commentElement.querySelector('[class*="date"],[class*="time"],time,[title*="20"],[aria-label*="ago"]') : null;
if (dateElement && dateElement instanceof Element) {
dateElement.classList.add(CONFIG.classes.checkboxAnchor);
checkbox.classList.add(CONFIG.classes.checkboxFloating);
dateElement.appendChild(checkbox);
} else {
parent && parent.insertBefore && parent.insertBefore(checkbox, button);
}
});
}, "addCheckboxes");
const addControlButtons = withErrorBoundary(() => {
if (!CONFIG.enabled || $(`.${CONFIG.classes.container}`)) {
return;
}
const deleteButtons = $$(CONFIG.selectors.deleteButtons);
if (!deleteButtons.length) {
return;
}
const first = deleteButtons[0];
const container = first && first.parentNode && first.parentNode.parentNode;
if (!(container && container instanceof Element)) {
return;
}
const panel = document.createElement("div");
panel.className = `${CONFIG.classes.container} ${CONFIG.classes.panel} glass-panel`;
panel.setAttribute("role", "region");
panel.setAttribute("aria-label", t("commentManagerControls"));
const header = document.createElement("div");
header.className = CONFIG.classes.header;
const title = document.createElement("div");
title.className = CONFIG.classes.title;
title.textContent = t("commentManager");
const collapseButton = document.createElement("button");
collapseButton.className = `${CONFIG.classes.close} ytp-plus-settings-close`;
collapseButton.setAttribute("type", "button");
collapseButton.setAttribute("aria-expanded", String(!state.panelCollapsed));
collapseButton.setAttribute("aria-label", t("togglePanel"));
renderTemplateClone(collapseButton, '\n        <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>\n        </svg>\n      ');
const togglePanelState = collapsed => {
state.panelCollapsed = collapsed;
header.classList.toggle("is-collapsed", collapsed);
actions.classList.toggle("is-hidden", collapsed);
collapseButton.setAttribute("aria-expanded", String(!collapsed));
panel.classList.toggle("is-collapsed", collapsed);
};
collapseButton.addEventListener("click", () => {
state.panelCollapsed = !state.panelCollapsed;
togglePanelState(state.panelCollapsed);
});
header.append(title, collapseButton);
const actions = document.createElement("div");
actions.className = CONFIG.classes.actions;
const createActionButton = (label, className, onClick, options = {}) => {
const button = document.createElement("button");
button.type = "button";
button.textContent = label;
button.className = `${CONFIG.classes.button} ${className}`;
options.id && (button.id = options.id);
options.disabled && (button.disabled = !0);
button.addEventListener("click", onClick);
return button;
};
const deleteAllButton = createActionButton(t("deleteSelected"), `${CONFIG.classes.buttonDanger} ${CONFIG.classes.deleteButton}`, deleteSelectedComments, {
disabled: !0
});
const selectAllButton = createActionButton(t("selectAll"), CONFIG.classes.buttonPrimary, () => {
$$(`.${CONFIG.classes.checkbox}`).forEach(cb => cb.checked = !0);
updateDeleteButtonState();
});
const clearAllButton = createActionButton(t("clearAll"), CONFIG.classes.buttonSuccess, () => {
$$(`.${CONFIG.classes.checkbox}`).forEach(cb => cb.checked = !1);
updateDeleteButtonState();
});
actions.append(deleteAllButton, selectAllButton, clearAllButton);
togglePanelState(state.panelCollapsed);
panel.append(header, actions);
const refNode = deleteButtons[0] && deleteButtons[0].parentNode;
refNode && refNode.parentNode ? container.insertBefore(panel, refNode) : container.appendChild(panel);
}, "addControlButtons");
const updateDeleteButtonState = withErrorBoundary(() => {
const deleteAllButton = $(`.${CONFIG.classes.deleteButton}`);
if (!(deleteAllButton instanceof HTMLButtonElement)) {
return;
}
const hasChecked = Array.from($$(`.${CONFIG.classes.checkbox}`)).some(cb => cb.checked);
deleteAllButton.disabled = !hasChecked;
deleteAllButton.style.opacity = hasChecked ? "1" : "0.6";
}, "updateDeleteButtonState");
const deleteSelectedComments = withErrorBoundary(() => {
const checkedBoxes = Array.from($$(`.${CONFIG.classes.checkbox}`)).filter(cb => cb.checked);
if (checkedBoxes.length && confirm(`Delete ${checkedBoxes.length} comment(s)?`)) {
state.isProcessing = !0;
checkedBoxes.forEach((checkbox, index) => {
setTimeout(() => {
const deleteButton = checkbox.nextElementSibling || checkbox.parentElement?.querySelector(CONFIG.selectors.deleteButtons) || null;
deleteButton?.click();
}, index * CONFIG.deleteDelay);
});
setTimeout(() => state.isProcessing = !1, checkedBoxes.length * CONFIG.deleteDelay + 1e3);
}
}, "deleteSelectedComments");
const cleanup = withErrorBoundary(() => {
$$(`.${CONFIG.classes.checkbox}`).forEach(el => el.remove());
$(`.${CONFIG.classes.container}`)?.remove();
}, "cleanup");
const initializeScript = withErrorBoundary(() => {
if (CONFIG.enabled) {
addCheckboxes();
addControlButtons();
updateDeleteButtonState();
} else {
cleanup();
}
}, "initializeScript");
const addStyles = withErrorBoundary(() => {
if ($("#comment-delete-styles")) {
return;
}
const styles = `\n  .${CONFIG.classes.checkboxAnchor}{position:relative;display:inline-flex;align-items:center;gap:8px;width:auto;}\n        .${CONFIG.classes.checkboxFloating}{position:absolute;top:-4px;right:-32px;margin:0;}\n        /* Panel styled to match shorts feedback: glassmorphism, rounded corners, soft shadow */\n        .${CONFIG.classes.panel}{position:fixed;top:50%;right:24px;transform:translateY(-50%);display:flex;flex-direction:column;gap:14px;z-index:10000;padding:16px 18px;background:var(--yt-glass-bg);border:1.5px solid var(--yt-glass-border);border-radius:20px;box-shadow:var(--yt-glass-shadow);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);min-width:220px;max-width:300px;color:var(--yt-text-primary);transition:transform .22s cubic-bezier(.4,0,.2,1),opacity .22s,box-shadow .2s}\n        html:not([dark]) .${CONFIG.classes.panel}{background:var(--yt-glass-bg);}\n        .${CONFIG.classes.header}{display:flex;align-items:center;justify-content:space-between;gap:12px;}\n        .${CONFIG.classes.panel}.is-collapsed{padding:14px 18px;}\n        .${CONFIG.classes.panel}.is-collapsed .${CONFIG.classes.title}{font-weight:500;opacity:.85;}\n        .${CONFIG.classes.panel}.is-collapsed .${CONFIG.classes.close}{transform:rotate(45deg);}\n        .${CONFIG.classes.panel}.is-collapsed .${CONFIG.classes.actions}{display:none!important;}\n        .${CONFIG.classes.title}{font-size:15px;font-weight:600;letter-spacing:.3px;}\n        .${CONFIG.classes.close}{background:transparent;border:none;cursor:pointer;padding:6px;border-radius:12px;display:flex;align-items:center;justify-content:center;color:var(--yt-text-primary);transition:all .2s ease;}\n        .${CONFIG.classes.close}:hover{transform:rotate(90deg) scale(1.05);color:var(--yt-accent);}\n        .${CONFIG.classes.actions}{display:flex;flex-direction:column;gap:10px;}\n        .${CONFIG.classes.actions}.is-hidden{display:none!important;}\n        .${CONFIG.classes.button}{padding:12px 16px;border-radius:var(--yt-radius-md);border:1px solid var(--yt-glass-border);cursor:pointer;font-size:13px;font-weight:500;background:var(--yt-button-bg);color:var(--yt-text-primary);transition:all .2s ease;text-align:center;}\n        .${CONFIG.classes.button}:disabled{opacity:.5;cursor:not-allowed;}\n        .${CONFIG.classes.button}:not(:disabled):hover{transform:translateY(-1px);box-shadow:var(--yt-shadow);}\n        .${CONFIG.classes.buttonDanger}{background:var(--yt-danger-soft);border-color:var(--yt-danger-border);color:var(--yt-danger-text);}\n        .${CONFIG.classes.buttonPrimary}{background:var(--yt-primary-soft);border-color:var(--yt-primary-border);color:var(--yt-primary-text);}\n        .${CONFIG.classes.buttonSuccess}{background:var(--yt-success-soft);border-color:var(--yt-success);color:var(--yt-success);}\n        .${CONFIG.classes.buttonDanger}:not(:disabled):hover{background:var(--yt-danger-soft-hover);}\n        .${CONFIG.classes.buttonPrimary}:not(:disabled):hover{background:var(--yt-primary-soft-hover);}\n        .${CONFIG.classes.buttonSuccess}:not(:disabled):hover{background:var(--yt-success-soft-hover);}\n        @media(max-width:1280px){\n          .${CONFIG.classes.panel}{top:auto;bottom:24px;transform:none;right:16px;}\n        }\n        @media(max-width:768px){\n          .${CONFIG.classes.panel}{position:fixed;left:16px;right:16px;bottom:16px;top:auto;transform:none;max-width:none;}\n          .${CONFIG.classes.actions}{flex-direction:row;flex-wrap:wrap;}\n          .${CONFIG.classes.button}{flex:1;min-width:140px;}\n        }\n      `;
(cssText => {
try {
if (window.YouTubeUtils && YouTubeUtils.StyleManager) {
YouTubeUtils.StyleManager.add("comment-delete-styles", cssText);
return;
}
} catch (e) {}
try {
if (document.getElementById("comment-delete-styles")) {
return;
}
const style = document.createElement("style");
style.id = "comment-delete-styles";
style.textContent = cssText;
(document.head || document.documentElement).appendChild(style);
} catch (e) {}
})(styles);
}, "addStyles");
const addCommentManagerSettings = withErrorBoundary(() => {
const experimentalSection = $('.ytp-plus-settings-section[data-section="experimental"]');
if (!experimentalSection) {
return;
}
const existing = $(".comment-manager-settings-item");
if (existing) {
try {
experimentalSection.appendChild(existing);
} catch (e) {}
return;
}
const settingsItem = document.createElement("div");
settingsItem.className = "ytp-plus-settings-item comment-manager-settings-item";
renderTemplateClone(settingsItem, `\n        <div>\n          <label class="ytp-plus-settings-item-label">${t("commentManagement")}</label>\n          <div class="ytp-plus-settings-item-description">${t("bulkDeleteDescription")}</div>\n        </div>\n        <button class="ytp-plus-button" id="open-comment-history-page" style="margin:0 0 0 30px;padding:12px 16px;font-size:13px;background:var(--yt-button-bg);border:1px solid var(--yt-glass-border)">\n          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">\n              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>\n              <polyline points="15,3 21,3 21,9"/>\n              <line x1="10" y1="14" x2="21" y2="3"/>\n            </svg>\n        </button>\n      `);
experimentalSection.appendChild(settingsItem);
$("#open-comment-history-page")?.addEventListener("click", () => {
window.open(COMMENT_HISTORY_URL, "_blank");
});
}, "addCommentManagerSettings");
const ensureCommentManagerSettings = (attempt = 0) => {
const experimentalSection = $('.ytp-plus-settings-section[data-section="experimental"]');
if (experimentalSection) {
addCommentManagerSettings();
!$(".comment-manager-settings-item") && attempt < 20 && setTimeout(() => ensureCommentManagerSettings(attempt + 1), 80);
} else {
attempt < 20 && setTimeout(() => ensureCommentManagerSettings(attempt + 1), 80);
}
};
const initSettingsIntegration = () => {
if (state.settingsIntegrationInitialized) {
return;
}
state.settingsIntegrationInitialized = !0;
document.addEventListener("youtube-plus-settings-modal-opened", () => {
setTimeout(() => ensureCommentManagerSettings(), 100);
});
const handleExperimentalNavClick = e => {
const target = e.target;
const navItem = target?.closest?.(".ytp-plus-settings-nav-item");
"experimental" === navItem?.dataset?.section && setTimeout(() => ensureCommentManagerSettings(), 50);
};
state.settingsNavListenerKey || (state.settingsNavListenerKey = ((target, event, handler, options) => {
try {
if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
return YouTubeUtils.cleanupManager.registerListener(target, event, handler, options);
}
} catch (e) {}
try {
target.addEventListener(event, handler, options);
} catch (e) {}
return null;
})(document, "click", handleExperimentalNavClick, {
passive: !0,
capture: !0
}));
};
const init = withErrorBoundary(() => {
if (state.initialized) {
return;
}
settings_load();
addStyles();
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.subscribeRoot ? state.rootSubId || (state.rootSubId = coordinator.subscribeRoot("comment-manager-runtime", debounce(initializeScript, CONFIG.debounceDelay), {
selector: "#comments, #content"
})) : window.console.warn("[YouTube+][CommentManager] MutationCoordinator unavailable");
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", initializeScript) : initializeScript();
initSettingsIntegration();
}, "init");
const isRelevantRoute = () => (() => {
try {
if (!isTrustedMyActivityHost()) {
return !1;
}
const params = new URLSearchParams(location.search || "");
return "youtube_comments" === params.get("page");
} catch (e) {
return !1;
}
})();
const scheduleInit = () => {
!state.initialized && isRelevantRoute() && requestIdleCallback(() => {
if (!state.initialized && isRelevantRoute()) {
init();
state.initialized = !0;
}
}, {
timeout: 2e3
});
};
initSettingsIntegration();
if (canRunCommentManagerRuntime) {
const coordinator = window.YouTubeMutationCoordinator;
coordinator?.subscribeRoot ? state.navSubId || (state.navSubId = coordinator.subscribeRoot("comment-manager-navigation", debounce(() => {
!state.initialized && isRelevantRoute() && scheduleInit();
if (state.initialized && state.navSubId) {
coordinator.unsubscribe(state.navSubId);
state.navSubId = null;
}
}, 300), {
selector: "body"
})) : window.console.warn("[YouTube+][CommentManager] MutationCoordinator unavailable");
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("comment", scheduleInit, {
priority: 1,
shouldLoad: isRelevantRoute
}) : scheduleInit();
}
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout;
const Y = window.YouTubeUtils || {};
const t = Y.t || (key => key || "");
function mk(tag, props = {}, children = []) {
const el = document.createElement(tag);
Object.entries(props).forEach(([k, v]) => {
"class" === k ? el.className = v : "html" === k ? window.YouTubeUtils.setSafeHTML(el, v, !0) : k.startsWith("on") && "function" == typeof v ? el.addEventListener(k.substring(2).toLowerCase(), v) : el.setAttribute(k, String(v));
});
children.forEach(c => el.appendChild("string" == typeof c ? document.createTextNode(c) : c));
return el;
}
function sanitizeHTML(html) {
if (window.YouTubeSafeDOM?.sanitizeHTML) {
return window.YouTubeSafeDOM.sanitizeHTML(html);
}
if (Y?.sanitizeHTML && "function" == typeof Y.sanitizeHTML) {
return Y.sanitizeHTML(html);
}
if ("string" != typeof html) {
return "";
}
const div = document.createElement("div");
div.textContent = html;
return div.innerHTML;
}
function isValidEmail(email) {
if (!email || "string" != typeof email) {
return !1;
}
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && email.length <= 254;
}
function validateTitle(title) {
return title && "string" == typeof title ? sanitizeHTML(title.trim().substring(0, 200)) : "";
}
function validateDescription(description) {
return description && "string" == typeof description ? sanitizeHTML(description.trim().substring(0, 5e3)) : "";
}
function getDebugInfo() {
try {
const debug = {
version: window.YouTubePlusDebug?.version || "unknown",
userAgent: navigator?.userAgent || "unknown",
url: location?.href || "unknown",
language: document.documentElement?.lang || navigator?.language || "unknown",
settings: "object" == typeof Y?.SettingsManager ? Y.SettingsManager.load() : null
};
return debug;
} catch (err) {
Y && "function" == typeof Y.logError && Y.logError("Report", "Failed to collect debug info", err);
return {
version: "unknown",
userAgent: "unknown",
url: "unknown",
language: "unknown",
settings: null,
error: "Failed to collect debug info"
};
}
}
function buildIssuePayload({type, title, description, email, includeDebug}) {
const debug = includeDebug ? getDebugInfo() : null;
const lines = [];
const typeLabel = t("bug" === type ? "typeBug" : "feature" === type ? "typeFeature" : "typeOther");
lines.push(`**Type:** ${typeLabel}`);
email && lines.push(`**Reporter email (optional):** ${email}`);
lines.push("\n**Description:**\n");
lines.push(description || "(no description)");
if (debug) {
lines.push("\n---\n**Debug info**\n");
lines.push("```json");
try {
lines.push(JSON.stringify(debug, null, 2));
} catch (err) {
Y && "function" == typeof Y.logError && Y.logError("Report", "Failed to stringify debug info", err);
const minimalDebug = {
version: debug.version || "unknown",
userAgent: debug.userAgent || "unknown",
url: debug.url || "unknown"
};
try {
lines.push(JSON.stringify(minimalDebug, null, 2));
} catch (e) {
lines.push('{ "error": "Failed to stringify debug info" }');
}
}
lines.push("```");
lines.push("\n_Please do not include sensitive personal data._");
}
const body = lines.join("\n");
const issueTitle = `${"bug" === type ? "[Bug]" : "feature" === type ? "[Feature]" : "[Report]"} ${title || ""}`.trim();
return {
title: issueTitle,
body
};
}
try {
window.youtubePlusReport = window.youtubePlusReport || {};
window.youtubePlusReport.render = function renderReportSection(modal) {
if (!modal || !modal.querySelector) {
return;
}
const section = modal.querySelector('.ytp-plus-settings-section[data-section="report"]');
if (!section) {
return;
}
section.replaceChildren();
const form = mk("div", {
style: "display:flex;flex-direction:column;gap:var(--yt-space-sm);margin-top:var(--yt-space-md);"
});
const typeSelect = mk("select", {
style: "display:none;"
}, []);
const typeOptions = [ {
v: "bug",
l: t("typeBug")
}, {
v: "feature",
l: t("typeFeature")
}, {
v: "other",
l: t("typeOther")
} ];
typeOptions.forEach(opt => {
const o = mk("option", {
value: opt.v
}, [ opt.l ]);
typeSelect.appendChild(o);
});
const typeDropdown = mk("div", {
class: "glass-dropdown",
id: "report-type-dropdown",
tabindex: "0",
role: "listbox",
"aria-expanded": "false"
});
const defaultLabel = typeOptions[0].l;
const toggleBtn = mk("button", {
class: "glass-dropdown__toggle",
type: "button",
"aria-haspopup": "listbox"
}, [ mk("span", {
class: "glass-dropdown__label"
}, [ defaultLabel ]) ]);
toggleBtn.appendChild(mk("svg", {
class: "glass-dropdown__chev",
width: "12",
height: "12",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2"
}, [ mk("polyline", {
points: "6 9 12 15 18 9"
}, []) ]));
const listEl = mk("ul", {
class: "glass-dropdown__list",
role: "presentation"
}, []);
typeOptions.forEach((opt, i) => {
const li = mk("li", {
class: "glass-dropdown__item",
"data-value": opt.v,
role: "option"
}, [ opt.l ]);
0 === i && li.setAttribute("aria-selected", "true");
listEl.appendChild(li);
});
typeDropdown.appendChild(toggleBtn);
typeDropdown.appendChild(listEl);
const inputStyle = "padding:var(--yt-space-sm);border-radius:var(--yt-radius-sm);background:var(--yt-input-bg);color:var(--yt-text-primary);border:1px solid var(--yt-glass-border);backdrop-filter:var(--yt-glass-blur-light);-webkit-backdrop-filter:var(--yt-glass-blur-light);font-size:14px;transition:var(--yt-transition);box-sizing:border-box;";
const titleInput = mk("input", {
placeholder: t("shortTitle"),
style: inputStyle
});
const emailInput = mk("input", {
placeholder: t("emailOptional"),
type: "email",
style: inputStyle
});
const descInput = mk("textarea", {
placeholder: t("descriptionPlaceholder"),
rows: 6,
style: inputStyle + "resize:vertical;font-family:inherit;"
});
const debugCheckboxInput = mk("input", {
type: "checkbox",
class: "ytp-plus-settings-checkbox"
});
const includeDebug = mk("label", {
style: "font-size:13px;display:flex;gap:var(--yt-space-sm);align-items:center;color:var(--yt-text-primary);cursor:pointer;align-self:center;"
}, [ debugCheckboxInput, " " + t("includeDebug") ]);
const actions = mk("div", {
style: "display:flex;gap:var(--yt-space-sm);margin-top:var(--yt-space-sm);flex-wrap:wrap;justify-content:center;"
});
const submitBtn = mk("button", {
class: "glass-button"
}, [ t("openGitHub") ]);
const copyBtn = mk("button", {
class: "glass-button"
}, [ t("copyReport") ]);
const emailBtn = mk("button", {
class: "glass-button"
}, [ t("prepareEmail") ]);
actions.appendChild(submitBtn);
actions.appendChild(copyBtn);
actions.appendChild(emailBtn);
form.appendChild(typeSelect);
form.appendChild(typeDropdown);
form.appendChild(titleInput);
form.appendChild(emailInput);
form.appendChild(descInput);
form.appendChild(includeDebug);
const debugPreview = mk("div", {
class: "glass-card",
style: "overflow:auto;max-height:240px;font-size:11px;display:none;margin-top:var(--yt-space-sm);padding:8px;box-sizing:border-box;"
}, []);
form.appendChild(debugPreview);
form.appendChild(actions);
const privacy = mk("div", {
class: "ytp-plus-settings-item-description",
style: "margin-top:var(--yt-space-sm);font-size:12px;color:var(--yt-text-secondary);"
}, [ t("privacy") ]);
section.appendChild(form);
section.appendChild(privacy);
try {
window.YouTubePlusDesignSystem?.initGlassDropdown?.({
dropdown: typeDropdown,
hiddenSelect: typeSelect
});
} catch (err) {
Y && "function" == typeof Y.logError && Y.logError("Report", "initReportTypeDropdown", err);
}
debugCheckboxInput.addEventListener("change", function updateDebugPreview() {
try {
if (debugCheckboxInput.checked) {
const d = getDebugInfo();
debugPreview.replaceChildren();
const header = mk("div", {
style: "display:flex;flex-direction:column;gap:6px;margin-bottom:6px;"
}, []);
header.appendChild(mk("div", {}, [ "Version: ", mk("strong", {}, [ String(d.version || "unknown") ]) ]));
header.appendChild(mk("div", {}, [ "User agent: ", mk("code", {
style: "font-size:11px;color:var(--yt-text-secondary);"
}, [ String(d.userAgent || "") ]) ]));
const urlStr = String(d.url || "unknown");
let urlEl = mk("span", {}, [ urlStr ]);
try {
/^https?:\/\//i.test(urlStr) && (urlEl = mk("a", {
href: urlStr,
target: "_blank",
rel: "noopener noreferrer",
style: "color:var(--yt-accent);word-break:break-all;"
}, [ urlStr ]));
} catch (e) {
Y && "function" == typeof Y.logError && Y.logError("Report", "URL link creation failed", e);
urlEl = mk("span", {}, [ String(urlStr) ]);
}
header.appendChild(mk("div", {}, [ "URL: ", urlEl ]));
header.appendChild(mk("div", {}, [ "Language: ", mk("code", {}, [ String(d.language || "") ]) ]));
debugPreview.appendChild(header);
if (d.settings) {
const settingsDetails = mk("details", {}, [ mk("summary", {}, [ "Settings" ]) ]);
settingsDetails.appendChild(mk("pre", {
style: "white-space:pre-wrap;margin:6px 0 0 0;font-size:11px;"
}, [ JSON.stringify(d.settings, null, 2) ]));
debugPreview.appendChild(settingsDetails);
}
const fullDetails = mk("details", {}, [ mk("summary", {}, [ "Full debug JSON" ]) ]);
fullDetails.appendChild(mk("pre", {
style: "white-space:pre-wrap;margin:6px 0 0 0;font-size:11px;"
}, [ JSON.stringify(d, null, 2) ]));
debugPreview.appendChild(fullDetails);
debugPreview.style.display = "block";
} else {
debugPreview.replaceChildren();
debugPreview.style.display = "none";
}
} catch (err) {
Y && "function" == typeof Y.logError && Y.logError("Report", "updateDebugPreview failed", err);
}
});
function gather() {
const type = typeSelect.value;
const rawTitle = titleInput.value.trim();
const rawDescription = descInput.value.trim();
const rawEmail = emailInput.value.trim();
const includeDebugValue = includeDebug.querySelector("input").checked;
const errors = [];
rawTitle ? rawTitle.length < 5 && errors.push(t("titleMin")) : errors.push(t("titleRequired"));
rawDescription ? rawDescription.length < 10 && errors.push(t("descMin")) : errors.push(t("descRequired"));
rawEmail && !isValidEmail(rawEmail) && errors.push(t("invalidEmail"));
return {
type,
title: validateTitle(rawTitle),
description: validateDescription(rawDescription),
email: rawEmail && isValidEmail(rawEmail) ? rawEmail : "",
includeDebug: includeDebugValue,
errors
};
}
submitBtn.addEventListener("click", e => {
e.preventDefault();
if (!submitBtn.disabled) {
try {
const data = gather();
if (data.errors && data.errors.length > 0) {
const errorMsg = t("fixErrorsPrefix") + data.errors.join("\n• ");
Y.NotificationManager && "function" == typeof Y.NotificationManager.show ? Y.NotificationManager.show(errorMsg, {
duration: 4e3,
type: "error"
}) : window.console.warn("[Report] Validation errors:", data.errors);
return;
}
const originalText = submitBtn.textContent;
submitBtn.disabled = !0;
submitBtn.textContent = t("opening");
submitBtn.style.opacity = "0.6";
const payload = buildIssuePayload(data);
!(function openGitHubIssue(payload, type) {
try {
const repoOwner = "diorhc";
const repo = "YTP";
const url = "feature" === type ? `https://github.com/${repoOwner}/${repo}/discussions/new?category=ideas&title=${encodeURIComponent(payload.title)}&body=${encodeURIComponent(payload.body)}` : `https://github.com/${repoOwner}/${repo}/issues/new?title=${encodeURIComponent(payload.title)}&body=${encodeURIComponent(payload.body)}`;
window.open(url, "_blank");
} catch (err) {
Y && "function" == typeof Y.logError && Y.logError("Report", "Failed to open GitHub issue", err);
throw err;
}
})(payload, "feature" === data.type ? "feature" : "other" === data.type ? "other" : "bug");
Y.NotificationManager && "function" == typeof Y.NotificationManager.show && Y.NotificationManager.show(t("openingGithubNotification"), {
duration: 2500
});
setTimeout_(() => {
submitBtn.disabled = !1;
submitBtn.textContent = originalText;
submitBtn.style.opacity = "1";
}, 2e3);
} catch (err) {
Y.logError && Y.logError("Report", "Failed to open GitHub issue", err);
Y.NotificationManager && "function" == typeof Y.NotificationManager.show && Y.NotificationManager.show(t("failedOpenGithub"), {
duration: 3e3,
type: "error"
});
submitBtn.disabled = !1;
submitBtn.textContent = t("openGitHub");
submitBtn.style.opacity = "1";
}
}
});
copyBtn.addEventListener("click", e => {
e.preventDefault();
if (!copyBtn.disabled) {
try {
const data = gather();
if (data.errors && data.errors.length > 0) {
const errorMsg = t("fixErrorsPrefix") + data.errors.join("\n• ");
Y.NotificationManager && "function" == typeof Y.NotificationManager.show ? Y.NotificationManager.show(errorMsg, {
duration: 4e3,
type: "error"
}) : window.console.warn("[Report] Validation errors:", data.errors);
return;
}
const originalText = copyBtn.textContent;
copyBtn.disabled = !0;
copyBtn.textContent = t("copying");
copyBtn.style.opacity = "0.6";
const payload = buildIssuePayload(data);
const full = `Title: ${payload.title}\n\n${payload.body}`;
(function copyToClipboard(text) {
return navigator.clipboard && navigator.clipboard.writeText ? navigator.clipboard.writeText(text) : new Promise((resolve, reject) => {
const ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.left = "-9999px";
ta.style.opacity = "0";
document.body.appendChild(ta);
try {
ta.select();
ta.setSelectionRange(0, text.length);
const success = document.execCommand("copy");
document.body.removeChild(ta);
success ? resolve() : reject(new Error("execCommand failed"));
} catch (err) {
document.body.removeChild(ta);
reject(err);
}
});
})(full).then(() => {
Y.NotificationManager && "function" == typeof Y.NotificationManager.show && Y.NotificationManager.show(t("reportCopied"), {
duration: 2e3
});
copyBtn.textContent = t("copied");
copyBtn.style.opacity = "1";
setTimeout_(() => {
copyBtn.disabled = !1;
copyBtn.textContent = originalText;
}, 2e3);
}).catch(err => {
Y && "function" == typeof Y.logError && Y.logError("Report", "copy failed", err);
Y && Y.NotificationManager && "function" == typeof Y.NotificationManager.show ? Y.NotificationManager.show(t("copyFailed"), {
duration: 3e3,
type: "error"
}) : window.console.warn("Copy failed; please copy manually", err);
copyBtn.disabled = !1;
copyBtn.textContent = originalText;
copyBtn.style.opacity = "1";
});
} catch (err) {
Y.logError && Y.logError("Report", "Failed to copy report", err);
copyBtn.disabled = !1;
copyBtn.textContent = t("copyReport");
copyBtn.style.opacity = "1";
}
}
});
emailBtn.addEventListener("click", e => {
e.preventDefault();
if (!emailBtn.disabled) {
try {
const data = gather();
if (data.errors && data.errors.length > 0) {
const errorMsg = t("fixErrorsPrefix") + data.errors.join("\n• ");
Y.NotificationManager && "function" == typeof Y.NotificationManager.show ? Y.NotificationManager.show(errorMsg, {
duration: 4e3,
type: "error"
}) : window.console.warn("[Report] Validation errors:", data.errors);
return;
}
const originalText = emailBtn.textContent;
emailBtn.disabled = !0;
emailBtn.textContent = t("opening");
emailBtn.style.opacity = "0.6";
const payload = buildIssuePayload(data);
const subject = payload.title;
const mailto = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(payload.body)}`;
window.location.href = mailto;
setTimeout_(() => {
emailBtn.disabled = !1;
emailBtn.textContent = originalText;
emailBtn.style.opacity = "1";
}, 2e3);
} catch (err) {
Y.logError && Y.logError("Report", "Failed to prepare email", err);
emailBtn.disabled = !1;
emailBtn.textContent = t("prepareEmail");
emailBtn.style.opacity = "1";
}
}
});
};
} catch (e) {
Y.logError && Y.logError("Report", "Failed to attach report module to window", e);
}
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytplusCreateHTML || (s => s);
const renderTemplateClone = (container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
};
const REFRESH_ICON_PATHS = [ "M21.5 2v6h-6M2.5 22v-6h6", "M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1" ];
const setManualCheckButtonContent = (button, label, spinning) => {
button instanceof HTMLElement && button.replaceChildren((spinning => {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "16");
svg.setAttribute("height", "16");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "currentColor");
svg.setAttribute("stroke-width", "2");
svg.setAttribute("stroke-linecap", "round");
svg.setAttribute("stroke-linejoin", "round");
svg.style.display = "inline-block";
svg.style.flexShrink = "0";
svg.style.verticalAlign = "middle";
spinning && (svg.style.animation = "spin .8s linear infinite");
for (const d of REFRESH_ICON_PATHS) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", d);
svg.appendChild(path);
}
return svg;
})(spinning), document.createTextNode(label));
};
const t = window.YouTubeUtils.t;
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
const getLanguage = () => window.YouTubeUtils.getLanguage();
const UPDATE_CONFIG = {
enabled: !0,
checkInterval: 864e5,
updateUrl: "https://update.greasyfork.org/scripts/537017/YouTube%20%2B.meta.js",
currentVersion: "2.4.5",
storageKey: "youtube_plus_update_check",
notificationDuration: 8e3,
autoInstallUrl: "https://update.greasyfork.org/scripts/537017/YouTube%20%2B.user.js",
autoInstallOnCheck: !1,
showNotificationIcon: !1
};
const windowRef = "undefined" == typeof window ? null : window;
const GM_namespace = windowRef?.GM || null;
const GM_info_safe = windowRef?.GM_info || null;
const GM_openInTab_safe = (() => {
if (windowRef) {
if ("function" == typeof windowRef.GM_openInTab) {
return windowRef.GM_openInTab.bind(windowRef);
}
if (GM_namespace?.openInTab) {
return GM_namespace.openInTab.bind(GM_namespace);
}
}
return null;
})();
GM_info_safe?.script?.version && (UPDATE_CONFIG.currentVersion = GM_info_safe.script.version);
const updateState = {
lastCheck: 0,
lastVersion: UPDATE_CONFIG.currentVersion,
updateAvailable: !1,
checkInProgress: !1,
updateDetails: null
};
const isVersionString = value => {
const text = String(value || "").trim();
if (!text) {
return !1;
}
const parts = text.split(".");
return !(parts.length < 1 || parts.length > 3) && parts.every(part => part.length > 0 && Array.from(part).every(ch => ch >= "0" && ch <= "9"));
};
const extractMetadataField = (text, field) => {
const prefix = `@${field} `;
const lines = String(text || "").replace(/\r/g, "").split("\n");
for (const line of lines) {
const trimmed = line.trimStart();
if (trimmed.startsWith(prefix)) {
return trimmed.slice(prefix.length).trim();
}
}
return "";
};
function pluralizeTime(n, unit) {
const lang = getLanguage();
const num = Math.abs(Number(n)) || 0;
if ("ru" === lang) {
const forms = (function getRussianForms(unit) {
return {
day: [ "день", "дня", "дней" ],
hour: [ "час", "часа", "часов" ],
minute: [ "минута", "минуты", "минут" ]
}[unit] || [ "дней", "дней", "дней" ];
})(unit);
const idx = (function getRussianPluralIndex(num) {
const mod10 = num % 10;
const mod100 = num % 100;
return 1 === mod10 && 11 !== mod100 ? 0 : mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14) ? 1 : 2;
})(num);
return `${num} ${forms[idx]}`;
}
const enForms = (function getEnglishForms(unit) {
return {
day: [ "day", "days" ],
hour: [ "hour", "hours" ],
minute: [ "minute", "minutes" ]
}[unit] || [ "minutes", "minutes" ];
})(unit);
return `${num} ${1 === num ? enForms[0] : enForms[1]}`;
}
const utils_loadSettings = () => {
try {
const saved = localStorage.getItem(UPDATE_CONFIG.storageKey);
if (!saved) {
return;
}
const parsed = JSON.parse(saved);
if ("object" != typeof parsed || null === parsed) {
window.console.error("[YouTube+][Update]", "Invalid settings structure");
return;
}
"number" == typeof parsed.lastCheck && parsed.lastCheck >= 0 && (updateState.lastCheck = parsed.lastCheck);
if ("string" == typeof parsed.lastVersion) {
const ver = parsed.lastVersion.replace(/^v/i, "").trim();
isVersionString(ver) && (updateState.lastVersion = ver);
}
"boolean" == typeof parsed.updateAvailable && (updateState.updateAvailable = parsed.updateAvailable);
parsed.updateDetails && "object" == typeof parsed.updateDetails && "string" == typeof parsed.updateDetails.version && isVersionString(parsed.updateDetails.version) && (updateState.updateDetails = parsed.updateDetails);
} catch (e) {
window.console.error("[YouTube+][Update]", "Failed to load update settings:", e);
}
}, utils_saveSettings = () => {
try {
const dataToSave = {
lastCheck: updateState.lastCheck,
lastVersion: updateState.lastVersion,
updateAvailable: updateState.updateAvailable,
updateDetails: updateState.updateDetails
};
localStorage.setItem(UPDATE_CONFIG.storageKey, JSON.stringify(dataToSave));
} catch (e) {
window.console.error("[YouTube+][Update]", "Failed to save update settings:", e);
}
}, utils_compareVersions = (v1, v2) => {
if ("string" != typeof v1 || "string" != typeof v2) {
window.console.error("[YouTube+][Update]", "Invalid version format - must be strings");
return 0;
}
const normalize = v => v.replace(/[^\d.]/g, "").split(".").map(n => parseInt(n, 10) || 0);
const [parts1, parts2] = [ normalize(v1), normalize(v2) ];
const maxLength = Math.max(parts1.length, parts2.length);
for (let i = 0; i < maxLength; i++) {
const diff = (parts1[i] || 0) - (parts2[i] || 0);
if (0 !== diff) {
return diff;
}
}
return 0;
}, utils_parseMetadata = text => {
if (null == text || "" === text) {
return {
version: null,
description: "",
downloadUrl: UPDATE_CONFIG.autoInstallUrl
};
}
if ("string" != typeof text || text.length > 1e5) {
window.console.warn("[YouTube+][Update]", "Invalid metadata text");
return {
version: null,
description: "",
downloadUrl: UPDATE_CONFIG.autoInstallUrl
};
}
let version = extractMetadataField(text, "version");
const description = extractMetadataField(text, "description");
const downloadUrl = extractMetadataField(text, "downloadURL") || UPDATE_CONFIG.autoInstallUrl;
if (version) {
version = version.replace(/^v/i, "").trim();
if (!isVersionString(version)) {
window.console.error("[YouTube+][Update]", "Invalid version format in metadata:", version);
return {
version: null,
description: "",
downloadUrl: UPDATE_CONFIG.autoInstallUrl
};
}
}
return {
version,
description: description.substring(0, 500),
downloadUrl
};
}, utils_formatTimeAgo = timestamp => {
if (!timestamp) {
return t("never");
}
const diffMs = Date.now() - timestamp;
const diffDays = Math.floor(diffMs / 864e5);
const diffHours = Math.floor(diffMs / 36e5);
const diffMinutes = Math.floor(diffMs / 6e4);
return diffDays > 0 ? pluralizeTime(diffDays, "day") : diffHours > 0 ? pluralizeTime(diffHours, "hour") : diffMinutes > 0 ? pluralizeTime(diffMinutes, "minute") : t("justNow");
}, utils_showNotification = (text, type = "info", duration = 3e3) => {
try {
YouTubeUtils.NotificationManager.show(text, {
type,
duration
});
} catch (error) {
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug(`[YouTube+] ${type.toUpperCase()}:`, text, error);
}
};
const markUpdateDismissed = details => {
if (details?.version && "string" == typeof details.version) {
try {
sessionStorage.setItem("update_dismissed", details.version);
} catch (err) {
window.console.error("[YouTube+][Update]", "Failed to persist dismissal state:", err);
}
}
};
const installUpdate = (details = updateState.updateDetails) => {
const downloadUrl = details?.downloadUrl || UPDATE_CONFIG.autoInstallUrl;
const validation = (downloadUrl => {
if (!downloadUrl || "string" != typeof downloadUrl) {
return {
valid: !1,
error: "Invalid download URL for installation"
};
}
try {
const parsedUrl = new URL(downloadUrl);
const allowedDomains = [ "update.greasyfork.org", "greasyfork.org" ];
return "https:" !== parsedUrl.protocol ? {
valid: !1,
error: "Only HTTPS URLs allowed for updates"
} : allowedDomains.includes(parsedUrl.hostname) ? {
valid: !0,
error: null
} : {
valid: !1,
error: `Update URL domain not in allowlist: ${parsedUrl.hostname}`
};
} catch (error) {
return {
valid: !1,
error: `Invalid URL format: ${error.message}`
};
}
})(downloadUrl);
if (!validation.valid) {
window.console.error("[YouTube+][Update]", validation.error);
return !1;
}
const success = (url => {
if (GM_openInTab_safe) {
try {
GM_openInTab_safe(url, {
active: !0,
insert: !0,
setParent: !0
});
return !0;
} catch (gmError) {
window.console.error("[YouTube+] GM_openInTab update install failed:", gmError);
}
}
try {
const popup = window.open(url, "_blank", "noopener");
if (popup) {
return !0;
}
} catch (popupError) {
window.console.error("[YouTube+] window.open update install failed:", popupError);
}
try {
window.location.assign(url);
return !0;
} catch (navigationError) {
window.console.error("[YouTube+] Navigation to update URL failed:", navigationError);
}
return !1;
})(downloadUrl);
success && markUpdateDismissed(details);
return success;
};
const fetchUpdateMetadata = async (url = UPDATE_CONFIG.updateUrl) => (async requestUrl => {
if ("undefined" != typeof GM_xmlhttpRequest) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout_(() => reject(new Error("Update check timeout")), 1e4);
GM_xmlhttpRequest({
method: "GET",
url: requestUrl,
timeout: 1e4,
headers: {
Accept: "text/plain",
"User-Agent": "YouTube+ UpdateChecker"
},
onload: response => {
clearTimeout(timeoutId);
response.status >= 200 && response.status < 300 ? resolve(response.responseText) : reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
},
onerror: e => {
clearTimeout(timeoutId);
reject(new Error(`Network error: ${e}`));
},
ontimeout: () => {
clearTimeout(timeoutId);
reject(new Error("Update check timeout"));
}
});
});
}
const controller = new AbortController;
const timeoutId = setTimeout_(() => controller.abort(), 1e4);
try {
const res = await fetch(requestUrl, {
method: "GET",
cache: "no-cache",
signal: controller.signal,
headers: {
Accept: "text/plain",
"User-Agent": "YouTube+ UpdateChecker"
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return await res.text();
} finally {
clearTimeout(timeoutId);
}
})(url);
const handleUpdateResult = (updateDetails, force) => {
const shouldShowNotification = updateState.updateAvailable && (force || sessionStorage.getItem("update_dismissed") !== updateDetails.version);
if (shouldShowNotification) {
(updateDetails => {
const iconHtml = UPDATE_CONFIG.showNotificationIcon ? '<div style="background: var(--yt-glass-bg);\n                        border-radius: var(--yt-radius-xs); padding: 10px; flex-shrink: 0; border: 1px solid var(--yt-glass-border);\n                        backdrop-filter: var(--yt-glass-blur); -webkit-backdrop-filter: var(--yt-glass-blur);">\n            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n              <path d="M21 12c0 1-1 2-1 2s-1-1-1-2 1-2 1-2 1 1 1 2z"/>\n              <path d="m21 12-5-5v3H8v4h8v3l5-5z"/>\n            </svg>\n          </div>' : "";
const notification = document.createElement("div");
notification.className = "youtube-enhancer-notification update-notification";
notification.setAttribute("role", "alertdialog");
notification.setAttribute("aria-label", t("updateAvailableTitle") || "Update available");
notification.style.cssText = "\n      z-index: 10001; max-width: 360px;\n      background: var(--yt-notification-bg); padding: 16px 18px; border-radius: var(--yt-radius-lg);\n      color: var(--yt-text-primary);\n      box-shadow: var(--yt-shadow);\n      border: 1px solid var(--yt-glass-border);\n      -webkit-backdrop-filter: var(--yt-glass-blur);\n      backdrop-filter: var(--yt-glass-blur);\n      animation: slideInFromBottom 0.4s ease-out;\n    ";
renderTemplateClone(notification, `\n        <div style="position: relative; display: flex; align-items: flex-start; gap: 12px;">\n            ${iconHtml}\n          <div style="flex: 1; min-width: 0;">\n            <div style="font-weight: 600; font-size: 15px; margin-bottom: 4px;">${t("updateAvailableTitle")}</div>\n            <div style="font-size: 13px; opacity: 0.9; margin-bottom: 8px;">\n              ${t("version")} ${updateDetails.version}\n            </div>\n            ${updateDetails.changelog || updateDetails.description ? (function() {
const header = t("changelogHeader");
const raw = updateDetails.changelog && updateDetails.changelog.length > 0 ? updateDetails.changelog : updateDetails.description || "";
const text = (s => String(s).replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]*>?/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#039;/g, "'").trim())(raw);
const lines = text.split(/\n+/).map(l => l.trim()).filter(Boolean);
const listHtml = lines.map(l => `<div style="font-size:12px; opacity:0.85; margin-bottom:6px;">${l}</div>`).join("");
return `<div style="font-size:12px; font-weight:600; opacity:0.95; margin-bottom:6px;">${header}</div><div style="font-size:12px; line-height:1.4; max-height:120px; overflow-y:auto; padding:8px; background: var(--yt-overlay-deep); border-radius:6px; border:1px solid var(--yt-surface-overlay-border); white-space:normal;">${listHtml}</div>`;
})() : `<div style="font-size: 12px); opacity: 0.85; margin-bottom: 12px;">${t("newFeatures")}</div>`}\n            <div style="display: flex; gap: 8px;">\n              <button id="update-install-btn" type="button" style="\n                background: var(--yt-accent); color: white; border: none;\n                padding: 8px 16px; border-radius: var(--yt-radius-xs); cursor: pointer;\n                font-size: 13px; font-weight: 700; transition: transform 0.15s ease;\n                box-shadow: 0 6px 18px var(--yt-danger-ghost);\n                backdrop-filter: var(--yt-glass-blur);\n              ">${t("installUpdate")}</button>\n              <button id="update-dismiss-btn" type="button" style="\n                background: var(--yt-button-bg); color: var(--yt-text-primary);\n                border: 1px solid var(--yt-glass-border); padding: 8px 12px;\n                border-radius: var(--yt-radius-xs); cursor: pointer; font-size: 13px; transition: all 0.12s ease;\n              ">${t("later")}</button>\n            </div>\n          </div>\n          <button id="update-close-btn" aria-label="${t("dismiss")}" style="\n            position: absolute; top: -8px; right: -8px; width: 28px; height: 28px;\n            border-radius: 50%; border: none; cursor: pointer; display: flex;\n            align-items: center; justify-content: center; font-size: 16px; line-height: 1;\n            background: var(--yt-button-bg); color: var(--yt-text-primary); transition: background 0.18s ease;\n            border: 1px solid var(--yt-glass-border);\n          ">&times;</button>\n        </div>\n        <style>${window.YouTubePlusStyleResources?.update || ""}</style>\n      `);
const _containerId = "youtube-enhancer-notification-container";
let _container = byId(_containerId);
if (!_container) {
_container = document.createElement("div");
_container.id = _containerId;
_container.className = "youtube-enhancer-notification-container";
try {
document.body.appendChild(_container);
} catch (e) {
document.body.appendChild(notification);
}
}
try {
_container.insertBefore(notification, _container.firstChild);
} catch (e) {
document.body.appendChild(notification);
}
const removeNotification = () => {
notification.style.animation = "slideOutToBottom 0.35s ease-in forwards";
setTimeout_(() => notification.remove(), 360);
};
const installBtn = notification.querySelector("#update-install-btn");
installBtn && installBtn.addEventListener("click", () => {
const success = installUpdate(updateDetails);
if (success) {
removeNotification();
setTimeout_(() => utils_showNotification(t("installing")), 500);
} else {
utils_showNotification(t("manualInstallHint"), "error", 5e3);
window.open("https://greasyfork.org/en/scripts/537017-youtube", "_blank");
}
});
const dismissBtn = notification.querySelector("#update-dismiss-btn");
dismissBtn && dismissBtn.addEventListener("click", () => {
updateDetails?.version && sessionStorage.setItem("update_dismissed", updateDetails.version);
removeNotification();
});
const closeBtn = notification.querySelector("#update-close-btn");
closeBtn && closeBtn.addEventListener("click", () => {
updateDetails?.version && sessionStorage.setItem("update_dismissed", updateDetails.version);
removeNotification();
});
setTimeout_(() => {
notification.isConnected && removeNotification();
}, UPDATE_CONFIG.notificationDuration);
})(updateDetails);
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug(`YouTube + Update available: ${updateDetails.version}`);
} else if (force) {
const message = updateState.updateAvailable ? t("updateAvailableMsg").replace("{version}", updateDetails.version) : t("upToDateMsg").replace("{version}", UPDATE_CONFIG.currentVersion);
utils_showNotification(message);
}
};
const retrieveUpdateDetails = async () => {
let metaText = await fetchUpdateMetadata(UPDATE_CONFIG.updateUrl);
let details = utils_parseMetadata(metaText);
if (!details.version) {
try {
const fallbackText = await fetchUpdateMetadata(UPDATE_CONFIG.autoInstallUrl);
const fallbackDetails = utils_parseMetadata(fallbackText);
if (fallbackDetails.version) {
details = fallbackDetails;
metaText = fallbackText;
}
} catch (fallbackErr) {
"undefined" != typeof console && window.console.warn && window.console.warn("[YouTube+][Update] Fallback metadata fetch failed:", fallbackErr.message);
}
}
if (details.version) {
try {
const changelog = await (async version => {
try {
const lang = getLanguage();
const url = `https://greasyfork.org/${lang}/scripts/537017-youtube/versions`;
const fetchPage = async requestUrl => {
if ("undefined" != typeof GM_xmlhttpRequest) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout_(() => reject(new Error("Changelog fetch timeout")), 1e4);
GM_xmlhttpRequest({
method: "GET",
url: requestUrl,
timeout: 1e4,
headers: {
Accept: "text/html"
},
onload: response => {
clearTimeout(timeoutId);
response.status >= 200 && response.status < 300 ? resolve(response.responseText) : reject(new Error(`HTTP ${response.status}`));
},
onerror: () => {
clearTimeout(timeoutId);
reject(new Error("Network error"));
},
ontimeout: () => {
clearTimeout(timeoutId);
reject(new Error("Timeout"));
}
});
});
}
const controller = new AbortController;
const timeoutId = setTimeout_(() => controller.abort(), 1e4);
try {
const res = await fetch(requestUrl, {
method: "GET",
cache: "no-cache",
signal: controller.signal,
headers: {
Accept: "text/html"
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return await res.text();
} finally {
clearTimeout(timeoutId);
}
};
const html = await fetchPage(url);
const domParser = "function" == typeof window.DOMParser ? new window.DOMParser : null;
if (domParser) {
try {
const doc = domParser.parseFromString(html, "text/html");
const versionText = String(version || "").trim();
const anchors = Array.from(doc.querySelectorAll("a"));
const matchingLink = anchors.find(anchor => {
const linkText = String(anchor.textContent || "").trim();
return linkText === versionText || linkText === `v${versionText}` || linkText.includes(versionText);
});
const container = matchingLink?.closest("li, article, section, div") || matchingLink?.parentElement || null;
const changelogNode = container?.querySelector(".version-changelog") || (matchingLink?.nextElementSibling?.matches?.(".version-changelog") ? matchingLink.nextElementSibling : null);
const changelogText = String(changelogNode?.textContent || "").trim();
if (changelogText) {
return changelogText.split("\n").map(line => line.trim()).filter(line => line.length > 0).join("\n");
}
} catch (e) {}
}
return "";
} catch (error) {
window.console.warn("[YouTube+][Update] Failed to fetch changelog:", error.message);
return "";
}
})(details.version);
details.changelog = "string" == typeof changelog && changelog.length > 0 ? changelog : "";
} catch (changelogErr) {
window.console.warn("[YouTube+][Update] Failed to fetch changelog:", changelogErr.message);
details.changelog = "";
}
} else {
details.changelog = "";
}
return details;
};
const validateUpdateConfiguration = () => {
try {
(url => {
const parsedUrl = new URL(url);
if ("https:" !== parsedUrl.protocol) {
throw new Error("Update URL must use HTTPS");
}
if (!parsedUrl.hostname.includes("greasyfork.org")) {
throw new Error("Update URL must be from greasyfork.org");
}
})(UPDATE_CONFIG.updateUrl);
return !0;
} catch (urlError) {
window.console.error("[YouTube+][Update]", "Invalid update URL configuration:", urlError);
throw urlError;
}
};
const checkForUpdates = async (force = !1, retryCount = 0) => {
const now = Date.now();
if (((force, now) => !(!UPDATE_CONFIG.enabled || updateState.checkInProgress) && (force || now - updateState.lastCheck >= UPDATE_CONFIG.checkInterval))(force, now)) {
updateState.checkInProgress = !0;
try {
validateUpdateConfiguration();
const updateDetails = await retrieveUpdateDetails();
updateDetails.version ? ((updateDetails, force, now) => {
updateState.lastCheck = now;
updateState.lastVersion = updateDetails.version;
updateState.updateDetails = updateDetails;
const comparison = utils_compareVersions(UPDATE_CONFIG.currentVersion, updateDetails.version);
updateState.updateAvailable = comparison < 0;
handleUpdateResult(updateDetails, force);
utils_saveSettings();
if (updateState.updateAvailable && UPDATE_CONFIG.autoInstallOnCheck) {
try {
const dismissed = sessionStorage.getItem("update_dismissed");
if (dismissed !== updateDetails.version) {
const started = installUpdate(updateDetails);
if (started) {
markUpdateDismissed(updateDetails);
try {
utils_showNotification(t("installing"));
} catch (e) {}
} else {
window.console.warn("[YouTube+][Update] Auto-install could not be initiated for", updateDetails.downloadUrl);
}
}
} catch (e) {
window.console.error("[YouTube+][Update] Auto-installation failed:", e);
}
}
})(updateDetails, force, now) : (force => {
updateState.updateAvailable = !1;
force && utils_showNotification(t("updateCheckFailed").replace("{msg}", t("noUpdateInfo")), "error", 4e3);
})(force);
} catch (error) {
await (async (error, force, retryCount) => {
if ((error => !!("AbortError" === error.name || "NetworkError" === error.name || error.message && error.message.includes("fetch") || error.message && error.message.includes("network")))(error) && retryCount < 2) {
window.console.warn(`[YouTube+][Update] Retry ${retryCount + 1}/2 after error:`, error.message);
await new Promise(resolve => setTimeout_(resolve, 2e3 * Math.pow(2, retryCount)));
return checkForUpdates(force, retryCount + 1);
}
window.console.error("[YouTube+][Update] Check failed after retries:", error);
force && utils_showNotification(t("updateCheckFailed").replace("{msg}", error.message), "error", 4e3);
})(error, force, retryCount);
} finally {
updateState.checkInProgress = !1;
}
}
};
const addUpdateSettings = () => {
const aboutSection = YouTubeUtils.querySelector('.ytp-plus-settings-section[data-section="about"]');
if (!aboutSection || YouTubeUtils.querySelector(".update-settings-container")) {
return;
}
const updateContainer = document.createElement("div");
updateContainer.className = "update-settings-container";
updateContainer.style.cssText = "\n        padding: 16px; margin-top: 20px; border-radius: 12px;\n        background: var(--yt-surface-overlay-subtle); border: 1px solid var(--yt-surface-overlay-border);\n        -webkit-backdrop-filter: blur(10px) saturate(120%);\n        backdrop-filter: blur(10px) saturate(120%);\n        box-shadow: 0 6px 20px var(--yt-update-card-shadow);\n      ";
const lastCheckTime = utils_formatTimeAgo(updateState.lastCheck);
renderTemplateClone(updateContainer, `\n        <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">\n          <h3 style="margin: 0; font-size: 16px; font-weight: 600; color: var(--yt-spec-text-primary);">\n            ${t("enhancedExperience")}\n          </h3>\n        </div>\n        \n        <div style="display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center; \n                    padding: 16px; background: var(--yt-surface-overlay-subtle); border-radius: 10px; margin-bottom: 16px;">\n          <div>\n            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">\n              <span style="font-size: 14px; font-weight: 600; color: var(--yt-spec-text-primary);">${t("currentVersion")}</span>\n              <span style="font-size: 13px; font-weight: 600; color: var(--yt-spec-text-primary); \n                           padding: 3px 10px; background: var(--yt-surface-overlay-soft); border-radius: 12px; \n                           border: 1px solid var(--yt-glass-border);">${UPDATE_CONFIG.currentVersion}</span>\n            </div>\n            <div style="font-size: 12px; color: var(--yt-spec-text-secondary);">\n              ${t("lastChecked")}: <span style="font-weight: 500;">${lastCheckTime}</span>\n              ${updateState.lastVersion && updateState.lastVersion !== UPDATE_CONFIG.currentVersion ? `<br>${t("latestAvailable")}: <span style="color: var(--yt-update-available-text); font-weight: 600;">${updateState.lastVersion}</span>` : ""}\n            </div>\n          </div>\n          \n          ${updateState.updateAvailable ? `\n            <div style="display: flex; flex-direction: column; align-items: flex-end; gap: 8px;">\n              <div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px; \n                          background: linear-gradient(135deg, var(--yt-danger-soft), var(--yt-danger-border)); \n                          border: 1px solid var(--yt-danger-card-border); border-radius: 20px;">\n                <div style="width: 6px; height: 6px; background: var(--yt-update-available-dot); border-radius: 50%; animation: pulse 2s infinite;"></div>\n                <span style="font-size: 11px; color: var(--yt-update-available-text); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">\n                  ${t("updateAvailable")}\n                </span>\n              </div>\n              <button id="install-update-btn" style="background: linear-gradient(135deg, var(--yt-update-install-bg-start), var(--yt-update-install-bg-end)); \n                      color: white; border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; \n                      font-size: 12px; font-weight: 600; transition: all 0.3s ease; \n                      box-shadow: 0 4px 12px var(--yt-update-install-shadow);">${t("installUpdate")}</button>\n            </div>\n          ` : `\n            <div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px; \n                        background: linear-gradient(135deg, var(--yt-success-soft), var(--yt-success-soft-hover)); \n                        border: 1px solid var(--yt-success-accent-soft); border-radius: 20px;">\n              <div style="width: 6px; height: 6px; background: var(--yt-success-accent); border-radius: 50%;"></div>\n              <span style="font-size: 11px; color: var(--yt-success-accent); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">\n                ${t("upToDate")}\n              </span>\n            </div>\n          `}\n        </div>\n        \n        <div style="display: flex; gap: 12px;">\n          <button class="ytp-plus-button ytp-plus-button-primary" id="manual-update-check" \n                  style="flex: 1; padding: 12px; font-size: 13px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; gap: 6px;">\n            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; flex-shrink: 0;">\n              <path d="M21.5 2v6h-6M2.5 22v-6h6M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1"/>\n            </svg>\n            ${t("checkForUpdates")}\n          </button>\n          <button class="ytp-plus-button" id="open-update-page" style="padding: 12px 16px; font-size: 13px; background: var(--yt-button-bg); border: 1px solid var(--yt-glass-border);">\n            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">\n              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>\n              <polyline points="15,3 21,3 21,9"/>\n              <line x1="10" y1="14" x2="21" y2="3"/>\n            </svg>\n          </button>\n        </div>\n\n        <style>${window.YouTubePlusStyleResources?.update || ""}</style>\n      `);
aboutSection.appendChild(updateContainer);
try {
const aboutActions = aboutSection.querySelector(".ytp-plus-about-actions");
const aboutFooter = aboutSection.querySelector(".ytp-plus-about-footer");
aboutActions && aboutSection.appendChild(aboutActions);
aboutFooter && aboutSection.appendChild(aboutFooter);
} catch (e) {}
const attachClickHandler = (id, handler) => {
const element = byId(id);
element && YouTubeUtils.cleanupManager.registerListener(element, "click", handler);
};
attachClickHandler("manual-update-check", async evt => {
const button = evt.currentTarget instanceof HTMLElement ? evt.currentTarget : evt.target instanceof Element ? evt.target.closest("#manual-update-check") : null;
if (button) {
setManualCheckButtonContent(button, t("checkingForUpdates"), !0);
button.disabled = !0;
await checkForUpdates(!0);
setTimeout_(() => {
setManualCheckButtonContent(button, t("checkForUpdates"), !1);
button.disabled = !1;
}, 1e3);
}
});
attachClickHandler("install-update-btn", () => {
const success = installUpdate();
if (success) {
utils_showNotification(t("installing"));
} else {
utils_showNotification(t("manualInstallHint"), "error", 5e3);
window.open("https://greasyfork.org/en/scripts/537017-youtube", "_blank");
}
});
attachClickHandler("open-update-page", () => {
utils_showNotification(t("updatePageFallback"));
window.open("https://greasyfork.org/en/scripts/537017-youtube", "_blank");
});
};
let _initDone = !1;
const init = () => {
if (!_initDone) {
_initDone = !0;
utils_loadSettings();
(() => {
setTimeout_(() => checkForUpdates(), 3e3);
const globalObject = "undefined" != typeof window ? window : globalThis;
const previousIntervalId = globalObject.__ytpUpdateCheckIntervalId;
if (previousIntervalId) {
clearInterval(previousIntervalId);
YouTubeUtils.cleanupManager?.unregisterInterval && YouTubeUtils.cleanupManager.unregisterInterval(previousIntervalId);
}
const intervalId = setInterval(() => checkForUpdates(), UPDATE_CONFIG.checkInterval);
globalObject.__ytpUpdateCheckIntervalId = intervalId;
YouTubeUtils.cleanupManager.registerInterval(intervalId);
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(window, "beforeunload", () => clearInterval(intervalId)) : window.addEventListener("beforeunload", () => clearInterval(intervalId));
})();
(() => {
const handler = () => {
setTimeout_(addUpdateSettings, 100);
};
YouTubeUtils.cleanupManager?.registerListener ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", handler) : document.addEventListener("youtube-plus-settings-modal-opened", handler);
})();
YouTubeUtils.cleanupManager.registerListener(document, "click", evt => {
const el = evt.target;
el.classList?.contains("ytp-plus-settings-nav-item") && "about" === el.dataset?.section && setTimeout_(addUpdateSettings, 50);
}, {
passive: !0,
capture: !0
});
(() => {
try {
window.YouTubeUtils && YouTubeUtils.logger && YouTubeUtils.logger.debug && YouTubeUtils.logger.debug("YouTube + Update Checker initialized", {
version: UPDATE_CONFIG.currentVersion,
enabled: UPDATE_CONFIG.enabled,
lastCheck: new Date(updateState.lastCheck).toLocaleString(),
updateAvailable: updateState.updateAvailable
});
} catch (e) {}
})();
}
};
const startUpdateRuntime = () => {
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", init, {
once: !0
}) : init();
};
window.YouTubePlusLazyLoader?.register ? window.YouTubePlusLazyLoader.register("update", startUpdateRuntime, {
priority: 20,
delay: 0,
shouldLoad: () => (() => {
const path = location.pathname || "";
return "music.youtube.com" === location.hostname || "/watch" === path || path.startsWith("/shorts");
})() || (() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()
}) : startUpdateRuntime();
})();

/**
 * YouTube Music Enhancement Module
 * Provides UI improvements and features for YouTube Music
 * @module music
 * @version 2.3
 *
 * Features:
 * - Scroll-to-top button with smart container detection
 * - Enhanced navigation styles (centered search, immersive mode)
 * - Sidebar hover effects and player enhancements
 * - Health monitoring and automatic recovery
 * - SPA navigation support with debounced updates
 */ !(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
const createVisibilityAwareInterval = window.YouTubeUtils?.createVisibilityAwareInterval || ((callback, delay) => {
const id = setInterval(() => {
document.hidden || callback();
}, delay);
return {
stop() {
clearInterval(id);
},
pause() {
clearInterval(id);
},
resume() {},
get active() {
return !0;
}
};
});
if ("undefined" != typeof location && "music.youtube.com" !== location.hostname) {
return;
}
const qs = window.YouTubeUtils?.$ || document.querySelector.bind(document);
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
const MUSIC_SETTINGS_DEFAULTS = {
enableMusic: !0,
immersiveSearchStyles: !0,
hoverStyles: !0,
playerSidebarStyles: !0,
centeredPlayerStyles: !0,
playerBarStyles: !0,
centeredPlayerBarStyles: !0,
miniPlayerStyles: !0
};
function mergeMusicSettings(parsed) {
const merged = {
...MUSIC_SETTINGS_DEFAULTS
};
if (!parsed || "object" != typeof parsed) {
return merged;
}
"boolean" == typeof parsed.enableMusic && (merged.enableMusic = parsed.enableMusic);
for (const key of Object.keys(MUSIC_SETTINGS_DEFAULTS)) {
"enableMusic" !== key && "boolean" == typeof parsed[key] && (merged[key] = parsed[key]);
}
"boolean" == typeof parsed.enableImmersiveSearch && (merged.immersiveSearchStyles = parsed.enableImmersiveSearch);
"boolean" == typeof parsed.enableSidebarHover && (merged.hoverStyles = parsed.enableSidebarHover);
"boolean" == typeof parsed.enableCenteredPlayer && (merged.centeredPlayerStyles = parsed.enableCenteredPlayer);
const legacyEnabled = !!(parsed.enableMusicStyles || parsed.enableMusicEnhancements || parsed.enableImmersiveSearch || parsed.enableSidebarHover || parsed.enableCenteredPlayer);
legacyEnabled && "boolean" != typeof parsed.enableMusic && (merged.enableMusic = !0);
return merged;
}
function readMusicSettings() {
try {
if ("undefined" != typeof GM_getValue) {
const stored = GM_getValue("youtube-plus-music-settings", null);
if ("string" == typeof stored && stored) {
const parsed = JSON.parse(stored);
return mergeMusicSettings(parsed);
}
}
} catch (e) {}
try {
const stored = localStorage.getItem("youtube-plus-music-settings");
if (!stored) {
return {
...MUSIC_SETTINGS_DEFAULTS
};
}
const parsed = JSON.parse(stored);
return mergeMusicSettings(parsed);
} catch (e) {
return {
...MUSIC_SETTINGS_DEFAULTS
};
}
}
function isMusicModuleEnabled(settings) {
return !(!settings || !settings.enableMusic);
}
let musicSettingsSnapshot = readMusicSettings();
let musicStyleEl = null;
let observerSubId = null;
let observerFallbackTimerId = null;
let healthCheckIntervalId = null;
let detachNavigationListeners = null;
function applyStyles() {
if ("music.youtube.com" !== window.location.hostname) {
return;
}
const s = musicSettingsSnapshot || readMusicSettings();
if (!s.enableMusic) {
return;
}
const styleParts = [ "\n        /* Remove borders and shadows from nav/guide when bauhaus sidenav is enabled */\n        ytmusic-app-layout[is-bauhaus-sidenav-enabled] #nav-bar-background.ytmusic-app-layout { border-bottom: none !important; box-shadow: none !important; }\n        ytmusic-app-layout[is-bauhaus-sidenav-enabled] #nav-bar-divider.ytmusic-app-layout { border-top: none !important; }\n        ytmusic-app-layout[is-bauhaus-sidenav-enabled] #mini-guide-background.ytmusic-app-layout { border-right: 0 !important; }\n        ytmusic-nav-bar, ytmusic-app-layout[is-bauhaus-sidenav-enabled] .ytmusic-nav-bar { border: none !important; box-shadow: none !important; }\n        /* Center the settings button in the top nav bar (fixes it being rendered at the bottom) */\n        ytmusic-settings-button.style-scope.ytmusic-nav-bar, ytmusic-nav-bar ytmusic-settings-button.style-scope.ytmusic-nav-bar {position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; bottom: auto !important; margin: 0 !important; z-index: 1000 !important;}\n        /* Center the search box in the top nav bar */\n        ytmusic-search-box, ytmusic-nav-bar ytmusic-search-box, ytmusic-searchbox, ytmusic-nav-bar ytmusic-searchbox {position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; margin: 0 !important; max-width: 75% !important; width: auto !important; z-index: 900 !important;}\n  " ];
s.immersiveSearchStyles && styleParts.push("\n      /* yt-Immersive search behaviour for YouTube Music: expand/center the search when focused */\n      ytmusic-search-box:has(input:focus), ytmusic-searchbox:has(input:focus), ytmusic-search-box:focus-within, ytmusic-searchbox:focus-within {position: fixed !important; left: 50% !important; top: 12vh !important; transform: translateX(-50%) !important; height: auto !important; max-width: 900px !important; width: min(90vw, 900px) !important; z-index: 1200 !important; display: block !important;}\n      @media only screen and (min-width: 1400px) {ytmusic-search-box:has(input:focus), ytmusic-searchbox:has(input:focus) {top: 10vh !important; max-width: 1000px !important; transform: translateX(-50%) scale(1.05) !important;}}\n      /* Highlight the input and add a soft glow */\n      ytmusic-search-box:has(input:focus) input, ytmusic-searchbox:has(input:focus) input, ytmusic-search-box:focus-within input, ytmusic-searchbox:focus-within input {background-color: var(--yt-bg-primary) !important; box-shadow: black 0 0 30px !important;}\n      @media (prefers-color-scheme: dark) {ytmusic-search-box:has(input:focus) input, ytmusic-searchbox:has(input:focus) input {background-color: var(--yt-modal-bg) !important;}}\n      /* Blur/scale the main content when immersive search is active */\n      ytmusic-app-layout:has(ytmusic-search-box:has(input:focus)) #main-panel, ytmusic-app-layout:has(ytmusic-searchbox:has(input:focus)) #main-panel {filter: blur(18px) !important; transform: scale(1.03) !important;}\n    ");
s.hoverStyles && styleParts.push("\n        .ytmusic-guide-renderer {opacity: 0.01 !important; transition: opacity 0.5s ease-in-out !important;}        \n        .ytmusic-guide-renderer:hover { opacity: 1 !important;}        \n        ytmusic-app[is-bauhaus-sidenav-enabled] #guide-wrapper.ytmusic-app {background-color: transparent !important; border: none !important;}    \n    ");
s.playerSidebarStyles && styleParts.push('\n        #side-panel {width: 40em !important; height: 80vh !important; padding: 0 2em !important; right: -30em !important; top: 10vh !important; opacity: 0 !important; position: absolute !important; transition: all 0.3s ease-in-out !important; backdrop-filter: blur(5px) !important; background-color: var(--yt-panel-overlay-subtle) !important; border-radius: 1em !important; box-shadow: var(--yt-shadow-deep-1) 0px -36px 30px inset, var(--yt-shadow-deep-2) 0px -79px 40px inset, var(--yt-shadow-deep-3) 0px 2px 1px, var(--yt-shadow-deep-4) 0px 4px 2px, var(--yt-shadow-deep-4) 0px 8px 4px, var(--yt-shadow-deep-4) 0px 16px 8px, var(--yt-shadow-deep-4) 0px 32px 16px !important;}        \n        #side-panel tp-yt-paper-tabs {transition: height 0.3s ease-in-out !important; height: 0 !important;}        \n        #side-panel:hover {right: 0 !important; opacity: 1 !important;}        \n        #side-panel:hover tp-yt-paper-tabs {height: 4em !important;}        \n        #side-panel:has(ytmusic-tab-renderer[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"]):not(:has(ytmusic-message-renderer:not([style="display: none;"]))) {right: 0 !important; opacity: 1 !important;}        \n        #side-panel {min-width: auto !important;}\n      /* Allow JS to control visibility; ensure pointer-events and positioning only. */\n        #side-panel .ytmusic-top-button { opacity: 1 !important; visibility: visible !important; pointer-events: auto !important; }\n      /* When button is placed inside the panel, prefer absolute positioning inside it\n         so it won\'t be forced to fixed by the global rule. Use high specificity + !important */\n        #side-panel .ytmusic-top-button {position: absolute !important; bottom: 20px !important; right: 20px !important; z-index: 1200 !important;}\n    ');
s.centeredPlayerStyles && styleParts.push('\n        ytmusic-app-layout:not([player-ui-state="FULLSCREEN"]) #main-panel {position: absolute !important; height: 70vh !important; max-width: 70vw !important; aspect-ratio: 1 !important; top: 50vh !important; left: 50vw !important; transform: translate(-50%, -50%) !important;}        \n        #player-page {padding: 0 !important; margin: 0 !important; left: 0 !important; top: 0 !important; height: 100% !important; width: 100% !important;}\n    ');
s.playerBarStyles && styleParts.push('\n        ytmusic-player-bar, #player-bar-background {margin: 1vw !important; width: 98vw !important; border-radius: 1em !important; overflow: hidden !important; transition: all 0.5s ease-in-out !important; background-color: var(--yt-panel-overlay-weak) !important; box-shadow: var(--yt-shadow-deep-1) 0px -36px 30px inset, var(--yt-shadow-deep-2) 0px -79px 40px inset, var(--yt-shadow-deep-3) 0px 2px 1px, var(--yt-shadow-deep-4) 0px 4px 2px, var(--yt-shadow-deep-4) 0px 8px 4px, var(--yt-shadow-deep-4) 0px 16px 8px, var(--yt-shadow-deep-4) 0px 32px 16px !important;}\n        #layout:not([player-ui-state="PLAYER_PAGE_OPEN"]) #player-bar-background {background-color: var(--yt-panel-overlay-subtle) !important;}\n    ');
s.centeredPlayerBarStyles && styleParts.push("\n        #left-controls {position: absolute !important; left: 49vw !important; bottom: 15px !important; transform: translateX(-50%) !important; width: fit-content !important; order: 1 !important;}        \n        .time-info {position: absolute !important; bottom: -10px !important; left: 0 !important; width: 100% !important; text-align: center !important; padding: 0 !important; margin: 0 !important;}\n        .middle-controls {position: absolute !important; left: 1vw !important; bottom: 15px !important; max-width: 30vw !important; order: 0 !important;}\n    ");
s.miniPlayerStyles && styleParts.push('\n        #main-panel:has(ytmusic-player[player-ui-state="MINIPLAYER"]) {position: fixed !important; width: 100vw !important; height: 100vh !important; top: -100vh !important; left: 0 !important; margin: 0 !important; padding: 0 !important; transform: none !important; max-width: 100vw !important;}        \n        ytmusic-player[player-ui-state="MINIPLAYER"] {position: fixed !important; bottom: calc(100vh + 120px) !important; right: 30px !important; width: 350px !important; height: fit-content !important;}        \n        #av-id:has(ytmusic-av-toggle) {position: absolute !important; left: 50% !important; transform: translateX(-50%) !important; top: -4em !important; opacity: 0 !important; transition: all 0.3s ease-in-out !important;}        \n        #av-id:has(ytmusic-av-toggle):hover {opacity: 1 !important;}        \n        #player[player-ui-state="MINIPLAYER"] {display: none !important;}\n      /* Chrome-specific robustness: ensure the AV toggle container is above overlays\n         and can receive hover even if :has() behaves differently. Also provide a\n         non-:has fallback so the element is hoverable regardless of child matching. */\n      /* Use absolute positioning (keeps internal menu alignment) but promote\n         stacking and rendering to ensure it sits above overlays and receives clicks. */\n        #av-id {position: absolute !important; left: 50% !important; transform: translateX(-50%) translateZ(0) !important; top: -4em !important; z-index: 10000 !important; pointer-events: auto !important; display: block !important; visibility: visible !important; width: auto !important; height: auto !important; will-change: transform, opacity !important;}\n        #av-id ytmusic-av-toggle {pointer-events: auto !important;}\n        #av-id:hover {opacity: 1 !important;}\n      /* Prevent overlapping overlays from stealing clicks when hovering the toggle.\n         This is a conservative rule; if a specific overlay still steals clicks we\n         can target it explicitly later. */\n        #av-id:hover, #av-id:active { filter: none !important; }\n    ');
const allStyles = `\n${styleParts.join("\n")}\n`;
if (musicStyleEl && musicStyleEl.isConnected) {
musicStyleEl.textContent = allStyles;
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Styles updated");
} else {
try {
if ("undefined" != typeof GM_addStyle) {
const el = GM_addStyle(allStyles);
if (el && "STYLE" === el.tagName) {
musicStyleEl = el;
try {
musicStyleEl.id = "youtube-plus-music-styles";
} catch (e) {}
}
}
} catch (e) {}
if (!musicStyleEl || !musicStyleEl.isConnected) {
const style = document.createElement("style");
style.id = "youtube-plus-music-styles";
style.textContent = allStyles;
document.head.appendChild(style);
musicStyleEl = style;
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Styles applied");
}
}
const getDebounce = () => window.YouTubeUtils.debounce;
const t = window.YouTubeUtils.t;
function createButton() {
const button = document.createElement("button");
button.id = "ytmusic-side-panel-top-button";
button.className = "ytmusic-top-button top-button";
button.title = t("scrollToTop");
button.setAttribute("aria-label", t("scrollToTop"));
((container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
})(button, '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>');
button.setAttribute("data-ytmusic-scroll-button", "true");
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Button element created", {
id: button.id,
className: button.className
});
return button;
}
const scrollContainerCache = new WeakMap;
function attachButtonToContainer(button, sidePanel, sc, MusicUtils) {
try {
!(function setupScrollBehavior(button, sc, MusicUtils, sidePanel) {
if (MusicUtils.setupScrollToTop) {
MusicUtils.setupScrollToTop(button, sc);
return;
}
const findNearestScrollable = startEl => {
let el = startEl;
for (;el && el !== document.body; ) {
try {
if (el.scrollHeight > el.clientHeight + 10) {
return el;
}
} catch (e) {}
el = el.parentElement;
}
return null;
};
button.addEventListener("click", ev => {
try {
ev.preventDefault?.();
} catch (e) {}
try {
ev.stopPropagation?.();
} catch (e) {}
let target = sc;
target && target.scrollHeight > target.clientHeight + 1 || (target = sidePanel && findNearestScrollable(sidePanel));
target || (target = findNearestScrollable(button.parentElement));
target || (target = document.scrollingElement || document.documentElement || document.body);
try {
const info = {
chosen: target && (target.id || target.tagName || "(window)"),
scrollTop: target && "scrollTop" in target ? target.scrollTop : null,
scrollHeight: target && "scrollHeight" in target ? target.scrollHeight : null,
clientHeight: target && "clientHeight" in target ? target.clientHeight : null
};
try {
window.YouTubeMusic = window.YouTubeMusic || {};
window.YouTubeMusic._lastClickDebug = info;
} catch (e) {}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "ScrollToTop click target", info);
} catch (e) {}
const tryScroll = el => {
if (!el) {
return !1;
}
try {
if ("function" == typeof el.scrollTo) {
el.scrollTo({
top: 0,
behavior: "smooth"
});
return !0;
}
if ("scrollTop" in el) {
el.scrollTop = 0;
return !0;
}
} catch (e) {}
return !1;
};
let scrolled = !1;
scrolled = tryScroll(target) || scrolled;
sc && sc !== target && (scrolled = tryScroll(sc) || scrolled);
scrolled = tryScroll(document.scrollingElement || document.documentElement || document.body) || scrolled;
if (!scrolled) {
try {
window.scrollTo(0, 0);
} catch (err2) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Final scroll fallback failed", err2);
}
}
}, {
passive: !1
});
})(button, sc, MusicUtils, sidePanel);
const attachInsidePanel = !!sidePanel;
!(function setupButtonPosition(button, sidePanel, MusicUtils, options = {}) {
if (MusicUtils.setupButtonStyles) {
MusicUtils.setupButtonStyles(button, sidePanel, options);
} else {
if (options.insideSidePanel && sidePanel) {
button.style.setProperty("position", "absolute", "important");
button.style.setProperty("bottom", "20px", "important");
button.style.setProperty("right", "20px", "important");
button.style.setProperty("z-index", "1200", "important");
button.style.setProperty("pointer-events", "auto", "important");
button.style.display = "flex";
} else {
button.style.position = "fixed";
button.style.bottom = "100px";
button.style.right = "20px";
button.style.zIndex = "10000";
button.style.pointerEvents = "auto";
button.style.display = "flex";
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Button positioned:", {
position: button.style.position,
bottom: button.style.bottom,
right: button.style.right,
zIndex: button.style.zIndex,
insideSidePanel: !!options.insideSidePanel
});
}
})(button, sidePanel, MusicUtils, {
insideSidePanel: attachInsidePanel
});
if (attachInsidePanel) {
try {
sidePanel.appendChild(button);
} catch (err) {
document.body.appendChild(button);
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Appending to sidePanel failed, appended to body", err);
}
} else {
document.body.appendChild(button);
}
!(function setupScrollVisibility(button, sc, MusicUtils) {
if (window.YouTubePlusScrollManager && window.YouTubePlusScrollManager.addScrollListener) {
try {
const cleanup = window.YouTubePlusScrollManager.addScrollListener(sc, () => {
const shouldShow = sc.scrollTop > 100;
button.classList.toggle("visible", shouldShow);
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `Scroll position: ${sc.scrollTop}px, button visible: ${shouldShow}`);
}, {
debounce: 100,
runInitial: !0
});
button._scrollCleanup = cleanup;
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Using ScrollManager for scroll handling");
return;
} catch (e) {
window.console.error("[YouTube+][Music] ScrollManager failed, using fallback");
}
}
if (MusicUtils.setupScrollVisibility) {
MusicUtils.setupScrollVisibility(button, sc, 100);
return;
}
let isTabVisible = !document.hidden;
let rafId = null;
const updateVisibility = () => {
rafId && cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
if (!isTabVisible) {
return;
}
const currentScroll = sc.scrollTop || 0;
const shouldShow = currentScroll > 100;
const wasVisible = button.classList.contains("visible");
button.classList.toggle("visible", shouldShow);
shouldShow !== wasVisible && window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `Button visibility changed: ${shouldShow ? "SHOWN" : "HIDDEN"} (scroll: ${currentScroll}px)`);
});
};
const debounce = getDebounce();
const scrollHandler = debounce(updateVisibility, 100);
const visibilityHandler = () => {
isTabVisible = !document.hidden;
isTabVisible && updateVisibility();
};
sc.addEventListener("scroll", scrollHandler, {
passive: !0
});
document.addEventListener("visibilitychange", visibilityHandler);
setTimeout(updateVisibility, 100);
setTimeout(updateVisibility, 500);
button._scrollCleanup = () => {
rafId && cancelAnimationFrame(rafId);
sc.removeEventListener("scroll", scrollHandler);
document.removeEventListener("visibilitychange", visibilityHandler);
};
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Using fallback scroll handler");
})(button, sc, MusicUtils);
const initialScroll = sc.scrollTop || 0;
if (initialScroll > 100) {
button.classList.add("visible");
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `Button shown immediately (scroll: ${initialScroll}px)`);
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Scroll to top button created successfully", {
buttonId: button.id,
scrollContainer: sc.tagName,
scrollContainerId: sc.id || "no-id",
scrollHeight: sc.scrollHeight,
clientHeight: sc.clientHeight,
scrollTop: initialScroll,
position: button.style.position,
computedDisplay: window.getComputedStyle(button).display,
computedOpacity: window.getComputedStyle(button).opacity,
computedVisibility: window.getComputedStyle(button).visibility
});
} catch (err) {
window.console.error("[YouTube+][Music] attachButton error:", err);
}
}
const buttonCreationState = {
attempts: 0,
maxAttempts: 5,
lastAttempt: 0,
minInterval: 500
};
function createScrollToTopButton() {
try {
if ("music.youtube.com" !== window.location.hostname) {
return;
}
const existingButton = byId("ytmusic-side-panel-top-button");
if (existingButton) {
if (document.body.contains(existingButton) && existingButton._scrollCleanup) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Button already exists and is properly attached");
return;
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Removing orphaned button");
existingButton.remove();
}
const now = Date.now();
if (now - buttonCreationState.lastAttempt < buttonCreationState.minInterval) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Rate limited, skipping button creation");
return;
}
buttonCreationState.attempts++;
buttonCreationState.lastAttempt = now;
if (buttonCreationState.attempts > buttonCreationState.maxAttempts) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `Max attempts (${buttonCreationState.maxAttempts}) reached, stopping retries`);
return;
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `Creating button (attempt ${buttonCreationState.attempts}/${buttonCreationState.maxAttempts})`);
const sidePanel = qs("#side-panel");
const MusicUtils = window.YouTubePlusMusicUtils || {};
const button = createButton();
if (!sidePanel) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "No side-panel found, checking for main content or queue");
const queueRenderer = qs("ytmusic-queue-renderer");
if (queueRenderer) {
const queueContents = queueRenderer.querySelector("#contents");
if (queueContents) {
attachButtonToContainer(button, queueRenderer, queueContents, MusicUtils);
buttonCreationState.attempts = 0;
return;
}
}
const mainContent = qs("ytmusic-browse");
if (mainContent) {
const scrollContainer = mainContent.querySelector("ytmusic-section-list-renderer");
if (scrollContainer) {
attachButtonToContainer(button, mainContent, scrollContainer, MusicUtils);
buttonCreationState.attempts = 0;
return;
}
}
setTimeout_(function() {
createScrollToTopButton();
}, 1e3);
return;
}
const scrollContainer = (function findScrollContainer(sidePanel, MusicUtils) {
if (scrollContainerCache.has(sidePanel)) {
const cached = scrollContainerCache.get(sidePanel);
if (cached && document.body.contains(cached) && cached.scrollHeight > cached.clientHeight + 10) {
return cached;
}
scrollContainerCache.delete(sidePanel);
}
if (MusicUtils.findScrollContainer) {
const result = MusicUtils.findScrollContainer(sidePanel);
result && scrollContainerCache.set(sidePanel, result);
return result;
}
const selectors = [ 'ytmusic-tab-renderer[tab-identifier="FEmusic_queue"] #contents', 'ytmusic-tab-renderer[tab-identifier="FEmusic_up_next"] #contents', 'ytmusic-tab-renderer[tab-identifier="FEmusic_lyrics"] #contents', "ytmusic-tab-renderer[selected] #contents", "ytmusic-tab-renderer #contents", "ytmusic-queue-renderer #contents", "ytmusic-playlist-shelf-renderer #contents", "#side-panel #contents", "#contents.ytmusic-tab-renderer", ".ytmusic-section-list-renderer", '[role="tabpanel"]', ".ytmusic-player-queue", "ytmusic-tab-renderer", ".scroller", "[scroll-container]" ];
for (const selector of selectors) {
const container = sidePanel?.querySelector(selector);
if (container) {
const isScrollable = container.scrollHeight > container.clientHeight + 10;
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `Checking ${selector}: scrollHeight=${container.scrollHeight}, clientHeight=${container.clientHeight}, isScrollable=${isScrollable}`);
if (isScrollable) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `✓ Found scroll container: ${selector}`);
scrollContainerCache.set(sidePanel, container);
return container;
}
}
}
if (sidePanel && sidePanel.scrollHeight > sidePanel.clientHeight + 10) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "✓ Using side-panel as scroll container");
scrollContainerCache.set(sidePanel, sidePanel);
return sidePanel;
}
if (sidePanel) {
const fallbackSelectors = [ "div[id]", "div[class]", '[role="tabpanel"]', '[role="list"]', '[role="listbox"]' ];
let best = null;
let bestDelta = 0;
for (const sel of fallbackSelectors) {
try {
const candidates = sidePanel.querySelectorAll(sel);
for (const el of candidates) {
const delta = (el.scrollHeight || 0) - (el.clientHeight || 0);
if (delta > 10 && delta > bestDelta) {
bestDelta = delta;
best = el;
}
}
} catch (e) {}
}
if (best) {
const tag = best.tagName.toLowerCase();
const id = best.id ? `#${best.id}` : "";
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", `✓ Best scroll container chosen: ${tag}${id}`, {
scrollHeight: best.scrollHeight,
clientHeight: best.clientHeight
});
scrollContainerCache.set(sidePanel, best);
return best;
}
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "✗ No scroll container found in side-panel");
return null;
})(sidePanel, MusicUtils);
if (!scrollContainer) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "No scroll container found, will retry with backoff");
const backoffDelay = Math.min(500 * buttonCreationState.attempts, 3e3);
setTimeout_(function() {
createScrollToTopButton();
}, backoffDelay);
return;
}
attachButtonToContainer(button, sidePanel, scrollContainer, MusicUtils);
buttonCreationState.attempts = 0;
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "✓ Button created successfully");
} catch (error) {
window.console.error("[YouTube+][Music] Error creating scroll to top button:", error);
buttonCreationState.attempts < buttonCreationState.maxAttempts && setTimeout_(function() {
createScrollToTopButton();
}, 1e3);
}
}
function checkAndCreateButton() {
try {
const existingButton = byId("ytmusic-side-panel-top-button");
if (existingButton) {
if (existingButton._scrollCleanup && document.body.contains(existingButton)) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Button is healthy, no action needed");
return;
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Cleaning up orphaned/detached button");
if (existingButton._scrollCleanup) {
try {
existingButton._scrollCleanup();
} catch (e) {}
}
if (existingButton._positionCleanup) {
try {
existingButton._positionCleanup();
} catch (e) {}
}
existingButton.remove();
}
const sidePanel = qs("#side-panel");
const mainContent = qs("ytmusic-browse");
const queueRenderer = qs("ytmusic-queue-renderer");
const tabRenderer = qs("ytmusic-tab-renderer[tab-identifier]");
if (sidePanel || mainContent || queueRenderer || tabRenderer) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Found container, scheduling button creation");
setTimeout(function() {
createScrollToTopButton();
}, 300);
} else {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "No suitable container found yet");
}
} catch (error) {
window.console.error("[YouTube+][Music] Error in checkAndCreateButton:", error);
}
}
const observeDocumentBodySafely = () => {
if (observerSubId || observerFallbackTimerId) {
return;
}
const debounce = getDebounce();
const debouncedCheck = debounce(checkAndCreateButton, 200);
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
observerSubId = "music::sidePanelObserver";
coordinator.subscribeRoot(observerSubId, mutations => {
const now = Date.now();
const existingButton = byId("ytmusic-side-panel-top-button");
if (existingButton && document.body.contains(existingButton) && existingButton._scrollCleanup) {
return;
}
const hasRelevantChange = mutations.some(mutation => 0 !== mutation.addedNodes.length && Array.from(mutation.addedNodes).some(node => {
if (1 !== node.nodeType) {
return !1;
}
const element = node;
if ("side-panel" === element.id || "contents" === element.id) {
return !0;
}
const tagName = element.tagName;
return "YTMUSIC-BROWSE" === tagName || "YTMUSIC-PLAYER-PAGE" === tagName || "YTMUSIC-QUEUE-RENDERER" === tagName || "YTMUSIC-TAB-RENDERER" === tagName || null != element.querySelector?.("#side-panel, #contents, ytmusic-browse, ytmusic-queue-renderer, ytmusic-tab-renderer");
}));
const hasTabChange = mutations.some(mutation => "attributes" === mutation.type && "selected" === mutation.attributeName && mutation.target instanceof Element && mutation.target.matches?.("ytmusic-tab-renderer, tp-yt-paper-tab"));
if (hasRelevantChange || hasTabChange) {
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Detected relevant DOM change, checking button");
debouncedCheck();
} else {
now % 2 == 0 && debouncedCheck();
}
}, {
selector: "#side-panel, #contents, ytmusic-browse, ytmusic-player-page, ytmusic-queue-renderer, ytmusic-tab-renderer",
childList: !0,
subtree: !0,
attributes: !0,
attributeFilter: [ "selected", "tab-identifier", "page-type" ]
});
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "✓ Coordinator watcher started");
} else {
observerFallbackTimerId = createVisibilityAwareInterval(() => {
checkAndCreateButton();
}, 500);
}
};
function stopScrollToTopRuntime() {
try {
if (null != healthCheckIntervalId) {
healthCheckIntervalId.stop();
healthCheckIntervalId = null;
}
if (observerSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(observerSubId);
observerSubId = null;
}
if (observerFallbackTimerId) {
observerFallbackTimerId.stop();
observerFallbackTimerId = null;
}
if (detachNavigationListeners) {
try {
detachNavigationListeners();
} catch (e) {}
detachNavigationListeners = null;
}
const button = byId("ytmusic-side-panel-top-button");
if (button?._scrollCleanup) {
try {
button._scrollCleanup();
} catch (e) {}
}
if (button?._positionCleanup) {
try {
button._positionCleanup();
} catch (e) {}
}
button && button.remove();
} catch (e) {
window.console.error("[YouTube+][Music] stopScrollToTopRuntime error:", e);
}
}
function applySettingsChanges() {
musicSettingsSnapshot = readMusicSettings();
if (isMusicModuleEnabled(musicSettingsSnapshot)) {
if ("music.youtube.com" === window.location.hostname) {
applyStyles();
stopScrollToTopRuntime();
}
} else {
stopScrollToTopRuntime();
musicStyleEl && musicStyleEl.isConnected && musicStyleEl.remove();
try {
document.querySelectorAll("#youtube-plus-music-styles").forEach(el => el !== musicStyleEl && el.remove());
} catch (e) {}
musicStyleEl = null;
}
}
"undefined" != typeof window && (window.YouTubeMusic = {
observeDocumentBodySafely,
checkAndCreateButton,
createScrollToTopButton,
saveSettings: function saveSettings(s) {
musicSettingsSnapshot = s && "object" == typeof s ? {
...musicSettingsSnapshot,
...s
} : readMusicSettings();
},
applySettingsChanges,
version: "2.4.5"
});
const isMusicRoute = () => "music.youtube.com" === window.location.hostname;
let musicRuntimeStarted = !1;
const startMusicRuntime = () => {
if (!musicRuntimeStarted) {
musicRuntimeStarted = !0;
window.addEventListener("beforeunload", () => {
try {
stopScrollToTopRuntime();
musicStyleEl && musicStyleEl.isConnected && musicStyleEl.remove();
musicStyleEl = null;
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Cleanup completed");
} catch (error) {
window.console.error("[YouTube+][Music] Cleanup error:", error);
}
});
!(function startIfEnabled() {
if ("music.youtube.com" === window.location.hostname) {
musicSettingsSnapshot = readMusicSettings();
isMusicModuleEnabled(musicSettingsSnapshot) && ("loading" === document.readyState ? document.addEventListener("DOMContentLoaded", applyStyles, {
once: !0
}) : applyStyles());
}
})();
try {
"undefined" != typeof GM_addValueChangeListener && GM_addValueChangeListener("youtube-plus-music-settings", (_name, _oldValue, newValue) => {
try {
if ("string" == typeof newValue && newValue) {
const parsed = JSON.parse(newValue);
musicSettingsSnapshot = mergeMusicSettings(parsed);
} else {
musicSettingsSnapshot = readMusicSettings();
}
} catch (e) {
musicSettingsSnapshot = readMusicSettings();
}
applySettingsChanges();
});
} catch (e) {
window.console.warn("[YouTube+][Music] Settings listener registration error:", e);
}
window.YouTubeUtils?.logger?.debug?.("[YouTube+][Music]", "Module loaded (lazy)", {
version: "2.4.5",
hostname: window.location.hostname,
enabled: isMusicRoute() && isMusicModuleEnabled(musicSettingsSnapshot)
});
}
};
window.YouTubePlusLazyLoader?.register ? window.YouTubePlusLazyLoader.register("music", startMusicRuntime, {
priority: 45,
delay: 0,
shouldLoad: isMusicRoute
}) : startMusicRuntime();
})();

!(function() {
"use strict";
const {$, $$} = window.YouTubeUtils || {};
const onDomReady = window.YouTubeUtils?.onDomReady || (cb => {
"loading" !== document.readyState ? cb() : document.addEventListener("DOMContentLoaded", cb, {
once: !0
});
});
const CONFIG = {
enabled: !0,
storageKey: "youtube_endscreen_settings",
selectors: ".ytp-ce-element-show,.ytp-ce-element,.ytp-endscreen-element,.ytp-ce-covering-overlay,.ytp-cards-teaser,.teaser-carousel,.ytp-cards-button,.iv-drawer,.iv-branding,.video-annotations,.ytp-cards-teaser-text",
debounceMs: 32,
batchSize: 20
};
const state = {
observerSubId: null,
styleEl: null,
isActive: !1,
removeCount: 0,
lastCheck: 0,
ytNavigateListenerKey: null,
settingsNavListenerKey: null
};
const debounce = window.YouTubeUtils.debounce;
const settings = {
load: () => {
try {
const data = localStorage.getItem(CONFIG.storageKey);
CONFIG.enabled = !data || (JSON.parse(data).enabled ?? !0);
} catch (e) {
CONFIG.enabled = !0;
}
},
save: () => {
try {
localStorage.setItem(CONFIG.storageKey, JSON.stringify({
enabled: CONFIG.enabled
}));
} catch (e) {}
settings.apply();
},
apply: () => CONFIG.enabled ? init() : cleanup()
};
const removeEndScreens = () => {
if (!CONFIG.enabled) {
return;
}
const now = performance.now();
if (now - state.lastCheck < CONFIG.debounceMs) {
return;
}
state.lastCheck = now;
const elements = $$(CONFIG.selectors);
elements.length && (elements => {
const len = Math.min(elements.length, CONFIG.batchSize);
for (let i = 0; i < len; i++) {
const el = elements[i];
if (el?.isConnected) {
el.style.display = "none";
el.style.visibility = "hidden";
try {
el.remove();
state.removeCount++;
} catch (e) {}
}
}
})(elements);
};
const isRelevantNode = node => {
if (!(node instanceof Element)) {
return !1;
}
const classNameValue = (node => "string" == typeof node.className ? node.className : node.className && "object" == typeof node.className && "baseVal" in node.className ? node.className.baseVal : "")(node);
return classNameValue.includes("ytp-") || node.querySelector?.(".ytp-ce-element");
};
const setupWatcher = () => {
if (state.observerSubId || !CONFIG.enabled) {
return;
}
const throttledRemove = debounce(removeEndScreens, CONFIG.debounceMs);
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.subscribeRoot) {
state.observerSubId = "endscreen::observer";
coordinator.subscribeRoot(state.observerSubId, mutations => {
(mutations => {
for (const {addedNodes} of mutations) {
for (const node of addedNodes) {
if (isRelevantNode(node)) {
return !0;
}
}
}
return !1;
})(mutations) && throttledRemove();
}, {
selector: "#movie_player, .ytp-ce-element, .ytp-endscreen-element, .ytp-cards-teaser, .ytp-cards-button, .iv-drawer, .iv-branding, .video-annotations",
childList: !0,
attributes: !0,
subtree: !0,
attributeFilter: [ "class", "style" ]
});
YouTubeUtils.cleanupManager.register(() => {
if (state.observerSubId) {
coordinator.unsubscribe(state.observerSubId);
state.observerSubId = null;
}
});
throttledRemove();
}
};
const cleanup = () => {
state.observerSubId && window.YouTubeMutationCoordinator?.unsubscribe && window.YouTubeMutationCoordinator.unsubscribe(state.observerSubId);
state.observerSubId = null;
if (state.styleEl) {
try {
YouTubeUtils.StyleManager.remove(state.styleEl);
} catch (e) {}
}
state.styleEl = null;
state.isActive = !1;
};
const init = () => {
if (!state.isActive && CONFIG.enabled) {
state.isActive = !0;
(() => {
if (state.styleEl || !CONFIG.enabled) {
return;
}
const styles = `${CONFIG.selectors}{display:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important;transform:scale(0)!important}`;
YouTubeUtils.StyleManager.add("end-screen-remover", styles);
state.styleEl = "end-screen-remover";
})();
removeEndScreens();
setupWatcher();
}
};
const setupEndscreenSettingsDelegation = (() => {
let attached = !1;
return () => {
if (attached) {
return;
}
attached = !0;
const delegator = window.YouTubePlusEventDelegation;
const handler = (ev, target) => {
if (target && target.classList?.contains("ytp-plus-settings-checkbox") && target.closest?.(".endscreen-settings")) {
CONFIG.enabled = target.checked;
settings.save();
}
};
if (delegator?.on) {
delegator.on(document, "change", ".endscreen-settings .ytp-plus-settings-checkbox", handler, {
passive: !0
});
} else {
const changeHandler = ev => {
const target = ev.target?.closest?.(".ytp-plus-settings-checkbox");
target && handler(0, target);
};
window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(document, "change", changeHandler, {
passive: !0,
capture: !0
}) : document.addEventListener("change", changeHandler, {
passive: !0,
capture: !0
});
}
};
})();
const addSettingsUI = () => {
const enhancedSlot = $(".endscreen-settings-slot");
const enhancedCard = $(".enhanced-submenu .glass-card");
const host = enhancedSlot || enhancedCard;
if (!host || $(".endscreen-settings", host)) {
return;
}
const container = document.createElement("div");
container.className = "ytp-plus-settings-item endscreen-settings";
((container, html) => {
window.YouTubeSafeDOM?.renderTemplateClone ? window.YouTubeSafeDOM.renderTemplateClone(container, html) : container instanceof Element && container.replaceChildren(document.createTextNode(String(html ?? "")));
})(container, `\n        <div>\n          <label class="ytp-plus-settings-item-label">${YouTubeUtils.t("endscreenHideLabel")}</label>\n          <div class="ytp-plus-settings-item-description">${YouTubeUtils.t("endscreenHideDesc")}${state.removeCount ? ` (${state.removeCount} ${YouTubeUtils.t("removedSuffix").replace("{n}", "")?.trim() || "removed"})` : ""}</div>\n        </div>\n        <input type="checkbox" class="ytp-plus-settings-checkbox" ${CONFIG.enabled ? "checked" : ""}>\n      `);
enhancedSlot ? enhancedSlot.replaceWith(container) : host.appendChild(container);
setupEndscreenSettingsDelegation();
};
const handlePageChange = debounce(() => {
if ("/watch" === location.pathname) {
cleanup();
requestIdleCallback ? requestIdleCallback(init) : setTimeout(init, 1);
}
}, 50);
let endscreenRuntimeStarted = !1;
const startEndscreenRuntime = () => {
if (endscreenRuntimeStarted) {
return;
}
endscreenRuntimeStarted = !0;
settings.load();
onDomReady(init);
const handleSettingsNavClick = e => {
const {target} = e;
"advanced" === target?.dataset?.section && setTimeout(addSettingsUI, 10);
};
state.ytNavigateListenerKey || (state.ytNavigateListenerKey = YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", handlePageChange, {
passive: !0
}));
YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-opened", () => setTimeout(addSettingsUI, 25));
state.settingsNavListenerKey || (state.settingsNavListenerKey = YouTubeUtils.cleanupManager.registerListener(document, "click", handleSettingsNavClick, {
passive: !0,
capture: !0
}));
};
window.YouTubePlusLazyLoader?.register ? window.YouTubePlusLazyLoader.register("end", startEndscreenRuntime, {
priority: 35,
delay: 0,
shouldLoad: () => (() => {
const path = location.pathname || "";
return "/watch" === path || path.startsWith("/shorts");
})() || (() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()
}) : startEndscreenRuntime();
})();

(async function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
let featureEnabled = !0;
let stopRandomPlayTimers = null;
let scheduleApplyRandomPlay = null;
let addButtonRetryTimer = null;
const setFeatureEnabled = nextEnabled => {
featureEnabled = !1 !== nextEnabled;
if (featureEnabled) {
try {
queueDesktopAddButton();
} catch (e) {}
try {
"function" == typeof scheduleApplyRandomPlay && scheduleApplyRandomPlay();
} catch (e) {}
} else {
try {
removeButton();
} catch (e) {}
try {
if (addButtonRetryTimer) {
clearTimeout(addButtonRetryTimer);
"function" == typeof addButtonRetryTimer.cancel && addButtonRetryTimer.cancel();
}
addButtonRetryTimer = null;
} catch (e) {}
try {
"function" == typeof stopRandomPlayTimers && stopRandomPlayTimers();
} catch (e) {}
}
};
featureEnabled = window.YouTubeUtils?.loadFeatureEnabled?.("enablePlayAll") ?? !0;
const $ = window.YouTubeUtils.$;
const $$ = window.YouTubeUtils.$$;
const _cm = window.YouTubeUtils?.cleanupManager;
const onDomReady = (() => {
let ready = "loading" !== document.readyState;
const queue = [];
ready || document.addEventListener("DOMContentLoaded", () => {
ready = !0;
for (;queue.length; ) {
const cb = queue.shift();
try {
cb && cb();
} catch (e) {
window.console.warn("[Play All] DOMReady callback error:", e);
}
}
}, {
once: !0
});
return cb => {
ready ? cb() : queue.push(cb);
};
})();
const t = window.YouTubeUtils.t;
const getPlayAllLabel = () => {
if ((key => {
try {
if (window.YouTubePlusI18n?.hasTranslation) {
return window.YouTubePlusI18n.hasTranslation(key);
}
} catch (e) {}
return !1;
})("playAllButton")) {
const localized = t("playAllButton");
if (localized && "playAllButton" !== localized) {
return localized;
}
}
return "Play All";
};
const getPlayAllAriaLabel = () => {
const localized = t("enablePlayAllLabel");
return localized && "enablePlayAllLabel" !== localized ? localized : getPlayAllLabel();
};
const gmInfo = (globalThis?.GM_info ?? null) || (window?.GM_info ?? null);
const scriptVersion = gmInfo?.script?.version ?? null;
if (scriptVersion && /-(alpha|beta|dev|test)$/.test(scriptVersion)) {
try {
window.YouTubeUtils && window.YouTubeUtils?.logger?.info?.("%cytp - YouTube Play All\n", "color: var(--yt-playall-accent-purple); font-size: 32px; font-weight: bold", "You are currently running a test version:", scriptVersion);
} catch (e) {}
}
(fn => {
"function" == typeof requestIdleCallback ? requestIdleCallback(fn, {
timeout: 2e3
}) : setTimeout(fn, 200);
})(() => {
const css = window.YouTubePlusStyleResources?.playall || ".ytp-play-all-btn{display:inline-flex;align-items:center;padding:0 12px;height:32px;border-radius:8px;background:linear-gradient(135deg,var(--yt-playall-accent-purple),var(--yt-playall-accent-blue));color:#fff;font-size:1.4rem;font-weight:500;text-decoration:none;white-space:nowrap;cursor:pointer;flex-shrink:0;user-select:none;font-family:Roboto,Arial,sans-serif;letter-spacing:.007em;line-height:1;vertical-align:middle;border:none;outline:none}.ytp-play-all-btn:hover{opacity:.85}";
(html => {
try {
const target = document.head || document.documentElement;
if (target && "function" == typeof target.insertAdjacentHTML) {
target.insertAdjacentHTML("beforeend", _createHTML(html));
return;
}
const onReady = () => {
try {
const t = document.head || document.documentElement;
t && "function" == typeof t.insertAdjacentHTML && t.insertAdjacentHTML("beforeend", _createHTML(html));
} catch (e) {}
};
onDomReady(onReady);
} catch (e) {
window.console.warn("[Play All] Style insertion error:", e);
}
})(`<style>${css}</style>`);
});
const getVideoId = url => {
try {
return new URLSearchParams(new URL(url).search).get("v");
} catch (e) {
return null;
}
};
const queryHTMLElement = selector => {
const el = $(selector);
return el instanceof HTMLElement ? el : null;
};
const getPlayer = () => $("#movie_player");
const isSupportedTabPath = () => {
const path = window.location.pathname || "";
return /^\/(?:@[^/]+|channel\/[^/]+|c\/[^/]+|user\/[^/]+)(?:\/(videos|shorts|streams))?\/?$/.test(path) || /\/(videos|shorts|streams)\/?$/.test(path);
};
let id = "";
let observerSubId = null;
let observerFallbackTimerId = null;
const scheduleApplyRetry = (retryCount, selector, timeoutMs) => {
if (retryCount >= 12) {
return;
}
const waitFor = window.YouTubeUtils?.waitFor || window.YouTubeUtils?.waitForElement;
"function" != typeof waitFor ? requestAnimationFrame(() => apply(retryCount + 1)) : waitFor(selector, timeoutMs).then(() => apply(retryCount + 1)).catch(() => apply(retryCount + 1));
};
const apply = (retryCount = 0) => {
if ("" === id) {
window.console.warn("[Play All] Channel ID not yet determined");
return;
}
let parent = null;
if ("m.youtube.com" === location.host) {
parent = queryHTMLElement("ytm-feed-filter-chip-bar-renderer .chip-bar-contents, ytm-feed-filter-chip-bar-renderer > div");
} else {
const desktopParentSelectors = [ "chip-bar-view-model.ytChipBarViewModelHost", "ytd-feed-filter-chip-bar-renderer iron-selector#chips", "ytd-feed-filter-chip-bar-renderer #chips-wrapper", "yt-chip-cloud-renderer #chips", "yt-chip-cloud-renderer .yt-chip-cloud-renderer" ];
for (const selector of desktopParentSelectors) {
const candidate = $(selector);
if (candidate instanceof HTMLElement) {
parent = candidate;
break;
}
}
}
if (null === parent) {
const grid = queryHTMLElement("ytd-rich-grid-renderer, ytm-rich-grid-renderer, div.ytChipBarViewModelChipWrapper");
if (!grid) {
scheduleApplyRetry(retryCount, "ytd-rich-grid-renderer, ytm-rich-grid-renderer, div.ytChipBarViewModelChipWrapper", 1500);
return;
}
const chipBarInGrid = grid.querySelector("chip-bar-view-model.ytChipBarViewModelHost, ytd-feed-filter-chip-bar-renderer iron-selector#chips, ytd-feed-filter-chip-bar-renderer #chips-wrapper, yt-chip-cloud-renderer #chips");
if (chipBarInGrid instanceof HTMLElement) {
parent = chipBarInGrid;
} else {
if (retryCount < 8) {
scheduleApplyRetry(retryCount, "chip-bar-view-model.ytChipBarViewModelHost, ytd-feed-filter-chip-bar-renderer iron-selector#chips, ytd-feed-filter-chip-bar-renderer #chips-wrapper, yt-chip-cloud-renderer #chips", 1200);
return;
}
{
let existingContainer = grid.querySelector(".ytp-button-container");
if (!existingContainer) {
grid.insertAdjacentHTML("afterbegin", _createHTML('<div class="ytp-button-container"></div>'));
existingContainer = grid.querySelector(".ytp-button-container");
}
parent = existingContainer instanceof HTMLElement ? existingContainer : null;
}
}
}
if (!parent) {
window.console.warn("[Play All] Could not find parent container");
return;
}
if (parent.querySelector(".ytp-play-all-btn")) {
try {
window.YouTubeUtils?.logger?.debug?.("[Play All] Buttons already exist, skipping");
} catch (e) {}
return;
}
const path = window.location.pathname || "";
const [allPlaylist] = path.endsWith("/shorts") ? [ "UUSH" ] : path.endsWith("/streams") ? [ "UULV" ] : [ "UULF" ];
const playlistSuffix = id.startsWith("UC") ? id.substring(2) : id;
parent.insertAdjacentHTML("beforeend", _createHTML(`<a class="ytp-btn ytp-play-all-btn" href="/playlist?list=${allPlaylist}${playlistSuffix}&playnext=1&ytp-random=random&ytp-random-initial=1" title="${getPlayAllAriaLabel()}" aria-label="${getPlayAllAriaLabel()}">${getPlayAllLabel()}</a>`));
if ("m.youtube.com" === location.host && !parent.hasAttribute("data-ytp-delegated")) {
parent.setAttribute("data-ytp-delegated", "true");
parent.addEventListener("click", event => {
const tgt = event.target instanceof Element ? event.target : null;
const btn = tgt?.closest?.(".ytp-btn") ?? null;
if (btn && btn.href) {
event.preventDefault();
(href => {
window.location.assign(href);
})(btn.href);
}
});
}
};
let observerFrame = 0;
const runObserverWork = () => {
observerFrame = 0;
if (featureEnabled) {
removeButton();
apply();
}
};
const scheduleObserverWork = () => {
featureEnabled && (observerFrame || (observerFrame = "function" != typeof requestAnimationFrame ? setTimeout(runObserverWork, 16) : requestAnimationFrame(runObserverWork)));
};
const detachObserver = () => {
if (observerSubId && window.YouTubeMutationCoordinator?.unsubscribe) {
window.YouTubeMutationCoordinator.unsubscribe(observerSubId);
observerSubId = null;
}
if (observerFallbackTimerId) {
clearInterval(observerFallbackTimerId);
observerFallbackTimerId = null;
}
};
const addButton = async () => {
detachObserver();
if (!featureEnabled) {
return;
}
if (!isSupportedTabPath()) {
return;
}
const observeTarget = $("ytd-rich-grid-renderer") || $("chip-bar-view-model.ytChipBarViewModelHost") || $("ytm-feed-filter-chip-bar-renderer .iron-selected, ytm-feed-filter-chip-bar-renderer .chip-bar-contents .selected");
(observeTarget => {
detachObserver();
if (!featureEnabled || !observeTarget) {
return;
}
const coordinator = window.YouTubeMutationCoordinator;
if (coordinator?.watchTarget) {
observerSubId = "playall::observer";
coordinator.watchTarget(observerSubId, observeTarget, () => scheduleObserverWork(), {
attributes: !0,
childList: !0,
subtree: !0
});
} else {
observerFallbackTimerId = setInterval(() => {
scheduleObserverWork();
}, 500);
}
})(observeTarget);
if ($(".ytp-play-all-btn")) {
return;
}
const resolvedFromDom = (() => {
try {
const metaChannel = $('meta[itemprop="channelId"]');
const metaValue = metaChannel?.getAttribute("content");
if (metaValue && /^UC[a-zA-Z0-9_-]{22}$/.test(metaValue)) {
return metaValue;
}
const canonical = $('link[rel="canonical"]');
const canonicalHref = canonical?.getAttribute("href") || "";
const canonicalMatch = canonicalHref.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/);
if (canonicalMatch?.[1]) {
return canonicalMatch[1];
}
const browseNode = $('ytd-browse[page-subtype="channels"]');
const attrId = browseNode?.getAttribute?.("channel-id") || browseNode?.getAttribute?.("external-id");
if (attrId && /^UC[a-zA-Z0-9_-]{22}$/.test(attrId)) {
return attrId;
}
const channelHrefNode = $('ytd-channel-name a[href*="/channel/UC"], #channel-name a[href*="/channel/UC"], a[href^="/channel/UC"]');
const channelHref = channelHrefNode?.getAttribute?.("href") || "";
const channelHrefMatch = channelHref.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/);
if (channelHrefMatch?.[1]) {
return channelHrefMatch[1];
}
const href = location.href;
const fromUrl = href.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/);
if (fromUrl?.[1]) {
return fromUrl[1];
}
const initialData = window.ytInitialData;
const headerId = initialData?.header?.c4TabbedHeaderRenderer?.channelId || initialData?.header?.pageHeaderRenderer?.content?.pageHeaderViewModel?.metadata?.contentMetadataViewModel?.metadataRows?.[0]?.metadataParts?.find?.(p => /^UC[a-zA-Z0-9_-]{22}$/.test(p?.text?.content || ""))?.text?.content;
if (headerId && /^UC[a-zA-Z0-9_-]{22}$/.test(headerId)) {
return headerId;
}
if (window.ytcfg?.get) {
const cfgId = window.ytcfg.get("CHANNEL_ID");
if (cfgId && /^UC[a-zA-Z0-9_-]{22}$/.test(cfgId)) {
return cfgId;
}
}
} catch (e) {
window.console.warn("[Play All] Failed to resolve channel ID from DOM:", e);
}
return null;
})();
if (resolvedFromDom) {
id = resolvedFromDom;
apply();
} else {
try {
const canonical = $('link[rel="canonical"]');
if (canonical && canonical.href) {
const match = canonical.href.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/);
if (match && match[1]) {
id = match[1];
apply();
return;
}
const handleMatch = canonical.href.match(/\/@([^\/]+)/);
if (handleMatch) {
const pageData = $('ytd-browse[page-subtype="channels"]');
if (pageData) {
const channelId = pageData.getAttribute("channel-id");
if (channelId && channelId.startsWith("UC")) {
id = channelId;
apply();
return;
}
}
}
}
} catch (e) {
window.console.warn("[Play All] Error extracting channel ID from canonical:", e);
}
try {
const currentUrl = location.href;
const parsedUrl = new URL(currentUrl);
if ("www.youtube.com" !== parsedUrl.hostname && "youtube.com" !== parsedUrl.hostname && "m.youtube.com" !== parsedUrl.hostname) {
window.console.warn("[Play All] Skipping fetch for non-YouTube URL");
return;
}
const _fetchCtrl = new AbortController;
const _fetchTimer = setTimeout_(function() {
_fetchCtrl.abort();
}, 1e4);
let _fetchResp;
try {
_fetchResp = await fetch(currentUrl, {
signal: _fetchCtrl.signal
});
} finally {
clearTimeout(_fetchTimer);
}
const html = await _fetchResp.text();
const canonicalMatch = html.match(/<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/(UC[a-zA-Z0-9_-]{22})"/);
if (canonicalMatch && canonicalMatch[1]) {
id = canonicalMatch[1];
} else {
const channelIdMatch = html.match(/"channelId":"(UC[a-zA-Z0-9_-]{22})"/);
channelIdMatch && channelIdMatch[1] && (id = channelIdMatch[1]);
}
id ? apply() : window.console.warn("[Play All] Could not extract channel ID");
} catch (e) {
window.console.error("[Play All] Error fetching channel data:", e);
}
}
};
const stopAddButtonRetries = () => {
if (addButtonRetryTimer) {
clearTimeout(addButtonRetryTimer);
"function" == typeof addButtonRetryTimer.cancel && addButtonRetryTimer.cancel();
}
addButtonRetryTimer = null;
};
const queueDesktopAddButton = (reset = !0) => {
if ("m.youtube.com" === location.host) {
addButton();
return;
}
reset && stopAddButtonRetries();
const scheduler = window.YouTubeUtils?.createRetryScheduler?.({
label: "playall-add-button",
interval: 120,
maxAttempts: 80,
check: () => {
if (!featureEnabled || !isSupportedTabPath()) {
return !0;
}
addButton();
return !!$(".ytp-play-all-btn");
}
});
scheduler ? addButtonRetryTimer = scheduler : requestAnimationFrame(addButton);
};
const removeButton = () => {
$$(".ytp-play-all-btn, .ytp-random-badge, .ytp-random-notice").forEach(element => element.remove());
};
let playAllRuntimeStarted = !1;
const startPlayAllRuntime = () => {
if (playAllRuntimeStarted) {
return;
}
playAllRuntimeStarted = !0;
if ("m.youtube.com" === location.host) {
let lastUrl = location.href;
const checkUrlChange = () => {
if (location.href !== lastUrl) {
lastUrl = location.href;
addButton();
}
};
const _ytpNavHandler = () => setTimeout(checkUrlChange, 50);
if (_cm?.registerListener) {
_cm.registerListener(window, "ytp-history-navigate", _ytpNavHandler, {
passive: !0
});
_cm.registerListener(window, "popstate", checkUrlChange, {
passive: !0
});
} else {
window.addEventListener("ytp-history-navigate", _ytpNavHandler, {
passive: !0
});
window.addEventListener("popstate", checkUrlChange, {
passive: !0
});
}
addButton();
} else {
const _navStartHandler = () => {
stopAddButtonRetries();
removeButton();
id = "";
};
const _navFinishHandler = () => {
queueDesktopAddButton();
setTimeout(function() {
queueDesktopAddButton(!1);
}, 120);
setTimeout(function() {
queueDesktopAddButton(!1);
}, 600);
setTimeout_(function() {
queueDesktopAddButton(!1);
}, 1400);
setTimeout_(function() {
queueDesktopAddButton(!1);
}, 2800);
};
const _pageshowHandler = () => setTimeout(function() {
queueDesktopAddButton();
}, 120);
const _visChangeHandler = () => {
"visible" === document.visibilityState && queueDesktopAddButton();
};
if (_cm?.registerListener) {
_cm.registerListener(window, "yt-navigate-start", _navStartHandler);
_cm.registerListener(window, "yt-navigate-finish", _navFinishHandler);
_cm.registerListener(document, "yt-page-data-updated", _navFinishHandler);
_cm.registerListener(document, "yt-page-data-fetched", _navFinishHandler);
_cm.registerListener(window, "pageshow", _pageshowHandler);
_cm.registerListener(document, "visibilitychange", _visChangeHandler);
} else {
window.addEventListener("yt-navigate-start", _navStartHandler);
window.addEventListener("yt-navigate-finish", _navFinishHandler);
document.addEventListener("yt-page-data-updated", _navFinishHandler);
document.addEventListener("yt-page-data-fetched", _navFinishHandler);
window.addEventListener("pageshow", _pageshowHandler);
document.addEventListener("visibilitychange", _visChangeHandler);
}
try {
onDomReady(() => queueDesktopAddButton(!1));
setTimeout(function() {
queueDesktopAddButton(!1);
}, 50);
setTimeout(function() {
queueDesktopAddButton(!1);
}, 400);
setTimeout_(function() {
queueDesktopAddButton(!1);
}, 1200);
} catch (e) {}
try {
window.addEventListener("ytp:nav-refresh", function() {
try {
queueDesktopAddButton(!1);
} catch (e) {}
});
} catch (e) {}
}
const _settingsUpdHandler = e => {
try {
const custom = e instanceof CustomEvent ? e : null;
const nextEnabled = !1 !== custom?.detail?.enablePlayAll;
if (nextEnabled === featureEnabled) {
return;
}
setFeatureEnabled(nextEnabled);
} catch (e) {
setFeatureEnabled(window.YouTubeUtils?.loadFeatureEnabled?.("enablePlayAll") ?? !0);
}
};
_cm?.registerListener ? _cm.registerListener(window, "youtube-plus-settings-updated", _settingsUpdHandler) : window.addEventListener("youtube-plus-settings-updated", _settingsUpdHandler);
(() => {
if ("m.youtube.com" === location.host) {
return;
}
const getRandomConfig = () => {
const params = new URLSearchParams(window.location.search);
const modeParam = params.get("ytp-random");
if (!modeParam || "0" === modeParam) {
return null;
}
const list = params.get("list") || "";
return list ? {
params,
mode: "random",
list,
storageKey: `ytp-random-${list}`
} : null;
};
const getStorage = storageKey => {
try {
return JSON.parse(localStorage.getItem(storageKey) || "{}");
} catch (e) {
return {};
}
};
const isWatched = (storageKey, videoId) => getStorage(storageKey)[videoId] || !1;
const markWatched = (storageKey, videoId) => {
localStorage.setItem(storageKey, JSON.stringify({
...getStorage(storageKey),
[videoId]: !0
}));
document.querySelectorAll("#wc-endpoint[href*=zsA3X40nz9w]").forEach(element => element.parentElement?.setAttribute("hidden", ""));
};
const playNextRandom = (cfg, reload = !1) => {
const playerInstance = getPlayer();
playerInstance && "function" == typeof playerInstance.pauseVideo && playerInstance.pauseVideo();
const videos = Object.entries(getStorage(cfg.storageKey)).filter(([_, watched]) => !watched);
const params = new URLSearchParams(window.location.search);
if (0 === videos.length) {
return;
}
let videoIndex = Math.floor(Math.random() * videos.length);
videoIndex < 0 && (videoIndex = 0);
videoIndex >= videos.length && (videoIndex = videos.length - 1);
if (reload) {
params.set("v", videos[videoIndex][0]);
params.set("ytp-random", cfg.mode);
params.delete("t");
params.delete("index");
params.delete("ytp-random-initial");
window.location.href = `${window.location.pathname}?${params.toString()}`;
} else {
try {
const listId = params.get("list") || "";
((v, list, ytpRandom = null) => {
if ("m.youtube.com" === location.host) {
const url = `/watch?v=${v}&list=${list}${null !== ytpRandom ? `&ytp-random=${ytpRandom}` : ""}`;
window.location.href = url;
} else {
try {
const playlistPanel = $("ytd-playlist-panel-renderer #items");
if (playlistPanel) {
const redirector = document.createElement("a");
redirector.className = "yt-simple-endpoint style-scope ytd-playlist-panel-video-renderer";
redirector.setAttribute("hidden", "");
redirector.data = {
commandMetadata: {
webCommandMetadata: {
url: `/watch?v=${v}&list=${list}${null !== ytpRandom ? `&ytp-random=${ytpRandom}` : ""}`,
webPageType: "WEB_PAGE_TYPE_WATCH",
rootVe: 3832
}
},
watchEndpoint: {
videoId: v,
playlistId: list
}
};
playlistPanel.append(redirector);
redirector.click();
} else {
const url = `/watch?v=${v}&list=${list}${null !== ytpRandom ? `&ytp-random=${ytpRandom}` : ""}`;
window.location.href = url;
}
} catch (e) {
const url = `/watch?v=${v}&list=${list}${null !== ytpRandom ? `&ytp-random=${ytpRandom}` : ""}`;
window.location.href = url;
}
}
})(videos[videoIndex][0], listId, cfg.mode);
} catch (error) {
window.console.error("[Play All] Error using redirect(), falling back to manual redirect:", error);
const redirector = document.createElement("a");
redirector.className = "yt-simple-endpoint style-scope ytd-playlist-panel-video-renderer";
redirector.setAttribute("hidden", "");
redirector.data = {
commandMetadata: {
webCommandMetadata: {
url: `/watch?v=${videos[videoIndex][0]}&list=${params.get("list")}&ytp-random=${cfg.mode}`,
webPageType: "WEB_PAGE_TYPE_WATCH",
rootVe: 3832
}
},
watchEndpoint: {
videoId: videos[videoIndex][0],
playlistId: params.get("list")
}
};
const listContainer = $("ytd-playlist-panel-renderer #items");
listContainer instanceof HTMLElement ? listContainer.append(redirector) : document.body.appendChild(redirector);
redirector.click();
}
}
};
let applyRetryTimeoutId = null;
let progressIntervalId = null;
stopRandomPlayTimers = () => {
applyRetryTimeoutId && ("number" == typeof applyRetryTimeoutId ? clearTimeout(applyRetryTimeoutId) : applyRetryTimeoutId.stop());
applyRetryTimeoutId = null;
progressIntervalId && "boolean" != typeof progressIntervalId && clearInterval(progressIntervalId);
progressIntervalId = null;
};
const applyRandomPlay = cfg => {
if (!featureEnabled) {
return;
}
if (!window.location.pathname.endsWith("/watch")) {
return;
}
const playlistContainer = $("#secondary ytd-playlist-panel-renderer");
if (null === playlistContainer) {
return;
}
if (playlistContainer.hasAttribute("ytp-random")) {
return;
}
playlistContainer.setAttribute("ytp-random", "applied");
const headerContainer = playlistContainer.querySelector("#header");
headerContainer && !headerContainer.querySelector(".ytp-random-notice") && headerContainer.insertAdjacentHTML("beforeend", _createHTML('<span class="ytp-random-notice">Play All mode</span>'));
const storage = getStorage(cfg.storageKey);
const anchors = [];
[ "#wc-endpoint", "ytd-playlist-panel-video-renderer a#wc-endpoint", "ytd-playlist-panel-video-renderer a", "a#video-title", '#secondary ytd-playlist-panel-renderer a[href*="/watch?"]' ].forEach(sel => {
playlistContainer.querySelectorAll(sel).forEach(a => {
a instanceof Element && "A" === a.tagName && anchors.push(a);
});
});
const uniq = [];
const seen = new Set;
anchors.forEach(a => {
const href = a.href || a.getAttribute("href") || "";
if (!seen.has(href)) {
seen.add(href);
uniq.push(a);
}
});
uniq.forEach(element => {
let videoId = null;
try {
videoId = new URL(element.href, window.location.origin).searchParams.get("v");
} catch (e) {
videoId = new URLSearchParams(element.search || "").get("v");
}
if (!videoId) {
return;
}
isWatched(cfg.storageKey, videoId) || (storage[videoId] = !1);
try {
const u = new URL(element.href, window.location.origin);
u.searchParams.set("ytp-random", cfg.mode);
element.href = u.toString();
} catch (e) {}
element.setAttribute("data-ytp-random-link", "true");
const entryKey = getVideoId(element.href);
entryKey && isWatched(cfg.storageKey, entryKey) && element.parentElement?.setAttribute("hidden", "");
});
if (playlistContainer && !playlistContainer.hasAttribute("data-ytp-random-delegated")) {
playlistContainer.setAttribute("data-ytp-random-delegated", "true");
playlistContainer.addEventListener("click", event => {
const tgt = event.target instanceof Element ? event.target : null;
const link = tgt?.closest?.("a[data-ytp-random-link]") ?? null;
if (link && link.href) {
event.preventDefault();
(href => {
window.location.href = href;
})(link.href);
}
});
}
localStorage.setItem(cfg.storageKey, JSON.stringify(storage));
const currentVideoId = getVideoId(location.href);
if ("1" === cfg.params.get("ytp-random-initial") || currentVideoId && isWatched(cfg.storageKey, currentVideoId)) {
playNextRandom(cfg);
return;
}
const header = playlistContainer.querySelector("h3 a");
if (header && "A" === header.tagName) {
const anchorHeader = header;
anchorHeader.insertAdjacentHTML("beforeend", _createHTML(' <span class="ytp-badge ytp-random-badge">Play All <span style="font-size: 2rem; vertical-align: top">&times;</span></span>'));
anchorHeader.href = "#";
const badge = anchorHeader.querySelector(".ytp-random-badge");
badge && badge.addEventListener("click", event => {
event.preventDefault();
localStorage.removeItem(cfg.storageKey);
const params = new URLSearchParams(location.search);
params.delete("ytp-random");
window.location.href = `${window.location.pathname}?${params.toString()}`;
});
}
const _keydownHandler = event => {
if (event instanceof KeyboardEvent && event.shiftKey && "n" === event.key.toLowerCase()) {
event.stopImmediatePropagation();
event.preventDefault();
const videoId = getVideoId(location.href);
videoId && markWatched(cfg.storageKey, videoId);
playNextRandom(cfg, !0);
}
};
_cm?.registerListener ? _cm.registerListener(document, "keydown", _keydownHandler, !0) : document.addEventListener("keydown", _keydownHandler, !0);
if (progressIntervalId) {
return;
}
const videoEl = $("video");
if (!videoEl) {
return;
}
videoEl.addEventListener("timeupdate", () => {
const videoId = getVideoId(location.href);
const params = new URLSearchParams(location.search);
params.set("ytp-random", cfg.mode);
window.history.replaceState({}, "", `${window.location.pathname}?${params.toString()}`);
const player = getPlayer();
if (!player || "function" != typeof player.getProgressState) {
return;
}
const progressState = player.getProgressState();
if (!progressState || "number" != typeof progressState.current || "number" != typeof progressState.duration) {
return;
}
if (!$(".ad-interrupting")) {
progressState.current / progressState.duration >= .9 && videoId && markWatched(cfg.storageKey, videoId);
if (progressState.current >= progressState.duration - 2) {
"function" == typeof player.pauseVideo && player.pauseVideo();
"function" == typeof player.seekTo && player.seekTo(0);
playNextRandom(cfg);
}
}
const nextButton = $('#ytd-player .ytp-next-button.ytp-button:not([ytp-random="applied"])');
if (nextButton instanceof HTMLElement) {
const newButton = document.createElement("span");
newButton.className = nextButton.className;
((container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
})(newButton, nextButton.innerHTML);
nextButton.replaceWith(newButton);
newButton.setAttribute("ytp-random", "applied");
newButton.addEventListener("click", () => {
videoId && markWatched(cfg.storageKey, videoId);
playNextRandom(cfg);
});
}
}, {
passive: !0
});
progressIntervalId = !0;
};
scheduleApplyRandomPlay = () => {
if (!featureEnabled) {
return;
}
stopRandomPlayTimers?.();
if (!window.location.pathname.endsWith("/watch")) {
return;
}
const scheduler = window.YouTubeUtils?.createRetryScheduler?.({
check: () => {
const cfg = getRandomConfig();
if (!cfg) {
return !1;
}
try {
const current = localStorage.getItem(cfg.storageKey);
current && Array.isArray(JSON.parse(current)) && localStorage.removeItem(cfg.storageKey);
} catch (e) {
localStorage.removeItem(cfg.storageKey);
}
applyRandomPlay(cfg);
return !!$("#secondary ytd-playlist-panel-renderer[ytp-random]");
},
maxAttempts: 30,
interval: 250
});
scheduler && (applyRetryTimeoutId = scheduler);
};
const onNavigate = () => {
if (featureEnabled) {
stopRandomPlayTimers?.();
scheduleApplyRandomPlay?.();
} else {
stopRandomPlayTimers?.();
}
};
onNavigate();
const _navFinishRandom = () => setTimeout(onNavigate, 200);
_cm?.registerListener ? _cm.registerListener(window, "yt-navigate-finish", _navFinishRandom) : window.addEventListener("yt-navigate-finish", _navFinishRandom);
})();
};
window.YouTubePlusLazyLoader?.register ? window.YouTubePlusLazyLoader.register("playall", startPlayAllRuntime, {
priority: 55,
delay: 0,
shouldLoad: () => !0
}) : startPlayAllRuntime();
})().catch(error => window.console.error("%cytp - YouTube Play All\n", "color: var(--yt-playall-accent-purple); font-size: 32px; font-weight: bold", error));

!(function() {
"use strict";
let featureEnabled = !0;
let activeCleanup = null;
const setFeatureEnabled = nextEnabled => {
featureEnabled = !1 !== nextEnabled;
if (featureEnabled) {
try {
initResume();
} catch (e) {}
} else {
const existingOverlay = byId(OVERLAY_ID);
if (existingOverlay) {
try {
existingOverlay.remove();
} catch (e) {}
}
if ("function" == typeof activeCleanup) {
try {
activeCleanup();
} catch (e) {}
activeCleanup = null;
}
}
};
featureEnabled = window.YouTubeUtils?.loadFeatureEnabled?.("enableResumeTime") ?? !0;
const {$, byId} = window.YouTubeUtils || {};
const onDomReady = window.YouTubeUtils?.onDomReady || (cb => {
"loading" !== document.readyState ? cb() : document.addEventListener("DOMContentLoaded", cb, {
once: !0
});
});
const setupResumeDelegation = (() => {
let attached = !1;
return () => {
if (attached) {
return;
}
attached = !0;
const delegator = window.YouTubePlusEventDelegation;
const handler = (_ev, target) => {
const action = target?.getAttribute("data-ytp-resume-action");
if (!action || !target) {
return;
}
const wrap = target.closest(".ytp-resume-overlay");
wrap && ("resume" === action ? wrap.dispatchEvent(new CustomEvent("ytp:resume", {
bubbles: !0
})) : "restart" === action && wrap.dispatchEvent(new CustomEvent("ytp:restart", {
bubbles: !0
})));
};
if (delegator?.on) {
delegator.on(document, "click", ".ytp-resume-btn", handler);
delegator.on(document, "keydown", ".ytp-resume-btn", (ev, target) => {
if ("Enter" === ev.key || " " === ev.key) {
ev.preventDefault();
handler(0, target);
}
});
} else {
const clickHandler = ev => {
const target1 = ev.target?.closest?.(".ytp-resume-btn");
target1 && handler(0, target1);
};
const keyHandler = ev => {
const target2 = ev.target?.closest?.(".ytp-resume-btn");
if (target2 && ("Enter" === ev.key || " " === ev.key)) {
ev.preventDefault();
handler(0, target2);
}
};
if (window.YouTubeUtils?.cleanupManager?.registerListener) {
window.YouTubeUtils.cleanupManager.registerListener(document, "click", clickHandler, !0);
window.YouTubeUtils.cleanupManager.registerListener(document, "keydown", keyHandler, !0);
} else {
document.addEventListener("click", clickHandler, !0);
document.addEventListener("keydown", keyHandler, !0);
}
}
};
})();
const OVERLAY_ID = "yt-resume-overlay";
const _localFallback = {
resumePlayback: {
en: "Resume playback?",
ru: "Продолжить воспроизведение?"
},
resume: {
en: "Resume",
ru: "Продолжить"
},
startOver: {
en: "Start over",
ru: "Начать сначала"
}
};
const t = (key, params = {}) => {
const U = window.YouTubeUtils;
if (U?.t) {
return U.t(key, params);
}
const htmlLang = document.documentElement.lang || "en";
const lang = htmlLang.startsWith("ru") ? "ru" : "en";
const val = _localFallback[key]?.[lang] || _localFallback[key]?.en || key;
if (!params || 0 === Object.keys(params).length) {
return val;
}
let result = val;
for (const [k, v] of Object.entries(params)) {
const token = `{${k}}`;
result = result.split(token).join(String(v));
}
return result;
};
const readStorage = () => {
try {
return JSON.parse(localStorage.getItem("youtube_resume_times_v1") || "{}");
} catch (e) {
return {};
}
};
const getVideoId = () => {
try {
const urlParams = new URLSearchParams(window.location.search);
const videoIdFromUrl = urlParams.get("v");
if (videoIdFromUrl) {
return videoIdFromUrl;
}
const meta = $('link[rel="canonical"]');
if (meta && meta.href) {
const u = new URL(meta.href);
const vParam = u.searchParams.get("v");
if (vParam) {
return vParam;
}
const pathMatch = u.pathname.match(/\/(watch|shorts)\/([^\/\?]+)/);
if (pathMatch && pathMatch[2]) {
return pathMatch[2];
}
}
if (window.ytInitialPlayerResponse && window.ytInitialPlayerResponse.videoDetails && window.ytInitialPlayerResponse.videoDetails.videoId) {
return window.ytInitialPlayerResponse.videoDetails.videoId;
}
const pathMatch = window.location.pathname.match(/\/(watch|shorts)\/([^\/\?]+)/);
return pathMatch && pathMatch[2] ? pathMatch[2] : null;
} catch (e) {
return null;
}
};
const createOverlay = (seconds, onResume, onRestart) => {
if (byId(OVERLAY_ID)) {
return null;
}
const wrap = document.createElement("div");
wrap.id = OVERLAY_ID;
wrap.setAttribute("role", "alertdialog");
wrap.setAttribute("aria-label", t("resumePlayback") || "Resume playback");
const player = $("#movie_player");
const inPlayer = !!player;
const resumeOverlayStyles = "\n      .ytp-resume-overlay{min-width:180px;max-width:36vw;background:var(--yt-glass-bg);color:var(--yt-text-primary,#fff);padding:12px 14px;border-radius:12px;backdrop-filter:blur(8px) saturate(150%);-webkit-backdrop-filter:blur(8px) saturate(150%);box-shadow:0 14px 40px var(--yt-shadow-flyout);border:1.25px solid var(--yt-surface-overlay-border);font-family:Arial,Helvetica,sans-serif;display:flex;flex-direction:column;align-items:center;text-align:center;animation:ytp-resume-fadein 0.3s ease-out}\n      @keyframes ytp-resume-fadein{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}\n      .ytp-resume-overlay .ytp-resume-title{font-weight:600;margin-bottom:8px;font-size:13px}\n      .ytp-resume-overlay .ytp-resume-actions{display:flex;gap:8px;justify-content:center;margin-top:6px}\n      .ytp-resume-overlay .ytp-resume-btn{padding:6px 12px;border-radius:8px;border:none;cursor:pointer;font-size:12px;font-weight:500;transition:all 0.2s ease;outline:none}\n      .ytp-resume-overlay .ytp-resume-btn:focus{box-shadow:0 0 0 2px var(--yt-glass-border);outline:2px solid transparent}\n      .ytp-resume-overlay .ytp-resume-btn:hover{transform:translateY(-1px)}\n      .ytp-resume-overlay .ytp-resume-btn:active{transform:translateY(0)}\n      .ytp-resume-overlay .ytp-resume-btn.primary{background:var(--yt-accent-secondary);color:#fff}\n      .ytp-resume-overlay .ytp-resume-btn.primary:hover{background:var(--yt-accent-secondary-light)}\n      .ytp-resume-overlay .ytp-resume-btn.ghost{background:var(--yt-button-bg);color:var(--yt-text-primary)}\n      .ytp-resume-overlay .ytp-resume-btn.ghost:hover{background:var(--yt-hover-bg)}\n    ";
try {
if (window.YouTubeUtils && YouTubeUtils.StyleManager) {
YouTubeUtils.StyleManager.add("ytp-resume-overlay-styles", resumeOverlayStyles);
} else if (!byId("ytp-resume-overlay-styles")) {
const s = document.createElement("style");
s.id = "ytp-resume-overlay-styles";
s.textContent = resumeOverlayStyles;
(document.head || document.documentElement).appendChild(s);
}
} catch (e) {
window.console.warn("[YouTube+] Failed to inject resume overlay styles:", e);
}
if (inPlayer) {
try {
const playerStyle = window.getComputedStyle(player);
"static" === playerStyle.position && (player.style.position = "relative");
} catch (e) {}
wrap.className = "ytp-resume-overlay ytp-plus-resume-overlay";
wrap.style.position = "absolute";
wrap.style.left = "50%";
wrap.style.bottom = "5%";
wrap.style.transform = "translate(-50%,-50%)";
wrap.style.zIndex = "9999";
wrap.style.pointerEvents = "auto";
player.appendChild(wrap);
} else {
wrap.className = "ytp-resume-overlay ytp-plus-resume-overlay";
wrap.style.position = "fixed";
wrap.style.left = "50%";
wrap.style.bottom = "5%";
wrap.style.transform = "translate(-50%,-50%)";
wrap.style.zIndex = "1200";
wrap.style.pointerEvents = "auto";
document.body.appendChild(wrap);
}
const title = document.createElement("div");
title.className = "ytp-resume-title";
title.textContent = `${t("resumePlayback")} (${formatTime(seconds)})`;
const btnResume = document.createElement("button");
btnResume.className = "ytp-resume-btn primary";
btnResume.textContent = t("resume");
btnResume.setAttribute("aria-label", `${t("resume")} at ${formatTime(seconds)}`);
btnResume.tabIndex = 0;
btnResume.setAttribute("data-ytp-resume-action", "resume");
const btnRestart = document.createElement("button");
btnRestart.className = "ytp-resume-btn ghost";
btnRestart.textContent = t("startOver");
btnRestart.setAttribute("aria-label", t("startOver"));
btnRestart.tabIndex = 0;
btnRestart.setAttribute("data-ytp-resume-action", "restart");
setupResumeDelegation();
wrap.addEventListener("ytp:resume", () => (() => {
try {
onResume();
} catch (err) {
window.console.error("[YouTube+] Resume error:", err);
}
try {
wrap.remove();
} catch (e) {}
})(), {
once: !0
});
wrap.addEventListener("ytp:restart", () => (() => {
try {
onRestart();
} catch (err) {
window.console.error("[YouTube+] Restart error:", err);
}
try {
wrap.remove();
} catch (e) {}
})(), {
once: !0
});
const actions = document.createElement("div");
actions.className = "ytp-resume-actions";
actions.appendChild(btnResume);
actions.appendChild(btnRestart);
wrap.appendChild(title);
wrap.appendChild(actions);
try {
requestAnimationFrame(() => {
btnResume.focus();
});
} catch (e) {}
const to = setTimeout(() => {
try {
wrap.remove();
} catch (e) {}
}, 1e4);
const cancel = () => clearTimeout(to);
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.register(() => {
try {
cancel();
} catch (e) {}
try {
wrap.remove();
} catch (e) {}
});
return cancel;
};
const formatTime = secs => {
const s = Math.floor(secs % 60).toString().padStart(2, "0");
const m = Math.floor(secs / 60 % 60).toString();
const h = Math.floor(secs / 3600);
return h ? `${h}:${m.padStart(2, "0")}:${s}` : `${m}:${s}`;
};
const attachResumeHandlers = videoEl => {
if (!featureEnabled) {
return null;
}
if (!videoEl || "VIDEO" !== videoEl.tagName) {
window.console.warn("[YouTube+] Invalid video element for resume handlers");
return;
}
if (videoEl._ytpResumeAttached) {
return;
}
videoEl._ytpResumeAttached = !0;
const getCurrentVideoId = () => getVideoId();
const vid = getCurrentVideoId();
if (!vid) {
return;
}
const storage = readStorage();
const saved = storage[vid];
let timeUpdateHandler = null;
let lastSavedAt = 0;
const startSaving = () => {
if (!timeUpdateHandler) {
timeUpdateHandler = () => {
try {
const currentVid = getCurrentVideoId();
if (!currentVid) {
return;
}
const t = Math.floor(videoEl.currentTime || 0);
const now = Date.now();
if (t && (!lastSavedAt || now - lastSavedAt > 800)) {
const s = readStorage();
s[currentVid] = t;
(obj => {
try {
localStorage.setItem("youtube_resume_times_v1", JSON.stringify(obj));
} catch (e) {
window.console.warn("[YouTube+] Failed to save resume time:", e);
}
})(s);
lastSavedAt = now;
}
} catch (e) {
window.console.warn("[YouTube+] Error saving playback time:", e);
}
};
videoEl.addEventListener("timeupdate", timeUpdateHandler, {
passive: !0
});
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.register(() => {
try {
timeUpdateHandler && videoEl.removeEventListener("timeupdate", timeUpdateHandler);
} catch (e) {}
});
}
};
if (saved && saved > 5 && !byId(OVERLAY_ID)) {
const cancelTimeout = createOverlay(saved, () => {
try {
videoEl.currentTime = saved;
videoEl.play();
} catch (e) {
window.console.error("[YouTube+] Failed to resume playback:", e);
}
}, () => {
try {
videoEl.currentTime = 0;
videoEl.play();
} catch (e) {
window.console.error("[YouTube+] Failed to start over:", e);
}
});
try {
const overlayEl = byId(OVERLAY_ID);
overlayEl && vid && overlayEl.setAttribute("data-vid", vid);
} catch (e) {}
window.YouTubeUtils && YouTubeUtils.cleanupManager && cancelTimeout && YouTubeUtils.cleanupManager.register(cancelTimeout);
}
const onPlay = () => startSaving();
const onPause = () => (() => {
if (timeUpdateHandler) {
try {
videoEl.removeEventListener("timeupdate", timeUpdateHandler);
} catch (e) {}
timeUpdateHandler = null;
lastSavedAt = 0;
}
})();
videoEl.addEventListener("play", onPlay, {
passive: !0
});
videoEl.addEventListener("pause", onPause, {
passive: !0
});
const cleanupHandlers = () => {
try {
videoEl.removeEventListener("play", onPlay);
videoEl.removeEventListener("pause", onPause);
timeUpdateHandler && videoEl.removeEventListener("timeupdate", timeUpdateHandler);
delete videoEl._ytpResumeAttached;
} catch (err) {
window.console.error("[YouTube+] Resume cleanup error:", err);
}
};
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.register(cleanupHandlers);
activeCleanup = cleanupHandlers;
return cleanupHandlers;
};
const initResume = () => {
if (!featureEnabled) {
const existingOverlay = byId(OVERLAY_ID);
if (existingOverlay) {
try {
existingOverlay.remove();
} catch (e) {}
}
return;
}
if ("/watch" !== window.location.pathname) {
const existingOverlay = byId(OVERLAY_ID);
existingOverlay && existingOverlay.remove();
return;
}
const currentVid = getVideoId();
const existingOverlay = byId(OVERLAY_ID);
if (existingOverlay) {
try {
existingOverlay.dataset && existingOverlay.getAttribute("data-vid") === currentVid || existingOverlay.remove();
} catch (e) {
try {
existingOverlay.remove();
} catch (e) {}
}
}
const videoEl = (() => {
const selectors = [ "video.html5-main-video", "video.video-stream", "#movie_player video", "video" ];
for (const selector of selectors) {
const video = $(selector);
if (video && "VIDEO" === video.tagName) {
return video;
}
}
return null;
})();
if (videoEl) {
attachResumeHandlers(videoEl);
} else {
const waitFor = window.YouTubeUtils?.waitForElement || window.YouTubeUtils?.waitFor;
"function" == typeof waitFor ? waitFor("video", 1200).then(() => initResume()) : requestAnimationFrame(initResume);
}
};
const onNavigate = () => setTimeout(initResume, 150);
onDomReady(initResume);
window && window.document && (window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(document, "yt-navigate-finish", onNavigate, {
passive: !0
}) : document.addEventListener("yt-navigate-finish", onNavigate, {
passive: !0
}));
const settingsUpdatedHandler = e => {
try {
const nextEnabled = !1 !== e?.detail?.enableResumeTime;
if (nextEnabled === featureEnabled) {
return;
}
setFeatureEnabled(nextEnabled);
} catch (e) {
setFeatureEnabled(window.YouTubeUtils?.loadFeatureEnabled?.("enableResumeTime") ?? !0);
}
};
window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(window, "youtube-plus-settings-updated", settingsUpdatedHandler) : window.addEventListener("youtube-plus-settings-updated", settingsUpdatedHandler);
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const isRelevantRoute = () => {
try {
const path = window.location.pathname || "";
return "/watch" === path || path.startsWith("/shorts");
} catch (e) {
return !1;
}
};
const initZoomModule = () => {
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
let featureEnabled = !0;
const clearZoomUI = () => {
try {
const ui = byId("ytp-zoom-control");
ui && ui.remove();
} catch (e) {}
try {
const styles = byId("ytp-zoom-styles");
styles && styles.remove();
} catch (e) {}
try {
const video = findVideoElement();
if (video) {
video.style.transform = "";
video.style.willChange = "";
video.style.transition = "";
video.style.cursor = "";
}
} catch (e) {}
};
const setFeatureEnabled = nextEnabled => {
featureEnabled = !1 !== nextEnabled;
if (featureEnabled) {
try {
initZoom();
} catch (e) {}
} else {
clearZoomUI();
}
};
const canRenderZoomUI = () => {
try {
return featureEnabled && isRelevantRoute() && !(() => {
try {
const mini = document.querySelector("ytd-miniplayer[active], ytd-miniplayer[enabled]");
if (mini && mini instanceof HTMLElement) {
if (null !== mini.offsetParent) {
return !0;
}
if (mini.getClientRects().length > 0) {
return !0;
}
}
const miniWatch = document.querySelector("ytd-watch-flexy[is-miniplayer], ytd-watch-flexy[miniplayer-is-active]");
return Boolean(miniWatch);
} catch (e) {
return !1;
}
})();
} catch (e) {
return !1;
}
};
featureEnabled = window.YouTubeUtils?.loadFeatureEnabled?.("enableZoom") ?? !0;
const {$, byId} = window.YouTubeUtils || {};
const ZOOM_PAN_STORAGE_KEY = "ytp_zoom_pan";
const RESTORE_LOG_KEY = "ytp_zoom_restore_log";
const DEFAULT_ZOOM = 1;
const MIN_ZOOM = .5;
const MAX_ZOOM = 2.5;
const ZOOM_STEP = .05;
const FULLSCREEN_APPLY_DELAY = 80;
const FULLSCREEN_APPLY_RETRIES = 4;
const FULLSCREEN_APPLY_RETRY_DELAY = 120;
function readZoomPan() {
try {
const raw = localStorage.getItem(ZOOM_PAN_STORAGE_KEY);
if (!raw) {
return {
zoom: DEFAULT_ZOOM,
panX: 0,
panY: 0
};
}
const obj = JSON.parse(raw);
const zoom = Number(obj && obj.zoom) || DEFAULT_ZOOM;
const panX = Number(obj && obj.panX) || 0;
const panY = Number(obj && obj.panY) || 0;
return {
zoom,
panX,
panY
};
} catch (e) {
return {
zoom: DEFAULT_ZOOM,
panX: 0,
panY: 0
};
}
}
function saveZoomPan(zoom, panX, panY) {
try {
const obj = {
zoom: Number(zoom) || DEFAULT_ZOOM,
panX: Number(panX) || 0,
panY: Number(panY) || 0
};
localStorage.setItem(ZOOM_PAN_STORAGE_KEY, JSON.stringify(obj));
} catch (e) {
window.console.warn("[YouTube+] Failed to save zoom/pan settings:", e);
}
}
function logRestoreEvent(evt) {
try {
const entry = Object.assign({
time: (new Date).toISOString()
}, evt);
try {
const raw = sessionStorage.getItem(RESTORE_LOG_KEY);
const arr = raw ? JSON.parse(raw) : [];
arr.push(entry);
arr.length > 200 && arr.splice(0, arr.length - 200);
sessionStorage.setItem(RESTORE_LOG_KEY, JSON.stringify(arr));
} catch (e) {}
("undefined" != typeof window && window.YTP_DEBUG || window.YouTubePlusConfig?.debug) && window.console.warn("[YouTube+] Zoom restore:", entry);
} catch (e) {}
}
const findVideoElement = () => {
const selectors = [ "#movie_player video", "video.video-stream", "video" ];
for (const s of selectors) {
const v = $(s);
if (v && "VIDEO" === v.tagName) {
return v;
}
}
return null;
};
let _lastTransformApplied = "";
let _isApplyingTransform = !1;
const applyZoomToVideo = (videoEl, zoom, panX = 0, panY = 0, skipTransformTracking = !1, skipTransition = !1) => {
if (!videoEl) {
return;
}
const container = videoEl.parentElement || videoEl;
try {
skipTransformTracking || (_isApplyingTransform = !0);
container.style.overflow = "visible";
container.style.position && "static" !== container.style.position || (container.style.position = "relative");
videoEl.style.transformOrigin = "center center";
const transformStr = `translate(${panX.toFixed(2)}px, ${panY.toFixed(2)}px) scale(${zoom.toFixed(3)})`;
videoEl.style.transform = transformStr;
skipTransformTracking || (_lastTransformApplied = transformStr);
videoEl.style.willChange = 1 !== zoom ? "transform" : "auto";
videoEl.style.transition = skipTransition ? "none" : "transform .08s ease-out";
skipTransformTracking || setTimeout_(() => {
_isApplyingTransform = !1;
}, 100);
} catch (e) {
window.console.error("[YouTube+] applyZoomToVideo error:", e);
_isApplyingTransform = !1;
}
};
function createZoomUI() {
const player = $("#movie_player");
if (!player) {
return null;
}
if (byId("ytp-zoom-control")) {
return byId("ytp-zoom-control");
}
if (!byId("ytp-zoom-styles")) {
const s = document.createElement("style");
s.id = "ytp-zoom-styles";
s.textContent = "\n      /* Compact control bar matching YouTube control style */\n      #ytp-zoom-control{position: absolute; left: 12px; bottom: 70px; z-index: 2200; display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 24px; background: var(--yt-shadow-inset-strong); color: #fff; font-size: 12px; box-shadow: 0 2px 8px var(--yt-shadow-flyout); backdrop-filter: blur(6px);}\n      #ytp-zoom-control input[type=range]{width: 120px; -webkit-appearance: none; background: transparent; height: 24px;}\n      /* WebKit track */\n      #ytp-zoom-control input[type=range]::-webkit-slider-runnable-track{height: 4px; background: var(--yt-button-bg); border-radius: 3px;}\n      #ytp-zoom-control input[type=range]::-webkit-slider-thumb{-webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--yt-text-primary); box-shadow: 0 0 0 6px var(--yt-button-bg); margin-top: -4px;}\n      /* Firefox */\n      #ytp-zoom-control input[type=range]::-moz-range-track{height: 4px; background: var(--yt-button-bg); border-radius: 3px;}\n      #ytp-zoom-control input[type=range]::-moz-range-thumb{width: 12px; height: 12px; border-radius: 50%; background: #fff; border: none;}\n      #ytp-zoom-control .zoom-label{min-width:36px;text-align:center;font-size:11px;padding:0 6px;user-select:none}\n      #ytp-zoom-control::after{content:'Shift + Wheel to zoom';position:absolute;bottom:100%;right:0;padding:4px 8px;background:var(--yt-notification-bg);color:#fff;font-size:10px;border-radius:4px;white-space:nowrap;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .2s,transform .2s}\n      #ytp-zoom-control:hover::after{opacity:1;transform:translateY(-4px)}\n      #ytp-zoom-control .zoom-reset{background: var(--yt-button-bg); border: none; color: inherit; padding: 4px; display: flex; align-items: center; justify-content: center; border-radius: 50%; cursor: pointer; width: 28px; height: 28px;}\n      #ytp-zoom-control .zoom-reset:hover{background: var(--yt-hover-bg)}\n      #ytp-zoom-control .zoom-reset svg{display:block;width:14px;height:14px}\n      /* Hidden state to mirror YouTube controls autohide */\n      #ytp-zoom-control.ytp-hidden{opacity:0;transform:translateY(6px);pointer-events:none}\n      #ytp-zoom-control{transition:opacity .18s ease, transform .18s ease}\n    ";
(document.head || document.documentElement).appendChild(s);
}
const wrap = document.createElement("div");
wrap.id = "ytp-zoom-control";
const input = document.createElement("input");
input.type = "range";
input.min = String(MIN_ZOOM);
input.max = String(MAX_ZOOM);
input.step = String(ZOOM_STEP);
const label = document.createElement("div");
label.className = "zoom-label";
label.setAttribute("role", "status");
label.setAttribute("aria-live", "polite");
label.setAttribute("aria-label", "Current zoom level");
const reset = document.createElement("button");
reset.className = "zoom-reset";
reset.type = "button";
reset.setAttribute("aria-label", "Reset zoom");
reset.title = "Reset zoom";
((container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
})(reset, '\n    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">\n      <path d="M12 4V1l-5 5 5 5V7a7 7 0 1 1-7 7" stroke="currentColor" stroke-width="2" fill="none"/>\n    </svg>\n  ');
wrap.appendChild(input);
wrap.appendChild(label);
wrap.appendChild(reset);
let video = findVideoElement();
const stored = readZoomPan().zoom;
const initZoomVal = Number.isFinite(stored) && !Number.isNaN(stored) ? stored : DEFAULT_ZOOM;
const setZoom = z => {
const clamped = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Number(z)));
input.value = String(clamped);
const percentage = Math.round(100 * clamped);
label.textContent = `${percentage}%`;
label.setAttribute("aria-label", `Current zoom level ${percentage} percent`);
if (video) {
clampPan(clamped);
requestAnimationFrame(() => {
try {
applyZoomToVideo(video, clamped, panX, panY);
try {
video.style.cursor = clamped > 1 ? "grab" : "";
} catch (e) {}
} catch (err) {
window.console.error("[YouTube+] Apply zoom error:", err);
}
});
}
try {
saveZoomPan(clamped, panX, panY);
} catch (err) {
window.console.error("[YouTube+] Save zoom error:", err);
}
};
input.addEventListener("input", e => {
const target = e.target;
setZoom(target ? target.value : DEFAULT_ZOOM);
});
reset.addEventListener("click", () => {
try {
panX = 0;
panY = 0;
setZoom(DEFAULT_ZOOM);
try {
saveZoomPan(DEFAULT_ZOOM, 0, 0);
} catch (e) {
window.console.warn("[YouTube+] Failed to persist zoom reset:", e);
}
reset.style.transform = "scale(0.9)";
setTimeout_(() => {
reset.style.transform = "";
}, 150);
} catch (err) {
window.console.error("[YouTube+] Reset zoom error:", err);
}
});
let wheelThrottleTimer = null;
let panSaveTimer = null;
const scheduleSavePan = () => {
try {
panSaveTimer && clearTimeout(panSaveTimer);
panSaveTimer = setTimeout_(() => {
try {
const currentZoom = parseFloat(input.value) || readZoomPan().zoom || DEFAULT_ZOOM;
saveZoomPan(currentZoom, panX, panY);
} catch (err) {
window.console.error("[YouTube+] Save pan error:", err);
}
panSaveTimer = null;
}, 220);
} catch (err) {
window.console.error("[YouTube+] Schedule save pan error:", err);
}
};
const wheelHandler = ev => {
try {
if (!featureEnabled) {
return;
}
if (!ev.shiftKey) {
return;
}
ev.preventDefault();
if (wheelThrottleTimer) {
return;
}
wheelThrottleTimer = setTimeout_(() => {
wheelThrottleTimer = null;
}, 50);
const delta = ev.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
const current = readZoomPan().zoom || DEFAULT_ZOOM;
const newZoom = current + delta;
newZoom >= MIN_ZOOM && newZoom <= MAX_ZOOM && setZoom(newZoom);
} catch (err) {
window.console.error("[YouTube+] Wheel zoom error:", err);
}
};
player.addEventListener("wheel", wheelHandler, {
passive: !1
});
if (video) {
try {
video.addEventListener("wheel", wheelHandler, {
passive: !1
});
} catch (err) {
window.console.error("[YouTube+] Failed to attach wheel handler to video:", err);
}
}
const keydownHandler = ev => {
try {
if (!featureEnabled) {
return;
}
const active = document.activeElement;
if (active && ("INPUT" === active.tagName || "TEXTAREA" === active.tagName || active.isContentEditable)) {
return;
}
if ("+" === ev.key || "=" === ev.key) {
ev.preventDefault();
const current = readZoomPan().zoom || DEFAULT_ZOOM;
setZoom(Math.min(MAX_ZOOM, current + ZOOM_STEP));
} else if ("-" === ev.key) {
ev.preventDefault();
const current = readZoomPan().zoom || DEFAULT_ZOOM;
setZoom(Math.max(MIN_ZOOM, current - ZOOM_STEP));
}
} catch (e) {
window.console.error("[YouTube+] Keyboard zoom error:", e);
}
};
window.addEventListener("keydown", keydownHandler);
let panX = 0;
let panY = 0;
const mutationCoordinator = window.YouTubeMutationCoordinator;
let videoStyleObserver = null;
let dragging = !1;
let dragStartX = 0;
let dragStartY = 0;
let dragStartPanX = 0;
let dragStartPanY = 0;
const clampPan = (zoom = readZoomPan().zoom) => {
try {
if (!video) {
return;
}
const container = video.parentElement || video;
if (!container) {
return;
}
const containerRect = container.getBoundingClientRect();
if (!containerRect || 0 === containerRect.width || 0 === containerRect.height) {
return;
}
const baseW = video.videoWidth || video.offsetWidth || containerRect.width;
const baseH = video.videoHeight || video.offsetHeight || containerRect.height;
if (!(baseW && baseH && Number.isFinite(baseW) && Number.isFinite(baseH))) {
return;
}
const scaledW = baseW * zoom;
const scaledH = baseH * zoom;
const maxX = Math.max(0, (scaledW - containerRect.width) / 2);
const maxY = Math.max(0, (scaledH - containerRect.height) / 2);
Number.isFinite(maxX) && Number.isFinite(panX) && (panX = Math.max(-maxX, Math.min(maxX, panX)));
Number.isFinite(maxY) && Number.isFinite(panY) && (panY = Math.max(-maxY, Math.min(maxY, panY)));
} catch (err) {
window.console.error("[YouTube+] Clamp pan error:", err);
}
};
const pointers = new Map;
let initialPinchDist = null;
let pinchStartZoom = null;
let prevTouchAction = null;
const getDistance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const pointerDown = ev => {
try {
if (!featureEnabled) {
return;
}
pointers.set(ev.pointerId, {
x: ev.clientX,
y: ev.clientY
});
try {
const target = ev.target;
target?.setPointerCapture && target.setPointerCapture(ev.pointerId);
} catch (e) {}
try {
const currentZoom = parseFloat(input.value) || readZoomPan().zoom || DEFAULT_ZOOM;
if ("mouse" === ev.pointerType && 0 === ev.button && pointers.size <= 1 && video && currentZoom > 1) {
dragging = !0;
dragStartX = ev.clientX;
dragStartY = ev.clientY;
dragStartPanX = panX;
dragStartPanY = panY;
try {
video.style.cursor = "grabbing";
} catch (e) {}
}
} catch (e) {}
if (2 === pointers.size) {
const pts = Array.from(pointers.values());
initialPinchDist = getDistance(pts[0], pts[1]);
pinchStartZoom = readZoomPan().zoom;
prevTouchAction = player.style.touchAction;
try {
player.style.touchAction = "none";
} catch (e) {}
}
} catch (e) {}
};
const pointerMove = ev => {
try {
if (!featureEnabled) {
return;
}
pointers.has(ev.pointerId) && pointers.set(ev.pointerId, {
x: ev.clientX,
y: ev.clientY
});
if (dragging && "mouse" === ev.pointerType && video) {
const dx = ev.clientX - dragStartX;
const dy = ev.clientY - dragStartY;
panX = dragStartPanX + dx;
panY = dragStartPanY + dy;
clampPan();
applyZoomToVideo(video, parseFloat(input.value) || DEFAULT_ZOOM, panX, panY);
scheduleSavePan();
ev.preventDefault();
return;
}
if (2 === pointers.size && initialPinchDist && null != pinchStartZoom) {
const pts = Array.from(pointers.values());
const dist = getDistance(pts[0], pts[1]);
if (dist <= 0) {
return;
}
const ratio = dist / initialPinchDist;
const newZoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, pinchStartZoom * ratio));
setZoom(newZoom);
ev.preventDefault();
}
} catch (e) {}
};
const pointerUp = ev => {
try {
if (!featureEnabled) {
return;
}
pointers.delete(ev.pointerId);
try {
const target = ev.target;
target?.releasePointerCapture && target.releasePointerCapture(ev.pointerId);
} catch (e) {}
try {
if (dragging && "mouse" === ev.pointerType) {
dragging = !1;
try {
video && (video.style.cursor = parseFloat(input.value) > 1 ? "grab" : "");
} catch (e) {}
}
} catch (e) {}
if (pointers.size < 2) {
initialPinchDist = null;
pinchStartZoom = null;
if (null != prevTouchAction) {
try {
player.style.touchAction = prevTouchAction;
} catch (e) {}
prevTouchAction = null;
}
}
} catch (e) {}
};
player.addEventListener("pointerdown", pointerDown, {
passive: !0
});
player.addEventListener("pointermove", pointerMove, {
passive: !1
});
player.addEventListener("pointerup", pointerUp, {
passive: !0
});
player.addEventListener("pointercancel", pointerUp, {
passive: !0
});
let touchDragging = !1;
let touchDragStartX = 0;
let touchDragStartY = 0;
let touchDragStartPanX = 0;
let touchDragStartPanY = 0;
let touchInitialDist = null;
let touchPinchStartZoom = null;
const getTouchDistance = (t1, t2) => Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY);
const touchStart = ev => {
try {
if (!featureEnabled) {
return;
}
if (!video) {
return;
}
if (1 === ev.touches.length) {
const currentZoom = parseFloat(input.value) || readZoomPan().zoom || DEFAULT_ZOOM;
if (currentZoom > 1) {
touchDragging = !0;
touchDragStartX = ev.touches[0].clientX;
touchDragStartY = ev.touches[0].clientY;
touchDragStartPanX = panX;
touchDragStartPanY = panY;
ev.preventDefault();
}
} else if (2 === ev.touches.length) {
touchInitialDist = getTouchDistance(ev.touches[0], ev.touches[1]);
touchPinchStartZoom = parseFloat(input.value) || readZoomPan().zoom || DEFAULT_ZOOM;
try {
prevTouchAction = player.style.touchAction;
player.style.touchAction = "none";
} catch (e) {}
ev.preventDefault();
}
} catch (e) {
window.console.error("[YouTube+] touchStart error:", e);
}
};
const touchMove = ev => {
try {
if (!featureEnabled) {
return;
}
if (!video) {
return;
}
if (1 === ev.touches.length && touchDragging) {
const dx = ev.touches[0].clientX - touchDragStartX;
const dy = ev.touches[0].clientY - touchDragStartY;
panX = touchDragStartPanX + dx;
panY = touchDragStartPanY + dy;
clampPan();
applyZoomToVideo(video, parseFloat(input.value) || DEFAULT_ZOOM, panX, panY);
scheduleSavePan();
ev.preventDefault();
return;
}
if (2 === ev.touches.length && touchInitialDist && null != touchPinchStartZoom) {
const dist = getTouchDistance(ev.touches[0], ev.touches[1]);
if (dist <= 0) {
return;
}
const ratio = dist / touchInitialDist;
const newZoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, touchPinchStartZoom * ratio));
setZoom(newZoom);
ev.preventDefault();
}
} catch (e) {
window.console.error("[YouTube+] touchMove error:", e);
}
};
const touchEnd = ev => {
try {
if (!featureEnabled) {
return;
}
touchDragging && 0 === ev.touches.length && (touchDragging = !1);
if (ev.touches.length < 2) {
touchInitialDist = null;
touchPinchStartZoom = null;
if (null != prevTouchAction) {
try {
player.style.touchAction = prevTouchAction;
} catch (e) {}
prevTouchAction = null;
}
}
} catch (e) {
window.console.error("[YouTube+] touchEnd error:", e);
}
};
try {
player.addEventListener("touchstart", touchStart, {
passive: !1
});
player.addEventListener("touchmove", touchMove, {
passive: !1
});
player.addEventListener("touchend", touchEnd, {
passive: !0
});
player.addEventListener("touchcancel", touchEnd, {
passive: !0
});
} catch (e) {
window.console.error("[YouTube+] Failed to attach touch handlers:", e);
}
const mouseDownHandler = ev => {
try {
if (!featureEnabled) {
return;
}
if (0 !== ev.button || !video) {
return;
}
const currentZoom = parseFloat(input.value) || readZoomPan().zoom || DEFAULT_ZOOM;
if (currentZoom <= 1) {
return;
}
dragging = !0;
dragStartX = ev.clientX;
dragStartY = ev.clientY;
dragStartPanX = panX;
dragStartPanY = panY;
try {
video.style.cursor = "grabbing";
} catch (e) {}
ev.preventDefault();
} catch (e) {}
};
const mouseMoveHandler = ev => {
try {
if (!featureEnabled) {
return;
}
if (!dragging || !video) {
return;
}
const dx = ev.clientX - dragStartX;
const dy = ev.clientY - dragStartY;
panX = dragStartPanX + dx;
panY = dragStartPanY + dy;
clampPan();
if (video && !video._panRAF) {
const activeVideo = video;
activeVideo._panRAF = requestAnimationFrame(() => {
applyZoomToVideo(activeVideo, parseFloat(input.value) || DEFAULT_ZOOM, panX, panY);
scheduleSavePan();
activeVideo._panRAF = null;
});
}
ev.preventDefault();
} catch (err) {
window.console.error("[YouTube+] Mouse move error:", err);
}
};
const mouseUpHandler = () => {
try {
if (!featureEnabled) {
return;
}
if (dragging) {
dragging = !1;
try {
video && (video.style.cursor = parseFloat(input.value) > 1 ? "grab" : "");
} catch (e) {}
}
} catch (e) {}
};
if (video) {
try {
video.addEventListener("mousedown", mouseDownHandler);
} catch (e) {}
try {
window.addEventListener("mousemove", mouseMoveHandler);
} catch (e) {}
try {
window.addEventListener("mouseup", mouseUpHandler);
} catch (e) {}
try {
attachStyleObserver();
} catch (e) {}
}
function handleVideoStyleMutations(muts) {
try {
if (_isApplyingTransform) {
return;
}
for (const m of muts) {
if ("attributes" === m.type && "style" === m.attributeName) {
const current = video && video.style && video.style.transform || "";
const expectedZoom = readZoomPan().zoom || parseFloat(input.value) || DEFAULT_ZOOM;
const expected = `translate(${panX.toFixed(2)}px, ${panY.toFixed(2)}px) scale(${expectedZoom.toFixed(3)})`;
expectedZoom !== DEFAULT_ZOOM && current !== expected && current !== _lastTransformApplied && requestAnimationFrame(() => {
try {
applyZoomToVideo(video, expectedZoom, panX, panY);
try {
logRestoreEvent({
action: "restore_transform",
currentTransform: current,
expectedTransform: expected,
zoom: expectedZoom,
panX,
panY
});
} catch (e) {}
} catch (e) {}
});
}
}
} catch (e) {}
}
function attachStyleObserver() {
if (videoStyleObserver) {
mutationCoordinator?.unwatch?.(videoStyleObserver);
videoStyleObserver = null;
}
if (video && mutationCoordinator?.watchTarget) {
videoStyleObserver = "zoom::videoStyle";
mutationCoordinator.watchTarget(videoStyleObserver, video, handleVideoStyleMutations, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "style" ]
});
}
}
const handlePlayerMutations = () => {
try {
const newVideo = findVideoElement();
if (newVideo && newVideo !== video) {
try {
if (video) {
video.removeEventListener("mousedown", mouseDownHandler);
video.removeEventListener("wheel", wheelHandler);
if (video._panRAF) {
cancelAnimationFrame(video._panRAF);
video._panRAF = null;
}
}
} catch (err) {
window.console.error("[YouTube+] Error detaching from old video:", err);
}
video = newVideo;
try {
attachStyleObserver();
} catch (err) {
window.console.error("[YouTube+] Error attaching style observer to new video:", err);
}
try {
const current = readZoomPan().zoom || DEFAULT_ZOOM;
clampPan(current);
applyZoomToVideo(video, current, panX, panY);
} catch (err) {
window.console.error("[YouTube+] Error applying zoom to new video:", err);
}
try {
video.addEventListener("mousedown", mouseDownHandler);
} catch (err) {
window.console.error("[YouTube+] Error attaching mousedown to new video:", err);
}
try {
video.addEventListener("wheel", wheelHandler, {
passive: !1
});
} catch (err) {
window.console.error("[YouTube+] Error attaching wheel to new video:", err);
}
}
} catch (err) {
window.console.error("[YouTube+] Player observer error:", err);
}
};
let playerObserverActive = !1;
try {
if (mutationCoordinator?.watchTarget) {
mutationCoordinator.watchTarget("zoom::playerVideoSwap", player, handlePlayerMutations, {
childList: !0,
subtree: !0,
attributes: !1
});
playerObserverActive = !0;
window.YouTubeUtils?.ObserverRegistry?.track && window.YouTubeUtils.ObserverRegistry.track();
}
} catch (err) {
window.console.error("[YouTube+] Failed to observe player for video changes:", err);
}
const fullscreenHandler = () => {
try {
const current = readZoomPan().zoom || DEFAULT_ZOOM;
setTimeout_(() => {
try {
let attempts = 0;
const tryApply = () => {
try {
const newVideo = findVideoElement();
let swapped = !1;
if (newVideo && newVideo !== video) {
try {
video && video.removeEventListener("wheel", wheelHandler);
} catch (e) {}
video = newVideo;
swapped = !0;
try {
video.addEventListener("wheel", wheelHandler, {
passive: !1
});
} catch (e) {}
}
clampPan(current);
video && applyZoomToVideo(video, current, panX, panY, !1, !0);
if (!swapped && (!video || attempts < FULLSCREEN_APPLY_RETRIES)) {
attempts += 1;
setTimeout_(tryApply, FULLSCREEN_APPLY_RETRY_DELAY);
}
} catch (e) {
window.console.error("[YouTube+] Fullscreen apply attempt error:", e);
}
};
tryApply();
} catch (e) {
window.console.error("[YouTube+] Fullscreen inner apply error:", e);
}
}, FULLSCREEN_APPLY_DELAY);
} catch (err) {
window.console.error("[YouTube+] Fullscreen handler error:", err);
}
};
[ "fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange" ].forEach(evt => {
document.addEventListener(evt, fullscreenHandler);
window.YouTubeUtils?.cleanupManager?.registerListener && window.YouTubeUtils.cleanupManager.registerListener(document, evt, fullscreenHandler);
});
try {
try {
const s = readZoomPan();
Number.isFinite(s.panX) && (panX = s.panX);
Number.isFinite(s.panY) && (panY = s.panY);
clampPan(initZoomVal);
} catch (err) {
window.console.error("[YouTube+] Restore pan error:", err);
}
} catch (err) {
window.console.error("[YouTube+] Initial zoom setup error:", err);
}
try {
const initialTransform = `translate(${panX.toFixed(2)}px, ${panY.toFixed(2)}px) scale(${initZoomVal.toFixed(3)})`;
_lastTransformApplied = initialTransform;
} catch (e) {}
setZoom(initZoomVal);
const updateZoomPosition = () => {
try {
const chrome = player.querySelector(".ytp-chrome-bottom");
if (chrome && chrome.offsetHeight) {
const offset = chrome.offsetHeight + 8;
wrap.style.bottom = `${offset}px`;
} else {
wrap.style.bottom = "";
}
} catch (e) {}
};
updateZoomPosition();
const ro = new ResizeObserver(() => {
try {
"undefined" != typeof window && "function" == typeof window.requestAnimationFrame ? requestAnimationFrame(() => {
try {
updateZoomPosition();
} catch (e) {
try {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Enhanced", "updateZoomPosition failed", e instanceof Error ? e : new Error(String(e)));
} catch (e) {}
}
}) : updateZoomPosition();
} catch (e) {
try {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Enhanced", "ResizeObserver callback error", e instanceof Error ? e : new Error(String(e)));
} catch (e) {}
}
});
try {
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.registerObserver(ro);
} catch (e) {}
try {
const chromeEl = player.querySelector(".ytp-chrome-bottom");
chromeEl && ro.observe(chromeEl);
} catch (e) {
try {
YouTubeUtils && YouTubeUtils.logError && YouTubeUtils.logError("Enhanced", "Failed to observe chrome element", e instanceof Error ? e : new Error(String(e)));
} catch (e) {}
}
try {
window.addEventListener("resize", updateZoomPosition, {
passive: !0
});
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.registerListener(window, "resize", updateZoomPosition);
} catch (e) {}
[ "fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange" ].forEach(evt => {
try {
document.addEventListener(evt, updateZoomPosition);
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.registerListener(document, evt, updateZoomPosition);
} catch (e) {}
});
player.appendChild(wrap);
const chromeBottom = player.querySelector(".ytp-chrome-bottom");
const updateHidden = () => {
try {
(() => {
try {
if (player.classList.contains("ytp-autohide") || player.classList.contains("ytp-hide-controls")) {
return !0;
}
if (chromeBottom) {
const style = window.getComputedStyle(chromeBottom);
if (style && ("0" === style.opacity || "hidden" === style.visibility || "none" === style.display)) {
return !0;
}
}
} catch (e) {}
return !1;
})() ? wrap.classList.add("ytp-hidden") : wrap.classList.remove("ytp-hidden");
} catch (e) {}
};
let visObserverPlayerActive = !1;
let visObserverChromeActive = !1;
try {
if (mutationCoordinator?.watchTarget) {
mutationCoordinator.watchTarget("zoom::visibilityPlayer", player, updateHidden, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "class", "style" ]
});
visObserverPlayerActive = !0;
if (chromeBottom) {
mutationCoordinator.watchTarget("zoom::visibilityChrome", chromeBottom, updateHidden, {
attributes: !0,
childList: !1,
subtree: !1,
attributeFilter: [ "class", "style" ]
});
visObserverChromeActive = !0;
}
}
} catch (e) {}
let showTimer = null;
const mouseMoveShow = () => {
try {
wrap.classList.remove("ytp-hidden");
showTimer && clearTimeout(showTimer);
showTimer = setTimeout_(updateHidden, 2200);
} catch (e) {}
};
player.addEventListener("mousemove", mouseMoveShow, {
passive: !0
});
updateHidden();
const cleanup = () => {
try {
if (wheelThrottleTimer) {
clearTimeout(wheelThrottleTimer);
wheelThrottleTimer = null;
}
if (panSaveTimer) {
clearTimeout(panSaveTimer);
panSaveTimer = null;
}
if (video && video._panRAF) {
cancelAnimationFrame(video._panRAF);
video._panRAF = null;
}
player.removeEventListener("wheel", wheelHandler);
player.removeEventListener("pointerdown", pointerDown);
player.removeEventListener("pointermove", pointerMove);
player.removeEventListener("pointerup", pointerUp);
player.removeEventListener("pointercancel", pointerUp);
player.removeEventListener("mousemove", mouseMoveShow);
window.removeEventListener("keydown", keydownHandler);
if (video) {
try {
video.removeEventListener("mousedown", mouseDownHandler);
} catch (e) {}
try {
video.removeEventListener("wheel", wheelHandler);
} catch (e) {}
try {
window.removeEventListener("mousemove", mouseMoveHandler);
} catch (e) {}
try {
window.removeEventListener("mouseup", mouseUpHandler);
} catch (e) {}
try {
video.style.cursor = "";
video.style.transform = "";
video.style.willChange = "auto";
video.style.transition = "";
} catch (e) {}
}
if (videoStyleObserver) {
mutationCoordinator?.unwatch?.(videoStyleObserver);
videoStyleObserver = null;
}
if (visObserverPlayerActive) {
mutationCoordinator?.unwatch?.("zoom::visibilityPlayer");
visObserverPlayerActive = !1;
}
if (visObserverChromeActive) {
mutationCoordinator?.unwatch?.("zoom::visibilityChrome");
visObserverChromeActive = !1;
}
if (playerObserverActive) {
mutationCoordinator?.unwatch?.("zoom::playerVideoSwap");
playerObserverActive = !1;
try {
window.YouTubeUtils?.ObserverRegistry?.untrack?.();
} catch (e) {}
}
try {
document.removeEventListener("fullscreenchange", fullscreenHandler);
} catch (e) {}
if (showTimer) {
clearTimeout(showTimer);
showTimer = null;
}
wrap.remove();
} catch (err) {
window.console.error("[YouTube+] Cleanup error:", err);
}
};
window.YouTubeUtils && YouTubeUtils.cleanupManager && YouTubeUtils.cleanupManager.register(cleanup);
return wrap;
}
let _navigateListenerAdded = !1;
function initZoom() {
try {
if (!canRenderZoomUI()) {
clearZoomUI();
return;
}
const ensure = () => {
if (!canRenderZoomUI()) {
clearZoomUI();
return;
}
const player = $("#movie_player");
player ? player.closest("ytd-miniplayer") ? clearZoomUI() : createZoomUI() : setTimeout_(ensure, 400);
};
ensure();
if (!_navigateListenerAdded) {
_navigateListenerAdded = !0;
window.addEventListener("yt-navigate-finish", () => {
setTimeout_(() => {
try {
canRenderZoomUI() ? createZoomUI() : clearZoomUI();
} catch (e) {}
}, 300);
});
window.addEventListener("ytp:nav-refresh", () => setTimeout_(() => {
try {
canRenderZoomUI() ? createZoomUI() : clearZoomUI();
} catch (e) {}
}, 300));
}
} catch (e) {
window.console.error("initZoom error", e);
}
}
window.addEventListener("youtube-plus-settings-updated", e => {
try {
const detail = e.detail;
const nextEnabled = !1 !== detail?.enableZoom;
if (nextEnabled === featureEnabled) {
return;
}
setFeatureEnabled(nextEnabled);
} catch (e) {
setFeatureEnabled(window.YouTubeUtils?.loadFeatureEnabled?.("enableZoom") ?? !0);
}
});
try {
initZoom();
} catch (e) {}
};
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("zoom", initZoomModule, {
priority: 1,
shouldLoad: isRelevantRoute
}) : initZoomModule();
})();

!(function() {
"use strict";
const setTimeout_ = setTimeout.bind(window);
const _setSafeHTML = window.YouTubeUtils.setSafeHTML;
const _createHTML = window._ytpDefaults?.createHTML || (s => s);
const byId = window.YouTubeUtils?.byId || (id => document.getElementById(id));
const $ = window.YouTubeUtils?.$ || ((selector, root) => (root || document).querySelector(selector));
if ("undefined" == typeof window) {
return;
}
const isRelevantRoute = () => {
try {
const host = window.location.hostname || "";
if (!host.endsWith("youtube.com") || "music.youtube.com" === host) {
return !1;
}
if ((() => {
try {
return Boolean(document.querySelector(".ytp-plus-settings-modal"));
} catch (e) {
return !1;
}
})()) {
return !0;
}
const path = window.location.pathname || "";
return "/watch" === path || path.startsWith("/shorts") || path.startsWith("/channel/");
} catch (e) {
return !1;
}
};
const queryOne = (selector, root = document) => {
const cache = window.YouTubeDOMCache;
return cache && "function" == typeof cache.querySelector ? cache.querySelector(selector, root || document) : (root || document).querySelector(selector);
};
const renderTemplateClone = (container, html) => {
if (!(container instanceof Element)) {
return;
}
const template = document.createElement("template");
const range = document.createRange();
const root = document.body || document.documentElement;
root && range.selectNode(root);
template.content.append(range.createContextualFragment(_createHTML(html)));
container.replaceChildren(template.content.cloneNode(!0));
};
const SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxkcGNjb2N4bHJkc3llamZocnZjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIyMTAyNDYsImV4cCI6MjA4Nzc4NjI0Nn0.QfwrAG4SMJBPLoP-Mcq3hETQXt0ezinoi0CpN57Zn90";
let votingInitialized = !1;
let voteRequestInFlight = !1;
let votingCommentsCache = {};
let votingFeaturesCache = {};
let settingsPanelBaseMarginLeftPx = null;
const getSettingsShell = () => queryOne(".ytp-plus-settings-shell");
const getSettingsPanel = () => queryOne(".ytp-plus-settings-panel");
function setSettingsPanelOffset(offsetPx) {
const settingsShell = getSettingsShell();
if (!(settingsShell instanceof HTMLElement)) {
return;
}
!(function ensureSettingsPanelBaseMargin() {
if (null !== settingsPanelBaseMarginLeftPx) {
return;
}
const settingsShell = getSettingsShell();
if (!(settingsShell instanceof HTMLElement)) {
return;
}
const parsed = parseFloat(window.getComputedStyle(settingsShell).marginLeft || "0");
settingsPanelBaseMarginLeftPx = Number.isFinite(parsed) ? parsed : 0;
})();
const base = settingsPanelBaseMarginLeftPx || 0;
settingsShell.style.marginLeft = `${Math.round(base + offsetPx)}px`;
}
function layoutCommentsPanel(sidePanel) {
const settingsShell = getSettingsShell();
const settingsPanel = getSettingsPanel();
if (!(settingsShell instanceof HTMLElement && settingsPanel instanceof HTMLElement)) {
return;
}
const initialRect = settingsPanel.getBoundingClientRect();
const width = Math.min(440, Math.floor(.34 * window.innerWidth));
const rightSpace = window.innerWidth - initialRect.right;
const hasEnoughRightSpace = rightSpace >= width + 12;
const idealOffset = hasEnoughRightSpace ? -Math.round((width + 12) / 2) : Math.round((width + 12) / 2);
setSettingsPanelOffset(idealOffset);
const alignedPanel = getSettingsPanel();
if (!(alignedPanel instanceof HTMLElement)) {
return;
}
const rect = alignedPanel.getBoundingClientRect();
const rightSpaceAfterShift = window.innerWidth - rect.right;
let left = rect.right + 12;
rightSpaceAfterShift < width + 12 && (left = Math.max(8, rect.left - width - 12));
sidePanel.style.left = `${Math.max(8, left)}px`;
sidePanel.style.top = `${Math.max(8, rect.top)}px`;
sidePanel.style.height = `${Math.max(260, rect.height)}px`;
}
function resetSettingsPanelOffset() {
const settingsShell = getSettingsShell();
if (settingsShell instanceof HTMLElement) {
null !== settingsPanelBaseMarginLeftPx ? settingsShell.style.marginLeft = `${Math.round(settingsPanelBaseMarginLeftPx)}px` : settingsShell.style.removeProperty("margin-left");
settingsPanelBaseMarginLeftPx = null;
} else {
settingsPanelBaseMarginLeftPx = null;
}
}
function setVoteControlsBusy(container, busy) {
container && container.querySelectorAll(".ytp-plus-vote-btn, .ytp-plus-vote-bar-btn").forEach(el => {
if (busy) {
el.setAttribute("aria-disabled", "true");
el.style.pointerEvents = "none";
el.style.opacity = "0.7";
} else {
el.removeAttribute("aria-disabled");
el.style.pointerEvents = "";
el.style.opacity = "";
}
});
}
const t = window.YouTubeUtils.t;
const tf = (key, fallback, params = {}) => {
try {
const value = t(key, params);
if ("string" == typeof value && value && value !== key) {
return value;
}
} catch (e) {}
return fallback || key || "";
};
function getLocalUserId() {
let userId = localStorage.getItem("ytp_voting_user_id");
if (!userId) {
const arr = new Uint8Array(16);
void 0 !== globalThis.crypto && globalThis.crypto.getRandomValues ? globalThis.crypto.getRandomValues(arr) : arr.forEach((_, i, a) => {
a[i] = 256 * Math.random() | 0;
});
const hex = Array.from(arr, b => b.toString(16).padStart(2, "0")).join("");
userId = "user_" + hex + "_" + Date.now().toString(36);
localStorage.setItem("ytp_voting_user_id", userId);
}
return userId;
}
const normalizeUserText = value => {
const normalized = String(value || "").normalize("NFKC").replace(/[\u0000-\u001F\u007F]+/g, " ").trim();
const sanitizeText = window.YouTubeSecurityUtils?.sanitizeText;
const safe = "function" == typeof sanitizeText ? sanitizeText(normalized) : normalized;
return safe.split(/\s+/).join(" ").trim();
};
function normalizeVoteType(value) {
const numeric = Number(value);
return 1 === numeric ? 1 : -1 === numeric ? -1 : 0;
}
async function supabaseFetch(endpoint, options = {}) {
const url = `https://ldpccocxlrdsyejfhrvc.supabase.co/rest/v1/${endpoint}`;
const headers = {
apikey: SUPABASE_KEY,
Authorization: `Bearer ${SUPABASE_KEY}`,
"Content-Type": "application/json",
Prefer: options.prefer || "return=representation"
};
try {
const response = await fetch(url, {
...options,
headers: {
...headers,
...options.headers
}
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}
const data = await response.json().catch(() => null);
return {
data,
error: null
};
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
return {
data: null,
error: msg
};
}
}
async function getFeatures() {
const {data, error} = await supabaseFetch("ytplus_feature_requests?select=*&order=created_at.desc");
if (error) {
window.console.error("[Voting] Error fetching features:", error);
return [];
}
return data || [];
}
async function getAllVotes() {
const {data, error} = await supabaseFetch("ytplus_feature_votes?select=feature_id,vote_type,ip_address");
if (error) {
window.console.error("[Voting] Error fetching votes:", error);
return {};
}
const votes = {};
(data || []).forEach(v => {
votes[v.feature_id] || (votes[v.feature_id] = {
upvotes: 0,
downvotes: 0
});
const voteType = normalizeVoteType(v.vote_type);
1 === voteType ? votes[v.feature_id].upvotes++ : -1 === voteType && votes[v.feature_id].downvotes++;
});
return votes;
}
async function getUserVotes() {
const userId = getLocalUserId();
const {data, error} = await supabaseFetch(`ytplus_feature_votes?select=feature_id,vote_type&ip_address=eq.${userId}`);
if (error) {
window.console.error("[Voting] Error fetching user votes:", error);
return {};
}
const userVotes = {};
(data || []).forEach(v => {
const voteType = normalizeVoteType(v.vote_type);
voteType && (userVotes[v.feature_id] = voteType);
});
return userVotes;
}
async function vote(featureId, voteType) {
const userId = getLocalUserId();
const {data: existing} = await supabaseFetch(`ytplus_feature_votes?feature_id=eq.${featureId}&ip_address=eq.${userId}&select=id`);
if (existing && existing.length > 0) {
const existingVote = existing[0];
if (0 === voteType) {
await supabaseFetch(`ytplus_feature_votes?id=eq.${existingVote.id}`, {
method: "DELETE"
});
return {
success: !0,
action: "removed"
};
}
await supabaseFetch(`ytplus_feature_votes?id=eq.${existingVote.id}`, {
method: "PATCH",
body: JSON.stringify({
vote_type: voteType
})
});
return {
success: !0,
action: "updated"
};
}
if (0 === voteType) {
return {
success: !0,
action: "none"
};
}
const {error} = await supabaseFetch("ytplus_feature_votes", {
method: "POST",
body: JSON.stringify({
feature_id: featureId,
vote_type: voteType,
ip_address: userId
})
});
if (error) {
window.console.error("[Voting] Vote error:", error);
return {
success: !1,
error
};
}
return {
success: !0,
action: "added"
};
}
async function submitFeature(title, description) {
const normalizeTitle = value => normalizeUserText(value).toLocaleLowerCase();
title = normalizeUserText(title).slice(0, 200);
description = normalizeUserText(description).slice(0, 2e3);
if (!title) {
return {
success: !1,
error: "Title is required"
};
}
if (normalizeTitle(title) === "__ytp_preview_vote__".toLocaleLowerCase()) {
return {
success: !1,
error: "This title is reserved"
};
}
const existingFeatures = await getFeatures();
const normalizedTitle = normalizeTitle(title);
const duplicateFeature = (existingFeatures || []).find(feature => normalizeTitle(String(feature?.title || "")) === normalizedTitle);
if (duplicateFeature) {
return {
success: !1,
error: "Feature already exists"
};
}
const userId = getLocalUserId();
const {error} = await supabaseFetch("ytplus_feature_requests", {
method: "POST",
body: JSON.stringify({
title,
description,
author_ip: userId
})
});
if (error) {
window.console.error("[Voting] Submit error:", error);
return {
success: !1,
error
};
}
return {
success: !0
};
}
async function getCommentsByFeatureIds(featureIds) {
const ids = (featureIds || []).filter(Boolean);
if (0 === ids.length) {
return {};
}
const inClause = ids.join(",");
const {data, error} = await supabaseFetch(`ytplus_feature_comments?select=id,feature_id,comment,author_ip,created_at&feature_id=in.(${inClause})&order=created_at.asc`);
if (error) {
window.console.error("[Voting] Error fetching comments:", error);
return {};
}
const grouped = {};
(data || []).forEach(c => {
const featureId = String(c?.feature_id || "");
if (featureId) {
grouped[featureId] || (grouped[featureId] = []);
grouped[featureId].push(c);
}
});
return grouped;
}
function isPreviewFeature(feature) {
return "__ytp_preview_vote__" === String(feature?.title || "").trim();
}
function getRenderableFeatureTitle(feature) {
return normalizeUserText(String(feature?.title || ""));
}
async function ensurePreviewFeature(features) {
const fromList = Array.isArray(features) ? features.find(isPreviewFeature) : null;
if (fromList) {
return fromList;
}
const userId = getLocalUserId();
const {data, error} = await supabaseFetch("ytplus_feature_requests", {
method: "POST",
body: JSON.stringify({
title: "__ytp_preview_vote__",
description: "Internal row for ytp-plus-voting-preview",
status: "proposed",
author_ip: userId
})
});
if (error) {
window.console.error("[Voting] Error creating preview row:", error);
const encodedTitle = encodeURIComponent("__ytp_preview_vote__");
const {data: existingPreview} = await supabaseFetch(`ytplus_feature_requests?select=id,title,description,status&title=eq.${encodedTitle}&limit=1`);
return Array.isArray(existingPreview) && existingPreview[0] ? existingPreview[0] : null;
}
if (Array.isArray(data) && data[0]) {
return data[0];
}
const refreshed = await getFeatures();
return refreshed.find(isPreviewFeature) || null;
}
function ensureCommentsModal() {
if (byId("ytp-plus-comments-panel")) {
return !0;
}
if (!document.body || !document.head) {
return !1;
}
const panel = document.createElement("div");
panel.id = "ytp-plus-comments-panel";
panel.className = "ytp-plus-comments-sidepanel";
renderTemplateClone(panel, `\n      <div class="ytp-plus-comments-header">\n        <div class="ytp-plus-comments-title" id="ytp-plus-comments-title">${tf("comments", "Comments")}</div>\n        <button class="ytp-plus-comments-close" data-comments-close="1" type="button">×</button>\n      </div>\n      <div class="ytp-plus-comments-list" id="ytp-plus-comments-list"></div>\n      <div class="ytp-plus-comments-form">\n        <textarea id="ytp-plus-comments-input" maxlength="1000" placeholder="${tf("addCommentPlaceholder", "Add a comment...")}"></textarea>\n        <button id="ytp-plus-comments-submit" type="button">${tf("submit", "Submit")}</button>\n      </div>\n    `);
document.body.appendChild(panel);
if (!byId("ytp-plus-comments-modal-style")) {
const style = document.createElement("style");
style.id = "ytp-plus-comments-modal-style";
style.textContent = "\n        .ytp-plus-comments-sidepanel{position:fixed;top:10vh;left:calc(50% + 390px);width:min(440px,34vw);max-width:92vw;height:60vh;background:var(--yt-glass-bg);border:1.5px solid var(--yt-glass-border);border-radius:24px;display:none;z-index:100001;box-shadow:0 12px 40px var(--yt-timecode-panel-shadow);overflow:hidden;backdrop-filter:blur(14px) saturate(140%);-webkit-backdrop-filter:blur(14px) saturate(140%);contain:layout style paint;font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n        .ytp-plus-comments-sidepanel.open{display:flex;flex-direction:column}\n        .ytp-plus-comments-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--yt-stats-card-border-dark)}\n        .ytp-plus-comments-title{font-size:16px;font-weight:500;color:var(--yt-text-primary);font-family:inherit}\n        .ytp-plus-comments-close{border:0;background:transparent;color:var(--yt-text-secondary);font-size:24px;cursor:pointer;line-height:1}\n        .ytp-plus-comments-list{flex:1;overflow:auto;padding:12px 16px;display:flex;flex-direction:column;gap:10px}\n        .ytp-plus-comments-item{border:1px solid var(--yt-stats-card-border-dark);background:var(--yt-surface-overlay-faint);border-radius:10px;padding:10px}\n        .ytp-plus-comments-item-text{color:var(--yt-text-primary);white-space:pre-wrap;word-break:break-word;font-size:small}\n        .ytp-plus-comments-item-meta{margin-top:6px;color:var(--yt-text-secondary);font-size:12px}\n        .ytp-plus-comments-form{padding:12px 16px;border-top:1px solid var(--yt-stats-card-border-dark);display:flex;gap:8px;align-items:center}\n        #ytp-plus-comments-input{flex:1;min-height:15px;max-height:160px;resize:vertical;background:var(--yt-glass-bg);color:var(--yt-text-primary);border:1px solid var(--yt-glass-border);border-radius:10px;padding:10px}\n        #ytp-plus-comments-submit{border:1px solid var(--yt-glass-border);background:var(--yt-accent);color:var(--yt-text-primary);border-radius:10px;padding:10px 14px;cursor:pointer}\n        .ytp-plus-voting-item-status-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px}\n        .ytp-plus-voting-comments-icon{border:1px solid var(--yt-glass-border);background:var(--yt-button-bg);color:var(--yt-text-secondary);border-radius:999px;min-width:28px;height:28px;padding:0 10px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;line-height:1;transition:background-color .2s ease,color .2s ease,border-color .2s ease}\n        .ytp-plus-voting-comments-icon:hover{background:var(--yt-hover-bg);color:var(--yt-text-primary)}\n        .ytp-plus-voting-comments-icon svg{width:14px;height:14px;display:block;fill:currentColor}\n        .ytp-plus-comments-sidepanel textarea,\n        .ytp-plus-comments-sidepanel button,\n        .ytp-plus-comments-sidepanel .ytp-plus-comments-item,\n        .ytp-plus-comments-sidepanel .ytp-plus-comments-item-text,\n        .ytp-plus-comments-sidepanel .ytp-plus-comments-item-meta,\n        .ytp-plus-comments-sidepanel .ytp-plus-voting-empty{font-family:inherit}\n      ";
document.head.appendChild(style);
}
const reposition = () => {
const sidePanel = byId("ytp-plus-comments-panel");
sidePanel instanceof HTMLElement && sidePanel.classList.contains("open") && layoutCommentsPanel(sidePanel);
};
if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
YouTubeUtils.cleanupManager.registerListener(window, "resize", reposition);
YouTubeUtils.cleanupManager.registerListener(window, "scroll", reposition, !0);
} else {
window.addEventListener("resize", reposition);
window.addEventListener("scroll", reposition, !0);
}
panel.dataset.repositionBound = "1";
const onSettingsClosed = () => {
closeCommentsModal();
resetSettingsPanelOffset();
};
window.YouTubeUtils && YouTubeUtils.cleanupManager ? YouTubeUtils.cleanupManager.registerListener(document, "youtube-plus-settings-modal-closed", onSettingsClosed) : document.addEventListener("youtube-plus-settings-modal-closed", onSettingsClosed);
return !0;
}
function closeCommentsModal() {
const panel = byId("ytp-plus-comments-panel");
panel && panel.classList.remove("open");
resetSettingsPanelOffset();
}
function openCommentsModal(featureId) {
if (!ensureCommentsModal()) {
return;
}
const panel = byId("ytp-plus-comments-panel");
const titleEl = byId("ytp-plus-comments-title");
const listEl = byId("ytp-plus-comments-list");
const inputEl = byId("ytp-plus-comments-input");
const submitEl = byId("ytp-plus-comments-submit");
if (!(panel && titleEl && listEl && inputEl && submitEl)) {
return;
}
const feature = votingFeaturesCache[featureId];
const comments = votingCommentsCache[featureId] || [];
panel.setAttribute("data-feature-id", featureId);
titleEl.textContent = String(feature?.title || "").trim() || tf("comments", "Comments");
renderTemplateClone(listEl, comments.length ? comments.map(c => `\n          <div class="ytp-plus-comments-item">\n            <div class="ytp-plus-comments-item-text">${escapeHtml(String(c.comment || ""))}</div>\n            <div class="ytp-plus-comments-item-meta">${escapeHtml((function formatCommentDate(value) {
try {
const d = new Date(value);
return Number.isNaN(d.getTime()) ? "" : d.toLocaleString();
} catch (e) {
return "";
}
})(String(c.created_at || "")))}</div>\n          </div>\n        `).join("") : '<div class="ytp-plus-voting-empty">No comments yet</div>');
inputEl.value = "";
submitEl.disabled = !1;
panel.classList.add("open");
panel instanceof HTMLElement && layoutCommentsPanel(panel);
}
async function loadFeatures() {
const listEl = byId("ytp-plus-voting-list");
if (!listEl) {
return;
}
const allFeaturesRaw = await getFeatures();
const previewFeature = await ensurePreviewFeature(allFeaturesRaw);
const seenTitles = new Set;
const features = (allFeaturesRaw || []).filter(f => {
if (!(function isRenderableFeature(feature) {
const title = getRenderableFeatureTitle(feature);
return Boolean(title) && "__ytp_preview_vote__" !== title;
})(f) || isPreviewFeature(f)) {
return !1;
}
const normalizedTitle = (function getNormalizedRenderableFeatureTitle(feature) {
return getRenderableFeatureTitle(feature).toLocaleLowerCase();
})(f);
if (!normalizedTitle || seenTitles.has(normalizedTitle)) {
return !1;
}
seenTitles.add(normalizedTitle);
return !0;
});
const [allVotes, userVotes, commentsByFeature] = await Promise.all([ getAllVotes(), getUserVotes(), getCommentsByFeatureIds(features.map(f => String(f.id || ""))) ]);
const renderFeatures = [ ...features ];
votingCommentsCache = commentsByFeature;
votingFeaturesCache = {};
renderFeatures.forEach(f => {
votingFeaturesCache[String(f.id || "")] = f;
});
if (0 !== renderFeatures.length) {
renderTemplateClone(listEl, renderFeatures.map(f => {
const votes = allVotes[f.id] || {
upvotes: 0,
downvotes: 0
};
const userVote = userVotes[f.id] || 0;
const featureComments = commentsByFeature[f.id] || [];
const totalVotes = votes.upvotes + votes.downvotes;
const upPercent = totalVotes > 0 ? Math.round(votes.upvotes / totalVotes * 100) : 50;
const statusMeta = (function getStatusMeta(status) {
const normalized = String(status || "").toLowerCase();
return "completed" === normalized ? {
className: "completed",
label: tf("statusCompleted", "Completed")
} : "in_progress" === normalized ? {
className: "in-progress",
label: tf("statusInProgress", "In progress")
} : {
className: "proposed",
label: tf("statusProposed", "Proposed")
};
})(f.status);
const featureTitle = getRenderableFeatureTitle(f);
const featureDescription = (function getRenderableFeatureDescription(feature) {
return normalizeUserText(String(feature?.description || ""));
})(f);
return `\n          <div class="ytp-plus-voting-item" data-feature-id="${f.id}">\n            <div class="ytp-plus-voting-item-content">\n              <div class="ytp-plus-voting-item-title">${escapeHtml(featureTitle)}</div>\n              <div class="ytp-plus-voting-item-desc">${escapeHtml(featureDescription)}</div>\n              <div class="ytp-plus-voting-item-status-row">\n                <div class="ytp-plus-voting-item-status ${statusMeta.className}">${escapeHtml(statusMeta.label)}</div>\n                <button class="ytp-plus-voting-comments-icon" data-comments-open="1" type="button" title="${tf("comments", "Comments")} (${featureComments.length})" aria-label="${tf("comments", "Comments")} (${featureComments.length})"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"> <path opacity="0.5" d="M13.0867 21.3877L13.7321 21.7697L13.0867 21.3877ZM13.6288 20.4718L12.9833 20.0898L13.6288 20.4718ZM10.3712 20.4718L9.72579 20.8539H9.72579L10.3712 20.4718ZM10.9133 21.3877L11.5587 21.0057L10.9133 21.3877ZM13.5 2.75C13.9142 2.75 14.25 2.41421 14.25 2C14.25 1.58579 13.9142 1.25 13.5 1.25V2.75ZM22.75 10.5C22.75 10.0858 22.4142 9.75 22 9.75C21.5858 9.75 21.25 10.0858 21.25 10.5H22.75ZM2.3806 15.9134L3.07351 15.6264V15.6264L2.3806 15.9134ZM7.78958 18.9915L7.77666 19.7413L7.78958 18.9915ZM5.08658 18.6194L4.79957 19.3123H4.79957L5.08658 18.6194ZM21.6194 15.9134L22.3123 16.2004V16.2004L21.6194 15.9134ZM16.2104 18.9915L16.1975 18.2416L16.2104 18.9915ZM18.9134 18.6194L19.2004 19.3123H19.2004L18.9134 18.6194ZM4.38751 2.7368L3.99563 2.09732V2.09732L4.38751 2.7368ZM2.7368 4.38751L2.09732 3.99563H2.09732L2.7368 4.38751ZM9.40279 19.2098L9.77986 18.5615L9.77986 18.5615L9.40279 19.2098ZM13.7321 21.7697L14.2742 20.8539L12.9833 20.0898L12.4412 21.0057L13.7321 21.7697ZM9.72579 20.8539L10.2679 21.7697L11.5587 21.0057L11.0166 20.0898L9.72579 20.8539ZM12.4412 21.0057C12.2485 21.3313 11.7515 21.3313 11.5587 21.0057L10.2679 21.7697C11.0415 23.0767 12.9585 23.0767 13.7321 21.7697L12.4412 21.0057ZM10.5 2.75H13.5V1.25H10.5V2.75ZM21.25 10.5V11.5H22.75V10.5H21.25ZM2.75 11.5V10.5H1.25V11.5H2.75ZM1.25 11.5C1.25 12.6546 1.24959 13.5581 1.29931 14.2868C1.3495 15.0223 1.45323 15.6344 1.68769 16.2004L3.07351 15.6264C2.92737 15.2736 2.84081 14.8438 2.79584 14.1847C2.75041 13.5189 2.75 12.6751 2.75 11.5H1.25ZM7.8025 18.2416C6.54706 18.2199 5.88923 18.1401 5.37359 17.9265L4.79957 19.3123C5.60454 19.6457 6.52138 19.7197 7.77666 19.7413L7.8025 18.2416ZM1.68769 16.2004C2.27128 17.6093 3.39066 18.7287 4.79957 19.3123L5.3736 17.9265C4.33223 17.4951 3.50486 16.6678 3.07351 15.6264L1.68769 16.2004ZM21.25 11.5C21.25 12.6751 21.2496 13.5189 21.2042 14.1847C21.1592 14.8438 21.0726 15.2736 20.9265 15.6264L22.3123 16.2004C22.5468 15.6344 22.6505 15.0223 22.7007 14.2868C22.7504 13.5581 22.75 12.6546 22.75 11.5H21.25ZM16.2233 19.7413C17.4786 19.7197 18.3955 19.6457 19.2004 19.3123L18.6264 17.9265C18.1108 18.1401 17.4529 18.2199 16.1975 18.2416L16.2233 19.7413ZM20.9265 15.6264C20.4951 16.6678 19.6678 17.4951 18.6264 17.9265L19.2004 19.3123C20.6093 18.7287 21.7287 17.6093 22.3123 16.2004L20.9265 15.6264ZM10.5 1.25C8.87781 1.25 7.6085 1.24921 6.59611 1.34547C5.57256 1.44279 4.73445 1.64457 3.99563 2.09732L4.77938 3.37628C5.24291 3.09223 5.82434 2.92561 6.73809 2.83873C7.663 2.75079 8.84876 2.75 10.5 2.75V1.25ZM2.75 10.5C2.75 8.84876 2.75079 7.663 2.83873 6.73809C2.92561 5.82434 3.09223 5.24291 3.37628 4.77938L2.09732 3.99563C1.64457 4.73445 1.44279 5.57256 1.34547 6.59611C1.24921 7.6085 1.25 8.87781 1.25 10.5H2.75ZM3.99563 2.09732C3.22194 2.57144 2.57144 3.22194 2.09732 3.99563L3.37628 4.77938C3.72672 4.20752 4.20752 3.72672 4.77938 3.37628L3.99563 2.09732ZM11.0166 20.0898C10.8136 19.7468 10.6354 19.4441 10.4621 19.2063C10.2795 18.9559 10.0702 18.7304 9.77986 18.5615L9.02572 19.8582C9.07313 19.8857 9.13772 19.936 9.24985 20.0898C9.37122 20.2564 9.50835 20.4865 9.72579 20.8539L11.0166 20.0898ZM7.77666 19.7413C8.21575 19.7489 8.49387 19.7545 8.70588 19.7779C8.90399 19.7999 8.98078 19.832 9.02572 19.8582L9.77986 18.5615C9.4871 18.3912 9.18246 18.3215 8.87097 18.287C8.57339 18.2541 8.21375 18.2487 7.8025 18.2416L7.77666 19.7413ZM14.2742 20.8539C14.4916 20.4865 14.6287 20.2564 14.7501 20.0898C14.8622 19.936 14.9268 19.8857 14.9742 19.8582L14.2201 18.5615C13.9298 18.7304 13.7204 18.9559 13.5379 19.2063C13.3646 19.4441 13.1864 19.7468 12.9833 20.0898L14.2742 20.8539ZM16.1975 18.2416C15.7862 18.2487 15.4266 18.2541 15.129 18.287C14.8175 18.3215 14.5129 18.3912 14.2201 18.5615L14.9742 19.8582C15.0192 19.832 15.096 19.7999 15.2941 19.7779C15.5061 19.7545 15.7842 19.7489 16.2233 19.7413L16.1975 18.2416Z" fill="currentColor"></path> <circle cx="19" cy="5" r="3" stroke="currentColor" stroke-width="1.5"></circle> </svg></button>\n              </div>\n            </div>\n            <div class="ytp-plus-voting-item-votes">\n              <div class="ytp-plus-voting-score">\n                <span class="ytp-plus-vote-total">${totalVotes} ${tf("votes", "votes")}</span>\n              </div>\n              <div class="ytp-plus-voting-buttons">\n                <div class="ytp-plus-voting-buttons-track" style="background:linear-gradient(to right, var(--yt-success) ${upPercent}%, var(--yt-danger) ${upPercent}%);"></div>\n                <button class="ytp-plus-vote-btn ${1 === userVote ? "active" : ""}" data-vote="1" title="${tf("like", "Like")}" type="button" aria-label="${tf("like", "Like")}">\n                  <svg class="ytp-plus-vote-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20.9751 12.1852L20.2361 12.0574L20.9751 12.1852ZM20.2696 16.265L19.5306 16.1371L20.2696 16.265ZM6.93776 20.4771L6.19055 20.5417H6.19055L6.93776 20.4771ZM6.1256 11.0844L6.87281 11.0198L6.1256 11.0844ZM13.9949 5.22142L14.7351 5.34269V5.34269L13.9949 5.22142ZM13.3323 9.26598L14.0724 9.38725V9.38725L13.3323 9.26598ZM6.69813 9.67749L6.20854 9.10933H6.20854L6.69813 9.67749ZM8.13687 8.43769L8.62646 9.00585H8.62646L8.13687 8.43769ZM10.518 4.78374L9.79207 4.59542L10.518 4.78374ZM10.9938 2.94989L11.7197 3.13821L11.7197 3.13821L10.9938 2.94989ZM12.6676 2.06435L12.4382 2.77841L12.4382 2.77841L12.6676 2.06435ZM12.8126 2.11093L13.0419 1.39687L13.0419 1.39687L12.8126 2.11093ZM9.86194 6.46262L10.5235 6.81599V6.81599L9.86194 6.46262ZM13.9047 3.24752L13.1787 3.43584V3.43584L13.9047 3.24752ZM11.6742 2.13239L11.3486 1.45675L11.3486 1.45675L11.6742 2.13239ZM20.2361 12.0574L19.5306 16.1371L21.0086 16.3928L21.7142 12.313L20.2361 12.0574ZM13.245 21.25H8.59634V22.75H13.245V21.25ZM7.68497 20.4125L6.87281 11.0198L5.37839 11.149L6.19055 20.5417L7.68497 20.4125ZM19.5306 16.1371C19.0238 19.0677 16.3813 21.25 13.245 21.25V22.75C17.0712 22.75 20.3708 20.081 21.0086 16.3928L19.5306 16.1371ZM13.2548 5.10015L12.5921 9.14472L14.0724 9.38725L14.7351 5.34269L13.2548 5.10015ZM7.18772 10.2456L8.62646 9.00585L7.64728 7.86954L6.20854 9.10933L7.18772 10.2456ZM11.244 4.97206L11.7197 3.13821L10.2678 2.76157L9.79207 4.59542L11.244 4.97206ZM12.4382 2.77841L12.5832 2.82498L13.0419 1.39687L12.897 1.3503L12.4382 2.77841ZM10.5235 6.81599C10.8354 6.23198 11.0777 5.61339 11.244 4.97206L9.79207 4.59542C9.65572 5.12107 9.45698 5.62893 9.20041 6.10924L10.5235 6.81599ZM12.5832 2.82498C12.8896 2.92342 13.1072 3.16009 13.1787 3.43584L14.6306 3.05921C14.4252 2.26719 13.819 1.64648 13.0419 1.39687L12.5832 2.82498ZM11.7197 3.13821C11.7547 3.0032 11.8522 2.87913 11.9998 2.80804L11.3486 1.45675C10.8166 1.71309 10.417 2.18627 10.2678 2.76157L11.7197 3.13821ZM11.9998 2.80804C12.1345 2.74311 12.2931 2.73181 12.4382 2.77841L12.897 1.3503C12.3872 1.18655 11.8312 1.2242 11.3486 1.45675L11.9998 2.80804ZM14.1537 10.9842H19.3348V9.4842H14.1537V10.9842ZM14.7351 5.34269C14.8596 4.58256 14.824 3.80477 14.6306 3.0592L13.1787 3.43584C13.3197 3.97923 13.3456 4.54613 13.2548 5.10016L14.7351 5.34269ZM8.59634 21.25C8.12243 21.25 7.726 20.887 7.68497 20.4125L6.19055 20.5417C6.29851 21.7902 7.34269 22.75 8.59634 22.75V21.25ZM8.62646 9.00585C9.30632 8.42 10.0391 7.72267 10.5235 6.81599L9.20041 6.10924C8.85403 6.75767 8.30249 7.30493 7.64728 7.86954L8.62646 9.00585ZM21.7142 12.313C21.9695 10.8365 20.8341 9.4842 19.3348 9.4842V10.9842C19.9014 10.9842 20.3332 11.4959 20.2361 12.0574L21.7142 12.313ZM12.5921 9.14471C12.4344 10.1076 13.1766 10.9842 14.1537 10.9842V9.4842C14.1038 9.4842 14.0639 9.43901 14.0724 9.38725L12.5921 9.14471ZM6.87281 11.0198C6.84739 10.7258 6.96474 10.4378 7.18772 10.2456L6.20854 9.10933C5.62021 9.61631 5.31148 10.3753 5.37839 11.149L6.87281 11.0198Z" fill="currentColor"></path> <path opacity="0.5" d="M3.9716 21.4709L3.22439 21.5355L3.9716 21.4709ZM3 10.2344L3.74721 10.1698C3.71261 9.76962 3.36893 9.46776 2.96767 9.48507C2.5664 9.50239 2.25 9.83274 2.25 10.2344L3 10.2344ZM4.71881 21.4063L3.74721 10.1698L2.25279 10.299L3.22439 21.5355L4.71881 21.4063ZM3.75 21.5129V10.2344H2.25V21.5129H3.75ZM3.22439 21.5355C3.2112 21.383 3.33146 21.2502 3.48671 21.2502V22.7502C4.21268 22.7502 4.78122 22.1281 4.71881 21.4063L3.22439 21.5355ZM3.48671 21.2502C3.63292 21.2502 3.75 21.3686 3.75 21.5129H2.25C2.25 22.1954 2.80289 22.7502 3.48671 22.7502V21.2502Z" fill="currentColor"></path> </svg>\n                </button>\n                <button class="ytp-plus-vote-btn ${-1 === userVote ? "active" : ""}" data-vote="-1" title="${tf("dislike", "Dislike")}" type="button" aria-label="${tf("dislike", "Dislike")}">\n                  <svg class="ytp-plus-vote-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20.9751 11.8148L20.2361 11.9426L20.9751 11.8148ZM20.2696 7.73505L19.5306 7.86285L20.2696 7.73505ZM6.93776 3.52293L6.19055 3.45832H6.19055L6.93776 3.52293ZM6.1256 12.9156L6.87281 12.9802L6.1256 12.9156ZM13.9949 18.7786L14.7351 18.6573V18.6573L13.9949 18.7786ZM13.3323 14.734L14.0724 14.6128V14.6128L13.3323 14.734ZM6.69813 14.3225L6.20854 14.8907H6.20854L6.69813 14.3225ZM8.13687 15.5623L8.62646 14.9942H8.62646L8.13687 15.5623ZM10.518 19.2163L9.79207 19.4046L10.518 19.2163ZM10.9938 21.0501L11.7197 20.8618L11.7197 20.8618L10.9938 21.0501ZM12.6676 21.9356L12.4382 21.2216L12.4382 21.2216L12.6676 21.9356ZM12.8126 21.8891L13.0419 22.6031L13.0419 22.6031L12.8126 21.8891ZM9.86194 17.5374L10.5235 17.184V17.184L9.86194 17.5374ZM13.9047 20.7525L13.1787 20.5642V20.5642L13.9047 20.7525ZM11.6742 21.8676L11.3486 22.5433L11.3486 22.5433L11.6742 21.8676ZM20.2361 11.9426L19.5306 7.86285L21.0086 7.60724L21.7142 11.687L20.2361 11.9426ZM13.245 2.75H8.59634V1.25H13.245V2.75ZM7.68497 3.58754L6.87281 12.9802L5.37839 12.851L6.19055 3.45832L7.68497 3.58754ZM19.5306 7.86285C19.0238 4.93226 16.3813 2.75 13.245 2.75V1.25C17.0712 1.25 20.3708 3.91895 21.0086 7.60724L19.5306 7.86285ZM13.2548 18.8998L12.5921 14.8553L14.0724 14.6128L14.7351 18.6573L13.2548 18.8998ZM7.18772 13.7544L8.62646 14.9942L7.64728 16.1305L6.20854 14.8907L7.18772 13.7544ZM11.244 19.0279L11.7197 20.8618L10.2678 21.2384L9.79207 19.4046L11.244 19.0279ZM12.4382 21.2216L12.5832 21.175L13.0419 22.6031L12.897 22.6497L12.4382 21.2216ZM10.5235 17.184C10.8354 17.768 11.0777 18.3866 11.244 19.0279L9.79207 19.4046C9.65572 18.8789 9.45698 18.3711 9.20041 17.8908L10.5235 17.184ZM12.5832 21.175C12.8896 21.0766 13.1072 20.8399 13.1787 20.5642L14.6306 20.9408C14.4252 21.7328 13.819 22.3535 13.0419 22.6031L12.5832 21.175ZM11.7197 20.8618C11.7547 20.9968 11.8522 21.1209 11.9998 21.192L11.3486 22.5433C10.8166 22.2869 10.417 21.8137 10.2678 21.2384L11.7197 20.8618ZM11.9998 21.192C12.1345 21.2569 12.2931 21.2682 12.4382 21.2216L12.897 22.6497C12.3872 22.8135 11.8312 22.7758 11.3486 22.5433L11.9998 21.192ZM14.1537 13.0158H19.3348V14.5158H14.1537V13.0158ZM14.7351 18.6573C14.8596 19.4174 14.824 20.1952 14.6306 20.9408L13.1787 20.5642C13.3197 20.0208 13.3456 19.4539 13.2548 18.8998L14.7351 18.6573ZM8.59634 2.75C8.12243 2.75 7.726 3.11302 7.68497 3.58754L6.19055 3.45832C6.29851 2.20975 7.34269 1.25 8.59634 1.25V2.75ZM8.62646 14.9942C9.30632 15.58 10.0391 16.2773 10.5235 17.184L9.20041 17.8908C8.85403 17.2423 8.30249 16.6951 7.64728 16.1305L8.62646 14.9942ZM21.7142 11.687C21.9695 13.1635 20.8341 14.5158 19.3348 14.5158V13.0158C19.9014 13.0158 20.3332 12.5041 20.2361 11.9426L21.7142 11.687ZM12.5921 14.8553C12.4344 13.8924 13.1766 13.0158 14.1537 13.0158V14.5158C14.1038 14.5158 14.0639 14.561 14.0724 14.6128L12.5921 14.8553ZM6.87281 12.9802C6.84739 13.2742 6.96474 13.5622 7.18772 13.7544L6.20854 14.8907C5.62021 14.3837 5.31148 13.6247 5.37839 12.851L6.87281 12.9802Z" fill="currentColor"></path> <path opacity="0.5" d="M3.9716 2.52911L3.22439 2.4645L3.9716 2.52911ZM3 13.7656L3.74721 13.8302C3.71261 14.2304 3.36893 14.5322 2.96767 14.5149C2.5664 14.4976 2.25 14.1673 2.25 13.7656L3 13.7656ZM4.71881 2.59372L3.74721 13.8302L2.25279 13.701L3.22439 2.4645L4.71881 2.59372ZM3.75 2.48709V13.7656H2.25V2.48709H3.75ZM3.22439 2.4645C3.2112 2.61704 3.33146 2.74983 3.48671 2.74983V1.24983C4.21268 1.24983 4.78122 1.87192 4.71881 2.59372L3.22439 2.4645ZM3.48671 2.74983C3.63292 2.74983 3.75 2.63139 3.75 2.48709H2.25C2.25 1.80457 2.80289 1.24983 3.48671 1.24983V2.74983Z" fill="currentColor"></path> </svg>\n                </button>\n              </div>\n            </div>\n          </div>\n        `;
}).join(""));
listEl.querySelectorAll(".ytp-plus-vote-btn").forEach(btn => {
btn.addEventListener("click", async () => {
if (voteRequestInFlight) {
return;
}
const card = btn.closest(".ytp-plus-voting-item");
const featureId = card?.dataset?.featureId;
const voteType = parseInt(btn.dataset?.vote || "0", 10);
if (!featureId) {
return;
}
const currentUserVote = userVotes[featureId] || 0;
let newVoteType = voteType;
currentUserVote === voteType && (newVoteType = 0);
try {
voteRequestInFlight = !0;
setVoteControlsBusy(listEl.closest(".ytp-plus-settings-section, .ytp-plus-voting") || listEl, !0);
const result = await vote(featureId, newVoteType);
result.success && await loadFeatures();
} finally {
voteRequestInFlight = !1;
setVoteControlsBusy(listEl.closest(".ytp-plus-settings-section, .ytp-plus-voting") || listEl, !1);
}
});
});
updateVoteBar(allVotes, userVotes, previewFeature?.id || null);
} else {
_setSafeHTML(listEl, `<div class="ytp-plus-voting-empty">${tf("noFeatures", "No feature requests yet")}</div>`);
updateVoteBar(allVotes, userVotes, previewFeature?.id || null);
}
}
function escapeHtml(str) {
if (!str) {
return "";
}
if (window.YouTubeSafeDOM?.escapeHTML) {
return window.YouTubeSafeDOM.escapeHTML(str);
}
if (window.YouTubeSecurityUtils?.escapeHtml) {
return window.YouTubeSecurityUtils.escapeHtml(str);
}
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
function updateVoteBar(allVotes, userVotes, previewFeatureId) {
const fillEl = byId("ytp-plus-vote-bar-fill");
const countEl = byId("ytp-plus-vote-bar-count");
const upBtn = byId("ytp-plus-vote-bar-up");
const downBtn = byId("ytp-plus-vote-bar-down");
if (!fillEl || !countEl) {
return;
}
const previewVotes = previewFeatureId && allVotes[previewFeatureId] || {
upvotes: 0,
downvotes: 0
};
const totalUp = previewVotes.upvotes || 0;
const totalDown = previewVotes.downvotes || 0;
const total = totalUp + totalDown;
const pct = total > 0 ? Math.round(totalUp / total * 100) : 50;
fillEl.style.background = `linear-gradient(to right, var(--yt-success) ${pct}%, var(--yt-danger) ${pct}%)`;
countEl.textContent = total > 0 ? `${total}` : "0";
const previewUserVote = previewFeatureId && userVotes[previewFeatureId] || 0;
upBtn && upBtn.classList.toggle("active", 1 === previewUserVote);
downBtn && downBtn.classList.toggle("active", -1 === previewUserVote);
}
function initVoting() {
if (!isRelevantRoute()) {
return;
}
if (votingInitialized) {
return;
}
votingInitialized = !0;
if (!ensureCommentsModal()) {
const onReady = () => {
ensureCommentsModal();
};
"loading" === document.readyState ? document.addEventListener("DOMContentLoaded", onReady, {
once: !0
}) : setTimeout_(onReady, 0);
}
const voteBarHandler = async e => {
const barBtn = e.target?.closest?.(".ytp-plus-vote-bar-btn");
if (barBtn) {
if (voteRequestInFlight) {
return;
}
const features = await getFeatures();
const previewFeature = await ensurePreviewFeature(features);
if (!previewFeature?.id) {
return;
}
const userVotes = await getUserVotes();
const voteType = parseInt(barBtn.dataset.vote, 10);
const currentUserVote = userVotes[previewFeature.id] || 0;
const newVoteType = currentUserVote === voteType ? 0 : voteType;
const controlsRoot = barBtn.closest(".ytp-plus-settings-section, .ytp-plus-voting") || document.body;
try {
voteRequestInFlight = !0;
setVoteControlsBusy(controlsRoot, !0);
await vote(previewFeature.id, newVoteType);
await loadFeatures();
} finally {
voteRequestInFlight = !1;
setVoteControlsBusy(controlsRoot, !1);
}
}
};
const addFeatureHandler = e => {
const showAddBtn = e.target?.closest?.("#ytp-plus-show-add-feature");
const cancelBtn = e.target?.closest?.("#ytp-plus-cancel-feature");
const submitBtn = e.target?.closest?.("#ytp-plus-submit-feature");
if (showAddBtn) {
const addFormEl = byId("ytp-plus-voting-add-form");
const showAddEl = byId("ytp-plus-show-add-feature");
addFormEl && (addFormEl.style.display = "block");
showAddEl && (showAddEl.style.display = "none");
}
if (cancelBtn) {
const addFormEl = byId("ytp-plus-voting-add-form");
const showAddEl = byId("ytp-plus-show-add-feature");
const titleEl = byId("ytp-plus-feature-title");
const descEl = byId("ytp-plus-feature-desc");
addFormEl && (addFormEl.style.display = "none");
showAddEl && (showAddEl.style.display = "block");
titleEl && (titleEl.value = "");
descEl && (descEl.value = "");
}
if (submitBtn) {
const titleInput = byId("ytp-plus-feature-title");
const descInput = byId("ytp-plus-feature-desc");
const title = titleInput?.value?.trim?.() || "";
const desc = descInput?.value?.trim?.() || "";
titleInput instanceof HTMLInputElement && titleInput.setCustomValidity("");
if (!title && titleInput instanceof HTMLInputElement) {
titleInput.setCustomValidity(tf("featureTitleRequired", "Feature title is required"));
titleInput.reportValidity();
return;
}
submitBtn.disabled = !0;
submitBtn.textContent = tf("loading", "Loading...");
submitFeature(title, desc).then(result => {
submitBtn.disabled = !1;
submitBtn.textContent = tf("submit", "Submit");
if (result.success) {
if (result.success) {
const addFormEl = byId("ytp-plus-voting-add-form");
const showAddEl = byId("ytp-plus-show-add-feature");
addFormEl && (addFormEl.style.display = "none");
showAddEl && (showAddEl.style.display = "block");
titleInput && (titleInput.value = "");
descInput && (descInput.value = "");
loadFeatures();
}
} else if (titleInput instanceof HTMLInputElement) {
titleInput.setCustomValidity(String(result.error || ""));
titleInput.reportValidity();
}
});
}
};
const commentHandler = e => {
const navItem = e.target?.closest?.(".ytp-plus-settings-nav-item");
if (navItem) {
const nextSection = String(navItem.dataset?.section || "");
nextSection && "voting" !== nextSection && closeCommentsModal();
}
const closeBtn = e.target?.closest?.('[data-comments-close="1"]');
if (closeBtn) {
closeCommentsModal();
return;
}
const openBtn = e.target?.closest?.('[data-comments-open="1"]');
if (openBtn) {
const card = openBtn.closest(".ytp-plus-voting-item");
const featureId = card?.dataset?.featureId || "";
featureId && openCommentsModal(featureId);
return;
}
const submitCommentBtn = e.target?.closest?.("#ytp-plus-comments-submit");
if (!submitCommentBtn) {
return;
}
const panel = byId("ytp-plus-comments-panel");
const featureId = panel?.getAttribute("data-feature-id") || "";
const input = byId("ytp-plus-comments-input");
const value = String(input?.value || "").trim();
if (featureId && value) {
submitCommentBtn.disabled = !0;
(async function addComment(featureId, commentText) {
const text = normalizeUserText(commentText).slice(0, 1e3);
if (!text) {
return {
success: !1,
error: "Comment is required"
};
}
const userId = getLocalUserId();
const {error} = await supabaseFetch("ytplus_feature_comments", {
method: "POST",
body: JSON.stringify({
feature_id: featureId,
comment: text,
author_ip: userId
})
});
if (error) {
window.console.error("[Voting] Add comment error:", error);
return {
success: !1,
error
};
}
return {
success: !0
};
})(featureId, value).then(result => {
if (result.success) {
input && (input.value = "");
return loadFeatures().then(() => openCommentsModal(featureId));
}
return null;
}).finally(() => {
submitCommentBtn.disabled = !1;
});
}
};
if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
YouTubeUtils.cleanupManager.registerListener(document, "click", voteBarHandler);
YouTubeUtils.cleanupManager.registerListener(document, "click", addFeatureHandler);
YouTubeUtils.cleanupManager.registerListener(document, "click", commentHandler);
} else {
document.addEventListener("click", voteBarHandler);
document.addEventListener("click", addFeatureHandler);
document.addEventListener("click", commentHandler);
}
}
const VotingSystem = {
init: initVoting,
createUI: function createVotingUI(container) {
renderTemplateClone(container, `\n      <div class="ytp-plus-voting">\n        <div class="ytp-plus-voting-header">\n          <h3>${tf("featureRequests", "Feature Requests")}</h3>\n          <button class="ytp-plus-voting-add-btn" id="ytp-plus-show-add-feature">\n            + ${tf("addFeature", "Add Feature")}\n          </button>\n        </div>\n        <div class="ytp-plus-voting-list" id="ytp-plus-voting-list">\n          <div class="ytp-plus-voting-loading">${tf("loading", "Loading...")}</div>\n        </div>\n        <div class="ytp-plus-voting-add-form" id="ytp-plus-voting-add-form" style="display:none;">\n          <input type="text" id="ytp-plus-feature-title" placeholder="${tf("featureTitle", "Feature title")}" />\n          <textarea id="ytp-plus-feature-desc" placeholder="${tf("featureDescription", "Description")}"></textarea>\n          <div class="ytp-plus-voting-form-actions">\n            <button class="ytp-plus-voting-cancel" id="ytp-plus-cancel-feature">${tf("cancel", "Cancel")}</button>\n            <button class="ytp-plus-voting-submit" id="ytp-plus-submit-feature">${tf("submit", "Submit")}</button>\n          </div>\n        </div>\n      </div>\n    `);
},
loadFeatures,
getFeatures,
vote,
submitFeature,
initSlider: function initSlider() {
const container = $(".ytp-plus-ba-container");
if (!container || container.dataset.sliderInit) {
return;
}
container.dataset.sliderInit = "1";
const afterEl = container.querySelector(".ytp-plus-ba-after");
const divider = container.querySelector(".ytp-plus-ba-divider");
if (!afterEl || !divider) {
return;
}
let dragging = !1;
let resumeTimer = null;
let rafId = null;
function setPosition(pct, manual = !1) {
const clamped = Math.max(2, Math.min(98, pct));
afterEl.style.clipPath = `inset(0 0 0 ${clamped}%)`;
manual && (divider.style.left = `${clamped}%`);
divider.setAttribute("aria-valuenow", String(Math.round(clamped)));
}
function getPct(clientX) {
const rect = container.getBoundingClientRect();
return (clientX - rect.left) / rect.width * 100;
}
function pauseAutoplay() {
divider.classList.remove("autoplay");
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
resumeTimer && clearTimeout(resumeTimer);
resumeTimer = setTimeout_(() => {
divider.classList.add("autoplay");
startAutoplayRaf();
}, 3e3);
}
function startAutoplayRaf() {
rafId || (rafId = requestAnimationFrame(function loop() {
if (!divider.classList.contains("autoplay")) {
rafId = null;
return;
}
const rect = container.getBoundingClientRect();
const dRect = divider.getBoundingClientRect();
const pct = (dRect.left + dRect.width / 2 - rect.left) / rect.width * 100;
setPosition(pct, !1);
rafId = requestAnimationFrame(loop);
}));
}
container.addEventListener("mousedown", e => {
dragging = !0;
pauseAutoplay();
setPosition(getPct(e.clientX), !0);
e.preventDefault();
});
const onMousemove = e => {
dragging && setPosition(getPct(e.clientX), !0);
};
const onMouseup = () => {
dragging = !1;
};
if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
YouTubeUtils.cleanupManager.registerListener(window, "mousemove", onMousemove);
YouTubeUtils.cleanupManager.registerListener(window, "mouseup", onMouseup);
} else {
window.addEventListener("mousemove", onMousemove);
window.addEventListener("mouseup", onMouseup);
}
container.addEventListener("touchstart", e => {
dragging = !0;
pauseAutoplay();
setPosition(getPct(e.touches[0].clientX), !0);
}, {
passive: !0
});
const onTouchmove = e => {
dragging && setPosition(getPct(e.touches[0].clientX), !0);
};
const onTouchend = () => {
dragging = !1;
};
if (window.YouTubeUtils && YouTubeUtils.cleanupManager) {
YouTubeUtils.cleanupManager.registerListener(window, "touchmove", onTouchmove, {
passive: !0
});
YouTubeUtils.cleanupManager.registerListener(window, "touchend", onTouchend);
} else {
window.addEventListener("touchmove", onTouchmove, {
passive: !0
});
window.addEventListener("touchend", onTouchend);
}
divider.addEventListener("keydown", e => {
pauseAutoplay();
const cur = parseFloat(divider.getAttribute("aria-valuenow") || "50");
if ("ArrowLeft" === e.key) {
setPosition(cur - 2, !0);
e.preventDefault();
}
if ("ArrowRight" === e.key) {
setPosition(cur + 2, !0);
e.preventDefault();
}
});
setPosition(50, !0);
setTimeout_(() => {
divider.classList.add("autoplay");
startAutoplayRaf();
}, 400);
},
updateVoteBar
};
void 0 === window.YouTubePlus && (window.YouTubePlus = {});
window.YouTubePlus.Voting = VotingSystem;
window.YouTubePlusLazyLoader ? window.YouTubePlusLazyLoader.register("voting", initVoting, {
priority: 0,
shouldLoad: isRelevantRoute
}) : isRelevantRoute() && initVoting();
window.YouTubeUtils?.cleanupManager ? window.YouTubeUtils.cleanupManager.registerListener(window, "yt-navigate-start", () => {
closeCommentsModal();
}) : window.addEventListener("yt-navigate-start", () => {
closeCommentsModal();
});
})();