import { Controller } from "@hotwired/stimulus" export default class extends Controller { static values = { target: { type: Number, default: 0 }, decimal: { type: Boolean, default: false }, duration: { type: Number, default: 2000 } } connect() { this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.animate() this.observer.unobserve(this.element) } }) }, { threshold: 0.5 }) this.observer.observe(this.element) } disconnect() { if (this.observer) { this.observer.disconnect() } } animate() { // Find the target element with data-target-value const targetElement = this.element.querySelector('.stat-number'); if (!targetElement) return; // Get the target value this.targetValue = parseInt(targetElement.getAttribute('data-target-value'), 10) || this.targetValue; const startValue = 0; const startTime = performance.now(); const updateCounter = (currentTime) => { const elapsedTime = currentTime - startTime; const progress = Math.min(elapsedTime / this.durationValue, 1); // Easing function for smooth animation const easeOutQuart = 1 - Math.pow(1 - progress, 4); let currentValue = startValue + (this.targetValue - startValue) * easeOutQuart; if (this.decimalValue && this.targetValue < 10) { currentValue = currentValue.toFixed(1); } else { currentValue = Math.floor(currentValue); } // Update only the text content of the target element targetElement.textContent = currentValue; if (progress < 1) { requestAnimationFrame(updateCounter); } else { const finalValue = this.decimalValue && this.targetValue < 10 ? this.targetValue.toFixed(1) : this.targetValue; targetElement.textContent = finalValue; } } requestAnimationFrame(updateCounter); } }