/* ========================================================================== SYSTEM AWAKENING - Animation Coordinator Module Manages and coordinates all animations across the system ========================================================================== */ import { ANIMATION, NAV_CONFIG } from './config.js'; /** * Animation Coordinator Class * Manages all animations and their synchronization */ export class AnimationCoordinator { /** * Create a new animation coordinator * @param {Object} components - All animation components */ constructor(components = {}) { // Component references this.particleSystem = components.particleSystem; this.progressCircles = components.progressCircles; this.terminal = components.terminal; this.typewriter = components.typewriter; // Animation state this.lastFrameTime = 0; this.animationId = null; this.isRunning = false; // Scroll tracking this.currentSection = 'hero'; this.scrollObserver = null; // Performance monitoring this.frameCount = 0; this.lastFpsUpdate = 0; this.currentFps = 60; // Initialize this.init(); } /** * Initialize the animation coordinator */ init() { // Start animation loop this.start(); // Set up scroll animations this.initScrollAnimations(); // Set up navigation tracking this.initNavigationTracking(); // Start performance monitoring this.startPerformanceMonitoring(); console.log('Animation coordinator initialized'); } /** * Start the main animation loop */ start() { if (this.isRunning) return; this.isRunning = true; this.lastFrameTime = performance.now(); const animate = (timestamp) => { if (!this.isRunning) return; // Calculate delta time const deltaTime = (timestamp - this.lastFrameTime) / 1000; // Convert to seconds this.lastFrameTime = timestamp; // Update frame count for FPS calculation this.frameCount++; // Update all animations this.updateAnimations(deltaTime); // Render all components this.renderAll(); // Continue animation loop this.animationId = requestAnimationFrame(animate); }; // Start animation loop this.animationId = requestAnimationFrame(animate); } /** * Stop the animation loop */ stop() { this.isRunning = false; if (this.animationId) { cancelAnimationFrame(this.animationId); this.animationId = null; } } /** * Update all animations * @param {number} deltaTime - Time since last frame in seconds */ updateAnimations(deltaTime) { // Update particle system if (this.particleSystem && this.particleSystem.update) { this.particleSystem.update(deltaTime); } // Update typewriter animation is handled internally by its class // Update terminal animation is handled internally by its class // Update progress circles animation is handled internally by its class } /** * Render all components */ renderAll() { // Render particle system if (this.particleSystem && this.particleSystem.render) { this.particleSystem.render(); } // Other components handle their own rendering internally } /** * Initialize scroll-triggered animations */ initScrollAnimations() { // Set up Intersection Observer for card animations this.setupCardAnimations(); // Set up parallax effects this.setupParallaxEffects(); // Update navigation on scroll this.setupScrollTracking(); } /** * Set up card reveal animations using Intersection Observer */ setupCardAnimations() { const cards = document.querySelectorAll('.card'); if (!cards.length) return; // Create observer with options const observerOptions = { root: null, // Use viewport as root rootMargin: '50px', // Trigger slightly before entering viewport threshold: 0.1 // Trigger when 10% of element is visible }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // Get delay from data attribute or calculate const delay = entry.target.dataset.delay || 0; // Animate card in after delay setTimeout(() => { entry.target.classList.add('visible'); // Add slight stagger for visual interest const index = Array.from(cards).indexOf(entry.target); const staggerDelay = index * 0.05; // 50ms per card setTimeout(() => { // Add hover-ready class after animation completes entry.target.classList.add('animation-complete'); }, staggerDelay * 1000); }, delay * 1000); // Unobserve after animation starts observer.unobserve(entry.target); } }); }, observerOptions); // Observe all cards cards.forEach(card => observer.observe(card)); } /** * Set up parallax effects for depth */ setupParallaxEffects() { // Simple parallax for hero section const heroSection = document.getElementById('hero'); if (!heroSection) return; // Use scroll event with throttling let lastScrollY = window.scrollY; let ticking = false; const updateParallax = () => { const scrollY = window.scrollY; // Calculate parallax offset (subtle effect) const parallaxOffset = scrollY * ANIMATION.PARALLAX_SPEED; // Apply transform to hero section heroSection.style.transform = `translateY(${parallaxOffset}px)`; // Update last scroll position lastScrollY = scrollY; ticking = false; }; const onScroll = () => { if (!ticking) { requestAnimationFrame(updateParallax); ticking = true; } }; // Add scroll listener window.addEventListener('scroll', onScroll, { passive: true }); } /** * Set up scroll tracking for section navigation */ setupScrollTracking() { // Throttle scroll events for performance let ticking = false; const updateCurrentSection = () => { // Get all sections const sections = NAV_CONFIG.SECTIONS.map(s => s.id); const scrollPosition = window.scrollY + 100; // Offset for nav // Find current section for (const sectionId of sections) { const section = document.getElementById(sectionId); if (section) { const sectionTop = section.offsetTop; const sectionHeight = section.offsetHeight; if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) { if (this.currentSection !== sectionId) { this.currentSection = sectionId; this.updateNavigation(); } break; } } } ticking = false; }; const onScroll = () => { if (!ticking) { requestAnimationFrame(updateCurrentSection); ticking = true; } }; // Add scroll listener window.addEventListener('scroll', onScroll, { passive: true }); } /** * Initialize navigation section tracking */ initNavigationTracking() { // Update navigation on page load this.updateNavigation(); // Set up click handlers for navigation this.setupNavigationClickHandlers(); } /** * Update navigation UI based on current section */ updateNavigation() { const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const sectionId = link.getAttribute('href').substring(1); if (sectionId === this.currentSection) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } /** * Set up click handlers for navigation links */ setupNavigationClickHandlers() { const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href').substring(1); const targetSection = document.getElementById(targetId); if (targetSection) { // Smooth scroll to section targetSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Update current section this.currentSection = targetId; this.updateNavigation(); } }); }); } /** * Start performance monitoring */ startPerformanceMonitoring() { // Update FPS every second setInterval(() => { this.updateFps(); }, 1000); } /** * Update FPS calculation */ updateFps() { const now = performance.now(); const elapsed = now - this.lastFpsUpdate; if (elapsed >= 1000) { this.currentFps = Math.round((this.frameCount * 1000) / elapsed); this.frameCount = 0; this.lastFpsUpdate = now; // Adaptive quality based on FPS this.adjustQualityForPerformance(); } } /** * Adjust animation quality based on performance */ adjustQualityForPerformance() { // Only adjust if adaptive quality is enabled // This is a placeholder for more sophisticated quality adjustment if (this.currentFps < 30) { console.warn(`Low FPS detected: ${this.currentFps}. Consider reducing particle count.`); } } /** * Get current FPS * @returns {number} Current frames per second */ getFps() { return this.currentFps; } /** * Get current section ID * @returns {string} Current section ID */ getCurrentSection() { return this.currentSection; } /** * Check if animation loop is running * @returns {boolean} True if running */ isAnimationRunning() { return this.isRunning; } /** * Pause all animations */ pause() { this.stop(); // Pause individual components if (this.typewriter && this.typewriter.pause) { this.typewriter.pause(); } if (this.terminal) { // Terminal auto-logging is handled by interval } console.log('Animations paused'); } /** * Resume all animations */ resume() { this.start(); // Resume individual components if (this.typewriter && this.typewriter.resume) { this.typewriter.resume(); } console.log('Animations resumed'); } /** * Clean up all resources */ dispose() { // Stop animation loop this.stop(); // Dispose individual components if (this.particleSystem && this.particleSystem.dispose) { this.particleSystem.dispose(); } if (this.progressCircles && this.progressCircles.dispose) { this.progressCircles.dispose(); } if (this.terminal && this.terminal.dispose) { this.terminal.dispose(); } if (this.typewriter && this.typewriter.dispose) { this.typewriter.dispose(); } // Remove event listeners window.removeEventListener('scroll', this.handleScroll); console.log('Animation coordinator disposed'); } }