| | |
| | document.addEventListener('DOMContentLoaded', function() { |
| | |
| | initAnimations(); |
| | |
| | |
| | initForms(); |
| | |
| | |
| | initScrollEffects(); |
| | |
| | |
| | if ('serviceWorker' in navigator) { |
| | navigator.serviceWorker.register('/sw.js') |
| | .then(() => console.log('Service Worker Registered')) |
| | .catch(err => console.log('Service Worker Registration Failed: ', err)); |
| | } |
| | }); |
| |
|
| | function initAnimations() { |
| | |
| | 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-up'); |
| | observer.unobserve(entry.target); |
| | } |
| | }); |
| | }, observerOptions); |
| |
|
| | |
| | document.querySelectorAll('.animate-on-scroll').forEach(el => { |
| | observer.observe(el); |
| | }); |
| | } |
| |
|
| | function initForms() { |
| | |
| | const contactForm = document.getElementById('contactForm'); |
| | if (contactForm) { |
| | contactForm.addEventListener('submit', function(e) { |
| | e.preventDefault(); |
| | const submitBtn = this.querySelector('button[type="submit"]'); |
| | const originalText = submitBtn.textContent; |
| | |
| | |
| | submitBtn.disabled = true; |
| | submitBtn.innerHTML = '<div class="loading-spinner"></div> Processing...'; |
| | |
| | |
| | setTimeout(() => { |
| | alert('Thank you for your message! I\'ll get back to you soon.'); |
| | contactForm.reset(); |
| | submitBtn.disabled = false; |
| | submitBtn.textContent = originalText; |
| | }, 2000); |
| | }); |
| | } |
| | } |
| |
|
| | function initScrollEffects() { |
| | |
| | let lastScrollY = window.scrollY; |
| | const navbar = document.querySelector('custom-navbar'); |
| | |
| | window.addEventListener('scroll', () => { |
| | if (!navbar) return; |
| | |
| | const currentScrollY = window.scrollY; |
| | |
| | if (currentScrollY > lastScrollY && currentScrollY > 100) { |
| | navbar.style.transform = 'translateY(-100%)'; |
| | } else { |
| | navbar.style.transform = 'translateY(0)'; |
| | } |
| | |
| | lastScrollY = currentScrollY; |
| | |
| | |
| | if (currentScrollY > 50) { |
| | navbar.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.1)'; |
| | } else { |
| | navbar.style.boxShadow = 'none'; |
| | } |
| | }); |
| | } |
| |
|
| | |
| | function smoothScrollTo(target) { |
| | const element = document.querySelector(target); |
| | if (element) { |
| | element.scrollIntoView({ |
| | behavior: 'smooth', |
| | block: 'start' |
| | }); |
| | } |
| | } |
| |
|
| | |
| | function debounce(func, wait) { |
| | let timeout; |
| | return function executedFunction(...args) { |
| | const later = () => { |
| | clearTimeout(timeout); |
| | func(...args); |
| | }; |
| | clearTimeout(timeout); |
| | timeout = setTimeout(later, wait); |
| | }; |
| | } |
| |
|
| | |
| | function formatPhoneNumber(phone) { |
| | const cleaned = ('' + phone).replace(/\D/g, ''); |
| | const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/); |
| | if (match) { |
| | return '(' + match[1] + ') ' + match[2] + '-' + match[3]; |
| | } |
| | return phone; |
| | } |
| |
|
| | |
| | function isValidEmail(email) { |
| | const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| | return re.test(email); |
| | } |