| | |
| |
|
| | document.addEventListener('DOMContentLoaded', function() { |
| | |
| | initMobileMenu(); |
| | initSmoothScrolling(); |
| | initScrollAnimations(); |
| | initFormHandling(); |
| | initScrollToTop(); |
| | initTypingEffect(); |
| | }); |
| |
|
| | |
| | function initMobileMenu() { |
| | const mobileMenuButton = document.getElementById('mobile-menu-button'); |
| | const mobileMenu = document.getElementById('mobile-menu'); |
| | |
| | if (mobileMenuButton && mobileMenu) { |
| | mobileMenuButton.addEventListener('click', function() { |
| | mobileMenu.classList.toggle('hidden'); |
| | |
| | |
| | const icon = mobileMenuButton.querySelector('i'); |
| | if (mobileMenu.classList.contains('hidden')) { |
| | icon.setAttribute('data-feather', 'menu'); |
| | } else { |
| | icon.setAttribute('data-feather', 'x'); |
| | } |
| | feather.replace(); |
| | }); |
| | |
| | |
| | const mobileLinks = mobileMenu.querySelectorAll('a'); |
| | mobileLinks.forEach(link => { |
| | link.addEventListener('click', function() { |
| | mobileMenu.classList.add('hidden'); |
| | const icon = mobileMenuButton.querySelector('i'); |
| | icon.setAttribute('data-feather', 'menu'); |
| | feather.replace(); |
| | }); |
| | }); |
| | } |
| | } |
| |
|
| | |
| | function initSmoothScrolling() { |
| | const navLinks = document.querySelectorAll('a[href^="#"]'); |
| | |
| | navLinks.forEach(link => { |
| | link.addEventListener('click', function(e) { |
| | e.preventDefault(); |
| | |
| | const targetId = this.getAttribute('href'); |
| | const targetSection = document.querySelector(targetId); |
| | |
| | if (targetSection) { |
| | const navHeight = document.querySelector('nav').offsetHeight; |
| | const targetPosition = targetSection.offsetTop - navHeight; |
| | |
| | window.scrollTo({ |
| | top: targetPosition, |
| | behavior: 'smooth' |
| | }); |
| | } |
| | }); |
| | }); |
| | } |
| |
|
| | |
| | function initScrollAnimations() { |
| | 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-up'); |
| | entry.target.style.opacity = '1'; |
| | } |
| | }); |
| | }, observerOptions); |
| | |
| | |
| | const animateElements = document.querySelectorAll('.card-hover, .bg-white, section > div'); |
| | animateElements.forEach(el => { |
| | el.style.opacity = '0'; |
| | observer.observe(el); |
| | }); |
| | |
| | |
| | window.addEventListener('scroll', highlightActiveNav); |
| | } |
| |
|
| | function highlightActiveNav() { |
| | const sections = document.querySelectorAll('section[id]'); |
| | const navLinks = document.querySelectorAll('nav a[href^="#"]'); |
| | const navHeight = document.querySelector('nav').offsetHeight; |
| | |
| | let current = ''; |
| | |
| | sections.forEach(section => { |
| | const sectionTop = section.offsetTop - navHeight - 100; |
| | const sectionHeight = section.offsetHeight; |
| | |
| | if (window.pageYOffset >= sectionTop && window.pageYOffset < sectionTop + sectionHeight) { |
| | current = section.getAttribute('id'); |
| | } |
| | }); |
| | |
| | navLinks.forEach(link => { |
| | link.classList.remove('text-primary-600', 'font-semibold'); |
| | link.classList.add('text-secondary-600'); |
| | |
| | if (link.getAttribute('href') === '#' + current) { |
| | link.classList.remove('text-secondary-600'); |
| | link.classList.add('text-primary-600', 'font-semibold'); |
| | } |
| | }); |
| | } |
| |
|
| | |
| | function initFormHandling() { |
| | const contactForm = document.querySelector('form'); |
| | |
| | if (contactForm) { |
| | contactForm.addEventListener('submit', function(e) { |
| | e.preventDefault(); |
| | |
| | |
| | const formData = new FormData(this); |
| | const formObject = {}; |
| | formData.forEach((value, key) => { |
| | formObject[key] = value; |
| | }); |
| | |
| | |
| | const submitButton = this.querySelector('button[type="submit"]'); |
| | const originalText = submitButton.textContent; |
| | submitButton.textContent = 'Sending...'; |
| | submitButton.disabled = true; |
| | |
| | |
| | setTimeout(() => { |
| | |
| | showNotification('Message sent successfully!', 'success'); |
| | |
| | |
| | this.reset(); |
| | |
| | |
| | submitButton.textContent = originalText; |
| | submitButton.disabled = false; |
| | }, 2000); |
| | }); |
| | } |
| | } |
| |
|
| | |
| | function showNotification(message, type = 'info') { |
| | const notification = document.createElement('div'); |
| | notification.className = `fixed top-20 right-4 z-50 px-6 py-3 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 ${ |
| | type === 'success' ? 'bg-green-500 text-white' : |
| | type === 'error' ? 'bg-red-500 text-white' : |
| | 'bg-blue-500 text-white' |
| | }`; |
| | notification.textContent = message; |
| | |
| | document.body.appendChild(notification); |
| | |
| | |
| | setTimeout(() => { |
| | notification.classList.remove('translate-x-full'); |
| | }, 100); |
| | |
| | |
| | setTimeout(() => { |
| | notification.classList.add('translate-x-full'); |
| | setTimeout(() => { |
| | document.body.removeChild(notification); |
| | }, 300); |
| | }, 4000); |
| | } |
| |
|
| | |
| | function initScrollToTop() { |
| | |
| | const scrollButton = document.createElement('button'); |
| | scrollButton.innerHTML = '<i data-feather="arrow-up"></i>'; |
| | scrollButton.className = 'fixed bottom-6 right-6 w-12 h-12 bg-primary-600 text-white rounded-full shadow-lg hover:bg-primary-700 transition-colors duration-300 opacity-0 transform translate-y-full z-50'; |
| | scrollButton.style.display = 'none'; |
| | document.body.appendChild(scrollButton); |
| | feather.replace(); |
| | |
| | |
| | window.addEventListener('scroll', function() { |
| | if (window.pageYOffset > 300) { |
| | scrollButton.style.display = 'block'; |
| | setTimeout(() => { |
| | scrollButton.classList.remove('opacity-0', 'translate-y-full'); |
| | }, 10); |
| | } else { |
| | scrollButton.classList.add('opacity-0', 'translate-y-full'); |
| | setTimeout(() => { |
| | scrollButton.style.display = 'none'; |
| | }, 300); |
| | } |
| | }); |
| | |
| | |
| | scrollButton.addEventListener('click', function() { |
| | window.scrollTo({ |
| | top: 0, |
| | behavior: 'smooth' |
| | }); |
| | }); |
| | } |
| |
|
| | |
| | function initTypingEffect() { |
| | const heroTitle = document.querySelector('#home h1'); |
| | if (heroTitle) { |
| | const text = heroTitle.textContent; |
| | heroTitle.textContent = ''; |
| | heroTitle.style.borderRight = '2px solid white'; |
| | |
| | let i = 0; |
| | const typeWriter = () => { |
| | if (i < text.length) { |
| | heroTitle.textContent += text.charAt(i); |
| | i++; |
| | setTimeout(typeWriter, 100); |
| | } else { |
| | |
| | setTimeout(() => { |
| | heroTitle.style.borderRight = 'none'; |
| | }, 1000); |
| | } |
| | }; |
| | |
| | |
| | setTimeout(typeWriter, 1000); |
| | } |
| | } |
| |
|
| | |
| | window.addEventListener('scroll', function() { |
| | const scrolled = window.pageYOffset; |
| | const heroSection = document.querySelector('#home'); |
| | |
| | if (heroSection) { |
| | const rate = scrolled * -0.5; |
| | heroSection.style.transform = `translateY(${rate}px)`; |
| | } |
| | }); |
| |
|
| | |
| | function animateCounters() { |
| | const counters = document.querySelectorAll('[data-count]'); |
| | |
| | counters.forEach(counter => { |
| | const target = parseInt(counter.getAttribute('data-count')); |
| | const increment = target / 100; |
| | let current = 0; |
| | |
| | const timer = setInterval(() => { |
| | current += increment; |
| | counter.textContent = Math.floor(current); |
| | |
| | if (current >= target) { |
| | counter.textContent = target; |
| | clearInterval(timer); |
| | } |
| | }, 20); |
| | }); |
| | } |
| |
|
| | |
| | const aboutSection = document.querySelector('#about'); |
| | if (aboutSection) { |
| | const observer = new IntersectionObserver(function(entries) { |
| | entries.forEach(entry => { |
| | if (entry.isIntersecting) { |
| | animateCounters(); |
| | observer.unobserve(entry.target); |
| | } |
| | }); |
| | }, { threshold: 0.5 }); |
| | |
| | observer.observe(aboutSection); |
| | } |
| |
|
| | |
| | function initPortfolioFilter() { |
| | const filterButtons = document.querySelectorAll('[data-filter]'); |
| | const portfolioItems = document.querySelectorAll('[data-category]'); |
| | |
| | filterButtons.forEach(button => { |
| | button.addEventListener('click', function() { |
| | const filter = this.getAttribute('data-filter'); |
| | |
| | |
| | filterButtons.forEach(btn => btn.classList.remove('active')); |
| | this.classList.add('active'); |
| | |
| | |
| | portfolioItems.forEach(item => { |
| | if (filter === 'all' || item.getAttribute('data-category') === filter) { |
| | item.style.display = 'block'; |
| | } else { |
| | item.style.display = 'none'; |
| | } |
| | }); |
| | }); |
| | }); |
| | } |
| |
|
| | |
| | function initThemeSwitcher() { |
| | const themeToggle = document.getElementById('theme-toggle'); |
| | |
| | if (themeToggle) { |
| | themeToggle.addEventListener('click', function() { |
| | document.body.classList.toggle('dark-theme'); |
| | |
| | |
| | const isDark = document.body.classList.contains('dark-theme'); |
| | localStorage.setItem('theme', isDark ? 'dark' : 'light'); |
| | |
| | |
| | const icon = this.querySelector('i'); |
| | if (isDark) { |
| | icon.setAttribute('data-feather', 'sun'); |
| | } else { |
| | icon.setAttribute('data-feather', 'moon'); |
| | } |
| | feather.replace(); |
| | }); |
| | |
| | |
| | const savedTheme = localStorage.getItem('theme'); |
| | if (savedTheme === 'dark') { |
| | document.body.classList.add('dark-theme'); |
| | } |
| | } |
| | } |
| |
|
| | |
| | function optimizePerformance() { |
| | |
| | const images = document.querySelectorAll('img[data-src]'); |
| | const imageObserver = new IntersectionObserver((entries, observer) => { |
| | entries.forEach(entry => { |
| | if (entry.isIntersecting) { |
| | const img = entry.target; |
| | img.src = img.dataset.src; |
| | img.classList.remove('lazy'); |
| | imageObserver.unobserve(img); |
| | } |
| | }); |
| | }); |
| | |
| | images.forEach(img => imageObserver.observe(img)); |
| | |
| | |
| | function debounce(func, wait) { |
| | let timeout; |
| | return function executedFunction(...args) { |
| | const later = () => { |
| | clearTimeout(timeout); |
| | func(...args); |
| | }; |
| | clearTimeout(timeout); |
| | timeout = setTimeout(later, wait); |
| | }; |
| | } |
| | |
| | |
| | const debouncedScroll = debounce(() => { |
| | |
| | }, 10); |
| | |
| | window.addEventListener('scroll', debouncedScroll); |
| | } |
| |
|
| | |
| | optimizePerformance(); |