// ==UserScript== // @name 高级标签页跳转器 // @namespace https://docs.scriptcat.org/ // @version 1.0 // @description 自动循环跳转预设网页,支持运行状态下自动最小化、编辑锁定、保存提醒及 Alt+M/R 快捷键控制。 // @tag 标签页 自动跳转 面板操作 // @author yangwenren // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // --- 状态初始化 --- const DEFAULT_URLS = []; let state = { urls: GM_getValue('cycle_urls', DEFAULT_URLS), interval: GM_getValue('cycle_interval', 30), isRunning: GM_getValue('is_running', false), currentIndex: GM_getValue('current_index', 0), isMinimized: GM_getValue('is_running', false) ? true : GM_getValue('is_minimized', false), isHidden: false, expandedPos: GM_getValue('expanded_pos', { x: 20, y: 20 }), minimizedPos: { x: 20, y: 20 }, timer: null }; // --- 样式注入 --- GM_addStyle(` #jump-panel { position: fixed; z-index: 2147483647; background: rgba(255, 255, 255, 0.98); backdrop-filter: blur(10px); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); font-family: -apple-system, system-ui, sans-serif; width: 240px; display: flex; flex-direction: column; user-select: none; overflow: hidden; transition: width 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.2), height 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.2), bottom 0.4s ease, right 0.4s ease, opacity 0.3s ease; } #jump-panel.minimized { width: 56px !important; height: 56px !important; border-radius: 28px !important; cursor: pointer; border-color: #1890ff; } #jump-panel.minimized.running { border-color: #ff4d4f; } .panel-header { background: rgba(0,0,0,0.03); padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; cursor: move; border-bottom: 1px solid rgba(0,0,0,0.05); } .panel-title { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: #333; } .minimized .panel-header, .minimized .panel-content { display: none !important; } .status-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; width: 100%; } .run-dot { width: 8px; height: 8px; border-radius: 50%; background: #ccc; } .run-dot.active { background: #ff4d4f; box-shadow: 0 0 8px #ff4d4f; animation: breathe 1.5s infinite; } .status-text { font-size: 10px; font-weight: 900; color: #999; margin-top: 2px; } .status-text.active { color: #ff4d4f; } @keyframes breathe { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.3); opacity: 0.7; } } .panel-content { padding: 12px; display: flex; flex-direction: column; gap: 8px; } textarea { width: 100%; height: 90px; font-size: 12px; padding: 8px; border: 1px solid #ddd; border-radius: 6px; resize: none; box-sizing: border-box; outline: none; transition: background 0.3s; } textarea:disabled, input:disabled { background: #f5f5f5; color: #aaa; cursor: not-allowed; border-color: #eee; } .control-row { display: flex; justify-content: space-between; align-items: center; font-size: 12px; margin-bottom: 2px; } input[type="number"] { width: 50px; padding: 3px; border: 1px solid #ddd; border-radius: 4px; text-align: center; } button { border: none; border-radius: 6px; cursor: pointer; font-weight: bold; width: 100%; transition: all 0.2s; } #save-btn { padding: 8px; background: #52c41a; color: white; font-size: 12px; display: none; margin-bottom: 2px; } #save-btn.show { display: block; animation: slideDown 0.3s ease; } #start-btn { padding: 10px; background: #1890ff; color: white; } #start-btn.running { background: #ff4d4f; } @keyframes slideDown { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } } .header-btns button { background: none; color: #888; font-size: 18px; padding: 0 4px; width: auto; } `); const panel = document.createElement('div'); panel.id = 'jump-panel'; document.body.appendChild(panel); const updatePosition = (useAnimation = true) => { panel.style.transition = useAnimation ? "" : "none"; const targetPos = state.isMinimized ? state.minimizedPos : state.expandedPos; panel.style.right = targetPos.x + 'px'; panel.style.bottom = targetPos.y + 'px'; }; const render = () => { panel.style.display = state.isHidden ? 'none' : 'flex'; panel.style.opacity = state.isHidden ? '0' : '1'; updatePosition(true); if (state.isMinimized) { panel.classList.add('minimized'); panel.innerHTML = `