GitHub Collapse Markdown

🚀 简洁高效的GitHub Markdown标题折叠脚本:智能嵌套🧠+快捷键⌨️+目录📑+搜索🔍+状态记忆💾+简约GUI🔘

当前为 2025-09-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        GitHub Collapse Markdown
// @version     3.3.0
// @description 🚀 简洁高效的GitHub Markdown标题折叠脚本:智能嵌套🧠+快捷键⌨️+目录📑+搜索🔍+状态记忆💾+简约GUI🔘
// @license     MIT
// @author      Xyea
// @namespace   https://github.com/XyeaOvO/GitHub-Collapse-Markdown
// @homepageURL https://github.com/XyeaOvO/GitHub-Collapse-Markdown
// @supportURL  https://github.com/XyeaOvO/GitHub-Collapse-Markdown/issues
// @match       https://github.com/*
// @match       https://gist.github.com/*
// @match       https://help.github.com/*
// @match       https://docs.github.com/*
// @run-at      document-idle
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @noframes
// @icon        https://github.githubassets.com/pinned-octocat.svg
// ==/UserScript==

(() => {
	"use strict";

	// 配置常量
	const CONFIG = {
		debug: GM_getValue("ghcm-debug-mode", false), // 调试模式开关
		colors: GM_getValue("ghcm-colors", [
			"#6778d0", "#ac9c3d", "#b94a73", "#56ae6c", "#9750a1", "#ba543d"
		]),
		animation: {
			duration: 200,
			easing: "cubic-bezier(0.4, 0, 0.2, 1)",
			maxAnimatedElements: GM_getValue("ghcm-performance-mode", false) ? 0 : 20, // 根据用户设置
			batchSize: 10 // 批量处理大小
		},
		selectors: {
			markdownContainers: [
				".markdown-body",
				".comment-body"
			],
			headers: ["H1", "H2", "H3", "H4", "H5", "H6"],
			excludeClicks: [".anchor", ".octicon-link", "a", "img"]
		},
		classes: {
			collapsed: "ghcm-collapsed",
			hidden: "ghcm-hidden",
			hiddenByParent: "ghcm-hidden-by-parent",
			noContent: "ghcm-no-content",
			tocContainer: "ghcm-toc-container",
			searchContainer: "ghcm-search-container",
			menuContainer: "ghcm-menu-container",
			menuButton: "ghcm-menu-button",
			bookmarked: "ghcm-bookmarked",
			activeHeading: "ghcm-active-heading",
			hoverHeading: "ghcm-hover-heading"
		},
		hotkeys: {
			enabled: GM_getValue("ghcm-hotkeys-enabled", true),
			toggleAll: "ctrl+shift+a", // 切换所有折叠
			collapseAll: "ctrl+shift+c", // 折叠所有
			expandAll: "ctrl+shift+e", // 展开所有
			showToc: "ctrl+shift+l", // 显示目录
			search: "ctrl+shift+f", // 搜索
			menu: "ctrl+shift+m", // 显示菜单
			bookmark: GM_getValue('ghcm-hotkey-bookmark', 'ctrl+shift+b'),
			nextHeading: 'j',
			prevHeading: 'k',
			navEnabled: GM_getValue('ghcm-nav-enabled', false)
		},
		memory: {
			enabled: GM_getValue("ghcm-memory-enabled", true),
			key: "ghcm-page-states"
		},
		bookmarks: {
			key: 'ghcm-bookmarks'
		},
		ui: {
			showLevelNumber: GM_getValue('ghcm-show-level-number', true),
		arrowSize: GM_getValue('ghcm-arrow-size', '0.8em')
		},
		colorSchemes: {
			default: ["#6778d0", "#ac9c3d", "#b94a73", "#56ae6c", "#9750a1", "#ba543d"],
			pastel:  ["#7aa2f7", "#e6a23c", "#f48fb1", "#9ccc65", "#b39ddb", "#ffab91"],
			vibrant: ["#3b82f6", "#f59e0b", "#ef4444", "#10b981", "#8b5cf6", "#f97316"],
		mono:    ["#6b7280", "#6b7280", "#6b7280", "#6b7280", "#6b7280", "#6b7280"]
		}
	};

	const storedCustomColors = GM_getValue('ghcm-custom-colors', null);
	if (Array.isArray(storedCustomColors) && storedCustomColors.length) {
		CONFIG.colorSchemes.custom = storedCustomColors;
	}

	// 日志控制函数
	const Logger = {
		log: (...args) => {
			if (CONFIG.debug) {
				console.log(...args);
			}
		},
		warn: (...args) => {
			console.warn(...args);
		},
		error: (...args) => {
			console.error(...args);
		}
	};

	// GUI菜单管理器
	class MenuManager {
		constructor(app) {
			this.app = app;
			this.isVisible = false;
			this.menuContainer = null;
			this.menuButton = null;
			this.init();
		}

		init() {
			this.createMenuButton();
			this.addMenuStyles();
			// 根据页面是否有 markdown 容器显示/隐藏按钮
			this.updateButtonVisibility();
			['pjax:end','turbo:load','turbo:render','pageshow'].forEach(evt => {
				try { document.addEventListener(evt, () => this.updateButtonVisibility()); } catch {}
			});
		}

		addMenuStyles() {
			GM_addStyle(`
				/* 菜单按钮 */
				.${CONFIG.classes.menuButton} {
					position: fixed;
					bottom: 20px;
					right: 20px;
					width: 50px;
					height: 50px;
					background: #6b7280;
					border: none;
					border-radius: 50%;
					cursor: pointer;
					z-index: 9999;
					box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
					transition: all 0.2s ease;
					display: flex;
					align-items: center;
					justify-content: center;
					font-size: 18px;
					color: white;
					user-select: none;
				}

				.${CONFIG.classes.menuButton}:hover {
					background: #4b5563;
					transform: translateY(-1px);
					box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
				}

				.${CONFIG.classes.menuButton}:active {
					transform: translateY(0) scale(0.95);
				}

				.${CONFIG.classes.menuButton}.menu-open {
					background: #374151;
					transform: rotate(45deg);
				}

				/* 菜单容器 */
				.${CONFIG.classes.menuContainer} {
					position: fixed;
					bottom: 80px;
					right: 20px;
					width: 300px;
					background: rgba(255, 255, 255, 0.98);
					backdrop-filter: blur(10px);
					border: 1px solid #e5e7eb;
					border-radius: 12px;
					box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
					z-index: 9998;
					opacity: 0;
					transform: translateY(10px) scale(0.95);
					transition: all 0.25s ease;
					overflow: hidden;
					font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
				}

				.${CONFIG.classes.menuContainer}.show {
					opacity: 1;
					transform: translateY(0) scale(1);
				}

				/* 菜单头部 */
				.ghcm-menu-header {
					padding: 16px 20px 12px;
					background: #f9fafb;
					color: #374151;
					text-align: center;
					border-bottom: 1px solid #e5e7eb;
				}

				.ghcm-menu-title {
					font-size: 16px;
					font-weight: 600;
					margin: 0 0 4px;
				}

				.ghcm-menu-subtitle {
					font-size: 11px;
					opacity: 0.7;
					margin: 0;
				}

				/* 菜单内容 */
				.ghcm-menu-content {
					padding: 0;
					max-height: 400px;
					overflow-y: auto;
				}

				/* 菜单分组 */
				.ghcm-menu-group {
					padding: 12px 0;
					border-bottom: 1px solid #f3f4f6;
				}

				.ghcm-menu-group:last-child {
					border-bottom: none;
				}

				.ghcm-menu-group-title {
					font-size: 10px;
					font-weight: 600;
					color: #9ca3af;
					text-transform: uppercase;
					letter-spacing: 0.5px;
					margin: 0 20px 8px;
				}

				/* 菜单项 */
				.ghcm-menu-item {
					display: flex;
					align-items: center;
					padding: 10px 20px;
					cursor: pointer;
					transition: background-color 0.15s ease;
					color: #374151;
					text-decoration: none;
					font-size: 13px;
					line-height: 1.4;
				}

				.ghcm-menu-item:hover {
					background: #f3f4f6;
					color: #1f2937;
				}

				.ghcm-menu-item:active {
					background: #e5e7eb;
				}

				.ghcm-menu-item-icon {
					width: 20px;
					height: 20px;
					margin-right: 12px;
					display: flex;
					align-items: center;
					justify-content: center;
					font-size: 16px;
					flex-shrink: 0;
				}

				.ghcm-menu-item-text {
					flex: 1;
					font-weight: 500;
				}

				.ghcm-menu-item-shortcut {
					font-size: 10px;
					color: #9ca3af;
					background: #f3f4f6;
					padding: 2px 6px;
					border-radius: 3px;
					font-family: Monaco, 'Courier New', monospace;
				}

				.ghcm-menu-item-note {
					margin-left: auto;
					font-size: 11px;
					color: #9ca3af;
				}

				.ghcm-menu-item-badge {
					background: #6b7280;
					color: white;
					font-size: 10px;
					padding: 2px 6px;
					border-radius: 6px;
					font-weight: 500;
				}

				/* 切换开关 */
				.ghcm-menu-toggle {
					position: relative;
					width: 36px;
					height: 18px;
					background: #d1d5db;
					border-radius: 9px;
					transition: background 0.2s ease;
					cursor: pointer;
				}

				.ghcm-menu-toggle.active {
					background: #6b7280;
				}

				.ghcm-menu-toggle::after {
					content: '';
					position: absolute;
					top: 2px;
					left: 2px;
					width: 14px;
					height: 14px;
					background: white;
					border-radius: 50%;
					transition: transform 0.2s ease;
					box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
				}

				.ghcm-menu-toggle.active::after {
					transform: translateX(18px);
				}

				/* 统计信息 */
				.ghcm-menu-stats {
					padding: 12px 20px;
					background: #f9fafb;
					font-size: 11px;
					color: #6b7280;
					line-height: 1.5;
				}

				.ghcm-menu-stats-item {
					display: flex;
					justify-content: space-between;
					margin-bottom: 3px;
				}

				.ghcm-menu-stats-item:last-child {
					margin-bottom: 0;
				}

				.ghcm-menu-stats-value {
					font-weight: 600;
					color: #374151;
				}

				.ghcm-bookmark-list {
					padding: 6px 10px;
					max-height: 160px;
					overflow-y: auto;
				}

				.ghcm-bookmark-item {
					display: flex;
					align-items: center;
					justify-content: space-between;
				}

				.ghcm-bookmark-info {
					display: flex;
					flex: 1;
					align-items: center;
					gap: 6px;
				}

				.ghcm-bookmark-level {
					font-size: 10px;
					font-weight: 600;
					color: #6b7280;
				}

				.ghcm-bookmark-text {
					flex: 1;
					font-size: 12px;
					color: #374151;
					overflow: hidden;
					text-overflow: ellipsis;
					white-space: nowrap;
				}

				.ghcm-bookmark-remove {
					background: none;
					border: none;
					color: #9ca3af;
					cursor: pointer;
					padding: 4px;
					border-radius: 4px;
					font-size: 12px;
				}

				.ghcm-bookmark-remove:hover {
					background: rgba(148, 163, 184, 0.18);
					color: #4b5563;
				}

				.ghcm-bookmark-empty {
					padding: 6px 0;
					text-align: center;
					font-size: 12px;
					color: #9ca3af;
				}

				/* 深色主题适配 */
				@media (prefers-color-scheme: dark) {
					.${CONFIG.classes.menuContainer} {
						background: rgba(31, 41, 55, 0.98);
						border-color: #374151;
					}

					.ghcm-menu-header {
						background: #1f2937;
						color: #f9fafb;
						border-bottom-color: #374151;
					}

					.ghcm-menu-item {
						color: #e5e7eb;
					}

					.ghcm-menu-item:hover {
						background: #374151;
						color: #f9fafb;
					}

					.ghcm-menu-group {
						border-bottom-color: #374151;
					}

					.ghcm-menu-group-title {
						color: #9ca3af;
					}

					.ghcm-menu-item-shortcut {
						background: #374151;
						color: #9ca3af;
					}

					.ghcm-menu-stats {
						background: #1f2937;
						color: #9ca3af;
					}

					.ghcm-menu-stats-value {
						color: #e5e7eb;
					}

					.${CONFIG.classes.bookmarked} {
						background: rgba(202, 138, 4, 0.38);
					}

					.ghcm-bookmark-text {
						color: #e5e7eb;
					}

					.ghcm-bookmark-remove:hover {
						background: rgba(75, 85, 99, 0.35);
						color: #f3f4f6;
					}

					.${CONFIG.classes.hoverHeading} {
						background: rgba(75, 85, 99, 0.32);
					}
				}

				/* 响应式设计 */
				@media (max-width: 480px) {
					.${CONFIG.classes.menuContainer} {
						right: 15px;
						width: calc(100vw - 30px);
						max-width: 320px;
					}

					.${CONFIG.classes.menuButton} {
						right: 15px;
						bottom: 15px;
					}
				}
			`);
		}

		createMenuButton() {
			this.menuButton = document.createElement('button');
			this.menuButton.className = CONFIG.classes.menuButton;
			this.menuButton.innerHTML = '⚙️';
			this.menuButton.title = 'GitHub Collapse Markdown 设置';
			this.menuButton.setAttribute('aria-label', '打开设置');
			this.menuButton.setAttribute('aria-expanded', 'false');
			this.menuButton.setAttribute('aria-controls', 'ghcm-menu-panel');

			this.menuButton.addEventListener('click', (e) => {
				e.stopPropagation();
				this.toggle();
			});

			document.body.appendChild(this.menuButton);
		}

		shouldShowButton() {
			try {
				return DOMUtils.hasMarkdownHeadings();
			} catch {
				return true;
			}
		}

		updateButtonVisibility() {
			if (!this.menuButton) return;
			const visible = this.shouldShowButton();
			this.menuButton.style.display = visible ? 'flex' : 'none';
		}

		createMenuContainer() {
			const container = document.createElement('div');
			container.className = CONFIG.classes.menuContainer;
			container.id = 'ghcm-menu-panel';
			container.setAttribute('role', 'dialog');
			container.setAttribute('aria-modal', 'true');
			container.setAttribute('aria-label', 'Collapse Markdown 设置');

			container.innerHTML = `
				<div class="ghcm-menu-header">
					<h3 class="ghcm-menu-title">📝 Collapse Markdown</h3>
					<p class="ghcm-menu-subtitle">智能标题折叠工具</p>
				</div>
				<div class="ghcm-menu-content">
					${this.generateMenuContent()}
				</div>
			`;

			this.setupMenuEvents(container);
			return container;
		}

		generateMenuContent() {
			const stats = this.getStatistics();

				return `
					<div class="ghcm-menu-stats">
						<div class="ghcm-menu-stats-item">
							<span>总标题数</span>
							<span class="ghcm-menu-stats-value" data-stat="total">${stats.total}</span>
						</div>
						<div class="ghcm-menu-stats-item">
							<span>已折叠</span>
							<span class="ghcm-menu-stats-value" data-stat="collapsed">${stats.collapsed}</span>
						</div>
						<div class="ghcm-menu-stats-item">
							<span>可见</span>
							<span class="ghcm-menu-stats-value" data-stat="visible">${stats.visible}</span>
						</div>
					</div>

					<div class="ghcm-menu-group">
						<div class="ghcm-menu-group-title">快速书签</div>
						<div class="ghcm-menu-item" data-action="bookmark-add">
							<div class="ghcm-menu-item-icon">⭐</div>
							<div class="ghcm-menu-item-text">收藏当前标题</div>
							<div class="ghcm-menu-item-shortcut">${CONFIG.hotkeys.bookmark}</div>
						</div>
						<div class="ghcm-menu-item" data-action="bookmark-clear">
							<div class="ghcm-menu-item-icon">🗂️</div>
							<div class="ghcm-menu-item-text">清空本页书签</div>
						</div>
						<div class="ghcm-bookmark-list">
							${this.renderBookmarkListItems()}
						</div>
					</div>

				<div class="ghcm-menu-group">
					<div class="ghcm-menu-group-title">基础操作</div>
					<div class="ghcm-menu-item" data-action="collapseAll">
						<div class="ghcm-menu-item-icon">📁</div>
						<div class="ghcm-menu-item-text">折叠所有</div>
						<div class="ghcm-menu-item-shortcut">${CONFIG.hotkeys.collapseAll}</div>
					</div>
					<div class="ghcm-menu-item" data-action="expandAll">
						<div class="ghcm-menu-item-icon">📂</div>
						<div class="ghcm-menu-item-text">展开所有</div>
						<div class="ghcm-menu-item-shortcut">${CONFIG.hotkeys.expandAll}</div>
					</div>
					<div class="ghcm-menu-item" data-action="toggleAll">
						<div class="ghcm-menu-item-icon">🔄</div>
						<div class="ghcm-menu-item-text">智能切换</div>
						<div class="ghcm-menu-item-shortcut">${CONFIG.hotkeys.toggleAll}</div>
					</div>
				</div>

				<div class="ghcm-menu-group">
					<div class="ghcm-menu-group-title">工具功能</div>
					<div class="ghcm-menu-item" data-action="showToc">
						<div class="ghcm-menu-item-icon">📑</div>
						<div class="ghcm-menu-item-text">目录导航</div>
						<div class="ghcm-menu-item-shortcut">${CONFIG.hotkeys.showToc}</div>
					</div>
					<div class="ghcm-menu-item" data-action="showSearch">
						<div class="ghcm-menu-item-icon">🔍</div>
						<div class="ghcm-menu-item-text">搜索标题</div>
						<div class="ghcm-menu-item-shortcut">${CONFIG.hotkeys.search}</div>
					</div>
				</div>

				<div class="ghcm-menu-group">
					<div class="ghcm-menu-group-title">按级别操作</div>
					<div class="ghcm-menu-item" data-action="collapseLevel-2">
						<div class="ghcm-menu-item-icon">➖</div>
						<div class="ghcm-menu-item-text">仅折叠 H2</div>
					</div>
					<div class="ghcm-menu-item" data-action="expandLevel-2">
						<div class="ghcm-menu-item-icon">➕</div>
						<div class="ghcm-menu-item-text">仅展开 H2</div>
					</div>
					<div class="ghcm-menu-item" data-action="collapseLevel-3">
						<div class="ghcm-menu-item-icon">➖</div>
						<div class="ghcm-menu-item-text">仅折叠 H3</div>
					</div>
					<div class="ghcm-menu-item" data-action="expandLevel-3">
						<div class="ghcm-menu-item-icon">➕</div>
						<div class="ghcm-menu-item-text">仅展开 H3</div>
					</div>
				</div>

					<div class="ghcm-menu-group">
						<div class="ghcm-menu-group-title">设置选项</div>
						<div class="ghcm-menu-item" data-action="togglePerformance">
							<div class="ghcm-menu-item-icon">⚡</div>
							<div class="ghcm-menu-item-text">性能模式</div>
							<div class="ghcm-menu-toggle ${CONFIG.animation.maxAnimatedElements === 0 ? 'active' : ''}" data-toggle="performance"></div>
						</div>
						<div class="ghcm-menu-item" data-action="toggleMemory">
							<div class="ghcm-menu-item-icon">💾</div>
							<div class="ghcm-menu-item-text">状态记忆</div>
							<div class="ghcm-menu-toggle ${CONFIG.memory.enabled ? 'active' : ''}" data-toggle="memory"></div>
						</div>
						<div class="ghcm-menu-item" data-action="toggleHotkeys">
							<div class="ghcm-menu-item-icon">⌨️</div>
							<div class="ghcm-menu-item-text">快捷键</div>
							<div class="ghcm-menu-toggle ${CONFIG.hotkeys.enabled ? 'active' : ''}" data-toggle="hotkeys"></div>
						</div>
						<div class="ghcm-menu-item" data-action="toggleVimNav">
							<div class="ghcm-menu-item-icon">🧭</div>
							<div class="ghcm-menu-item-text">Vim 导航热键</div>
							<div class="ghcm-menu-toggle ${CONFIG.hotkeys.navEnabled ? 'active' : ''}" data-toggle="vimNav"></div>
						</div>
						<div class="ghcm-menu-item" data-action="toggleDebug">
							<div class="ghcm-menu-item-icon">🐛</div>
							<div class="ghcm-menu-item-text">调试模式</div>
							<div class="ghcm-menu-toggle ${CONFIG.debug ? 'active' : ''}" data-toggle="debug"></div>
						</div>
				</div>

					<div class="ghcm-menu-group">
						<div class="ghcm-menu-group-title">样式设置</div>
						<div class="ghcm-menu-item" data-action="toggleShowLevelNumber">
							<div class="ghcm-menu-item-icon">🔽</div>
							<div class="ghcm-menu-item-text">仅显示箭头</div>
							<div class="ghcm-menu-toggle ${CONFIG.ui.showLevelNumber ? '' : 'active'}" data-toggle="showLevel"></div>
						</div>
						<div class="ghcm-menu-item" data-action="customColors">
							<div class="ghcm-menu-item-icon">🖌️</div>
							<div class="ghcm-menu-item-text">自定义配色</div>
						</div>
						<div class="ghcm-menu-item" data-action="adjustArrowSize">
							<div class="ghcm-menu-item-icon">🔠</div>
							<div class="ghcm-menu-item-text">箭头大小</div>
							<div class="ghcm-menu-item-note" data-arrow-size-value>${CONFIG.ui.arrowSize}</div>
						</div>
						<div class="ghcm-menu-item" data-action="setColors-default">
							<div class="ghcm-menu-item-icon">🎨</div>
							<div class="ghcm-menu-item-text">默认配色</div>
						</div>
					<div class="ghcm-menu-item" data-action="setColors-pastel">
						<div class="ghcm-menu-item-icon">🎨</div>
						<div class="ghcm-menu-item-text">柔和 Pastel</div>
					</div>
					<div class="ghcm-menu-item" data-action="setColors-vibrant">
						<div class="ghcm-menu-item-icon">🎨</div>
						<div class="ghcm-menu-item-text">鲜艳 Vibrant</div>
					</div>
					<div class="ghcm-menu-item" data-action="setColors-mono">
						<div class="ghcm-menu-item-icon">🎨</div>
						<div class="ghcm-menu-item-text">单色 Mono</div>
					</div>
				</div>

				<div class="ghcm-menu-group">
					<div class="ghcm-menu-group-title">重置功能</div>
					<div class="ghcm-menu-item" data-action="resetStates">
						<div class="ghcm-menu-item-icon">🔄</div>
						<div class="ghcm-menu-item-text">重置状态</div>
					</div>
					<div class="ghcm-menu-item" data-action="clearMemory">
						<div class="ghcm-menu-item-icon">🗑️</div>
						<div class="ghcm-menu-item-text">清除记忆</div>
					</div>
				</div>

				<div class="ghcm-menu-group">
					<div class="ghcm-menu-group-title">帮助信息</div>
					<div class="ghcm-menu-item" data-action="showHelp">
						<div class="ghcm-menu-item-icon">ℹ️</div>
						<div class="ghcm-menu-item-text">使用说明</div>
					</div>
				</div>
			`;
		}

		setupMenuEvents(container) {
			// 点击菜单项事件
			container.addEventListener('click', (e) => {
				const removeBtn = e.target.closest('[data-remove-bookmark]');
				if (removeBtn) {
					const index = parseInt(removeBtn.getAttribute('data-remove-bookmark'), 10);
					this.app.bookmarkManager.removeBookmarkByIndex(index);
					this.updateBookmarkList();
					e.stopPropagation();
					return;
				}

				const item = e.target.closest('.ghcm-menu-item');
				if (!item) return;

				const action = item.getAttribute('data-action');
				const toggle = e.target.closest('.ghcm-menu-toggle');

				if (toggle) {
					this.handleToggle(toggle);
					return;
				}

				if (action) {
					const shouldClose = this.handleAction(action);
					if (shouldClose !== false) {
						this.hide();
					}
				}
			});

			// 阻止菜单容器内的点击事件冒泡
			container.addEventListener('click', (e) => {
				e.stopPropagation();
			});
		}

		handleAction(action) {
			let shouldClose = true;
			switch (action) {
				case 'collapseAll':
					this.app.collapseManager.collapseAll();
					break;
				case 'expandAll':
					this.app.collapseManager.expandAll();
					break;
				case 'toggleAll':
					this.app.collapseManager.toggleAll();
					break;
				case 'showToc':
					this.app.tocGenerator.toggle();
					break;
				case 'showSearch':
					this.app.searchManager.toggle();
					break;
				case 'togglePerformance':
					this.app.togglePerformanceMode();
					this.refreshMenu();
					break;
				case 'toggleMemory':
					this.app.toggleMemory();
					this.refreshMenu();
					break;
				case 'toggleHotkeys':
					this.app.toggleHotkeys();
					this.refreshMenu();
					break;
				case 'toggleVimNav':
					this.app.toggleVimNav();
					this.refreshMenu();
					break;
				case 'toggleDebug':
					this.app.toggleDebug();
					this.refreshMenu();
					break;
				case 'bookmark-add':
					this.app.bookmarkManager.addBookmarkFromViewport();
					this.refreshMenu();
					shouldClose = false;
					break;
				case 'bookmark-clear':
					this.app.bookmarkManager.clearPageBookmarks();
					this.refreshMenu();
					shouldClose = false;
					break;
				case 'customColors':
					this.app.promptCustomColors();
					this.refreshMenu();
					shouldClose = false;
					break;
				case 'adjustArrowSize':
					this.app.promptArrowSize();
					this.refreshMenu();
					shouldClose = false;
					break;
				case 'resetStates':
					if (confirm('确定要重置当前页面的所有折叠状态吗?')) {
						this.app.resetAllStates();
						this.refreshMenu();
					}
					break;
				case 'clearMemory':
					if (confirm('确定要清除所有页面的记忆数据吗?')) {
						this.app.clearAllMemory();
						this.refreshMenu();
					}
					break;
				case 'showHelp':
					this.app.showHotkeyHelp();
					break;
				default:
					if (action.startsWith('bookmark-open-')) {
						const idx = parseInt(action.split('-')[2], 10);
						if (!Number.isFinite(idx)) {
							shouldClose = false;
							break;
						}
						this.app.bookmarkManager.openBookmarkByIndex(idx);
						break;
					}
					if (action.startsWith('collapseLevel-')) {
						const lvl = parseInt(action.split('-')[1], 10);
						this.app.collapseManager.collapseLevel(lvl);
						break;
					}
					if (action.startsWith('expandLevel-')) {
						const lvl = parseInt(action.split('-')[1], 10);
						this.app.collapseManager.expandLevel(lvl);
						break;
					}
					if (action === 'toggleShowLevelNumber') {
						this.app.toggleShowLevelNumber();
						this.refreshMenu();
						break;
					}
					if (action.startsWith('setColors-')) {
						const scheme = action.split('-')[1];
						this.app.setColorScheme(scheme);
						this.refreshMenu();
						break;
					}
			}
			return shouldClose;
		}

		handleToggle(toggle) {
			const toggleType = toggle.getAttribute('data-toggle');
			const isActive = toggle.classList.contains('active');

			toggle.classList.toggle('active', !isActive);

			switch (toggleType) {
				case 'performance':
					this.app.togglePerformanceMode();
					break;
				case 'memory':
					this.app.toggleMemory();
					break;
				case 'hotkeys':
					this.app.toggleHotkeys();
					break;
				case 'vimNav':
					this.app.toggleVimNav();
					break;
				case 'debug':
					this.app.toggleDebug();
					break;
				case 'showLevel':
					this.app.toggleShowLevelNumber();
					break;
			}
		}

		getStatistics() {
			const headers = this.app.collapseManager.getAllHeaders();
			const collapsed = headers.filter(h => h.classList.contains(CONFIG.classes.collapsed));
			const visible = headers.filter(h =>
				!h.classList.contains(CONFIG.classes.collapsed) &&
				!h.classList.contains(CONFIG.classes.noContent)
			);

			return {
				total: headers.length,
				collapsed: collapsed.length,
				visible: visible.length
			};
		}

		refreshMenu() {
			if (!this.menuContainer || !this.isVisible) return;
			const stats = this.getStatistics();
			this.updateMenuStats(stats);
			this.syncToggleState('performance', CONFIG.animation.maxAnimatedElements === 0);
			this.syncToggleState('memory', CONFIG.memory.enabled);
			this.syncToggleState('hotkeys', CONFIG.hotkeys.enabled);
			this.syncToggleState('vimNav', CONFIG.hotkeys.navEnabled);
			this.syncToggleState('debug', CONFIG.debug);
			// showLevel toggle active 代表仅显示箭头
			this.syncToggleState('showLevel', !CONFIG.ui.showLevelNumber);
			this.updateBookmarkList();
			this.updateArrowSizeValue();
		}

		updateMenuStats(stats) {
			if (!this.menuContainer) return;
			const mapping = {
				total: stats.total,
				collapsed: stats.collapsed,
				visible: stats.visible
			};
			Object.entries(mapping).forEach(([key, value]) => {
				const el = this.menuContainer.querySelector(`.ghcm-menu-stats-value[data-stat="${key}"]`);
				if (el) el.textContent = String(value);
			});
		}

			syncToggleState(toggleType, isActive) {
			if (!this.menuContainer) return;
			const toggle = this.menuContainer.querySelector(`.ghcm-menu-toggle[data-toggle="${toggleType}"]`);
			if (toggle) {
				toggle.classList.toggle('active', !!isActive);
			}
		}

		updateBookmarkList() {
			if (!this.menuContainer) return;
			const list = this.menuContainer.querySelector('.ghcm-bookmark-list');
			if (list) {
				list.innerHTML = this.renderBookmarkListItems();
			}
		}

		updateArrowSizeValue() {
			if (!this.menuContainer) return;
			const value = this.menuContainer.querySelector('[data-arrow-size-value]');
			if (value) value.textContent = CONFIG.ui.arrowSize;
		}

		renderBookmarkListItems() {
			const bookmarks = this.app.bookmarkManager?.getBookmarksForCurrentPage?.() || [];
			if (!bookmarks.length) {
				return `<div class="ghcm-bookmark-empty">暂无书签</div>`;
			}
			return bookmarks.map((bookmark, index) => {
				const levelLabel = typeof bookmark.level === 'number' ? `H${bookmark.level}` : 'H?';
				return `
					<div class="ghcm-menu-item ghcm-bookmark-item" data-action="bookmark-open-${index}">
						<div class="ghcm-bookmark-info">
							<span class="ghcm-bookmark-level">${levelLabel}</span>
							<span class="ghcm-bookmark-text">${this.escapeHtml(bookmark.text || '未命名标题')}</span>
						</div>
						<button class="ghcm-bookmark-remove" type="button" data-remove-bookmark="${index}" aria-label="移除书签">✕</button>
					</div>
				`;
			}).join('');
		}

		escapeHtml(text) {
			return String(text ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;','\'':'&#39;'}[c]));
		}

		show() {
			if (this.isVisible) return;

			if (this.menuContainer) {
				this.menuContainer.remove();
			}

			// 打开菜单前关闭其他浮层
			try { this.app.tocGenerator.hideToc(); } catch {}
			try { this.app.searchManager.hideSearch(); } catch {}

			this.menuContainer = this.createMenuContainer();
			document.body.appendChild(this.menuContainer);

			// 动画显示
			requestAnimationFrame(() => {
				this.menuContainer.classList.add('show');
			});

			this.menuButton.classList.add('menu-open');
			this.menuButton.setAttribute('aria-expanded', 'true');
			this.isVisible = true;

			// 点击外部关闭
			setTimeout(() => {
				document.addEventListener('click', this.hideOnClickOutside);
			}, 100);

			// ESC 关闭
			this._keyHandler = (e) => {
				if (e.key === 'Escape') this.hide();
			};
			document.addEventListener('keydown', this._keyHandler);

			// 初始焦点
			try { this.menuContainer.setAttribute('tabindex','-1'); this.menuContainer.focus(); } catch {}
		}

		hide() {
			if (!this.isVisible || !this.menuContainer) return;

			this.menuContainer.classList.remove('show');
			this.menuButton.classList.remove('menu-open');
			this.menuButton.setAttribute('aria-expanded', 'false');

			setTimeout(() => {
				if (this.menuContainer) {
					this.menuContainer.remove();
					this.menuContainer = null;
				}
			}, 300);

			this.isVisible = false;
			document.removeEventListener('click', this.hideOnClickOutside);
			if (this._keyHandler) {
				document.removeEventListener('keydown', this._keyHandler);
				this._keyHandler = null;
			}
		}

		toggle() {
			if (this.isVisible) {
				this.hide();
			} else {
				this.show();
			}
		}

		hideOnClickOutside = (e) => {
			if (!this.menuContainer?.contains(e.target) &&
				!this.menuButton?.contains(e.target)) {
				this.hide();
			}
		}
	}

	class HelpModal {
		constructor(app) {
			this.app = app;
			this.overlay = null;
			this.modal = null;
			this.contentContainer = null;
			this.content = null;
			this.closeButton = null;
			this.previousActive = null;
			this.handleOverlayClick = this.handleOverlayClick.bind(this);
			this.handleKeydown = this.handleKeydown.bind(this);
		}

		ensureElements() {
			if (this.overlay) return;

			this.overlay = document.createElement('div');
			this.overlay.className = 'ghcm-help-overlay';

			this.modal = document.createElement('div');
			this.modal.className = 'ghcm-help-modal';
			this.modal.setAttribute('role', 'dialog');
			this.modal.setAttribute('aria-modal', 'true');
			this.modal.setAttribute('aria-label', 'GitHub Collapse Markdown 使用说明');
			this.modal.setAttribute('tabindex', '-1');

			const header = document.createElement('div');
			header.className = 'ghcm-help-header';

			const title = document.createElement('div');
			title.className = 'ghcm-help-title';

			const titleText = document.createElement('span');
			titleText.className = 'ghcm-help-title-text';
			titleText.textContent = 'GitHub Collapse Markdown';

			const titleSub = document.createElement('span');
			titleSub.className = 'ghcm-help-title-sub';
			titleSub.textContent = '使用说明';

			title.append(titleText, titleSub);

			this.closeButton = document.createElement('button');
			this.closeButton.type = 'button';
			this.closeButton.className = 'ghcm-help-close';
			this.closeButton.setAttribute('aria-label', '关闭使用说明弹窗');
			this.closeButton.textContent = '✕';
			this.closeButton.addEventListener('click', () => this.hide());

			header.append(title, this.closeButton);

			this.contentContainer = document.createElement('div');
			this.contentContainer.className = 'ghcm-help-content';

			this.content = document.createElement('article');
			this.content.className = 'markdown-body';
			this.contentContainer.appendChild(this.content);

			this.modal.append(header, this.contentContainer);
			this.overlay.appendChild(this.modal);
		}

		show() {
			this.ensureElements();
			this.previousActive = document.activeElement instanceof HTMLElement ? document.activeElement : null;

			this.updateContent();

			try { this.app.menuManager?.hide(); } catch {}
			try { this.app.tocGenerator?.hideToc?.(); } catch {}
			try { this.app.searchManager?.hideSearch?.(); } catch {}

			if (!this.overlay.isConnected) {
				document.body.appendChild(this.overlay);
			}

			requestAnimationFrame(() => {
				this.overlay.classList.add('show');
			});

			document.addEventListener('keydown', this.handleKeydown, true);
			this.overlay.addEventListener('click', this.handleOverlayClick);

			try { this.modal.focus(); } catch {}
		}

		hide() {
			if (!this.overlay) return;
			this.overlay.classList.remove('show');
			document.removeEventListener('keydown', this.handleKeydown, true);
			this.overlay.removeEventListener('click', this.handleOverlayClick);

			setTimeout(() => {
				if (this.overlay?.parentNode) {
					this.overlay.parentNode.removeChild(this.overlay);
				}
			}, 220);

			if (this.previousActive) {
				try { this.previousActive.focus(); } catch {}
			}
		}

		handleOverlayClick(event) {
			if (event.target === this.overlay) {
				this.hide();
			}
		}

		handleKeydown(event) {
			if (event.key === 'Escape') {
				event.stopPropagation();
				event.preventDefault();
				this.hide();
			}
		}

		updateContent() {
			if (!this.content) return;
			this.content.innerHTML = this.generateContentHTML();
			this.contentContainer.scrollTop = 0;
		}

		generateContentHTML() {
			const hotkeys = CONFIG.hotkeys;
			const navHint = hotkeys.navEnabled ? '(Vim 导航已启用)' : '(默认关闭,可在设置中开启)';
			return `
<h1>🚀 GitHub Collapse Markdown 使用指南</h1>
<p>脚本为 GitHub 上的 Markdown、Issue、PR 与 Gist 页面提供标题折叠、目录导航、搜索、书签与状态记忆等增强功能。本指南涵盖快速入门、快捷键、界面操作、设置项与进阶技巧。</p>

<section class="ghcm-help-section">
	<h2>⚡ 快速开始</h2>
	<ol>
		<li>打开任意支持的 GitHub 页面,脚本会在右下角生成浮动菜单按钮。</li>
		<li>点击任意标题即可折叠/展开对应内容,嵌套标题会智能保持层级状态。</li>
		<li>使用右下角菜单或快捷键 ${this.wrapHotkey(hotkeys.menu)} 呼出设置面板,探索目录、搜索和自定义选项。</li>
		<li>折叠状态与书签会针对当前页面自动保存,刷新后仍保持。</li>
	</ol>
</section>

<section class="ghcm-help-section">
	<h2>⌨️ 快捷键速查</h2>
	<div class="ghcm-help-grid">
		<div class="ghcm-help-card">
			<h3>折叠与视图</h3>
			${this.renderShortcut('折叠全部', hotkeys.collapseAll)}
			${this.renderShortcut('展开全部', hotkeys.expandAll)}
			${this.renderShortcut('智能切换', hotkeys.toggleAll)}
			${this.renderShortcut('显示菜单', hotkeys.menu)}
		</div>
		<div class="ghcm-help-card">
			<h3>导航工具</h3>
			${this.renderShortcut('打开目录', hotkeys.showToc)}
			${this.renderShortcut('搜索标题', hotkeys.search)}
			${this.renderShortcut('收藏当前标题', hotkeys.bookmark)}
		</div>
		<div class="ghcm-help-card">
			<h3>高级导航 ${this.escapeHtml(navHint)}</h3>
			${this.renderShortcut('下一标题', hotkeys.nextHeading)}
			${this.renderShortcut('上一标题', hotkeys.prevHeading)}
		</div>
	</div>
	<p class="ghcm-help-footnote">快捷键可在设置菜单中整体开关;收藏、导航键位均支持自定义(Tampermonkey 菜单)。</p>
</section>

<section class="ghcm-help-section">
	<h2>🖱️ 鼠标手势</h2>
	<ul class="ghcm-help-list">
		<li><strong>单击标题</strong><span>折叠或展开对应内容块。</span></li>
		<li><strong>Shift + 单击</strong><span>同步折叠/展开当前层级所有标题。</span></li>
		<li><strong>悬停标题</strong><span>查看当前层级高亮,配合目录定位更直观。</span></li>
		<li><strong>右下角菜单按钮</strong><span>打开现代化 GUI,集中管理所有功能。</span></li>
	</ul>
</section>

<section class="ghcm-help-section">
	<h2>🎛️ 主要界面</h2>
	<div class="ghcm-help-grid">
		<div class="ghcm-help-card">
			<h3>目录导航</h3>
			<p>以树形结构展示页面所有标题,支持自动折叠同步、快速跳转与当前标题高亮。</p>
		</div>
		<div class="ghcm-help-card">
			<h3>标题搜索</h3>
			<p>即时索引当前页面标题,支持模糊匹配、键盘上下键切换结果以及 Tab/Shift+Tab 在输入框与结果间移动。</p>
		</div>
		<div class="ghcm-help-card">
			<h3>书签面板</h3>
			<p>为常用段落添加收藏,支持从视窗捕获、列表跳转与一键清除,跨会话持久保存。</p>
		</div>
	</div>
</section>

<section class="ghcm-help-section">
	<h2>⚙️ 设置选项</h2>
	<ul class="ghcm-help-list">
		<li><strong>性能模式</strong><span>在长篇文档中禁用动画,提升滚动与切换响应。</span></li>
		<li><strong>状态记忆</strong><span>按页面保存折叠状态与展开偏好,可随时清空。</span></li>
		<li><strong>快捷键总开关</strong><span>与 Vim 导航独立控制,满足不同编辑习惯。</span></li>
		<li><strong>箭头外观</strong><span>可切换显示级别数字、调整箭头尺寸并自定义配色。</span></li>
	</ul>
</section>

<section class="ghcm-help-section">
	<h2>💡 实用技巧</h2>
	<ul class="ghcm-help-list">
		<li><strong>智能嵌套</strong><span>展开父级标题不会强制展开子级,保持阅读上下文。</span></li>
		<li><strong>哈希定位</strong><span>访问含锚点链接时自动展开相关标题并滚动到视图。</span></li>
		<li><strong>跨页面记忆</strong><span>Issue / PR / Wiki / 文档页面均以 URL 为键保存状态。</span></li>
		<li><strong>调试模式</strong><span>启用后在控制台输出内部状态,便于排查自定义冲突。</span></li>
	</ul>
</section>

<p class="ghcm-help-footnote">如遇折叠异常,可在菜单中清空记忆数据或刷新页面重新加载脚本;欢迎在 GitHub Issues 提交反馈与建议。</p>
`.trim();
		}

		renderShortcut(label, hotkey) {
			return `
		<div class="ghcm-help-shortcut">
			<span>${this.escapeHtml(label)}</span>
			${this.wrapHotkey(hotkey)}
		</div>
		`.trim();
		}

		wrapHotkey(hotkey) {
			return `<span class="ghcm-help-kbd"><span>${this.escapeHtml(hotkey || '未设置')}</span></span>`;
		}

		escapeHtml(text) {
			return String(text ?? '').replace(/[&<>"']/g, char => ({
				'&': '&amp;',
				'<': '&lt;',
				'>': '&gt;',
				'"': '&quot;',
				"'": '&#39;'
			}[char] || char));
		}
	}

	// 状态管理
	class StateManager {
		constructor() {
			this.headerStates = new Map();
			this.observers = [];
			this.pageUrl = this.getPageKey();
			this._saveTimer = null;
			this._pendingSave = false;
			this._saveDelay = 200;
			try {
				window.addEventListener('beforeunload', () => this.flushPendingSave());
			} catch {}
		}

		getPageKey() {
			try {
				return `${window.location.origin}${window.location.pathname}`;
			} catch (e) {
				return window.location.href;
			}
		}

		updatePageKey() {
			const newKey = this.getPageKey();
			if (newKey !== this.pageUrl) {
				this.headerStates.clear();
				this.pageUrl = newKey;
			}
		}

		setHeaderState(headerKey, state) {
			this.headerStates.set(headerKey, state);
			this.scheduleSave();
		}

		getHeaderState(headerKey) {
			return this.headerStates.get(headerKey);
		}

		generateHeaderKey(element) {
			// 优先使用稳定的 DOM id/锚点,避免文本或位置变化导致状态错配
			try {
				const normalize = value => (typeof value === 'string' ? value.trim() : '');
				const isSynthetic = id => /^ghcm-(?:bookmark|h)-/i.test(id || '');
				const stableId = (() => {
					const directId = normalize(element.getAttribute?.('id') || element.id);
					if (directId && !isSynthetic(directId)) return directId;

					const anchor = element.querySelector?.('.anchor');
					if (anchor) {
						const anchorId = normalize(anchor.getAttribute('id'));
						if (anchorId && !isSynthetic(anchorId)) return anchorId;
						const hrefId = normalize(anchor.getAttribute('href')?.replace(/^#/, ''));
						if (hrefId && !isSynthetic(hrefId)) return hrefId;
					}

					const anyWithId = element.querySelector?.('[id]');
					const childId = normalize(anyWithId?.getAttribute('id'));
					if (childId && !isSynthetic(childId)) return childId;
					return null;
				})();

				if (stableId) return `id:${stableId}`;
			} catch {}

			// 回退到基于 level+文本+位置 的键
			const level = this.getHeaderLevel(element);
			const text = element.textContent?.trim() || "";
			const position = Array.from(element.parentElement?.children || []).indexOf(element);
			return `${level}-${text}-${position}`;
		}

		getHeaderLevel(element) {
			return DOMUtils.getHeadingLevel(element);
		}

		clear() {
			this.headerStates.clear();
			this.scheduleSave({ force: true });
		}

		// 状态记忆功能
		scheduleSave({ force = false } = {}) {
			if (!CONFIG.memory.enabled) {
				this.cancelScheduledSave();
				return;
			}

			this._pendingSave = true;
			if (force) {
				this.flushPendingSave();
				return;
			}

			if (this._saveTimer) return;
			this._saveTimer = setTimeout(() => {
				this.flushPendingSave();
			}, this._saveDelay);
		}

		cancelScheduledSave() {
			if (this._saveTimer) {
				clearTimeout(this._saveTimer);
				this._saveTimer = null;
			}
			this._pendingSave = false;
		}

		flushPendingSave() {
			if (!this._pendingSave) return;
			this._pendingSave = false;
			if (this._saveTimer) {
				clearTimeout(this._saveTimer);
				this._saveTimer = null;
			}
			if (!CONFIG.memory.enabled) return;

			try {
				const pageStates = GM_getValue(CONFIG.memory.key, {});
				const currentStates = {};

				this.headerStates.forEach((state, key) => {
					currentStates[key] = state.isCollapsed;
				});

				pageStates[this.pageUrl] = currentStates;
				GM_setValue(CONFIG.memory.key, pageStates);
			} catch (e) {
				Logger.warn("[GHCM] 保存状态失败:", e);
			}
		}

		loadFromMemory() {
			if (!CONFIG.memory.enabled) return;

			try {
				const pageStates = GM_getValue(CONFIG.memory.key, {});
				const currentStates = pageStates[this.pageUrl];

				if (currentStates) {
					Object.entries(currentStates).forEach(([key, isCollapsed]) => {
						this.headerStates.set(key, { isCollapsed });
					});
					Logger.log(`[GHCM] 已加载 ${Object.keys(currentStates).length} 个已保存的状态`);
				}
			} catch (e) {
				Logger.warn("[GHCM] 加载状态失败:", e);
			}
		}

		clearMemory() {
			try {
				const pageStates = GM_getValue(CONFIG.memory.key, {});
				delete pageStates[this.pageUrl];
				GM_setValue(CONFIG.memory.key, pageStates);
				Logger.log("[GHCM] 已清除当前页面的记忆状态");
			} catch (e) {
				Logger.warn("[GHCM] 清除状态失败:", e);
			}
		}
	}

	// 快捷键管理器
	const EVENT_HANDLED_FLAG = '__ghcmHotkeyHandled__';

	class HotkeyManager {
		constructor(collapseManager) {
			this.collapseManager = collapseManager;
			this._boundHandler = null;
			this._isBound = false;
			this.app = null;
			this._listenerOptions = { capture: true, passive: false };
			const nativeWindow = (() => {
				try { return typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; }
				catch { return window; }
			})();
			this._listenerTargets = Array.from(new Set([nativeWindow, document]));
			this.setupHotkeys();
		}

		setApp(app) {
			this.app = app;
		}

		setupHotkeys() {
			if (!CONFIG.hotkeys.enabled || this._isBound) return;

			if (!this._boundHandler) {
				this._boundHandler = this.handleKeyDown.bind(this);
			}
			this._listenerTargets.forEach(target => {
				try { target.addEventListener('keydown', this._boundHandler, this._listenerOptions); } catch {}
			});
			this._isBound = true;
			Logger.log("[GHCM] 快捷键已启用:", Object.entries(CONFIG.hotkeys)
				.filter(([k]) => k !== 'enabled' && k !== 'navEnabled')
				.map(([k, v]) => `${k}: ${v}`)
				.join(', '));
		}

		teardownHotkeys() {
			if (this._isBound && this._boundHandler) {
				this._listenerTargets.forEach(target => {
					try { target.removeEventListener('keydown', this._boundHandler, this._listenerOptions); } catch {}
				});
				this._isBound = false;
			}
		}

		blockEvent(event) {
			try {
				event.preventDefault();
				event.stopPropagation();
				if (typeof event.stopImmediatePropagation === 'function') {
					event.stopImmediatePropagation();
				}
			} catch {}
		}

		handleKeyDown(event) {
			if (event[EVENT_HANDLED_FLAG]) return;
			event[EVENT_HANDLED_FLAG] = true;

			if (!CONFIG.hotkeys.enabled) return;
			// 在输入/可编辑区域内不触发全局快捷键
			try {
				const t = event.target;
				if (t && (t.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"], [role="textbox"]')))
					return;
			} catch {}

			const combo = this.getKeyCombo(event);

			switch (combo) {
				case CONFIG.hotkeys.collapseAll:
					this.blockEvent(event);
					this.collapseManager.collapseAll();
					break;
				case CONFIG.hotkeys.expandAll:
					this.blockEvent(event);
					this.collapseManager.expandAll();
					break;
				case CONFIG.hotkeys.toggleAll:
					this.blockEvent(event);
					this.collapseManager.toggleAll();
					break;
				case CONFIG.hotkeys.showToc:
					this.blockEvent(event);
					this.collapseManager.toggleToc();
					break;
				case CONFIG.hotkeys.search:
					this.blockEvent(event);
					this.collapseManager.toggleSearch();
					break;
				case CONFIG.hotkeys.menu:
					this.blockEvent(event);
					if (this.collapseManager.menuManager) {
						this.collapseManager.menuManager.toggle();
					}
					break;
				case CONFIG.hotkeys.bookmark:
					this.blockEvent(event);
					this.app?.bookmarkManager?.toggleBookmarkForActiveHeader();
					break;
			}

			if (CONFIG.hotkeys.navEnabled) {
				if (combo === CONFIG.hotkeys.nextHeading) {
					this.blockEvent(event);
					this.collapseManager.focusNextHeading();
					return;
				}
				if (combo === CONFIG.hotkeys.prevHeading) {
					this.blockEvent(event);
					this.collapseManager.focusPreviousHeading();
					return;
				}
			}
		}

		getKeyCombo(event) {
			const keys = [];
			const isMac = (() => { try { return /mac/i.test(navigator.platform || navigator.userAgent || ''); } catch { return false; } })();
			if (event.ctrlKey || (isMac && event.metaKey)) keys.push('ctrl');
			if (event.shiftKey) keys.push('shift');
			if (event.altKey) keys.push('alt');
			if (event.metaKey && !isMac) keys.push('meta');

			const key = event.key.toLowerCase();
			if (key !== 'control' && key !== 'shift' && key !== 'alt' && key !== 'meta') {
				keys.push(key);
			}

			return keys.join('+');
		}
	}

	// 目录生成器
	class TocGenerator {
		constructor() {
			this.tocContainer = null;
			this.isVisible = false;
			this._keyHandler = null;
			this._idSeed = 0;
			this._scrollHandler = null;
			this._raf = null;
		}

		generateToc() {
			const headers = this.getAllHeaders();
			if (headers.length === 0) return null;

			const toc = document.createElement('div');
			toc.className = CONFIG.classes.tocContainer;
			toc.setAttribute('role', 'dialog');
			toc.setAttribute('aria-modal', 'false');
			toc.setAttribute('aria-label', '目录导航');
			toc.innerHTML = `
				<div class="ghcm-toc-header">
					<h3 id="ghcm-toc-title">📑 目录导航</h3>
					<button class="ghcm-toc-close" title="关闭目录" aria-label="关闭目录">✕</button>
				</div>
				<div class="ghcm-toc-content">
					${this.generateTocItems(headers)}
				</div>
			`;

			this.setupTocEvents(toc);
			return toc;
		}

		getAllHeaders() {
			// 复用 CollapseManager 的收集以减少重复遍历
			const source = (this.collapseManager && typeof this.collapseManager.getAllHeaders === 'function')
				? this.collapseManager.getAllHeaders()
				: DOMUtils.collectHeadings();

			const list = source.map(el => ({
				element: el,
				level: DOMUtils.getHeadingLevel(el),
				text: el.textContent.trim(),
				id: this.getHeaderId(el)
			}));

			return this.dedupeById(list);
		}

		// 以 header id 去重,避免部分页面 DOM 结构重复扫描
		dedupeById(items) {
			try {
				const map = new Map();
				for (const it of items) {
					if (!it || !it.id) continue;
					if (!map.has(it.id)) map.set(it.id, it);
				}
				return Array.from(map.values());
			} catch {
				return items;
			}
		}

		generateTocItems(headers) {
			return headers.map(header => {
				const indent = (header.level - 1) * 20;
				const element = header.element;
				const isCollapsed = element?.classList?.contains(CONFIG.classes.collapsed);
				let isNavigable = true;
				try {
					if (element && this.collapseManager && typeof this.collapseManager.isHeaderNavigable === 'function') {
						isNavigable = this.collapseManager.isHeaderNavigable(element);
					}
				} catch {}
				const shouldShowCollapsed = Boolean(isCollapsed || !isNavigable);
				const collapseIcon = shouldShowCollapsed ? '▶' : '▼';
				const collapsedClass = shouldShowCollapsed ? ' ghcm-toc-item-collapsed' : '';

				return `
					<div class="ghcm-toc-item${collapsedClass}" style="padding-left: ${indent}px;" data-level="${header.level}" data-header-id="${header.id}" tabindex="0">
						<span class="ghcm-toc-collapse-icon">${collapseIcon}</span>
						<a href="#${header.id}" class="ghcm-toc-link" data-header-id="${header.id}">
							${header.text}
						</a>
					</div>
				`;
			}).join('');
		}

		getHeaderId(element) {
			// 尝试获取已有的ID
			const anchor = element.querySelector('.anchor');
			if (anchor) return anchor.getAttribute('href')?.slice(1) || '';

			const id = element.id || element.getAttribute('id');
			if (id) return id;

			// 无现成ID,则赋予一个稳定、唯一的ID
			const newId = `ghcm-h-${++this._idSeed}`;
			try { element.setAttribute('id', newId); } catch {}
			return newId;
		}

		getElementPosition(element) {
			let position = 0;
			let current = element;
			while (current && current.parentNode) {
				const siblings = Array.from(current.parentNode.children);
				position += siblings.indexOf(current);
				current = current.parentNode;
			}
			return position;
		}

		setupTocEvents(toc) {
			// 关闭按钮
			toc.querySelector('.ghcm-toc-close').addEventListener('click', () => {
				this.hideToc();
			});

			// 整行可点击:事件委托在容器上处理
			toc.addEventListener('click', (e) => {
				const item = e.target.closest('.ghcm-toc-item');
				if (!item) return;
				e.preventDefault();
				const headerId = item.getAttribute('data-header-id') || item.querySelector('.ghcm-toc-link')?.getAttribute('data-header-id');
				if (headerId) this.scrollToHeader(headerId);
			});

			// 键盘回车/空格激活整行
			toc.addEventListener('keydown', (e) => {
				if (e.key !== 'Enter' && e.key !== ' ') return;
				const item = e.target.closest('.ghcm-toc-item');
				if (!item) return;
				e.preventDefault();
				const headerId = item.getAttribute('data-header-id') || item.querySelector('.ghcm-toc-link')?.getAttribute('data-header-id');
				if (headerId) this.scrollToHeader(headerId);
			});
		}

		scrollToHeader(headerId) {
			const element = document.getElementById(headerId) ||
							document.querySelector(`[id="${headerId}"]`) ||
							document.querySelector(`#user-content-${headerId}`);

			if (element) {
				// 如果标题被折叠,自动展开其父级
				this.expandParentHeaders(element);
				// 使用统一滚动函数,避免重复滚动与抖动
				requestAnimationFrame(() => {
					this.collapseManager.scrollToElement(element);
					this.collapseManager.setActiveHeading(element);
				});
				// 更新目录显示状态
				setTimeout(() => {
					this.refreshTocStates();
				}, 300);
			}
		}

		// 刷新目录中的折叠状态显示
		refreshTocStates() {
			if (!this.tocContainer) return;

			const tocItems = this.tocContainer.querySelectorAll('.ghcm-toc-item');
			tocItems.forEach(item => {
				const link = item.querySelector('.ghcm-toc-link');
				const icon = item.querySelector('.ghcm-toc-collapse-icon');
				if (!link || !icon) return;

				const headerId = link.getAttribute('data-header-id');
				if (!headerId) return;

				const safeId = (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') ? CSS.escape(headerId) : headerId;
				let headerElement = document.getElementById(headerId);
				if (!headerElement) {
					try { headerElement = document.querySelector(`[id="${headerId}"]`); } catch {}
				}
				if (!headerElement && safeId) {
					try { headerElement = document.querySelector(`#${safeId}`); } catch {}
				}
				if (!headerElement && safeId) {
					try { headerElement = document.querySelector(`#user-content-${safeId}`); } catch {}
				}
				if (!headerElement) {
					try { headerElement = document.querySelector(`#user-content-${headerId}`); } catch {}
				}

				if (!headerElement) {
					icon.textContent = '▼';
					item.classList.remove('ghcm-toc-item-collapsed');
					return;
				}

				const isCollapsed = headerElement.classList.contains(CONFIG.classes.collapsed);
				let isNavigable = true;
				try {
					if (this.collapseManager && typeof this.collapseManager.isHeaderNavigable === 'function') {
						isNavigable = this.collapseManager.isHeaderNavigable(headerElement);
					}
				} catch {}

				const shouldShowCollapsed = Boolean(isCollapsed || !isNavigable);
				icon.textContent = shouldShowCollapsed ? '▶' : '▼';
				item.classList.toggle('ghcm-toc-item-collapsed', shouldShowCollapsed);
			});
		}

		expandParentHeaders(targetElement) {
			// 找到对应的collapseManager实例并展开到该标题
		if (window.ghcmInstance && window.ghcmInstance.collapseManager) {
			window.ghcmInstance.collapseManager.expandToHeader(targetElement, { scroll: false, setActive: false });
		}
		}

		showToc() {
			if (this.tocContainer) {
				this.tocContainer.remove();
			}

			this.tocContainer = this.generateToc();
			if (this.tocContainer) {
				// 打开目录前关闭其他浮层
				try { this.collapseManager?.menuManager?.hide(); } catch {}
				try { this.collapseManager?.searchManager?.hideSearch(); } catch {}
				document.body.appendChild(this.tocContainer);
				this.isVisible = true;

				// ESC 关闭
				this._keyHandler = (e) => {
					if (e.key === 'Escape') this.hideToc();
				};
				document.addEventListener('keydown', this._keyHandler);

				// 初始焦点
				try { this.tocContainer.setAttribute('tabindex', '-1'); this.tocContainer.focus(); } catch {}

				// 确保状态正确显示
				setTimeout(() => {
					this.refreshTocStates();
				}, 100);

				// 启动滚动监听(Scroll Spy)
				this.startScrollSpy();
			}
		}

		hideToc() {
			if (this.tocContainer) {
				this.tocContainer.remove();
				this.tocContainer = null;
				this.isVisible = false;
				if (this._keyHandler) {
					document.removeEventListener('keydown', this._keyHandler);
					this._keyHandler = null;
				}
				this.stopScrollSpy();
			}
		}

		toggle() {
			if (this.isVisible) {
				this.hideToc();
			} else {
				this.showToc();
			}
		}

		// ========= Scroll Spy =========
		startScrollSpy() {
			if (this._scrollHandler) return;
			this._scrollHandler = () => {
				if (this._raf) return;
				this._raf = requestAnimationFrame(() => {
					this._raf = null;
					this.updateActiveFromScroll();
				});
			};
			window.addEventListener('scroll', this._scrollHandler, { passive: true });
			window.addEventListener('resize', this._scrollHandler, { passive: true });
			// 初次计算
			this.updateActiveFromScroll();
		}

		stopScrollSpy() {
			if (!this._scrollHandler) return;
			window.removeEventListener('scroll', this._scrollHandler);
			window.removeEventListener('resize', this._scrollHandler);
			this._scrollHandler = null;
			if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; }
		}

		updateActiveFromScroll() {
			try {
				const headers = this.getAllHeaders();
				if (!headers.length || !this.tocContainer) return;
				const headerEl = document.querySelector('header[role="banner"], .Header, .AppHeader-globalBar');
				const headerOffset = (headerEl?.offsetHeight || 80) + 20;
				const pos = window.scrollY + headerOffset + 1;
				let active = null;
				let firstNavigable = null;

				for (const h of headers) {
					const element = h.element;
					if (!element) continue;

					let isNavigable = true;
					try {
						if (this.collapseManager && typeof this.collapseManager.isHeaderNavigable === 'function') {
							isNavigable = this.collapseManager.isHeaderNavigable(element);
						}
					} catch {}

					if (isNavigable && !firstNavigable) {
						firstNavigable = h;
					}

					if (!isNavigable) {
						continue;
					}

					const rect = element.getBoundingClientRect();
					const top = rect.top + window.pageYOffset;
					if (top <= pos) {
						active = h;
					} else {
						break;
					}
				}

				if (!active) {
					active = firstNavigable;
				}

				if (active) {
					if (active.id) {
						this.highlightTocById(active.id);
					}
					this.collapseManager?.setActiveHeading(active.element);
				}
			} catch {}
		}

		highlightTocById(id) {
			if (!this.tocContainer) return;
			this.tocContainer.querySelectorAll('.ghcm-toc-item').forEach(el => el.classList.remove('active'));
			const link = this.tocContainer.querySelector(`.ghcm-toc-link[data-header-id="${CSS.escape(id)}"]`);
			if (link) {
				const item = link.closest('.ghcm-toc-item');
				if (item) item.classList.add('active');
			}
		}
	}

	// 搜索功能
	class SearchManager {
		constructor(collapseManager) {
			this.collapseManager = collapseManager;
			this.searchContainer = null;
			this.isVisible = false;
			this.activeIndex = -1;
			this._keyHandler = null;
			this._headerIndex = [];
			this._indexDirty = true;
			this._indexSeed = 0;
			this._idMap = new WeakMap();
		}

		invalidateIndex() {
			if (this._headerIndex.length) {
				try {
					this._headerIndex.forEach(item => {
						item?.element?.removeAttribute?.('data-search-id');
					});
				} catch {}
			}
			this._headerIndex = [];
			this._indexDirty = true;
			this._idMap = new WeakMap();
		}

		ensureIndex() {
			if (!this._indexDirty && this._headerIndex.length) {
				return this._headerIndex;
			}

			const elements = DOMUtils.collectHeadings();
			const index = elements.map(el => {
				const existingId = this._idMap.get(el);
				const id = existingId || `search-header-${++this._indexSeed}`;
				this._idMap.set(el, id);
				try { el.setAttribute('data-search-id', id); } catch {}
				return {
					element: el,
					level: DOMUtils.getHeadingLevel(el),
					text: el.textContent.trim(),
					id
				};
			});

			this._headerIndex = index;
			this._indexDirty = false;
			return this._headerIndex;
		}

		createSearchUI() {
			const container = document.createElement('div');
			container.className = CONFIG.classes.searchContainer;
			container.setAttribute('role', 'dialog');
			container.setAttribute('aria-modal', 'true');
			container.setAttribute('aria-label', '搜索标题');
			container.innerHTML = `
				<div class="ghcm-search-header">
					<h3 id="ghcm-search-title">🔍 搜索标题</h3>
					<button class="ghcm-search-close" title="关闭搜索" aria-label="关闭搜索">✕</button>
				</div>
				<div class="ghcm-search-content">
					<input type="text" class="ghcm-search-input" placeholder="输入关键词搜索标题..." autocomplete="off">
					<div class="ghcm-search-filters">
						<div class="ghcm-level-filters" aria-label="过滤级别">
							<label><input type="checkbox" data-level="1" checked> H1</label>
							<label><input type="checkbox" data-level="2" checked> H2</label>
							<label><input type="checkbox" data-level="3" checked> H3</label>
							<label><input type="checkbox" data-level="4" checked> H4</label>
							<label><input type="checkbox" data-level="5" checked> H5</label>
							<label><input type="checkbox" data-level="6" checked> H6</label>
						</div>
						<div class="ghcm-search-hint-row">Enter 跳转,Shift+Enter 上一个</div>
					</div>
					<div class="ghcm-search-results"></div>
				</div>
			`;

			this.setupSearchEvents(container);
			return container;
		}

		setupSearchEvents(container) {
			const input = container.querySelector('.ghcm-search-input');
			const results = container.querySelector('.ghcm-search-results');
			const closeBtn = container.querySelector('.ghcm-search-close');
            const levelBox = container.querySelector('.ghcm-level-filters');

            // 级别过滤默认全部启用
            this.levelFilter = new Set([1,2,3,4,5,6]);

			// 实时搜索
			let searchTimeout;
			input.addEventListener('input', () => {
				clearTimeout(searchTimeout);
				searchTimeout = setTimeout(() => {
					this.performSearch(input.value.trim(), results);
				}, 300);
			});

			// 级别过滤变更
			levelBox.addEventListener('change', (e) => {
				const cb = e.target.closest('input[type="checkbox"][data-level]');
				if (!cb) return;
				const lvl = parseInt(cb.getAttribute('data-level'), 10);
				if (cb.checked) this.levelFilter.add(lvl); else this.levelFilter.delete(lvl);
				this.performSearch(input.value.trim(), results);
			});

			// 关闭搜索
			closeBtn.addEventListener('click', () => {
				this.hideSearch();
			});

			// 搜索结果点击委托
			results.addEventListener('click', (event) => {
				const item = event.target.closest('.ghcm-search-result');
				if (!item) return;
				const headerId = item.getAttribute('data-header-element');
				if (headerId) {
					this.jumpToHeader(headerId);
				}
			});

			results.addEventListener('focusin', (event) => {
				const item = event.target.closest('.ghcm-search-result');
				if (!item) return;
				const items = Array.from(results.querySelectorAll('.ghcm-search-result'));
				const idx = items.indexOf(item);
				if (idx !== -1) {
					this.activeIndex = idx;
					this.updateActiveResult(items);
				}
			});

			// 键盘导航与 ESC 关闭
			this._keyHandler = (e) => {
				if (e.key === 'Escape') {
					this.hideSearch();
					return;
				}
				if (e.key === 'Tab') {
					const focusables = this.getSearchFocusables(container);
					const current = document.activeElement;
					const idx = focusables.indexOf(current);
					if (idx !== -1 && focusables.length > 0) {
						e.preventDefault();
						const nextIndex = (idx + (e.shiftKey ? -1 : 1) + focusables.length) % focusables.length;
						const target = focusables[nextIndex];
						target?.focus();
						if (target?.classList?.contains('ghcm-search-result')) {
							const items = Array.from(results.querySelectorAll('.ghcm-search-result'));
							const focusIdx = items.indexOf(target);
							if (focusIdx !== -1) {
								this.activeIndex = focusIdx;
								this.updateActiveResult(items);
							}
						}
					}
					return;
				}
				const items = Array.from(results.querySelectorAll('.ghcm-search-result'));
				if (items.length === 0) return;
				if (e.key === 'ArrowDown') {
					e.preventDefault();
					this.activeIndex = (this.activeIndex + 1) % items.length;
					this.updateActiveResult(items);
				} else if (e.key === 'ArrowUp') {
					e.preventDefault();
					this.activeIndex = (this.activeIndex - 1 + items.length) % items.length;
					this.updateActiveResult(items);
				} else if (e.key === 'Enter') {
					if (this.activeIndex >= 0 && this.activeIndex < items.length) {
						if (e.shiftKey) {
							// Shift+Enter 上一个
							this.activeIndex = (this.activeIndex - 1 + items.length) % items.length;
							this.updateActiveResult(items);
						} else {
							items[this.activeIndex].click();
						}
					}
				}
			};
			container.addEventListener('keydown', this._keyHandler);

			// 自动聚焦
			setTimeout(() => input.focus(), 100);
		}

		updateActiveResult(items) {
			items.forEach((el, i) => el.classList.toggle('active', i === this.activeIndex));
			if (this.activeIndex >= 0 && items[this.activeIndex]) {
				items[this.activeIndex].scrollIntoView({ block: 'nearest' });
			}
		}

		getSearchFocusables(container) {
			const focusables = [];
			const input = container.querySelector('.ghcm-search-input');
			if (input) focusables.push(input);
			focusables.push(...Array.from(container.querySelectorAll('.ghcm-level-filters input[type="checkbox"]')));
			focusables.push(...Array.from(container.querySelectorAll('.ghcm-search-result')));
			const closeBtn = container.querySelector('.ghcm-search-close');
			if (closeBtn) focusables.push(closeBtn);
			return focusables;
		}

		performSearch(query, resultsContainer) {
			if (!query) {
				resultsContainer.innerHTML = '<div class="ghcm-search-hint">请输入搜索关键词</div>';
				this.activeIndex = -1;
				return;
			}

			const headers = this.getAllSearchableHeaders();
			// 级别过滤
			const filtered = headers.filter(h => this.levelFilter?.has(h.level));
			// 模糊匹配 + 打分
			const q = query.trim();
			const matches = [];
			for (const h of filtered) {
				const res = this.fuzzyMatch(h.text, q);
				if (res.matched) {
					matches.push({ h, score: res.score, indices: res.indices });
				}
			}
			matches.sort((a,b) => b.score - a.score);

			if (matches.length === 0) {
				resultsContainer.innerHTML = '<div class="ghcm-search-no-results">未找到匹配的标题</div>';
				this.activeIndex = -1;
				return;
			}

			const resultHtml = matches.map(({h, indices}) => `
				<div class="ghcm-search-result" data-header-element="${h.id}" tabindex="0">
					<span class="ghcm-search-level">H${h.level}</span>
					<span class="ghcm-search-text">${this.safeHighlightByIndices(h.text, indices)}</span>
				</div>
			`).join('');

			resultsContainer.innerHTML = resultHtml;
			const items = Array.from(resultsContainer.querySelectorAll('.ghcm-search-result'));
			if (items.length) {
				this.activeIndex = 0;
				this.updateActiveResult(items);
			} else {
				this.activeIndex = -1;
			}
		}

		// 简单模糊匹配:
		// 1) 连续子串匹配给高分;2) 按字符顺序匹配有惩罚;3) 记录命中索引用于高亮
		fuzzyMatch(text, query) {
			const t = (text || '').toLowerCase();
			const q = (query || '').toLowerCase();
			if (!q) return { matched: true, score: 0, indices: [] };
			const i = t.indexOf(q);
			if (i !== -1) {
				const indices = Array.from({length: q.length}, (_,k)=> i+k);
				const score = 1000 - i; // 越靠前越高分
				return { matched: true, score, indices };
			}
			// 顺序子序列匹配
			let ti = 0; const indices = [];
			for (let qi = 0; qi < q.length; qi++) {
				const ch = q[qi];
				ti = t.indexOf(ch, ti);
				if (ti === -1) return { matched: false, score: -Infinity, indices: [] };
				indices.push(ti);
				ti++;
			}
			// 评分:越连续、跨度越小得分越高
			let gaps = 0; for (let k=1;k<indices.length;k++){ gaps += (indices[k]-indices[k-1]-1); }
			const span = indices[indices.length-1] - indices[0] + 1;
			const score = 500 - gaps*5 - span;
			return { matched: true, score, indices };
		}

		safeHighlightByIndices(text, indices) {
			try {
				if (!indices || !indices.length) return this.safeHighlightMatch(text, '');
				let out = '';
				let last = 0;
				const set = new Set(indices);
				for (let i=0;i<text.length;i++) {
					if (set.has(i)) {
						// 开始标记连续段
						let j = i;
						while (set.has(j)) j++;
						out += this.escapeHtml(text.slice(last, i)) + '<mark>' + this.escapeHtml(text.slice(i, j)) + '</mark>';
						last = j; i = j-1;
					}
				}
				out += this.escapeHtml(text.slice(last));
				return out;
			} catch { return this.escapeHtml(String(text||'')); }
		}

		escapeHtml(s){
			return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;','\'':'&#39;'}[c]));
		}

		getAllSearchableHeaders() {
			return this.ensureIndex();
		}

		highlightMatch(text, query) {
			const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
			const regex = new RegExp(`(${escaped})`, 'gi');
			return text.replace(regex, '<mark>$1</mark>');
		}

		safeHighlightMatch(text, query) {
			try {
				const escaped = String(query).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
				const regex = new RegExp(`(${escaped})`, 'gi');
				return String(text).replace(regex, '<mark>$1</mark>');
			} catch (e) {
				return text;
			}
		}

		jumpToHeader(headerId) {
			const element = document.querySelector(`[data-search-id="${headerId}"]`);
			if (element) {
				// 展开到该标题
				this.collapseManager.expandToHeader(element, { scroll: false, setActive: false });
				this.collapseManager.scrollToElement(element);
				this.collapseManager.setActiveHeading(element);
				// 隐藏搜索界面
				this.hideSearch();
			}
		}

		showSearch() {
			if (this.searchContainer) {
				this.searchContainer.remove();
			}

			// 打开搜索前关闭其他浮层
			try { this.collapseManager?.menuManager?.hide(); } catch {}
			try { this.collapseManager?.tocGenerator?.hideToc(); } catch {}

			this.searchContainer = this.createSearchUI();
			document.body.appendChild(this.searchContainer);
			this.isVisible = true;
		}

		hideSearch() {
			if (this.searchContainer) {
				this.searchContainer.remove();
				this.searchContainer = null;
				this.isVisible = false;
				// 清理键盘事件
				if (this._keyHandler) {
					// 绑定在容器上,容器已移除即可
					this._keyHandler = null;
				}
				this.activeIndex = -1;
			}
		}

		toggle() {
			if (this.isVisible) {
				this.hideSearch();
			} else {
				this.showSearch();
			}
		}
	}

	class BookmarkManager {
		constructor(app) {
			this.app = app;
			this.storageKey = CONFIG.bookmarks.key;
			this.bookmarksByPage = this.normalizeStoredBookmarks(GM_getValue(this.storageKey, null));
			this._applyRetryTimer = null;
			this._applyRetryAttempts = 0;
			this._applyRetryMax = 6;
			this._applyRetryDelay = 300;
			this.ensurePageEntry();
			setTimeout(() => this.applyBookmarks(), 200);
		}

		getPageKey() {
			return this.app.stateManager.pageUrl;
		}

		normalizeStoredBookmarks(raw) {
			if (!raw) return {};
			let data = raw;
			if (typeof raw === 'string') {
				try {
					data = JSON.parse(raw);
				} catch {
					return {};
				}
			}
			if (!data || typeof data !== 'object') return {};
			const normalized = {};
			Object.entries(data).forEach(([pageKey, entries]) => {
				if (!Array.isArray(entries)) return;
				const cleaned = entries
					.map(entry => this.cloneBookmark(entry))
					.filter(Boolean);
				if (cleaned.length) {
					normalized[pageKey] = cleaned;
				}
			});
			return normalized;
		}

		sanitizeBookmark(entry) {
			if (!entry || typeof entry !== 'object') return null;
			const key = typeof entry.key === 'string' ? entry.key.trim() : '';
			if (!key) return null;
			const sanitized = { key };
			if (typeof entry.id === 'string' && entry.id.trim()) {
				sanitized.id = entry.id.trim();
			}
			if (typeof entry.text === 'string') {
				sanitized.text = entry.text;
			}
			if (typeof entry.level === 'number' && entry.level >= 1 && entry.level <= 6) {
				sanitized.level = entry.level;
			}
			return sanitized;
		}

		cloneBookmark(entry) {
			const sanitized = this.sanitizeBookmark(entry);
			return sanitized ? { ...sanitized } : null;
		}

		serializeBookmarks() {
			const snapshot = {};
			Object.entries(this.bookmarksByPage).forEach(([pageKey, entries]) => {
				if (!Array.isArray(entries) || entries.length === 0) return;
				const cleaned = entries
					.map(entry => this.sanitizeBookmark(entry))
					.filter(Boolean);
				if (cleaned.length) {
					snapshot[pageKey] = cleaned;
				}
			});
			return JSON.stringify(snapshot);
		}

		ensurePageEntry() {
			const key = this.getPageKey();
			if (!Array.isArray(this.bookmarksByPage[key])) {
				this.bookmarksByPage[key] = [];
			}
			this.bookmarksByPage[key] = this.bookmarksByPage[key]
				.map(entry => this.cloneBookmark(entry))
				.filter(Boolean);
			return this.bookmarksByPage[key];
		}

		getBookmarksForCurrentPage() {
			return this.ensurePageEntry().map(entry => this.cloneBookmark(entry)).filter(Boolean);
		}

		save() {
			try {
				GM_setValue(this.storageKey, this.serializeBookmarks());
			} catch (e) {
				Logger.warn('[GHCM] 保存书签失败:', e);
			}
		}

		getHeaderElement(node) {
			if (!node) return null;
			if (DOMUtils.isHeader(node)) return node;
			let header = null;
			try {
				if (typeof node.closest === 'function') {
					header = node.closest(DOMUtils.getUpperHeadingSelector());
				}
			} catch {}
			if (header && DOMUtils.isHeader(header)) return header;
			try {
				const wrapper = node.closest?.('.markdown-heading');
				if (wrapper) {
					header = wrapper.querySelector(DOMUtils.getUpperHeadingSelector());
				}
			} catch {}
			return (header && DOMUtils.isHeader(header)) ? header : null;
		}

		ensureHeaderId(element) {
			const header = this.getHeaderElement(element) || element;
			if (!header) return null;
			const normalize = value => (typeof value === 'string' ? value.trim() : '');

			const directId = normalize(header.getAttribute?.('id') || header.id);
			if (directId && !/^ghcm-(?:bookmark|h)-/i.test(directId)) {
				return directId;
			}

			try {
				if (this.app.tocGenerator && typeof this.app.tocGenerator.getHeaderId === 'function') {
					const tocId = normalize(this.app.tocGenerator.getHeaderId(header));
					if (tocId) return tocId;
				}
			} catch {}

			let anchorId = '';
			let hrefId = '';
			try {
				const anchor = header.querySelector?.('.anchor');
				if (anchor) {
					anchorId = normalize(anchor.getAttribute('id'));
					hrefId = normalize(anchor.getAttribute('href')?.replace(/^#/, ''));
				}
			} catch {}
			if (anchorId) return anchorId;
			if (hrefId) return hrefId;

			let childId = '';
			try {
				const anyWithId = header.querySelector?.('[id]');
				childId = normalize(anyWithId?.getAttribute('id'));
			} catch {}
			if (childId) return childId;

			const generated = `ghcm-bookmark-${Date.now()}-${Math.random().toString(36).slice(2)}`;
			try { header.setAttribute('id', generated); } catch {}
			return generated;
		}

		addBookmarkForElement(element, { notify = true } = {}) {
			const header = this.getHeaderElement(element) || element;
			if (!header || !DOMUtils.isHeader(header)) return;
			const pageBookmarks = this.ensurePageEntry();
			const key = this.app.stateManager.generateHeaderKey(header);
			const existingIndex = pageBookmarks.findIndex(item => item.key === key);
			if (existingIndex !== -1) {
				if (notify) this.app.collapseManager.showNotification('⭐ 该标题已在书签中');
				return;
			}
			const id = this.ensureHeaderId(header);
			this.app.collapseManager.setActiveHeading(header);
			const entry = {
				id,
				key,
				text: header.textContent?.trim() || '未命名标题',
				level: DOMUtils.getHeadingLevel(header)
			};
			pageBookmarks.push(entry);
			this.save();
			this.applyBookmarks();
			if (notify) this.app.collapseManager.showNotification('⭐ 已收藏当前标题');
			this.app.menuManager?.updateBookmarkList();
		}

			toggleBookmarkForElement(element) {
				const header = this.getHeaderElement(element) || element;
				if (!header || !DOMUtils.isHeader(header)) return;
				this.app.collapseManager.setActiveHeading(header);
				const pageBookmarks = this.ensurePageEntry();
				const key = this.app.stateManager.generateHeaderKey(header);
			const existingIndex = pageBookmarks.findIndex(item => item.key === key);
			if (existingIndex !== -1) {
				pageBookmarks.splice(existingIndex, 1);
				this.save();
				this.applyBookmarks();
				this.app.collapseManager.showNotification('🗑️ 已移除书签');
				this.app.menuManager?.updateBookmarkList();
				return;
			}
			this.addBookmarkForElement(header);
		}

			toggleBookmarkForActiveHeader() {
				let header = DOMUtils.getHeaderFromSelection();
				if (!header) {
					header = this.app.collapseManager.getActiveHeaderElement();
				}
				if (!header) {
					header = this.app.collapseManager.getActiveHeaderElement(true);
				}
				if (!header) {
					this.app.collapseManager.showNotification('⚠️ 未找到可收藏的标题');
					return;
				}
				this.app.collapseManager.setActiveHeading(header);
				this.toggleBookmarkForElement(header);
			}

			addBookmarkFromViewport() {
				let header = DOMUtils.getHeaderFromSelection();
				if (!header) {
					header = this.app.collapseManager.getActiveHeaderElement();
				}
				if (!header) {
					header = this.app.collapseManager.getActiveHeaderElement(true);
				}
				if (!header) {
					this.app.collapseManager.showNotification('⚠️ 当前视图未找到标题');
					return;
			}
			this.app.collapseManager.setActiveHeading(header);
			this.addBookmarkForElement(header);
		}

		removeBookmarkByIndex(index) {
			const pageBookmarks = this.ensurePageEntry();
			if (index < 0 || index >= pageBookmarks.length) return;
			pageBookmarks.splice(index, 1);
			this.save();
			this.applyBookmarks();
			this.app.collapseManager.showNotification('🗑️ 已移除书签');
			this.app.menuManager?.updateBookmarkList();
		}

		clearPageBookmarks() {
			const key = this.getPageKey();
			this.bookmarksByPage[key] = [];
			this.save();
			this.applyBookmarks();
			this.app.collapseManager.showNotification('🗂️ 已清空本页书签');
			this.app.menuManager?.updateBookmarkList();
		}

			openBookmarkByIndex(index) {
				const pageBookmarks = this.ensurePageEntry();
				if (index < 0 || index >= pageBookmarks.length) return;
				const bookmark = pageBookmarks[index];
				const element = this.resolveBookmarkElement(bookmark);
			if (element) {
				this.app.collapseManager.expandToHeader(element, { scroll: false, setActive: false });
				this.app.collapseManager.scrollToElement(element);
				this.app.collapseManager.setActiveHeading(element);
				this.highlightTemporarily(element);
			}
		}

		resolveBookmarkElement(bookmark) {
			if (!bookmark) return null;
			const candidates = [];
			if (typeof bookmark.id === 'string' && bookmark.id.trim()) {
				const trimmed = bookmark.id.trim();
				candidates.push(trimmed);
				if (!trimmed.startsWith('user-content-')) {
					candidates.push(`user-content-${trimmed}`);
				}
			}

			let element = null;
			for (const candidate of candidates) {
				if (!candidate) continue;
				let found = null;
				try { found = document.getElementById(candidate); } catch {}
				if (!found) continue;
				const header = this.getHeaderElement(found);
				if (header) {
					element = header;
					break;
				}
			}

			if (!element) {
				const headers = this.app.collapseManager.getAllHeaders();
				for (const header of headers) {
					const key = this.app.stateManager.generateHeaderKey(header);
					if (key === bookmark.key) {
						element = header;
						break;
					}
				}
			}

			if (!element) return null;

			const newId = this.ensureHeaderId(element);
			if (newId && newId !== bookmark.id) {
				bookmark.id = newId;
				this.save();
			}
			return element;
		}

		applyBookmarks({ attempt = 0 } = {}) {
			const headers = this.app.collapseManager.getAllHeaders();
			headers.forEach(header => header.classList.remove(CONFIG.classes.bookmarked));
			const pageKey = this.getPageKey();
			const pageBookmarks = this.ensurePageEntry();
			let unresolved = 0;
			pageBookmarks.forEach(bookmark => {
				const element = this.resolveBookmarkElement(bookmark);
				if (element) {
					element.classList.add(CONFIG.classes.bookmarked);
				} else {
					unresolved++;
				}
			});

			if (unresolved > 0 && attempt < this._applyRetryMax) {
				if (this._applyRetryTimer) clearTimeout(this._applyRetryTimer);
				const nextAttempt = attempt + 1;
				this._applyRetryAttempts = nextAttempt;
				this._applyRetryTimer = setTimeout(() => {
					this._applyRetryTimer = null;
					this.applyBookmarks({ attempt: nextAttempt });
				}, this._applyRetryDelay);
			} else if (unresolved === 0) {
				this._applyRetryAttempts = 0;
				if (this._applyRetryTimer) {
					clearTimeout(this._applyRetryTimer);
					this._applyRetryTimer = null;
				}
			} else if (unresolved > 0) {
				this._applyRetryAttempts = 0;
				if (this._applyRetryTimer) {
					clearTimeout(this._applyRetryTimer);
					this._applyRetryTimer = null;
				}
			}

			this.app.menuManager?.updateBookmarkList();
		}

			highlightTemporarily(element) {
			if (!element) return;
			try {
				element.classList.add('ghcm-temp-highlight');
				setTimeout(() => element.classList.remove('ghcm-temp-highlight'), 600);
			} catch {}
		}
	}

	// DOM 工具类
	class DOMUtils {
		static getHeadingTagsLower() {
			if (!DOMUtils._headingTagsLower) {
				DOMUtils._headingTagsLower = CONFIG.selectors.headers.map(tag => tag.toLowerCase());
			}
			return DOMUtils._headingTagsLower;
		}

		static getUpperHeadingSelector() {
			if (!DOMUtils._upperHeadingSelector) {
				DOMUtils._upperHeadingSelector = CONFIG.selectors.headers.join(',');
			}
			return DOMUtils._upperHeadingSelector;
		}

		static getHeadingTags({ level, upToLevel } = {}) {
			const tags = DOMUtils.getHeadingTagsLower();
			if (typeof level === 'number') {
				const tag = tags[level - 1];
				return tag ? [tag] : [];
			}
			if (typeof upToLevel === 'number') {
				return tags.slice(0, upToLevel);
			}
			return tags;
		}

		static getCachedSelector(key, builder) {
			if (!DOMUtils._selectorCache) {
				DOMUtils._selectorCache = new Map();
			}
			if (!DOMUtils._selectorCache.has(key)) {
				DOMUtils._selectorCache.set(key, builder());
			}
			return DOMUtils._selectorCache.get(key);
		}

		static buildSelector(tags, { scopedTo, includeWrapper } = {}) {
			if (!tags || !tags.length) return '';
			const selectors = [];
			tags.forEach(tag => {
				const base = scopedTo ? `${scopedTo} ${tag}` : tag;
				selectors.push(base);
				if (includeWrapper) {
					selectors.push(`${base}.heading-element`);
				}
			});
			return selectors.join(', ');
		}

		static getHeadingSelector() {
			return DOMUtils.getCachedSelector('all-headings', () =>
				DOMUtils.buildSelector(DOMUtils.getHeadingTags())
			);
		}

		static getHeadingSelectorUpToLevel(level) {
			return DOMUtils.getCachedSelector(`upto-${level}`, () =>
				DOMUtils.buildSelector(DOMUtils.getHeadingTags({ upToLevel: level }))
			);
		}

		static getScopedHeadingSelector(container, { includeWrapper = false, level, upToLevel } = {}) {
			if (!container) return '';
			const key = `scope-${container}|wrap:${includeWrapper}|level:${level ?? 'all'}|upto:${upToLevel ?? 'na'}`;
			return DOMUtils.getCachedSelector(key, () =>
				DOMUtils.buildSelector(
					DOMUtils.getHeadingTags({ level, upToLevel }),
					{ scopedTo: container, includeWrapper }
				)
			);
		}

		static collectHeadings(containers = CONFIG.selectors.markdownContainers) {
			const useCache = containers === CONFIG.selectors.markdownContainers;
			if (useCache && DOMUtils._headingCache) {
				return DOMUtils._headingCache.slice();
			}

			const selectors = containers
				.map(container => DOMUtils.getScopedHeadingSelector(container))
				.filter(Boolean);
			if (!selectors.length) return [];
			try {
				const list = DOMUtils.$$(selectors.join(', '))
					.filter(element => DOMUtils.shouldIncludeHeading(element));
				if (useCache) {
					DOMUtils._headingCache = list;
					return list.slice();
				}
				return list;
			} catch {
				return [];
			}
		}

		static hasMarkdownHeadings() {
			return CONFIG.selectors.markdownContainers.some(container => {
				try {
					const selector = DOMUtils.getScopedHeadingSelector(container);
					return selector ? !!document.querySelector(selector) : false;
				} catch {
					return false;
				}
			});
		}

		static getHeadingLevel(element) {
			if (!element || !element.nodeName) return 0;
			const match = element.nodeName.match(/h([1-6])/i);
			return match ? parseInt(match[1], 10) : 0;
		}

		static $(selector, parent = document) {
			return parent.querySelector(selector);
		}

		static $$(selector, parent = document) {
			return Array.from(parent.querySelectorAll(selector));
		}

		static isHeader(element) {
			return CONFIG.selectors.headers.includes(element.nodeName);
		}

		static isInMarkdown(element) {
			return CONFIG.selectors.markdownContainers.some(selector =>
				element.closest(selector)
			);
		}

		static getHeaderContainer(header) {
			return header.closest('.markdown-heading') || header;
		}

		static clearSelection() {
			const selection = window.getSelection?.() || document.selection;
			if (selection) {
				if (selection.removeAllRanges) {
					selection.removeAllRanges();
				} else if (selection.empty) {
					selection.empty();
				}
			}
		}

		static blurActiveElement() {
			try {
				const active = document.activeElement;
				if (!active || active === document.body) return;
				if (typeof active.blur === 'function') {
					active.blur();
				}
			} catch {}
		}

		static getHeaderFromSelection() {
			try {
				const selection = window.getSelection?.();
				if (!selection || selection.rangeCount === 0) return null;
				const node = selection.focusNode || selection.anchorNode;
				if (!node) return null;
				const isElementNode = typeof Node !== 'undefined' && node.nodeType === Node.ELEMENT_NODE;
				const element = (isElementNode ? node : node.parentElement) || null;
				if (!element) return null;
				const direct = element.closest(DOMUtils.getUpperHeadingSelector());
				if (direct && DOMUtils.shouldIncludeHeading(direct)) {
					return direct;
				}
				const wrapper = element.closest('.markdown-heading');
				if (wrapper) {
					const header = wrapper.querySelector(DOMUtils.getUpperHeadingSelector());
					if (header && DOMUtils.shouldIncludeHeading(header)) {
						return header;
					}
				}
			} catch {}
			return null;
		}

		// 仅收录页面中可见且非辅助导航区域的标题
		static isVisible(el) {
			try {
				if (!el || el.getAttribute('aria-hidden') === 'true' || el.hidden) return false;
				// 常见 SR-only 类
				const cls = el.className || '';
				if (typeof cls === 'string' && /(sr-only|visually-hidden)/i.test(cls)) return false;
				// 计算可见性
				const rects = el.getClientRects?.();
				if (!rects || rects.length === 0) return false;
				return (el.offsetWidth + el.offsetHeight) > 0;
			} catch { return true; }
		}

		static inIgnoredRegion(el) {
			try {
				return !!el.closest('nav, header, footer, aside, [role="navigation"], [role="menu"], [role="menubar"], [role="toolbar"]');
			} catch { return false; }
		}

		static shouldIncludeHeading(el) {
			if (!DOMUtils.isHeader(el)) return false;
			if (!DOMUtils.isInMarkdown(el)) return false;
			if (DOMUtils.inIgnoredRegion(el)) return false;
			if (!DOMUtils.isVisible(el)) return false;
			return true;
		}

		static invalidateHeadingCache() {
			DOMUtils._headingCache = null;
		}
	}

	// 样式管理器
	class StyleManager {
		constructor() {
			this.arrowColors = document.createElement("style");
			this.arrowContentOverride = document.createElement("style");
			this.init();
		}

			init() {
				this.addBaseStyles();
				this.addColorStyles();
				document.head.appendChild(this.arrowColors);
				// 初始箭头内容覆盖(用于“仅显示箭头”开关)
				document.head.appendChild(this.arrowContentOverride);
				this.updateArrowContentOverride();
				this.applyArrowSize(CONFIG.ui.arrowSize);
			}

		addBaseStyles() {
			const headerSelectors = this.generateHeaderSelectors();

	GM_addStyle(`
				/* 基础样式 */
				${headerSelectors.base} {
			position: relative;
			padding-right: 3em;
			cursor: pointer;
					transition: all ${CONFIG.animation.duration}ms ${CONFIG.animation.easing};
				}

				/* 箭头指示器 */
				${headerSelectors.after} {
			display: inline-block;
				position: absolute;
			right: 0.5em;
						top: 50%;
						transform: translateY(-50%);
				font-size: var(--ghcm-arrow-size, 0.8em);
						font-weight: bold;
						pointer-events: none;
						transition: transform ${CONFIG.animation.duration}ms ${CONFIG.animation.easing};
				}

				/* 各级标题的箭头内容 */
				${this.generateArrowContent()}

					/* 折叠状态的箭头旋转 */
					.${CONFIG.classes.collapsed}:after {
						transform: translateY(-50%) rotate(-90deg);
					}

					/* 书签标记 */
					.${CONFIG.classes.bookmarked} {
						background: rgba(252, 211, 77, 0.35);
						border-radius: 4px;
					}

					.${CONFIG.classes.bookmarked}::before {
						content: none;
					}

					/* 当前激活标题 */
					.${CONFIG.classes.activeHeading} {
						background: rgba(191, 219, 254, 0.55);
						border-radius: 4px;
					}

					.${CONFIG.classes.bookmarked}.${CONFIG.classes.activeHeading} {
						background: rgba(224, 231, 255, 0.6);
					}

					.${CONFIG.classes.hoverHeading} {
						background: rgba(107, 114, 128, 0.12);
						border-radius: 4px;
					}

					.ghcm-temp-highlight {
						background: rgba(191, 219, 254, 0.4);
						transition: background 0.4s ease;
					}

					/* 隐藏元素 */
				.${CONFIG.classes.hidden},
				.${CONFIG.classes.hiddenByParent} {
					display: none !important;
					opacity: 0 !important;
				}

				/* 无内容标题 */
				.${CONFIG.classes.noContent}:after {
					display: none !important;
				}

				/* 保留 GitHub 标题锚点交互,不禁止点击 */

				/* 平滑动画 */
				.ghcm-transitioning {
					transition: opacity ${CONFIG.animation.duration}ms ${CONFIG.animation.easing},
					           transform ${CONFIG.animation.duration}ms ${CONFIG.animation.easing};
				}

				/* 目录容器样式 */
				.${CONFIG.classes.tocContainer} {
					position: fixed;
					top: 20px;
					right: 20px;
					width: 300px;
					max-height: 70vh;
					background: var(--color-canvas-default, #ffffff);
					border: 1px solid var(--color-border-default, #d0d7de);
					border-radius: 8px;
					box-shadow: 0 8px 24px rgba(0,0,0,0.12);
					z-index: 10000;
					overflow: hidden;
					font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
				}

				.ghcm-toc-header {
					padding: 8px 12px;
					background: var(--color-canvas-subtle, #f6f8fa);
					border-bottom: 1px solid var(--color-border-default, #d0d7de);
					display: flex;
					justify-content: space-between;
					align-items: center;
					min-height: 36px;
				}

				.ghcm-toc-header h3 {
					margin: 0;
					font-size: 13px;
					font-weight: 600;
					color: var(--color-fg-default, #24292f);
					line-height: 1.2;
				}

				.ghcm-toc-close {
					background: none;
					border: none;
					font-size: 14px;
					cursor: pointer;
					padding: 2px 4px;
					border-radius: 3px;
					color: var(--color-fg-muted, #656d76);
					line-height: 1;
				}

				.ghcm-toc-close:hover {
					background: var(--color-danger-subtle, #ffebe9);
					color: var(--color-danger-fg, #cf222e);
				}

				.ghcm-toc-content {
					max-height: calc(70vh - 44px);
					overflow-y: auto;
					padding: 6px 0;
				}

				.ghcm-toc-item {
					display: flex;
					align-items: center;
					padding: 4px 16px;
					border-radius: 4px;
					margin: 1px 8px;
					cursor: pointer;
				}

				.ghcm-toc-item.ghcm-toc-item-collapsed {
					opacity: 0.78;
				}

				.ghcm-toc-item.ghcm-toc-item-collapsed .ghcm-toc-link {
					color: var(--color-fg-muted, #656d76);
				}

				.ghcm-toc-item:hover {
					background: var(--color-neutral-subtle, #f6f8fa);
				}

				/* TOC 活动高亮 */
				.ghcm-toc-item.active {
					background: var(--color-accent-subtle, #ddf4ff);
				}
				.ghcm-toc-item.active .ghcm-toc-link {
					color: var(--color-accent-fg, #0969da);
					font-weight: 600;
				}

				.ghcm-toc-collapse-icon {
					font-size: 10px;
					margin-right: 8px;
					color: var(--color-fg-muted, #656d76);
					min-width: 12px;
				}

				.ghcm-toc-link {
					text-decoration: none;
					color: var(--color-fg-default, #24292f);
					font-size: 13px;
					line-height: 1.4;
					flex: 1;
				}

				.ghcm-toc-link:hover {
					color: var(--color-accent-fg, #0969da);
				}

				/* 搜索容器样式 */
				.${CONFIG.classes.searchContainer} {
					position: fixed;
					top: 50%;
					left: 50%;
					transform: translate(-50%, -50%);
					width: 480px;
					max-width: 90vw;
					max-height: 80vh;
					background: var(--color-canvas-default, #ffffff);
					border: 1px solid var(--color-border-default, #d0d7de);
					border-radius: 12px;
					box-shadow: 0 16px 32px rgba(0,0,0,0.24);
					z-index: 10001;
					overflow: hidden;
					font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
				}

				.ghcm-search-header {
					padding: 16px 20px;
					background: var(--color-canvas-subtle, #f6f8fa);
					border-bottom: 1px solid var(--color-border-default, #d0d7de);
					display: flex;
					justify-content: space-between;
					align-items: center;
				}

				.ghcm-search-header h3 {
					margin: 0;
					font-size: 16px;
					font-weight: 600;
					color: var(--color-fg-default, #24292f);
				}

				.ghcm-search-close {
					background: none;
					border: none;
					font-size: 18px;
					cursor: pointer;
					padding: 6px;
					border-radius: 6px;
					color: var(--color-fg-muted, #656d76);
				}

				.ghcm-search-close:hover {
					background: var(--color-danger-subtle, #ffebe9);
					color: var(--color-danger-fg, #cf222e);
				}

				.ghcm-search-content {
					padding: 20px;
				}

				.ghcm-search-input {
					width: 100%;
					padding: 12px 16px;
					border: 2px solid var(--color-border-default, #d0d7de);
					border-radius: 8px;
					font-size: 16px;
					background: var(--color-canvas-default, #ffffff);
					color: var(--color-fg-default, #24292f);
					outline: none;
					transition: border-color 0.2s;
				}

				.ghcm-search-input:focus {
					border-color: var(--color-accent-emphasis, #0969da);
				}

				.ghcm-search-results {
					margin-top: 16px;
					max-height: 400px;
					overflow-y: auto;
				}

				/* 搜索过滤栏 */
				.ghcm-search-filters {
					margin-top: 10px;
					display: flex;
					justify-content: space-between;
					align-items: center;
					flex-wrap: wrap;
					gap: 8px;
				}
				.ghcm-level-filters label {
					margin-right: 8px;
					font-size: 12px;
					color: var(--color-fg-muted, #656d76);
				}
				.ghcm-search-hint-row {
					font-size: 12px;
					color: var(--color-fg-muted, #656d76);
				}

				.ghcm-search-result {
					display: flex;
					align-items: center;
					padding: 12px 16px;
					border-radius: 8px;
					cursor: pointer;
					margin: 4px 0;
					border: 1px solid transparent;
				}

				.ghcm-search-result:hover {
					background: var(--color-neutral-subtle, #f6f8fa);
					border-color: var(--color-border-default, #d0d7de);
				}

				/* 键盘导航高亮 */
				.ghcm-search-result.active {
					background: var(--color-neutral-subtle, #f6f8fa);
					border-color: var(--color-border-default, #d0d7de);
				}

				.ghcm-search-level {
					background: var(--color-accent-subtle, #ddf4ff);
					color: var(--color-accent-fg, #0969da);
					padding: 2px 6px;
					border-radius: 4px;
					font-size: 11px;
					font-weight: 600;
					margin-right: 12px;
					min-width: 24px;
					text-align: center;
				}

				.ghcm-search-text {
					flex: 1;
					font-size: 14px;
					color: var(--color-fg-default, #24292f);
				}

				.ghcm-search-text mark {
					background: var(--color-attention-subtle, #fff8c5);
					color: var(--color-attention-fg, #9a6700);
					padding: 1px 2px;
					border-radius: 2px;
				}

				.ghcm-search-hint, .ghcm-search-no-results {
					text-align: center;
					padding: 40px 20px;
					color: var(--color-fg-muted, #656d76);
					font-style: italic;
				}

				/* 帮助弹窗 */
				.ghcm-help-overlay {
					position: fixed;
					inset: 0;
					background: rgba(17, 24, 39, 0.45);
					backdrop-filter: blur(4px);
					display: flex;
					align-items: center;
					justify-content: center;
					padding: 24px;
					opacity: 0;
					transition: opacity 180ms ease;
					z-index: 10001;
				}

				.ghcm-help-overlay.show {
					opacity: 1;
				}

				.ghcm-help-modal {
					position: relative;
					width: min(720px, 90vw);
					max-height: min(90vh, 720px);
					background: var(--color-canvas-default, #ffffff);
					border-radius: 16px;
					border: 1px solid var(--color-border-default, #d0d7de);
					box-shadow: 0 24px 48px rgba(15, 23, 42, 0.26);
					display: flex;
					flex-direction: column;
					overflow: hidden;
					transform: translateY(10px) scale(0.96);
					transition: transform 200ms ease, opacity 200ms ease;
					opacity: 0;
				}

				.ghcm-help-modal:focus,
				.ghcm-help-modal:focus-visible {
					outline: none;
					box-shadow: 0 24px 48px rgba(15, 23, 42, 0.26), 0 0 0 3px rgba(99, 102, 241, 0.22);
				}

				.ghcm-help-overlay.show .ghcm-help-modal {
					transform: translateY(0) scale(1);
					opacity: 1;
				}

				.ghcm-help-header {
					display: flex;
					align-items: center;
					justify-content: space-between;
					padding: 20px 24px 16px;
					background: var(--color-canvas-subtle, #f6f8fa);
					border-bottom: 1px solid var(--color-border-default, #d0d7de);
					gap: 12px;
				}

				.ghcm-help-title {
					display: flex;
					flex-direction: column;
					gap: 4px;
				}

				.ghcm-help-title-text {
					font-size: 1.15rem;
					font-weight: 600;
					color: var(--color-fg-default, #1f2329);
				}

				.ghcm-help-title-sub {
					font-size: 0.85rem;
					color: var(--color-fg-muted, #4c566a);
				}

				.ghcm-help-close {
					width: 36px;
					height: 36px;
					border: none;
					border-radius: 50%;
					background: transparent;
					color: var(--color-fg-muted, #4c566a);
					cursor: pointer;
					transition: background 160ms ease, color 160ms ease, transform 160ms ease;
					font-size: 18px;
					line-height: 1;
				}

				.ghcm-help-close:hover {
					background: rgba(99, 102, 241, 0.08);
					color: var(--color-fg-default, #1f2329);
					transform: scale(1.05);
				}

				.ghcm-help-close:focus {
					outline: 2px solid rgba(99, 102, 241, 0.35);
					outline-offset: 2px;
				}

				.ghcm-help-content {
					padding: 20px 24px 28px;
					overflow: auto;
					scrollbar-width: thin;
				}

				.ghcm-help-content::-webkit-scrollbar {
					width: 8px;
				}

				.ghcm-help-content::-webkit-scrollbar-thumb {
					background: rgba(148, 163, 184, 0.5);
					border-radius: 999px;
				}

				.ghcm-help-content > .markdown-body {
					font-size: 14px;
					line-height: 1.65;
					color: var(--color-fg-default, #1f2329);
				}

				.ghcm-help-grid {
					display: grid;
					grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
					gap: 16px;
				}

				.ghcm-help-card {
					padding: 16px;
					border-radius: 12px;
					border: 1px solid rgba(99, 102, 241, 0.15);
					background: rgba(99, 102, 241, 0.05);
				}

				.ghcm-help-card h3 {
					margin-top: 0;
					margin-bottom: 8px;
					font-size: 0.95rem;
				}

				.ghcm-help-shortcut {
					display: flex;
					align-items: center;
					justify-content: space-between;
					gap: 12px;
					padding: 8px 0;
					border-bottom: 1px solid rgba(148, 163, 184, 0.2);
				}

				.ghcm-help-shortcut:last-child {
					border-bottom: none;
				}

				.ghcm-help-kbd {
					display: inline-flex;
					align-items: center;
					justify-content: center;
					min-width: 82px;
					padding: 6px 10px;
					border-radius: 8px;
					background: rgba(15, 23, 42, 0.05);
					border: 1px solid rgba(148, 163, 184, 0.4);
					font-family: ui-monospace, SFMono-Regular, SFMono, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
					font-size: 0.85rem;
					color: var(--color-fg-default, #1f2329);
				}

				.ghcm-help-kbd span {
					white-space: nowrap;
				}

				.ghcm-help-list {
					display: grid;
					gap: 10px;
					padding-left: 0;
					list-style: none;
				}

				.ghcm-help-list li {
					display: flex;
					gap: 8px;
				}

				.ghcm-help-list strong {
					color: rgba(79, 70, 229, 1);
				}

				.ghcm-help-section + .ghcm-help-section {
					margin-top: 24px;
				}

				.ghcm-help-footnote {
					margin-top: 12px;
					font-size: 0.85rem;
					color: var(--color-fg-muted, #4c566a);
				}

				/* 深色主题适配 */
				@media (prefers-color-scheme: dark) {
					.${CONFIG.classes.tocContainer},
					.${CONFIG.classes.searchContainer} {
						background: var(--color-canvas-default, #0d1117);
						border-color: var(--color-border-default, #30363d);
					}

					.ghcm-help-overlay {
						background: rgba(15, 23, 42, 0.6);
					}

					.ghcm-help-modal {
						background: var(--color-canvas-default, #0d1117);
						border-color: var(--color-border-default, #30363d);
						box-shadow: 0 24px 48px rgba(8, 13, 23, 0.6);
					}

					.ghcm-help-modal:focus,
					.ghcm-help-modal:focus-visible {
						box-shadow: 0 24px 48px rgba(8, 13, 23, 0.6), 0 0 0 3px rgba(129, 140, 248, 0.35);
					}

					.ghcm-help-header {
						background: var(--color-canvas-subtle, #161b22);
						border-color: var(--color-border-default, #30363d);
					}

					.ghcm-help-title-text {
						color: var(--color-fg-default, #e6edf3);
					}

					.ghcm-help-title-sub,
					.ghcm-help-footnote {
						color: var(--color-fg-muted, #8b949e);
					}

					.ghcm-help-close {
						color: var(--color-fg-muted, #8b949e);
					}

					.ghcm-help-close:hover {
						background: rgba(99, 102, 241, 0.18);
						color: var(--color-fg-default, #e6edf3);
					}

					.ghcm-help-content > .markdown-body {
						color: var(--color-fg-default, #e6edf3);
					}

					.ghcm-help-card {
						background: rgba(79, 70, 229, 0.12);
						border-color: rgba(129, 140, 248, 0.45);
					}

					.ghcm-help-kbd {
						background: rgba(148, 163, 184, 0.12);
						border-color: rgba(148, 163, 184, 0.35);
						color: var(--color-fg-default, #e6edf3);
					}
				}
			`);
		}

		generateHeaderSelectors() {
			const containers = CONFIG.selectors.markdownContainers;
			const headers = DOMUtils.getHeadingTagsLower();

			const baseSelectors = [];
			const afterSelectors = [];

			containers.forEach(container => {
				if (container) {
					headers.forEach(header => {
						baseSelectors.push(`${container} ${header}`);
						baseSelectors.push(`${container} ${header}.heading-element`);
						afterSelectors.push(`${container} ${header}:after`);
						afterSelectors.push(`${container} ${header}.heading-element:after`);
					});
				}
			});

			return {
				base: baseSelectors.join(", "),
				after: afterSelectors.join(", ")
			};
		}

		generateArrowContent() {
			const headers = DOMUtils.getHeadingTagsLower();
			return headers.map((header, index) => {
				const level = index + 1;
				const containers = CONFIG.selectors.markdownContainers;
				const selectors = [];

				containers.forEach(container => {
					if (container) {
						selectors.push(`${container} ${header}:after`);
						selectors.push(`${container} ${header}.heading-element:after`);
					}
				});

				return `${selectors.join(", ")} { content: "${level}▼"; }`;
			}).join("\n");
		}

		addColorStyles() {
			const headers = DOMUtils.getHeadingTagsLower();
			const styles = headers.map((header, index) => {
				const containers = CONFIG.selectors.markdownContainers;
				const selectors = [];

				containers.forEach(container => {
					if (container) {
						selectors.push(`${container} ${header}:after`);
						selectors.push(`${container} ${header}.heading-element:after`);
					}
				});

				return `${selectors.join(", ")} { color: ${CONFIG.colors[index]}; }`;
			}).join("\n");

			this.arrowColors.textContent = styles;
		}

			updateColors(newColors) {
				CONFIG.colors = newColors;
				GM_setValue("ghcm-colors", newColors);
				this.addColorStyles();
		}

		applyArrowSize(size) {
			try {
				document.documentElement.style.setProperty('--ghcm-arrow-size', size || '0.8em');
			} catch {}
		}

		updateArrowSize(size) {
			if (!size) return;
			CONFIG.ui.arrowSize = size;
			GM_setValue('ghcm-arrow-size', size);
			this.applyArrowSize(size);
		}

		updateArrowContentOverride() {
			const headerSelectors = this.generateHeaderSelectors();
			const after = headerSelectors.after;
			const showNum = !!CONFIG.ui.showLevelNumber;
			if (!showNum) {
				// 仅显示箭头(覆盖初始带数字的内容)
				this.arrowContentOverride.textContent = `${after} { content: "\\25BC" !important; }`;
			} else {
				// 显示级别数字 + 箭头,覆盖以确保与当前设置一致
				const headers = DOMUtils.getHeadingTagsLower();
				const rules = headers.map((header, index) => {
					const level = index + 1;
					const containers = CONFIG.selectors.markdownContainers;
					const selectors = [];
					containers.forEach(container => {
						if (container) {
							selectors.push(`${container} ${header}:after`);
							selectors.push(`${container} ${header}.heading-element:after`);
						}
					});
					return `${selectors.join(", ")} { content: "${level}\\25BC" !important; }`;
				}).join("\n");
				this.arrowContentOverride.textContent = rules;
			}
		}
	}

	// 折叠功能核心类
class CollapseManager {
	constructor(stateManager) {
		this.stateManager = stateManager;
		// Map<headerKey, Set<timeoutId>> to track and cancel animations per header
		this.animationQueue = new Map();
		// 单一滚动校准定时器,防止快速点击产生来回滚动
		this._scrollEnsureTimeout = null;
		this.activeHeading = null;
		this._activeNotification = null;
	}

	// Track a timeout for a header key
	trackTimeout(headerKey, timeoutId) {
		if (!this.animationQueue.has(headerKey)) {
			this.animationQueue.set(headerKey, new Set());
		}
		this.animationQueue.get(headerKey).add(timeoutId);
	}

	// Cancel all pending timeouts for a header key
	cancelTimeouts(headerKey) {
		const set = this.animationQueue.get(headerKey);
		if (!set) return;
		set.forEach(id => clearTimeout(id));
		this.animationQueue.delete(headerKey);
	}

	// Cancel all pending animations (used on navigation)
	clearAllAnimations() {
		for (const set of this.animationQueue.values()) {
			set.forEach(id => clearTimeout(id));
		}
		this.animationQueue.clear();
	}

		toggle(header, isShiftClicked = false) {
			if (!header || header.classList.contains(CONFIG.classes.noContent)) {
				return;
			}

			const startTime = performance.now();
			const level = this.stateManager.getHeaderLevel(header);
			const isCollapsed = !header.classList.contains(CONFIG.classes.collapsed);

			Logger.log("[GHCM] Toggle:", header, "Level:", level, "Will collapse:", isCollapsed);

			if (isShiftClicked) {
				this.toggleAllSameLevel(level, isCollapsed);
			} else {
				this.toggleSingle(header, isCollapsed);
			}

			// 性能监控
			const endTime = performance.now();
			const duration = endTime - startTime;

			if (duration > 100 && CONFIG.animation.maxAnimatedElements > 0) {
				Logger.warn(`[GHCM] 检测到性能问题 (${duration.toFixed(1)}ms),建议启用性能模式`);

				// 自动降级性能设置
				if (!GM_getValue("ghcm-auto-performance-warned", false)) {
					CONFIG.animation.maxAnimatedElements = Math.max(5, CONFIG.animation.maxAnimatedElements / 2);
					Logger.log(`[GHCM] 自动调整动画阈值为: ${CONFIG.animation.maxAnimatedElements}`);
					GM_setValue("ghcm-auto-performance-warned", true);
				}
			}

			this.setActiveHeading(header);
			DOMUtils.clearSelection();
			DOMUtils.blurActiveElement();
			this.dispatchToggleEvent(header, level, isCollapsed);
		}

		toggleSingle(header, isCollapsed) {
			header.classList.toggle(CONFIG.classes.collapsed, isCollapsed);
			this.updateAriaExpanded(header);
			this.updateContent(header, isCollapsed);
		}

		toggleAllSameLevel(level, isCollapsed) {
			const selectors = CONFIG.selectors.markdownContainers
				.map(container => DOMUtils.getScopedHeadingSelector(container, {
					level,
					includeWrapper: true
				}))
				.filter(Boolean)
				.join(', ');

			if (!selectors) return;

			DOMUtils.$$(selectors).forEach(header => {
				if (DOMUtils.isHeader(header)) {
					header.classList.toggle(CONFIG.classes.collapsed, isCollapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, isCollapsed);
				}
			});
		}

		updateAriaExpanded(header) {
			try {
				const expanded = !header.classList.contains(CONFIG.classes.collapsed);
				header.setAttribute('aria-expanded', String(expanded));
			} catch {}
		}

		updateContent(header, isCollapsed) {
			const level = this.stateManager.getHeaderLevel(header);
			const headerKey = this.stateManager.generateHeaderKey(header);
			const elements = this.getContentElements(header, level);

			// 分析元素:区分普通内容和子标题
			const analyzedElements = elements.map(el => {
				const childHeader = DOMUtils.isHeader(el) ? el : el.querySelector(DOMUtils.getUpperHeadingSelector());
				return {
					element: el,
					isHeader: !!childHeader,
					childHeader: childHeader,
					childHeaderCollapsed: childHeader ? childHeader.classList.contains(CONFIG.classes.collapsed) : false
				};
			});

		// 更新状态(仅存折叠布尔,避免 DOM 引用常驻)
		this.stateManager.setHeaderState(headerKey, { isCollapsed });

			// 执行智能动画(考虑子标题状态)
			this.animateElementsIntelligent(analyzedElements, isCollapsed, headerKey);
		}

		getContentElements(header, level) {
			const container = DOMUtils.getHeaderContainer(header);
			const elements = [];
			let nextElement = container.nextElementSibling;

			// 构建同级和更高级别的选择器
			const higherLevelSelectors = DOMUtils.getHeadingSelectorUpToLevel(level);

			while (nextElement) {
				// 如果遇到同级或更高级别的标题,停止
				if (nextElement.matches(higherLevelSelectors) ||
					(nextElement.classList?.contains('markdown-heading') &&
					nextElement.querySelector(higherLevelSelectors))) {
				break;
			}

				elements.push(nextElement);
				nextElement = nextElement.nextElementSibling;
			}

			return elements;
		}

		animateElements(elements, isCollapsed, headerKey) {
		// 取消之前的动画
		this.cancelTimeouts(headerKey);

			// 性能优化:如果元素太多,直接切换而不做动画
			if (elements.length > CONFIG.animation.maxAnimatedElements) {
				this.toggleElementsInstantly(elements, isCollapsed);
				return;
			}

			// 对于适量元素,使用优化的批量动画
			this.animateElementsBatch(elements, isCollapsed, headerKey);
		}

		// 新的智能动画方法,考虑子标题状态
		animateElementsIntelligent(analyzedElements, isCollapsed, headerKey) {
		// 取消之前的动画
		this.cancelTimeouts(headerKey);

			Logger.log(`[GHCM] 智能动画: ${analyzedElements.length} 个元素, 阈值: ${CONFIG.animation.maxAnimatedElements}`);

			// 性能优化:如果元素太多,直接切换
			if (analyzedElements.length > CONFIG.animation.maxAnimatedElements) {
				Logger.log(`[GHCM] 元素过多,使用即时切换模式`);
				this.toggleElementsIntelligentInstantly(analyzedElements, isCollapsed);
				return;
			}

			// 使用智能批量动画
			Logger.log(`[GHCM] 使用批量动画模式`);
			this.animateElementsIntelligentBatch(analyzedElements, isCollapsed, headerKey);
		}

		// 智能即时切换(性能模式)
		toggleElementsIntelligentInstantly(analyzedElements, isCollapsed) {
			Logger.log(`[GHCM] 性能模式:即时切换 ${analyzedElements.length} 个元素`);

			analyzedElements.forEach(({ element, isHeader, childHeader, childHeaderCollapsed }) => {
				if (isCollapsed) {
					// 折叠:隐藏所有内容
					element.classList.add(CONFIG.classes.hiddenByParent);
					element.style.removeProperty('display');
				} else {
					// 展开:根据子标题状态决定是否显示
					element.classList.remove(CONFIG.classes.hiddenByParent);
					element.style.removeProperty('display');

					// 如果是子标题且原本是折叠的,需要保持其内容隐藏
					if (isHeader && childHeaderCollapsed) {
						setTimeout(() => {
							this.ensureChildHeaderContentHidden(childHeader);
						}, 10);
					}

					// 清理动画样式
					element.style.removeProperty('opacity');
					element.style.removeProperty('transform');
					element.style.removeProperty('transition');
					element.classList.remove('ghcm-transitioning');
				}
			});
		}

				// 智能批量动画
		animateElementsIntelligentBatch(analyzedElements, isCollapsed, headerKey) {
			// 检查是否应该使用动画
			if (CONFIG.animation.maxAnimatedElements === 0) {
				this.toggleElementsIntelligentInstantly(analyzedElements, isCollapsed);
				return;
			}

			const batches = this.createIntelligentBatches(analyzedElements, CONFIG.animation.batchSize);

			const processBatch = (batchIndex) => {
				if (batchIndex >= batches.length) return;

				const batch = batches[batchIndex];

			if (isCollapsed) {
				this.collapseIntelligentBatch(batch, headerKey);
			} else {
				this.expandIntelligentBatch(batch, headerKey);
			}

				// 处理下一个批次
			if (batchIndex < batches.length - 1) {
				const timeout = setTimeout(() => {
					processBatch(batchIndex + 1);
				}, 30); // 减少延迟,让动画更流畅
				this.trackTimeout(headerKey, timeout);
			}
		};

			processBatch(0);
		}

		createIntelligentBatches(analyzedElements, batchSize) {
			const batches = [];
			for (let i = 0; i < analyzedElements.length; i += batchSize) {
				batches.push(analyzedElements.slice(i, i + batchSize));
			}
			return batches;
		}

	collapseIntelligentBatch(batch, headerKey) {
			Logger.log(`[GHCM] 折叠动画批次: ${batch.length} 个元素`);

			// 折叠批次:先设置初始状态和过渡效果
			batch.forEach(({ element }) => {
				element.style.opacity = '1';
				element.style.transform = 'translateY(0)';
				element.style.transition = `opacity ${CONFIG.animation.duration}ms ${CONFIG.animation.easing}, transform ${CONFIG.animation.duration}ms ${CONFIG.animation.easing}`;
			});

			// 使用requestAnimationFrame确保样式已应用
			requestAnimationFrame(() => {
				batch.forEach(({ element }) => {
					element.style.opacity = '0';
					element.style.transform = 'translateY(-8px)';
				});

				// 动画完成后隐藏元素
								const t = setTimeout(() => {
					batch.forEach(({ element }) => {
						element.classList.add(CONFIG.classes.hiddenByParent);
						element.style.removeProperty('display');
						element.style.removeProperty('opacity');
						element.style.removeProperty('transform');
						element.style.removeProperty('transition');
					});
					Logger.log(`[GHCM] 折叠动画批次完成`);
				}, CONFIG.animation.duration);
				this.trackTimeout(headerKey, t);
			});
		}

	expandIntelligentBatch(batch, headerKey) {
			Logger.log(`[GHCM] 展开动画批次: ${batch.length} 个元素`);

			// 展开批次:先显示元素但设为初始动画状态
			batch.forEach(({ element, isHeader, childHeader, childHeaderCollapsed }) => {
				element.classList.remove(CONFIG.classes.hiddenByParent);
				element.style.removeProperty('display');
				element.style.opacity = '0';
				element.style.transform = 'translateY(-8px)';
				element.style.transition = `opacity ${CONFIG.animation.duration}ms ${CONFIG.animation.easing}, transform ${CONFIG.animation.duration}ms ${CONFIG.animation.easing}`;
			});

			// 使用requestAnimationFrame确保DOM更新完成
			requestAnimationFrame(() => {
				batch.forEach(({ element, isHeader, childHeader, childHeaderCollapsed }) => {
					element.style.opacity = '1';
					element.style.transform = 'translateY(0)';

					// 如果是子标题且原本是折叠的,确保其内容保持隐藏
					if (isHeader && childHeaderCollapsed) {
						// 延迟执行,确保动画和DOM更新完成
                        setTimeout(() => {
							this.ensureChildHeaderContentHidden(childHeader);
						}, CONFIG.animation.duration + 50);
					}
				});

				// 清理样式
				const t = setTimeout(() => {
					batch.forEach(({ element }) => {
						element.style.removeProperty('opacity');
						element.style.removeProperty('transform');
						element.style.removeProperty('transition');
					});
					Logger.log(`[GHCM] 展开动画批次完成`);
				}, CONFIG.animation.duration);
				this.trackTimeout(headerKey, t);
			});
		}

		// 确保子标题的内容保持隐藏状态
		ensureChildHeaderContentHidden(childHeader) {
			if (!childHeader || !childHeader.classList.contains(CONFIG.classes.collapsed)) {
				return;
			}

			const childLevel = this.stateManager.getHeaderLevel(childHeader);
			const childElements = this.getContentElements(childHeader, childLevel);

			// 立即隐藏子标题的内容,不使用动画
			childElements.forEach(element => {
				element.classList.add(CONFIG.classes.hiddenByParent);
				element.style.removeProperty('display');
				element.style.removeProperty('opacity');
				element.style.removeProperty('transform');
				element.classList.remove('ghcm-transitioning');
			});

			Logger.log(`[GHCM] 已恢复子标题的折叠状态:`, childHeader.textContent.trim());
		}

		// 即时切换,无动画
		toggleElementsInstantly(elements, isCollapsed) {
			// 批量DOM操作,减少重排

			elements.forEach(element => {
				if (isCollapsed) {
					element.classList.add(CONFIG.classes.hiddenByParent);
					element.style.removeProperty('display');
            } else {
					element.classList.remove(CONFIG.classes.hiddenByParent);
					element.style.removeProperty('display');
					// 清理可能存在的动画样式
					element.style.removeProperty('opacity');
					element.style.removeProperty('transform');
					element.classList.remove('ghcm-transitioning');
				}
			});
		}

		// 批量动画处理
		animateElementsBatch(elements, isCollapsed, headerKey) {
			const batches = this.createBatches(elements, CONFIG.animation.batchSize);
			let completedBatches = 0;

			const processBatch = (batchIndex) => {
				if (batchIndex >= batches.length) return;

				const batch = batches[batchIndex];

				// 为每个批次准备DOM变更
			if (isCollapsed) {
				this.collapseBatch(batch, headerKey);
			} else {
				this.expandBatch(batch, headerKey);
			}

				completedBatches++;

				// 处理下一个批次
				if (batchIndex < batches.length - 1) {
					const timeout = setTimeout(() => {
						processBatch(batchIndex + 1);
					}, 50); // 批次间短暂延迟
					this.trackTimeout(headerKey, timeout);
				}
		};

			processBatch(0);
		}

		createBatches(elements, batchSize) {
			const batches = [];
			for (let i = 0; i < elements.length; i += batchSize) {
				batches.push(elements.slice(i, i + batchSize));
			}
			return batches;
		}

	collapseBatch(batch, headerKey) {
			// 先设置初始状态
			batch.forEach(element => {
				element.style.transition = `opacity ${CONFIG.animation.duration}ms ${CONFIG.animation.easing}`;
				element.style.opacity = '1';
			});

			// 触发动画
			requestAnimationFrame(() => {
				batch.forEach(element => {
					element.style.opacity = '0';
				});

				// 动画完成后隐藏
					const t = setTimeout(() => {
					batch.forEach(element => {
						element.classList.add(CONFIG.classes.hiddenByParent);
						element.style.removeProperty('display');
						element.style.removeProperty('opacity');
						element.style.removeProperty('transition');
					});
				}, CONFIG.animation.duration);
				this.trackTimeout(headerKey, t);
			});
		}

	expandBatch(batch, headerKey) {
			// 先显示元素但设为透明
			batch.forEach(element => {
				element.classList.remove(CONFIG.classes.hiddenByParent);
				element.style.removeProperty('display');
				element.style.opacity = '0';
				element.style.transition = `opacity ${CONFIG.animation.duration}ms ${CONFIG.animation.easing}`;
			});

			// 触发淡入动画
			requestAnimationFrame(() => {
				batch.forEach(element => {
					element.style.opacity = '1';
				});

				// 清理样式
						const t = setTimeout(() => {
					batch.forEach(element => {
						element.style.removeProperty('opacity');
						element.style.removeProperty('transition');
					});
					}, CONFIG.animation.duration);
					this.trackTimeout(headerKey, t);
			});
		}

		// 展开到指定标题(用于hash导航)
		expandToHeader(targetHeader, { scroll = true, setActive = true } = {}) {
			if (!targetHeader) return;

			const level = this.stateManager.getHeaderLevel(targetHeader);
			let current = targetHeader;

			// 向上查找所有父级标题并展开
			while (current) {
				const container = DOMUtils.getHeaderContainer(current);
				let previous = container.previousElementSibling;
				let foundParent = false;

				// 查找更高级别的父标题
				while (previous) {
					const parentHeader = this.findHeaderInElement(previous, level - 1);
					if (parentHeader) {
						if (parentHeader.classList.contains(CONFIG.classes.collapsed)) {
							this.toggleSingle(parentHeader, false);
						}
						current = parentHeader;
						foundParent = true;
						break;
					}
					previous = previous.previousElementSibling;
				}

				if (!foundParent) break;
			}

			// 滚动到目标位置
			if (scroll) {
				this.scrollToElement(targetHeader);
			}
			if (setActive) {
				this.setActiveHeading(targetHeader, { scroll: false });
			}
		}

		findHeaderInElement(element, maxLevel) {
			if (DOMUtils.isHeader(element)) {
				const elementLevel = this.stateManager.getHeaderLevel(element);
				if (elementLevel <= maxLevel) return element;
			}

			// 查找容器内的标题
			for (let i = 1; i < maxLevel; i++) {
				const headerName = CONFIG.selectors.headers[i - 1].toLowerCase();
				const header = element.querySelector(headerName) ||
							  element.querySelector(`${headerName}.heading-element`);
				if (header) return header;
			}

			return null;
		}

		scrollToElement(element) {
			if (!element) return;

			// 顶部偏移考虑 GitHub 顶栏高度
			const headerEl = document.querySelector('header[role="banner"], .Header, .AppHeader-globalBar');
			const headerOffset = (headerEl?.offsetHeight || 80) + 20; // 额外留白
			const rect = element.getBoundingClientRect();
			const targetPosition = Math.max(0, rect.top + window.pageYOffset - headerOffset);

			// 平滑滚动
			window.scrollTo({ top: targetPosition, behavior: 'smooth' });

			// 延迟再次确保位置正确
			if (this._scrollEnsureTimeout) clearTimeout(this._scrollEnsureTimeout);
			this._scrollEnsureTimeout = setTimeout(() => {
				if (Math.abs(window.scrollY - targetPosition) > 50) {
					window.scrollTo({ top: targetPosition, behavior: 'smooth' });
				}
			}, 500);
		}

		setActiveHeading(element, { scroll = false } = {}) {
			if (!element) return;
			let header = element;
			if (!DOMUtils.isHeader(header)) {
				header = header.querySelector(DOMUtils.getUpperHeadingSelector());
			}
			if (!header) return;
			if (this.activeHeading && this.activeHeading !== header) {
				try { this.activeHeading.classList.remove(CONFIG.classes.activeHeading); } catch {}
			}
			this.activeHeading = header;
			try { header.classList.add(CONFIG.classes.activeHeading); } catch {}
			try {
				const id = this.tocGenerator?.getHeaderId?.(header) || header.id || header.getAttribute('id');
				if (id && this.tocGenerator?.tocContainer) {
					this.tocGenerator.highlightTocById(id);
				}
			} catch {}
			if (scroll) {
				this.scrollToElement(header);
			}
		}

		getActiveHeaderElement(force = false) {
			if (!force && this.activeHeading && document.contains(this.activeHeading)) {
				return this.activeHeading;
			}
			const headers = this.getAllHeaders();
			if (!headers.length) return null;
			const headerEl = document.querySelector('header[role="banner"], .Header, .AppHeader-globalBar');
			const headerOffset = (headerEl?.offsetHeight || 80) + 20;
			const position = window.scrollY + headerOffset + 1;
			let active = headers[0];
			for (const header of headers) {
				const top = header.getBoundingClientRect().top + window.pageYOffset;
				if (top <= position) {
					active = header;
				} else {
					break;
				}
			}
			if (active) {
				this.setActiveHeading(active);
			}
			return active;
		}

		isHeaderNavigable(header) {
			if (!header) return false;
			if (header.classList?.contains(CONFIG.classes.hidden) ||
				header.classList?.contains(CONFIG.classes.hiddenByParent)) {
				return false;
			}
			try {
				if (header.closest(`.${CONFIG.classes.hiddenByParent}`)) {
					return false;
				}
			} catch {}
			try {
				const style = window.getComputedStyle(header);
				if (style.display === 'none' || style.visibility === 'hidden') {
					return false;
				}
			} catch {}
			return true;
		}

		findNavigableIndex(headers, startIndex, step) {
			for (let i = startIndex; i >= 0 && i < headers.length; i += step) {
				const candidate = headers[i];
				if (this.isHeaderNavigable(candidate)) {
					return i;
				}
			}
			return -1;
		}

		focusNextHeading() {
			DOMUtils.blurActiveElement();
			const headers = this.getAllHeaders();
			if (!headers.length) return;
			const current = this.getActiveHeaderElement();
			const currentIndex = headers.indexOf(current);
			const startIndex = currentIndex === -1 ? 0 : currentIndex + 1;
			const targetIndex = this.findNavigableIndex(headers, startIndex, 1);
			if (targetIndex === -1) {
				this.showNotification('📌 已是最后一个可见标题');
				return;
			}
			const target = headers[targetIndex];
			this.expandToHeader(target, { scroll: false, setActive: false });
			this.scrollToElement(target);
			this.setActiveHeading(target);
		}

		focusPreviousHeading() {
			DOMUtils.blurActiveElement();
			const headers = this.getAllHeaders();
			if (!headers.length) return;
			const current = this.getActiveHeaderElement();
			const currentIndex = headers.indexOf(current);
			const startIndex = currentIndex === -1 ? headers.length - 1 : currentIndex - 1;
			const targetIndex = this.findNavigableIndex(headers, startIndex, -1);
			if (targetIndex === -1) {
				this.showNotification('📌 已是第一个可见标题');
				return;
			}
			const target = headers[targetIndex];
			this.expandToHeader(target, { scroll: false, setActive: false });
			this.scrollToElement(target);
			this.setActiveHeading(target);
		}

		dispatchToggleEvent(header, level, isCollapsed) {
			document.dispatchEvent(new CustomEvent("ghcm:toggle-complete", {
				detail: { header, level, isCollapsed }
			}));

			// 如果是展开操作,检查并恢复子标题状态
			if (!isCollapsed) {
				setTimeout(() => {
					this.checkAndRestoreChildHeaderStates(header, level);
				}, CONFIG.animation.duration + 100);
			}
		}

		// 检查并恢复子标题的折叠状态
		checkAndRestoreChildHeaderStates(parentHeader, parentLevel) {
			const container = DOMUtils.getHeaderContainer(parentHeader);
			let nextElement = container.nextElementSibling;

			// 查找所有子标题并恢复其状态
			const higherLevelSelectors = DOMUtils.getHeadingSelectorUpToLevel(parentLevel);
			while (nextElement) {
				// 停止条件:遇到同级或更高级别的标题
				if (nextElement.matches(higherLevelSelectors) ||
					(nextElement.classList?.contains('markdown-heading') &&
					nextElement.querySelector(higherLevelSelectors))) {
					break;
				}

				// 检查是否是子标题
				const childHeader = DOMUtils.isHeader(nextElement) ?
					nextElement : nextElement.querySelector(DOMUtils.getUpperHeadingSelector());

				if (childHeader && childHeader.classList.contains(CONFIG.classes.collapsed)) {
					// 确保这个子标题的内容保持隐藏
					this.ensureChildHeaderContentHidden(childHeader);
				}

				nextElement = nextElement.nextElementSibling;
			}
		}

		// 批量操作方法
		getAllHeaders() {
			return DOMUtils.collectHeadings();
		}

		syncAriaExpandedForAll() {
			try {
				this.getAllHeaders().forEach(h => {
					const expanded = !h.classList.contains(CONFIG.classes.collapsed);
					h.setAttribute('aria-expanded', String(expanded));
				});
			} catch {}
		}

		collapseAll() {
			const headers = this.getAllHeaders();
			let count = 0;

			headers.forEach(header => {
				if (!header.classList.contains(CONFIG.classes.collapsed) &&
					!header.classList.contains(CONFIG.classes.noContent)) {
					header.classList.add(CONFIG.classes.collapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, true);
					count++;
				}
			});

			Logger.log(`[GHCM] 已折叠 ${count} 个标题`);
			this.showNotification(`📁 已折叠 ${count} 个标题`);
		}

		expandAll() {
			const headers = this.getAllHeaders();
			let count = 0;

			headers.forEach(header => {
				if (header.classList.contains(CONFIG.classes.collapsed)) {
					header.classList.remove(CONFIG.classes.collapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, false);
					count++;
				}
			});

			Logger.log(`[GHCM] 已展开 ${count} 个标题`);
			this.showNotification(`📂 已展开 ${count} 个标题`);
		}

		toggleAll() {
			const headers = this.getAllHeaders();
			const collapsedCount = headers.filter(h =>
				h.classList.contains(CONFIG.classes.collapsed)
			).length;
			const totalCount = headers.filter(h =>
				!h.classList.contains(CONFIG.classes.noContent)
			).length;

			// 如果超过一半已折叠,则全部展开;否则全部折叠
			if (collapsedCount > totalCount / 2) {
				this.expandAll();
			} else {
				this.collapseAll();
			}
		}

		// 按级别批量操作
		collapseLevel(level) {
			const selectors = CONFIG.selectors.markdownContainers
				.map(container => DOMUtils.getScopedHeadingSelector(container, { level }))
				.filter(Boolean)
				.join(', ');
			if (!selectors) return;

			const headers = DOMUtils.$$(selectors).filter(el => DOMUtils.isHeader(el));

			let count = 0;
			headers.forEach(header => {
				if (!header.classList.contains(CONFIG.classes.collapsed) &&
					!header.classList.contains(CONFIG.classes.noContent)) {
					header.classList.add(CONFIG.classes.collapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, true);
					count++;
				}
			});

			Logger.log(`[GHCM] 已折叠 ${count} 个 H${level} 标题`);
			this.showNotification(`📁 已折叠 ${count} 个 H${level} 标题`);
		}

		expandLevel(level) {
			const selectors = CONFIG.selectors.markdownContainers
				.map(container => DOMUtils.getScopedHeadingSelector(container, { level }))
				.filter(Boolean)
				.join(', ');
			if (!selectors) return;

			const headers = DOMUtils.$$(selectors).filter(el => DOMUtils.isHeader(el));

			let count = 0;
			headers.forEach(header => {
				if (header.classList.contains(CONFIG.classes.collapsed)) {
					header.classList.remove(CONFIG.classes.collapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, false);
					count++;
				}
			});

			Logger.log(`[GHCM] 已展开 ${count} 个 H${level} 标题`);
			this.showNotification(`📂 已展开 ${count} 个 H${level} 标题`);
		}

		// 通知功能
		showNotification(message) {
			if (this._activeNotification) {
				try { this._activeNotification.remove(); } catch {}
				this._activeNotification = null;
			}

			// 创建通知元素
			const notification = document.createElement('div');
			notification.style.cssText = `
				position: fixed;
				top: 20px;
				left: 50%;
				transform: translateX(-50%);
				background: var(--color-canvas-default, #ffffff);
				border: 1px solid var(--color-border-default, #d0d7de);
				border-radius: 8px;
				padding: 12px 20px;
				box-shadow: 0 4px 12px rgba(0,0,0,0.15);
				z-index: 10002;
				font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
				font-size: 14px;
				color: var(--color-fg-default, #24292f);
				opacity: 0;
				transition: opacity 0.3s ease;
			`;
			notification.textContent = message;

			document.body.appendChild(notification);
			this._activeNotification = notification;

			// 显示动画
			requestAnimationFrame(() => {
				notification.style.opacity = '1';
			});

			// 自动消失
			setTimeout(() => {
				notification.style.opacity = '0';
				setTimeout(() => {
					if (notification.parentNode) {
						notification.parentNode.removeChild(notification);
					}
					if (this._activeNotification === notification) {
						this._activeNotification = null;
					}
				}, 300);
			}, 2000);
		}

		// 加载已保存的状态
		loadSavedStates() {
			this.stateManager.loadFromMemory();

			// 分层应用已保存的状态(从高级别到低级别)
			for (let level = 1; level <= 6; level++) {
				this.applyStatesForLevel(level);
			}
		}

		applyStatesForLevel(level) {
			const headers = this.getAllHeaders().filter(h =>
				this.stateManager.getHeaderLevel(h) === level
			);

			headers.forEach(header => {
				const headerKey = this.stateManager.generateHeaderKey(header);
				const savedState = this.stateManager.getHeaderState(headerKey);

				if (savedState && savedState.isCollapsed) {
					Logger.log(`[GHCM] 恢复 H${level} 标题状态:`, header.textContent.trim());
					header.classList.add(CONFIG.classes.collapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, true);
			}
		});
	}

		applyStateToElement(headerKey, state) {
			// 保留原方法作为备用
			const headers = this.getAllHeaders();
			headers.forEach(header => {
				const currentKey = this.stateManager.generateHeaderKey(header);
				if (currentKey === headerKey && state.isCollapsed) {
					header.classList.add(CONFIG.classes.collapsed);
					this.updateAriaExpanded(header);
					this.updateContent(header, true);
				}
			});
		}

		// 代理目录和搜索功能
		toggleToc() {
			if (this.tocGenerator) {
				this.tocGenerator.toggle();
			}
		}

		toggleSearch() {
			if (this.searchManager) {
				this.searchManager.toggle();
			}
		}

		// 检查标题是否有内容
		markEmptyHeaders() {
			CONFIG.selectors.markdownContainers.forEach(containerSelector => {
				const selector = DOMUtils.getScopedHeadingSelector(containerSelector, { includeWrapper: true });
				if (!selector) return;

				DOMUtils.$$(selector).forEach(header => {
					const level = this.stateManager.getHeaderLevel(header);
					const elements = this.getContentElements(header, level);

					if (elements.length === 0) {
						header.classList.add(CONFIG.classes.noContent);
					} else {
						header.classList.remove(CONFIG.classes.noContent);
					}
				});
			});
		}
	}

	// 事件管理器
	class EventManager {
	constructor(collapseManager) {
		this.collapseManager = collapseManager;
		this.hoverHeader = null;
		this.setupEventListeners();
	}

		setupEventListeners() {
			// 点击事件
			document.addEventListener("click", this.handleClick.bind(this), true);
			// Hover 高亮
			this._hoverHandler = this.handleHover.bind(this);
			this._hoverLeaveHandler = this.handleHoverLeave.bind(this);
			document.addEventListener('mouseover', this._hoverHandler, true);
			document.addEventListener('mouseout', this._hoverLeaveHandler, true);

			// Hash 变化事件
			window.addEventListener("hashchange", this.handleHashChange.bind(this));

			// DOM 变化监听(如果有其他脚本修改DOM)
			if (window.ghmo) {
				window.addEventListener("ghmo:dom", this.handleDOMChange.bind(this));
			}

			// GitHub 导航事件(PJAX/Turbo)
			document.addEventListener("pjax:end", this.handleNavigation.bind(this));
			document.addEventListener("turbo:load", this.handleNavigation.bind(this));
			document.addEventListener("turbo:render", this.handleNavigation.bind(this));
			window.addEventListener("pageshow", this.handleNavigation.bind(this));

			// 页面加载完成后初始化
			if (document.readyState === 'loading') {
				document.addEventListener('DOMContentLoaded', this.handleDOMChange.bind(this));
			} else {
				setTimeout(() => this.handleDOMChange(), 200);
			}
		}

		handleClick(event) {
			let target = event.target;

			// 仅处理左键
			if (event.button !== 0) return;

			// 文本选择时不触发
			try {
				const sel = window.getSelection?.();
				if (sel && sel.toString && sel.toString().trim().length > 0) return;
			} catch {}

			// 处理SVG点击
			if (target.nodeName === "path") {
				target = target.closest("svg");
			}

			// 跳过排除的元素与自身UI
			if (!target || this.shouldSkipElement(target) || target.closest('.ghcm-menu-container, .ghcm-search-container, .ghcm-toc-container, .ghcm-menu-button')) {
				return;
			}

			// 查找最近的标题元素
			const header = target.closest(DOMUtils.getHeadingSelector());

			if (header && DOMUtils.isHeader(header) && DOMUtils.isInMarkdown(header)) {
				// 仅在真正执行折叠时处理,避免干扰默认链接等行为
				Logger.log("[GHCM] Header clicked:", header);
			this.collapseManager.toggle(header, event.shiftKey);
		}
			}

		handleHover(event) {
			const header = event.target.closest(DOMUtils.getHeadingSelector());
			if (!header || !DOMUtils.isHeader(header)) return;
			if (this.hoverHeader === header) return;
			try {
				if (this.hoverHeader) {
					this.hoverHeader.classList.remove(CONFIG.classes.hoverHeading);
				}
				header.classList.add(CONFIG.classes.hoverHeading);
				this.hoverHeader = header;
			} catch {}
		}

		handleHoverLeave(event) {
			const header = event.target.closest(DOMUtils.getHeadingSelector());
			if (!header || !DOMUtils.isHeader(header)) return;
			const related = event.relatedTarget;
			if (related && (related === header || related.closest?.(DOMUtils.getHeadingSelector()) === header)) {
				return;
			}
			if (this.hoverHeader === header) {
				header.classList.remove(CONFIG.classes.hoverHeading);
				this.hoverHeader = null;
			}
		}

		shouldSkipElement(element) {
			const nodeName = element.nodeName?.toLowerCase();
			// 表单/可编辑区域内的交互不触发折叠
			try {
				if (element.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"], [role="textbox"]')) {
					return true;
				}
			} catch {}

			return CONFIG.selectors.excludeClicks.some(selector => {
				if (selector.startsWith('.')) {
					return element.classList.contains(selector.slice(1));
				}
				return nodeName === selector;
			});
		}

		handleHashChange() {
			const hash = window.location.hash.replace(/#/, "");
			if (hash) {
				this.openHashTarget(hash);
			}
		}

		handleDOMChange() {
			DOMUtils.invalidateHeadingCache();
			try { this.collapseManager.searchManager?.invalidateIndex?.(); } catch {}
			try { this.collapseManager.bookmarkManager?.applyBookmarks?.(); } catch {}
			// 重新标记空标题
			this.collapseManager.markEmptyHeaders();

			// 处理当前hash
			this.handleHashChange();
			try {
				const active = this.collapseManager.getActiveHeaderElement();
				if (active) this.collapseManager.setActiveHeading(active);
			} catch {}
		}

		handleNavigation() {
			DOMUtils.invalidateHeadingCache();
			try { this.collapseManager.searchManager?.invalidateIndex?.(); } catch {}
			try { this.collapseManager.bookmarkManager?.applyBookmarks?.(); } catch {}
			// 先清理任何挂起的动画/定时器
			try { this.collapseManager.clearAllAnimations(); } catch {}
			// 更新页面键,适配单页导航
			try { this.collapseManager.stateManager.updatePageKey(); } catch (e) {}
			// 重建标记并按需恢复状态
			this.handleDOMChange();
			if (CONFIG.memory.enabled) {
				setTimeout(() => {
					try { this.collapseManager.loadSavedStates(); } catch (e) {}
				}, 300);
			}
		}

		openHashTarget(id) {
			// 尝试多种ID格式
			const possibleSelectors = [
				`#user-content-${id}`,
				`#${id}`,
				`[id="${id}"]`
			];

			let targetElement = null;
			for (const selector of possibleSelectors) {
				targetElement = DOMUtils.$(selector);
				if (targetElement) break;
			}

			if (!targetElement) return;

			// 查找对应的标题
			let header = targetElement;
			if (!DOMUtils.isHeader(header)) {
				header = targetElement.closest(DOMUtils.getHeadingSelector());
			}

			if (header && DOMUtils.isHeader(header)) {
				this.collapseManager.expandToHeader(header, { scroll: false, setActive: false });
				this.collapseManager.scrollToElement(header);
				this.collapseManager.setActiveHeading(header);
			}
		}
	}

	// 主应用类
	class GitHubCollapseMarkdown {
		constructor() {
			this.stateManager = new StateManager();
			this.styleManager = new StyleManager();
			this.collapseManager = new CollapseManager(this.stateManager);
			this.tocGenerator = new TocGenerator();
			this.searchManager = new SearchManager(this.collapseManager);
			this.bookmarkManager = new BookmarkManager(this);
			this.menuManager = new MenuManager(this);
			this.helpModal = new HelpModal(this);
			this.hotkeyManager = new HotkeyManager(this.collapseManager);
			this.hotkeyManager.setApp(this);
			this.eventManager = new EventManager(this.collapseManager);

			// 将附加功能关联到折叠管理器
			this.collapseManager.tocGenerator = this.tocGenerator;
			this.collapseManager.searchManager = this.searchManager;
			this.collapseManager.menuManager = this.menuManager;
			this.collapseManager.bookmarkManager = this.bookmarkManager;
			this.tocGenerator.collapseManager = this.collapseManager;

			this.init();
		}

		init() {
			const performanceMode = GM_getValue("ghcm-performance-mode", false);
			const memoryEnabled = CONFIG.memory.enabled;
			const hotkeysEnabled = CONFIG.hotkeys.enabled;

			const animationStatus = (CONFIG.animation.maxAnimatedElements === 0) ? "性能模式 (无动画)" : "标准模式 (有动画)";

			Logger.log(`[GHCM] Initializing GitHub Collapse Markdown (Optimized v3.2.4) - ${animationStatus}`);
			Logger.log(`[GHCM] 🧠 智能嵌套状态管理: 启用`);
			Logger.log(`[GHCM] 🎨 现代GUI界面: 启用`);
			Logger.log(`[GHCM] 动画阈值: ${CONFIG.animation.maxAnimatedElements} 个元素`);
			Logger.log(`[GHCM] 状态记忆: ${memoryEnabled ? "启用" : "禁用"}`);
			Logger.log(`[GHCM] 快捷键: ${hotkeysEnabled ? "启用" : "禁用"}`);

			// 添加菜单命令
			this.setupMenuCommands();

			// 初始检查和状态加载
				setTimeout(() => {
				this.collapseManager.markEmptyHeaders();

				// 加载已保存的折叠状态
				if (memoryEnabled) {
					this.collapseManager.loadSavedStates();
				}
				// 同步所有标题的无障碍状态
				this.collapseManager.syncAriaExpandedForAll();
				this.bookmarkManager.applyBookmarks();
				}, 500);

			// 监听折叠状态变化,更新目录显示和菜单统计
			document.addEventListener('ghcm:toggle-complete', () => {
				if (this.tocGenerator.isVisible) {
					setTimeout(() => {
						this.tocGenerator.refreshTocStates();
					}, CONFIG.animation.duration + 150);
				}
				// 如果菜单打开,刷新统计信息
				if (this.menuManager.isVisible) {
					setTimeout(() => {
						this.menuManager.refreshMenu();
					}, CONFIG.animation.duration + 150);
				}
			});
		}

		setupMenuCommands() {
			try {
				// === 基础操作 ===
				GM_registerMenuCommand("📁 折叠所有标题", () => {
					this.collapseManager.collapseAll();
				});

				GM_registerMenuCommand("📂 展开所有标题", () => {
					this.collapseManager.expandAll();
				});

				GM_registerMenuCommand("🔄 智能切换", () => {
					this.collapseManager.toggleAll();
				});

				// === 工具功能 ===
				GM_registerMenuCommand("📑 目录导航", () => {
					this.tocGenerator.toggle();
				});

					GM_registerMenuCommand("🔍 搜索标题", () => {
						this.searchManager.toggle();
					});

					GM_registerMenuCommand("⭐ 收藏当前标题", () => {
						this.bookmarkManager.addBookmarkFromViewport();
					});

					GM_registerMenuCommand("🗂️ 清空本页书签", () => {
						this.bookmarkManager.clearPageBookmarks();
					});

				// === 设置选项 ===
				GM_registerMenuCommand("⚡ 性能模式", () => {
					this.togglePerformanceMode();
				});

				GM_registerMenuCommand("💾 状态记忆", () => {
					this.toggleMemory();
				});

				GM_registerMenuCommand("⌨️ 快捷键", () => {
					this.toggleHotkeys();
				});

				GM_registerMenuCommand("🐛 调试模式", () => {
					this.toggleDebug();
				});

				// === 重置功能 ===
				GM_registerMenuCommand("🔄 重置折叠状态", () => {
					this.resetAllStates();
				});

				GM_registerMenuCommand("🗑️ 清除记忆数据", () => {
					this.clearAllMemory();
				});

				// === 信息帮助 ===
				GM_registerMenuCommand("📊 当前统计", () => {
					this.showStatistics();
				});

				GM_registerMenuCommand("ℹ️ 使用说明", () => {
					this.showHotkeyHelp();
				});

			} catch (e) {
				Logger.warn("[GHCM] 菜单功能不可用:", e);
			}
		}

		toggleMemory() {
			const newState = !CONFIG.memory.enabled;
			CONFIG.memory.enabled = newState;
			GM_setValue("ghcm-memory-enabled", newState);

			const status = newState ? "启用" : "禁用";
			Logger.log(`[GHCM] 状态记忆已${status}`);
			this.collapseManager.showNotification(`💾 状态记忆已${status}`);
			if (newState) {
				this.stateManager.scheduleSave({ force: true });
			} else {
				this.stateManager.cancelScheduledSave();
			}
		}

		toggleHotkeys() {
			const newState = !CONFIG.hotkeys.enabled;
			CONFIG.hotkeys.enabled = newState;
			GM_setValue("ghcm-hotkeys-enabled", newState);

			const status = newState ? "启用" : "禁用";
			Logger.log(`[GHCM] 快捷键已${status}`);
			this.collapseManager.showNotification(`⌨️ 快捷键已${status}`);

			if (newState) {
				// 重新绑定快捷键
				this.hotkeyManager.setupHotkeys();
			} else {
				// 解除绑定,避免重复与多次触发
				this.hotkeyManager.teardownHotkeys();
			}
		}

		toggleVimNav() {
			const newState = !CONFIG.hotkeys.navEnabled;
			CONFIG.hotkeys.navEnabled = newState;
			GM_setValue('ghcm-nav-enabled', newState);

			const status = newState ? '启用' : '禁用';
			Logger.log(`[GHCM] Vim 导航热键已${status}`);
			this.collapseManager.showNotification(`🧭 Vim 导航热键已${status}`);
		}

		toggleShowLevelNumber() {
			CONFIG.ui.showLevelNumber = !CONFIG.ui.showLevelNumber;
			GM_setValue('ghcm-show-level-number', CONFIG.ui.showLevelNumber);
			try { this.styleManager.updateArrowContentOverride(); } catch {}
			this.collapseManager.showNotification(CONFIG.ui.showLevelNumber ? '🔢 显示级别数字' : '🔽 仅显示箭头');
		}

		setColorScheme(name) {
			const scheme = CONFIG.colorSchemes[name];
			if (!scheme) {
				this.collapseManager.showNotification('⚠️ 未找到指定的配色方案');
				return;
			}
			this.styleManager.updateColors(scheme);
			if (name === 'custom') {
				this.collapseManager.showNotification('🎨 已应用自定义配色');
			} else {
				this.collapseManager.showNotification(`🎨 已应用配色:${name}`);
			}
		}

		promptCustomColors() {
			const current = (CONFIG.colorSchemes.custom || CONFIG.colors).join(', ');
			const input = prompt('请输入新的配色(可用逗号或空格分隔,至少 1 个色值)', current);
			if (input === null) return;
			const parts = input.split(/[\s,]+/).map(part => part.trim()).filter(Boolean);
			if (!parts.length) {
				this.collapseManager.showNotification('⚠️ 未输入有效的颜色');
				return;
			}
			while (parts.length < 6) {
				parts.push(parts[parts.length - 1] || parts[0]);
			}
			const colors = parts.slice(0, 6);
			CONFIG.colorSchemes.custom = colors;
			GM_setValue('ghcm-custom-colors', colors);
			this.setColorScheme('custom');
			this.menuManager.refreshMenu();
		}

		promptArrowSize() {
			const current = CONFIG.ui.arrowSize || '0.8em';
			const input = prompt('设置箭头字号(如 0.8em、12px)', current);
			if (input === null) return;
			const value = input.trim();
			if (!value) {
				this.collapseManager.showNotification('⚠️ 请输入有效的尺寸');
				return;
			}
			CONFIG.ui.arrowSize = value;
			this.styleManager.updateArrowSize(value);
			this.collapseManager.showNotification(`🔠 已更新箭头大小:${value}`);
			this.menuManager.refreshMenu();
		}

		toggleDebug() {
			const newState = !CONFIG.debug;
			CONFIG.debug = newState;
			GM_setValue("ghcm-debug-mode", newState);

			const status = newState ? "启用" : "禁用";
			Logger.log(`[GHCM] 调试模式已${status}`);
			this.collapseManager.showNotification(`🐛 调试模式已${status}`);
		}

		togglePerformanceMode() {
			const isPerformanceMode = CONFIG.animation.maxAnimatedElements === 0;
			const newState = !isPerformanceMode;

			if (newState) {
				// 启用性能模式(禁用动画)
				CONFIG.animation.maxAnimatedElements = 0;
				GM_setValue("ghcm-performance-mode", true);
				Logger.log("[GHCM] 已启用性能模式 - 动画已禁用");
				this.collapseManager.showNotification("⚡ 性能模式已启用");
			} else {
				// 禁用性能模式(启用动画)
				CONFIG.animation.maxAnimatedElements = 20;
				GM_setValue("ghcm-performance-mode", false);
				Logger.log("[GHCM] 已禁用性能模式 - 动画已启用");
				this.collapseManager.showNotification("🎬 动画效果已启用");
			}
		}

		clearAllMemory() {
			if (confirm("确定要清除所有页面的折叠状态记忆吗?")) {
				GM_setValue(CONFIG.memory.key, {});
				this.stateManager.clear();
				Logger.log("[GHCM] 已清除所有记忆数据");
				this.collapseManager.showNotification("🗑️ 已清除所有记忆数据");
			}
		}

		showHotkeyHelp() {
			this.helpModal?.show();
		}

		showStatistics() {
			const headers = this.collapseManager.getAllHeaders();
			const collapsed = headers.filter(h => h.classList.contains(CONFIG.classes.collapsed));
			const visible = headers.filter(h =>
				!h.classList.contains(CONFIG.classes.collapsed) &&
				!h.classList.contains(CONFIG.classes.noContent)
			);

			const levelStats = {};
			for (let i = 1; i <= 6; i++) {
				const levelHeaders = headers.filter(h =>
					this.stateManager.getHeaderLevel(h) === i
				);
				if (levelHeaders.length > 0) {
					levelStats[`H${i}`] = {
						total: levelHeaders.length,
						collapsed: levelHeaders.filter(h => h.classList.contains(CONFIG.classes.collapsed)).length
					};
				}
			}

			const levelStatsText = Object.entries(levelStats)
				.map(([level, stats]) =>
					`${level}: ${stats.total}个 (${stats.collapsed}个已折叠)`
				).join(', ');

			const statsContent = `
📊 当前页面统计

📝 标题概况:
• 总计:${headers.length} 个标题
• 已折叠:${collapsed.length} 个
• 可见:${visible.length} 个

📋 级别分布:${levelStatsText || '无标题'}

⚙️ 功能状态:
• 性能模式:${CONFIG.animation.maxAnimatedElements === 0 ? '🟢 启用' : '🔴 禁用'}
• 状态记忆:${CONFIG.memory.enabled ? '🟢 启用' : '🔴 禁用'}
• 快捷键:${CONFIG.hotkeys.enabled ? '🟢 启用' : '🔴 禁用'}
			`.trim();

			alert(statsContent);
		}

		resetAllStates() {
			// 移除所有折叠状态
			DOMUtils.$$(".ghcm-collapsed").forEach(element => {
				element.classList.remove(CONFIG.classes.collapsed);
				try { element.setAttribute('aria-expanded', 'true'); } catch {}
			});

			// 显示所有隐藏的内容
			DOMUtils.$$(".ghcm-hidden-by-parent").forEach(element => {
				element.classList.remove(CONFIG.classes.hiddenByParent);
				element.style.removeProperty('display');
				element.style.opacity = '';
				element.style.transform = '';
			});

			// 清空状态
			this.stateManager.clear();

			Logger.log("[GHCM] 已重置所有折叠状态");
		}
	}

	// 启动应用
	window.ghcmInstance = new GitHubCollapseMarkdown();

})();