Spaces:
Running
Running
| // 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); | |
| }; | |
| } |