|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AnimationController {
|
|
|
constructor() {
|
|
|
this.init();
|
|
|
}
|
|
|
|
|
|
init() {
|
|
|
this.setupMicroAnimations();
|
|
|
this.setupSliderAnimations();
|
|
|
this.setupButtonAnimations();
|
|
|
this.setupMenuAnimations();
|
|
|
this.setupScrollAnimations();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupMicroAnimations() {
|
|
|
|
|
|
document.querySelectorAll('button, .nav-button, .stat-card, .glass-card').forEach(el => {
|
|
|
el.addEventListener('click', (e) => {
|
|
|
el.classList.add('micro-bounce');
|
|
|
setTimeout(() => el.classList.remove('micro-bounce'), 600);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.stat-card, .glass-card').forEach(card => {
|
|
|
card.addEventListener('mouseenter', () => {
|
|
|
card.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupSliderAnimations() {
|
|
|
document.querySelectorAll('.slider-container').forEach(container => {
|
|
|
const track = container.querySelector('.slider-track');
|
|
|
const thumb = container.querySelector('.slider-thumb');
|
|
|
const fill = container.querySelector('.slider-fill');
|
|
|
const input = container.querySelector('input[type="range"]');
|
|
|
|
|
|
if (!input) return;
|
|
|
|
|
|
let isDragging = false;
|
|
|
|
|
|
const updateSlider = (value) => {
|
|
|
const min = parseFloat(input.min) || 0;
|
|
|
const max = parseFloat(input.max) || 100;
|
|
|
const percentage = ((value - min) / (max - min)) * 100;
|
|
|
|
|
|
if (fill) fill.style.width = `${percentage}%`;
|
|
|
if (thumb) thumb.style.left = `${percentage}%`;
|
|
|
};
|
|
|
|
|
|
input.addEventListener('input', (e) => {
|
|
|
updateSlider(e.target.value);
|
|
|
|
|
|
container.classList.add('feedback-pulse');
|
|
|
setTimeout(() => container.classList.remove('feedback-pulse'), 300);
|
|
|
});
|
|
|
|
|
|
|
|
|
if (thumb) {
|
|
|
thumb.addEventListener('mousedown', (e) => {
|
|
|
isDragging = true;
|
|
|
e.preventDefault();
|
|
|
});
|
|
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
|
if (!isDragging) return;
|
|
|
|
|
|
const rect = track.getBoundingClientRect();
|
|
|
const x = e.clientX - rect.left;
|
|
|
const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100));
|
|
|
|
|
|
const min = parseFloat(input.min) || 0;
|
|
|
const max = parseFloat(input.max) || 100;
|
|
|
const value = min + (percentage / 100) * (max - min);
|
|
|
|
|
|
input.value = value;
|
|
|
updateSlider(value);
|
|
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
});
|
|
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
|
isDragging = false;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
updateSlider(input.value);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupButtonAnimations() {
|
|
|
document.querySelectorAll('.button-3d, button.primary, button.secondary').forEach(button => {
|
|
|
|
|
|
button.classList.add('feedback-ripple');
|
|
|
|
|
|
|
|
|
button.addEventListener('mousedown', () => {
|
|
|
button.style.transform = 'translateY(2px) scale(0.98)';
|
|
|
});
|
|
|
|
|
|
button.addEventListener('mouseup', () => {
|
|
|
button.style.transform = '';
|
|
|
});
|
|
|
|
|
|
button.addEventListener('mouseleave', () => {
|
|
|
button.style.transform = '';
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupMenuAnimations() {
|
|
|
|
|
|
document.querySelectorAll('[data-menu]').forEach(menuTrigger => {
|
|
|
menuTrigger.addEventListener('click', (e) => {
|
|
|
e.stopPropagation();
|
|
|
const menu = document.querySelector(menuTrigger.dataset.menu);
|
|
|
if (!menu) return;
|
|
|
|
|
|
const isOpen = menu.classList.contains('menu-open');
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.menu-dropdown').forEach(m => {
|
|
|
m.classList.remove('menu-open');
|
|
|
});
|
|
|
|
|
|
|
|
|
if (!isOpen) {
|
|
|
menu.classList.add('menu-open');
|
|
|
this.animateMenuIn(menu);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
if (!e.target.closest('[data-menu]') && !e.target.closest('.menu-dropdown')) {
|
|
|
document.querySelectorAll('.menu-dropdown').forEach(menu => {
|
|
|
menu.classList.remove('menu-open');
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
animateMenuIn(menu) {
|
|
|
menu.style.opacity = '0';
|
|
|
menu.style.transform = 'translateY(-10px) scale(0.95)';
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
menu.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
|
menu.style.opacity = '1';
|
|
|
menu.style.transform = 'translateY(0) scale(1)';
|
|
|
}, 0);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupScrollAnimations() {
|
|
|
const observerOptions = {
|
|
|
threshold: 0.1,
|
|
|
rootMargin: '0px 0px -50px 0px'
|
|
|
};
|
|
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
|
entries.forEach(entry => {
|
|
|
if (entry.isIntersecting) {
|
|
|
entry.target.classList.add('animate-in');
|
|
|
}
|
|
|
});
|
|
|
}, observerOptions);
|
|
|
|
|
|
document.querySelectorAll('.stat-card, .glass-card, .section').forEach(el => {
|
|
|
observer.observe(el);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addSmoothTransition(element, property = 'all') {
|
|
|
element.style.transition = `${property} 0.3s cubic-bezier(0.4, 0, 0.2, 1)`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
window.animationController = new AnimationController();
|
|
|
});
|
|
|
} else {
|
|
|
window.animationController = new AnimationController();
|
|
|
}
|
|
|
|
|
|
|