2libra-enhance

2libra.com 论坛增强:帖子快速查看、智能返回顶部

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         2libra-enhance
// @namespace    http://tampermonkey.net/
// @version      1.6.4
// @description  2libra.com 论坛增强:帖子快速查看、智能返回顶部
// @author       twocold0451
// @homepage     https://github.com/twocold0451/2libra-enhance
// @supportURL   https://github.com/twocold0451/2libra-enhance/issues
// @match        https://2libra.com/*
// @license MIT
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_unregisterMenuCommand
// @grant        GM_addValueChangeListener
// @require https://github.com/PRO-2684/GM_config/releases/download/v1.2.2/config.min.js#md5=c45f9b0d19ba69bb2d44918746c4d7ae
// ==/UserScript==

(function() {
    'use strict';

    // 初始化日志
    console.log('2libra-enhance Script: Loaded and running...');

    // 配置
    const CONFIG = {
        btnText: '快速查看',
        modalId: 'libra-quick-view-modal',
        iframeId: 'libra-quick-view-iframe',
        settingsKey: 'libra-click-title-quick-view',
        settingsModalId: 'libra-settings-modal'
    };

    // 设置管理 - 将在GM_config初始化后设置
    let Settings;

    // 样式注入
    const style = document.createElement('style');
    style.textContent = `
        /* SpinKit CSS */
        @keyframes sk-chase {
          100% { transform: rotate(360deg); }
        }

        .sk-chase {
          width: 40px;
          height: 40px;
          position: relative;
          animation: sk-chase 2.0s infinite linear both;
        }

        .sk-chase-dot {
          width: 100%;
          height: 100%;
          position: absolute;
          left: 0;
          top: 0;
          animation: sk-chase 2.0s infinite ease-in-out both;
        }

        .sk-chase-dot:before {
          content: '';
          display: block;
          width: 25%;
          height: 25%;
          background-color: var(--color-primary, #4a00ff);
          border-radius: 100%;
          animation: sk-chase-dot 2.0s infinite ease-in-out both;
        }

        .sk-chase-dot:nth-child(1) { animation-delay: -1.1s; }
        .sk-chase-dot:nth-child(2) { animation-delay: -1.0s; }
        .sk-chase-dot:nth-child(3) { animation-delay: -0.9s; }
        .sk-chase-dot:nth-child(4) { animation-delay: -0.8s; }
        .sk-chase-dot:nth-child(5) { animation-delay: -0.7s; }
        .sk-chase-dot:nth-child(6) { animation-delay: -0.6s; }

        @keyframes sk-chase-dot {
          80%, 100% {
            transform: rotate(360deg);
          }
        }

        /* 脉冲动画 */
        @keyframes sk-pulse {
          0% { opacity: 1; }
          50% { opacity: 0.5; }
          100% { opacity: 1; }
        }

        .sk-pulse {
          width: 40px;
          height: 40px;
          background-color: var(--color-primary, #4a00ff);
          border-radius: 50%;
          animation: sk-pulse 1.5s infinite ease-in-out;
        }

        /* 波纹动画 */
        @keyframes sk-ripple {
          0% { transform: scale(0); opacity: 1; }
          100% { transform: scale(1); opacity: 0; }
        }

        .sk-ripple {
          width: 40px;
          height: 40px;
          position: relative;
        }

        .sk-ripple:before,
        .sk-ripple:after {
          content: '';
          position: absolute;
          border: 2px solid var(--color-primary, #4a00ff);
          border-radius: 50%;
          animation: sk-ripple 1.5s infinite;
        }

        .sk-ripple:after {
          animation-delay: 0.5s;
        }

        /* 旋转方块 */
        @keyframes sk-rotate {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }

        .sk-rotate {
          width: 40px;
          height: 40px;
          border: 3px solid rgba(74, 0, 255, 0.3);
          border-top: 3px solid var(--color-primary, #4a00ff);
          border-radius: 50%;
          animation: sk-rotate 1.2s infinite linear;
        }

        /* 弹跳动画 */
        @keyframes sk-bounce {
          0%, 80%, 100% { transform: scale(0); }
          10% { transform: scale(1.0); }
          50% { transform: scale(1.0); }
        }

        .sk-bounce {
          width: 40px;
          height: 40px;
          position: relative;
        }

        .sk-bounce:before,
        .sk-bounce:after {
          content: '';
          position: absolute;
          width: 100%;
          height: 100%;
          border-radius: 50%;
          background-color: var(--color-primary, #4a00ff);
          animation: sk-bounce 1.4s infinite ease-in-out both;
        }

        .sk-bounce:after {
          animation-delay: -0.16s;
        }

        /* 波浪动画 */
        @keyframes sk-wave {
          0% { transform: rotate(0deg); }
          10% { transform: rotate(14deg); }
          20% { transform: rotate(-8deg); }
          30% { transform: rotate(14deg); }
          40% { transform: rotate(-4deg); }
          50% { transform: rotate(10deg); }
          60% { transform: rotate(0deg); }
          100% { transform: rotate(0deg); }
        }

        .sk-wave {
          width: 40px;
          height: 40px;
          position: relative;
        }

        .sk-wave:before,
        .sk-wave:after {
          content: '';
          position: absolute;
          top: 0;
          width: 100%;
          height: 100%;
          border-radius: 50%;
          background-color: var(--color-primary, #4a00ff);
          animation: sk-wave 1.3s infinite ease-in-out;
        }

        .sk-wave:after {
          animation-delay: -0.3s;
        }

        /* 方块网格动画 */
        @keyframes sk-cube {
          0%, 70%, 100% { transform: scale3D(1, 1, 1); }
          35% { transform: scale3D(0, 0, 1); }
        }

        .sk-cube-grid {
          width: 40px;
          height: 40px;
        }

        .sk-cube-grid .sk-cube {
          width: 33%;
          height: 33%;
          background-color: var(--color-primary, #4a00ff);
          float: left;
          animation: sk-cube 1.3s infinite ease-in-out;
        }

        .sk-cube-grid .sk-cube:nth-child(1) { animation-delay: 0.2s; }
        .sk-cube-grid .sk-cube:nth-child(2) { animation-delay: 0.3s; }
        .sk-cube-grid .sk-cube:nth-child(3) { animation-delay: 0.4s; }
        .sk-cube-grid .sk-cube:nth-child(4) { animation-delay: 0.1s; }
        .sk-cube-grid .sk-cube:nth-child(5) { animation-delay: 0.2s; }
        .sk-cube-grid .sk-cube:nth-child(6) { animation-delay: 0.3s; }
        .sk-cube-grid .sk-cube:nth-child(7) { animation-delay: 0.0s; }
        .sk-cube-grid .sk-cube:nth-child(8) { animation-delay: 0.1s; }
        .sk-cube-grid .sk-cube:nth-child(9) { animation-delay: 0.2s; }
        .libra-quick-btn {
            position: absolute;
            padding: 2px 8px;
            font-size: 12px;
            cursor: pointer;
            border-radius: 4px;
            background-color: var(--color-primary, #4a00ff);
            color: #fff;
            border: none;
            display: none;
            white-space: nowrap;
            z-index: 10;
            opacity: 0;
            transform: translateY(-50%);
            transition: opacity 0.2s;
        }
        
        .libra-post-item:hover .libra-quick-btn {
            display: block;
            opacity: 1;
        }

        .libra-quick-btn:hover {
            opacity: 0.9;
        }

        /* 自定义返回顶部按钮 */
        #custom-back-to-top {
            position: fixed;
            z-index: 900;
            width: 48px;
            height: 48px;
            border-radius: 14px; /* 更加圆润的矩形,类似 iOS 风格 */
            background: rgba(255, 255, 255, 0.8);
            backdrop-filter: blur(12px) saturate(180%);
            -webkit-backdrop-filter: blur(12px) saturate(180%);
            color: var(--color-primary, #4a00ff);
            border: 1px solid rgba(255, 255, 255, 0.3);
            cursor: pointer;
            display: none;
            justify-content: center;
            align-items: center;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            opacity: 0;
            transform: translateY(20px);
        }
        
        #custom-back-to-top.visible {
            display: flex;
            opacity: 1;
            transform: translateY(0);
        }

        #custom-back-to-top:hover {
            background: var(--color-primary, #4a00ff);
            color: #fff;
            transform: translateY(-4px);
            box-shadow: 0 12px 40px rgba(74, 0, 255, 0.2);
            border-color: transparent;
        }

        #custom-back-to-top svg {
            width: 24px;
            height: 24px;
            transition: transform 0.3s ease;
        }

        #custom-back-to-top:hover svg {
            transform: translateY(-2px);
        }

        #${CONFIG.modalId} {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: rgba(0, 0, 0, 0.5);
            z-index: 900;
            display: flex;
            justify-content: center;
            align-items: center;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s;
        }
        #${CONFIG.modalId}.active {
            opacity: 1;
            pointer-events: auto;
        }
        #${CONFIG.modalId} .modal-content {
            width: 90%;
            max-width: 1000px;
            height: 90%;
            background: var(--base-100, var(--libra-dynamic-bg, #fff));
            border-radius: 12px;
            box-shadow: 0 10px 25px rgba(0,0,0,0.2);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            position: relative;
        }
        #${CONFIG.modalId} .modal-header {
            padding: 10px 20px;
            border-bottom: 1px solid rgba(0,0,0,0.1);
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: var(--base-100, var(--libra-dynamic-bg, #fff));
        }
        #${CONFIG.modalId} .modal-title {
            font-weight: bold;
            font-size: 16px;
        }
        #${CONFIG.modalId} .modal-actions {
            display: flex;
            gap: 12px;
            align-items: center;
        }
        #${CONFIG.modalId} .btn-go-thread {
            padding: 6px 16px;
            background-color: var(--color-primary, #4a00ff);
            color: #fff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            transition: background 0.2s;
        }
        #${CONFIG.modalId} .btn-go-thread:hover {
            opacity: 0.9;
        }
        #${CONFIG.modalId} .btn-close-large {
            padding: 6px 16px;
            background-color: #e5e7eb;
            color: #374151;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
            transition: background 0.2s;
        }
        #${CONFIG.modalId} .btn-close-large:hover {
            background-color: #d1d5db;
        }

        /* 加载占位符样式 */
        .libra-modal-loading {
            position: absolute;
            top: 60px;
            left: 0;
            right: 0;
            bottom: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            background: var(--libra-dynamic-bg);
            z-index: 50;
        }

        .loading-content {
            text-align: center;
            color: var(--color-primary, #4a00ff);
            width: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        .loading-content p {
            margin: 20px 0 0 0;
            font-size: 16px;
            font-weight: 500;
        }

        #${CONFIG.modalId} iframe {
            width: 100%;
            height: 100%;
            border: none;
            background: transparent;
            opacity: 0;
            transition: opacity 0.3s;
        }



        /* 标题链接视觉提示 */
        .libra-title-link-quick-view {
            cursor: pointer !important;
        }

        /* Toast通知样式 */
        .libra-toast {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10001;
            padding: 12px 20px;
            background: var(--base-100, #fff);
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            border-left: 4px solid var(--color-primary, #4a00ff);
            font-size: 14px;
            color: var(--color-primary, #4a00ff);
            opacity: 0;
            transform: translateX(100%);
            transition: all 0.3s ease;
            pointer-events: none;
            max-width: 300px;
            word-wrap: break-word;
        }

        .libra-toast.show {
            opacity: 1;
            transform: translateX(0);
            pointer-events: auto;
        }

        .libra-toast.success {
            border-left-color: #10b981;
            color: #10b981;
        }

        .libra-toast.info {
            border-left-color: var(--color-primary, #4a00ff);
            color: var(--color-primary, #4a00ff);
        }

        /* 通知弹窗样式 */
        #notifications-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: rgba(0, 0, 0, 0.5);
            z-index: 901;
            display: flex;
            justify-content: center;
            align-items: center;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s;
        }
        #notifications-modal.active {
            opacity: 1;
            pointer-events: auto;
        }
        #notifications-modal .modal-content {
            width: 80%;
            max-width: 800px;
            height: 80%;
            background: var(--base-100, var(--libra-dynamic-bg, #fff));
            border-radius: 12px;
            box-shadow: 0 10px 25px rgba(0,0,0,0.2);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            position: relative;
        }
        #notifications-modal .modal-header {
            padding: 10px 20px;
            border-bottom: 1px solid rgba(0,0,0,0.1);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        #notifications-modal .modal-title {
            font-weight: bold;
        }
        #notifications-modal .modal-actions {
            display: flex;
            gap: 10px;
        }
        #notifications-modal .btn-close {
             padding: 4px 12px;
            background-color: #e5e7eb;
            color: #374151;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
        }
        #notifications-modal .btn-back-notifications {
             padding: 4px 12px;
            background-color: var(--color-primary, #4a00ff);
            color: #fff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
        }
        #notifications-modal .btn-back-notifications:hover {
            opacity: 0.9;
        }
        #notifications-modal iframe {
            width: 100%;
            height: 100%;
            border: none;
        }
    `;
    document.head.appendChild(style);

    // 创建模态框
    function createModal() {
        if (document.getElementById(CONFIG.modalId)) return;

        // 获取随机加载内容
        const { animation, text } = getRandomLoadingContent();

        const modal = document.createElement('div');
        modal.id = CONFIG.modalId;
        modal.innerHTML = `
            <div class="modal-content">
                <div class="modal-header">
                    <span class="modal-title">快速查看</span>
                    <div class="modal-actions">
                        <button class="btn-go-thread">进入帖子 ↗</button>
                        <button class="btn-close-large">关闭</button>
                    </div>
                </div>

                <!-- 加载占位符 -->
                <div class="libra-modal-loading" id="modal-loading">
                    <div class="loading-content">
                        ${animation.html}
                        <p class="loading-text">${text}</p>
                    </div>
                </div>

                <iframe id="${CONFIG.iframeId}" src=""></iframe>
            </div>
        `;
        
        // 点击背景关闭
        modal.addEventListener('click', (e) => {
            if (e.target === modal) closeModal();
        });
        
        // 绑定按钮事件
        modal.querySelector('.btn-close-large').addEventListener('click', closeModal);
        
        document.body.appendChild(modal);
    }

    function openModal(url, title) {
        createModal();
        const modal = document.getElementById(CONFIG.modalId);

        // 动态获取网页背景色,解决变量未定义导致的透明问题
        let bg = window.getComputedStyle(document.body).backgroundColor;
        if (bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') {
            bg = window.getComputedStyle(document.documentElement).backgroundColor;
        }
        modal.style.setProperty('--libra-dynamic-bg', bg);

        const iframe = document.getElementById(CONFIG.iframeId);
        const titleEl = modal.querySelector('.modal-title');
        const goBtn = modal.querySelector('.btn-go-thread');
        const loadingEl = document.getElementById('modal-loading');

        // 更新随机加载内容
        if (loadingEl) {
            const { animation, text } = getRandomLoadingContent();
            const loadingContent = loadingEl.querySelector('.loading-content');
            if (loadingContent) {
                loadingContent.innerHTML = `${animation.html}<p class="loading-text">${text}</p>`;
            }
        }

        // 显示加载占位符,隐藏iframe
        if (loadingEl) loadingEl.style.display = 'flex';
        iframe.style.backgroundColor = bg;
        iframe.style.opacity = '0';

        iframe.src = url;
        titleEl.textContent = title || '快速查看';

        // 绑定跳转事件
        goBtn.onclick = () => {
            window.location.href = url;
        };

        modal.classList.add('active');
        document.body.style.overflow = 'hidden';

        iframe.onload = () => {
            try {
                const doc = iframe.contentDocument;
                const css = `
                    header, .navbar, aside:not(.EmojiPickerReact), .menu:not(.dropdown-left), [role="banner"], [role="contentinfo"], footer.footer-center { display: none !important; }
                    div.breadcrumbs.text-sm.overflow-visible { display: none !important; }
                    [data-main-left="true"], .flex.w-full > .flex-1 {
                        position: fixed !important;
                        top: 0 !important;
                        left: 0 !important;
                        width: 100vw !important;
                        height: 100vh !important;
                        z-index: 900 !important;
                        background: var(--base-100, ${bg}) !important;
                        overflow-y: auto !important;
                        padding: 0 20px 20px 20px !important;
                        margin: 0 !important;
                        border: none !important;
                    }
                    .EmojiPickerReact { z-index: 900 !important; }
                    .medium-zoom-overlay { z-index: 900 !important; }
                    body, html { overflow: hidden !important; }
                `;
                const style = doc.createElement('style');
                style.textContent = css;
                doc.head.appendChild(style);

                // 自动滚动到弹窗顶部
                function scrollToTop() {
                    console.log('2libra-enhance: Scrolling to top');
                    const mainContent = doc.querySelector('[data-main-left="true"]') || doc.querySelector('.flex-1');
                    if (mainContent) {
                        mainContent.scrollTop = 0;
                    } else {
                        doc.documentElement.scrollTop = 0;
                    }
                }

                // 监听URL变化,自动滚动到顶部
                const win = iframe.contentWindow;

                // 重写pushState和replaceState
                const originalPushState = win.history.pushState;
                win.history.pushState = function(state, title, url) {
                    console.log('2libra-enhance: pushState triggered, new URL:', url);
                    originalPushState.apply(this, arguments);
                    scrollToTop();
                };

                // 样式注入完成后隐藏加载占位符并显示内容
                if (loadingEl) loadingEl.style.display = 'none';
                iframe.style.opacity = '1';
            } catch (e) {
                // 出错也隐藏加载占位符并显示内容,避免死锁
                if (loadingEl) loadingEl.style.display = 'none';
                iframe.style.opacity = '1';
            }
        };
    }

    function closeModal() {
        const modal = document.getElementById(CONFIG.modalId);
        const iframe = document.getElementById(CONFIG.iframeId);
        if (modal) {
            modal.classList.remove('active');
            iframe.src = '';
            document.body.style.overflow = '';
        }
    }


    // 更新标题链接样式和行为
    function updateTitleLinkStyle(titleLink) {
        if (!titleLink) return;

        // 总是更新设置相关的属性
        if (Settings.clickTitleQuickView) {
            // 添加视觉提示样式和title提示
            titleLink.classList.add('libra-title-link-quick-view');
            titleLink.title = '点击快速查看';

            // 添加点击事件监听器(如果还没有添加)
            if (!titleLink.dataset.libraClickAdded) {
                titleLink.dataset.libraClickAdded = 'true';
                titleLink.addEventListener('click', (e) => {
                    // 首先检查当前设置状态
                    if (!Settings.clickTitleQuickView) return; // 如果设置已禁用,不执行

                    // 检查是否按下了Ctrl键(在新标签页打开)
                    if (e.ctrlKey || e.metaKey) return; // 让浏览器处理新标签页打开

                    e.preventDefault();
                    e.stopPropagation();
                    openModal(titleLink.href, titleLink.textContent);
                });
            }
        } else {
            // 移除视觉提示和title提示
            titleLink.classList.remove('libra-title-link-quick-view');
            if (titleLink.title === '点击快速查看') {
                titleLink.title = '';
            }
        }
    }

    // 主逻辑:尝试为单个 LI 元素添加按钮
    function processListItem(li) {

        //如果设置了点击标题快速查看,则不添加按钮
        if (Settings.clickTitleQuickView){
            //如果已存在按钮,则移除 ,隐藏libra-quick-btn
            const existingBtn = li.querySelector('.libra-quick-btn');
            if (existingBtn) {
                existingBtn.remove();
            }
            return;
        }

        if (!li) return;

        // 查找这一行中的 time 元素
        const timeEl = li.querySelector('time');
        if (!timeEl) return;

        // 查找帖子标题链接
        const titleLink = timeEl.parentElement.parentElement.querySelector('a.link');
        if (!titleLink || titleLink.tagName !== 'A') return;

        // 查找元数据行 (标题下面的 div flex items-center gap-2)
        const metaRow = timeEl.closest('.flex.items-center.gap-2');
        if (!metaRow) return;

        // 标记为已添加(如果还没有标记),用于CSS hover
        if (!li.classList.contains('libra-post-item')) {
            li.classList.add('libra-post-item');
        }

        let btn = li.querySelector('.libra-quick-btn');
        // 添加快速查看按钮(如果还没有添加)
        if (!btn) {
            // 确保metaRow的父元素有定位上下文
            if (getComputedStyle(metaRow.parentElement).position === 'static') {
                metaRow.parentElement.style.position = 'relative';
            }

            btn = document.createElement('button');
            btn.className = 'libra-quick-btn';
            btn.textContent = CONFIG.btnText;
            btn.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                openModal(titleLink.href, titleLink.textContent);
            };
            // 插入到metaRow的父元素中
            metaRow.parentElement.appendChild(btn);
            
            li.dataset.libraQuickBtnAdded = 'true';
        }

        // 计算内容实际宽度:找到最右侧子元素的边缘
        const children = Array.from(metaRow.children).filter(c => getComputedStyle(c).display !== 'none');
        let contentRightEdge = 0;
        if (children.length > 0) {
            // 取所有子元素中最靠右的那个边缘
            children.forEach(child => {
                const right = child.offsetLeft + child.offsetWidth;
                if (right > contentRightEdge) contentRightEdge = right;
            });
        } else {
            contentRightEdge = metaRow.offsetWidth;
        }

        // 按钮位置 = metaRow 的起点 + 内容实际右边缘 + 间距
        const leftPos = metaRow.offsetLeft + contentRightEdge + 8;
        btn.style.left = `${leftPos}px`;
        
        // 垂直居中对齐到 metaRow
        const topPos = metaRow.offsetTop + (metaRow.offsetHeight / 2);
        btn.style.top = `${topPos}px`;
    }

    // 策略1:鼠标移入监听 (Lazy Load)
    document.body.addEventListener('mouseover', (e) => {
        const li = e.target.closest('li');
        if (li) {
            processListItem(li);
        }
    }, { passive: true });


    // --- 自定义返回顶部按钮逻辑 ---

    // 1. 创建按钮元素并添加到页面
    const topButton = document.createElement('button');
    topButton.id = 'custom-back-to-top';
    topButton.innerHTML = `
        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M12 19V5M12 5L5 12M12 5L19 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
    `;
    document.body.appendChild(topButton);

    // 2. 添加点击事件
    topButton.addEventListener('click', () => {
        window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    // 3. 核心:更新按钮位置和可见性的函数
    function updateTopButtonPosition() {
        const card = document.querySelector('ul.card') || document.querySelector('[data-main-left="true"]') || document.querySelector('.flex-1');
        
        if (!card) {
            topButton.classList.remove('visible');
            setTimeout(() => { 
                if(!topButton.classList.contains('visible')) topButton.style.display = 'none'; 
            }, 300);
            return;
        }

        const cardRect = card.getBoundingClientRect();
        const requiredWidth = cardRect.width + 60; 
        const hasEnoughSpace = window.innerWidth >= requiredWidth;
        const isScrolledDown = window.scrollY > 100;

        if (!hasEnoughSpace || !isScrolledDown) {
            if (topButton.classList.contains('visible')) {
                topButton.classList.remove('visible');
                setTimeout(() => { 
                    if(!topButton.classList.contains('visible')) topButton.style.display = 'none'; 
                }, 300);
            }
            return;
        }
        
        topButton.style.display = 'flex';
        requestAnimationFrame(() => {
             topButton.classList.add('visible');
        });

        topButton.style.left = `${cardRect.right + 16}px`;
        topButton.style.right = 'auto';
        
        const desiredBottomOffset = 24; 
        const buttonHeight = topButton.offsetHeight;
        const fixedPos = window.innerHeight - buttonHeight - desiredBottomOffset;
        const stickyPos = cardRect.bottom - buttonHeight;

        topButton.style.top = `${Math.min(fixedPos, stickyPos)}px`;
        topButton.style.bottom = 'auto';
    }

    let ticking = false;
    function throttledUpdater() {
        if (!ticking) {
            window.requestAnimationFrame(() => {
                updateTopButtonPosition();
                ticking = false;
            });
            ticking = true;
        }
    }

    window.addEventListener('scroll', throttledUpdater);
    window.addEventListener('resize', throttledUpdater);
    setTimeout(throttledUpdater, 500);

    // --- 加载动画配置 ---

    // 随机加载动画和文字
    const loadingAnimations = [
        {
            class: 'sk-chase',
            html: `
                <div class="sk-chase">
                    <div class="sk-chase-dot"></div>
                    <div class="sk-chase-dot"></div>
                    <div class="sk-chase-dot"></div>
                    <div class="sk-chase-dot"></div>
                    <div class="sk-chase-dot"></div>
                    <div class="sk-chase-dot"></div>
                </div>
            `
        },
        {
            class: 'sk-pulse',
            html: '<div class="sk-pulse"></div>'
        },
        {
            class: 'sk-ripple',
            html: '<div class="sk-ripple"></div>'
        },
        {
            class: 'sk-rotate',
            html: '<div class="sk-rotate"></div>'
        },
        {
            class: 'sk-bounce',
            html: '<div class="sk-bounce"></div>'
        },
        {
            class: 'sk-wave',
            html: '<div class="sk-wave"></div>'
        },
        {
            class: 'sk-cube-grid',
            html: `
                <div class="sk-cube-grid">
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                    <div class="sk-cube"></div>
                </div>
            `
        }
    ];

    const loadingTexts = [
        '正在加载精彩内容...',
        '正在飞速加载中...',
        '正在准备精彩内容...',
        '正在组装像素魔法...',
        '正在穿越网络的海洋...',
        '正在召唤帖子的灵魂...',
        '正在加载宇宙的奥秘...',
        '正在点亮知识的火花...',
        '正在编织信息的网络...',
        '正在唤醒沉睡的数据...',
        '正在绘制数字的画卷...',
        '正在解码比特的秘密...',
        '正在搭建内容的桥梁...',
        '正在收集思维的碎片...'
    ];

    // 获取随机动画和文字
    function getRandomLoadingContent() {
        const animation = loadingAnimations[Math.floor(Math.random() * loadingAnimations.length)];
        const text = loadingTexts[Math.floor(Math.random() * loadingTexts.length)];
        return { animation, text };
    }

    // --- 通知弹窗逻辑 ---
    const NOTIFICATIONS_MODAL_ID = 'notifications-modal';

    function createNotificationsModal() {
        if (document.getElementById(NOTIFICATIONS_MODAL_ID)) return;

        const modal = document.createElement('div');
        modal.id = NOTIFICATIONS_MODAL_ID;
        modal.innerHTML = `
            <div class="modal-content">
                <div class="modal-header">
                    <span class="modal-title">通知</span>
                    <div class="modal-actions">
                        <button class="btn-back-notifications">返回通知</button>
                        <button class="btn-close">关闭</button>
                    </div>
                </div>
                <div class="libra-modal-loading" id="notifications-modal-loading"></div>
                <iframe src=""></iframe>
            </div>
        `;

        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                closeNotificationsModal();
            }
        });
        modal.querySelector('.btn-close').addEventListener('click', closeNotificationsModal);
        
        // 绑定返回通知按钮事件
        modal.querySelector('.btn-back-notifications').addEventListener('click', () => {
             const iframe = modal.querySelector('iframe');
             iframe.src = '/notifications';
        });

        document.body.appendChild(modal);
    }

    function openNotificationsModal(url) {
        createNotificationsModal();
        const modal = document.getElementById(NOTIFICATIONS_MODAL_ID);

        let bg = window.getComputedStyle(document.body).backgroundColor;
        if (bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') {
            bg = window.getComputedStyle(document.documentElement).backgroundColor;
        }
        modal.style.setProperty('--libra-dynamic-bg', bg);

        const iframe = modal.querySelector('iframe');
        const loading = modal.querySelector('#notifications-modal-loading');

        iframe.style.background = bg;
        iframe.style.opacity = '0';

        // 显示加载动画
        const { animation, text } = getRandomLoadingContent();
        loading.innerHTML = `
            <div class="loading-content">
                ${animation.html}
                <p class="loading-text">${text}</p>
            </div>
        `;
        loading.style.display = 'flex';

        iframe.src = url;

        modal.classList.add('active');
        document.body.style.overflow = 'hidden';

        iframe.onload = () => {
            try {
                const doc = iframe.contentDocument;
                const css = `
                    header, .navbar, aside, [role="banner"], [role="contentinfo"] { display: none !important; }
                    .container { width: 100% !important; max-width: none !important; padding: 10px !important; }
                    main { margin-top: 0 !important; }
                    body { background: ${bg} !important; overflow-y: auto !important; }
                    [data-right-sidebar="true"]{display: none !important;}
                    .breadcrumbs{ display: none !important; }
                    .footer-horizontal{ display: none !important; }
                `;
                const style = doc.createElement('style');
                style.textContent = css;
                doc.head.appendChild(style);

                // 使用 MutationObserver 持续监听并隐藏目标元素(应对动态加载和SPA跳转)
                const hideWorkNodeList = () => {
                    const workNodeList = doc.querySelector('[role="work node list"]');
                    if (workNodeList && workNodeList.parentElement && workNodeList.parentElement.parentElement) {
                        const target = workNodeList.parentElement.parentElement;
                        if (target.style.display !== 'none') {
                            target.style.display = 'none';
                        }
                    }
                };

                // 立即执行一次
                hideWorkNodeList();

                // 持续监听 DOM 变化
                const observer = new MutationObserver(hideWorkNodeList);
                observer.observe(doc.body, { childList: true, subtree: true });

                // 隐藏加载动画,显示iframe
                iframe.style.opacity = '1';
                loading.style.display = 'none';
            } catch (e) {
                iframe.style.opacity = '1';
                loading.style.display = 'none';
            }
        };
    }

    function closeNotificationsModal() {
        const modal = document.getElementById(NOTIFICATIONS_MODAL_ID);
        if (modal) {
            modal.classList.remove('active');
            const iframe = modal.querySelector('iframe');
            iframe.src = '';
            document.body.style.overflow = '';
        }
    }


    // --- 设置功能 ---

    // 显示toast通知
    function showToast(message, type = 'info') {
        // 移除现有的toast
        const existingToast = document.querySelector('.libra-toast');
        if (existingToast) {
            existingToast.remove();
        }

        // 创建新的toast
        const toast = document.createElement('div');
        toast.className = `libra-toast ${type}`;
        toast.textContent = message;

        document.body.appendChild(toast);

        // 强制重绘
        toast.offsetHeight;

        // 显示toast
        toast.classList.add('show');

        // 3秒后自动隐藏
        setTimeout(() => {
            toast.classList.remove('show');
            setTimeout(() => {
                if (toast.parentNode) {
                    toast.remove();
                }
            }, 300);
        }, 3000);
    }

    // 全局扫描并处理所有帖子项
    function processAllPostItems() {
        // 找到所有包含指定class的a标签,这些就是帖子项
        const postLinks = document.querySelectorAll('a.link.link-hover.leading-4');
        postLinks.forEach(postLink => {
            const li = postLink.closest('li');
            if (li) {
                processListItem(li);
            }
        });
        //找到所有 href 以 /post-flat开头的a标签
        let postFlatLinks = document.querySelectorAll('a[href^="/post-flat"]');
        if(!postFlatLinks || postFlatLinks.length==0){
        	//firefox中url不一样?
            postFlatLinks = document.querySelectorAll('a[href^="/post/"]');
        }
        postFlatLinks.forEach(postLink => {
            //过滤掉class 包含 join-item 的标签
            if (postLink.classList.contains('join-item')) return;
            updateTitleLinkStyle(postLink);
        });
    }

    // 创建配置描述
    const configDesc = {
        clickTitleQuickView: {
            name: "点击帖子标题开启快速查看",
            type: "bool",
            value: true
        },
        showQuickViewToast: {
            name: "通知快速查看",
            type: "bool",
            value: true
        }
    };

    // 注册配置
    const config = new GM_config(configDesc, { immediate: true });

    // 设置Settings对象使用GM_config
    Settings = {
        get clickTitleQuickView() {
            return config.get('clickTitleQuickView');
        },
        set clickTitleQuickView(value) {
            config.set('clickTitleQuickView', value);
        },
        get showQuickViewToast() {
            return config.get('showQuickViewToast');
        },
        set showQuickViewToast(value) {
            config.set('showQuickViewToast', value);
        }
    };

    // 处理点击标题快速查看设置变化
    function handleClickTitleQuickViewChange(enabled) {
        const message = enabled ? '✅ 已启用:点击帖子标题开启快速查看' : '⬜ 已禁用:点击帖子标题开启快速查看';
        showToast(message, enabled ? 'success' : 'info');
        processAllPostItems();
    }

    // 处理通知快速查看设置变化
    function handleShowQuickViewToastChange(enabled) {
        const message = enabled ? '✅ 已启用:通知快速查看' : '⬜ 已禁用:通知快速查看';
        showToast(message, enabled ? 'success' : 'info');
        // 立即刷新通知链接的状态
        updateNotificationLinkState();
    }

    // 监听配置变化,实时生效
    config.addEventListener('set', (e) => {
        const { prop, after } = e.detail;
        if (prop === 'clickTitleQuickView') {
            handleClickTitleQuickViewChange(after);
        } else if (prop === 'showQuickViewToast') {
            handleShowQuickViewToastChange(after);
        }
    });

    // --- 初始化功能 ---
    function updateNotificationLinkState() {
        const notificationLink = document.querySelector('a[href="/notifications"], a[href$="/notifications"]');
        if (notificationLink) {
            if (Settings.showQuickViewToast) {
                notificationLink.title = '点击快速查看通知';
                notificationLink.style.cursor = 'pointer';
            } else {
                notificationLink.title = '';
            }

            if (!notificationLink.dataset.notificationModalAdded) {
                notificationLink.dataset.notificationModalAdded = 'true';
                notificationLink.addEventListener('click', e => {
                    // 动态检查设置,实现联动
                    if (Settings.showQuickViewToast) {
                        // 检查是否按下了Ctrl键(在新标签页打开)
                        if (e.ctrlKey || e.metaKey) return; 
                        
                        e.preventDefault();
                        e.stopPropagation();
                        openNotificationsModal(notificationLink.href);
                    }
                });
            }
        }
    }

    function initializeNotificationQuickView() {
        // 初次尝试
        updateNotificationLinkState();
        
        // 持续观察(针对 SPA 路由切换或动态加载)
        const observer = new MutationObserver(() => {
            updateNotificationLinkState();
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // 启动所有初始化
    initializeNotificationQuickView();

    // 初始化帖子标题快速查看
    processAllPostItems();

    // 持续监听新加载的帖子(针对SPA和动态加载)
    const postListObserver = new MutationObserver(() => {
        processAllPostItems();
    });
    postListObserver.observe(document.body, { childList: true, subtree: true });

})();