PageSourceSearch

https://fractiontutor.org/timer.js?v=2

js fractiontutor.org collected 2026-10-09 22:04:31 UTC 4,344 bytes, 112 lines download raw bytes

1// Visual session timer — shrinking ring, idle pill -> minute presets -> ring.
2// Exposes window.MathTimer.
3
4(function () {
5    const TWO_MIN = 120;
6
7    function ringSvg(size) {
8        return '<svg class="ft-ring" width="' + size + '" height="' + size + '" viewBox="0 0 36 36">' +
9            '<circle class="ft-ring-trk" cx="18" cy="18" r="15.9" pathLength="100"></circle>' +
10            '<circle class="ft-ring-prg" cx="18" cy="18" r="15.9" pathLength="100"></circle></svg>';
11    }
12
13    function fmt(sec) {
14        const m = Math.floor(sec / 60);
15        const s = sec % 60;
16        return m + ':' + String(s).padStart(2, '0');
17    }
18
19    function create(minutes, el, callbacks) {
20        const cb = callbacks || {};
21        const total = Math.max(1, Math.round(minutes * 60));
22        let remaining = total;
23        let handle = null;
24        let warned = false;
25
26        el.classList.add('ft-timer');
27        el.innerHTML = ringSvg(el.dataset.size || 96) + '<span class="ft-time"></span>';
28        const prg = el.querySelector('.ft-ring-prg');
29        const label = el.querySelector('.ft-time');
30
31        function paint() {
32            const pct = Math.max(0, (remaining / total) * 100);
33            prg.style.strokeDasharray = pct + ' 100';
34            label.textContent = fmt(remaining);
35            el.classList.toggle('warn', remaining <= TWO_MIN && remaining > 0);
36            el.classList.toggle('done', remaining <= 0);
37        }
38
39        function tick() {
40            remaining -= 1;
41            if (remaining <= TWO_MIN && !warned) { warned = true; if (cb.onWarn) cb.onWarn(); }
42            if (remaining <= 0) {
43                remaining = 0;
44                paint();
45                stop();
46                if (cb.onEnd) cb.onEnd();
47                return;
48            }
49            paint();
50            if (cb.onTick) cb.onTick(remaining);
51        }
52
53        function start() { if (!handle) handle = setInterval(tick, 1000); }
54        function stop() { if (handle) { clearInterval(handle); handle = null; } }
55
56        paint();
57        return { start, stop, get remaining() { return remaining; } };
58    }
59
60    const CLOCK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>';
61
62    function mountDock() {
63        if (document.getElementById('ftTimerDock')) return;
64        const host = document.querySelector('.topbar-right');
65        if (!host) return;
66        const dock = document.createElement('div');
67        dock.id = 'ftTimerDock';
68        dock.className = 'ft-dock';
69        host.insertBefore(dock, host.firstChild);
70
71        let ctrl = null;
72
73        function idle() {
74            if (ctrl) { ctrl.stop(); ctrl = null; }
75            dock.innerHTML = '<button class="ft-dock-open" aria-label="Timer">' + CLOCK + ' <span>Timer</span></button>';
76            dock.querySelector('.ft-dock-open').addEventListener('click', pick);
77        }
78
79        function pick() {
80            dock.innerHTML = '<span class="ft-dock-lbl">min</span>' +
81                [5, 10, 15, 20].map(m =>
82                    '<button class="ft-dock-min" data-m="' + m + '">' + m + '</button>').join('') +
83                '<button class="ft-dock-x" aria-label="Close">&times;</button>';
84            dock.querySelectorAll('.ft-dock-min').forEach(b => b.addEventListener('click', () => run(+b.dataset.m)));
85            dock.querySelector('.ft-dock-x').addEventListener('click', idle);
86        }
87
88        function run(minutes) {
89            dock.innerHTML = '<span class="ft-dock-ring" data-size="34"></span><button class="ft-dock-x" aria-label="Stop timer">&times;</button>';
90            const el = dock.querySelector('.ft-dock-ring');
91            ctrl = create(minutes, el, { onEnd: done });
92            ctrl.start();
93            dock.querySelector('.ft-dock-x').addEventListener('click', idle);
94        }
95
96        function done() {
97            ctrl = null;
98            dock.innerHTML = '<span class="ft-dock-done">Time!</span>';
99            setTimeout(idle, 6000);
100        }
101
102        idle();
103    }
104
105    if (document.readyState === 'loading') {
106        document.addEventListener('DOMContentLoaded', mountDock);
107    } else {
108        mountDock();
109    }
110
111    window.MathTimer = { create, fmt };
112})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.