// Main JavaScript for CodeCanvas Noir Portfolio class PortfolioApp { constructor() { this.init(); } init() { this.setupScrollAnimations(); this.setupIntersectionObserver(); this.setupSmoothScrolling(); this.setupProjectCards(); } setupScrollAnimations() { // Add scroll-triggered animations window.addEventListener('scroll', this.handleScroll.bind(this)); } setupIntersectionObserver() { 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('animate-fade-in'); } }); }, observerOptions); // Observe all sections for animation document.querySelectorAll('section').forEach(section => { observer.observe(section); }); } setupSmoothScrolling() { // Smooth scrolling for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } setupProjectCards() { // Add tilt effect to project cards const projectCards = document.querySelectorAll('.project-card'); projectCards.forEach(card => { card.addEventListener('mousemove', this.handleTilt.bind(this)); card.addEventListener('mouseleave', this.resetTilt.bind(this)); }); } handleTilt(e) { const card = e.currentTarget; const cardRect = card.getBoundingClientRect(); const cardCenterX = cardRect.left + cardRect.width / 2; const cardCenterY = cardRect.top + cardRect.height / 2; const mouseX = e.clientX - cardCenterX; const mouseY = e.clientY - cardCenterY; const rotateX = (mouseY / cardRect.height) * 10; const rotateY = -(mouseX / cardRect.width) * 10; card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02); } resetTilt(e) { const card = e.currentTarget; card.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)'; } handleScroll() { // Update navigation active state based on scroll position this.updateNavigation(); } updateNavigation() { const sections = document.querySelectorAll('section'); const navLinks = document.querySelectorAll('nav a'); let currentSection = ''; sections.forEach(section => { const sectionTop = section.offsetTop; const sectionHeight = section.clientHeight; if (window.scrollY >= sectionTop - 100 && window.scrollY < sectionTop + sectionHeight - 100) { currentSection = section.getAttribute('id'); } }); navLinks.forEach(link => { link.classList.remove('nav-active'); if (link.getAttribute('href') === `#${currentSection}`) { link.classList.add('nav-active'); } }); } // Utility method for debouncing debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } } // Initialize the portfolio app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new PortfolioApp(); }); // Handle page visibility changes for performance document.addEventListener('visibilitychange', () => { if (document.hidden) { // Page is hidden, pause heavy animations if any } else { // Page is visible, resume animations } }); // Handle resize events with debouncing window.addEventListener('resize', debounce(() => { // Handle responsive adjustments }, 250)); // Helper function for debouncing function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }