| |
| |
| |
| |
| |
|
|
|
|
| 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();
|
| }
|
|
|
|
|