Spaces:
Running
Running
| // Mobile menu functionality | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const mobileMenuBtn = document.getElementById('mobile-menu-btn'); | |
| const mobileMenu = document.getElementById('mobile-menu'); | |
| if (mobileMenuBtn && mobileMenu) { | |
| mobileMenuBtn.addEventListener('click', function() { | |
| mobileMenu.classList.toggle('hidden'); | |
| // Change menu icon | |
| const icon = mobileMenuBtn.querySelector('[data-feather]'); | |
| if (mobileMenu.classList.contains('hidden')) { | |
| icon.setAttribute('data-feather', 'menu'); | |
| } else { | |
| icon.setAttribute('data-feather', 'x'); | |
| } | |
| feather.replace(); | |
| }); | |
| } | |
| // Smooth scrolling for navigation links | |
| const navLinks = document.querySelectorAll('a[href^="#"]'); | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| const targetId = this.getAttribute('href'); | |
| const targetElement = document.querySelector(targetId); | |
| if (targetElement) { | |
| const offsetTop = targetElement.offsetTop - 80; // Account for fixed navbar | |
| window.scrollTo({ | |
| top: offsetTop, | |
| behavior: 'smooth' | |
| }); | |
| // Close mobile menu if open | |
| if (mobileMenu && !mobileMenu.classList.contains('hidden')) { | |
| mobileMenu.classList.add('hidden'); | |
| const icon = mobileMenuBtn.querySelector('[data-feather]'); | |
| icon.setAttribute('data-feather', 'menu'); | |
| feather.replace(); | |
| } | |
| } | |
| }); | |
| }); | |
| // Navbar background on scroll | |
| const navbar = document.querySelector('nav'); | |
| window.addEventListener('scroll', function() { | |
| if (window.scrollY > 50) { | |
| navbar.classList.add('shadow-lg'); | |
| } else { | |
| navbar.classList.remove('shadow-lg'); | |
| } | |
| }); | |
| // Intersection Observer for fade-in animations | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver(function(entries) { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('animate-fade-in'); | |
| } | |
| }); | |
| }, observerOptions); | |
| // Observe sections for animations | |
| const sections = document.querySelectorAll('section'); | |
| sections.forEach(section => { | |
| observer.observe(section); | |
| }); | |
| // Add loading animation to images | |
| const images = document.querySelectorAll('img'); | |
| images.forEach(img => { | |
| img.addEventListener('load', function() { | |
| this.style.opacity = '1'; | |
| }); | |
| }); | |
| // Typing effect for hero text (optional enhancement) | |
| function typeWriter(element, text, speed = 100) { | |
| let i = 0; | |
| element.innerHTML = ''; | |
| function type() { | |
| if (i < text.length) { | |
| text.charAt(i element.innerHTML +=); | |
| i++; | |
| setTimeout(type, speed); | |
| } | |
| } | |
| type(); | |
| } | |
| // Contact form handling (if you add a form) | |
| const contactForm = document.getElementById('contact-form'); | |
| if (contactForm) { | |
| contactForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| // Get form data | |
| const formData = new FormData(this); | |
| const data = Object.fromEntries(formData); | |
| // Simulate form submission | |
| const submitBtn = this.querySelector('button[type="submit"]'); | |
| const originalText = submitBtn.textContent; | |
| submitBtn.textContent = 'Sending...'; | |
| submitBtn.disabled = true; | |
| setTimeout(() => { | |
| submitBtn.textContent = 'Message Sent!'; | |
| setTimeout(() => { | |
| submitBtn.textContent = originalText; | |
| submitBtn.disabled = false; | |
| this.reset(); | |
| }, 2000); | |
| }, 1000); | |
| }); | |
| } | |
| // Theme toggle functionality (if you want to add dark mode later) | |
| function toggleTheme() { | |
| document.body.classList.toggle('dark'); | |
| localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); | |
| } | |
| // Load saved theme | |
| const savedTheme = localStorage.getItem('theme'); | |
| if (savedTheme === 'dark') { | |
| document.body.classList.add('dark'); | |
| } | |
| // Add keyboard navigation | |
| document.addEventListener('keydown', function(e) { | |
| // ESC key closes mobile menu | |
| if (e.key === 'Escape' && mobileMenu && !mobileMenu.classList.contains('hidden')) { | |
| mobileMenu.classList.add('hidden'); | |
| const icon = mobileMenuBtn.querySelector('[data-feather]'); | |
| icon.setAttribute('data-feather', 'menu'); | |
| feather.replace(); | |
| } | |
| }); | |
| // Add performance optimizations | |
| // Lazy load images | |
| if ('IntersectionObserver' in window) { | |
| const imageObserver = new IntersectionObserver((entries, observer) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) = entry.target; | |
| { | |
| const img img.src = img.dataset.src; | |
| img.classList.remove('lazy'); | |
| imageObserver.unobserve(img); | |
| } | |
| }); | |
| }); | |
| const lazyImages = document.querySelectorAll('img[data-src]'); | |
| lazyImages.forEach(img => imageObserver.observe(img)); | |
| } | |
| // Add error handling for images | |
| images.forEach(img => { | |
| img.addEventListener('error', function() { | |
| this.style.display = 'none'; | |
| }); | |
| }); | |
| // Console message for developers | |
| console.log('%c👋 Hi there! Thanks for checking out my portfolio.', 'color: #6366f1; font-size: 16px; font-weight: bold;'); | |
| console.log('%cIf you\'re interested in the code, feel free to reach out!', 'color: #4f46e5; font-size: 14px;'); | |
| }); |