/* ======================================== MSPE Ultimate UI Interactions v2.0 Advanced JavaScript for Premium UX ======================================== */ document.addEventListener('DOMContentLoaded', function() { // Initialize all modules initCustomCursor(); initParticles(); initScrollProgress(); initMagneticButtons(); initSmoothScroll(); initParallax(); initRevealAnimations(); initTiltEffect(); initRippleEffect(); initTypewriter(); initLazyLoad(); }); /* ==================== Optimized Custom Cursor ==================== */ function initCustomCursor() { // Custom cursor disabled return; // Only on desktop with no reduced motion preference if (window.innerWidth < 768 || 'ontouchstart' in window || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; // Add class to body to hide default cursor document.body.classList.add('custom-cursor-enabled'); const cursor = document.createElement('div'); cursor.classList.add('custom-cursor'); document.body.appendChild(cursor); const trails = []; const trailLength = 5; // Reduced from 12 for (let i = 0; i < trailLength; i++) { const trail = document.createElement('div'); trail.classList.add('cursor-trail'); trail.style.opacity = (1 - i / trailLength) * 0.4; const size = 6 - i; trail.style.width = size + 'px'; trail.style.height = size + 'px'; document.body.appendChild(trail); trails.push({ element: trail, x: 0, y: 0 }); } let mouseX = 0, mouseY = 0; let cursorX = 0, cursorY = 0; let isMoving = false; let rafId = null; document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; if (!isMoving) { isMoving = true; rafId = requestAnimationFrame(animateCursor); } }, { passive: true }); function animateCursor() { cursorX += (mouseX - cursorX) * 0.15; cursorY += (mouseY - cursorY) * 0.15; cursor.style.transform = `translate3d(${cursorX}px, ${cursorY}px, 0) translate(-50%, -50%)`; trails.forEach((trail, index) => { const prev = trails[index - 1] || { x: cursorX, y: cursorY }; trail.x += (prev.x - trail.x) * 0.25; trail.y += (prev.y - trail.y) * 0.25; trail.element.style.transform = `translate3d(${trail.x}px, ${trail.y}px, 0)`; }); // Stop loop when cursor catches up if (Math.abs(mouseX - cursorX) > 0.5 || Math.abs(mouseY - cursorY) > 0.5) { rafId = requestAnimationFrame(animateCursor); } else { isMoving = false; } } // Delegate hover detection instead of per-element listeners document.addEventListener('mouseover', (e) => { if (e.target.closest('a, button, .btn, input, textarea, select, .service-card, .news-card, .portfolio-item, .nav-link')) { cursor.classList.add('active'); } }, { passive: true }); document.addEventListener('mouseout', (e) => { if (e.target.closest('a, button, .btn, input, textarea, select, .service-card, .news-card, .portfolio-item, .nav-link')) { cursor.classList.remove('active'); } }, { passive: true }); } /* ==================== Optimized Particle Background ==================== */ function initParticles() { // Particles disabled for performance — 20 animated DOM elements cause compositor overhead return; const particlesContainer = document.createElement('div'); particlesContainer.classList.add('particles-bg'); document.body.appendChild(particlesContainer); // Drastically reduced: 20 on desktop, 10 on mobile (was 60/25) const particleCount = window.innerWidth < 768 ? 10 : 20; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.classList.add('particle'); particle.style.left = Math.random() * 100 + '%'; particle.style.animationDelay = Math.random() * 20 + 's'; particle.style.animationDuration = (25 + Math.random() * 15) + 's'; const size = 2 + Math.random() * 2; particle.style.width = size + 'px'; particle.style.height = size + 'px'; particlesContainer.appendChild(particle); } // Geometric shapes removed for performance } /* ==================== Enhanced Scroll Progress Indicator ==================== */ function initScrollProgress() { const progressBar = document.createElement('div'); progressBar.classList.add('scroll-progress'); document.body.appendChild(progressBar); let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { const windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; const scrolled = (window.scrollY / windowHeight) * 100; progressBar.style.width = scrolled + '%'; ticking = false; }); ticking = true; } }); } /* ==================== Optimized Magnetic Buttons (disabled for performance) ==================== */ function initMagneticButtons() { // Magnetic effect disabled — per-element mousemove listeners are expensive return; // Only apply to .btn elements, not cards/nav-links (too many listeners) const magneticElements = document.querySelectorAll('.btn'); magneticElements.forEach(el => { el.addEventListener('mousemove', (e) => { const rect = el.getBoundingClientRect(); const x = (e.clientX - rect.left - rect.width / 2) * 0.25; const y = (e.clientY - rect.top - rect.height / 2) * 0.25; el.style.transform = `translate3d(${x}px, ${y}px, 0)`; }, { passive: true }); el.addEventListener('mouseleave', () => { el.style.transform = ''; }, { passive: true }); }); } /* ==================== Enhanced Smooth Scroll ==================== */ function initSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { const href = this.getAttribute('href'); if (href === '#') return; e.preventDefault(); const target = document.querySelector(href); if (target) { const headerOffset = 100; const elementPosition = target.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }); }); } /* ==================== Lightweight Parallax (hero only) ==================== */ function initParallax() { const heroVisual = document.querySelector('.hero-visual'); if (!heroVisual || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { const scrollY = window.scrollY; if (scrollY < window.innerHeight) { heroVisual.style.transform = `translate3d(0, ${scrollY * 0.15}px, 0)`; } ticking = false; }); ticking = true; } }, { passive: true }); } /* ==================== Optimized Reveal Animations ==================== */ function initRevealAnimations() { // Only observe sections, not every individual card (let CSS handle card animations) const revealElements = document.querySelectorAll('.section'); revealElements.forEach(el => el.classList.add('reveal')); const revealObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('active'); revealObserver.unobserve(entry.target); // Stop observing once revealed } }); }, { threshold: 0.1, rootMargin: '0px 0px -30px 0px' }); revealElements.forEach(el => revealObserver.observe(el)); } // Counter animation helper function animateValue(element) { const parent = element.closest('[data-count]'); if (!parent) return; const target = parseInt(parent.dataset.count) || 0; const duration = 2000; const start = performance.now(); function update(currentTime) { const elapsed = currentTime - start; const progress = Math.min(elapsed / duration, 1); // Easing function const easeOutQuart = 1 - Math.pow(1 - progress, 4); const current = Math.floor(easeOutQuart * target); element.textContent = current; if (progress < 1) { requestAnimationFrame(update); } else { element.textContent = target; } } requestAnimationFrame(update); } /* ==================== Lightweight 3D Tilt (disabled — duplicate of initCardPointerLift in main.js) ==================== */ function initTiltEffect() { // Disabled — main.js initCardPointerLift already handles card hover effects return; // Only service cards - removed from news, portfolio, feature, philosophy cards const cards = document.querySelectorAll('.service-card'); cards.forEach(card => { card.addEventListener('mousemove', (e) => { const rect = card.getBoundingClientRect(); const rotateX = ((e.clientY - rect.top) - rect.height / 2) / 20; const rotateY = (rect.width / 2 - (e.clientX - rect.left)) / 20; card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(5px)`; }, { passive: true }); card.addEventListener('mouseleave', () => { card.style.transform = ''; }, { passive: true }); }); } /* ==================== Ripple Effect (buttons only, no global click) ==================== */ function initRippleEffect() { // Use event delegation for button ripples document.addEventListener('click', function(e) { const btn = e.target.closest('.btn'); if (!btn) return; const rect = btn.getBoundingClientRect(); const ripple = document.createElement('span'); ripple.classList.add('btn-ripple'); ripple.style.left = (e.clientX - rect.left) + 'px'; ripple.style.top = (e.clientY - rect.top) + 'px'; btn.appendChild(ripple); setTimeout(() => ripple.remove(), 600); }); // Global click ripple removed for performance } /* ==================== Typewriter Effect ==================== */ function initTypewriter() { const typingElements = document.querySelectorAll('[data-typing]'); typingElements.forEach(el => { const text = el.dataset.typing || el.textContent; const speed = parseInt(el.dataset.typingSpeed) || 50; el.textContent = ''; el.style.borderRight = '2px solid var(--neon-cyan)'; let charIndex = 0; const typeInterval = setInterval(() => { if (charIndex < text.length) { el.textContent += text.charAt(charIndex); charIndex++; } else { clearInterval(typeInterval); // Blinking cursor effect el.style.animation = 'blinkCursor 0.8s infinite'; } }, speed); }); // Add blink cursor keyframes if (!document.querySelector('#blinkCursorKeyframes')) { const style = document.createElement('style'); style.id = 'blinkCursorKeyframes'; style.textContent = ` @keyframes blinkCursor { 0%, 50% { border-color: var(--neon-cyan); } 51%, 100% { border-color: transparent; } } `; document.head.appendChild(style); } } /* Dynamic Background Gradient - REMOVED for performance */ // The continuous rAF loop updating body background-position was a major perf drain /* ==================== Text Scramble Effect ==================== */ class TextScramble { constructor(el) { this.el = el; this.chars = '!<>-_\\/[]{}—=+*^?#________'; this.update = this.update.bind(this); } setText(newText) { const oldText = this.el.textContent; const length = Math.max(oldText.length, newText.length); const promise = new Promise(resolve => this.resolve = resolve); this.queue = []; for (let i = 0; i < length; i++) { const from = oldText[i] || ''; const to = newText[i] || ''; const start = Math.floor(Math.random() * 40); const end = start + Math.floor(Math.random() * 40); this.queue.push({ from, to, start, end }); } cancelAnimationFrame(this.frameRequest); this.frame = 0; this.update(); return promise; } update() { let output = ''; let complete = 0; for (let i = 0, n = this.queue.length; i < n; i++) { let { from, to, start, end, char } = this.queue[i]; if (this.frame >= end) { complete++; output += to; } else if (this.frame >= start) { if (!char || Math.random() < 0.28) { char = this.randomChar(); this.queue[i].char = char; } output += `${char}`; } else { output += from; } } this.el.innerHTML = output; if (complete === this.queue.length) { this.resolve(); } else { this.frameRequest = requestAnimationFrame(this.update); this.frame++; } } randomChar() { return this.chars[Math.floor(Math.random() * this.chars.length)]; } } // Initialize text scramble on hover for certain elements document.querySelectorAll('.logo-text').forEach(el => { const fx = new TextScramble(el); const originalText = el.textContent; el.addEventListener('mouseenter', () => { fx.setText(originalText); }); }); /* ==================== Lazy Load Images ==================== */ function initLazyLoad() { const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.add('loaded'); imageObserver.unobserve(img); } }); }, { rootMargin: '50px 0px' }); images.forEach(img => imageObserver.observe(img)); } /* ==================== Performance Optimization ==================== */ // Debounce function for expensive operations 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(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /* Loading Screen Enhancement - handled by preloader in main.js */ /* ==================== Console Easter Egg ==================== */ console.log('%c🚀 MSPE - Architects of Digital Resilience', 'color: #00f5ff; font-size: 24px; font-weight: bold; text-shadow: 0 0 10px #00f5ff;'); console.log('%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'color: #b537f2'); console.log('%c💼 Looking for opportunities? We\'re hiring!', 'color: #b537f2; font-size: 14px;'); console.log('%c🔧 Built with passion, precision, and cutting-edge tech', 'color: #ff006e; font-size: 12px;'); console.log('%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'color: #b537f2');