ChatGPT, GROK, and DEEPSEEK AI WEB Chat Scroll Navigator Tool(AI网页聊天智能滚动导航工具)

A unified interface for scrolling, navigation in ChatGPT, GROK, and DEEPSEEK AI chats with multiple themes. 为ChatGPT、GROK和DEEPSEEK AI聊天提供统一的滚动导航界面,支持多种主题风格和自定义设置。

目前為 2025-08-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         ChatGPT, GROK, and DEEPSEEK AI WEB Chat Scroll Navigator Tool(AI网页聊天智能滚动导航工具)
// @namespace    http://tampermonkey.net/
// @version      0.23
// @description  A unified interface for scrolling, navigation in ChatGPT, GROK, and DEEPSEEK AI chats with multiple themes. 为ChatGPT、GROK和DEEPSEEK AI聊天提供统一的滚动导航界面,支持多种主题风格和自定义设置。
// @author       Lepturus
// @match        *://chatgpt.com/*
// @match        *://chat.deepseek.com/*
// @match        *://grok.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. CONFIGURATION & STATE --- //

    const CONFIG = {
        platforms: {
            chatgpt: { container: '.flex.basis-auto.flex-col.grow.overflow-hidden div.relative.h-full div.overflow-y-auto' },
            grok: { container: '.scrollbar-gutter-stable' },
            deepseek: { container: '.scrollable' },
            generic: { container: 'html, body' }
        },
        defaults: {
            showAutoScrollBtn: true,
            showSectionNavBtn: true,
            language: 'en',
            themeColor: '#007AFF',
            themeStyle: 'minimal',
            scrollSpeed: 5,
            showProgressIndicator: true,
            alwaysShowButtons: true,
            iconSet: 'minimal'
        },
        iconSets: {
            minimal: {
                settings: '⚙️',      // 齿轮
                section: '📄',       // 文档
                autoscroll: '⏯️',    // 播放/暂停
                scrollTop: '⬆️',     // 向上箭头
                scrollBottom: '⬇️'   // 向下箭头
            },
            colorful: {
                settings: '🎨',      // 调色板
                section: '📖',       // 书本
                autoscroll: '🚀',    // 火箭
                scrollTop: '👆',     // 向上手指
                scrollBottom: '👇'   // 向下手指
            },
            tech: {
                settings: '🔧',      // 扳手
                section: '📊',       // 图表
                autoscroll: '⚡',    // 闪电
                scrollTop: '🔼',     // 三角形上
                scrollBottom: '🔽'   // 三角形下
            },
            forest: {
                settings: '🌿',      // 叶子
                section: '🌳',       // 树
                autoscroll: '🌊',    // 波浪
                scrollTop: '⛰️',     // 山峰
                scrollBottom: '🌱'   // 幼苗
            },
            anime: {
                settings: '✨',      // 星星
                section: '📑',       // 书签
                autoscroll: '🎵',    // 音乐
                scrollTop: '↑',      // 简洁向上箭头
                scrollBottom: '↓'    // 简洁向下箭头
            }
        },
        themes: {
            minimal: {
                name: { en: 'Minimal', zh: '简约风格' },
                bgColor: '#2c2c2e',
                hoverColor: '#444',
                activeColor: 'var(--enh-nav-theme)',
                textColor: 'white',
                shadow: '0 4px 12px rgba(0,0,0,0.3)',
                panelBg: '#1e1e1e',
                panelBorder: '1px solid #e0e0e0',
                selectColor: 'black',
            },
            colorful: {
                name: { en: 'Colorful', zh: '多彩风格' },
                bgColor: 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
                hoverColor: 'linear-gradient(135deg, #ff6a6e 0%, #f8c0b4 100%)',
                activeColor: 'linear-gradient(135deg, #ff4b50 0%, #f6b0a0 100%)',
                textColor: '#7c4dff',
                shadow: '0 8px 16px rgba(124, 77, 255, 0.4)',
                panelBg: 'linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%)',
                panelBorder: '1px solid rgba(124, 77, 255, 0.3)',
                selectColor: 'rgba(255,255,255,0.2)',
            },
            tech: {
                name: { en: 'Tech', zh: '科技风格' },
                bgColor: 'rgba(0, 20, 40, 0.8)',
                hoverColor: 'rgba(0, 100, 200, 0.8)',
                activeColor: 'var(--enh-nav-theme)',
                textColor: '#00e5ff',
                shadow: '0 0 15px rgba(0, 229, 255, 0.7)',
                panelBg: 'rgba(0, 30, 60, 0.95)',
                panelBorder: '1px solid rgba(0, 229, 255, 0.5)',
                selectColor: 'rgba(255,255,255,0.2)',
            },
            forest: {
                name: { en: 'Forest', zh: '森林风格' },
                bgColor: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
                hoverColor: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
                activeColor: 'linear-gradient(135deg, #2ed573 0%, #1ed6c8 100%)',
                textColor: '#004d40',
                shadow: '0 6px 14px rgba(0, 77, 64, 0.4)',
                panelBg: 'linear-gradient(135deg, #7bc6cc 0%, #be93c5 100%)',
                panelBorder: '1px solid rgba(0, 77, 64, 0.3)',
                selectColor: 'rgba(255,255,255,0.2)',
            },
            anime: {
                name: { en: 'Anime', zh: '二次元风格' },
                bgColor: 'linear-gradient(135deg, #ffcbf2 0%, #e0c3fc 100%)',
                hoverColor: 'linear-gradient(135deg, #ffafcc 0%, #cdb4db 100%)',
                activeColor: 'linear-gradient(135deg, #ff85a1 0%, #b8a1d9 100%)',
                textColor: '#ff006e',
                shadow: '0 8px 20px rgba(255, 0, 110, 0.4)',
                panelBg: 'linear-gradient(135deg, #f6d5f7 0%, #fbe9d7 100%)',
                panelBorder: '1px solid rgba(255, 0, 110, 0.3)',
                selectColor: 'rgba(255,255,255,0.2)',
            }
        },
        i18n: {
            zh: {
                // Main Titles & Tooltips
                mainTitle: '导航',
                autoScroll: '自动滚动',
                pauseAutoScroll: '暂停滚动',
                sectionNav: '章节导航',
                settings: '设置',
                scrollToTop: '滚动到顶部',
                scrollToBottom: '滚动到底部',
                // Settings Panel
                settingsTitle: '脚本设置',
                showHideButtons: '显示/隐藏功能按钮',
                showReadingButton: '阅读模式',
                showAutoScrollButton: '自动滚动',
                showSectionNavButton: '章节导航',
                showProgressIndicator: '显示进度指示器',
                alwaysShowButtons: '常驻显示按钮',
                language: '语言',
                themeColor: '主题颜色',
                themeStyle: '主题风格',
                scrollSpeed: '自动滚动速度',
                speedValue: (val) => ({1: '很慢', 3: '慢', 5: '中等', 8: '快', 10: '很快'})[val] || '自定义',
                // Section Nav Panel
                navTitle: '页面导航',
                noHeadings: '未找到章节标题。',
                iconSet: '图标风格',
                iconSetMinimal: '简约',
                iconSetColorful: '多彩',
                iconSetTech: '科技',
                iconSetForest: '森林',
                iconSetAnime: 'ACG'
            },
            en: {
                // Main Titles & Tooltips
                mainTitle: 'Navigate',
                autoScroll: 'Auto-Scroll',
                pauseAutoScroll: 'Pause Auto-Scroll',
                sectionNav: 'Section Nav',
                settings: 'Settings',
                scrollToTop: 'Scroll to Top',
                scrollToBottom: 'Scroll to Bottom',
                // Settings Panel
                settingsTitle: 'Script Settings',
                showHideButtons: 'Show/Hide Feature Buttons',
                showReadingButton: 'Reading Mode',
                showAutoScrollButton: 'Auto-Scroll',
                showSectionNavButton: 'Section Nav',
                showProgressIndicator: 'Show Progress Indicator',
                alwaysShowButtons: 'Always Show Buttons',
                language: 'Language',
                themeColor: 'Theme Color',
                themeStyle: 'Theme Style',
                scrollSpeed: 'Auto-Scroll Speed',
                speedValue: (val) => ({1: 'Very Slow', 3: 'Slow', 5: 'Medium', 8: 'Fast', 10: 'Very Fast'})[val] || 'Custom',
                // Section Nav Panel
                navTitle: 'Section Navigation',
                noHeadings: 'No section headings found.',
                iconSet: 'Icon Style',
                iconSetMinimal: 'Minimal',
                iconSetColorful: 'Colorful',
                iconSetTech: 'Tech',
                iconSetForest: 'Forest',
                iconSetAnime: 'Anime'
            }
        }
    };

    let STATE = {
        isInitialized: false,
        isAutoScrolling: false,
        autoScrollInterval: null,
        scrollContainer: null,
        settings: {},
        currentPlatform: 'generic',
    };

    // --- 2. CORE LOGIC --- //

    /** Gets a setting value, falling back to default */
    function getSetting(key) {
        return GM_getValue(key, CONFIG.defaults[key]);
    }

    /** Loads all settings into the STATE object */
    function loadSettings() {
        STATE.settings = {
            showAutoScrollBtn: getSetting('showAutoScrollBtn'),
            showSectionNavBtn: getSetting('showSectionNavBtn'),
            language: getSetting('language'),
            themeColor: getSetting('themeColor'),
            iconSet: getSetting('iconSet'),
            themeStyle: getSetting('themeStyle'),
            scrollSpeed: getSetting('scrollSpeed'),
            showProgressIndicator: getSetting('showProgressIndicator'),
            alwaysShowButtons: getSetting('alwaysShowButtons'),
        };
    }

    /** Finds the correct scrollable element on the page */
    function findScrollContainer() {
        const { host } = window.location;
        let platform = 'generic';
        if (host.includes('chatgpt.com')) platform = 'chatgpt';
        else if (host.includes('grok.com')) platform = 'grok';
        else if (host.includes('deepseek.com')) platform = 'deepseek';
        STATE.currentPlatform = platform;

        const selector = CONFIG.platforms[platform].container;
        if (platform === 'deepseek') {
             const containers = document.querySelectorAll(selector);
             return containers.length > 1 ? containers[1] : containers[0];
        }
        return platform === 'generic' ? (document.scrollingElement || document.documentElement) : document.querySelector(selector);
    }

    /** Handles the main scroll button click */
    function handleScrollClick() {
        if (!STATE.scrollContainer) return;
        const isNearTop = STATE.scrollContainer.scrollTop < 100;
        STATE.scrollContainer.scrollTo({
            top: isNearTop ? STATE.scrollContainer.scrollHeight : 0,
            behavior: 'smooth'
        });
    }

    function updateIcons() {
        const iconSet = CONFIG.iconSets[STATE.settings.iconSet] || CONFIG.iconSets.minimal;

        const settingsBtn = document.getElementById('enh-nav-settings-btn');
        if (settingsBtn) settingsBtn.textContent = iconSet.settings;

        const sectionBtn = document.getElementById('enh-nav-section-btn');
        if (sectionBtn) sectionBtn.textContent = iconSet.section;

        const autoscrollBtn = document.getElementById('enh-nav-autoscroll-btn');
        if (autoscrollBtn) autoscrollBtn.textContent = iconSet.autoscroll;

        updateScrollArrow();
    }


    function updateScrollArrow() {
        if (!STATE.scrollContainer) return;

        const arrowEl = document.getElementById('enh-nav-scroll-arrow');
        const iconSet = CONFIG.iconSets[STATE.settings.iconSet] || CONFIG.iconSets.minimal;

        const isNearTop = STATE.scrollContainer.scrollTop < 100;
        if (arrowEl) arrowEl.textContent = isNearTop ? iconSet.scrollBottom : iconSet.scrollTop;
    }


    /** Toggles Auto-Scroll */
    function toggleAutoScroll() {
        STATE.isAutoScrolling = !STATE.isAutoScrolling;
        const btn = document.getElementById('enh-nav-autoscroll-btn');
        if (btn) btn.classList.toggle('active', STATE.isAutoScrolling);

        if (STATE.isAutoScrolling) {
            STATE.autoScrollInterval = setInterval(() => {
                if (!STATE.scrollContainer) return;
                const atBottom = STATE.scrollContainer.scrollTop + STATE.scrollContainer.clientHeight >= STATE.scrollContainer.scrollHeight - 2;
                if (atBottom) {
                    toggleAutoScroll(); // Stop scrolling
                } else {
                    // 修改这里:确保最小滚动距离为1像素
                    const scrollAmount = Math.max(1, STATE.settings.scrollSpeed / 5);
                    STATE.scrollContainer.scrollBy(0, scrollAmount);
                }
            }, 20);
        } else {
            clearInterval(STATE.autoScrollInterval);
        }
        updateUIText(); // Update title
    }

    /** Updates the UI text based on the current language without reloading */
    function updateUIText() {
        const lang = STATE.settings.language;
        const translations = CONFIG.i18n[lang];

        document.querySelectorAll('[data-i18n-key]').forEach(el => {
            const key = el.dataset.i18nKey;
            const prop = el.dataset.i18nProp || 'textContent';
            if (translations[key]) el[prop] = translations[key];
        });

        const autoScrollBtn = document.getElementById('enh-nav-autoscroll-btn');
        if (autoScrollBtn) autoScrollBtn.title = STATE.isAutoScrolling ? translations.pauseAutoScroll : translations.autoScroll;

        const mainBtn = document.getElementById('enh-nav-main-btn');
        if (mainBtn && STATE.scrollContainer) {
            const isNearTop = STATE.scrollContainer.scrollTop < 100;
            mainBtn.title = isNearTop ? translations.scrollToBottom : translations.scrollToTop;
        }

        // Update speed value text
        const speedValueEl = document.getElementById('enh-nav-speed-value');
        if (speedValueEl) {
            speedValueEl.textContent = translations.speedValue(STATE.settings.scrollSpeed);
        }
    }


    // --- 3. UI CREATION & UPDATES --- //

    /** Injects all CSS into the page */
    function injectStyles() {
        const theme = CONFIG.themes[STATE.settings.themeStyle] || CONFIG.themes.minimal;

        GM_addStyle(`
        :root {
            --enh-nav-theme: ${STATE.settings.themeColor};
            --enh-nav-bg: ${theme.bgColor};
            --enh-nav-hover: ${theme.hoverColor};
            --enh-nav-active: ${theme.activeColor};
            --enh-nav-text: ${theme.textColor};
            --enh-nav-shadow: ${theme.shadow};
            --enh-nav-panel-bg: ${theme.panelBg};
            --enh-nav-panel-border: ${theme.panelBorder};
            --enh-nav-select: ${theme.selectColor};
        }

        #enh-nav-container {
            position: fixed;
            right: 15px;
            bottom: 15px;
            z-index: 99999;
            display: flex !important;
            flex-direction: column;
            align-items: center;
            transition: opacity 0.3s ease;
        }

        #enh-nav-container.hidden {
            opacity: 0;
            pointer-events: none;
        }

        #enh-nav-container:hover,
        #enh-nav-container.always-visible {
            opacity: 1;
            pointer-events: auto;
        }


        #enh-nav-hover-area {
            position: fixed;
            right: 0;
            bottom: 0;
            width: 60px;
            height: 60px;
            z-index: 99998;
        }

        .enh-nav-btn {
            background: var(--enh-nav-bg);
            color: var(--enh-nav-text);
            border-radius: 50%;
            width: 48px;
            height: 48px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 24px;
            cursor: pointer;
            border: none;
            box-shadow: var(--enh-nav-shadow);
            transition: all 0.3s ease;
            user-select: none;
        }

        .enh-nav-btn:hover {
            transform: scale(1.1);
            background: var(--enh-nav-hover);
        }

        .enh-nav-btn.active {
            background: var(--enh-nav-active);
        }

        #enh-nav-main-btn {
            position: relative;
            font-weight: bold;
        }

        #enh-nav-progress-circle {
            position: absolute;
            width: 100%;
            height: 100%;
            transform: rotate(-90deg);
        }

        #enh-nav-progress-circle-bar {
            stroke: var(--enh-nav-theme);
            stroke-width: 4;
            fill: transparent;
            transition: stroke-dashoffset 0.1s linear;
        }

        #enh-nav-progress-text {
            font-size: 12px;
            font-weight: bold;
            color: var(--enh-nav-text);
        }

        #enh-nav-menu {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-bottom: 10px;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            transform-origin: bottom center;
            transform: scale(0.8) translateY(20px);
            opacity: 0;
            pointer-events: none;
        }

        #enh-nav-container:hover #enh-nav-menu {
            transform: scale(1) translateY(0);
            opacity: 1;
            pointer-events: auto;
        }

        .enh-nav-menu-btn {
            width: 40px;
            height: 40px;
            font-size: 16px;
            margin-top: 10px;
        }

        .enh-nav-panel {
            position: fixed;
            right: 80px;
            bottom: 15px;
            width: 210px;
            background: var(--enh-nav-panel-bg);
            color: var(--enh-nav-text);
            border-radius: 16px;
            padding: 11px;
            box-shadow: var(--enh-nav-shadow);
            border: var(--enh-nav-panel-border);
            opacity: 0;
            transform: translateX(20px);
            transition: all 0.3s ease;
            pointer-events: none;
            z-index: 99998;
            backdrop-filter: blur(10px);
        }

        .enh-nav-panel.visible {
            opacity: 1;
            transform: translateX(0);
            pointer-events: auto;
        }

        .enh-nav-panel h3 {
            margin: 0 0 11px;
            font-size: 15px;
            border-bottom: 1px solid rgba(255,255,255,0.2);
            padding-bottom: 10px;
            text-align: center;
        }

        .enh-nav-panel .close-btn {
            position: absolute;
            top: 15px;
            right: 15px;
            cursor: pointer;
            font-size: 16px;
            opacity: 0.7;
            transition: all 0.2s;
        }

        .enh-nav-panel .close-btn:hover {
            opacity: 1;
            transform: scale(1.1);
        }

        .enh-nav-setting-row {
            margin-bottom: 9px;
            padding: 6px 0;
            border-bottom: 1px solid rgba(255,255,255,0.1);
        }

        .enh-nav-setting-row label {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-size: 14px;
            width: 100%;
        }

        .enh-nav-panel input[type=checkbox] {
            transform: scale(1.2);
            accent-color: var(--enh-nav-theme);
        }

        .enh-nav-panel input[type=color] {
            width: 40px;
            height: 25px;
            border: none;
            background: none;
            padding: 0;
            cursor: pointer;
        }

        .enh-nav-panel input[type=range] {
            width: 100%;
            margin-top: 5px;
            accent-color: var(--enh-nav-theme);
        }

        .enh-nav-panel select {
            background: var(--enh-nav-select);
            color: var(--enh-nav-text);
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 8px;
            padding: 5px 10px;
            cursor: pointer;
        }

        #enh-nav-section-panel {
            width: 350px;
            height: 500px;
            max-height: 70vh;
            right: 90px;
            transform: translateX(30px) scale(0.95);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        #enh-nav-section-panel.visible {
            transform: translateX(0) scale(1);
        }

        #enh-nav-section-panel ul {
            list-style: none;
            padding: 0;
            margin: 0;
            max-height: calc(100% - 60px);
            overflow-x: hidden;
            overflow-y: auto;
        }

        #enh-nav-section-panel ul::-webkit-scrollbar {
            width: 8px;
        }

        #enh-nav-section-panel ul::-webkit-scrollbar-track {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
        }

        #enh-nav-section-panel ul::-webkit-scrollbar-thumb {
            background: var(--enh-nav-theme);
            border-radius: 4px;
        }

        #enh-nav-section-panel ul::-webkit-scrollbar-thumb:hover {
            background: var(--enh-nav-active);
        }

        #enh-nav-section-panel li {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            padding: 12px 15px;
            cursor: pointer;
            border-radius: 10px;
            font-size: 14px;
            margin-bottom: 8px;
            transition: all 0.3s ease;
            background: rgba(255,255,255,0.05);
            opacity: 0;
            transform: translateY(10px);
        }

        #enh-nav-section-panel.visible li {
            opacity: 1;
            transform: translateY(0);
        }

        #enh-nav-section-panel li {
            transition-delay: calc(var(--index, 0) * 0.05s);
        }

        #enh-nav-section-panel li:hover {
            background-color: var(--enh-nav-theme);
            color: white;
            transform: translateX(5px) translateY(0);
        }

        #enh-nav-section-panel li[data-i18n-key="noHeadings"] {
            text-align: center;
            padding: 30px;
            font-size: 16px;
            opacity: 0.7;
            white-space: normal;
            transform: none;
        }

        .theme-preview {
            display: inline-block;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            margin-right: 8px;
            vertical-align: middle;
        }

        .speed-container {
            display: flex;
            flex-direction: column;
            margin-top: 5px;
        }
        `);
    }

    /** Creates the main UI elements */
    function createUI() {
        const container = document.createElement('div');
        container.id = 'enh-nav-container';

        if (!STATE.settings.alwaysShowButtons) {
            container.classList.add('hidden');
        } else {
            container.classList.add('always-visible');
        }

        const hoverArea = document.createElement('div');
        hoverArea.id = 'enh-nav-hover-area';
        document.body.appendChild(hoverArea);

        const progressRadius = 22;
        const progressCircumference = 2 * Math.PI * progressRadius;

        container.innerHTML = `
            <!-- Menu Buttons (appear on hover) -->
            <div id="enh-nav-menu">
                <button id="enh-nav-settings-btn" class="enh-nav-btn enh-nav-menu-btn" title="Settings" data-i18n-prop="title" data-i18n-key="settings">⚙️</button>
                <button id="enh-nav-section-btn" class="enh-nav-btn enh-nav-menu-btn" title="Section Nav" data-i18n-prop="title" data-i18n-key="sectionNav">📄</button>
                <button id="enh-nav-autoscroll-btn" class="enh-nav-btn enh-nav-menu-btn" title="Auto-Scroll" data-i18n-prop="title" data-i18n-key="autoScroll">⏯️</button>
            </div>

            <!-- Main Scroll Button -->
            <button id="enh-nav-main-btn" class="enh-nav-btn" title="Scroll to Bottom">
                <span id="enh-nav-scroll-arrow">⬇️</span>
                <span id="enh-nav-progress-text">0%</span>
                <svg id="enh-nav-progress-circle" width="48" height="48">
                    <circle id="enh-nav-progress-circle-bar" r="${progressRadius}" cx="24" cy="24" stroke-dasharray="${progressCircumference}" stroke-dashoffset="${progressCircumference}"></circle>
                </svg>
            </button>

            <!-- Settings Panel -->
            <div id="enh-nav-settings-panel" class="enh-nav-panel">
                <span class="close-btn">✖️</span>
                <h3 data-i18n-key="settingsTitle">Script Settings</h3>
                <div class="enh-nav-setting-row">
                    <strong data-i18n-key="showHideButtons">Show/Hide Buttons</strong>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="showAutoScrollButton">Auto-Scroll</span><input type="checkbox" id="enh-nav-toggle-autoscroll"></label>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="showSectionNavButton">Section Nav</span><input type="checkbox" id="enh-nav-toggle-section"></label>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="showProgressIndicator">Show Progress Indicator</span><input type="checkbox" id="enh-nav-toggle-progress"></label>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="alwaysShowButtons">Always Show Buttons</span><input type="checkbox" id="enh-nav-toggle-always-show"></label>
                </div>
                <hr style="border-color: rgba(255,255,255,0.2); margin: 15px 0;">
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="language">Language</span>
                        <select id="enh-nav-lang-select">
                            <option value="en">English</option>
                            <option value="zh">中文</option>
                        </select>
                    </label>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="themeStyle">Theme Style</span>
                    <select id="enh-nav-theme-style">
                    ${Object.entries(CONFIG.themes).map(([id, theme]) =>
                        `<option value="${id}">${theme.name[STATE.settings.language] || theme.name.en}</option>`
                    ).join('')}
                    </select>
                    </label>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="iconSet">Icon Style</span>
                        <select id="enh-nav-icon-set">
                            <option value="minimal" data-i18n-key="iconSetMinimal">Minimal</option>
                            <option value="colorful" data-i18n-key="iconSetColorful">Colorful</option>
                            <option value="tech" data-i18n-key="iconSetTech">Tech</option>
                            <option value="forest" data-i18n-key="iconSetForest">Forest</option>
                            <option value="anime" data-i18n-key="iconSetAnime">Anime</option>
                        </select>
                    </label>
                </div>
                <div class="enh-nav-setting-row">
                    <label><span data-i18n-key="themeColor">Theme Color</span><input type="color" id="enh-nav-theme-color"></label>
                </div>
                <div class="enh-nav-setting-row">
                     <label><span data-i18n-key="scrollSpeed">Scroll Speed</span> <span id="enh-nav-speed-value"></span></label>
                     <div class="speed-container">
                         <input type="range" id="enh-nav-scroll-speed" min="1" max="10" step="1">
                     </div>
                </div>
            </div>

            <!-- Section Nav Panel -->
            <div id="enh-nav-section-panel" class="enh-nav-panel">
                 <span class="close-btn">✖️</span>
                 <h3 data-i18n-key="navTitle">Page Navigation</h3>
                 <ul id="enh-nav-section-list"></ul>
            </div>
        `;
        document.body.appendChild(container);

        // Apply initial visibility from settings
        document.getElementById('enh-nav-autoscroll-btn').style.display = STATE.settings.showAutoScrollBtn ? 'flex' : 'none';
        document.getElementById('enh-nav-section-btn').style.display = STATE.settings.showSectionNavBtn ? 'flex' : 'none';

        // Apply initial progress indicator setting
        updateProgressIndicatorVisibility();
    }

    /** Updates the circular progress bar and scroll direction arrow */
    function updateScrollState() {
        if (!STATE.scrollContainer) return;
        const { scrollTop, scrollHeight, clientHeight } = STATE.scrollContainer;
        const scrollableHeight = scrollHeight - clientHeight;

        const arrowEl = document.getElementById('enh-nav-scroll-arrow');
        const textEl = document.getElementById('enh-nav-progress-text');
        const circleBar = document.getElementById('enh-nav-progress-circle-bar');

        if (scrollableHeight <= 0) {
            if (arrowEl) arrowEl.style.display = 'block';
            if (textEl) textEl.style.display = 'none';
            if (arrowEl) arrowEl.textContent = '↕️';
            return;
        }

        const percent = Math.min(Math.round((scrollTop / scrollableHeight) * 100), 100);
        const isNearTop = scrollTop < 100;
        const isNearBottom = scrollTop > scrollableHeight - 100;

        // Show arrow or percentage based on settings
        const showProgress = STATE.settings.showProgressIndicator;

        if (isNearTop || isNearBottom || !showProgress) {
             if (arrowEl) arrowEl.style.display = 'block';
             if (textEl) textEl.style.display = 'none';
             if (arrowEl) arrowEl.textContent = isNearTop ? '⬇️' : '⬆️';
        } else {
             if (arrowEl) arrowEl.style.display = 'none';
             if (textEl) textEl.style.display = 'block';
             if (textEl) textEl.textContent = `${percent}%`;
        }

        // Update progress circle
        if (circleBar) {
            const radius = circleBar.r.baseVal.value;
            const circumference = 2 * Math.PI * radius;
            const offset = circumference - (percent / 100) * circumference;
            circleBar.style.strokeDashoffset = offset;
        }

        // Update main button title for accessibility
        updateScrollArrow();
        updateUIText();
    }

    /** Updates progress indicator visibility based on setting */
    function updateProgressIndicatorVisibility() {
        const textEl = document.getElementById('enh-nav-progress-text');
        const circleBar = document.getElementById('enh-nav-progress-circle-bar');
        const showProgress = STATE.settings.showProgressIndicator;

        if (textEl && circleBar) {
            textEl.style.display = showProgress ? 'block' : 'none';
            circleBar.style.display = showProgress ? 'block' : 'none';
        }
        updateScrollState(); // Refresh the display
    }

    /** Populates the section navigation panel */
  /** Populates the section navigation panel */
function updateSectionNav() {
    const list = document.getElementById('enh-nav-section-list');
    const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); // 包含所有标题级别

    list.innerHTML = '';

    const unwantedTexts = [
        '脚本设置', 'Script Settings',
        '页面导航', 'Section Navigation',
        'Upgrade to SuperGrok',
        'Chat history', 'Chats','You said:', 'ChatGPT said:',
        '导航', 'Navigate',
        '设置', 'Settings'
    ];

    if (headings.length === 0) {
        list.innerHTML = `<li data-i18n-key="noHeadings" style="opacity: 0.6; text-align: center; padding: 30px;">
            ${CONFIG.i18n[STATE.settings.language].noHeadings}
        </li>`;
        return;
    }

    let hasValidHeadings = false;
    let index = 0;
    headings.forEach(h => {
        const text = h.textContent.trim();
        const id = h.id || h.textContent.toLowerCase().replace(/\s+/g, '-');

        if (text === '' || unwantedTexts.includes(text)) return;

        hasValidHeadings = true;

        const item = document.createElement('li');
        item.textContent = text;

        const level = parseInt(h.tagName.substring(1));
        item.style.paddingLeft = `${(level - 1) * 15 + 15}px`;
        item.style.fontSize = `${18 - level * 2}px`;
        item.style.fontWeight = level <= 2 ? 'bold' : 'normal';

        item.style.setProperty('--index', index);
        index++;
        item.addEventListener('mouseenter', () => {
            h.style.transition = 'background-color 0.2s';
            h.style.backgroundColor = 'var(--enh-nav-theme)';
        });

        item.addEventListener('mouseleave', () => {
            setTimeout(() => {
                h.style.backgroundColor = '';
            }, 200);
        });
        item.onclick = () => {
            const element = document.getElementById(id) || h;
            element.scrollIntoView({
                behavior: 'smooth',
                block: 'start'
            });

            const originalBg = element.style.backgroundColor;
            element.style.backgroundColor = 'var(--enh-nav-theme)';
            element.style.transition = 'background-color 0.3s';

            setTimeout(() => {
                element.style.backgroundColor = originalBg;
            }, 2000);

            document.getElementById('enh-nav-section-panel').classList.remove('visible');
        };

        list.appendChild(item);
    });

    if (!hasValidHeadings) {
        list.innerHTML = `<li data-i18n-key="noHeadings" style="opacity: 0.6; text-align: center; padding: 30px;">
            ${CONFIG.i18n[STATE.settings.language].noHeadings}
        </li>`;
    }
}
    /** Toggles the visibility of a panel */
    function togglePanel(panelId, forceState) {
        const panel = document.getElementById(panelId);
        if (!panel) return;
        // Hide other panels
        document.querySelectorAll('.enh-nav-panel').forEach(p => {
            if (p.id !== panelId) p.classList.remove('visible');
        });
        // Toggle current panel
        const isVisible = panel.classList.toggle('visible', forceState);
        // If we are opening the section nav, refresh its content
        if (isVisible && panelId === 'enh-nav-section-panel') {
            updateSectionNav();
        }
    }

    /** Applies the selected theme style */
    function applyThemeStyle(themeId) {
        STATE.settings.themeStyle = themeId;
        GM_setValue('themeStyle', themeId);
        injectStyles(); // Re-inject styles with the new theme
    }


    // --- 4. EVENT LISTENERS --- //

    /** Binds all event listeners to the UI */
    function attachEventListeners() {
        // Main Actions
        document.getElementById('enh-nav-main-btn').addEventListener('click', handleScrollClick);
        document.getElementById('enh-nav-autoscroll-btn').addEventListener('click', toggleAutoScroll);

        // Panel Toggles
        document.getElementById('enh-nav-settings-btn').addEventListener('click', () => togglePanel('enh-nav-settings-panel'));
        document.getElementById('enh-nav-section-btn').addEventListener('click', () => togglePanel('enh-nav-section-panel'));

        // Close Panel Buttons
        document.querySelectorAll('.enh-nav-panel .close-btn').forEach(btn => {
            btn.addEventListener('click', (e) => e.currentTarget.parentElement.classList.remove('visible'));
        });

        // Settings Controls
        const settingsMap = {
            'enh-nav-toggle-autoscroll': { key: 'showAutoScrollBtn', target: '#enh-nav-autoscroll-btn', type: 'toggle' },
            'enh-nav-toggle-section': { key: 'showSectionNavBtn', target: '#enh-nav-section-btn', type: 'toggle' },
            'enh-nav-toggle-progress': { key: 'showProgressIndicator', type: 'value', callback: updateProgressIndicatorVisibility },
            'enh-nav-icon-set': {
                key: 'iconSet',
                type: 'value',
                callback: updateIcons
            },
            'enh-nav-toggle-always-show': {
                key: 'alwaysShowButtons',
                type: 'toggle',
                callback: (value) => {
                    const container = document.getElementById('enh-nav-container');
                    const hoverArea = document.getElementById('enh-nav-hover-area');

                    if (value) {
                        container.classList.add('always-visible');
                        container.classList.remove('hidden');
                        if (hoverArea) hoverArea.style.display = 'none';
                    } else {
                        container.classList.remove('always-visible');
                        container.classList.add('hidden');
                        if (hoverArea) hoverArea.style.display = 'block';
                    }
                }
            },
            'enh-nav-lang-select': { key: 'language', type: 'value', callback: updateUIText },
            'enh-nav-theme-style': { key: 'themeStyle', type: 'value', callback: applyThemeStyle },
            'enh-nav-theme-color': { key: 'themeColor', type: 'value', callback: (value) => {
                document.documentElement.style.setProperty('--enh-nav-theme', value);
                updateUIText();
            } },
            'enh-nav-scroll-speed': { key: 'scrollSpeed', type: 'value', callback: updateUIText },
        };

        for (const [id, config] of Object.entries(settingsMap)) {
            const el = document.getElementById(id);
            if (!el) continue;

            const eventType = (el.type === 'checkbox' || el.tagName === 'SELECT') ? 'change' : 'input';

            // Set initial value
            if (el.type === 'checkbox') el.checked = STATE.settings[config.key];
            else el.value = STATE.settings[config.key];

            // Add listener
            el.addEventListener(eventType, (e) => {
                const value = (e.target.type === 'checkbox') ? e.target.checked : e.target.value;
                GM_setValue(config.key, value);
                STATE.settings[config.key] = value;

                // Apply change instantly
                if (config.type === 'toggle' && config.target) {
                    document.querySelector(config.target).style.display = value ? 'flex' : 'none';
                }
                if (config.callback) {
                    config.callback(value);
                }
            });
        }

        const hoverArea = document.getElementById('enh-nav-hover-area');
        const container = document.getElementById('enh-nav-container');

        if (hoverArea && container) {
            hoverArea.addEventListener('mouseenter', () => {
                if (!STATE.settings.alwaysShowButtons) {
                    container.classList.remove('hidden');
                }
            });

            container.addEventListener('mouseleave', () => {
                if (!STATE.settings.alwaysShowButtons) {
                    container.classList.add('hidden');
                }
            });
        }
    }


    // --- 5. INITIALIZATION --- //

    /** Main function to start the script */
    function init() {
        if (document.getElementById('enh-nav-container')) return;

        STATE.scrollContainer = findScrollContainer();
        if (!STATE.scrollContainer) {
            setTimeout(init, 1000); // Retry if container not found yet
            return;
        }

        loadSettings();
        injectStyles();
        updateIcons();
        createUI();
        attachEventListeners();
        updateUIText();
        updateScrollState();

        const scrollTarget = (STATE.currentPlatform === 'generic') ? window : STATE.scrollContainer;
        scrollTarget.addEventListener('scroll', updateScrollState, { passive: true });

        const observer = new MutationObserver(() => {
             const currentContainer = findScrollContainer();
             if (STATE.scrollContainer !== currentContainer) {
                 STATE.scrollContainer = currentContainer;
                 const newTarget = (STATE.currentPlatform === 'generic') ? window : STATE.scrollContainer;
                 newTarget.addEventListener('scroll', updateScrollState, { passive: true });
                 updateScrollState();
             }
        });
        observer.observe(document.body, { childList: true, subtree: true });

        console.log("Enhanced AI Chat Scroll Navigator initialized.");
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址