// ===== ANIMATIONS & INTERACTIONS ===== class AnimationController { constructor() { this.init(); } init() { this.setupScrollAnimations(); this.setupParallaxEffects(); this.setupInteractiveElements(); this.setupFormAnimations(); this.setupLoadingAnimations(); this.setupCursorEffects(); } // Scroll reveal animations 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('revealed'); // Add staggered animation for grid items if (entry.target.classList.contains('stagger-parent')) { const children = entry.target.querySelectorAll('.stagger-child'); children.forEach((child, index) => { setTimeout(() => { child.classList.add('revealed'); }, index * 100); }); } } }); }, observerOptions); // Observe all scroll-reveal elements document.querySelectorAll('.scroll-reveal').forEach(el => { observer.observe(el); }); } // Parallax effects setupParallaxEffects() { window.addEventListener('scroll', () => { const scrolled = window.pageYOffset; const parallaxElements = document.querySelectorAll('.parallax'); parallaxElements.forEach(element => { const speed = element.dataset.speed || 0.5; const yPos = -(scrolled * speed); element.style.transform = `translateY(${yPos}px)`; }); }); } // Interactive elements setupInteractiveElements() { // Button hover effects document.querySelectorAll('.btn').forEach(btn => { btn.addEventListener('mouseenter', this.createRippleEffect.bind(this)); }); // Card hover effects document.querySelectorAll('.course-card, .industry-item, .feature-item').forEach(card => { card.addEventListener('mouseenter', this.enhanceCardHover.bind(this)); card.addEventListener('mouseleave', this.resetCardHover.bind(this)); }); // Smooth scrolling for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', this.smoothScroll.bind(this)); }); } // Form animations setupFormAnimations() { const form = document.getElementById('contactForm'); if (!form) return; // Input focus effects const inputs = form.querySelectorAll('input, textarea, select'); inputs.forEach(input => { input.addEventListener('focus', this.animateInputFocus.bind(this)); input.addEventListener('blur', this.animateInputBlur.bind(this)); }); // Form submission form.addEventListener('submit', this.handleFormSubmission.bind(this)); } // Loading animations setupLoadingAnimations() { // Page load animation window.addEventListener('load', () => { document.body.classList.add('loaded'); this.animateHeroSection(); }); // Loading spinner for async operations this.createLoadingSpinner(); } // Cursor effects setupCursorEffects() { this.createCustomCursor(); this.setupCursorTrail(); } // ===== HELPER METHODS ===== createRippleEffect(event) { const button = event.currentTarget; const ripple = document.createElement('span'); const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = event.clientX - rect.left - size / 2; const y = event.clientY - rect.top - size / 2; ripple.style.cssText = ` position: absolute; width: ${size}px; height: ${size}px; left: ${x}px; top: ${y}px; background: rgba(255, 255, 255, 0.3); border-radius: 50%; transform: scale(0); animation: ripple 0.6s linear; pointer-events: none; `; button.appendChild(ripple); setTimeout(() => ripple.remove(), 600); } enhanceCardHover(event) { const card = event.currentTarget; card.style.transform = 'translateY(-8px) scale(1.02)'; card.style.boxShadow = '0 25px 50px -12px rgba(0, 0, 0, 0.25)'; } resetCardHover(event) { const card = event.currentTarget; card.style.transform = 'translateY(0) scale(1)'; card.style.boxShadow = ''; } smoothScroll(event) { event.preventDefault(); const targetId = event.currentTarget.getAttribute('href'); const targetElement = document.querySelector(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } animateInputFocus(event) { const input = event.currentTarget; input.parentElement.classList.add('focused'); input.style.transform = 'scale(1.02)'; } animateInputBlur(event) { const input = event.currentTarget; input.parentElement.classList.remove('focused'); input.style.transform = 'scale(1)'; } async handleFormSubmission(event) { event.preventDefault(); const form = event.currentTarget; const submitBtn = form.querySelector('.submit-btn'); const originalText = submitBtn.textContent; // Show loading state submitBtn.innerHTML = '
Đang gửi...'; submitBtn.disabled = true; try { // Simulate form submission (replace with actual API call) await new Promise(resolve => setTimeout(resolve, 2000)); // Show success message this.showNotification('Gửi yêu cầu thành công! Chúng tôi sẽ liên hệ sớm nhất.', 'success'); form.reset(); } catch (error) { this.showNotification('Có lỗi xảy ra. Vui lòng thử lại.', 'error'); } finally { // Reset button state submitBtn.innerHTML = originalText; submitBtn.disabled = false; } } animateHeroSection() { const heroTitle = document.querySelector('.hero-title'); const heroSubtitle = document.querySelector('.hero-subtitle'); const heroButtons = document.querySelector('.hero-buttons'); if (heroTitle) { setTimeout(() => heroTitle.classList.add('animate-fade-in-up'), 300); } if (heroSubtitle) { setTimeout(() => heroSubtitle.classList.add('animate-fade-in-up'), 600); } if (heroButtons) { setTimeout(() => heroButtons.classList.add('animate-fade-in-up'), 900); } } createLoadingSpinner() { const spinner = document.createElement('div'); spinner.className = 'loading-spinner'; spinner.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; display: none; `; document.body.appendChild(spinner); } showLoadingSpinner() { const spinner = document.querySelector('.loading-spinner'); if (spinner) spinner.style.display = 'block'; } hideLoadingSpinner() { const spinner = document.querySelector('.loading-spinner'); if (spinner) spinner.style.display = 'none'; } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `