// ===== 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 = `
${message}
`; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'}; color: white; padding: 1rem 1.5rem; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); z-index: 10000; transform: translateX(400px); transition: transform 0.3s ease; `; document.body.appendChild(notification); // Animate in setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); // Auto remove setTimeout(() => { notification.style.transform = 'translateX(400px)'; setTimeout(() => notification.remove(), 300); }, 5000); } createCustomCursor() { const cursor = document.createElement('div'); cursor.className = 'custom-cursor'; cursor.style.cssText = ` position: fixed; width: 20px; height: 20px; background: var(--primary-color); border-radius: 50%; pointer-events: none; z-index: 9999; mix-blend-mode: difference; transition: transform 0.1s ease; `; document.body.appendChild(cursor); // Update cursor position document.addEventListener('mousemove', (e) => { cursor.style.left = e.clientX - 10 + 'px'; cursor.style.top = e.clientY - 10 + 'px'; }); // Cursor effects on hover document.querySelectorAll('a, button, .course-card, .industry-item').forEach(el => { el.addEventListener('mouseenter', () => { cursor.style.transform = 'scale(2)'; cursor.style.background = 'var(--accent-color)'; }); el.addEventListener('mouseleave', () => { cursor.style.transform = 'scale(1)'; cursor.style.background = 'var(--primary-color)'; }); }); } setupCursorTrail() { const trail = []; const trailLength = 8; for (let i = 0; i < trailLength; i++) { const dot = document.createElement('div'); dot.className = 'cursor-trail'; dot.style.cssText = ` position: fixed; width: 4px; height: 4px; background: var(--primary-color); border-radius: 50%; pointer-events: none; z-index: 9998; opacity: ${1 - i / trailLength}; transition: all 0.1s ease; `; document.body.appendChild(dot); trail.push(dot); } let mouseX = 0; let mouseY = 0; document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); // Animate trail const animateTrail = () => { let x = mouseX; let y = mouseY; trail.forEach((dot, index) => { const nextDot = trail[index + 1] || trail[0]; x += (nextDot.offsetLeft - x) * 0.3; y += (nextDot.offsetTop - y) * 0.3; dot.style.left = x + 'px'; dot.style.top = y + 'px'; }); requestAnimationFrame(animateTrail); }; animateTrail(); } } // ===== UTILITY FUNCTIONS ===== // Debounce function for performance function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Throttle function for scroll events function throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // ===== INITIALIZATION ===== // Initialize animations when DOM is ready document.addEventListener('DOMContentLoaded', () => { new AnimationController(); }); // Add CSS for additional animations const additionalStyles = ` @keyframes ripple { to { transform: scale(4); opacity: 0; } } .notification-content { display: flex; align-items: center; gap: 0.5rem; } .custom-cursor { mix-blend-mode: difference; } .cursor-trail { mix-blend-mode: difference; } .focused .form-input, .focused .form-select, .focused .form-textarea { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); } .loaded .hero-section { animation: fadeIn 1s ease-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } `; // Inject additional styles const styleSheet = document.createElement('style'); styleSheet.textContent = additionalStyles; document.head.appendChild(styleSheet);