/* ========================================================================== SYSTEM AWAKENING - Event Manager Module Handles all user interactions and system events ========================================================================== */ import { debounce, throttle, isTouchDevice } from './config.js'; /** * Event Manager Class * Manages all event listeners and user interactions */ export class EventManager { /** * Create a new event manager * @param {Object} components - System components that need event handling */ constructor(components = {}) { // Component references this.particleSystem = components.particleSystem; this.progressCircles = components.progressCircles; this.terminal = components.terminal; this.typewriter = components.typewriter; this.animationCoordinator = components.animationCoordinator; // Event state this.isResizing = false; this.lastScrollY = 0; this.scrollDirection = 'down'; this.mousePosition = { x: 0, y: 0 }; this.activeTouches = []; this.isMobile = window.innerWidth <= 768; // Throttled/debounced functions this.handleResizeThrottled = throttle(this.handleResize.bind(this), 250); this.handleScrollThrottled = throttle(this.handleScroll.bind(this), 100); this.handleMouseMoveThrottled = throttle(this.handleMouseMove.bind(this), 50); // Initialize this.init(); } /** * Initialize the event manager */ init() { // Bind all event listeners this.bindEvents(); // Set up touch-specific optimizations if (isTouchDevice()) { this.setupTouchOptimizations(); } // Initialize navigation this.initNavigation(); console.log('Event manager initialized'); } /** * Bind all event listeners */ bindEvents() { // Window events window.addEventListener('resize', this.handleResizeThrottled); window.addEventListener('scroll', this.handleScrollThrottled); window.addEventListener('mousemove', this.handleMouseMoveThrottled); // Mouse events for interactive effects window.addEventListener('mousedown', this.handleMouseDown.bind(this)); window.addEventListener('mouseup', this.handleMouseUp.bind(this)); // Touch events for mobile if (isTouchDevice()) { this.bindTouchEvents(); } // Keyboard events for shortcuts document.addEventListener('keydown', this.handleKeyDown.bind(this)); // Page visibility for performance document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); // Before unload for cleanup window.addEventListener('beforeunload', this.handleBeforeUnload.bind(this)); } /** * Handle window resize */ handleResize() { this.isMobile = window.innerWidth <= 768; // Update particle system if (this.particleSystem && this.particleSystem.onResize) { this.particleSystem.onResize(); } // Update progress circles if (this.progressCircles && this.progressCircles.resize) { this.progressCircles.resize(); } // Dispatch custom event for other components this.dispatchEvent('system:resize', { width: window.innerWidth, height: window.innerHeight, isMobile: this.isMobile }); // Log resize for debugging console.log(`Window resized: ${window.innerWidth}x${window.innerHeight}`); } /** * Handle window scroll */ handleScroll() { const currentScrollY = window.scrollY; // Determine scroll direction this.scrollDirection = currentScrollY > this.lastScrollY ? 'down' : 'up'; this.lastScrollY = currentScrollY; // Dispatch custom scroll event this.dispatchEvent('system:scroll', { scrollY: currentScrollY, direction: this.scrollDirection }); // Update navigation based on scroll position this.updateNavigationOnScroll(); } /** * Handle mouse movement for interactive effects * @param {MouseEvent} event - Mouse event */ handleMouseMove(event) { this.mousePosition.x = event.clientX; this.mousePosition.y = event.clientY; // Optional: Add interactive particle effects if (this.particleSystem && this.particleSystem.interactive) { this.particleSystem.interactive.updateMousePosition( this.mousePosition.x, this.mousePosition.y ); } // Dispatch mouse move event this.dispatchEvent('system:mouseMove', { x: this.mousePosition.x, y: this.mousePosition.y }); } /** * Handle mouse down */ handleMouseDown() { this.dispatchEvent('system:mouseDown'); } /** * Handle mouse up */ handleMouseUp() { this.dispatchEvent('system:mouseUp'); } /** * Bind touch events for mobile devices */ bindTouchEvents() { document.addEventListener('touchstart', this.handleTouchStart.bind(this)); document.addEventListener('touchmove', this.handleTouchMove.bind(this)); document.addEventListener('touchend', this.handleTouchEnd.bind(this)); document.addEventListener('touchcancel', this.handleTouchCancel.bind(this)); } /** * Handle touch start * @param {TouchEvent} event - Touch event */ handleTouchStart(event) { event.preventDefault(); this.activeTouches = Array.from(event.touches); // Dispatch touch event this.dispatchEvent('system:touchStart', { touches: this.activeTouches }); // Optional: Add touch-specific visual feedback this.showTouchFeedback(event.touches[0]); } /** * Handle touch move * @param {TouchEvent} event - Touch event */ handleTouchMove(event) { event.preventDefault(); this.activeTouches = Array.from(event.touches); // Dispatch touch move event this.dispatchEvent('system:touchMove', { touches: this.activeTouches }); } /** * Handle touch end * @param {TouchEvent} event - Touch event */ handleTouchEnd(event) { event.preventDefault(); this.activeTouches = Array.from(event.touches); // Dispatch touch end event this.dispatchEvent('system:touchEnd', { touches: this.activeTouches }); // Remove touch feedback this.removeTouchFeedback(); } /** * Handle touch cancel */ handleTouchCancel() { this.activeTouches = []; // Dispatch touch cancel event this.dispatchEvent('system:touchCancel'); // Remove touch feedback this.removeTouchFeedback(); } /** * Show visual feedback for touch interactions * @param {Touch} touch - Touch point */ showTouchFeedback(touch) { // Create feedback element if it doesn't exist if (!this.touchFeedback) { this.touchFeedback = document.createElement('div'); this.touchFeedback.className = 'touch-feedback'; this.touchFeedback.style.cssText = ` position: fixed; width: 60px; height: 60px; border-radius: 50%; background: radial-gradient(circle, rgba(0,243,255,0.3) 0%, rgba(0,243,255,0) 70%); pointer-events: none; z-index: 1000; transform: translate(-50%, -50%); transition: opacity 0.3s ease; `; document.body.appendChild(this.touchFeedback); } // Update position this.touchFeedback.style.left = `${touch.clientX}px`; this.touchFeedback.style.top = `${touch.clientY}px`; this.touchFeedback.style.opacity = '1'; } /** * Remove touch feedback */ removeTouchFeedback() { if (this.touchFeedback) { this.touchFeedback.style.opacity = '0'; } } /** * Handle keyboard shortcuts * @param {KeyboardEvent} event - Key event */ handleKeyDown(event) { // Ctrl/Cmd + L: Clear terminal if ((event.ctrlKey || event.metaKey) && event.key === 'l') { event.preventDefault(); if (this.terminal && this.terminal.clear) { this.terminal.clear(); } } // Escape: Pause/resume animations if (event.key === 'Escape') { if (this.animationCoordinator) { if (this.animationCoordinator.isAnimationRunning()) { this.animationCoordinator.pause(); } else { this.animationCoordinator.resume(); } } } // Space: Skip to next typewriter text if (event.key === ' ') { event.preventDefault(); if (this.typewriter && this.typewriter.next) { this.typewriter.next(); } } } /** * Handle page visibility change */ handleVisibilityChange() { const isVisible = document.visibilityState === 'visible'; if (isVisible) { // Page became visible - resume animations if (this.animationCoordinator && this.animationCoordinator.resume) { this.animationCoordinator.resume(); } } else { // Page became hidden - pause animations to save resources if (this.animationCoordinator && this.animationCoordinator.pause) { this.animationCoordinator.pause(); } } this.dispatchEvent('system:visibilityChange', { isVisible }); } /** * Handle before unload for cleanup */ handleBeforeUnload() { // Clean up resources this.cleanup(); // Dispatch unload event this.dispatchEvent('system:beforeUnload'); } /** * Set up optimizations for touch devices */ setupTouchOptimizations() { // Increase tap target sizes this.increaseTapTargets(); // Disable hover effects on touch devices this.disableHoverEffects(); // Optimize scrolling performance this.optimizeTouchScrolling(); } /** * Increase tap target sizes for better touch interaction */ increaseTapTargets() { const style = document.createElement('style'); style.textContent = ` .nav-link, .tab, .control-btn { min-height: 44px; min-width: 44px; } /* Increase touch area around interactive elements */ .card { padding: 1.5rem; } `; document.head.appendChild(style); } /** * Disable hover effects on touch devices */ disableHoverEffects() { const style = document.createElement('style'); style.textContent = ` @media (hover: none) and (pointer: coarse) { .card:hover { transform: none !important; } .nav-link:hover { background: transparent !important; } } `; document.head.appendChild(style); } /** * Optimize scrolling performance for touch devices */ optimizeTouchScrolling() { // Enable passive event listeners for better scrolling performance const supportsPassive = (() => { let supports = false; try { const opts = Object.defineProperty({}, 'passive', { get: () => { supports = true; } }); window.addEventListener('test', null, opts); window.removeEventListener('test', null, opts); } catch (e) {} return supports; })(); if (supportsPassive) { // Re-bind scroll event with passive listener window.removeEventListener('scroll', this.handleScrollThrottled); window.addEventListener('scroll', this.handleScrollThrottled, { passive: true }); } } /** * Initialize navigation event handlers */ initNavigation() { // Set up smooth scrolling for anchor links this.setupSmoothScrolling(); // Update active navigation on scroll this.setupNavigationTracking(); } /** * Set up smooth scrolling for navigation links */ setupSmoothScrolling() { const navLinks = document.querySelectorAll('a[href^="#"]'); navLinks.forEach(link => { link.addEventListener('click', (event) => { event.preventDefault(); const targetId = link.getAttribute('href'); const targetElement = document.querySelector(targetId); if (targetElement) { // Smooth scroll to target targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Update navigation state this.updateNavigationState(targetId.substring(1)); } }); }); } /** * Set up navigation tracking based on scroll position */ setupNavigationTracking() { // This is called by the scroll handler // Implementation in handleScroll and updateNavigationOnScroll } /** * Update navigation state based on current section * @param {string} sectionId - ID of the current section */ updateNavigationState(sectionId) { // Update navigation links const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const linkSectionId = link.getAttribute('href').substring(1); if (linkSectionId === sectionId) { link.classList.add('active'); } else { link.classList.remove('active'); } }); // Dispatch navigation change event this.dispatchEvent('system:navigationChange', { sectionId }); } /** * Update navigation based on scroll position */ updateNavigationOnScroll() { // Get all sections const sections = ['hero', 'manifesto', 'data-core', 'terminal']; const scrollPosition = window.scrollY + 100; // Offset for early activation // 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) { this.updateNavigationState(sectionId); break; } } } } /** * Dispatch a custom event * @param {string} eventName - Name of the event * @param {Object} detail - Event details */ dispatchEvent(eventName, detail = {}) { const event = new CustomEvent(eventName, { detail: detail, bubbles: true }); document.dispatchEvent(event); } /** * Clean up all event listeners and resources */ cleanup() { // Remove event listeners window.removeEventListener('resize', this.handleResizeThrottled); window.removeEventListener('scroll', this.handleScrollThrottled); window.removeEventListener('mousemove', this.handleMouseMoveThrottled); document.removeEventListener('keydown', this.handleKeyDown); document.removeEventListener('visibilitychange', this.handleVisibilityChange); // Remove touch event listeners if (isTouchDevice()) { document.removeEventListener('touchstart', this.handleTouchStart); document.removeEventListener('touchmove', this.handleTouchMove); document.removeEventListener('touchend', this.handleTouchEnd); document.removeEventListener('touchcancel', this.handleTouchCancel); } // Remove touch feedback element if (this.touchFeedback && this.touchFeedback.parentNode) { this.touchFeedback.parentNode.removeChild(this.touchFeedback); } console.log('Event manager cleaned up'); } /** * Get current mouse position * @returns {Object} Mouse coordinates {x, y} */ getMousePosition() { return { ...this.mousePosition }; } /** * Get current scroll information * @returns {Object} Scroll information {scrollY, direction} */ getScrollInfo() { return { scrollY: this.lastScrollY, direction: this.scrollDirection }; } /** * Check if device is mobile * @returns {boolean} True if mobile */ isMobileDevice() { return this.isMobile; } /** * Clean up all resources */ dispose() { this.cleanup(); console.log('Event manager disposed'); } }