|
|
|
|
|
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); |
|
|
} |