// 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 = `