codecanvas-noir / script.js
fokan's picture
You are a professional UI/UX designer specialized in creating sleek, dark-mode portfolio landing pages for developers.
9b00824 verified
// 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);
};
}