/** * Main function to initialize the animated stat counters when they scroll into view. */ function initializeAnimatedStats() { const statsContainer = document.querySelector( '.minigame_content-block-wrapper' ); if (!statsContainer) { console.error( "Stats container '.minigame_content-block-wrapper' not found." ); return; } // URLs for the API endpoints const liveActivityUrl = 'https://hicomply-puzzle.replit.app/api/stats/live-activity'; const highScoreUrl = 'https://hicomply-puzzle.replit.app/api/stats/daily-high-score'; // Elements to update const peopleRightNowElement = document.getElementById('peoplerightnow'); const highScoreElement = document.getElementById('todayhighscore'); /** * Animates a number counting up from 0 to a final value. * @param {HTMLElement} element - The HTML element to update. * @param {number} endValue - The final number to count up to. * @param {number} duration - The duration of the animation in milliseconds. */ const animateCountUp = (element, endValue, duration = 2000) => { if (!element) return; let startTime = null; const startValue = 0; // Easing function for a smooth, decelerating animation (ease-out cubic) const easeOutCubic = (t, b, c, d) => c * ((t = t / d - 1) * t * t + 1) + b; const step = timestamp => { if (!startTime) startTime = timestamp; const progress = timestamp - startTime; const currentValue = Math.floor( easeOutCubic(progress, startValue, endValue, duration) ); if (progress < duration) { element.textContent = currentValue.toLocaleString(); requestAnimationFrame(step); } else { // Ensure the final value is exact and formatted element.textContent = endValue.toLocaleString(); } }; requestAnimationFrame(step); }; /** * Fetches data from all endpoints, processes it, and starts the animations. */ const fetchAndAnimateStats = async () => { try { // Fetch both endpoints concurrently for better performance const [liveActivityResponse, highScoreResponse] = await Promise.all([ fetch(liveActivityUrl), fetch(highScoreUrl), ]); if (!liveActivityResponse.ok || !highScoreResponse.ok) { throw new Error('Network response was not ok.'); } const liveActivityResult = await liveActivityResponse.json(); const highScoreResult = await highScoreResponse.json(); // --- Process Data and Apply Logic --- // Logic for active users: use 15 if the value is less than 15 const activeUsers = liveActivityResult.data.activeUsers; const peopleRightNowValue = Math.max(15, activeUsers); // Get the high score const highScoreValue = highScoreResult.data.score; // --- Trigger Animations --- animateCountUp(peopleRightNowElement, peopleRightNowValue); animateCountUp(highScoreElement, highScoreValue); } catch (error) { console.error('Failed to fetch or animate stats:', error); // Optional: You could hide the elements or show an error state } }; // Create an Intersection Observer to trigger the fetch and animation // when the element is 50% visible in the viewport. const observer = new IntersectionObserver( (entries, observer) => { entries.forEach(entry => { // When the element is intersecting (visible) if (entry.isIntersecting) { // Fetch data and start the animations fetchAndAnimateStats(); // Stop observing the element to ensure the animation only runs once observer.unobserve(entry.target); } }); }, { threshold: 0.5, } ); // Start observing the stats container observer.observe(statsContainer); } // Run the initialization function initializeAnimatedStats();