Greasy Fork

Greasy Fork is available in English.

双色胶囊计时器(含运行状态自动同步)

红绿胶囊计时器,数字自适应,运行/暂停状态全标签页同步,新开页面自动跟随计时

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         双色胶囊计时器(含运行状态自动同步)
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  红绿胶囊计时器,数字自适应,运行/暂停状态全标签页同步,新开页面自动跟随计时
// @author       ChatGPT
// @match        *://*/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = '__tm_pill_timer_fullsync_v1__';
    // 默认数据结构,计数数字和运行状态分开
    const defaultData = {
        red: { value: 0, running: false },
        green: { value: 0, running: false }
    };

    // 读取本地数据
    function getStored() {
        let d = defaultData;
        try {
            let str = localStorage.getItem(STORAGE_KEY);
            if (str) d = Object.assign({}, d, JSON.parse(str));
        } catch(e) {}
        // 补充类型安全
        if(typeof d.red !== "object" || typeof d.red.value !== "number") d.red = { value: 0, running: false };
        if(typeof d.green !== "object" || typeof d.green.value !== "number") d.green = { value: 0, running: false };
        return d;
    }
    // 存储到本地
    function setStored(obj) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(obj));
    }

    // CSS
    const style = document.createElement('style');
    style.innerHTML = `
    .tm-pill-timer-container {
        position: fixed;
        right: 40px;
        bottom: 40px;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: 18px;
        z-index: 999999;
        font-family: 'Segoe UI', Arial, sans-serif;
        user-select: none;
    }
    .tm-pill-timers-col {
        display: flex;
        flex-direction: column;
        gap: 14px;
        align-items: flex-end;
    }
    .tm-pill-timer {
        min-width: 80px;
        padding: 0 24px;
        height: 42px;
        background: #ee5555;
        border-radius: 24px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #fff;
        font-size: 1.18rem;
        font-weight: bold;
        font-variant-numeric: tabular-nums;
        letter-spacing: 2px;
        box-shadow: 0 2.5px 12px rgba(0,0,0,0.10);
        border: 2.6px solid rgba(0,0,0,0.10);
        cursor: pointer;
        transition: box-shadow 0.18s, background 0.16s;
        outline: none;
        user-select: text;
    }
    .tm-pill-timer.green {
        background: #36c24b;
    }
    .tm-pill-timer.active {
        box-shadow: 0 0 0 4px #3338;
        background: linear-gradient(90deg, rgba(54,194,75,0.85), rgba(54,194,75,1));
    }
    .tm-pill-timer.red.active {
        background: linear-gradient(90deg, rgba(238,85,85,0.88), #ee5555);
    }
    .tm-pill-reset-btn {
        margin-top: 10px;
        background: #eee;
        color: #333;
        font-size: 1rem;
        border-radius: 18px;
        border: none;
        padding: 7px 18px;
        cursor: pointer;
        box-shadow: 0 1px 5px rgba(0,0,0,0.06);
        transition: background 0.18s;
        outline: none;
        align-self: flex-end;
    }
    .tm-pill-reset-btn:hover {
        background: #fad7d7;
        color: #b20a0a;
    }
    `;
    document.head.appendChild(style);

    // DOM
    const container = document.createElement('div');
    container.className = 'tm-pill-timer-container';
    container.innerHTML = `
        <div class="tm-pill-timers-col">
            <div class="tm-pill-timer red" id="tm-pill-timer-1">00:00:00</div>
            <div class="tm-pill-timer green" id="tm-pill-timer-2">00:00:00</div>
        </div>
        <button class="tm-pill-reset-btn">清零</button>
    `;
    document.body.appendChild(container);

    // 工具
    function secToHMS(sec) {
        const h = Math.floor(sec / 3600);
        const m = Math.floor((sec % 3600) / 60);
        const s = sec % 60;
        return (
            (h<10?'0':'')+h+':'+
            (m<10?'0':'')+m+':'+
            (s<10?'0':'')+s
        );
    }

    // 计时器对象
    function createTimer(domElem, storeKey, colorClass) {
        let timer = getStored();
        let running = !!timer[storeKey].running;
        let value = +timer[storeKey].value || 0;
        let interval = null;
        function updateView() {
            domElem.textContent = secToHMS(value);
            if (running) domElem.classList.add('active');
            else domElem.classList.remove('active');
        }
        function start() {
            if (!running) {
                running = true;
                updateView();
                updateStored('running', true);
                interval = setInterval(() => {
                    value++;
                    updateStored('value', value);
                    updateView();
                }, 1000);
            }
        }
        function stop() {
            if (running) {
                running = false;
                updateView();
                updateStored('running', false);
                if(interval) { clearInterval(interval); interval = null; }
            }
        }
        function updateStored(attr, dat) {
            // 存储一次完整对象
            let now = getStored();
            now[storeKey][attr] = dat;
            if(attr==='value') value = dat;
            if(attr==='running') running = dat;
            setStored(now);
        }
        function toggle() {
            running ? stop() : start();
        }
        function reset() {
            stop();
            value = 0;
            updateStored('value', 0);
            updateStored('running', false);
            updateView();
        }
        function syncFromStorage() {
            const s = getStored();
            let newVal = +s[storeKey].value || 0;
            let shouldRunning = !!s[storeKey].running;
            value = newVal;
            updateView();
            if (shouldRunning && !running) {
                start();
            } else if (!shouldRunning && running) {
                stop();
            }
        }
        // 初始化
        updateView();
        if (running) start();

        domElem.onclick = toggle;
        return { reset, syncFromStorage, getValue:()=>value, setValue:(v)=>{value=v;updateView();}, isRunning:()=>running, updateView, start, stop };
    }

    // 实例&事件绑定
    const timer1 = createTimer(document.getElementById('tm-pill-timer-1'), 'red', 'red');
    const timer2 = createTimer(document.getElementById('tm-pill-timer-2'), 'green', 'green');

    container.querySelector('.tm-pill-reset-btn').onclick = function(){
        timer1.reset();
        timer2.reset();
        setStored(JSON.stringify(defaultData));
    };

    // ---- 多标签同步 ----
    window.addEventListener('storage', function(e){
        if(e.key === STORAGE_KEY && e.newValue){
            // 同步两个定时器
            timer1.syncFromStorage();
            timer2.syncFromStorage();
        }
    });

    // 定期兜底同步
    setInterval(() => {
        timer1.syncFromStorage();
        timer2.syncFromStorage();
    }, 2000);
    window.addEventListener('beforeunload', ()=>{
        // 最后时刻刷一次(避免关闭丢状态)
        let s = getStored();
        s.red.value = timer1.getValue();
        s.green.value = timer2.getValue();
        setStored(s);
    });

})();