import { Button } from "../components/Button.tsx"; import { useEffect, useState } from "preact/hooks"; import axios from "axios-web"; interface SharedProps { globalCount: bigint; audioFiles: string[]; } /** * Scrolls the mascot from right to left relative to the viewport */ export function animateMascot() { // create a new element to animate let id = 0; const mascotId = Math.floor(Math.random() * 2) + 1; const scrollSpeed = Math.floor(Math.random() * 30) + 20; const reversalSpeed = 100 - Math.floor(scrollSpeed); const counterButton = document.getElementById("ctr-btn") as HTMLElement; const mascotEl = document.createElement("img"); const parentEl = document.getElementById("mascot-tgt") as HTMLElement; mascotEl.src = `/assets/img/hertaa${mascotId}.gif`; mascotEl.style.right = "-500px"; mascotEl.style.opacity = "60%"; mascotEl.style.top = counterButton.getClientRects()[0].top + scrollY - 408 + "px"; mascotEl.classList.add("z-[0]", "absolute", "bg-scroll"); parentEl.appendChild(mascotEl); let pos = -500; const limit = window.innerWidth + 500; clearInterval(id); id = setInterval(() => { if (pos >= limit) { clearInterval(id); mascotEl.remove(); } else { pos += Math.floor(window.innerWidth / reversalSpeed); mascotEl.style.right = pos + "px"; } }, 12); } export default function Counter(props: SharedProps) { const [count, setCount] = useState(0); const [globalCount, setGlobalCount] = useState( BigInt(props.globalCount ?? 0), ); const [internalCount, setInternalCount] = useState(0); const [timer, setTimer] = useState(0); const onClick = () => { setInternalCount(internalCount + 1); setCount(count + 1); animateMascot(); let audioFile = props.audioFiles[Math.floor(Math.random() * props.audioFiles.length)]; let lastAudioPlayed = audioFile; const audio = new Audio(); // Check if the audio file is the same as the last one played // If so, pick another one if (lastAudioPlayed === audioFile) { audioFile = props.audioFiles[Math.floor(Math.random() * props.audioFiles.length)]; lastAudioPlayed = audioFile; audio.src = audioFile; } else { audio.src = audioFile; } audio.play(); clearTimeout(timer); setTimer(setTimeout(() => { // guard against numbers that are beyond MAX_SAFE_INTEGER. if (internalCount === Number.MAX_SAFE_INTEGER) { console.warn( "Data too large to be submitted and represented safely. Disposing.", ); setCount(0); setInternalCount(0); } else { axios.post( window.location.href, JSON.stringify({ data: internalCount + 1 }), ); console.info( `[${new Date().toISOString()}] Updating global count: ${internalCount + 1}`, ); setInternalCount(0); } }, 5000)); }; useEffect(() => { let ws = new WebSocket(window.location.href.replace("http", "ws")); ws.addEventListener("open", () => { console.log(`[${new Date().toISOString()}] Connected to statistics socket`); }); ws.addEventListener("message", (e) => { console.log(`[${new Date().toISOString()}] Received global count: ${e.data}`); const data = JSON.parse(e.data); setGlobalCount(BigInt(parseInt(data.globalCount))); }); ws.addEventListener("error", () => { console.warn( `[${new Date().toISOString()}] Disconnected from statistics socket, attempting to reconnect...`, ); const backoff = 1000 + Math.random() * 5000; new Promise((resolve) => setTimeout(resolve, backoff)); ws = new WebSocket(window.location.href.replace("http", "ws")); }) }, []); return (

{count.toLocaleString()}

Times the kuru was squished~

Everyone has squished the kuru {globalCount.toLocaleString()} times!

); }