LabEx Helper

Helper script for labex.io website

// ==UserScript==
// @name         LabEx Helper
// @namespace    http://tampermonkey.net/
// @version      2.0.2
// @description  Helper script for labex.io website
// @author       huhuhang
// @match        https://labex.io/*
// @match        https://labex.io/zh/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=labex.io
// @grant        GM_xmlhttpRequest
// @connect      labex-api-proxy.zhanghang.me
// ==/UserScript==

(function () {
    'use strict';

    // Function to detect system dark mode preference
    function prefersDarkMode() {
        return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    }

    // Function to get user theme preference (or default to system preference)
    function getUserThemePreference() {
        const savedPreference = localStorage.getItem('labex_theme_preference');
        if (savedPreference === 'light' || savedPreference === 'dark') {
            return savedPreference;
        }
        return prefersDarkMode() ? 'dark' : 'light';
    }

    // Function to save user theme preference
    function saveUserThemePreference(theme) {
        localStorage.setItem('labex_theme_preference', theme);
    }

    // Function to format timestamp into relative time
    function formatRelativeTime(timestamp) {
        const now = new Date();
        const past = new Date(timestamp);
        const diffInSeconds = Math.floor((now - past) / 1000);

        if (diffInSeconds < 60) {
            return `${diffInSeconds}s ago`;
        }

        const diffInMinutes = Math.floor(diffInSeconds / 60);
        if (diffInMinutes < 60) {
            return `${diffInMinutes}m ago`;
        }

        const diffInHours = Math.floor(diffInMinutes / 60);
        if (diffInHours < 24) {
            const remainingMinutes = diffInMinutes % 60;
            let result = `${diffInHours}h`;
            if (remainingMinutes > 0) {
                result += `, ${remainingMinutes}m`;
            }
            return result + ' ago';
        }

        const diffInDays = Math.floor(diffInHours / 24);
        const remainingHours = diffInHours % 24;
        let result = `${diffInDays}d`;
        if (remainingHours > 0) {
            result += `, ${remainingHours}h`;
        }
        return result + ' ago';
    }

    // Function to fetch and display lab data
    async function fetchAndDisplayLabData(labAlias, buttonContainer) {
        const apiUrl = `https://labex-api-proxy.zhanghang.me/feishu/labs/${labAlias}`;

        // Remove existing data container if any
        const existingDataContainer = buttonContainer.querySelector('.labex-stats-container');
        if (existingDataContainer) {
            existingDataContainer.remove();
        }

        const labDataContainer = document.createElement('div');
        labDataContainer.classList.add('labex-stats-container');

        // 获取当前主题模式
        const currentTheme = getUserThemePreference();

        // 根据主题设置样式
        const isDarkMode = currentTheme === 'dark';
        labDataContainer.style.cssText = `
            position: absolute;
            bottom: 40px;
            left: 0;
            background: ${isDarkMode ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)'};
            backdrop-filter: blur(10px);
            padding: 10px 12px;
            border-radius: 12px;
            box-shadow: ${isDarkMode ? '0 4px 20px rgba(0, 0, 0, 0.3)' : '0 4px 20px rgba(0, 0, 0, 0.12)'};
            font-size: 12px;
            color: ${isDarkMode ? '#E5E7EB' : '#374151'};
            display: flex;
            flex-direction: column;
            gap: 6px;
            min-width: 250px;
            width: 280px;
            opacity: 1;
            transform: translateY(0);
            transition: opacity 0.4s ease, transform 0.4s ease;
            border: 1px solid ${isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
            z-index: 9998;
            pointer-events: auto;
        `;

        buttonContainer.appendChild(labDataContainer);

        // 创建加载动画 - 确保显眼易见
        labDataContainer.innerHTML = `
        <div class="loading-container">
            <div class="loading-header">
                <div class="loading-title">
                    <div class="pulse-ring"></div>
                    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
                    </svg>
                    Loading Lab Stats...
                </div>
            </div>
            <div class="loading-skeleton">
                <div class="skeleton-row">
                    <div class="skeleton-item"></div>
                    <div class="skeleton-item"></div>
                    <div class="skeleton-item"></div>
                </div>
                <div class="skeleton-divider"></div>
                <div class="skeleton-row">
                    <div class="skeleton-item"></div>
                    <div class="skeleton-item"></div>
                    <div class="skeleton-item"></div>
                </div>
                <div class="skeleton-divider"></div>
                <div class="skeleton-row">
                    <div class="skeleton-item"></div>
                    <div class="skeleton-item"></div>
                </div>
                <div class="skeleton-divider"></div>
                <div class="skeleton-badges">
                    <div class="skeleton-badge"></div>
                    <div class="skeleton-badge"></div>
                    <div class="skeleton-badge"></div>
                </div>
            </div>
            <div class="loading-stats">
                <div class="spinner"></div>
                <span class="loading-text">Fetching Lab data...</span>
            </div>
        </div>`;

        // 注入加载动画样式 - 增强视觉效果
        const loadingStyleSheet = document.createElement('style');
        loadingStyleSheet.textContent = `
            .loading-container {
                display: flex;
                flex-direction: column;
                gap: 8px;
                width: 100%;
                animation: fadeIn 0.3s ease;
            }
            .loading-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .loading-title {
                font-size: 13px;
                font-weight: 600;
                color: ${isDarkMode ? '#E5E7EB' : '#1f2937'};
                display: flex;
                align-items: center;
                gap: 5px;
                position: relative;
            }
            .pulse-ring {
                width: 10px;
                height: 10px;
                border-radius: 50%;
                background-color: #3b82f6;
                position: absolute;
                left: -5px;
                animation: pulseRing 1.5s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
            }
            @keyframes pulseRing {
                0% {
                    transform: scale(0.5);
                    opacity: 0.5;
                }
                50% {
                    transform: scale(1);
                    opacity: 0.2;
                }
                100% {
                    transform: scale(1.5);
                    opacity: 0;
                }
            }
            .loading-skeleton {
                display: flex;
                flex-direction: column;
                gap: 8px;
                padding: 5px 0;
            }
            .skeleton-row {
                display: flex;
                gap: 8px;
                width: 100%;
            }
            .skeleton-item {
                height: 32px;
                flex: 1;
                background: ${isDarkMode ?
                'linear-gradient(90deg, #2a2a2a 0%, #3a3a3a 50%, #2a2a2a 100%)' :
                'linear-gradient(90deg, #f0f0f0 0%, #e0e0e0 50%, #f0f0f0 100%)'};
                background-size: 200% 100%;
                animation: shimmer 1.5s infinite linear;
                border-radius: 6px;
                box-shadow: ${isDarkMode ? '0 1px 3px rgba(0,0,0,0.2)' : '0 1px 3px rgba(0,0,0,0.05)'};
            }
            .skeleton-divider {
                height: 1px;
                width: 100%;
                background-color: ${isDarkMode ? '#3f3f46' : '#e5e7eb'};
                margin: 2px 0;
            }
            .skeleton-badges {
                display: flex;
                gap: 8px;
                width: 100%;
            }
            .skeleton-badge {
                height: 22px;
                flex: 1;
                background: ${isDarkMode ?
                'linear-gradient(90deg, #2a2a2a 0%, #3a3a3a 50%, #2a2a2a 100%)' :
                'linear-gradient(90deg, #f0f0f0 0%, #e0e0e0 50%, #f0f0f0 100%)'};
                background-size: 200% 100%;
                animation: shimmer 1.5s infinite linear;
                border-radius: 6px;
                box-shadow: ${isDarkMode ? '0 1px 3px rgba(0,0,0,0.2)' : '0 1px 3px rgba(0,0,0,0.05)'};
            }
            @keyframes shimmer {
                0% {
                    background-position: 200% 0;
                }
                100% {
                    background-position: -200% 0;
                }
            }
            @keyframes fadeIn {
                from {
                    opacity: 0.7;
                }
                to {
                    opacity: 1;
                }
            }
            .loading-stats {
                display: flex;
                align-items: center;
                gap: 8px;
                font-size: 12px;
                color: ${isDarkMode ? '#9CA3AF' : '#4b5563'};
                margin-top: 4px;
                padding: 6px 8px;
                border-radius: 4px;
                background-color: ${isDarkMode ? 'rgba(37, 99, 235, 0.2)' : 'rgba(239, 246, 255, 0.7)'};
                box-shadow: 0 1px 2px rgba(0,0,0,0.05);
                border: 1px solid ${isDarkMode ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.2)'};
            }
            .loading-text {
                font-weight: 500;
            }
            .spinner {
                width: 12px;
                height: 12px;
                border: 2px solid #3b82f6;
                border-top-color: transparent;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
            @keyframes spin {
                to {
                    transform: rotate(360deg);
                }
            }
            .pulse-dot {
                width: 8px;
                height: 8px;
                background-color: #3b82f6;
                border-radius: 50%;
                animation: pulse 1.5s infinite;
            }
            @keyframes pulse {
                0% {
                    transform: scale(0.8);
                    opacity: 0.5;
                }
                50% {
                    transform: scale(1.1);
                    opacity: 1;
                }
                100% {
                    transform: scale(0.8);
                    opacity: 0.5;
                }
            }
        `;
        labDataContainer.appendChild(loadingStyleSheet);

        // 立即显示卡片
        setTimeout(() => {
            if (labDataContainer && buttonContainer.contains(labDataContainer)) {
                labDataContainer.style.opacity = '1';
                labDataContainer.style.transform = 'translateY(0)';
            }
        }, 10);

        // Delay fetching data until after page load is complete
        // Use requestIdleCallback for browsers that support it, otherwise use setTimeout
        const fetchData = () => {
            try {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: apiUrl,
                    onload: function (response) {
                        if (response.status >= 200 && response.status < 300) {
                            const data = JSON.parse(response.responseText);
                            const learned = data.ALL_LEARNED || 0;
                            const passed = data.ALL_PASSED || 0;
                            const githubLink = data.GITHUB?.link;
                            const githubText = data.GITHUB?.text || 'GitHub';
                            const feeType = data.FEE_TYPE || 'N/A';
                            const positiveReviews = data.POSITIVE_REVIEW || 0;
                            const neutralReviews = data.NEUTRAL_REVIEW || 0;
                            const negativeReviews = data.NEGATIVE_REVIEW || 0;
                            const isVerified = data.VERIFIED === true;
                            const isOpenNetwork = data.OPEN_NETWORK === true;
                            const updatedAtTimestamp = data.UPDATE_AT; // Extract timestamp

                            // Format the timestamp as relative time
                            let relativeUpdatedAt = '';
                            if (updatedAtTimestamp) {
                                relativeUpdatedAt = formatRelativeTime(updatedAtTimestamp);
                            }

                            const totalReviews = positiveReviews + neutralReviews + negativeReviews;
                            const passRate = learned > 0 ? ((passed / learned) * 100).toFixed(1) : '0.0';
                            const positiveRate = totalReviews > 0 ? ((positiveReviews / totalReviews) * 100).toFixed(1) : '0.0';
                            const negativeRate = totalReviews > 0 ? ((negativeReviews / totalReviews) * 100).toFixed(1) : '0.0';
                            // 计算综合好评率(正面 + 中立)和差评率
                            const combinedPositiveReviews = positiveReviews + neutralReviews;
                            const combinedPositiveRate = totalReviews > 0 ? ((combinedPositiveReviews / totalReviews) * 100).toFixed(1) : '0.0';
                            const dislikeRate = totalReviews > 0 ? ((negativeReviews / totalReviews) * 100).toFixed(1) : '0.0';

                            // Build HTML structure for compact floating display
                            // Inject CSS for styling the stats
                            const styleSheet = document.createElement('style');
                            styleSheet.textContent = `
                                .labex-stats-container {
                                    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                                }
                                .labex-stats-container strong {
                                    font-weight: 600;
                                    color: ${isDarkMode ? '#E5E7EB' : '#1f2937'};
                                }
                                .labex-stats-container .title {
                                    font-size: 13px;
                                    font-weight: 600;
                                    color: ${isDarkMode ? '#E5E7EB' : '#1f2937'};
                                    margin-bottom: 6px;
                                    display: flex;
                                    align-items: center;
                                    gap: 5px;
                                    padding: 2px 0;
                                    opacity: 0;
                                    transform: translateY(8px);
                                    animation: fadeInUp 0.4s ease forwards;
                                    animation-delay: 0.1s;
                                }
                                .labex-stats-container .updated-at {
                                    font-size: 10px;
                                    color: ${isDarkMode ? '#9CA3AF' : '#6b7280'};
                                    margin-left: auto; /* Push to the right */
                                    font-weight: 400;
                                }
                                .stats-row {
                                    display: flex;
                                    width: 100%;
                                    justify-content: space-between;
                                    margin-bottom: 8px;
                                    padding: 5px;
                                    border-radius: 8px;
                                    opacity: 0;
                                    transform: translateY(8px);
                                    animation: fadeInUp 0.4s ease forwards;
                                }
                                .stats-row:nth-of-type(1) {
                                    animation-delay: 0.2s;
                                }
                                .stats-row:nth-of-type(2) {
                                    animation-delay: 0.3s;
                                }
                                .stats-row:nth-of-type(3) {
                                    animation-delay: 0.4s;
                                }
                                .badge-row {
                                    background-color: ${isDarkMode ? 'rgba(254, 243, 199, 0.1)' : 'rgba(254, 243, 199, 0.3)'}; /* 浅黄色背景 */
                                    border-radius: 8px;
                                    padding: 5px;
                                    display: flex;
                                    justify-content: space-between;
                                    align-items: stretch;
                                    width: 100%;
                                    gap: 6px;
                                    opacity: 0;
                                    transform: translateY(8px);
                                    animation: fadeInUp 0.4s ease forwards;
                                    animation-delay: 0.5s;
                                }
                                .divider {
                                    height: 1px;
                                    background: ${isDarkMode ? 'rgba(75, 85, 99, 0.3)' : 'rgba(229, 231, 235, 0.5)'};
                                    margin: 6px 0;
                                    width: 100%;
                                    opacity: 0;
                                    animation: fadeIn 0.4s ease forwards;
                                }
                                .divider:nth-of-type(1) {
                                    animation-delay: 0.25s;
                                }
                                .divider:nth-of-type(2) {
                                    animation-delay: 0.35s;
                                }
                                .divider:nth-of-type(3) {
                                    animation-delay: 0.45s;
                                }
                                @keyframes fadeInUp {
                                    from {
                                        opacity: 0;
                                        transform: translateY(8px);
                                    }
                                    to {
                                        opacity: 1;
                                        transform: translateY(0);
                                    }
                                }
                                @keyframes fadeIn {
                                    from {
                                        opacity: 0;
                                    }
                                    to {
                                        opacity: 1;
                                    }
                                }
                                .stat-item {
                                    flex: 1;
                                    display: flex;
                                    flex-direction: column;
                                    align-items: center;
                                    justify-content: center;
                                    background: ${isDarkMode ? 'rgba(50, 50, 50, 0.7)' : 'rgba(255, 255, 255, 0.7)'};
                                    padding: 6px 4px;
                                    border-radius: 6px;
                                    min-width: 0;
                                    transition: all 0.2s ease;
                                    text-align: center;
                                    margin: 0 3px;
                                }
                                .stat-item:first-child {
                                    margin-left: 0;
                                }
                                .stat-item:last-child {
                                    margin-right: 0;
                                }
                                .stat-item:hover {
                                    background: ${isDarkMode ? 'rgba(60, 60, 60, 0.9)' : 'rgba(243, 244, 246, 0.9)'};
                                    transform: translateY(-2px);
                                }
                                .stat-item.positive-rate {
                                    background-color: ${isDarkMode ? 'rgba(59, 130, 246, 0.4)' : 'rgba(134, 239, 172, 0.7)'}; /* 浅绿色背景 */
                                    color: ${isDarkMode ? '#93c5fd' : '#065f46'};
                                }
                                .stat-item.positive-rate:hover {
                                    background-color: ${isDarkMode ? 'rgba(59, 130, 246, 0.5)' : 'rgba(134, 239, 172, 0.85)'};
                                }
                                .stat-item.negative-rate {
                                    background-color: ${isDarkMode ? 'rgba(248, 113, 113, 0.4)' : 'rgba(252, 165, 165, 0.7)'}; /* 浅红色背景 */
                                    color: ${isDarkMode ? '#fca5a5' : '#991b1b'};
                                }
                                .stat-item.negative-rate:hover {
                                    background-color: ${isDarkMode ? 'rgba(248, 113, 113, 0.5)' : 'rgba(252, 165, 165, 0.85)'};
                                }
                                .stat-item .value {
                                    font-weight: 600;
                                    color: ${isDarkMode ? '#E5E7EB' : '#1f2937'};
                                    font-size: 13px;
                                    text-align: center;
                                    width: 100%;
                                    white-space: nowrap;
                                    overflow: hidden;
                                    text-overflow: ellipsis;
                                }
                                .stat-item .label {
                                    font-size: 10px;
                                    color: ${isDarkMode ? '#9CA3AF' : '#6b7280'};
                                    margin-top: 2px;
                                    text-align: center;
                                    width: 100%;
                                    white-space: nowrap;
                                    overflow: hidden;
                                    text-overflow: ellipsis;
                                }
                                .badge {
                                    flex: 1 1 0;
                                    display: inline-flex;
                                    align-items: center;
                                    justify-content: center;
                                    gap: 4px;
                                    padding: 5px 3px;
                                    border-radius: 6px;
                                    font-size: 10px;
                                    font-weight: 500;
                                    cursor: help;
                                    transition: all 0.2s ease;
                                    margin: 0;
                                    text-align: center;
                                    background: ${isDarkMode ? 'rgba(50, 50, 50, 0.7)' : 'rgba(255, 255, 255, 0.7)'};
                                    min-width: 0;
                                    overflow: hidden;
                                    text-overflow: ellipsis;
                                    white-space: nowrap;
                                }
                                .badge:first-child {
                                    margin-left: 0;
                                }
                                .badge:last-child {
                                    margin-right: 0;
                                }
                                .badge:hover {
                                    transform: translateY(-1px);
                                    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
                                }
                                .badge.verified {
                                    background-color: ${isDarkMode ? 'rgba(59, 130, 246, 0.4)' : 'rgba(134, 239, 172, 0.7)'}; /* 浅绿色背景 */
                                    color: ${isDarkMode ? '#93c5fd' : '#065f46'};
                                }
                                .badge.unverified {
                                    background-color: ${isDarkMode ? 'rgba(248, 113, 113, 0.4)' : 'rgba(252, 165, 165, 0.7)'}; /* 浅红色背景 */
                                    color: ${isDarkMode ? '#fca5a5' : '#991b1b'};
                                }
                                .badge.fee { background-color: ${isDarkMode ? '#374151' : '#f3f4f6'}; color: ${isDarkMode ? '#9CA3AF' : '#4b5563'}; }
                                .badge.network-open {
                                    background-color: ${isDarkMode ? 'rgba(248, 113, 113, 0.4)' : 'rgba(252, 165, 165, 0.7)'}; /* 浅红色背景 */
                                    color: ${isDarkMode ? '#fca5a5' : '#991b1b'};
                                }
                                .badge.network-closed {
                                    background-color: ${isDarkMode ? 'rgba(59, 130, 246, 0.4)' : 'rgba(134, 239, 172, 0.7)'}; /* 浅绿色背景 */
                                    color: ${isDarkMode ? '#93c5fd' : '#065f46'};
                                }
                                .badge.github {
                                    background-color: transparent;
                                    color: #3b82f6;
                                    text-decoration: none;
                                    cursor: pointer;
                                    border: 1px solid ${isDarkMode ? 'rgba(59, 130, 246, 0.3)' : '#dbeafe'};
                                }
                                .badge.github svg {
                                    width: 11px; height: 11px;
                                }
                                .loading-stats {
                                    display: flex;
                                    align-items: center;
                                    gap: 8px;
                                    font-size: 12px;
                                    color: ${isDarkMode ? '#9CA3AF' : '#6b7280'};
                                }
                                .pulse-dot {
                                    width: 8px;
                                    height: 8px;
                                    background-color: #3b82f6;
                                    border-radius: 50%;
                                    animation: pulse 1.5s infinite;
                                }
                                @keyframes pulse {
                                    0% {
                                        transform: scale(0.8);
                                        opacity: 0.5;
                                    }
                                    50% {
                                        transform: scale(1.1);
                                        opacity: 1;
                                    }
                                    100% {
                                        transform: scale(0.8);
                                        opacity: 0.5;
                                    }
                                }
                            `;

                            labDataContainer.innerHTML = ``; // Clear previous content
                            labDataContainer.appendChild(styleSheet);

                            const contentWrapper = document.createElement('div');
                            contentWrapper.innerHTML = `
                                <div class="title">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                        <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
                                    </svg>
                                    Lab Stats
                                    ${relativeUpdatedAt ? `<span class="updated-at" title="Last Data Update Time">⏱ ${relativeUpdatedAt}</span>` : ''}
                                </div>

                                <!-- 第一排:学习人数、通过人数、通过率 -->
                                <div class="stats-row">
                                    <div class="stat-item" title="Total Learners">
                                        <div class="value">${learned.toLocaleString()}</div>
                                        <div class="label">Learners</div>
                                    </div>
                                    <div class="stat-item" title="Students Passed">
                                        <div class="value">${passed.toLocaleString()}</div>
                                        <div class="label">Passed</div>
                                    </div>
                                    <div class="stat-item" title="Success Rate">
                                        <div class="value">${passRate}%</div>
                                        <div class="label">Pass Rate</div>
                                    </div>
                                </div>

                                <div class="divider"></div>

                                <!-- 第二排:好评、中立评价、差评 -->
                                <div class="stats-row">
                                    <div class="stat-item" title="Positive Reviews (${positiveRate}%)">
                                        <div class="value">${positiveReviews.toLocaleString()}</div>
                                        <div class="label">👍 Likes</div>
                                    </div>
                                    <div class="stat-item" title="Neutral Reviews">
                                        <div class="value">${neutralReviews.toLocaleString()}</div>
                                        <div class="label">😐 Neutral</div>
                                    </div>
                                    <div class="stat-item" title="Negative Reviews (${negativeRate}%)">
                                        <div class="value">${negativeReviews.toLocaleString()}</div>
                                        <div class="label">👎 Dislikes</div>
                                    </div>
                                </div>

                                <div class="divider"></div>

                                <!-- 新增:综合好评率和差评率 -->
                                <div class="stats-row">
                                    <div class="stat-item positive-rate" title="Combined Positive Rate (Likes + Neutral)">
                                        <div class="value">${combinedPositiveRate}%</div>
                                        <div class="label">😊 Approval</div>
                                    </div>
                                    <div class="stat-item negative-rate" title="Dislike Rate">
                                        <div class="value">${dislikeRate}%</div>
                                        <div class="label">😟 Dislike</div>
                                    </div>
                                </div>

                                <div class="divider"></div>

                                <!-- 第三排:验证状态、网络需求、GitHub 链接 -->
                                <div class="badge-row">
                                    <span class="badge ${isVerified ? 'verified' : 'unverified'}" title="${isVerified ? 'Verified Lab' : 'Not Verified'}">
                                        <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                                            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
                                            <polyline points="22 4 12 14.01 9 11.01"></polyline>
                                        </svg>
                                        ${isVerified ? 'Verified' : 'Unverified'}
                                    </span>
                                    <span class="badge ${isOpenNetwork ? 'network-open' : 'network-closed'}" title="${isOpenNetwork ? 'Open Network Required' : 'No Open Network Required'}">
                                        <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                                            <circle cx="12" cy="12" r="10"></circle>
                                            <line x1="2" y1="12" x2="22" y2="12"></line>
                                            <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
                                        </svg>
                                        ${isOpenNetwork ? 'Open Net' : 'Local Net'}
                                    </span>
                                    ${githubLink ? `
                                    <a class="badge github" href="${githubLink}" target="_blank" title="${githubText}">
                                        <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                            <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
                                        </svg>
                                        GitHub
                                    </a>` : `
                                    <span class="badge fee">
                                        <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                                            <circle cx="12" cy="12" r="10"></circle>
                                            <line x1="12" y1="8" x2="12" y2="12"></line>
                                            <line x1="12" y1="16" x2="12.01" y2="16"></line>
                                        </svg>
                                        ${feeType}
                                    </span>`}
                                </div>
                            `;
                            labDataContainer.appendChild(contentWrapper);

                            // 添加渐入的整体动画效果
                            labDataContainer.style.opacity = '1';
                            labDataContainer.style.transform = 'translateY(0)';

                            // Add hover effects for Github link
                            const githubAnchor = labDataContainer.querySelector('a.github');
                            if (githubAnchor) {
                                githubAnchor.onmouseover = () => {
                                    githubAnchor.style.backgroundColor = isDarkMode ? 'rgba(59, 130, 246, 0.2)' : '#dbeafe';
                                    githubAnchor.style.transform = 'translateY(-2px)';
                                    githubAnchor.style.boxShadow = isDarkMode ?
                                        '0 2px 5px rgba(59, 130, 246, 0.3)' :
                                        '0 2px 5px rgba(59, 130, 246, 0.2)';
                                };
                                githubAnchor.onmouseout = () => {
                                    githubAnchor.style.backgroundColor = isDarkMode ? 'rgba(59, 130, 246, 0.1)' : '#eff6ff';
                                    githubAnchor.style.transform = 'translateY(-1px)';
                                    githubAnchor.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.05)';
                                };
                            }
                        } else {
                            console.error('Failed to fetch lab data:', response.statusText);
                            labDataContainer.innerHTML = `<div class="loading-stats" style="color: #ef4444; padding: 15px; display: flex; align-items: center; gap: 8px; font-weight: 500;">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
                                Unable to load data. Try again later.
                            </div>`;
                        }
                    },
                    onerror: function (error) {
                        console.error('Error fetching lab data:', error);
                        labDataContainer.innerHTML = `<div class="loading-stats" style="color: #ef4444; padding: 15px; display: flex; align-items: center; gap: 8px; font-weight: 500;">
                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
                            Network error. Check connection.
                        </div>`;
                    }
                });
            } catch (error) {
                console.error('Error fetching or processing lab data:', error);
                labDataContainer.innerHTML = `<div class="loading-stats" style="color: #ef4444; padding: 15px; display: flex; align-items: center; gap: 8px; font-weight: 500;">
                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
                    Error processing data.
                </div>`;
            }
        };

        // Use requestIdleCallback if available, otherwise fall back to setTimeout
        if (window.requestIdleCallback) {
            window.requestIdleCallback(fetchData, { timeout: 2000 });
        } else {
            // Delay fetch by 1 second to ensure page has loaded
            setTimeout(fetchData, 1000);
        }
    }

    // 将主要逻辑封装成函数以便重用
    function initializeHelper() {
        // 移除现有的按钮容器(如果存在)
        const existingContainer = document.querySelector('.labex-helper-container');
        if (existingContainer) {
            existingContainer.remove();
        }

        // 获取当前主题模式
        const currentTheme = getUserThemePreference();
        const isDarkMode = currentTheme === 'dark';

        // 从 localStorage 中获取信息卡片显示状态,默认为显示
        let isStatsCardVisible = localStorage.getItem('labex_stats_card_visible') !== 'false';

        // Create button container
        const buttonContainer = document.createElement('div');
        buttonContainer.classList.add('labex-helper-container');
        buttonContainer.style.cssText = `
            position: fixed;
            left: 20px;
            bottom: 20px;
            z-index: 9999;
            font-family: Maple Mono NF CN, IBM Plex Mono, monospace;
        `;

        // Create menu container with modern design
        const menuContainer = document.createElement('div');
        menuContainer.style.cssText = `
            display: none;
            flex-direction: column;
            gap: 4px;
            background: ${isDarkMode ? 'rgba(30, 30, 30, 0.95)' : 'rgba(255, 255, 255, 0.95)'};
            backdrop-filter: blur(10px);
            padding: 8px;
            border-radius: 12px;
            box-shadow: ${isDarkMode ? '0 4px 20px rgba(0, 0, 0, 0.2)' : '0 4px 20px rgba(0, 0, 0, 0.08)'};
            margin-bottom: 8px;
            min-width: 160px;
            border: 1px solid ${isDarkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
            transform-origin: bottom left;
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
        `;

        // Modern style for menu items
        const createMenuItem = (text, icon, isDisabled = false) => {
            const item = document.createElement('button');
            item.innerHTML = `${icon} ${text}`;

            const baseStyles = `
                padding: 6px 10px;
                background: transparent;
                border: none;
                border-radius: 8px;
                cursor: ${isDisabled ? 'not-allowed' : 'pointer'};
                font-size: 12px;
                font-weight: 500;
                color: ${isDisabled ? (isDarkMode ? '#6B7280' : '#9CA3AF') : (isDarkMode ? '#E5E7EB' : '#374151')};
                display: flex;
                align-items: center;
                gap: 6px;
                width: 100%;
                text-align: left;
                transition: all 0.2s ease;
                position: relative;
                overflow: hidden;
                opacity: ${isDisabled ? '0.6' : '1'};
            `;

            item.style.cssText = baseStyles;

            if (!isDisabled) {
                item.onmouseover = function () {
                    this.style.backgroundColor = isDarkMode ? 'rgba(75, 85, 99, 0.2)' : 'rgba(75, 85, 99, 0.08)';
                    this.style.transform = 'translateX(4px)';
                    this.style.color = isDarkMode ? '#F3F4F6' : '#4B5563';
                };
                item.onmouseout = function () {
                    this.style.backgroundColor = 'transparent';
                    this.style.transform = 'translateX(0)';
                    this.style.color = isDarkMode ? '#E5E7EB' : '#374151';
                };
            }

            return item;
        };

        // Modern floating button
        const floatingButton = document.createElement('button');
        floatingButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="12" y1="16" x2="12" y2="16"></line></svg>`;
        floatingButton.style.cssText = `
            width: 28px;
            height: 28px;
            border-radius: 14px;
            background: ${isDarkMode ?
                'linear-gradient(135deg, #2563EB, #1D4ED8)' :
                'linear-gradient(135deg, #4B5563, #374151)'};
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: ${isDarkMode ?
                '0 2px 10px rgba(37, 99, 235, 0.4)' :
                '0 2px 10px rgba(75, 85, 99, 0.3)'};
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            color: white;
            position: relative;
        `;

        // 添加拖动功能
        let isDragging = false;
        let offsetX, offsetY;

        // 处理鼠标按下事件,开始拖动
        buttonContainer.addEventListener('mousedown', function (e) {
            // 只在点击悬浮球而不是菜单时启用拖动
            if (!menuContainer.contains(e.target)) {
                isDragging = true;
                // 记录鼠标与按钮容器左上角的相对位置
                offsetX = e.clientX - buttonContainer.getBoundingClientRect().left;
                offsetY = e.clientY - buttonContainer.getBoundingClientRect().top;

                // 更改光标样式
                buttonContainer.style.cursor = 'grabbing';

                // 防止拖动时触发按钮点击
                e.preventDefault();
            }
        });

        // 处理鼠标移动事件,实现拖动
        document.addEventListener('mousemove', function (e) {
            if (isDragging) {
                // 计算新位置
                let newLeft = e.clientX - offsetX;
                let newTop = e.clientY - offsetY;

                // 限制不超出视口
                newLeft = Math.max(10, Math.min(window.innerWidth - buttonContainer.offsetWidth - 10, newLeft));
                newTop = Math.max(10, Math.min(window.innerHeight - buttonContainer.offsetHeight - 10, newTop));

                // 更新位置
                buttonContainer.style.left = newLeft + 'px';
                buttonContainer.style.bottom = (window.innerHeight - newTop - buttonContainer.offsetHeight) + 'px';
            }
        });

        // 处理鼠标释放事件,停止拖动
        document.addEventListener('mouseup', function () {
            if (isDragging) {
                isDragging = false;
                buttonContainer.style.cursor = 'default';
            }
        });

        // 鼠标离开窗口时停止拖动
        document.addEventListener('mouseleave', function () {
            if (isDragging) {
                isDragging = false;
                buttonContainer.style.cursor = 'default';
            }
        });

        // Toggle menu visibility
        let isMenuVisible = false;
        floatingButton.onclick = function (e) {
            if (!isDragging) {
                e.stopPropagation();
                isMenuVisible = !isMenuVisible;
                menuContainer.style.display = isMenuVisible ? 'flex' : 'none';
                menuContainer.style.zIndex = '9999'; // 确保菜单在最上层
                const labDataContainer = buttonContainer.querySelector('.labex-stats-container');
                if (labDataContainer) {
                    if (isMenuVisible) {
                        labDataContainer.style.opacity = '0';
                        labDataContainer.style.transform = 'translateY(10px)';
                        labDataContainer.style.pointerEvents = 'none';
                    } else {
                        labDataContainer.style.opacity = '1';
                        labDataContainer.style.transform = 'translateY(0)';
                        labDataContainer.style.pointerEvents = 'auto';
                    }
                }
            }
        };

        // Close menu when clicking outside
        document.addEventListener('click', function () {
            if (isMenuVisible) {
                isMenuVisible = false;
                menuContainer.style.display = 'none';
                const labDataContainer = buttonContainer.querySelector('.labex-stats-container');
                if (labDataContainer) {
                    labDataContainer.style.opacity = '1';
                    labDataContainer.style.transform = 'translateY(0)';
                }
            }
        });

        menuContainer.addEventListener('click', function (e) {
            e.stopPropagation();
        });

        // Button hover effect
        floatingButton.onmouseover = function () {
            this.style.transform = 'scale(1.05)';
            this.style.boxShadow = isDarkMode ?
                '0 6px 24px rgba(37, 99, 235, 0.5)' :
                '0 6px 24px rgba(75, 85, 99, 0.4)';
        };

        floatingButton.onmouseout = function () {
            this.style.transform = 'scale(1)';
            this.style.boxShadow = isDarkMode ?
                '0 4px 20px rgba(37, 99, 235, 0.4)' :
                '0 4px 20px rgba(75, 85, 99, 0.3)';
        };

        // Create menu items with Feather icons - check routes for disabled state
        const isLabsRoute = window.location.href.includes('/labs/');
        const isTutorialsRoute = window.location.href.includes('/tutorials/');

        const langMenuItem = createMenuItem('Switch Language', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>');
        const modeMenuItem = createMenuItem('Lab ⇌ Tutorial', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>', !(isLabsRoute || isTutorialsRoute));
        const clearCacheMenuItem = createMenuItem('Clear Cache', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>');
        const quickStartMenuItem = createMenuItem('Quick Start', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>');
        const zenModeMenuItem = createMenuItem('Zen Mode', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>', !isLabsRoute);
        const closeMenuItem = createMenuItem('Close Helper', '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>');

        // 添加信息卡片切换菜单项
        const toggleStatsCardMenuItem = createMenuItem(
            isStatsCardVisible ? 'Hide Stats Card' : 'Show Stats Card',
            `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>`,
            !isLabsRoute
        );

        // 信息卡片切换功能
        toggleStatsCardMenuItem.onclick = function (e) {
            e.stopPropagation();
            isStatsCardVisible = !isStatsCardVisible;
            localStorage.setItem('labex_stats_card_visible', isStatsCardVisible.toString());

            // 更新菜单项文本
            this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg> ${isStatsCardVisible ? 'Hide Stats Card' : 'Show Stats Card'}`;

            // 隐藏或显示统计卡片
            const labDataContainer = buttonContainer.querySelector('.labex-stats-container');
            if (labDataContainer) {
                if (isStatsCardVisible) {
                    labDataContainer.style.display = 'flex';
                    labDataContainer.style.pointerEvents = 'auto'; // 允许统计卡片接收点击事件
                    setTimeout(() => {
                        labDataContainer.style.opacity = '1';
                        labDataContainer.style.transform = 'translateY(0)';
                    }, 10);
                } else {
                    labDataContainer.style.opacity = '0';
                    labDataContainer.style.transform = 'translateY(10px)';
                    labDataContainer.style.pointerEvents = 'none';
                    setTimeout(() => {
                        labDataContainer.style.display = 'none';
                    }, 300); // 等待过渡动画完成
                }
            } else if (isStatsCardVisible && isLabsRoute) {
                // 如果卡片不存在但用户选择显示且在实验室页面,则重新获取数据
                const currentUrl = window.location.href;
                const labMatch = currentUrl.match(/\/labs\/([^\/?#]+)/);
                if (labMatch && labMatch[1]) {
                    const labAlias = labMatch[1];
                    fetchAndDisplayLabData(labAlias, buttonContainer);
                }
            }

            // 关闭菜单
            isMenuVisible = false;
            menuContainer.style.display = 'none';
        };

        // IBM Plex Mono Font Toggle functionality
        let isPlexFontEnabled = localStorage.getItem('labex_use_plex_font') === 'true';
        const plexFontMenuItem = createMenuItem(
            isPlexFontEnabled ? 'Disable Mono' : 'Enable Mono',
            '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"></path><path d="M9 20h6"></path><path d="M12 4v16"></path></svg>'
        );

        // Function to apply IBM Plex Mono font
        const applyPlexFont = () => {
            const styleId = 'labex-plex-font-style';

            if (isPlexFontEnabled) {
                // Create style element if it doesn't exist
                if (!document.getElementById(styleId)) {
                    const styleElement = document.createElement('style');
                    styleElement.id = styleId;
                    styleElement.textContent = `
                        * {
                            font-family: 'Maple Mono NF CN','IBM Plex Mono', monospace !important;
                        }
                    `;
                    document.head.appendChild(styleElement);
                }
            } else {
                // Remove style if it exists
                const existingStyle = document.getElementById(styleId);
                if (existingStyle) {
                    existingStyle.remove();
                }
            }
        };

        // Toggle IBM Plex Mono font
        plexFontMenuItem.onclick = function (e) {
            e.stopPropagation();
            isPlexFontEnabled = !isPlexFontEnabled;
            localStorage.setItem('labex_use_plex_font', isPlexFontEnabled);

            // Update menu item text
            this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"></path><path d="M9 20h6"></path><path d="M12 4v16"></path></svg> ${isPlexFontEnabled ? 'Disable Mono' : 'Enable Mono'}`;

            // Apply or remove the font
            applyPlexFont();
        };

        // Apply font on page load if enabled
        applyPlexFont();

        // Modern language submenu
        const langSubmenu = document.createElement('div');
        langSubmenu.style.cssText = `
            display: none;
            position: absolute;
            left: -12px;
            right: -12px;
            bottom: 100%;
            background: ${isDarkMode ? 'rgba(30, 30, 30, 0.95)' : 'rgba(255, 255, 255, 0.95)'};
            backdrop-filter: blur(10px);
            padding: 10px;
            border-radius: 16px;
            box-shadow: ${isDarkMode ? '0 4px 20px rgba(0, 0, 0, 0.2)' : '0 4px 20px rgba(0, 0, 0, 0.08)'};
            margin-bottom: 24px;
            flex-direction: column;
            gap: 4px;
            border: 1px solid ${isDarkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
        `;

        const languages = [
            { code: 'en', name: 'English', flag: '🇬🇧' },
            { code: 'zh', name: '中文', flag: '🇨🇳' },
            { code: 'es', name: 'Español', flag: '🇪🇸' },
            { code: 'fr', name: 'Français', flag: '🇫🇷' },
            { code: 'de', name: 'Deutsch', flag: '🇩🇪' },
            { code: 'ja', name: '日本語', flag: '🇯🇵' },
            { code: 'ru', name: 'Русский', flag: '🇷🇺' }
        ];

        languages.forEach(lang => {
            const langOption = createMenuItem(lang.name, lang.flag);
            langOption.onclick = function (e) {
                e.stopPropagation();
                const currentUrl = window.location.href;
                const baseUrl = 'labex.io/';
                const urlParts = currentUrl.split(baseUrl);

                if (urlParts.length > 1) {
                    let path = urlParts[1];
                    path = path.replace(/^(zh|es|fr|de|ja|ru)\//, '');
                    const newPath = lang.code === 'en' ? path : `${lang.code}/${path}`;
                    const newUrl = `${urlParts[0]}${baseUrl}${newPath}`;
                    window.location.href = newUrl;
                }
            };
            langSubmenu.appendChild(langOption);
        });

        // Toggle language submenu on click
        let isLangSubmenuVisible = false;
        langMenuItem.onclick = function (e) {
            e.stopPropagation();
            isLangSubmenuVisible = !isLangSubmenuVisible;
            langSubmenu.style.display = isLangSubmenuVisible ? 'flex' : 'none';
        };

        const langMenuWrapper = document.createElement('div');
        langMenuWrapper.style.position = 'relative';
        langMenuWrapper.appendChild(langMenuItem);
        langMenuWrapper.appendChild(langSubmenu);

        // Close language submenu when clicking outside
        document.addEventListener('click', function () {
            if (isLangSubmenuVisible) {
                isLangSubmenuVisible = false;
                langSubmenu.style.display = 'none';
            }
        });

        langSubmenu.addEventListener('click', function (e) {
            e.stopPropagation();
        });

        // Mode switch functionality
        modeMenuItem.onclick = function () {
            // Skip if disabled
            if (!(isLabsRoute || isTutorialsRoute)) return;

            const currentUrl = window.location.href;
            let newUrl;

            if (currentUrl.includes('/tutorials/')) {
                newUrl = currentUrl.replace('/tutorials/', '/labs/');
            } else if (currentUrl.includes('/labs/')) {
                newUrl = currentUrl.replace('/labs/', '/tutorials/');
            }

            if (newUrl) {
                window.location.href = newUrl;
            }
        };

        // Clear cache functionality
        clearCacheMenuItem.onclick = async function () {
            try {
                const cacheNames = await caches.keys();
                await Promise.all(cacheNames.map(name => caches.delete(name)));
                localStorage.clear();
                sessionStorage.clear();
                window.location.reload(true);
            } catch (error) {
                console.error('Failed to clear cache:', error);
                alert('Failed to clear cache. Please try again.');
            }
        };

        // Zen Mode functionality
        zenModeMenuItem.onclick = function () {
            // Skip if disabled
            if (!isLabsRoute) return;

            const currentUrl = window.location.href;
            // Only work on lab pages
            if (currentUrl.includes('/labs/')) {
                const url = new URL(currentUrl);
                const hasParams = url.searchParams.has('hidelabby') || url.searchParams.has('hideheader');

                if (hasParams) {
                    // Remove zen mode parameters
                    url.searchParams.delete('hidelabby');
                    url.searchParams.delete('hideheader');
                } else {
                    // Add zen mode parameters
                    url.searchParams.set('hidelabby', 'true');
                    url.searchParams.set('hideheader', 'true');
                }

                window.location.href = url.toString();
            }
        };

        // Add tooltips to explain disabled state
        if (!isLabsRoute) {
            zenModeMenuItem.title = "Zen Mode is only available on Lab pages";
        }

        if (!(isLabsRoute || isTutorialsRoute)) {
            modeMenuItem.title = "Mode switch is only available on Lab or Tutorial pages";
        }

        // Hide the entire button container when close menu item is clicked
        closeMenuItem.onclick = function (e) {
            e.stopPropagation();
            buttonContainer.style.display = 'none';
        };

        // Quick Start functionality
        quickStartMenuItem.onclick = function (e) {
            e.stopPropagation();
            window.open('https://labex.io/labs/linux-your-first-linux-lab-270253?hidelabby=true&hideheader=true', '_blank');
        };

        // Theme toggle menu item
        const themeMenuItem = createMenuItem(
            isDarkMode ? 'Light Mode' : 'Dark Mode',
            `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                ${isDarkMode ?
                '<circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>' :
                '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>'}
            </svg>`
        );

        // Toggle theme functionality
        themeMenuItem.onclick = function (e) {
            e.stopPropagation();
            const newTheme = isDarkMode ? 'light' : 'dark';
            saveUserThemePreference(newTheme);
            window.location.reload(); // Reload to apply theme changes everywhere
        };

        // Update menu items addition
        menuContainer.appendChild(langMenuWrapper);
        menuContainer.appendChild(modeMenuItem);
        menuContainer.appendChild(quickStartMenuItem);
        menuContainer.appendChild(zenModeMenuItem);
        menuContainer.appendChild(plexFontMenuItem);
        menuContainer.appendChild(toggleStatsCardMenuItem); // 添加信息卡片切换选项
        menuContainer.appendChild(themeMenuItem); // 添加主题切换选项
        menuContainer.appendChild(clearCacheMenuItem);
        menuContainer.appendChild(closeMenuItem);

        // Add elements to container
        buttonContainer.appendChild(menuContainer);
        buttonContainer.appendChild(floatingButton);

        // Add container to page
        document.body.appendChild(buttonContainer);

        // --- Lab Data Fetching Logic ---
        const currentUrl = window.location.href;
        const labMatch = currentUrl.match(/\/labs\/([^\/?#]+)/);
        if (labMatch && labMatch[1] && isStatsCardVisible) {
            const labAlias = labMatch[1];
            fetchAndDisplayLabData(labAlias, buttonContainer);

            // 确保统计卡片上的链接可点击
            setTimeout(() => {
                const labDataContainer = buttonContainer.querySelector('.labex-stats-container');
                if (labDataContainer) {
                    labDataContainer.style.pointerEvents = 'auto';

                    // 处理菜单和卡片的显示优先级
                    document.addEventListener('click', function (e) {
                        // 当点击菜单按钮且菜单打开时,暂时禁用卡片的点击事件
                        if (isMenuVisible && !menuContainer.contains(e.target)) {
                            const statsCard = buttonContainer.querySelector('.labex-stats-container');
                            if (statsCard) {
                                statsCard.style.pointerEvents = 'none';
                            }
                        }
                    });
                }
            }, 1500);
        }
        // --- End Lab Data Fetching Logic ---
    }

    // Wait for the page to fully load before initializing the helper initially
    window.addEventListener('load', () => {
        initializeHelper();
    });

    // 监听 URL 变化 (for SPA navigations)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            initializeHelper();
        }
    }).observe(document, { subtree: true, childList: true });

})();