// ── Watch plugin — analog clock overlay ── const $ = id => document.getElementById(id); const LAYER_MAP = { digits: 'watch-layer-digits', hour: 'watch-layer-hour', minute: 'watch-layer-minute', second: 'watch-second-wobble', }; let visible = false; let tickTimer = null; let idleTimer = null; let lastActivity = Date.now(); let showedAt = 0; let wobbleKick = null, wobbleRebound = null; let svgCache = {}; let loaded = false; const IDLE_MINUTES = 5; // ── SVG loading ── async function loadSvg(layer) { if (svgCache[layer]) return svgCache[layer]; try { const res = await fetch(`plugins/watch/faces/simple/${layer}.svg`); if (!res.ok) return ''; let svg = await res.text(); svg = svg.replace(/<\?xml[\s\S]*?\?>/gi, '').replace(//gi, '').trim(); svgCache[layer] = svg; return svg; } catch { return ''; } } async function loadFace() { if (loaded) return; for (const [layer, elId] of Object.entries(LAYER_MAP)) { const el = $(elId); if (el) el.innerHTML = await loadSvg(layer); } loaded = true; } // ── Clock logic ── function tick() { const now = new Date(); const h12 = now.getHours() % 12; const m = now.getMinutes(); const s = now.getSeconds(); const hourEl = $('watch-layer-hour'); const minEl = $('watch-layer-minute'); const secEl = $('watch-layer-second'); if (hourEl) hourEl.style.transform = `rotate(${(((h12 + m / 60) / 12) * 360).toFixed(3)}deg)`; if (minEl) minEl.style.transform = `rotate(${(((m + s / 60) / 60) * 360).toFixed(3)}deg)`; if (secEl) secEl.style.transform = `rotate(${((s / 60) * 360).toFixed(3)}deg)`; triggerWobble(); } function triggerWobble() { const wobEl = $('watch-second-wobble'); if (!wobEl) return; clearTimeout(wobbleKick); clearTimeout(wobbleRebound); wobEl.style.transform = 'rotate(0deg)'; requestAnimationFrame(() => { wobEl.style.transform = 'rotate(0.78deg)'; wobbleKick = setTimeout(() => { wobEl.style.transform = 'rotate(-0.25deg)'; }, 95); wobbleRebound = setTimeout(() => { wobEl.style.transform = 'rotate(0deg)'; }, 205); }); } function startTick() { if (tickTimer) return; tick(); tickTimer = setInterval(tick, 1000); } function stopTick() { if (tickTimer) { clearInterval(tickTimer); tickTimer = null; } clearTimeout(wobbleKick); clearTimeout(wobbleRebound); } // ── Show / hide ── async function show() { if (visible) return; visible = true; const overlay = $('watch-overlay'); if (!overlay) return; showedAt = Date.now(); await loadFace(); overlay.classList.remove('hidden'); startTick(); requestAnimationFrame(() => overlay.classList.add('visible')); } function hide() { if (!visible) return; visible = false; const overlay = $('watch-overlay'); if (!overlay) return; overlay.classList.remove('visible'); stopTick(); setTimeout(() => { if (!visible) overlay.classList.add('hidden'); }, 250); } function toggle() { visible ? hide() : show(); } // ── Idle auto-show ── function resetIdleTimer() { clearTimeout(idleTimer); if (!IDLE_MINUTES) return; idleTimer = setTimeout(() => { if (visible) return; if (document.visibilityState !== 'visible') return; if (document.querySelector('.overlay.open')) return; show(); }, IDLE_MINUTES * 60000); } function onActivity() { const now = Date.now(); if (now - lastActivity < 300) return; lastActivity = now; if (visible && now - showedAt > 500) hide(); resetIdleTimer(); } // ── Init ── function init() { const overlay = $('watch-overlay'); if (!overlay) return; overlay.addEventListener('click', () => { hide(); onActivity(); }); window.addEventListener('keydown', e => { if (e.key === 'Escape' && visible) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); hide(); onActivity(); } }, { capture: true }); for (const ev of ['pointermove', 'pointerdown', 'keydown', 'wheel', 'touchstart']) { window.addEventListener(ev, onActivity, { passive: true }); } document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') onActivity(); }); resetIdleTimer(); window.hyprcrd?.registerPlugin({ name: 'watch', version: '1.0.0' }); } window._watchToggle = toggle; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }