Greasy Fork

Greasy Fork is available in English.

xyDouYinTools 抖音沉浸式优化

美化抖音界面 一个偏沉浸式观感(最大程度全屏化) + 纯鼠标刷视频的抖音网页端增强脚本:尽量让视频当主角,UI少打扰;同时把高频操作塞到鼠标手势里,省得手来回摸键盘。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         xyDouYinTools 抖音沉浸式优化
// @namespace    http://greasyfork.icu/zh-CN/users/1513167-%E9%83%A7%E5%B1%B1%E6%9D%8E%E5%92%B8%E9%B1%BC
// @version      0.28.1
// @description  美化抖音界面 一个偏沉浸式观感(最大程度全屏化) + 纯鼠标刷视频的抖音网页端增强脚本:尽量让视频当主角,UI少打扰;同时把高频操作塞到鼠标手势里,省得手来回摸键盘。
// @author       lxy
// @license      MIT
// @match        https://www.douyin.com/*
// @match        https://live.douyin.com/*
// @match        https://www.douyin.com/follow/live/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
	'use strict';

	const FEATURE = {
		SIDEBAR: 1,
		STYLES: 1,
		NAVIGATION: 1,
		LIVE: 1,
		HIDE_ELEMENTS: 1,
		ADS: 1,
		MOUSE: 1,
		LOADING: 1,
		PERFORMANCE: 1
	};

	const DEBUG = {
		LOG_LEVEL: 3,
		MODULES: {
			SIDEBAR: 0,
			STYLES: 0,
			NAVIGATION: 1,
			LIVE: 0,
			HIDE_ELEMENTS: 1,
			ADS: 1,
			MOUSE: 0,
			LOADING: 0,
			PERFORMANCE: 1
		}
	};

	const LS_PREFIX = 'xyDYTools_feature_';

	(function loadFeatureFlags() {
		try {
			Object.keys(FEATURE).forEach(k => {
				const v = localStorage.getItem(LS_PREFIX + k);
				if (v === '0') FEATURE[k] = 0;
				if (v === '1') FEATURE[k] = 1;
			});
		} catch (_) {}
	})();

	const nativeConsole = {
		log: console.log.bind(console),
		warn: console.warn.bind(console),
		error: console.error.bind(console),
		info: console.info.bind(console),
		time: console.time ? console.time.bind(console) : null,
		timeEnd: console.timeEnd ? console.timeEnd.bind(console) : null
	};

	if (DEBUG.LOG_LEVEL === 0) {
		['log', 'warn', 'error', 'info'].forEach(type => {
			console[type] = function() {};
		});
	}

	function ts() {
		return new Date().toLocaleTimeString();
	}

	const logger = {
		log(module, message, ...args) {
			if (DEBUG.LOG_LEVEL >= 3 && DEBUG.MODULES[module]) nativeConsole.log(
				`[${ts()}] [DouYinTools] [${module}]`, message, ...args);
		},
		warn(module, message, ...args) {
			if (DEBUG.LOG_LEVEL >= 2 && DEBUG.MODULES[module]) nativeConsole.warn(
				`[${ts()}] [DouYinTools] [${module}]`, message, ...args);
		},
		error(module, message, ...args) {
			if (DEBUG.LOG_LEVEL >= 1 && DEBUG.MODULES[module]) nativeConsole.error(
				`[${ts()}] [DouYinTools] [${module}]`, message, ...args);
		},
		info(module, message, ...args) {
			if (DEBUG.LOG_LEVEL >= 3 && DEBUG.MODULES[module]) nativeConsole.info(
				`[${ts()}] [DouYinTools] [${module}]`, message, ...args);
		},
		initModule(module) {
			if (DEBUG.LOG_LEVEL >= 3 && DEBUG.MODULES[module]) nativeConsole.log(
				`[${ts()}] [DouYinTools] [${module}] 模块初始化开始`);
		},
		moduleReady(module) {
			if (DEBUG.LOG_LEVEL >= 3 && DEBUG.MODULES[module]) nativeConsole.log(
				`[${ts()}] [DouYinTools] [${module}] 模块初始化完成`);
		}
	};

	const performanceTracker = {
		startTime: performance.now(),
		moduleTimes: {},
		moduleStartTimes: {},
		start(module) {
			if (FEATURE.PERFORMANCE && DEBUG.MODULES.PERFORMANCE && DEBUG.LOG_LEVEL >= 3) {
				this.moduleStartTimes[module] = performance.now();
				logger.log('PERFORMANCE', `开始执行: ${module}`);
			}
		},
		end(module) {
			if (!(FEATURE.PERFORMANCE && DEBUG.MODULES.PERFORMANCE && DEBUG.LOG_LEVEL >= 3)) return 0;
			if (!this.moduleStartTimes[module]) return 0;
			const duration = performance.now() - this.moduleStartTimes[module];
			this.moduleTimes[module] = duration;
			logger.log('PERFORMANCE', `${module} 执行完成, 耗时: ${duration.toFixed(2)}ms`);
			delete this.moduleStartTimes[module];
			return duration;
		},
		getTotalTime() {
			return (performance.now() - this.startTime).toFixed(2);
		},
		printPerformanceReport() {
			if (!(FEATURE.PERFORMANCE && DEBUG.MODULES.PERFORMANCE && DEBUG.LOG_LEVEL >= 3)) return;
			const totalTime = this.getTotalTime();
			const entries = Object.entries(this.moduleTimes).sort((a, b) => b[1] - a[1]);
			const sum = entries.reduce((acc, [, t]) => acc + t, 0);

			nativeConsole.log('\n' + '='.repeat(60));
			nativeConsole.log('%c🎯 抖音工具脚本性能报告', 'color: #4CAF50; font-size: 16px; font-weight: bold;');
			nativeConsole.log('='.repeat(60));
			nativeConsole.log('%c📊 模块执行时间统计:', 'color: #2196F3; font-weight: bold;');

			entries.forEach(([module, time]) => {
				const percent = sum > 0 ? ((time / sum) * 100).toFixed(1) : '0.0';
				nativeConsole.log(`  ${module}: ${time.toFixed(2)}ms (${percent}%)`);
			});

			nativeConsole.log('%c⏱️ 总运行时间统计:', 'color: #FF9800; font-weight: bold;');
			nativeConsole.log(`  脚本总运行时间: ${totalTime}ms`);
			nativeConsole.log(`  模块总耗时: ${sum.toFixed(2)}ms`);

			nativeConsole.log('%c⚙️ 调试配置:', 'color: #9C27B0; font-weight: bold;');
			nativeConsole.log(`  日志级别: ${DEBUG.LOG_LEVEL}`);
			nativeConsole.log(
				`  输出模块: ${Object.keys(DEBUG.MODULES).filter(m => DEBUG.MODULES[m]).length}/${Object.keys(DEBUG.MODULES).length}`
				);

			nativeConsole.log('%c🧩 功能开关:', 'color: #795548; font-weight: bold;');
			Object.keys(FEATURE).forEach(k => nativeConsole.log(`  ${k}: ${FEATURE[k] ? '✅ 开启' : '❌ 关闭'}`));

			nativeConsole.log('='.repeat(60) + '\n');
			return totalTime;
		}
	};

	function sleep(ms) {
		return new Promise(r => setTimeout(r, ms));
	}

	function normText(s) {
		return String(s || '').replace(/\s+/g, ' ').trim();
	}

	function isVisible(el) {
		if (!el) return false;
		const st = getComputedStyle(el);
		if (st.display === 'none' || st.visibility === 'hidden' || Number(st.opacity) === 0) return false;
		const r = el.getBoundingClientRect();
		if (r.width < 5 || r.height < 5) return false;
		return r.bottom > 0 && r.top < window.innerHeight && r.right > 0 && r.left < window.innerWidth;
	}

	function dispatchKeyEvent(type, key, repeat) {
		const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
		const k = key.length === 1 ? key : (key === 'ArrowDown' ? 'ArrowDown' : key);
		const keyCode = key.length === 1 ? key.charCodeAt(0) : (key === 'ArrowDown' ? 40 : 0);
		const ev = new KeyboardEvent(type, {
			key: k,
			code,
			keyCode,
			which: keyCode,
			bubbles: true,
			cancelable: true,
			repeat: !!repeat
		});
		document.dispatchEvent(ev);
	}

	function keyDown(key, repeat) {
		dispatchKeyEvent('keydown', key, repeat);
	}

	function keyUp(key) {
		dispatchKeyEvent('keyup', key, false);
	}

	function createLoadingScreen() {
		performanceTracker.start('LOADING_SCREEN');
		logger.initModule('LOADING');

		if (!FEATURE.LOADING) {
			logger.moduleReady('LOADING');
			performanceTracker.end('LOADING_SCREEN');
			return;
		}

		const EXIST_ID = 'tm-fullscreen-loader';
		if (document.getElementById(EXIST_ID)) {
			logger.moduleReady('LOADING');
			performanceTracker.end('LOADING_SCREEN');
			return;
		}

		const FADE_MS = 450;
		const MIN_SHOW_MS = 250;
		const MAX_SHOW_MS = 12000;
		const ESC_TO_CLOSE = true;
		const CLICK_TO_CLOSE = true;

		window.__xyDYToolsBoot = window.__xyDYToolsBoot || {
			styles: false,
			safeStyles: false,
			nav: false,
			ads: false,
			hide: false,
			inited: false
		};

		if (!document.getElementById('tm-fullscreen-loader-style')) {
			const style = document.createElement('style');
			style.id = 'tm-fullscreen-loader-style';
			style.textContent =
				`#tm-fullscreen-loader{position:fixed;inset:0;background:rgba(0,0,0,0.92);z-index:999999;display:flex;justify-content:center;align-items:center;flex-direction:column;opacity:1;transition:opacity ${FADE_MS}ms ease;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);}#tm-fullscreen-loader .tm-spinner{width:52px;height:52px;border:5px solid rgba(255,255,255,0.26);border-radius:50%;border-top-color:#fff;animation:tm_spin 0.95s linear infinite;}#tm-fullscreen-loader .tm-loading-text{color:#fff;margin-top:18px;font-family:Arial,sans-serif;}#tm-fullscreen-loader .s1{font-size:18px;font-weight:600;letter-spacing:0.5px;}#tm-fullscreen-loader .s2{color:rgba(255,255,255,0.62);font-size:13px;margin-top:10px;max-width:min(560px, 86vw);text-align:center;line-height:1.45;}#tm-fullscreen-loader .hint{color:rgba(255,255,255,0.45);font-size:12px;margin-top:14px;text-align:center;}@keyframes tm_spin{to{transform:rotate(360deg);}}`;
			(document.head || document.documentElement).appendChild(style);
		}

		const loader = document.createElement('div');
		loader.id = EXIST_ID;
		loader.innerHTML = `
			<div class="tm-spinner"></div>
			<p class="tm-loading-text s1">加载中...</p>
			<p class="tm-loading-text s2">正在等抖音自身加载 + 脚本正在接管抖音界面</p>
			<p class="tm-loading-text hint">${ESC_TO_CLOSE ? '按 ESC 可关闭 / ' : ''}${CLICK_TO_CLOSE ? '点一下也能关' : ''}<br/><br/>UI正在挨打,场面会略显混乱</p>
		`;
		document.documentElement.appendChild(loader);

		let removed = false;
		const startAt = Date.now();

		const remove = (reason) => {
			if (removed) return;
			removed = true;

			const cost = Date.now() - startAt;
			logger.log('LOADING', `加载界面移除:${reason},展示 ${cost}ms`);

			loader.style.opacity = '0';
			setTimeout(() => {
				try {
					loader.remove();
				} catch (_) {}
				try {
					document.getElementById('tm-fullscreen-loader-style')?.remove();
				} catch (_) {}
			}, FADE_MS + 30);
		};

		const safeRemove = (reason) => {
			const elapsed = Date.now() - startAt;
			const wait = Math.max(0, MIN_SHOW_MS - elapsed);
			setTimeout(() => remove(reason), wait);
		};

		function isBootReady() {
			const b = window.__xyDYToolsBoot || {};
			return !!b.inited;
		}

		function isDomReady() {
			return document.readyState === 'interactive' || document.readyState === 'complete';
		}

		const POLL_MS = 120;
		const AFTER_READY_MS = 1200;

		(function poll() {
			if (removed) return;
			if (isDomReady() && isBootReady()) {
				setTimeout(() => {
					requestAnimationFrame(() => requestAnimationFrame(() => safeRemove('BOOT_READY')));
				}, AFTER_READY_MS);
				return;
			}
			setTimeout(poll, POLL_MS);
		})();

		setTimeout(() => safeRemove('MAX_SHOW_TIMEOUT'), MAX_SHOW_MS);

		if (ESC_TO_CLOSE) {
			window.addEventListener('keydown', function onKey(e) {
				if (e.key === 'Escape') {
					window.removeEventListener('keydown', onKey, true);
					safeRemove('ESC');
				}
			}, true);
		}

		if (CLICK_TO_CLOSE) loader.addEventListener('click', () => safeRemove('CLICK'), {
			once: true
		});

		logger.moduleReady('LOADING');
		performanceTracker.end('LOADING_SCREEN');
	}

	/* =========================
	   SIDEBAR(修复版)
	   你这个“抽风”核心点基本就俩:
	   1) mousemove 里只看 x<=hoverWidth,会把“鼠标已经在侧栏里”的情况当成“离开左侧边缘”,从而立刻排队隐藏
	   2) MutationObserver 盯整个 documentElement subtree,抖音一更新 DOM 你就疯狂重绑监听,状态来回抖
	   下面这版:用 RAF 限频 + 用“鼠标是否在侧栏矩形内”来决定显隐 + 只在侧栏节点变更时重绑
	========================= */

	const SIDEBAR_CONFIG = {
		hoverDelay: 240,
		hoverWidth: 18,
		storageKey: 'hide_sidebar',
		lockKey: 'hide_sidebar_lock',
		repairKey: 'xyDYTools_sidebar_repair_v1'
	};

	let sidebarHoverEnabled = false;
	let sidebarHideTimer = null;
	let sidebarVisible = false;

	let sidebarElCache = null;
	let sidebarBindToken = 0;

	let rafMovePending = false;
	let lastMoveX = 0;
	let lastMoveY = 0;

	function safeLSGet(key) {
		try {
			return localStorage.getItem(key);
		} catch (_) {
			return null;
		}
	}

	function safeLSSet(key, val) {
		try {
			localStorage.setItem(key, val);
		} catch (_) {}
	}

	function safeLSDel(key) {
		try {
			localStorage.removeItem(key);
		} catch (_) {}
	}

	function pickSidebarEl() {
		return (
			document.querySelector('#douyin-navigation') ||
			document.querySelector('[data-e2e="douyin-navigation"]') ||
			null
		);
	}

	function isSidebarLocked() {
		return safeLSGet(SIDEBAR_CONFIG.lockKey) === 'true';
	}

	function isHideSidebarWanted() {
		return safeLSGet(SIDEBAR_CONFIG.storageKey) === 'true';
	}

	function setSidebarHiddenOnRoot(hidden) {
		if (hidden) {
			document.documentElement.classList.add('xy-hide-sidebar');
			sidebarVisible = false;
		} else {
			document.documentElement.classList.remove('xy-hide-sidebar');
			sidebarVisible = true;
		}
	}

	(function injectSidebarStyle() {
		if (document.getElementById('xy-sidebar-style')) return;
		const style = document.createElement('style');
		style.id = 'xy-sidebar-style';
		style.textContent = `
html.xy-hide-sidebar #douyin-navigation{display:none !important;}
html.xy-hide-sidebar [data-e2e="douyin-navigation"]{display:none !important;}
html.xy-hide-sidebar #douyin-navigation,
html.xy-hide-sidebar [data-e2e="douyin-navigation"]{
	transition:none !important;
}
html.xy-hide-sidebar #douyin-navigation *,
html.xy-hide-sidebar [data-e2e="douyin-navigation"] *{
	pointer-events:none !important;
}
html:not(.xy-hide-sidebar) #douyin-navigation{display:block !important;}
html:not(.xy-hide-sidebar) [data-e2e="douyin-navigation"]{display:block !important;}
		`;
		(document.head || document.documentElement).appendChild(style);
	})();

	(function sidebarLSRepairOnce() {
		const mark = safeLSGet(SIDEBAR_CONFIG.repairKey);
		if (mark === '1') return;

		const a = safeLSGet(SIDEBAR_CONFIG.storageKey);
		const b = safeLSGet(SIDEBAR_CONFIG.lockKey);

		if (a !== null && a !== 'true' && a !== 'false') safeLSDel(SIDEBAR_CONFIG.storageKey);
		if (b !== null && b !== 'true' && b !== 'false') safeLSDel(SIDEBAR_CONFIG.lockKey);

		if (safeLSGet(SIDEBAR_CONFIG.lockKey) === 'true') safeLSSet(SIDEBAR_CONFIG.storageKey, 'true');

		safeLSSet(SIDEBAR_CONFIG.repairKey, '1');
	})();

	function clearSidebarTimers() {
		if (sidebarHideTimer) {
			clearTimeout(sidebarHideTimer);
			sidebarHideTimer = null;
		}
	}

	function mouseInsideSidebarRect(x, y) {
		const el = sidebarElCache || pickSidebarEl();
		if (!el) return false;

		const r = el.getBoundingClientRect();
		if (r.width <= 0 || r.height <= 0) return false;

		return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
	}

	function shouldShowSidebarByMouse(x, y) {
		if (!sidebarHoverEnabled) return false;
		if (isSidebarLocked()) return false;
		if (!isHideSidebarWanted()) return false;

		if (x <= SIDEBAR_CONFIG.hoverWidth) return true;
		if (mouseInsideSidebarRect(x, y)) return true;

		return false;
	}

	function scheduleHideIfNeeded(x, y) {
		if (!sidebarHoverEnabled) return;
		if (isSidebarLocked()) return;

		const wantHide = isHideSidebarWanted();

		if (!wantHide) {
			setSidebarHiddenOnRoot(false);
			clearSidebarTimers();
			return;
		}

		const shouldShow = shouldShowSidebarByMouse(x, y);

		if (shouldShow) {
			clearSidebarTimers();
			if (!sidebarVisible) setSidebarHiddenOnRoot(false);
			return;
		}

		if (!sidebarVisible) return;

		clearSidebarTimers();
		sidebarHideTimer = setTimeout(() => {
			sidebarHideTimer = null;
			if (!isHideSidebarWanted()) return;

			const x2 = lastMoveX;
			const y2 = lastMoveY;

			if (shouldShowSidebarByMouse(x2, y2)) return;
			setSidebarHiddenOnRoot(true);
		}, SIDEBAR_CONFIG.hoverDelay);
	}

	function onPointerMove(e) {
		if (!FEATURE.SIDEBAR) return;
		if (!sidebarHoverEnabled) return;
		if (isSidebarLocked()) return;

		lastMoveX = e.clientX;
		lastMoveY = e.clientY;

		if (rafMovePending) return;
		rafMovePending = true;

		requestAnimationFrame(() => {
			rafMovePending = false;
			scheduleHideIfNeeded(lastMoveX, lastMoveY);
		});
	}

	function bindSidebarHoverOnce() {
		const token = ++sidebarBindToken;

		document.removeEventListener('pointermove', onPointerMove, true);
		document.addEventListener('pointermove', onPointerMove, {
			passive: true,
			capture: true
		});

		setTimeout(() => {
			if (token !== sidebarBindToken) return;
			sidebarElCache = pickSidebarEl();
		}, 0);
	}

	function watchSidebarMount() {
		let lastEl = null;
		let debounce = null;

		const ensure = () => {
			const el = pickSidebarEl();
			if (el && el !== lastEl) {
				lastEl = el;
				sidebarElCache = el;
				bindSidebarHoverOnce();
				scheduleHideIfNeeded(lastMoveX || 9999, lastMoveY || 9999);
			}
		};

		const ob = new MutationObserver(() => {
			if (!FEATURE.SIDEBAR) return;
			if (debounce) return;
			debounce = setTimeout(() => {
				debounce = null;
				ensure();
			}, 180);
		});

		const root = document.body || document.documentElement;
		if (root) ob.observe(root, {
			childList: true,
			subtree: true
		});

		setInterval(() => {
			if (!FEATURE.SIDEBAR) return;
			ensure();
		}, 2500);
	}

	function setSidebarLock(locked) {
		safeLSSet(SIDEBAR_CONFIG.lockKey, locked ? 'true' : 'false');

		if (locked) {
			safeLSSet(SIDEBAR_CONFIG.storageKey, 'true');
			sidebarHoverEnabled = false;
			clearSidebarTimers();
			setSidebarHiddenOnRoot(true);
			logger.log('SIDEBAR', '已锁定隐藏');
			return;
		}

		sidebarHoverEnabled = true;
		const wantHide = isHideSidebarWanted();
		setSidebarHiddenOnRoot(!!wantHide);
		bindSidebarHoverOnce();
		logger.log('SIDEBAR', '已解锁');
	}

	function initSidebarState() {
		performanceTracker.start('SIDEBAR_INIT');
		logger.initModule('SIDEBAR');

		if (!FEATURE.SIDEBAR) {
			setSidebarHiddenOnRoot(false);
			sidebarHoverEnabled = false;
			clearSidebarTimers();
			logger.moduleReady('SIDEBAR');
			performanceTracker.end('SIDEBAR_INIT');
			return;
		}

		if (isSidebarLocked()) {
			safeLSSet(SIDEBAR_CONFIG.storageKey, 'true');
			setSidebarHiddenOnRoot(true);
			sidebarHoverEnabled = false;
			clearSidebarTimers();
			logger.moduleReady('SIDEBAR');
			performanceTracker.end('SIDEBAR_INIT');
			return;
		}

		sidebarHoverEnabled = true;

		const wantHide = isHideSidebarWanted();
		setSidebarHiddenOnRoot(!!wantHide);

		bindSidebarHoverOnce();
		watchSidebarMount();

		logger.moduleReady('SIDEBAR');
		performanceTracker.end('SIDEBAR_INIT');
	}

	function initSidebarToggle() {
		performanceTracker.start('SIDEBAR_SETUP');
		logger.initModule('SIDEBAR');

		initSidebarState();

		logger.moduleReady('SIDEBAR');
		performanceTracker.end('SIDEBAR_SETUP');
	}

	window.xyDYTools = window.xyDYTools || {};
	window.xyDYTools.sidebar = window.xyDYTools.sidebar || {};
	window.xyDYTools.sidebar.clearLS = function() {
		safeLSDel(SIDEBAR_CONFIG.storageKey);
		safeLSDel(SIDEBAR_CONFIG.lockKey);
		safeLSDel(SIDEBAR_CONFIG.repairKey);
		return 'ok';
	};

	/* ========================= */

	function injectStyles() {
		performanceTracker.start('STYLES_INJECTION');
		logger.initModule('STYLES');

		if (!FEATURE.STYLES) {
			logger.moduleReady('STYLES');
			performanceTracker.end('STYLES_INJECTION');
			return;
		}

		if (!document.head) {
			setTimeout(injectStyles, 100);
			return;
		}

		if (document.getElementById('douyin-dynamic-styles')) {
			logger.moduleReady('STYLES');
			performanceTracker.end('STYLES_INJECTION');
			return;
		}

		const style = document.createElement('style');
		style.id = 'douyin-dynamic-styles';
		style.innerHTML = `
.wUFzLKZF.danmakuContainer,.xgplayer-immersive-switch-setting.immersive-switch{display:none !important;}
#douyin-header{
	background:rgba(0,0,0,0.10) !important;
	backdrop-filter:blur(5px) !important;
	-webkit-backdrop-filter:blur(5px) !important;
	position:fixed;
	top:0;left:0;width:100%;
	z-index:1000;
	border-bottom:1px solid rgba(255,255,255,0.10) !important;
	will-change:transform;
}
html.tm-nav-autohide #douyin-header{
	transform:translateY(-100%);
	transition:transform 0.28s ease;
}
html.tm-nav-autohide #douyin-header.show{transform:translateY(0);}
html:not(.tm-nav-autohide) #relatedVideoCard{padding-top:60px !important;}
#douyin-right-container{padding:0 !important;}
.ES4ZDMnf.N07M3gwz.isDark.nUy2mwhv.tC5Oyw9g{background:transparent !important;backdrop-filter:blur(5px) !important;-webkit-backdrop-filter:blur(5px) !important;}
.P2uoik9n .basePlayerContainer:not(.highlightPlayer) .xgplayer-controls{background-image:none !important;background:rgba(255,255,255,0.06) !important;backdrop-filter:blur(1px) saturate(1.1) !important;-webkit-backdrop-filter:blur(1px) saturate(1.1) !important;border:1px solid rgba(255,255,255,0.06) !important;box-shadow:none !important;transition:background 0.18s ease,backdrop-filter 0.18s ease,-webkit-backdrop-filter 0.18s ease,border-color 0.18s ease !important;}
.P2uoik9n .basePlayerContainer:not(.highlightPlayer) .xgplayer-controls:hover{background:rgba(255,255,255,0.16) !important;backdrop-filter:blur(16px) saturate(1.8) !important;-webkit-backdrop-filter:blur(16px) saturate(1.8) !important;border-color:rgba(255,255,255,0.10) !important;}
.xgplayer-controls.show{transform:translateY(0);}
#video-info-wrap{opacity:0.3 !important;transition:opacity 0.3s ease !important;}
#video-info-wrap:hover{opacity:1 !important;}
.video-info-mask,.UXyEyqbq.UdkDK3ea.DZKZZklc{display:none !important;}
.xg-video-container{height:100% !important;margin:auto 0 !important;bottom:0 !important;}
.L1TH4HdO.d6KxRih3.positionBox,.L1TH4HdO.positionBox{display:none !important;}
.nM3w4mVK.byAYwK4P.fullscreen_capture_feedback._z4WuFBP.NdwOHaIw{height:100% !important;border-radius:0 !important;}
.nM3w4mVK .kkbhJDRa{width:calc(100%) !important;height:calc(100%) !important;border-radius:0 !important;}
#sliderVideo,#sliderVideo::before,#sliderVideo::after{border-radius:0 !important;clip-path:none !important;-webkit-clip-path:none !important;mask:none !important;-webkit-mask:none !important;}
#slider-card{border-radius:0 !important;overflow:visible !important;}
.PsHhivd9.sHGvkSIn,.Q6VYnosf.userMenuPanelShadowAnimation,.yZlWL2BG,.vtP34xIV.sRPWIReI *,.vlXyJ9Hj,.h3JxiXCd.ZiQxU1tx *,.N3mRyy_g.popShadowAnimation,.F_XLoeLO.c64lf7w4,.ScTolygy.q1kuhAxH,.eBcBNgnP.PHb7B2cG,.k9soqkkm{
	background:rgba(255,255,255,0.25) !important;
	backdrop-filter:blur(10px) saturate(180%) !important;
	-webkit-backdrop-filter:blur(10px) saturate(180%) !important;
	border-radius:12px !important;
	border:1px solid rgba(255,255,255,0.6) !important;
	box-shadow:0 4px 12px rgba(0,0,0,0.05),inset 0 1px 0 rgba(255,255,255,0.6) !important;
	color:rgba(0,0,0,0.85) !important;
}
.PsHhivd9.sHGvkSIn,.Q6VYnosf.userMenuPanelShadowAnimation,.yZlWL2BG *,.vlXyJ9Hj,.N3mRyy_g.popShadowAnimation *,.F_XLoeLO.c64lf7w4 *,.ScTolygy.q1kuhAxH *,.eBcBNgnP.PHb7B2cG *,.k9soqkkm *{
	color:rgba(0,0,0,0.85) !important;
	text-shadow:0 1px 1px rgba(255,255,255,0.35);
}
.semi-button.semi-button-primary.semi-button-size-small.B7L9kG57{
	background:rgba(180,30,45,0.75) !important;
	backdrop-filter:blur(8px) saturate(120%) !important;
	-webkit-backdrop-filter:blur(8px) saturate(120%) !important;
	color:rgba(255,255,255,0.95) !important;
	border:1px solid rgba(255,255,255,0.25) !important;
	border-radius:999px !important;
	font-weight:600;
}
.semi-button.semi-button-primary.semi-button-size-small.B7L9kG57:hover{
	background:rgba(255,77,79,0.9) !important;
	transform:translateY(-1px);
	box-shadow:0 4px 10px rgba(0,0,0,0.2) !important;
}
		`;
		document.head.appendChild(style);

		logger.moduleReady('STYLES');
		performanceTracker.end('STYLES_INJECTION');
	}

	function injectSafeStyles() {
		performanceTracker.start('SAFE_STYLES');
		logger.initModule('STYLES');

		if (!FEATURE.STYLES) {
			logger.moduleReady('STYLES');
			performanceTracker.end('SAFE_STYLES');
			return;
		}

		const styleId = 'douyin-safe-styles';
		let style = document.getElementById(styleId);

		if (!style) {
			style = document.createElement('style');
			style.id = styleId;
			if (!document.head) {
				setTimeout(injectSafeStyles, 100);
				return;
			}
			document.head.appendChild(style);
		}

		style.textContent =
			`.xgplayer-playswitch.recommend-out-switch-btn{visibility:hidden !important;opacity:0 !important;}.fullscreen_capture_feedback{padding-right:0 !important;}`;

		logger.moduleReady('STYLES');
		performanceTracker.end('SAFE_STYLES');
	}

	function initTopNavigation() {
		performanceTracker.start('NAVIGATION_INIT');
		logger.initModule('NAVIGATION');

		if (!FEATURE.NAVIGATION) {
			document.documentElement.classList.remove('tm-nav-autohide');
			const h = document.getElementById('douyin-header');
			if (h) h.classList.add('show');
			logger.moduleReady('NAVIGATION');
			performanceTracker.end('NAVIGATION_INIT');
			return;
		}

		let header = null;
		let lastMouseY = 9999;
		let showTimeout = null;
		let hideTimeout = null;
		let inactivityTimer = null;
		let bound = false;

		function findHeader() {
			header = document.getElementById('douyin-header');
			return !!header;
		}

		function showHeader() {
			if (!header) return;
			clearTimeout(hideTimeout);
			hideTimeout = null;
			if (showTimeout) return;
			showTimeout = setTimeout(() => {
				if (!header) return;
				header.classList.add('show');
				showTimeout = null;
			}, 0);
		}

		function hideHeader() {
			if (!header) return;
			clearTimeout(showTimeout);
			showTimeout = null;
			if (hideTimeout) return;
			hideTimeout = setTimeout(() => {
				if (!header) return;
				header.classList.remove('show');
				hideTimeout = null;
			}, 160);
		}

		function resetInactivity() {
			clearTimeout(inactivityTimer);
			inactivityTimer = setTimeout(() => {
				if (lastMouseY > 120) hideHeader();
			}, 1200);
		}

		function onMouseMove(e) {
			lastMouseY = e.clientY;
			if (e.clientY <= 14) {
				showHeader();
				resetInactivity();
			}
		}

		function onWindowOut(e) {
			if (!e.relatedTarget && !e.toElement) hideHeader();
		}

		function bindOnce() {
			if (bound) return;
			bound = true;
			document.addEventListener('mousemove', onMouseMove, true);
			window.addEventListener('blur', hideHeader, true);
			document.addEventListener('mouseout', onWindowOut, true);
			window.addEventListener('scroll', () => {
				if (lastMouseY > 120) hideHeader();
			}, {
				passive: true
			});
		}

		function enable() {
			document.documentElement.classList.add('tm-nav-autohide');
			showHeader();
			setTimeout(() => {
				if (lastMouseY > 120) hideHeader();
			}, 800);
		}

		function boot() {
			if (!findHeader()) {
				setTimeout(boot, 700);
				return;
			}
			bindOnce();
			enable();
			logger.moduleReady('NAVIGATION');
			performanceTracker.end('NAVIGATION_INIT');
		}

		boot();

		(function watchRebuild() {
			const ob = new MutationObserver(() => {
				if (!FEATURE.NAVIGATION) return;
				const h = document.getElementById('douyin-header');
				if (h && h !== header) {
					header = h;
					showHeader();
					setTimeout(() => {
						if (lastMouseY > 120) hideHeader();
					}, 600);
				}
			});
			ob.observe(document.documentElement, {
				childList: true,
				subtree: true
			});
		})();
	}

	const LIVE_CONFIG = {
		debounceDelay: 3000
	};
	let lastPressTime = 0;
	let liveCheckInterval;

	function isElementVisible(el) {
		if (!el) return false;
		const rect = el.getBoundingClientRect();
		return (rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0 && rect.bottom <= window
			.innerHeight && rect.right <= window.innerWidth);
	}

	function startLiveCheck() {
		performanceTracker.start('LIVE_DETECTION');
		logger.initModule('LIVE');

		if (!FEATURE.LIVE) {
			logger.moduleReady('LIVE');
			performanceTracker.end('LIVE_DETECTION');
			return;
		}

		if (liveCheckInterval) clearInterval(liveCheckInterval);

		liveCheckInterval = setInterval(() => {
			const liveIndicators = document.querySelectorAll(
			'.live-indicator, [data-e2e="live-indicator"]');
			const visibleLive = Array.from(liveIndicators).filter(isElementVisible);
			if (visibleLive.length > 0) {
				const now = Date.now();
				if (now - lastPressTime >= LIVE_CONFIG.debounceDelay) {
					keyDown('ArrowDown', false);
					lastPressTime = now;
				}
			}
		}, 1000);

		logger.moduleReady('LIVE');
		performanceTracker.end('LIVE_DETECTION');
	}

	const HIDE_CONFIG = {
		textsToHide: ['充钻石', '客户端', '壁纸', '投稿', '更多'],
		maxRetry: 5
	};
	let hideRetryCount = 0;

	function hideElements() {
		performanceTracker.start('ELEMENTS_HIDING');
		logger.initModule('HIDE_ELEMENTS');

		if (!FEATURE.HIDE_ELEMENTS) {
			logger.moduleReady('HIDE_ELEMENTS');
			performanceTracker.end('ELEMENTS_HIDING');
			return;
		}

		let foundCount = 0;

		document.querySelectorAll('.jenVD1aU, .iQdAxsPk, .xgplayer-playswitch').forEach(item => {
			const text = (item.textContent || '').trim();
			if (HIDE_CONFIG.textsToHide.some(t => text.includes(t))) {
				const parent = item.closest('.Xu0nlrYh') || item.closest('div');
				if (parent) {
					parent.style.visibility = 'hidden';
					parent.style.opacity = '0';
					parent.style.pointerEvents = 'none';
					foundCount++;
				}
			}
		});

		const wallpaper = document.querySelector('.xFzvM6nY, .iQdAxsPk');
		if (wallpaper) {
			const p = wallpaper.closest('div');
			if (p) {
				p.style.visibility = 'hidden';
				foundCount++;
			}
		}

		if (foundCount < 4 && hideRetryCount < HIDE_CONFIG.maxRetry) {
			hideRetryCount++;
			setTimeout(hideElements, 3000);
		}

		logger.moduleReady('HIDE_ELEMENTS');
		performanceTracker.end('ELEMENTS_HIDING');
	}

	function setupElementHider() {
		performanceTracker.start('ELEMENTS_MONITOR');
		logger.initModule('HIDE_ELEMENTS');

		if (!FEATURE.HIDE_ELEMENTS) {
			logger.moduleReady('HIDE_ELEMENTS');
			performanceTracker.end('ELEMENTS_MONITOR');
			return;
		}

		let checkInterval = null;
		let successCount = 0;

		function hideElementsPersistent() {
			let foundCount = 0;

			document.querySelectorAll('.jenVD1aU, .iQdAxsPk').forEach(item => {
				const text = (item.textContent || '').trim();
				if (HIDE_CONFIG.textsToHide.some(t => text.includes(t))) {
					const parent = item.closest('.Xu0nlrYh') || item.closest('div');
					if (parent && parent.style.display !== 'none') {
						parent.style.display = 'none';
						foundCount++;
					}
				}
			});

			const wallpapers = document.querySelectorAll('.xFzvM6nY, .iQdAxsPk');
			wallpapers.forEach(wallpaper => {
				const parent = wallpaper.closest('div');
				if (parent && parent.style.display !== 'none') {
					parent.style.display = 'none';
					foundCount++;
				}
			});

			if (foundCount > 0) {
				successCount++;
				if (successCount > 3 && checkInterval) {
					clearInterval(checkInterval);
					checkInterval = setInterval(hideElementsPersistent, 5000);
				}
			}
		}

		hideElementsPersistent();
		checkInterval = setInterval(hideElementsPersistent, 1000);

		const observer = new MutationObserver(hideElementsPersistent);
		if (document.body) {
			observer.observe(document.body, {
				childList: true,
				subtree: true,
				attributes: true,
				attributeFilter: ['class']
			});
		}

		logger.moduleReady('HIDE_ELEMENTS');
		performanceTracker.end('ELEMENTS_MONITOR');
	}

	const AD_CONFIG = {
		blockedKeywords: ['影视', '电影', '电视剧', '观影', '好剧', '狂飙'],
		blockedAuthors: ['@王者荣耀'],
		debounceDelay: 3000,
		stableTries: 6,
		stableGap: 80
	};

	try {
		const kw = localStorage.getItem('xyDYTools_ads_keywords');
		if (kw) AD_CONFIG.blockedKeywords = kw.split(/[\n,,]+/g).map(v => v.trim()).filter(Boolean);
	} catch (_) {}

	try {
		const ba = localStorage.getItem('xyDYTools_ads_blockedAuthors');
		if (ba) AD_CONFIG.blockedAuthors = ba.split(/[\n,,]+/g).map(v => v.trim()).filter(Boolean);
	} catch (_) {}

	let lastSkipTime = 0;
	let adObserver = null;
	let adCheckTimer = null;

	function pickBestBySelector(selector) {
		const list = Array.from(document.querySelectorAll(selector));
		if (!list.length) return null;

		let best = null;
		let bestScore = -Infinity;

		for (const el of list) {
			if (!isVisible(el)) continue;
			const r = el.getBoundingClientRect();
			const area = r.width * r.height;
			const score = area * 0.001 + (window.innerHeight - r.bottom) * 0.5 + (window.innerWidth - r.left) *
			0.05;
			if (score > bestScore) {
				bestScore = score;
				best = el;
			}
		}

		return best;
	}

	function getVideoDescriptionNow() {
		const el = pickBestBySelector('[data-e2e="video-desc"]');
		if (!el) return '';
		return normText(el.textContent);
	}

	async function getVideoDescriptionStable() {
		let last = '';
		for (let i = 0; i < AD_CONFIG.stableTries; i++) {
			if (i > 0) await sleep(AD_CONFIG.stableGap);

			const cur = getVideoDescriptionNow();
			if (!cur) {
				last = '';
				continue;
			}

			await sleep(AD_CONFIG.stableGap);
			const cur2 = getVideoDescriptionNow();
			if (cur2 && cur2 === cur) return cur2;
			last = cur2 || cur;
		}
		return last;
	}

	function getAuthorIdNow() {
		const el = pickBestBySelector('[data-e2e="feed-video-nickname"]');
		if (!el) return '';
		return normText(el.textContent);
	}

	async function getAuthorIdStable() {
		let last = '';
		for (let i = 0; i < AD_CONFIG.stableTries; i++) {
			if (i > 0) await sleep(AD_CONFIG.stableGap);

			const cur = getAuthorIdNow();
			if (!cur) {
				last = '';
				continue;
			}

			await sleep(AD_CONFIG.stableGap);
			const cur2 = getAuthorIdNow();
			if (cur2 && cur2 === cur) return cur2;
			last = cur2 || cur;
		}
		return last;
	}

	function showBlockNotice(reason) {
		if (!FEATURE.ADS) return;

		const notice = document.createElement('div');
		notice.textContent = `[屏蔽] ${reason}`;
		Object.assign(notice.style, {
			position: 'fixed',
			bottom: '20px',
			left: '50%',
			transform: 'translateX(-50%)',
			background: 'rgba(0,0,0,0.7)',
			color: '#fff',
			padding: '8px 12px',
			borderRadius: '20px',
			zIndex: 9999,
			fontSize: '14px',
			maxWidth: '80%'
		});
		document.body.appendChild(notice);
		setTimeout(() => notice.remove(), 2000);
	}

	function hitKeyword(desc) {
		const d = String(desc || '').toLowerCase();
		return AD_CONFIG.blockedKeywords.find(k => d.includes(String(k).toLowerCase())) || '';
	}

	function hitAuthor(author) {
		const a = normText(author).toLowerCase();
		if (!a) return '';


		for (const raw of (AD_CONFIG.blockedAuthors || [])) {
			const p = normText(raw).toLowerCase();
			if (!p) continue;
			if (a.includes(p)) return raw;
		}
		return '';
	}


	function skipVideo(reasonText) {
		const now = Date.now();
		if (now - lastSkipTime < AD_CONFIG.debounceDelay) return;
		lastSkipTime = now;

		try {
			keyDown('r', false);
		} catch (_) {}
		try {
			keyUp('r');
		} catch (_) {}

		if (reasonText) showBlockNotice(reasonText);
	}

	async function checkAndHandleVideo() {
		if (!FEATURE.ADS) return;

		const [desc, author] = await Promise.all([
			getVideoDescriptionStable(),
			getAuthorIdStable()
		]);

		const a = hitAuthor(author);
		if (a) {
			skipVideo(`作者命中:${a}`);
			return;
		}

		const k1 = hitKeyword(desc);
		const k2 = hitKeyword(author);
		if (k1 || k2) skipVideo(`关键词命中:"${k1 || k2}"`);
	}

	function scheduleAdCheck() {
		if (!FEATURE.ADS) return;
		if (adCheckTimer) return;

		adCheckTimer = setTimeout(async () => {
			adCheckTimer = null;
			try {
				await checkAndHandleVideo();
			} catch (_) {}
		}, 120);
	}

	function observeVideo() {
		performanceTracker.start('ADS_OBSERVER');
		logger.initModule('ADS');

		if (!FEATURE.ADS) {
			logger.moduleReady('ADS');
			performanceTracker.end('ADS_OBSERVER');
			return;
		}

		if (adObserver) adObserver.disconnect();

		const target = document.body || document.documentElement;
		if (!target) {
			setTimeout(observeVideo, 800);
			return;
		}

		adObserver = new MutationObserver(() => scheduleAdCheck());
		adObserver.observe(target, {
			childList: true,
			subtree: true
		});

		scheduleAdCheck();

		logger.moduleReady('ADS');
		performanceTracker.end('ADS_OBSERVER');
	}

	window.xyDYTools = window.xyDYTools || {};

	window.xyDYTools.getDescNow = function() {
		return getVideoDescriptionNow();
	};
	window.xyDYTools.getDescStable = async function() {
		return await getVideoDescriptionStable();
	};
	window.xyDYTools.getAuthorNow = function() {
		return getAuthorIdNow();
	};
	window.xyDYTools.getAuthorStable = async function() {
		return await getAuthorIdStable();
	};
	window.xyDYTools.forceCheck = async function() {
		await checkAndHandleVideo();
		return 'checked';
	};
	window.xyDYTools.setBlockedAuthors = function(arr) {
		AD_CONFIG.blockedAuthors = Array.isArray(arr) ? arr : AD_CONFIG.blockedAuthors;
		return AD_CONFIG.blockedAuthors;
	};
	window.xyDYTools.addBlockedAuthor = function(one) {
		const v = normText(one);
		if (!v) return AD_CONFIG.blockedAuthors;
		if (!AD_CONFIG.blockedAuthors.some(x => normText(x) === v)) AD_CONFIG.blockedAuthors.push(v);
		return AD_CONFIG.blockedAuthors;
	};
	window.xyDYTools.listAllDescNodes = function() {
		const list = Array.from(document.querySelectorAll('[data-e2e="video-desc"]'));
		return list.map((el, i) => {
			const r = el.getBoundingClientRect();
			return {
				index: i,
				text: normText(el.textContent),
				visible: isVisible(el),
				rect: {
					top: r.top,
					left: r.left,
					width: r.width,
					height: r.height
				}
			};
		});
	};
	window.xyDYTools.listAllAuthorNodes = function() {
		const list = Array.from(document.querySelectorAll('[data-e2e="feed-video-nickname"]'));
		return list.map((el, i) => {
			const r = el.getBoundingClientRect();
			return {
				index: i,
				text: normText(el.textContent),
				visible: isVisible(el),
				rect: {
					top: r.top,
					left: r.left,
					width: r.width,
					height: r.height
				}
			};
		});
	};
	window.xyDYTools.printADDebug = async function() {
		const time = new Date().toLocaleTimeString();
		const nowDesc = window.xyDYTools.getDescNow();
		const nowAuthor = window.xyDYTools.getAuthorNow();
		const stableDesc = await window.xyDYTools.getDescStable();
		const stableAuthor = await window.xyDYTools.getAuthorStable();

		console.log('==== AD DEBUG ====');
		console.log('time:', time);
		console.log('nowAuthor:', nowAuthor);
		console.log('stableAuthor:', stableAuthor);
		console.log('nowDesc:', nowDesc);
		console.log('stableDesc:', stableDesc);
		console.log('blockedAuthors:', AD_CONFIG.blockedAuthors);
		console.log('blockedKeywords:', AD_CONFIG.blockedKeywords);
		console.log('==================');

		return {
			time,
			nowAuthor,
			stableAuthor,
			nowDesc,
			stableDesc
		};
	};

	const MOUSE_CONFIG = {
		swipeThreshold: 200,
		clickMaxMove: 5,
		clickMaxTime: 300,
		longPressTime: 500
	};

	let startX = 0;
	let startY = 0;
	let startTime = 0;
	let isMouseDown = false;
	let isSwiping = false;
	let blockClick = false;
	let longPressTimer = null;
	let isLongPressing = false;

	function mouseKeyDown(key, repeat) {
		if (!FEATURE.MOUSE) return;
		keyDown(key, repeat);
	}

	function mouseKeyUp(key) {
		if (!FEATURE.MOUSE) return;
		keyUp(key);
	}

	function likeVideo() {
		const video = document.querySelector('video');
		if (!video) return;

		const wasPlaying = !video.paused;
		mouseKeyDown('z', false);

		if (wasPlaying) {
			setTimeout(() => {
				if (video.paused) video.play();
			}, 100);
		}
	}

	function handleSwipe(direction) {
		if (!FEATURE.MOUSE) return;
		if (direction === 'left') likeVideo();
		if (direction === 'right') mouseKeyDown('r', false);
	}

	document.addEventListener('mousedown', function(e) {
		if (!FEATURE.MOUSE) return;

		startX = e.clientX;
		startY = e.clientY;
		startTime = Date.now();
		isMouseDown = true;
		isSwiping = false;
		blockClick = false;
		isLongPressing = false;

		if (e.button === 1) {
			e.preventDefault();
			e.stopPropagation();
			mouseKeyDown('x', false);
			return;
		}

		if (e.button === 0) {
			longPressTimer = setTimeout(() => {
				if (isMouseDown && !isSwiping) {
					isLongPressing = true;
					blockClick = true;
					mouseKeyDown('d', true);
				}
			}, MOUSE_CONFIG.longPressTime);
		}
	}, true);

	document.addEventListener('mousemove', function(e) {
		if (!FEATURE.MOUSE) return;
		if (!isMouseDown) return;
		if (isLongPressing) return;

		const deltaX = e.clientX - startX;
		const deltaY = e.clientY - startY;

		if (!isSwiping) {
			if (Math.abs(deltaX) > MOUSE_CONFIG.swipeThreshold || Math.abs(deltaY) > MOUSE_CONFIG
				.swipeThreshold) {
				isSwiping = true;
				blockClick = true;

				if (longPressTimer) {
					clearTimeout(longPressTimer);
					longPressTimer = null;
				}

				const direction = Math.abs(deltaX) > Math.abs(deltaY) ?
					(deltaX > 0 ? 'right' : 'left') :
					(deltaY > 0 ? 'down' : 'up');

				handleSwipe(direction);
			}
		}
	}, true);

	document.addEventListener('mouseup', function(e) {
		if (!FEATURE.MOUSE) return;
		if (!isMouseDown) return;

		if (e.button === 0) {
			if (longPressTimer) {
				clearTimeout(longPressTimer);
				longPressTimer = null;
			}

			if (isLongPressing) {
				mouseKeyUp('d');
				e.preventDefault();
				e.stopPropagation();
			}
		}

		const endX = e.clientX;
		const endY = e.clientY;
		const deltaX = endX - startX;
		const deltaY = endY - startY;
		const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
		const duration = Date.now() - startTime;

		if (distance >= MOUSE_CONFIG.clickMaxMove || duration >= MOUSE_CONFIG.clickMaxTime || isSwiping ||
			isLongPressing) {
			e.preventDefault();
			e.stopPropagation();
		}

		isMouseDown = false;
		isSwiping = false;
		isLongPressing = false;

		setTimeout(() => {
			blockClick = false;
		}, 50);
	}, true);

	document.addEventListener('click', function(e) {
		if (!FEATURE.MOUSE) return;
		if (blockClick) {
			e.stopPropagation();
			e.preventDefault();
		}
	}, true);

	document.addEventListener('keydown', (e) => {
		if (!e.altKey || e.ctrlKey || e.shiftKey) return;
		const key = e.key.toUpperCase();
		if (key === '1') {
			e.preventDefault();
			e.stopPropagation();
			setSidebarLock(!isSidebarLocked());
		}
	}, true);

	function initSettingsModal() {
		(function injectUIStyle() {
			if (document.getElementById('xydytools-ui-style')) return;
			const style = document.createElement('style');
			style.id = 'xydytools-ui-style';
			style.textContent = `
#xydytools-modal{position:fixed;inset:0;z-index:9999999;display:none;}
#xydytools-modal.show{display:block;}
#xydytools-modal .mask{position:absolute;inset:0;background:rgba(0,0,0,0.55);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);}
#xydytools-modal .panel{
	position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
	width:min(520px, 92vw);
	max-height:min(640px, 82vh);
	overflow:auto;
	background:rgba(255,255,255,0.28);
	border:1px solid rgba(255,255,255,0.55);
	border-radius:16px;
	box-shadow:0 16px 60px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.55);
	color:rgba(0,0,0,0.88);
}
#xydytools-modal .hd{
	position:sticky;top:0;z-index:1;
	display:flex;align-items:center;justify-content:space-between;gap:12px;
	padding:14px 14px 10px;
	background:linear-gradient(to bottom, rgba(255,255,255,0.38), rgba(255,255,255,0.18));
	backdrop-filter:blur(10px) saturate(180%);
	-webkit-backdrop-filter:blur(10px) saturate(180%);
	border-bottom:1px solid rgba(255,255,255,0.35);
	border-top-left-radius:16px;border-top-right-radius:16px;
}
#xydytools-modal .title{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:0.2px;}
#xydytools-modal .sub{margin-left:26px;margin-top:2px;font-size:12px;color:rgba(0,0,0,0.55);}
#xydytools-modal .close{
	width:34px;height:34px;border-radius:12px;
	display:flex;align-items:center;justify-content:center;
	cursor:pointer;user-select:none;
	background:rgba(255,255,255,0.35);
	border:1px solid rgba(255,255,255,0.45);
	transition:transform .12s ease, background .12s ease;
}
#xydytools-modal .close:hover{background:rgba(255,255,255,0.48);transform:translateY(-1px);}
#xydytools-modal .close:active{transform:translateY(0px) scale(0.98);}
#xydytools-modal .bd{padding:12px 14px 14px;}
#xydytools-modal .hint{
	font-size:12px;color:rgba(0,0,0,0.58);
	padding:10px 12px;border-radius:12px;
	background:rgba(255,255,255,0.26);
	border:1px solid rgba(255,255,255,0.35);
	margin-bottom:12px;
}
#xydytools-modal .list{display:flex;flex-direction:column;gap:10px;}
#xydytools-modal .row{
	display:flex;align-items:center;justify-content:space-between;gap:12px;
	padding:10px 12px;border-radius:14px;
	background:rgba(255,255,255,0.22);
	border:1px solid rgba(255,255,255,0.32);
}
#xydytools-modal .row .l{display:flex;flex-direction:column;gap:4px;min-width:0;}
#xydytools-modal .row .name{font-weight:700;font-size:14px;}
#xydytools-modal .row .desc{
	font-size:12px;color:rgba(0,0,0,0.55);
	white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
	max-width:320px;
}
#xydytools-modal .toggle{position:relative;display:inline-block;width:58px;height:32px;}
#xydytools-modal .toggle input{opacity:0;width:0;height:0;position:absolute;}
#xydytools-modal .slider{
	position:absolute;inset:0;background:#d1d1d6;border-radius:999px;
	cursor:pointer;-webkit-tap-highlight-color:transparent;user-select:none;
	transition:background .2s .25s;
}
#xydytools-modal .slider:before{
	content:'';position:absolute;top:3px;left:3px;width:26px;height:26px;border-radius:50%;
	background:#fff;box-shadow:0 2px 6px rgba(0,0,0,.2);
	transition:transform .25s cubic-bezier(.4,0,.2,1);
}
#xydytools-modal .toggle input:checked + .slider{background:#34c759;}
#xydytools-modal .toggle input:checked + .slider:before{transform:translateX(26px);}
#xydytools-modal .toggle input:focus + .slider{box-shadow:0 0 0 3px rgba(52,199,89,.3);}
#xydytools-modal .adsBox{
	margin-top:10px;
	padding:10px 12px;
	border-radius:14px;
	background:rgba(255,255,255,0.20);
	border:1px solid rgba(255,255,255,0.30);
	display:none;
}
#xydytools-modal .adsBox.show{display:block;}
#xydytools-modal .adsBox .t{font-weight:800;font-size:13px;margin-bottom:6px;}
#xydytools-modal .adsBox .d{font-size:12px;color:rgba(0,0,0,0.55);margin-bottom:8px;line-height:1.4;}
#xydytools-modal .adsBox textarea{
	width:100%;
	min-height:74px;
	resize:vertical;
	padding:10px 10px;
	border-radius:12px;
	background:rgba(255,255,255,0.35);
	border:1px solid rgba(255,255,255,0.45);
	outline:none;
	color:rgba(0,0,0,0.88);
	font-size:12px;
	line-height:1.45;
}
#xydytools-modal .adsBox textarea:focus{box-shadow:0 0 0 3px rgba(52,199,89,.20);}
#xydytools-modal .adsBox .ops{display:flex;gap:10px;margin-top:8px;align-items:center;}
#xydytools-modal .adsBox .btn{
	padding:7px 12px;
	border-radius:999px;
	background:rgba(0,0,0,0.75);
	color:#fff;
	border:1px solid rgba(255,255,255,0.25);
	cursor:pointer;
	user-select:none;
	font-size:12px;
}
#xydytools-modal .adsBox .btn:active{transform:scale(0.98);}
#xydytools-modal .adsBox .tip{font-size:12px;color:rgba(0,0,0,0.55);}
			`;
			(document.head || document.documentElement).appendChild(style);
		})();

		const FEATURE_META = [{
				k: 'SIDEBAR',
				name: '侧栏',
				desc: '隐藏/悬停弹出/锁定隐藏'
			},
			{
				k: 'STYLES',
				name: '样式美化',
				desc: '玻璃拟态 + 播放器控件优化'
			},
			{
				k: 'NAVIGATION',
				name: '顶部栏智能',
				desc: '靠近顶部显示,离开自动收起'
			},
			{
				k: 'LIVE',
				name: '直播跳过(未实装)',
				desc: '检测直播标识并跳过'
			},
			{
				k: 'HIDE_ELEMENTS',
				name: '隐藏杂项',
				desc: '充钻石/壁纸/投稿等'
			},
			{
				k: 'ADS',
				name: '关键词屏蔽',
				desc: '作者信息,视频信息命中关键词就跳过'
			},
			{
				k: 'MOUSE',
				name: '鼠标手势',
				desc: '左滑点赞/右滑R/中键X/左键长按D'
			},
			{
				k: 'LOADING',
				name: '加载遮罩',
				desc: '加载提示层'
			},
			{
				k: 'PERFORMANCE',
				name: '性能统计',
				desc: '模块耗时统计与报告'
			}
		];

		function ensureModal() {
			let modal = document.getElementById('xydytools-modal');
			if (modal) return modal;

			modal = document.createElement('div');
			modal.id = 'xydytools-modal';
			modal.innerHTML = `
				<div class="mask"></div>
				<div class="panel" role="dialog" aria-modal="true">
					<div class="hd">
						<div>
							<div class="title">
								<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" width="20" height="20">
									<path d="M14 7.5V6a2 2 0 0 0-4 0v1.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
									<path d="M5.5 10.5h13l-1 10H6.5l-1-10Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
									<path d="M9 13.5v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
									<path d="M15 13.5v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
								</svg>
								<span>xyDYTools</span>
							</div>
							<div class="sub">Alt + X 打开 / ESC 关闭</div>
						</div>
						<div class="close" title="关闭" aria-label="关闭">
							<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" width="18" height="18">
								<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
							</svg>
						</div>
					</div>
					<div class="bd">
						<div class="hint">有些开关改完建议刷新页面(比如已经启动的定时检测/观察者)。</div>
						<div class="list" id="xydytools-list"></div>
					</div>
				</div>
			`;
			document.documentElement.appendChild(modal);

			const hide = () => {
				modal.classList.remove('show');
				document.documentElement.style.overflow = '';
			};

			modal.querySelector('.mask').addEventListener('click', hide, true);
			modal.querySelector('.close').addEventListener('click', hide, true);

			window.addEventListener('keydown', (e) => {
				if (modal.classList.contains('show') && e.key === 'Escape') {
					e.preventDefault();
					e.stopPropagation();
					hide();
				}
			}, true);

			return modal;
		}

		function renderModal() {
			const modal = ensureModal();
			const list = modal.querySelector('#xydytools-list');
			list.innerHTML = '';

			FEATURE_META.forEach(item => {
				const row = document.createElement('div');
				row.className = 'row';
				row.innerHTML = `
					<div class="l">
						<div class="name">${item.name}</div>
						<div class="desc">${item.desc}</div>
					</div>
					<label class="toggle" data-key="${item.k}">
						<input type="checkbox" ${FEATURE[item.k] ? 'checked' : ''}>
						<span class="slider"></span>
					</label>
				`;
				list.appendChild(row);

				const input = row.querySelector('input');

				if (item.k === 'ADS') {
					const LS_ADS_KW = 'xyDYTools_ads_keywords';
					const LS_ADS_AUTH = 'xyDYTools_ads_blockedAuthors';

					function parseList(s) {
						return String(s || '').split(/[\n,,]+/g).map(v => v.trim()).filter(Boolean);
					}

					function joinList(arr) {
						return (Array.isArray(arr) ? arr : []).join('\n');
					}

					let kw = [];
					let ba = [];

					try {
						const kwRaw = localStorage.getItem(LS_ADS_KW);
						kw = kwRaw ? parseList(kwRaw) : (AD_CONFIG.blockedKeywords || []);
					} catch (_) {
						kw = (AD_CONFIG.blockedKeywords || []);
					}

					try {
						const baRaw = localStorage.getItem(LS_ADS_AUTH);
						ba = baRaw ? parseList(baRaw) : (AD_CONFIG.blockedAuthors || []);
					} catch (_) {
						ba = (AD_CONFIG.blockedAuthors || []);
					}

					const box = document.createElement('div');
					box.className = 'adsBox' + (FEATURE.ADS ? ' show' : '');
					box.innerHTML = `
						<div class="t">关键词库(简介 + 作者名共用)</div>
						<div class="d">一行一个;支持逗号分隔。命中就屏蔽。</div>
						<textarea class="kw" placeholder="例如:影视\n电影\n剪辑\n好剧">${joinList(kw)}</textarea>

						<div style="height:10px;"></div>

						<div class="t">作者黑名单(可选)</div>
						<div class="d">填作者名关键词,命中就屏蔽。</div>
						<textarea class="ba" placeholder="例如:@王者荣耀,直接填‘王者’">${joinList(ba)}</textarea>

						<div class="ops">
							<div class="btn save">保存</div>
							<div class="btn reset" style="background:rgba(180,30,45,0.75);">恢复默认</div>
							<div class="tip">保存后立刻生效</div>
						</div>
					`;
					list.appendChild(box);

					const kwEl = box.querySelector('textarea.kw');
					const baEl = box.querySelector('textarea.ba');

					function applyToConfig() {
						AD_CONFIG.blockedKeywords = parseList(kwEl.value);
						AD_CONFIG.blockedAuthors = parseList(baEl.value);
					}

					box.querySelector('.save').addEventListener('click', () => {
						try {
							localStorage.setItem(LS_ADS_KW, kwEl.value);
						} catch (_) {}
						try {
							localStorage.setItem(LS_ADS_AUTH, baEl.value);
						} catch (_) {}
						applyToConfig();
						try {
							scheduleAdCheck();
						} catch (_) {}
					}, true);

					box.querySelector('.reset').addEventListener('click', () => {
						kwEl.value = joinList(['影视', '电影', '电视剧', '观影', '好剧', '狂飙']);
						baEl.value = joinList(['@王者荣耀']);
						try {
							localStorage.removeItem(LS_ADS_KW);
						} catch (_) {}
						try {
							localStorage.removeItem(LS_ADS_AUTH);
						} catch (_) {}
						applyToConfig();
						try {
							scheduleAdCheck();
						} catch (_) {}
					}, true);

					input.addEventListener('change', () => box.classList.toggle('show', !!FEATURE.ADS),
						true);
				}

				input.addEventListener('change', () => {
					const v = input.checked ? 1 : 0;
					FEATURE[item.k] = v;
					try {
						localStorage.setItem(LS_PREFIX + item.k, String(v));
					} catch (_) {}

					if (item.k === 'STYLES') {
						if (!v) {
							try {
								document.getElementById('douyin-dynamic-styles')?.remove();
							} catch (_) {}
							try {
								document.getElementById('douyin-safe-styles')?.remove();
							} catch (_) {}
						} else {
							try {
								injectStyles();
							} catch (_) {}
							try {
								injectSafeStyles();
							} catch (_) {}
						}
					}

					if (item.k === 'NAVIGATION') {
						if (!v) {
							document.documentElement.classList.remove('tm-nav-autohide');
							const h = document.getElementById('douyin-header');
							if (h) h.classList.add('show');
						} else {
							try {
								initTopNavigation();
							} catch (_) {}
						}
					}

					if (item.k === 'ADS') {
						if (!v) {
							try {
								adObserver && adObserver.disconnect();
							} catch (_) {}
						} else {
							try {
								observeVideo();
								checkAndHandleVideo();
							} catch (_) {}
						}
					}

					if (item.k === 'LIVE') {
						if (!v) {
							try {
								liveCheckInterval && clearInterval(liveCheckInterval);
							} catch (_) {}
						} else {
							try {
								startLiveCheck();
							} catch (_) {}
						}
					}
				}, true);
			});
		}

		function showModal() {
			const modal = ensureModal();
			renderModal();
			modal.classList.add('show');
			document.documentElement.style.overflow = 'hidden';
		}

		window.addEventListener('keydown', (e) => {
			if (!e.altKey || e.ctrlKey || e.shiftKey) return;
			if ((e.key || '').toLowerCase() !== 'x') return;
			e.preventDefault();
			e.stopPropagation();
			showModal();
		}, true);
	}

	(function dySilentDislikeAndNoLive() {
		if (window.__dySilentMenuBound) return;
		window.__dySilentMenuBound = true;

		let running = false;

		document.addEventListener('mousemove', (e) => {
			window.__dyLastMouseX = e.clientX;
			window.__dyLastMouseY = e.clientY;
		}, true);

		(function injectSilentMenuStyle() {
			if (document.getElementById('dy-silent-menu-style')) return;
			const style = document.createElement('style');
			style.id = 'dy-silent-menu-style';
			style.textContent =
				`html.dy-silent-menu div[id="qgmiQqfW"]{opacity:0 !important;transition:none !important;}`;
			(document.head || document.documentElement).appendChild(style);
		})();

		function fireFullClick(el) {
			if (!el) return false;
			el.dispatchEvent(new MouseEvent('mousemove', {
				bubbles: true
			}));
			el.dispatchEvent(new MouseEvent('mousedown', {
				bubbles: true,
				button: 0,
				buttons: 1
			}));
			el.dispatchEvent(new MouseEvent('mouseup', {
				bubbles: true,
				button: 0,
				buttons: 0
			}));
			el.click();
			return true;
		}

		function findMenuItemByText(text) {
			const nodes = Array.from(document.querySelectorAll('.JyNO_DD4'));
			return nodes.find(n => (n.textContent || '').trim() === text) || null;
		}

		function waitMenuItem(text, timeoutMs) {
			return new Promise(resolve => {
				const t0 = Date.now();
				const hit = findMenuItemByText(text);
				if (hit) return resolve(hit);

				const ob = new MutationObserver(() => {
					const n = findMenuItemByText(text);
					if (n) {
						ob.disconnect();
						resolve(n);
					} else if (Date.now() - t0 > timeoutMs) {
						ob.disconnect();
						resolve(null);
					}
				});
				ob.observe(document.documentElement, {
					childList: true,
					subtree: true
				});

				setTimeout(() => {
					ob.disconnect();
					resolve(findMenuItemByText(text));
				}, timeoutMs + 50);
			});
		}

		function triggerRightClick() {
			let x = window.__dyLastMouseX;
			let y = window.__dyLastMouseY;
			if (typeof x !== 'number' || typeof y !== 'number') {
				x = Math.floor(window.innerWidth * 0.5);
				y = Math.floor(window.innerHeight * 0.5);
			}

			const el = document.elementFromPoint(x, y) || document.body;
			const down = {
				bubbles: true,
				cancelable: true,
				view: window,
				button: 2,
				buttons: 2,
				clientX: x,
				clientY: y
			};
			const up = {
				bubbles: true,
				cancelable: true,
				view: window,
				button: 2,
				buttons: 0,
				clientX: x,
				clientY: y
			};

			el.dispatchEvent(new MouseEvent('mousedown', down));
			el.dispatchEvent(new MouseEvent('contextmenu', down));
			el.dispatchEvent(new MouseEvent('mouseup', up));

			document.dispatchEvent(new MouseEvent('mouseup', {
				bubbles: true,
				cancelable: true,
				view: window,
				button: 0,
				buttons: 0,
				clientX: x,
				clientY: y
			}));
		}

		async function runSilentBlock() {
			if (running) return;
			running = true;

			try {
				document.documentElement.classList.add('dy-silent-menu');
				triggerRightClick();

				const a = await waitMenuItem('不感兴趣', 2500);
				if (a) fireFullClick(a);

				await sleep(80);

				const b = await waitMenuItem('不想看直播', 2500);
				if (b) fireFullClick(b);
			} finally {
				setTimeout(() => {
					document.documentElement.classList.remove('dy-silent-menu');
					running = false;
				}, 200);
			}
		}

		document.addEventListener('keydown', (e) => {
			if (e.altKey && !e.ctrlKey && !e.shiftKey && (e.key === 'f' || e.key === 'F')) {
				e.preventDefault();
				e.stopPropagation();
				runSilentBlock();
			}
		}, true);
	})();

	function initializeScript() {
		performanceTracker.start('TOTAL_INIT');

		createLoadingScreen();

		try {
			initSidebarToggle();
		} catch (_) {}
		try {
			injectStyles();
			window.__xyDYToolsBoot.styles = true;
		} catch (_) {}
		try {
			injectSafeStyles();
			window.__xyDYToolsBoot.safeStyles = true;
		} catch (_) {}

		try {
			initSettingsModal();
		} catch (_) {}

		if (window.location.host === 'www.douyin.com') {
			try {
				initTopNavigation();
				window.__xyDYToolsBoot.nav = true;
			} catch (_) {}
		}

		try {
			hideElements();
			window.__xyDYToolsBoot.hide = true;
		} catch (_) {}
		try {
			observeVideo();
			window.__xyDYToolsBoot.ads = true;
		} catch (_) {}
		try {
			checkAndHandleVideo();
		} catch (_) {}

		window.__xyDYToolsBoot.inited = true;

		try {
			startLiveCheck();
		} catch (_) {}

		if (document.readyState === 'complete') setupElementHider();
		else window.addEventListener('load', setupElementHider);

		setTimeout(() => {
			performanceTracker.printPerformanceReport();
			performanceTracker.end('TOTAL_INIT');
		}, 2000);
	}

	initializeScript();
	window.douyinPerformance = performanceTracker;
})();