|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const reveals = document.querySelectorAll('.reveal'); |
|
|
|
|
|
function checkReveal() { |
|
|
reveals.forEach(element => { |
|
|
const windowHeight = window.innerHeight; |
|
|
const elementTop = element.getBoundingClientRect().top; |
|
|
const elementVisible = 150; |
|
|
|
|
|
if (elementTop < windowHeight - elementVisible) { |
|
|
element.classList.add('active'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
window.addEventListener('scroll', checkReveal); |
|
|
checkReveal(); |
|
|
|
|
|
|
|
|
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' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const parallaxElements = document.querySelectorAll('.parallax'); |
|
|
|
|
|
window.addEventListener('scroll', () => { |
|
|
const scrolled = window.pageYOffset; |
|
|
|
|
|
parallaxElements.forEach(element => { |
|
|
const speed = element.dataset.speed || 0.5; |
|
|
element.style.transform = `translateY(${scrolled * speed}px)`; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const counters = document.querySelectorAll('[data-counter]'); |
|
|
const speed = 200; |
|
|
|
|
|
const countUp = () => { |
|
|
counters.forEach(counter => { |
|
|
const updateCount = () => { |
|
|
const target = +counter.getAttribute('data-counter'); |
|
|
const count = +counter.innerText; |
|
|
const inc = target / speed; |
|
|
|
|
|
if (count < target) { |
|
|
counter.innerText = Math.ceil(count + inc); |
|
|
setTimeout(updateCount, 1); |
|
|
} else { |
|
|
counter.innerText = target; |
|
|
} |
|
|
}; |
|
|
|
|
|
updateCount(); |
|
|
}); |
|
|
}; |
|
|
|
|
|
|
|
|
const observerOptions = { |
|
|
threshold: 0.5 |
|
|
}; |
|
|
|
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
countUp(); |
|
|
observer.unobserve(entry.target); |
|
|
} |
|
|
}); |
|
|
}, observerOptions); |
|
|
|
|
|
counters.forEach(counter => { |
|
|
observer.observe(counter); |
|
|
}); |
|
|
|
|
|
|
|
|
const cards = document.querySelectorAll('.card-hover'); |
|
|
|
|
|
cards.forEach(card => { |
|
|
card.addEventListener('mousemove', (e) => { |
|
|
const rect = card.getBoundingClientRect(); |
|
|
const x = e.clientX - rect.left; |
|
|
const y = e.clientY - rect.top; |
|
|
|
|
|
const centerX = rect.width / 2; |
|
|
const centerY = rect.height / 2; |
|
|
|
|
|
const rotateX = (y - centerY) / 10; |
|
|
const rotateY = (centerX - x) / 10; |
|
|
|
|
|
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.05, 1.05, 1.05)`; |
|
|
}); |
|
|
|
|
|
card.addEventListener('mouseleave', () => { |
|
|
card.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)'; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const forms = document.querySelectorAll('form'); |
|
|
|
|
|
forms.forEach(form => { |
|
|
form.addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
|
|
|
const formData = new FormData(form); |
|
|
const data = Object.fromEntries(formData); |
|
|
|
|
|
|
|
|
let isValid = true; |
|
|
const requiredFields = form.querySelectorAll('[required]'); |
|
|
|
|
|
requiredFields.forEach(field => { |
|
|
if (!field.value.trim()) { |
|
|
field.classList.add('border-red-500'); |
|
|
isValid = false; |
|
|
} else { |
|
|
field.classList.remove('border-red-500'); |
|
|
} |
|
|
}); |
|
|
|
|
|
if (isValid) { |
|
|
|
|
|
const successMessage = document.createElement('div'); |
|
|
successMessage.className = 'fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50'; |
|
|
successMessage.textContent = 'Form submitted successfully!'; |
|
|
document.body.appendChild(successMessage); |
|
|
|
|
|
setTimeout(() => { |
|
|
successMessage.remove(); |
|
|
}, 3000); |
|
|
|
|
|
form.reset(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const yearElements = document.querySelectorAll('[data-year]'); |
|
|
const currentYear = new Date().getFullYear(); |
|
|
|
|
|
yearElements.forEach(element => { |
|
|
element.textContent = currentYear; |
|
|
}); |
|
|
|
|
|
|
|
|
const themeToggle = document.querySelector('[data-theme-toggle]'); |
|
|
|
|
|
if (themeToggle) { |
|
|
themeToggle.addEventListener('click', () => { |
|
|
document.body.classList.toggle('dark'); |
|
|
const isDark = document.body.classList.contains('dark'); |
|
|
localStorage.setItem('theme', isDark ? 'dark' : 'light'); |
|
|
}); |
|
|
|
|
|
|
|
|
const savedTheme = localStorage.getItem('theme'); |
|
|
if (savedTheme === 'dark') { |
|
|
document.body.classList.add('dark'); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const utils = { |
|
|
debounce: (func, wait) => { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
}, |
|
|
|
|
|
throttle: (func, limit) => { |
|
|
let inThrottle; |
|
|
return function(...args) { |
|
|
if (!inThrottle) { |
|
|
func.apply(this, args); |
|
|
inThrottle = true; |
|
|
setTimeout(() => inThrottle = false, limit); |
|
|
} |
|
|
}; |
|
|
}, |
|
|
|
|
|
animateValue: (element, start, end, duration) => { |
|
|
let startTimestamp = null; |
|
|
const step = (timestamp) => { |
|
|
if (!startTimestamp) startTimestamp = timestamp; |
|
|
const progress = Math.min((timestamp - startTimestamp) / duration, 1); |
|
|
element.textContent = Math.floor(progress * (end - start) + start); |
|
|
if (progress < 1) { |
|
|
window.requestAnimationFrame(step); |
|
|
} |
|
|
}; |
|
|
window.requestAnimationFrame(step); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
window.utils = utils; |