您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
20s countdown with styled preview, resets on task change, background blinks when negative. Developed by Mostofa Tanim Anik
// ==UserScript== // @name Parimango Task Timer (Blink Background Negative) // @namespace http://tampermonkey.net/ // @version 2.0 // @author Mostofa Tanim Anik // @description 20s countdown with styled preview, resets on task change, background blinks when negative. Developed by Mostofa Tanim Anik // @match https://www.parimango.com/* // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; let countdown = 20; let intervalId = null; let lastTaskNumber = null; let debounceTimeout = null; const POLL_MS = 1000; const OBS_DEBOUNCE_MS = 200; function createTimerElement() { let el = document.getElementById('tm-task-timer'); if (el) return el; el = document.createElement('div'); el.id = 'tm-task-timer'; Object.assign(el.style, { position: 'fixed', top: '10px', right: '10px', background: '#111', color: '#fff', fontSize: '18px', fontWeight: '600', padding: '6px 10px', borderRadius: '2px', zIndex: '2147483647', // boxShadow: '0 3px 10px rgba(0,0,0,.5)', fontFamily: 'monospace', textAlign: 'center', minWidth: '120px', transition: 'background 0.3s ease' }); document.body.appendChild(el); return el; } function updateTimerUI() { const el = createTimerElement(); el.textContent = `⏱ ${countdown}s`; if (countdown >= 8) { el.style.background = '#111'; el.style.color = 'lime'; el.style.animation = ''; } else if (countdown >= 0 && countdown < 8) { el.style.background = '#222'; el.style.color = 'orange'; el.style.animation = ''; } else { el.style.color = '#fff'; el.style.animation = 'tmBlinkBg 1s infinite'; } } // background blinking animation const style = document.createElement('style'); style.textContent = ` @keyframes tmBlinkBg { 0% { background: #800; } 50% { background: #f00; } 100% { background: #800; } } `; document.head.appendChild(style); function startTimerOnce() { if (intervalId) return; updateTimerUI(); intervalId = setInterval(() => { countdown--; updateTimerUI(); }, 1000); } function resetTimerToDefault() { countdown = 20; updateTimerUI(); if (!intervalId) startTimerOnce(); } function findTasksBadgeNode() { const xpath = "//*[contains(normalize-space(.), 'Tasks Completed:')]"; const res = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); return res.singleNodeValue || null; } function readTasksCompletedNumber() { const node = findTasksBadgeNode(); if (!node) return null; const txt = node.textContent || ''; const m = txt.match(/Tasks\s*Completed\s*:\s*(\d+)/i); return m ? parseInt(m[1], 10) : null; } function checkBadgeAndResetIfNeeded() { createTimerElement(); const num = readTasksCompletedNumber(); if (num === null) return; if (lastTaskNumber === null) { lastTaskNumber = num; return; } if (num !== lastTaskNumber) { lastTaskNumber = num; resetTimerToDefault(); } } function setupObservers() { setInterval(checkBadgeAndResetIfNeeded, POLL_MS); const observer = new MutationObserver(() => { clearTimeout(debounceTimeout); debounceTimeout = setTimeout(checkBadgeAndResetIfNeeded, OBS_DEBOUNCE_MS); }); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); } function init() { createTimerElement(); lastTaskNumber = readTasksCompletedNumber(); startTimerOnce(); setupObservers(); setInterval(() => { if (!document.getElementById('tm-task-timer')) createTimerElement(); }, 3000); } if (document.readyState === 'complete' || document.readyState === 'interactive') { init(); } else { window.addEventListener('DOMContentLoaded', init); setTimeout(() => { if (!intervalId) init(); }, 3000); } })();