【工程】强力学术Plus

· · 科技·工程

\textup{Cnblogs link.}

这个人平时很不自觉,喜欢水谷但不喜欢上什么其它网站。

于是她参考了这篇文章,花了一个晚上将近两周调了十几版,最终用 AI 做出了这个插件。

直接粘贴到篡改猴里面保存就能用了。

功能介绍:

那就上代码。

:::success[\textup{V1 Code.}]

// ==UserScript==
// @name         洛谷强力学术pro
// @namespace    http://tampermonkey.net/
// @version      10.12
// @author       wendywan
// @icon         https://cdn.luogu.com.cn/upload/image_hosting/psd5a03h.png
// @match        *://*/*
// @grant        none
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    const STORAGE_KEY_PID = "luogu_study_plan_pids";
    const STORAGE_KEY_SOLUTION = "luogu_study_plan_solution";
    const STORAGE_KEY_FOCUS_ACTIVE = "luogu_focus_active";
    const STORAGE_KEY_FOCUS_END = "luogu_focus_end_time";
    const STORAGE_KEY_FOCUS_DURATION = "luogu_focus_duration";
    const STORAGE_KEY_FOCUS_COLOR = "luogu_focus_color";
    const STORAGE_KEY_FOCUS_POS = "luogu_focus_position";
    const STORAGE_KEY_FOCUS_REMAINING = "luogu_focus_remaining";
    const STORAGE_KEY_FOCUS_STATE = "luogu_focus_state";
    const STORAGE_KEY_REST_DURATION = "luogu_rest_duration";
    const STORAGE_KEY_CURRENT_MODE = "luogu_focus_current_mode";
    const DEFAULT_WORK_DURATION = 25;
    const DEFAULT_REST_DURATION = 5;
    const DEFAULT_COLOR = "#ff6b6b";
    const DEFAULT_POSITION = { x: 20, y: 100 };
    const DEFAULT_SIZE = { width: 120, height: 100 };
    const MAX_PIDS = 6;

    function getSavePids() {
        try {
            const pids = JSON.parse(localStorage.getItem(STORAGE_KEY_PID)) || [];
            return pids.filter(pid => pid.trim() !== '');
        } catch {
            return [];
        }
    }

    function savePids(pids) {
        localStorage.setItem(STORAGE_KEY_PID, JSON.stringify(pids));
    }

    function removePid(pid) {
        const pids = getSavePids();
        const index = pids.indexOf(pid);
        if (index !== -1) {
            pids.splice(index, 1);
            savePids(pids);
            return true;
        }
        return false;
    }

    function getSolutionStatus() {
        return localStorage.getItem(STORAGE_KEY_SOLUTION) !== 'false';
    }

    function saveSolutionStatus(status) {
        localStorage.setItem(STORAGE_KEY_SOLUTION, status);
    }

    function isFocusActive() {
        const active = localStorage.getItem(STORAGE_KEY_FOCUS_ACTIVE) === 'true';
        if (!active) return false;

        // 修复:暂停状态下不检查结束时间,避免刷新时重置
        const state = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        if (state === 'paused') {
            return true;
        }

        const endTime = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_END) || '0');
        const now = Date.now();
        if (active && now >= endTime) {
            endFocusMode();
            return false;
        }
        return active;
    }

    function startFocusMode(durationMinutes, isWorkMode) {
        const endTime = Date.now() + durationMinutes * 60 * 1000;
        localStorage.setItem(STORAGE_KEY_FOCUS_ACTIVE, 'true');
        localStorage.setItem(STORAGE_KEY_FOCUS_END, endTime.toString());
        if (isWorkMode) {
            localStorage.setItem(STORAGE_KEY_FOCUS_DURATION, durationMinutes.toString());
        }
        localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'running');
        localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
    }

    function pauseFocusMode(remainingSeconds) {
        localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'paused');
        localStorage.setItem(STORAGE_KEY_FOCUS_REMAINING, remainingSeconds.toString());
        localStorage.removeItem(STORAGE_KEY_FOCUS_END);
    }

    function endFocusMode() {
        localStorage.removeItem(STORAGE_KEY_FOCUS_ACTIVE);
        localStorage.removeItem(STORAGE_KEY_FOCUS_END);
        localStorage.removeItem(STORAGE_KEY_FOCUS_STATE);
        localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
        localStorage.removeItem(STORAGE_KEY_CURRENT_MODE);
    }

    function getWorkDuration() {
        return parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_DURATION) || DEFAULT_WORK_DURATION);
    }

    function getRestDuration() {
        return parseInt(localStorage.getItem(STORAGE_KEY_REST_DURATION) || DEFAULT_REST_DURATION);
    }

    function saveDurations(work, rest) {
        localStorage.setItem(STORAGE_KEY_FOCUS_DURATION, work.toString());
        localStorage.setItem(STORAGE_KEY_REST_DURATION, rest.toString());
    }

    function getFocusColor() {
        return localStorage.getItem(STORAGE_KEY_FOCUS_COLOR) || DEFAULT_COLOR;
    }

    function saveFocusColor(color) {
        localStorage.setItem(STORAGE_KEY_FOCUS_COLOR, color);
    }

    function getFocusPosition() {
        try {
            return JSON.parse(localStorage.getItem(STORAGE_KEY_FOCUS_POS) || JSON.stringify(DEFAULT_POSITION));
        } catch {
            return DEFAULT_POSITION;
        }
    }

    function saveFocusPosition(pos) {
        localStorage.setItem(STORAGE_KEY_FOCUS_POS, JSON.stringify(pos));
    }

    function getRedirectUrl() {
        const pids = getSavePids();
        return pids.length > 0
            ? `https://www.luogu.com.cn/problem/${pids[0]}`
            : 'https://www.luogu.com.cn/problem/list';
    }

    function shouldAllow() {
        const currentURL = window.location.href;
        const isLuoguDomain = /^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(currentURL);
        if (!isLuoguDomain) return true;
        if (!isFocusActive()) return true;

        // 修复:休息模式下允许访问所有页面
        const currentMode = localStorage.getItem(STORAGE_KEY_CURRENT_MODE);
        if (currentMode === 'rest') {
            return true;
        }

        const path = window.location.pathname.toLowerCase();
        return (
            path.startsWith('/problem/') ||
            path.startsWith('/paste') ||
            path.startsWith('/paste/') ||
            path.startsWith('/record/')
        );
    }

    function blockSolutionAccess() {
        if (!/\/problem\/solution\//i.test(window.location.pathname)) {
            return false;
        }
        if (!getSolutionStatus()) {
            window.location.replace(getRedirectUrl());
            return true;
        }
        return false;
    }

    function monitorSolutionElements() {
        if (!getSolutionStatus()) {
            const observer = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== 1) return;
                        const selectors = [
                            'a[href*="/problem/solution/"]',
                            '[class*="solution"]',
                            '[class*="answer"]'
                        ];
                        try {
                            node.querySelectorAll(selectors.join(',')).forEach(el => {
                                if ((el.textContent && el.textContent.includes('题解')) ||
                                    (el.href && el.href.includes('/problem/solution/'))) {
                                    el.style.display = 'none !important';
                                    el.style.visibility = 'hidden !important';
                                    el.style.height = '0 !important';
                                    el.style.overflow = 'hidden !important';
                                }
                            });
                        } catch (e) {}
                    });
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    if (blockSolutionAccess()) return;
    if (isFocusActive() && !shouldAllow()) {
        window.location.replace(getRedirectUrl());
        return;
    }

    function createPomodoroWidget() {
        if (!/^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(window.location.href)) return;
        const workDuration = getWorkDuration();
        const restDuration = getRestDuration();
        const config = {
            workDuration: workDuration,
            restDuration: restDuration,
            color: getFocusColor(),
            position: getFocusPosition()
        };
        const style = document.createElement('style');
        style.textContent = `
            #luogu-pomodoro-widget {
                position: fixed;
                z-index: 99999;
                border-radius: 10px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.15);
                overflow: hidden;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
                user-select: none;
                width: ${DEFAULT_SIZE.width}px;
                height: ${DEFAULT_SIZE.height}px;
                background: white;
                border: 1px solid #e0e0e0;
                cursor: move;
            }
            #luogu-pomodoro-widget:hover {
                box-shadow: 0 3px 15px rgba(0,0,0,0.2);
            }
            #luogu-pomodoro-time {
                font-size: 20px;
                font-weight: bold;
                text-align: center;
                color: #333;
                padding: 12px 0;
                height: 40px;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            #luogu-pomodoro-progress {
                height: 4px;
                background: #f0f0f0;
                border-radius: 2px;
                margin: 4px 8px;
                overflow: hidden;
            }
            #luogu-pomodoro-progress-bar {
                height: 100%;
                width: 0%;
                background: ${config.color};
                border-radius: 2px;
                transition: width 0.5s ease;
            }
            #luogu-pomodoro-controls {
                display: flex;
                justify-content: center;
                gap: 6px;
                padding: 6px 0;
                background: #f8f9fa;
                border-top: 1px solid #eee;
            }
            .pomodoro-btn {
                width: 48%;
                height: 22px;
                border: none;
                border-radius: 3px;
                font-size: 11px;
                font-weight: 500;
                cursor: pointer;
                transition: all 0.2s;
                user-select: none;
                text-align: center;
                line-height: 22px;
                color: #333;
            }
            .pomodoro-btn:active {
                transform: scale(0.95);
            }
            .pomodoro-btn.start {
                background: #e8f5e9;
                color: #2e7d32;
            }
            .pomodoro-btn.pause {
                background: #fff8e1;
                color: #ff8f00;
            }
            .pomodoro-btn.stop {
                background: #ffebee;
                color: #c62828;
            }
            .pomodoro-btn.start:disabled,
            .pomodoro-btn.pause:disabled {
                opacity: 0.6;
                cursor: not-allowed;
                transform: none !important;
            }
            #luogu-pomodoro-settings {
                position: absolute;
                top: 6px;
                right: 6px;
                width: 18px;
                height: 18px;
                border-radius: 50%;
                background: #000;
                color: white;
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 12px;
                cursor: pointer;
                z-index: 10;
            }
            #luogu-pomodoro-settings:hover {
                background: #333;
                transform: scale(1.1);
            }
        `;
        document.head.appendChild(style);
        const widget = document.createElement('div');
        widget.id = 'luogu-pomodoro-widget';
        widget.style.left = `${config.position.x}px`;
        widget.style.top = `${config.position.y}px`;
        widget.innerHTML = `
            <div id="luogu-pomodoro-time">25:00</div>
            <div id="luogu-pomodoro-progress">
                <div id="luogu-pomodoro-progress-bar"></div>
            </div>
            <div id="luogu-pomodoro-controls">
                <button class="pomodoro-btn start">开始</button>
                <button class="pomodoro-btn stop">结束</button>
            </div>
            <div id="luogu-pomodoro-settings">⚙</div>
        `;
        document.body.appendChild(widget);
        const settingsBtn = widget.querySelector('#luogu-pomodoro-settings');
        settingsBtn.onclick = (e) => {
            e.stopPropagation();
            createSettingUI();
        };
        let timer = null;
        let remainingTime = workDuration * 60;
        let currentMode = 'work';
        let isResuming = false;
        let autoStart = false;
        const savedMode = localStorage.getItem(STORAGE_KEY_CURRENT_MODE);
        if (savedMode === 'rest') {
            currentMode = 'rest';
            remainingTime = restDuration * 60;
        }
        const timeDisplay = widget.querySelector('#luogu-pomodoro-time');
        const progressBar = widget.querySelector('#luogu-pomodoro-progress-bar');
        const startBtn = widget.querySelector('.pomodoro-btn.start');
        const stopBtn = widget.querySelector('.pomodoro-btn.stop');

        // 修复:统一状态恢复逻辑,支持休息模式
        const savedState = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        const savedRemaining = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_REMAINING) || '0');
        const endTime = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_END) || '0');
        const now = Date.now();

        if (savedState === 'paused' && savedRemaining > 0) {
            // 恢复暂停状态(工作或休息)
            remainingTime = savedRemaining;
            isResuming = true;
            autoStart = false;
        } else if (savedState === 'running' && endTime > now) {
            // 恢复运行状态(工作或休息)
            remainingTime = Math.floor((endTime - now) / 1000);
            if (remainingTime > 0) {
                isResuming = true;
                autoStart = true;
            }
        }

        // 修复:根据保存状态初始化按钮状态(关键修复)
        if (savedState === 'paused' && savedRemaining > 0) {
            startBtn.textContent = '继续';
            startBtn.classList.add('pause');
            startBtn.classList.remove('start');
            startBtn.disabled = false;
            stopBtn.disabled = false;
        } else if (savedState === 'running' && endTime > now && remainingTime > 0) {
            startBtn.textContent = '暂停';
            startBtn.classList.remove('pause');
            startBtn.classList.add('start');
            startBtn.disabled = false;
            stopBtn.disabled = false;
        } else {
            startBtn.textContent = '开始';
            startBtn.classList.add('start');
            startBtn.classList.remove('pause');
            startBtn.disabled = false;
            stopBtn.disabled = (currentMode === 'work' && remainingTime === workDuration * 60);
        }

        function updateDisplay() {
            const mins = Math.floor(remainingTime / 60);
            const secs = remainingTime % 60;
            timeDisplay.textContent = `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
            const totalSeconds = (currentMode === 'work' ? workDuration : restDuration) * 60;
            const progress = ((totalSeconds - remainingTime) / totalSeconds) * 100;
            progressBar.style.width = `${Math.max(0, Math.min(100, progress))}%`;
        }

        function toggleTimer() {
            if (timer) {
                clearInterval(timer);
                timer = null;
                pauseFocusMode(remainingTime);
                localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                isResuming = true;
                startBtn.textContent = '继续';
                startBtn.classList.add('pause');
                startBtn.classList.remove('start');
                startBtn.disabled = false;
                stopBtn.disabled = false;
            } else {
                if (isResuming) {
                    const endTime = Date.now() + remainingTime * 1000;
                    localStorage.setItem(STORAGE_KEY_FOCUS_END, endTime.toString());
                    localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'running');
                    localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                    localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
                    isResuming = false;
                } else {
                    remainingTime = (currentMode === 'work' ? workDuration : restDuration) * 60;
                    // 修复:统一设置专注模式状态,支持休息模式
                    startFocusMode(
                        currentMode === 'work' ? workDuration : restDuration,
                        currentMode === 'work'
                    );
                    localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                }
                startBtn.textContent = '暂停';
                startBtn.classList.remove('pause');
                startBtn.classList.add('start');
                startBtn.disabled = false;
                stopBtn.disabled = false;
                timer = setInterval(() => {
                    remainingTime--;
                    updateDisplay();
                    if (remainingTime <= 0) {
                        clearInterval(timer);
                        timer = null;
                        if (currentMode === 'work') {
                            // 修复:工作结束进入休息模式,清除专注状态(不保存暂停状态)
                            currentMode = 'rest';
                            remainingTime = restDuration * 60;
                            endFocusMode(); // 清除工作计时状态
                            localStorage.setItem(STORAGE_KEY_CURRENT_MODE, 'rest');
                            updateDisplay();
                            startBtn.textContent = '开始';
                            startBtn.classList.add('start');
                            startBtn.classList.remove('pause');
                            startBtn.disabled = false;
                            stopBtn.disabled = false;
                        } else {
                            // 休息结束,完全结束专注模式
                            endFocusMode();
                            startBtn.textContent = '开始';
                            startBtn.classList.add('start');
                            startBtn.classList.remove('pause');
                            startBtn.disabled = false;
                            stopBtn.disabled = true;
                            // 重置为工作模式
                            currentMode = 'work';
                            remainingTime = workDuration * 60;
                            updateDisplay();
                        }
                    }
                }, 1000);
            }
        }

        function stopTimer() {
            clearInterval(timer);
            timer = null;
            remainingTime = workDuration * 60;
            currentMode = 'work';
            isResuming = false;
            updateDisplay();
            endFocusMode();
            startBtn.textContent = '开始';
            startBtn.classList.add('start');
            startBtn.classList.remove('pause');
            startBtn.disabled = false;
            stopBtn.disabled = true;
        }

        startBtn.onclick = toggleTimer;
        stopBtn.onclick = stopTimer;
        let isDragging = false;
        let offsetX, offsetY;
        widget.onmousedown = (e) => {
            if (e.target.id === 'luogu-pomodoro-settings') return;
            isDragging = true;
            const rect = widget.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            widget.style.cursor = 'move';
            e.preventDefault();
        };
        document.onmousemove = (e) => {
            if (!isDragging) return;
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;
            const maxX = window.innerWidth - widget.offsetWidth;
            const maxY = window.innerHeight - widget.offsetHeight;
            widget.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            widget.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
        };
        document.onmouseup = () => {
            if (isDragging) {
                isDragging = false;
                widget.style.cursor = 'default';
                const rect = widget.getBoundingClientRect();
                saveFocusPosition({
                    x: Math.round(rect.left),
                    y: Math.round(rect.top)
                });
            }
        };
        updateDisplay();
        if (autoStart) {
            setTimeout(() => {
                toggleTimer();
            }, 300);
        }
    }

    function createSettingUI() {
        if (!/^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(window.location.href)) return;
        const existingMask = document.getElementById('luogu-modal-mask');
        const existingBox = document.getElementById('luogu-modal-box');
        if (existingMask) existingMask.remove();
        if (existingBox) existingBox.remove();
        const style = document.createElement('style');
        style.textContent = `
            #luogu-modal-mask {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0,0,0,0.5);
                z-index: 99997;
                display: none;
            }
            #luogu-modal-box {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                width: 340px;
                padding: 20px;
                background: #fff;
                border-radius: 10px;
                z-index: 99998;
                display: none;
                box-shadow: 0 0 20px rgba(0,0,0,0.3);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
            }
            #luogu-modal-box h3 {
                margin: 0 0 15px 0;
                color: #222;
                text-align: center;
                font-size: 20px;
                border-bottom: 1px solid #eee;
                padding-bottom: 10px;
            }
            .luogu-form-group {
                margin-bottom: 15px;
            }
            .luogu-form-group label {
                display: block;
                margin-bottom: 8px;
                font-weight: 600;
                color: #333;
                font-size: 14px;
            }
            #luogu-pid-input {
                width: 100%;
                padding: 10px;
                box-sizing: border-box;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 14px;
                transition: border-color 0.3s;
            }
            #luogu-pid-input:focus {
                border-color: #0099ff;
                outline: none;
            }
            #luogu-solution-switch {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 8px 0;
            }
            #luogu-solution-switch input {
                width: 18px;
                height: 18px;
                cursor: pointer;
            }
            .duration-settings {
                display: flex;
                gap: 10px;
            }
            .duration-col {
                flex: 1;
            }
            .duration-input {
                width: 100%;
                padding: 8px;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 14px;
                margin-top: 4px;
            }
            .pid-list {
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                margin-top: 8px;
                min-height: 30px;
            }
            .pid-item {
                background: #f0f0f0;
                padding: 5px 10px;
                border-radius: 12px;
                font-size: 13px;
                display: flex;
                align-items: center;
                gap: 5px;
                cursor: pointer;
                transition: all 0.2s;
            }
            .pid-item:hover {
                background: #e0e0e0;
                transform: scale(1.02);
            }
            .pid-item .remove {
                color: #ff4444;
                cursor: pointer;
                font-size: 14px;
                margin-left: 4px;
            }
            #luogu-color-input {
                width: 100%;
                height: 30px;
                border: none;
                border-radius: 4px;
                padding: 0;
                margin-top: 5px;
                cursor: pointer;
            }
            #luogu-modal-btn-group {
                display: flex;
                gap: 10px;
                margin-top: 15px;
                padding-top: 10px;
                border-top: 1px solid #f5f5f5;
            }
            #luogu-save-btn, #luogu-cancel-btn {
                flex: 1;
                padding: 10px;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
                transition: all 0.2s;
            }
            #luogu-save-btn {
                background: #0099ff;
                color: white;
            }
            #luogu-save-btn:hover {
                background: #0077cc;
            }
            #luogu-cancel-btn {
                background: #f0f0f0;
                color: #666;
            }
            #luogu-cancel-btn:hover {
                background: #e0e0e0;
            }
            .luogu-hint {
                font-size: 12px;
                color: #888;
                margin-top: 5px;
                line-height: 1.4;
            }
        `;
        document.head.appendChild(style);
        const mask = document.createElement('div');
        mask.id = 'luogu-modal-mask';
        mask.onclick = () => {
            mask.style.display = 'none';
            modalBox.style.display = 'none';
        };
        document.body.appendChild(mask);
        const modalBox = document.createElement('div');
        modalBox.id = 'luogu-modal-box';
        modalBox.innerHTML = `
            <h3>专注设置</h3>
            <div class="luogu-form-group">
                <label>题目计划</label>
                <input type="text" id="luogu-pid-input" placeholder="输入题号(如:P1000)">
                <div class="luogu-hint">最多添加6道题目,点击题号跳转</div>
                <div class="pid-list" id="pid-list"></div>
            </div>
            <div class="luogu-form-group">
                <label>题解访问</label>
                <div id="luogu-solution-switch">
                    <input type="checkbox" id="luogu-solution-checkbox">
                    <span>允许查看题解(独立控制,不受计时影响)</span>
                </div>
            </div>
            <div class="luogu-form-group">
                <label>番茄钟时长(分钟)</label>
                <div class="duration-settings">
                    <div class="duration-col">
                        <div>工作</div>
                        <input type="number" id="luogu-work-input" class="duration-input" min="1" max="120" value="${getWorkDuration()}">
                    </div>
                    <div class="duration-col">
                        <div>休息</div>
                        <input type="number" id="luogu-rest-input" class="duration-input" min="1" max="60" value="${getRestDuration()}">
                    </div>
                </div>
            </div>
            <div class="luogu-form-group">
                <label>番茄钟颜色</label>
                <input type="color" id="luogu-color-input" value="${getFocusColor()}">
            </div>
            <div id="luogu-modal-btn-group">
                <button id="luogu-save-btn">保存</button>
                <button id="luogu-cancel-btn">取消</button>
            </div>
        `;
        document.body.appendChild(modalBox);
        function renderPidList() {
            const pidList = document.getElementById('pid-list');
            pidList.innerHTML = '';
            const pids = getSavePids();
            if (pids.length === 0) {
                pidList.innerHTML = '<div style="text-align:center;color:#999;padding:5px 0">暂无题目</div>';
                return;
            }
            pids.forEach(pid => {
                const item = document.createElement('div');
                item.className = 'pid-item';
                item.innerHTML = `${pid}<span class="remove">×</span>`;
                item.addEventListener('click', (e) => {
                    if (e.target.classList.contains('remove')) return;
                    window.location.href = `https://www.luogu.com.cn/problem/${pid}`;
                });
                const removeBtn = item.querySelector('.remove');
                removeBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (removePid(pid)) {
                        renderPidList();
                    }
                });
                pidList.appendChild(item);
            });
        }
        document.getElementById('luogu-save-btn').onclick = () => {
            const pidInput = document.getElementById('luogu-pid-input');
            const pid = pidInput.value.trim().toUpperCase();
            const solutionChecked = document.getElementById('luogu-solution-checkbox').checked;
            const workDuration = parseInt(document.getElementById('luogu-work-input').value) || DEFAULT_WORK_DURATION;
            const restDuration = parseInt(document.getElementById('luogu-rest-input').value) || DEFAULT_REST_DURATION;
            const color = document.getElementById('luogu-color-input').value;
            if (workDuration < 1 || workDuration > 120) {
                alert('工作时长必须在 1-120 分钟之间');
                return;
            }
            if (restDuration < 1 || restDuration > 60) {
                alert('休息时长必须在 1-60 分钟之间');
                return;
            }
            if (pid && !/^(P|SP)\d{4,6}$/i.test(pid)) {
                alert('题号格式错误!\n正确格式:P1000 或 SP123');
                return;
            }
            const pids = getSavePids();
            if (pid && pids.length < MAX_PIDS && !pids.includes(pid)) {
                pids.push(pid);
                savePids(pids);
            }
            saveSolutionStatus(solutionChecked);
            saveDurations(workDuration, restDuration);
            saveFocusColor(color);
            alert('设置已保存!');
            mask.style.display = 'none';
            modalBox.style.display = 'none';
            renderPidList();
            monitorSolutionElements();
        };
        document.getElementById('luogu-cancel-btn').onclick = () => {
            mask.style.display = 'none';
            modalBox.style.display = 'none';
        };
        document.getElementById('luogu-pid-input').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                document.getElementById('luogu-save-btn').click();
            }
        });
        mask.style.display = 'block';
        modalBox.style.display = 'block';
        document.getElementById('luogu-pid-input').value = '';
        document.getElementById('luogu-solution-checkbox').checked = getSolutionStatus();
        renderPidList();
    }

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

    window.addEventListener('load', () => {
        monitorSolutionElements();
    });
})();

:::

\textup{Upd on 2026.3.7}

注意到我们的添加题库出了一点问题,可能是因为在原文章基础上进行开发,没有把入门与面试、CF、AT 和 UVA 题库的题目添加到计划的功能。

在完善这个功能的同时,我正添加了在学习过程中按暂停可以暂时访问其他网页的功能。

(否则暂停键没有起到任何作用吧。)

包括考虑将在未来添加一个可以选择是否拦截所有除题目外的网址的模式,但是调了好久没整出来。

:::success[\textup{V2 Code.}]

// ==UserScript==
// @name         洛谷强力学术 pro
// @namespace    http://tampermonkey.net/
// @version      10.57
// @author       wendywan
// @icon         https://cdn.luogu.com.cn/upload/image_hosting/psd5a03h.png  
// @match        *://*/*
// @grant        none
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    const STORAGE_KEY_PID = "luogu_study_plan_pids";
    const STORAGE_KEY_SOLUTION = "luogu_study_plan_solution";
    const STORAGE_KEY_FOCUS_ACTIVE = "luogu_focus_active";
    const STORAGE_KEY_FOCUS_END = "luogu_focus_end_time";
    const STORAGE_KEY_FOCUS_DURATION = "luogu_focus_duration";
    const STORAGE_KEY_FOCUS_COLOR = "luogu_focus_color";
    const STORAGE_KEY_FOCUS_POS = "luogu_focus_position";
    const STORAGE_KEY_FOCUS_REMAINING = "luogu_focus_remaining";
    const STORAGE_KEY_FOCUS_STATE = "luogu_focus_state";
    const STORAGE_KEY_REST_DURATION = "luogu_rest_duration";
    const STORAGE_KEY_CURRENT_MODE = "luogu_focus_current_mode";
    const DEFAULT_WORK_DURATION = 25;
    const DEFAULT_REST_DURATION = 5;
    const DEFAULT_COLOR = "#ff6b6b";
    const DEFAULT_POSITION = { x: 20, y: 100 };
    const DEFAULT_SIZE = { width: 120, height: 100 };
    const MAX_PIDS = 6;

    function getSavePids() {
        try {
            const pids = JSON.parse(localStorage.getItem(STORAGE_KEY_PID)) || [];
            return pids.filter(pid => pid.trim() !== '');
        } catch {
            return [];
        }
    }

    function savePids(pids) {
        localStorage.setItem(STORAGE_KEY_PID, JSON.stringify(pids));
    }

    function removePid(pid) {
        const pids = getSavePids();
        const index = pids.indexOf(pid);
        if (index !== -1) {
            pids.splice(index, 1);
            savePids(pids);
            return true;
        }
        return false;
    }

    function getSolutionStatus() {
        return localStorage.getItem(STORAGE_KEY_SOLUTION) !== 'false';
    }

    function saveSolutionStatus(status) {
        localStorage.setItem(STORAGE_KEY_SOLUTION, status);
    }

    function isFocusActive() {
        const active = localStorage.getItem(STORAGE_KEY_FOCUS_ACTIVE) === 'true';
        if (!active) return false;

        const state = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        if (state === 'paused') {
            return true;
        }

        const endTime = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_END) || '0');
        const now = Date.now();
        if (active && now >= endTime) {
            endFocusMode();
            return false;
        }
        return active;
    }

    function startFocusMode(durationMinutes, isWorkMode) {
        const endTime = Date.now() + durationMinutes * 60 * 1000;
        localStorage.setItem(STORAGE_KEY_FOCUS_ACTIVE, 'true');
        localStorage.setItem(STORAGE_KEY_FOCUS_END, endTime.toString());
        if (isWorkMode) {
            localStorage.setItem(STORAGE_KEY_FOCUS_DURATION, durationMinutes.toString());
        }
        localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'running');
        localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
    }

    function pauseFocusMode(remainingSeconds) {
        localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'paused');
        localStorage.setItem(STORAGE_KEY_FOCUS_REMAINING, remainingSeconds.toString());
        localStorage.removeItem(STORAGE_KEY_FOCUS_END);
    }

    function endFocusMode() {
        localStorage.removeItem(STORAGE_KEY_FOCUS_ACTIVE);
        localStorage.removeItem(STORAGE_KEY_FOCUS_END);
        localStorage.removeItem(STORAGE_KEY_FOCUS_STATE);
        localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
        localStorage.removeItem(STORAGE_KEY_CURRENT_MODE);
    }

    function getWorkDuration() {
        return parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_DURATION) || DEFAULT_WORK_DURATION);
    }

    function getRestDuration() {
        return parseInt(localStorage.getItem(STORAGE_KEY_REST_DURATION) || DEFAULT_REST_DURATION);
    }

    function saveDurations(work, rest) {
        localStorage.setItem(STORAGE_KEY_FOCUS_DURATION, work.toString());
        localStorage.setItem(STORAGE_KEY_REST_DURATION, rest.toString());
    }

    function getFocusColor() {
        return localStorage.getItem(STORAGE_KEY_FOCUS_COLOR) || DEFAULT_COLOR;
    }

    function saveFocusColor(color) {
        localStorage.setItem(STORAGE_KEY_FOCUS_COLOR, color);
    }

    function getFocusPosition() {
        try {
            return JSON.parse(localStorage.getItem(STORAGE_KEY_FOCUS_POS) || JSON.stringify(DEFAULT_POSITION));
        } catch {
            return DEFAULT_POSITION;
        }
    }

    function saveFocusPosition(pos) {
        localStorage.setItem(STORAGE_KEY_FOCUS_POS, JSON.stringify(pos));
    }

    function getRedirectUrl() {
        const pids = getSavePids();
        return pids.length > 0
            ? `https://www.luogu.com.cn/problem/${pids[0]}`
            : 'https://www.luogu.com.cn/problem/list';
    }

    function shouldAllow() {
        const currentURL = window.location.href;
        const isLuoguDomain = /^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(currentURL);
        if (!isLuoguDomain) return true;
        if (!isFocusActive()) return true;

        const state = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        if (state === 'paused') {
            return true;
        }

        const currentMode = localStorage.getItem(STORAGE_KEY_CURRENT_MODE);
        if (currentMode === 'rest') {
            return true;
        }

        const path = window.location.pathname.toLowerCase();
        return (
            path.startsWith('/problem/') ||
            path.startsWith('/paste') ||
            path.startsWith('/paste/') ||
            path.startsWith('/record/')
        );
    }

    function blockSolutionAccess() {
        if (!/\/problem\/solution\//i.test(window.location.pathname)) {
            return false;
        }
        if (!getSolutionStatus()) {
            window.location.replace(getRedirectUrl());
            return true;
        }
        return false;
    }

    function monitorSolutionElements() {
        if (!getSolutionStatus()) {
            const observer = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== 1) return;
                        const selectors = [
                            'a[href*="/problem/solution/"]',
                            '[class*="solution"]',
                            '[class*="answer"]'
                        ];
                        try {
                            node.querySelectorAll(selectors.join(',')).forEach(el => {
                                if ((el.textContent && el.textContent.includes('题解')) ||
                                    (el.href && el.href.includes('/problem/solution/'))) {
                                    el.style.display = 'none !important';
                                    el.style.visibility = 'hidden !important';
                                    el.style.height = '0 !important';
                                    el.style.overflow = 'hidden !important';
                                }
                            });
                        } catch (e) {}
                    });
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    if (blockSolutionAccess()) return;
    if (isFocusActive() && !shouldAllow()) {
        window.location.replace(getRedirectUrl());
        return;
    }

    function createPomodoroWidget() {
        if (!/^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(window.location.href)) return;
        const workDuration = getWorkDuration();
        const restDuration = getRestDuration();
        const config = {
            workDuration: workDuration,
            restDuration: restDuration,
            color: getFocusColor(),
            position: getFocusPosition()
        };
        const style = document.createElement('style');
        style.textContent = `
            #luogu-pomodoro-widget {
                position: fixed;
                z-index: 99999;
                border-radius: 10px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.15);
                overflow: hidden;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
                user-select: none;
                width: ${DEFAULT_SIZE.width}px;
                height: ${DEFAULT_SIZE.height}px;
                background: white;
                border: 1px solid #e0e0e0;
                cursor: move;
                transition: border-color 0.3s ease;
            }
            #luogu-pomodoro-widget:hover {
                box-shadow: 0 3px 15px rgba(0,0,0,0.2);
            }
            #luogu-pomodoro-time {
                font-size: 20px;
                font-weight: bold;
                text-align: center;
                color: #333;
                padding: 12px 0;
                height: 40px;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            #luogu-pomodoro-progress {
                height: 4px;
                background: #f0f0f0;
                border-radius: 2px;
                margin: 4px 8px;
                overflow: hidden;
            }
            #luogu-pomodoro-progress-bar {
                height: 100%;
                width: 0%;
                background: ${config.color};
                border-radius: 2px;
                transition: width 0.5s ease;
            }
            #luogu-pomodoro-controls {
                display: flex;
                justify-content: center;
                gap: 6px;
                padding: 6px 0;
                background: #f8f9fa;
                border-top: 1px solid #eee;
            }
            .pomodoro-btn {
                width: 48%;
                height: 22px;
                border: none;
                border-radius: 3px;
                font-size: 11px;
                font-weight: 500;
                cursor: pointer;
                transition: all 0.2s;
                user-select: none;
                text-align: center;
                line-height: 22px;
                color: #333;
            }
            .pomodoro-btn:active {
                transform: scale(0.95);
            }
            .pomodoro-btn.start {
                background: #e8f5e9;
                color: #2e7d32;
            }
            .pomodoro-btn.pause {
                background: #fff8e1;
                color: #ff8f00;
            }
            .pomodoro-btn.stop {
                background: #ffebee;
                color: #c62828;
            }
            .pomodoro-btn.start:disabled,
            .pomodoro-btn.pause:disabled {
                opacity: 0.6;
                cursor: not-allowed;
                transform: none !important;
            }
            #luogu-pomodoro-settings {
                position: absolute;
                top: 8px;
                right: 8px;
                width: 24px;
                height: 24px;
                border-radius: 6px;
                background: ${config.color};
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 14px;
                cursor: pointer;
                z-index: 10;
                transition: all 0.2s;
                box-shadow: 0 1px 3px rgba(0,0,0,0.2);
            }
            #luogu-pomodoro-settings:hover {
                transform: scale(1.1);
                box-shadow: 0 2px 6px rgba(0,0,0,0.3);
            }
            #luogu-pomodoro-settings svg {
                width: 14px;
                height: 14px;
                fill: white;
            }
            #luogu-pomodoro-widget.focus-running {
                border: 2px solid ${config.color} !important;
            }
        `;
        document.head.appendChild(style);
        const widget = document.createElement('div');
        widget.id = 'luogu-pomodoro-widget';
        widget.style.left = `${config.position.x}px`;
        widget.style.top = `${config.position.y}px`;
        widget.innerHTML = `
            <div id="luogu-pomodoro-time">25:00</div>
            <div id="luogu-pomodoro-progress">
                <div id="luogu-pomodoro-progress-bar"></div>
            </div>
            <div id="luogu-pomodoro-controls">
                <button class="pomodoro-btn start">开始</button>
                <button class="pomodoro-btn stop">结束</button>
            </div>
            <div id="luogu-pomodoro-settings">
                <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                    <path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
                </svg>
            </div>
        `;
        document.body.appendChild(widget);
        const settingsBtn = widget.querySelector('#luogu-pomodoro-settings');
        settingsBtn.onclick = (e) => {
            e.stopPropagation();
            createSettingUI();
        };
        let timer = null;
        let remainingTime = workDuration * 60;
        let currentMode = 'work';
        let isResuming = false;
        let autoStart = false;
        const savedMode = localStorage.getItem(STORAGE_KEY_CURRENT_MODE);
        if (savedMode === 'rest') {
            currentMode = 'rest';
            remainingTime = restDuration * 60;
        }
        const timeDisplay = widget.querySelector('#luogu-pomodoro-time');
        const progressBar = widget.querySelector('#luogu-pomodoro-progress-bar');
        const startBtn = widget.querySelector('.pomodoro-btn.start');
        const stopBtn = widget.querySelector('.pomodoro-btn.stop');

        const savedState = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        const savedRemaining = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_REMAINING) || '0');
        const endTime = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_END) || '0');
        const now = Date.now();

        if (savedState === 'paused' && savedRemaining > 0) {
            remainingTime = savedRemaining;
            isResuming = true;
            autoStart = false;
        } else if (savedState === 'running' && endTime > now) {
            remainingTime = Math.floor((endTime - now) / 1000);
            if (remainingTime > 0) {
                isResuming = true;
                autoStart = true;
            }
        }

        if (savedState === 'paused' && savedRemaining > 0) {
            startBtn.textContent = '继续';
            startBtn.classList.add('pause');
            startBtn.classList.remove('start');
            startBtn.disabled = false;
            stopBtn.disabled = false;
        } else if (savedState === 'running' && endTime > now && remainingTime > 0) {
            startBtn.textContent = '暂停';
            startBtn.classList.remove('pause');
            startBtn.classList.add('start');
            startBtn.disabled = false;
            stopBtn.disabled = false;
        } else {
            startBtn.textContent = '开始';
            startBtn.classList.add('start');
            startBtn.classList.remove('pause');
            startBtn.disabled = false;
            stopBtn.disabled = (currentMode === 'work' && remainingTime === workDuration * 60);
        }

        function updateFocusBorderStyle() {
            const isRunning = timer !== null;
            const isWorkMode = currentMode === 'work';
            const isPaused = localStorage.getItem(STORAGE_KEY_FOCUS_STATE) === 'paused';
            const isRest = currentMode === 'rest';

            if (isRunning && isWorkMode && !isPaused && !isRest) {
                widget.classList.add('focus-running');
            } else {
                widget.classList.remove('focus-running');
            }
        }

        function updateDisplay() {
            const mins = Math.floor(remainingTime / 60);
            const secs = remainingTime % 60;
            timeDisplay.textContent = `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
            const totalSeconds = (currentMode === 'work' ? workDuration : restDuration) * 60;
            const progress = ((totalSeconds - remainingTime) / totalSeconds) * 100;
            progressBar.style.width = `${Math.max(0, Math.min(100, progress))}%`;
            updateFocusBorderStyle();
        }

        function toggleTimer() {
            if (timer) {
                clearInterval(timer);
                timer = null;
                pauseFocusMode(remainingTime);
                localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                isResuming = true;
                startBtn.textContent = '继续';
                startBtn.classList.add('pause');
                startBtn.classList.remove('start');
                startBtn.disabled = false;
                stopBtn.disabled = false;
                updateFocusBorderStyle();
            } else {
                if (isResuming) {
                    const endTime = Date.now() + remainingTime * 1000;
                    localStorage.setItem(STORAGE_KEY_FOCUS_END, endTime.toString());
                    localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'running');
                    localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                    localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
                    isResuming = false;
                } else {
                    remainingTime = (currentMode === 'work' ? workDuration : restDuration) * 60;
                    startFocusMode(
                        currentMode === 'work' ? workDuration : restDuration,
                        currentMode === 'work'
                    );
                    localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                }
                startBtn.textContent = '暂停';
                startBtn.classList.remove('pause');
                startBtn.classList.add('start');
                startBtn.disabled = false;
                stopBtn.disabled = false;
                updateFocusBorderStyle();
                timer = setInterval(() => {
                    remainingTime--;
                    updateDisplay();
                    if (remainingTime <= 0) {
                        clearInterval(timer);
                        timer = null;
                        if (currentMode === 'work') {
                            currentMode = 'rest';
                            remainingTime = restDuration * 60;
                            endFocusMode();
                            localStorage.setItem(STORAGE_KEY_CURRENT_MODE, 'rest');
                            updateDisplay();
                            startBtn.textContent = '开始';
                            startBtn.classList.add('start');
                            startBtn.classList.remove('pause');
                            startBtn.disabled = false;
                            stopBtn.disabled = false;
                        } else {
                            endFocusMode();
                            startBtn.textContent = '开始';
                            startBtn.classList.add('start');
                            startBtn.classList.remove('pause');
                            startBtn.disabled = false;
                            stopBtn.disabled = true;
                            currentMode = 'work';
                            remainingTime = workDuration * 60;
                            updateDisplay();
                        }
                    }
                }, 1000);
            }
        }

        function stopTimer() {
            clearInterval(timer);
            timer = null;
            remainingTime = workDuration * 60;
            currentMode = 'work';
            isResuming = false;
            updateDisplay();
            endFocusMode();
            startBtn.textContent = '开始';
            startBtn.classList.add('start');
            startBtn.classList.remove('pause');
            startBtn.disabled = false;
            stopBtn.disabled = true;
        }

        startBtn.onclick = toggleTimer;
        stopBtn.onclick = stopTimer;
        let isDragging = false;
        let offsetX, offsetY;
        widget.onmousedown = (e) => {
            if (e.target.id === 'luogu-pomodoro-settings') return;
            isDragging = true;
            const rect = widget.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            widget.style.cursor = 'move';
            e.preventDefault();
        };
        document.onmousemove = (e) => {
            if (!isDragging) return;
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;
            const maxX = window.innerWidth - widget.offsetWidth;
            const maxY = window.innerHeight - widget.offsetHeight;
            widget.style.left = `${Math.max(0, Math.min(x, maxX))}px`;
            widget.style.top = `${Math.max(0, Math.min(y, maxY))}px`;
        };
        document.onmouseup = () => {
            if (isDragging) {
                isDragging = false;
                widget.style.cursor = 'default';
                const rect = widget.getBoundingClientRect();
                saveFocusPosition({
                    x: Math.round(rect.left),
                    y: Math.round(rect.top)
                });
            }
        };
        updateDisplay();
        if (autoStart) {
            setTimeout(() => {
                toggleTimer();
            }, 300);
        }
    }

    function createSettingUI() {
        if (!/^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(window.location.href)) return;
        const existingMask = document.getElementById('luogu-modal-mask');
        const existingBox = document.getElementById('luogu-modal-box');
        if (existingMask) existingMask.remove();
        if (existingBox) existingBox.remove();
        const style = document.createElement('style');
        style.textContent = `
            #luogu-modal-mask {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0,0,0,0.5);
                z-index: 99997;
                display: none;
            }
            #luogu-modal-box {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                width: 420px;
                padding: 24px;
                background: #fff;
                border-radius: 12px;
                z-index: 99998;
                display: none;
                box-shadow: 0 0 20px rgba(0,0,0,0.3);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
            }
            #luogu-modal-box h3 {
                margin: 0 0 20px 0;
                color: #222;
                text-align: center;
                font-size: 22px;
                border-bottom: 1px solid #eee;
                padding-bottom: 12px;
            }
            .luogu-form-group {
                margin-bottom: 18px;
            }
            .luogu-form-group label {
                display: block;
                margin-bottom: 8px;
                font-weight: 600;
                color: #333;
                font-size: 15px;
            }
            #luogu-pid-input {
                width: 100%;
                padding: 12px;
                box-sizing: border-box;
                border: 1px solid #ddd;
                border-radius: 6px;
                font-size: 14px;
                transition: border-color 0.3s;
            }
            #luogu-pid-input:focus {
                border-color: #0099ff;
                outline: none;
            }
            #luogu-solution-switch {
                display: flex;
                align-items: center;
                gap: 10px;
                padding: 10px 0;
            }
            #luogu-solution-switch input {
                width: 20px;
                height: 20px;
                cursor: pointer;
            }
            .duration-settings {
                display: flex;
                gap: 15px;
            }
            .duration-col {
                flex: 1;
            }
            .duration-input {
                width: 100%;
                padding: 10px;
                border: 1px solid #ddd;
                border-radius: 6px;
                font-size: 14px;
                margin-top: 6px;
            }
            .pid-list {
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                margin-top: 10px;
                min-height: 35px;
            }
            .pid-item {
                background: #f0f0f0;
                padding: 6px 12px;
                border-radius: 14px;
                font-size: 13px;
                display: flex;
                align-items: center;
                gap: 6px;
                cursor: pointer;
                transition: all 0.2s;
            }
            .pid-item:hover {
                background: #e0e0e0;
                transform: scale(1.03);
            }
            .pid-item .remove {
                color: #ff4444;
                cursor: pointer;
                font-size: 15px;
                margin-left: 4px;
            }
            #luogu-color-input {
                width: 100%;
                height: 36px;
                border: 1px solid #ddd;
                border-radius: 6px;
                padding: 2px;
                margin-top: 6px;
                cursor: pointer;
            }
            #luogu-modal-btn-group {
                display: flex;
                gap: 12px;
                margin-top: 20px;
                padding-top: 16px;
                border-top: 1px solid #f5f5f5;
            }
            #luogu-save-btn, #luogu-cancel-btn {
                flex: 1;
                padding: 12px;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                font-size: 15px;
                font-weight: 500;
                transition: all 0.2s;
            }
            #luogu-save-btn {
                background: #0099ff;
                color: white;
            }
            #luogu-save-btn:hover {
                background: #0077cc;
            }
            #luogu-cancel-btn {
                background: #f0f0f0;
                color: #666;
            }
            #luogu-cancel-btn:hover {
                background: #e0e0e0;
            }
            .luogu-hint {
                font-size: 12px;
                color: #888;
                margin-top: 8px;
                line-height: 1.5;
            }
        `;
        document.head.appendChild(style);
        const mask = document.createElement('div');
        mask.id = 'luogu-modal-mask';
        mask.onclick = () => {
            mask.style.display = 'none';
            modalBox.style.display = 'none';
        };
        document.body.appendChild(mask);
        const modalBox = document.createElement('div');
        modalBox.id = 'luogu-modal-box';
        modalBox.innerHTML = `
            <h3>专注设置</h3>
            <div class="luogu-form-group">
                <label>题目计划</label>
                <input type="text" id="luogu-pid-input" placeholder="输入题号(如:P1000、B2001、CF1C、AT_abc001_a、UVA100)">
                <div class="luogu-hint">最多添加 6 道题目,点击题号可直接跳转。支持洛谷、Codeforces、AtCoder、UVA 等题库编号。</div>
                <div class="pid-list" id="pid-list"></div>
            </div>
            <div class="luogu-form-group">
                <label>题解访问</label>
                <div id="luogu-solution-switch">
                    <input type="checkbox" id="luogu-solution-checkbox">
                    <span>允许查看题解(独立控制,不受计时影响)</span>
                </div>
            </div>
            <div class="luogu-form-group">
                <label>番茄钟时长(分钟)</label>
                <div class="duration-settings">
                    <div class="duration-col">
                        <div style="font-size:13px;color:#666;">工作时长</div>
                        <input type="number" id="luogu-work-input" class="duration-input" min="1" max="120" value="${getWorkDuration()}">
                    </div>
                    <div class="duration-col">
                        <div style="font-size:13px;color:#666;">休息时长</div>
                        <input type="number" id="luogu-rest-input" class="duration-input" min="1" max="60" value="${getRestDuration()}">
                    </div>
                </div>
            </div>
            <div class="luogu-form-group">
                <label>番茄钟颜色</label>
                <input type="color" id="luogu-color-input" value="${getFocusColor()}">
                <div class="luogu-hint">专注工作时,番茄钟边框会显示此颜色。</div>
            </div>
            <div id="luogu-modal-btn-group">
                <button id="luogu-save-btn">保存</button>
                <button id="luogu-cancel-btn">取消</button>
            </div>
        `;
        document.body.appendChild(modalBox);
        function renderPidList() {
            const pidList = document.getElementById('pid-list');
            pidList.innerHTML = '';
            const pids = getSavePids();
            if (pids.length === 0) {
                pidList.innerHTML = '<div style="text-align:center;color:#999;padding:8px 0;width:100%">暂无题目</div>';
                return;
            }
            pids.forEach(pid => {
                const item = document.createElement('div');
                item.className = 'pid-item';
                item.innerHTML = `${pid}<span class="remove">×</span>`;
                item.addEventListener('click', (e) => {
                    if (e.target.classList.contains('remove')) return;
                    window.location.href = `https://www.luogu.com.cn/problem/${pid}`;
                });
                const removeBtn = item.querySelector('.remove');
                removeBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (removePid(pid)) {
                        renderPidList();
                    }
                });
                pidList.appendChild(item);
            });
        }
        document.getElementById('luogu-save-btn').onclick = () => {
            const pidInput = document.getElementById('luogu-pid-input');
            const rawPid = pidInput.value.trim();
            const pid = rawPid.startsWith('AT_') ? rawPid : rawPid.toUpperCase();
            const solutionChecked = document.getElementById('luogu-solution-checkbox').checked;
            const workDuration = parseInt(document.getElementById('luogu-work-input').value) || DEFAULT_WORK_DURATION;
            const restDuration = parseInt(document.getElementById('luogu-rest-input').value) || DEFAULT_REST_DURATION;
            const color = document.getElementById('luogu-color-input').value;
            if (workDuration < 1 || workDuration > 120) {
                alert('工作时长必须在 1-120 分钟之间');
                return;
            }
            if (restDuration < 1 || restDuration > 60) {
                alert('休息时长必须在 1-60 分钟之间');
                return;
            }
            if (pid && !/^(P\d{4,6}|SP\d{3,5}|B\d{4,5}|CF\d+[A-Z]?|AT_[a-z0-9_]+|UVA\d{3,5})$/i.test(pid)) {
                alert('题号格式错误!\n正确格式:P1000, SP123, B2001, CF1C, AT_abc001_a, UVA100 等');
                return;
            }
            const pids = getSavePids();
            if (pid && pids.length < MAX_PIDS && !pids.includes(pid)) {
                pids.push(pid);
                savePids(pids);
            }
            saveSolutionStatus(solutionChecked);
            saveDurations(workDuration, restDuration);
            saveFocusColor(color);
            alert('设置已保存!');
            mask.style.display = 'none';
            modalBox.style.display = 'none';
            renderPidList();
            monitorSolutionElements();
        };
        document.getElementById('luogu-cancel-btn').onclick = () => {
            mask.style.display = 'none';
            modalBox.style.display = 'none';
        };
        document.getElementById('luogu-pid-input').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                document.getElementById('luogu-save-btn').click();
            }
        });
        mask.style.display = 'block';
        modalBox.style.display = 'block';
        document.getElementById('luogu-pid-input').value = '';
        document.getElementById('luogu-solution-checkbox').checked = getSolutionStatus();
        renderPidList();
    }

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

    window.addEventListener('load', () => {
        monitorSolutionElements();
    });
})();

:::

\textup{Upd on 2026.5.24}

修复目前已知的 bug,比如无法拦截进入题库等功能。

:::success[\textup{V3 Code.}]

// ==UserScript==
// @name         洛谷强力学术Pro
// @namespace    http://tampermonkey.net/
// @version      10.58
// @author       wendywan
// @icon         https://cdn.luogu.com.cn/upload/image_hosting/psd5a03h.png
// @match        *://*/*
// @grant        none
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    const STORAGE_KEY_PID = "luogu_study_plan_pids";
    const STORAGE_KEY_SOLUTION = "luogu_study_plan_solution";
    const STORAGE_KEY_FOCUS_ACTIVE = "luogu_focus_active";
    const STORAGE_KEY_FOCUS_END = "luogu_focus_end_time";
    const STORAGE_KEY_FOCUS_DURATION = "luogu_focus_duration";
    const STORAGE_KEY_FOCUS_COLOR = "luogu_focus_color";
    const STORAGE_KEY_FOCUS_POS = "luogu_focus_position";
    const STORAGE_KEY_FOCUS_REMAINING = "luogu_focus_remaining";
    const STORAGE_KEY_FOCUS_STATE = "luogu_focus_state";
    const STORAGE_KEY_REST_DURATION = "luogu_rest_duration";
    const STORAGE_KEY_CURRENT_MODE = "luogu_focus_current_mode";
    const DEFAULT_WORK_DURATION = 25;
    const DEFAULT_REST_DURATION = 5;
    const DEFAULT_COLOR = "#ff6b6b";
    const DEFAULT_POSITION = { x: 20, y: 100 };
    const DEFAULT_SIZE = { width: 120, height: 100 };
    const MAX_PIDS = 6;

    function getSavePids() {
        try {
            const pids = JSON.parse(localStorage.getItem(STORAGE_KEY_PID)) || [];
            return pids.filter(pid => pid.trim() !== '');
        } catch {
            return [];
        }
    }

    function savePids(pids) {
        localStorage.setItem(STORAGE_KEY_PID, JSON.stringify(pids));
    }

    function removePid(pid) {
        const pids = getSavePids();
        const index = pids.indexOf(pid);
        if (index !== -1) {
            pids.splice(index, 1);
            savePids(pids);
            return true;
        }
        return false;
    }

    function getSolutionStatus() {
        return localStorage.getItem(STORAGE_KEY_SOLUTION) !== 'false';
    }

    function saveSolutionStatus(status) {
        localStorage.setItem(STORAGE_KEY_SOLUTION, status);
    }

    function isFocusActive() {
        const active = localStorage.getItem(STORAGE_KEY_FOCUS_ACTIVE) === 'true';
        if (!active) return false;

        const state = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        if (state === 'paused') {
            return true;
        }

        const endTime = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_END) || '0');
        const now = Date.now();
        if (active && now >= endTime) {
            endFocusMode();
            return false;
        }
        return active;
    }

    function startFocusMode(durationMinutes, isWorkMode) {
        const endTime = Date.now() + durationMinutes * 60 * 1000;
        localStorage.setItem(STORAGE_KEY_FOCUS_ACTIVE, 'true');
        localStorage.setItem(STORAGE_KEY_FOCUS_END, endTime.toString());
        if (isWorkMode) {
            localStorage.setItem(STORAGE_KEY_FOCUS_DURATION, durationMinutes.toString());
        }
        localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'running');
        localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
    }

    function pauseFocusMode(remainingSeconds) {
        localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'paused');
        localStorage.setItem(STORAGE_KEY_FOCUS_REMAINING, remainingSeconds.toString());
        localStorage.removeItem(STORAGE_KEY_FOCUS_END);
    }

    function endFocusMode() {
        localStorage.removeItem(STORAGE_KEY_FOCUS_ACTIVE);
        localStorage.removeItem(STORAGE_KEY_FOCUS_END);
        localStorage.removeItem(STORAGE_KEY_FOCUS_STATE);
        localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING);
        localStorage.removeItem(STORAGE_KEY_CURRENT_MODE);
    }

    function getWorkDuration() {
        return parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_DURATION) || DEFAULT_WORK_DURATION);
    }

    function getRestDuration() {
        return parseInt(localStorage.getItem(STORAGE_KEY_REST_DURATION) || DEFAULT_REST_DURATION);
    }

    function saveDurations(work, rest) {
        localStorage.setItem(STORAGE_KEY_FOCUS_DURATION, work.toString());
        localStorage.setItem(STORAGE_KEY_REST_DURATION, rest.toString());
    }

    function getFocusColor() {
        return localStorage.getItem(STORAGE_KEY_FOCUS_COLOR) || DEFAULT_COLOR;
    }

    function saveFocusColor(color) {
        localStorage.setItem(STORAGE_KEY_FOCUS_COLOR, color);
    }

    function getFocusPosition() {
        try {
            return JSON.parse(localStorage.getItem(STORAGE_KEY_FOCUS_POS) || JSON.stringify(DEFAULT_POSITION));
        } catch {
            return DEFAULT_POSITION;
        }
    }

    function saveFocusPosition(pos) {
        localStorage.setItem(STORAGE_KEY_FOCUS_POS, JSON.stringify(pos));
    }

    function getRedirectUrl() {
        const pids = getSavePids();
        return pids.length > 0
            ? `https://www.luogu.com.cn/problem/${pids[0]}`
            : 'https://www.luogu.com.cn/problem/list';
    }

    function shouldAllow() {
        const currentURL = window.location.href;
        const isLuoguDomain = /^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(currentURL);
        if (!isLuoguDomain) return true;
        if (!isFocusActive()) return true;

        const state = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        if (state === 'paused') return true;

        const currentMode = localStorage.getItem(STORAGE_KEY_CURRENT_MODE);
        if (currentMode === 'rest') return true;

        const path = window.location.pathname.toLowerCase();
        const allowedProblem = /^\/problem\//i.test(path);
        const allowedPaste = /^\/paste(\/.*)?$/i.test(path);
        const allowedRecord = /^\/record\//i.test(path);

        if (/\/problem\/solution\//i.test(path)) {
            return getSolutionStatus();
        }

        return allowedProblem || allowedPaste || allowedRecord;
    }

    function blockSolutionAccess() {
        const path = window.location.pathname.toLowerCase();
        if (/\/problem\/solution\//i.test(path)) {
            if (!getSolutionStatus()) {
                window.location.replace(getRedirectUrl());
                return true;
            }
        }
        return false;
    }

    function enforceAccessRules() {
        if (blockSolutionAccess()) return true;
        if (isFocusActive() && !shouldAllow()) {
            window.location.replace(getRedirectUrl());
            return true;
        }
        return false;
    }

    function monitorSolutionElements() {
        if (!getSolutionStatus()) {
            const observer = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== 1) return;
                        const selectors = [
                            'a[href*="/problem/solution/"]',
                            '[class*="solution"]',
                            '[class*="answer"]',
                            '.lg-article-content'
                        ];
                        try {
                            node.querySelectorAll(selectors.join(',')).forEach(el => {
                                if ((el.textContent && el.textContent.includes('题解')) ||
                                    (el.href && el.href.includes('/problem/solution/'))) {
                                    el.style.display = 'none';
                                    el.style.visibility = 'hidden';
                                    el.style.height = '0';
                                    el.style.overflow = 'hidden';
                                    el.style.pointerEvents = 'none';
                                }
                            });
                        } catch (e) {}
                    });
                });
            });
            if (document.body) observer.observe(document.body, { childList: true, subtree: true });
            else document.addEventListener('DOMContentLoaded', () => observer.observe(document.body, { childList: true, subtree: true }));
        }
    }

    function createPomodoroWidget() {
        if (!/^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(window.location.href)) return;
        const workDuration = getWorkDuration();
        const restDuration = getRestDuration();
        const config = {
            workDuration: workDuration,
            restDuration: restDuration,
            color: getFocusColor(),
            position: getFocusPosition()
        };
        const style = document.createElement('style');
        style.textContent = `
            #luogu-pomodoro-widget {
                position: fixed; z-index: 99999; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.15);
                overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
                user-select: none; width: ${DEFAULT_SIZE.width}px; height: ${DEFAULT_SIZE.height}px;
                background: white; border: 1px solid #e0e0e0; cursor: move; transition: border-color 0.3s ease, box-shadow 0.3s ease;
            }
            #luogu-pomodoro-widget:hover { box-shadow: 0 3px 15px rgba(0,0,0,0.2); }
            #luogu-pomodoro-time { font-size: 20px; font-weight: bold; text-align: center; color: #333; padding: 12px 0; height: 40px; display: flex; align-items: center; justify-content: center; }
            #luogu-pomodoro-progress { height: 4px; background: #f0f0f0; border-radius: 2px; margin: 4px 8px; overflow: hidden; }
            #luogu-pomodoro-progress-bar { height: 100%; width: 0%; background: ${config.color}; border-radius: 2px; transition: width 0.5s ease; }
            #luogu-pomodoro-controls { display: flex; justify-content: center; gap: 6px; padding: 6px 0; background: #f8f9fa; border-top: 1px solid #eee; }
            .pomodoro-btn { width: 48%; height: 22px; border: none; border-radius: 3px; font-size: 11px; font-weight: 500; cursor: pointer; transition: all 0.2s; user-select: none; text-align: center; line-height: 22px; color: #333; }
            .pomodoro-btn:active { transform: scale(0.95); }
            .pomodoro-btn.start { background: #e8f5e9; color: #2e7d32; }
            .pomodoro-btn.pause { background: #fff8e1; color: #ff8f00; }
            .pomodoro-btn.stop { background: #ffebee; color: #c62828; }
            .pomodoro-btn.start:disabled, .pomodoro-btn.pause:disabled { opacity: 0.6; cursor: not-allowed; transform: none !important; }
            #luogu-pomodoro-settings { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; border-radius: 6px; background: ${config.color}; display: flex; align-items: center; justify-content: center; font-size: 14px; cursor: pointer; z-index: 10; transition: all 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
            #luogu-pomodoro-settings:hover { transform: scale(1.1); box-shadow: 0 2px 6px rgba(0,0,0,0.3); }
            #luogu-pomodoro-settings svg { width: 14px; height: 14px; fill: white; }
            #luogu-pomodoro-widget.focus-running { border: 2px solid ${config.color} !important; box-shadow: 0 4px 12px ${config.color}40 !important; }
        `;
        document.head.appendChild(style);
        const widget = document.createElement('div');
        widget.id = 'luogu-pomodoro-widget';
        widget.style.left = `${config.position.x}px`;
        widget.style.top = `${config.position.y}px`;
        widget.innerHTML = `
            <div id="luogu-pomodoro-time">25:00</div>
            <div id="luogu-pomodoro-progress"><div id="luogu-pomodoro-progress-bar"></div></div>
            <div id="luogu-pomodoro-controls">
                <button class="pomodoro-btn start">开始</button>
                <button class="pomodoro-btn stop">结束</button>
            </div>
            <div id="luogu-pomodoro-settings"><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg></div>
        `;
        document.body.appendChild(widget);
        const settingsBtn = widget.querySelector('#luogu-pomodoro-settings');
        settingsBtn.onclick = (e) => { e.stopPropagation(); createSettingUI(); };
        let timer = null;
        let remainingTime = workDuration * 60;
        let currentMode = 'work';
        let isResuming = false;
        let autoStart = false;
        const savedMode = localStorage.getItem(STORAGE_KEY_CURRENT_MODE);
        if (savedMode === 'rest') { currentMode = 'rest'; remainingTime = restDuration * 60; }
        const timeDisplay = widget.querySelector('#luogu-pomodoro-time');
        const progressBar = widget.querySelector('#luogu-pomodoro-progress-bar');
        const startBtn = widget.querySelector('.pomodoro-btn.start');
        const stopBtn = widget.querySelector('.pomodoro-btn.stop');

        const savedState = localStorage.getItem(STORAGE_KEY_FOCUS_STATE);
        const savedRemaining = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_REMAINING) || '0');
        const endTime = parseInt(localStorage.getItem(STORAGE_KEY_FOCUS_END) || '0');
        const now = Date.now();

        if (savedState === 'paused' && savedRemaining > 0) { remainingTime = savedRemaining; isResuming = true; autoStart = false; }
        else if (savedState === 'running' && endTime > now) { remainingTime = Math.floor((endTime - now) / 1000); if (remainingTime > 0) { isResuming = true; autoStart = true; } }

        if (savedState === 'paused' && savedRemaining > 0) { startBtn.textContent = '继续'; startBtn.classList.add('pause'); startBtn.classList.remove('start'); startBtn.disabled = false; stopBtn.disabled = false; }
        else if (savedState === 'running' && endTime > now && remainingTime > 0) { startBtn.textContent = '暂停'; startBtn.classList.remove('pause'); startBtn.classList.add('start'); startBtn.disabled = false; stopBtn.disabled = false; }
        else { startBtn.textContent = '开始'; startBtn.classList.add('start'); startBtn.classList.remove('pause'); startBtn.disabled = false; stopBtn.disabled = (currentMode === 'work' && remainingTime === workDuration * 60); }

        function updateFocusBorderStyle() {
            if (timer && currentMode === 'work' && localStorage.getItem(STORAGE_KEY_FOCUS_STATE) !== 'paused') widget.classList.add('focus-running');
            else widget.classList.remove('focus-running');
        }
        function updateDisplay() {
            const mins = Math.floor(remainingTime / 60); const secs = remainingTime % 60;
            timeDisplay.textContent = `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
            const totalSeconds = (currentMode === 'work' ? workDuration : restDuration) * 60;
            progressBar.style.width = `${Math.max(0, Math.min(100, ((totalSeconds - remainingTime) / totalSeconds) * 100))}%`;
            updateFocusBorderStyle();
        }
        function toggleTimer() {
            if (timer) {
                clearInterval(timer); timer = null; pauseFocusMode(remainingTime); localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode);
                isResuming = true; startBtn.textContent = '继续'; startBtn.classList.add('pause'); startBtn.classList.remove('start'); startBtn.disabled = false; stopBtn.disabled = false; updateFocusBorderStyle();
            } else {
                if (isResuming) { localStorage.setItem(STORAGE_KEY_FOCUS_END, (Date.now() + remainingTime * 1000).toString()); localStorage.setItem(STORAGE_KEY_FOCUS_STATE, 'running'); localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode); localStorage.removeItem(STORAGE_KEY_FOCUS_REMAINING); isResuming = false; }
                else { remainingTime = (currentMode === 'work' ? workDuration : restDuration) * 60; startFocusMode(currentMode === 'work' ? workDuration : restDuration, currentMode === 'work'); localStorage.setItem(STORAGE_KEY_CURRENT_MODE, currentMode); }
                startBtn.textContent = '暂停'; startBtn.classList.remove('pause'); startBtn.classList.add('start'); startBtn.disabled = false; stopBtn.disabled = false; updateFocusBorderStyle();
                timer = setInterval(() => { remainingTime--; updateDisplay(); if (remainingTime <= 0) { clearInterval(timer); timer = null; if (currentMode === 'work') { currentMode = 'rest'; remainingTime = restDuration * 60; endFocusMode(); localStorage.setItem(STORAGE_KEY_CURRENT_MODE, 'rest'); startBtn.textContent = '开始'; startBtn.classList.add('start'); startBtn.classList.remove('pause'); startBtn.disabled = false; stopBtn.disabled = false; alert('工作阶段完成!进入休息。'); } else { endFocusMode(); startBtn.textContent = '开始'; startBtn.classList.add('start'); startBtn.classList.remove('pause'); startBtn.disabled = false; stopBtn.disabled = true; currentMode = 'work'; remainingTime = workDuration * 60; alert('休息结束!准备开始新的工作。'); } updateDisplay(); } }, 1000);
            }
        }
        function stopTimer() { clearInterval(timer); timer = null; remainingTime = workDuration * 60; currentMode = 'work'; isResuming = false; updateDisplay(); endFocusMode(); startBtn.textContent = '开始'; startBtn.classList.add('start'); startBtn.classList.remove('pause'); startBtn.disabled = false; stopBtn.disabled = true; }
        startBtn.onclick = toggleTimer; stopBtn.onclick = stopTimer;
        let isDragging = false; let offsetX, offsetY;
        widget.onmousedown = (e) => { if (e.target.id === 'luogu-pomodoro-settings') return; isDragging = true; const rect = widget.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; widget.style.cursor = 'move'; e.preventDefault(); };
        document.onmousemove = (e) => { if (!isDragging) return; widget.style.left = `${Math.max(0, Math.min(e.clientX - offsetX, window.innerWidth - widget.offsetWidth))}px`; widget.style.top = `${Math.max(0, Math.min(e.clientY - offsetY, window.innerHeight - widget.offsetHeight))}px`; };
        document.onmouseup = () => { if (isDragging) { isDragging = false; widget.style.cursor = 'default'; const rect = widget.getBoundingClientRect(); saveFocusPosition({ x: Math.round(rect.left), y: Math.round(rect.top) }); } };
        updateDisplay(); if (autoStart) setTimeout(() => toggleTimer(), 300);
    }

    function createSettingUI() {
        if (!/^(https?:\/\/)?(www\.)?luogu\.com\.cn/i.test(window.location.href)) return;
        ['luogu-modal-mask','luogu-modal-box'].forEach(id => { const el = document.getElementById(id); if(el) el.remove(); });
        const style = document.createElement('style');
        style.textContent = `
            #luogu-modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 99997; display: none; backdrop-filter: blur(3px); }
            #luogu-modal-box { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; max-width: 92vw; padding: 20px; background: #fff; border-radius: 14px; z-index: 99998; display: none; box-shadow: 0 8px 24px rgba(0,0,0,0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
            #luogu-modal-box *, #luogu-modal-box *::before, #luogu-modal-box *::after { box-sizing: border-box; }
            #luogu-modal-box h3 { margin: 0 0 16px; color: #111; text-align: center; font-size: 22px; font-weight: 700; border-bottom: 1px solid #f3f4f6; padding-bottom: 12px; }
            .luogu-form-group { margin-bottom: 12px; }
            .luogu-form-group label { display: block; margin-bottom: 6px; font-weight: 600; color: #333; font-size: 15px; }
            #luogu-pid-input { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; transition: border-color 0.2s; }
            #luogu-pid-input:focus { border-color: #3b82f6; outline: none; box-shadow: 0 0 0 2px rgba(59,130,246,0.1); }
            .switch-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; background: #f8fafc; border-radius: 8px; border: 1px solid #f1f5f9; }
            .switch-text { font-size: 14px; color: #475569; }
            .switch-toggle { position: relative; width: 44px; height: 24px; }
            .switch-toggle input { opacity: 0; width: 0; height: 0; }
            .switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #cbd5e1; transition: .2s; border-radius: 24px; }
            .switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .2s; border-radius: 50%; }
            input:checked + .switch-slider { background-color: #3b82f6; }
            input:checked + .switch-slider:before { transform: translateX(20px); }
            .duration-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
            .duration-input-wrap label { font-size: 13px; color: #64748b; margin-bottom: 4px; font-weight: 400; }
            .duration-input { width: 100%; padding: 8px 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
            .duration-input:focus { outline: none; border-color: #3b82f6; }
            .pid-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; min-height: 32px; }
            .pid-item { background: #eef2ff; border: 1px solid #e0e7ff; color: #4338ca; padding: 5px 10px; border-radius: 14px; font-size: 13px; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s; }
            .pid-item:hover { background: #e0e7ff; transform: translateY(-1px); }
            .pid-item .remove { color: #ef4444; font-weight: bold; cursor: pointer; line-height: 1; }
            #luogu-color-input { width: 100%; height: 32px; padding: 2px; border: 1px solid #ddd; border-radius: 6px; cursor: pointer; background: #fff; }
            .luogu-hint { font-size: 12px; color: #94a3b8; margin-top: 4px; line-height: 1.4; }
            #luogu-modal-btn-group { display: grid; grid-template-columns: 1fr 1.2fr; gap: 10px; margin-top: 16px; }
            #luogu-save-btn, #luogu-cancel-btn { padding: 10px; border: none; border-radius: 8px; cursor: pointer; font-size: 15px; font-weight: 600; transition: all 0.2s; }
            #luogu-save-btn { background: #3b82f6; color: white; }
            #luogu-save-btn:hover { background: #2563eb; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(59,130,246,0.3); }
            #luogu-cancel-btn { background: #f1f5f9; color: #475569; }
            #luogu-cancel-btn:hover { background: #e2e8f0; }
        `;
        document.head.appendChild(style);
        const mask = document.createElement('div'); mask.id = 'luogu-modal-mask';
        mask.onclick = () => { mask.style.display = 'none'; modalBox.style.display = 'none'; };
        document.body.appendChild(mask);
        const modalBox = document.createElement('div'); modalBox.id = 'luogu-modal-box';
        modalBox.innerHTML = `
            <h3>专注设置</h3>
            <div class="luogu-form-group">
                <label>题目计划</label>
                <input type="text" id="luogu-pid-input" placeholder="输入题号(如:P1000、CF1628D、AT_abc001_a)">
                <div class="pid-list" id="pid-list"></div>
                <div class="luogu-hint">最多 6 题,回车添加。支持 Luogu/CF/AtCoder/UVA 等。</div>
            </div>
            <div class="luogu-form-group">
                <label>题解访问</label>
                <div class="switch-row">
                    <span class="switch-text">允许查看题解</span>
                    <label class="switch-toggle">
                        <input type="checkbox" id="luogu-solution-checkbox">
                        <span class="switch-slider"></span>
                    </label>
                </div>
            </div>
            <div class="luogu-form-group">
                <label>番茄钟时长(分钟)</label>
                <div class="duration-grid">
                    <div class="duration-input-wrap">
                        <label>工作</label>
                        <input type="number" id="luogu-work-input" class="duration-input" min="1" max="120">
                    </div>
                    <div class="duration-input-wrap">
                        <label>休息</label>
                        <input type="number" id="luogu-rest-input" class="duration-input" min="1" max="60">
                    </div>
                </div>
            </div>
            <div class="luogu-form-group">
                <label>主题颜色</label>
                <input type="color" id="luogu-color-input">
            </div>
            <div id="luogu-modal-btn-group">
                <button id="luogu-cancel-btn">取消</button>
                <button id="luogu-save-btn">保存配置</button>
            </div>
        `;
        document.body.appendChild(modalBox);

        function renderPidList() {
            const list = document.getElementById('pid-list'); list.innerHTML = '';
            const pids = getSavePids();
            if (!pids.length) { list.innerHTML = '<div style="text-align:center;color:#cbd5e1;width:100%;padding:4px;font-size:13px">暂无题目</div>'; return; }
            pids.forEach((pid, idx) => {
                const item = document.createElement('div'); item.className = 'pid-item';
                item.innerHTML = `${pid}<span class="remove">×</span>`;
                item.onclick = (e) => { if (!e.target.classList.contains('remove')) window.location.href = `https://www.luogu.com.cn/problem/${pid}`; };
                item.querySelector('.remove').onclick = (e) => { e.stopPropagation(); removePid(pid); renderPidList(); };
                list.appendChild(item);
            });
        }

        function doSave() {
            const raw = document.getElementById('luogu-pid-input').value.trim();
            const pid = raw.startsWith('AT_') ? raw : raw.toUpperCase();
            const solChecked = document.getElementById('luogu-solution-checkbox').checked;
            const work = parseInt(document.getElementById('luogu-work-input').value) || DEFAULT_WORK_DURATION;
            const rest = parseInt(document.getElementById('luogu-rest-input').value) || DEFAULT_REST_DURATION;
            const color = document.getElementById('luogu-color-input').value;
            if (work < 1 || work > 120) { alert('工作时长 1-120'); return; }
            if (rest < 1 || rest > 60) { alert('休息时长 1-60'); return; }
            // 修复正则,放宽 CF 及通用格式匹配,防止报错
            if (pid && !/^(P\d{3,6}|SP\d{3,5}|B\d{3,5}|CF\d+[A-Z0-9]*|AT_[a-z0-9_]+|UVA\d{3,5}|[A-Z]{2,6}\d+[A-Z0-9]*)$/i.test(pid)) {
                alert('题号格式错误'); return;
            }
            const pids = getSavePids();
            if (pid && pids.length < MAX_PIDS && !pids.includes(pid)) { pids.push(pid); savePids(pids); }
            saveSolutionStatus(solChecked); saveDurations(work, rest); saveFocusColor(color);
            document.getElementById('luogu-pid-input').value = ''; renderPidList(); monitorSolutionElements();
            mask.style.display = 'none'; modalBox.style.display = 'none';
        }

        document.getElementById('luogu-save-btn').onclick = doSave;
        document.getElementById('luogu-cancel-btn').onclick = () => { mask.style.display = 'none'; modalBox.style.display = 'none'; };
        document.getElementById('luogu-pid-input').onkeypress = (e) => { if (e.key === 'Enter') doSave(); };

        mask.style.display = 'block'; modalBox.style.display = 'block';
        document.getElementById('luogu-solution-checkbox').checked = getSolutionStatus();
        document.getElementById('luogu-work-input').value = getWorkDuration();
        document.getElementById('luogu-rest-input').value = getRestDuration();
        document.getElementById('luogu-color-input').value = getFocusColor();
        renderPidList();
    }

    function initRouterGuard() {
        let lastPath = window.location.pathname + window.location.hash;
        const _push = history.pushState; const _replace = history.replaceState;
        history.pushState = function(){ _push.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); };
        history.replaceState = function(){ _replace.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); };
        window.addEventListener('popstate', () => window.dispatchEvent(new Event('locationchange')));
        window.addEventListener('locationchange', () => { if (blockSolutionAccess()) return; if (isFocusActive() && !shouldAllow()) window.location.replace(getRedirectUrl()); });
        setInterval(() => {
            const cur = window.location.pathname + window.location.hash;
            if (cur !== lastPath) { lastPath = cur; if (blockSolutionAccess()) return; if (isFocusActive() && !shouldAllow()) window.location.replace(getRedirectUrl()); }
        }, 800);
    }

    enforceAccessRules();
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', createPomodoroWidget);
    else createPomodoroWidget();
    window.addEventListener('load', () => { monitorSolutionElements(); initRouterGuard(); });
})();

:::

有什么建议可以评论私信,目前还不太成熟,持续更新。

谢谢你看到这里~