Spaces:
Running
Running
| // Smooth scroll 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' | |
| }); | |
| } | |
| }); | |
| }); | |
| // Contact form handler | |
| document.getElementById('contactForm')?.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| // Get form data | |
| const formData = new FormData(this); | |
| const data = Object.fromEntries(formData); | |
| // Show success message | |
| showNotification('Message sent successfully! We\'ll get back to you soon.', 'success'); | |
| // Reset form | |
| this.reset(); | |
| }); | |
| // Notification system | |
| function showNotification(message, type = 'info') { | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed top-4 right-4 z-50 px-6 py-4 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); | |
| // Animate in | |
| setTimeout(() => { | |
| notification.style.transform = 'translateX(0)'; | |
| }, 100); | |
| // Remove after 3 seconds | |
| setTimeout(() => { | |
| notification.style.transform = 'translateX(100%)'; | |
| setTimeout(() => { | |
| notification.remove(); | |
| }, 300); | |
| }, 3000); | |
| } | |
| // Intersection Observer for scroll animations | |
| 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'); | |
| observer.unobserve(entry.target); | |
| } | |
| }); | |
| }, observerOptions); | |
| // Observe all sections | |
| document.querySelectorAll('section').forEach(section => { | |
| observer.observe(section); | |
| }); | |
| // Parallax effect for hero section | |
| window.addEventListener('scroll', () => { | |
| const scrolled = window.pageYOffset; | |
| const parallax = document.querySelector('.gradient-animation'); | |
| if (parallax) { | |
| parallax.style.transform = `translateY(${scrolled * 0.5}px)`; | |
| } | |
| }); | |
| // Mobile menu toggle (if navbar is custom component) | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Initialize Feather icons | |
| feather.replace(); | |
| // Add loading animation | |
| document.body.classList.add('loaded'); | |
| // Counter animation for stats (if any) | |
| animateCounters(); | |
| }); | |
| // Counter animation function | |
| function animateCounters() { | |
| const counters = document.querySelectorAll('.counter'); | |
| const speed = 200; | |
| counters.forEach(counter => { | |
| const animate = () => { | |
| const value = +counter.getAttribute('data-target'); | |
| const data = +counter.innerText; | |
| const time = value / speed; | |
| if (data < value) { | |
| counter.innerText = Math.ceil(data + time); | |
| setTimeout(animate, 1); | |
| } else { | |
| counter.innerText = value; | |
| } | |
| } | |
| animate(); | |
| }); | |
| } | |
| // Dark mode toggle (if implemented) | |
| function toggleDarkMode() { | |
| document.documentElement.classList.toggle('dark'); | |
| localStorage.setItem('darkMode', document.documentElement.classList.contains('dark')); | |
| } | |
| // Check for saved dark mode preference | |
| if (localStorage.getItem('darkMode') === 'true') { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| // Lazy loading for images | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const lazyImages = 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); | |
| } | |
| }); | |
| }); | |
| lazyImages.forEach(img => imageObserver.observe(img)); | |
| }); | |
| // Typewriter effect for hero title | |
| function typeWriter(element, text, speed = 100) { | |
| let i = 0; | |
| element.textContent = ''; | |
| function type() { | |
| if (i < text.length) { | |
| element.textContent += text.charAt(i); | |
| i++; | |
| setTimeout(type, speed); | |
| } | |
| } | |
| type(); | |
| } | |
| // Initialize typewriter effect if needed | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const heroTitle = document.querySelector('.hero-title'); | |
| if (heroTitle) { | |
| typeWriter(heroTitle, heroTitle.textContent, 50); | |
| } | |
| }); |