Download app/src/pi/idle-paws.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 2.98 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/src/pi/idle-paws.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/src/pi/idle-paws.mjs
-
curl -L -o idle-paws.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/src/pi/idle-paws.mjs
2.98 kB
| const IDLE_DELAY = 7_000; | |
| const TRAIL_DURATION = 6_500; | |
| export function createIdlePaws(element, getArea) { | |
| const layer = document.createElement('div'); | |
| layer.className = 'terminal-paws'; layer.hidden = true; | |
| layer.setAttribute('aria-hidden', 'true'); | |
| for (let i = 0; i < 6; i++) layer.append(document.createElement('i')); | |
| element.append(layer); | |
| const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)'); | |
| const listeners = new AbortController(); | |
| let enabled = false, visible = false, disposed = false; | |
| let timer = 0, trailTimer = 0, wakeAt = 0, nextAt = 0; | |
| function canRun() { | |
| return enabled && visible && !disposed && !document.hidden && document.hasFocus() | |
| && !reducedMotion.matches && !document.querySelector('dialog[open]'); | |
| } | |
| function hide() { | |
| clearTimeout(trailTimer); trailTimer = 0; | |
| if (!layer.hidden) layer.hidden = true; | |
| } | |
| function schedule() { | |
| if (!canRun()) { clearTimeout(timer); timer = 0; return; } | |
| // Reuse the pending timer when repeated input moves the deadline later. | |
| if (timer && wakeAt <= nextAt) return; | |
| clearTimeout(timer); wakeAt = nextAt; | |
| timer = setTimeout(tick, Math.max(0, nextAt - Date.now())); | |
| } | |
| function reset() { | |
| hide(); nextAt = Date.now() + IDLE_DELAY; schedule(); | |
| } | |
| function tick() { | |
| timer = 0; | |
| if (!canRun()) return; | |
| if (Date.now() < nextAt) { schedule(); return; } | |
| const area = getArea(); | |
| if (area && area.width >= 220 && area.height >= 180) { | |
| Object.assign(layer.style, { | |
| left: `${area.left}px`, top: `${area.top}px`, | |
| width: `${area.width}px`, height: `${area.height}px`, | |
| }); | |
| layer.style.setProperty('--trail-shift', `${Math.random() * 10}%`); | |
| layer.hidden = false; | |
| trailTimer = setTimeout(hide, TRAIL_DURATION); | |
| } | |
| nextAt = Date.now() + TRAIL_DURATION + 45_000 + Math.random() * 15_000; | |
| schedule(); | |
| } | |
| const options = { passive: true, capture: true, signal: listeners.signal }; | |
| for (const event of ['pointerdown', 'keydown', 'input', 'wheel', 'scroll', 'touchstart', 'touchmove', 'focusin']) { | |
| document.addEventListener(event, reset, options); | |
| } | |
| document.addEventListener('visibilitychange', reset, options); | |
| window.addEventListener('blur', reset, options); | |
| window.addEventListener('focus', reset, options); | |
| reducedMotion.addEventListener('change', reset, options); | |
| const dialogs = new MutationObserver(reset); | |
| for (const dialog of document.querySelectorAll('dialog')) dialogs.observe(dialog, { attributes: true, attributeFilter: ['open'] }); | |
| const visibility = new IntersectionObserver(([entry]) => { visible = entry.isIntersecting; reset(); }); | |
| visibility.observe(element); | |
| return { | |
| reset, | |
| setEnabled(value) { if (value !== enabled) { enabled = value; reset(); } }, | |
| dispose() { | |
| disposed = true; hide(); clearTimeout(timer); | |
| listeners.abort(); dialogs.disconnect(); visibility.disconnect(); layer.remove(); | |
| }, | |
| }; | |
| } | |