// Main Application Logic // FAQ Toggle document.addEventListener('DOMContentLoaded', function() { // Cache DOM elements const faqItems = document.querySelectorAll('.faq-item'); const resultModal = document.getElementById('result-modal'); const processingModal = document.getElementById('processing-modal'); const closeBtn = document.querySelector('.close'); const closeModalBtn = document.getElementById('close-modal'); let isAnimating = false; // FAQ Toggle with requestAnimationFrame faqItems.forEach(item => { const question = item.querySelector('.faq-question'); question.addEventListener('click', () => { if (isAnimating) return; isAnimating = true; requestAnimationFrame(() => { // Close other items faqItems.forEach(otherItem => { if (otherItem !== item && otherItem.classList.contains('active')) { otherItem.classList.remove('active'); } }); // Toggle current item item.classList.toggle('active'); isAnimating = false; }); }); }); // Smooth scroll for anchor links - оптимизированная версия document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href'); if (targetId === '#') return; const target = document.querySelector(targetId); if (target) { // Используем современный smooth scroll const targetPosition = target.getBoundingClientRect().top + window.pageYOffset; const startPosition = window.pageYOffset; const distance = targetPosition - startPosition; const duration = 800; let startTime = null; function animation(currentTime) { if (startTime === null) startTime = currentTime; const timeElapsed = currentTime - startTime; const progress = Math.min(timeElapsed / duration, 1); // Easing function const ease = easeOutQuart(progress); window.scrollTo(0, startPosition + distance * ease); if (timeElapsed < duration) { requestAnimationFrame(animation); } } // Easing function function easeOutQuart(x) { return 1 - Math.pow(1 - x, 4); } requestAnimationFrame(animation); } }); }); // Animate statistics on scroll - оптимизированная версия function animateNumbers() { const stats = [ { element: document.getElementById('users-count'), target: 102112, suffix: '', formatted: false }, { element: document.getElementById('photos-count'), target: 218694, suffix: '', formatted: false }, { element: document.getElementById('time-count'), target: 4.9, suffix: '', formatted: true } ]; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const stat = stats.find(s => s.element === entry.target); if (stat) { // Используем requestAnimationFrame для анимации requestAnimationFrame(() => { animateNumberOptimized(stat); }); observer.unobserve(entry.target); } } }); }, { threshold: 0.5, rootMargin: '50px' // Начинаем анимацию немного раньше }); stats.forEach(stat => { if (stat.element) { // Устанавливаем начальные значения stat.element.textContent = '0'; observer.observe(stat.element); } }); }; // Оптимизированная анимация чисел с requestAnimationFrame function animateNumberOptimized(stat) { const duration = 1500; // Уменьшено с 2000 для лучшей производительности const startTime = performance.now(); const startValue = 0; function animate(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Easing function const ease = easeOutCubic(progress); const currentValue = startValue + (stat.target - startValue) * ease; // Обновляем значение if (stat.formatted) { stat.element.textContent = currentValue.toFixed(1) + stat.suffix; } else { stat.element.textContent = Math.floor(currentValue).toLocaleString('ru-RU') + stat.suffix; } if (progress < 1) { requestAnimationFrame(animate); } else { // Финальное значение if (stat.formatted) { stat.element.textContent = stat.target.toFixed(1) + stat.suffix; } else { stat.element.textContent = stat.target.toLocaleString('ru-RU') + stat.suffix; } } } function easeOutCubic(x) { return 1 - Math.pow(1 - x, 3); } requestAnimationFrame(animate); } // Оптимизированная версия старой функции (для обратной совместимости) function animateNumber(element, target, suffix) { requestAnimationFrame(() => { animateNumberOptimized({ element: element, target: target, suffix: suffix, formatted: target < 10 }); }); } // Инициализация анимации чисел if (document.getElementById('users-count') && document.getElementById('photos-count') && document.getElementById('time-count')) { animateNumbers(); } // Modal functionality with requestAnimationFrame function hideModal(modal) { if (!modal) return; requestAnimationFrame(() => { modal.style.display = 'none'; document.body.classList.remove('modal-open'); // Используем класс вместо inline стилей }); } function showModal(modal) { if (!modal) return; requestAnimationFrame(() => { modal.style.display = 'block'; document.body.classList.add('modal-open'); }); } // Обработчики закрытия модальных окон if (closeBtn) { closeBtn.addEventListener('click', () => { hideModal(resultModal); hideModal(processingModal); }); } if (closeModalBtn) { closeModalBtn.addEventListener('click', () => { hideModal(resultModal); }); } // Close modals when clicking outside window.addEventListener('click', (e) => { if (e.target === resultModal) { hideModal(resultModal); } if (e.target === processingModal) { // Don't allow closing processing modal by clicking outside } }); // Предотвращаем скролл при открытых модалках через CSS класс const style = document.createElement('style'); style.textContent = ` .modal-open { overflow: hidden; position: fixed; width: 100%; height: 100%; } .modal-open body { overflow: hidden; } `; document.head.appendChild(style); // Debounce для resize events let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { // При ресайзе можно обновить позиции элементов // но стараемся избегать чтения геометрических свойств }, 250); }); }); // Оптимизированная функция showResult с requestAnimationFrame function showResult(imageUrl) { const modal = document.getElementById('result-modal'); const resultImage = document.getElementById('result-image'); if (!modal || !resultImage) return; // Создаем содержимое вне DOM const fullImageUrl = imageUrl.startsWith('http') ? imageUrl : new URL(imageUrl, window.location.origin).href; const wrapper = document.createElement('div'); wrapper.className = 'result-blur-wrapper'; wrapper.innerHTML = ` Результат обработки
Открой результат в Telegram
`; // Используем requestAnimationFrame для обновления DOM requestAnimationFrame(() => { // Очищаем и добавляем новое содержимое while (resultImage.firstChild) { resultImage.removeChild(resultImage.firstChild); } resultImage.appendChild(wrapper); // Показываем модалку modal.style.display = 'block'; document.body.classList.add('modal-open'); }); } // Queue simulation removed - queue info now only shows in modal // Дополнительные оптимизации (function() { // Предотвращаем layout thrashing let scheduled = false; const batchedUpdates = []; function batchUpdate(callback) { batchedUpdates.push(callback); if (!scheduled) { scheduled = true; requestAnimationFrame(() => { const updates = batchedUpdates.slice(); batchedUpdates.length = 0; scheduled = false; updates.forEach(cb => cb()); }); } } // Экспортируем для использования в других скриптах window.batchUpdate = batchUpdate; })();