/**
* LeafBox Technologies - Main JavaScript
* Handles core functionality, theme toggle, navigation, and form interactions
*/
// DOM Content Loaded
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
});
/**
* Initialize the application
*/
function initializeApp() {
initThemeToggle();
initNavigation();
initScrollEffects();
initFormHandling();
initFestiveCursor();
initSectionReveal();
initHeaderScroll();
}
/**
* Theme Toggle Functionality
*/
function initThemeToggle() {
const themeToggle = document.getElementById('themeToggle');
const body = document.body;
// Get saved theme or default to light
const savedTheme = localStorage.getItem('theme') || 'light';
setTheme(savedTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = body.getAttribute('data-theme') || 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
});
function setTheme(theme) {
body.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
// Update icons
const lightIcon = themeToggle.querySelector('.theme-icon:not(.hidden)');
const darkIcon = themeToggle.querySelector('.theme-icon.hidden');
if (theme === 'dark') {
lightIcon?.classList.add('hidden');
darkIcon?.classList.remove('hidden');
} else {
lightIcon?.classList.remove('hidden');
darkIcon?.classList.add('hidden');
}
}
}
/**
* Navigation Functionality
*/
function initNavigation() {
const navLinks = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('section[id]');
// Smooth scrolling for navigation links
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href');
const targetSection = document.querySelector(targetId);
if (targetSection) {
const headerHeight = document.querySelector('.header').offsetHeight;
const targetPosition = targetSection.offsetTop - headerHeight;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
}
});
});
// Update active navigation on scroll
window.addEventListener('scroll', () => {
let current = '';
const scrollPos = window.scrollY + 100;
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
if (scrollPos >= sectionTop && scrollPos < sectionTop + sectionHeight) {
current = section.getAttribute('id');
}
});
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === `#${current}`) {
link.classList.add('active');
}
});
});
}
/**
* Scroll Effects
*/
function initScrollEffects() {
// Add scroll-based animations
const animateOnScroll = () => {
const elements = document.querySelectorAll('.service-card, .stat-item');
elements.forEach(element => {
const elementTop = element.getBoundingClientRect().top;
const elementVisible = 150;
if (elementTop < window.innerHeight - elementVisible) {
element.style.opacity = '1';
element.style.transform = 'translateY(0)';
}
});
};
// Initialize elements with hidden state
const elementsToAnimate = document.querySelectorAll('.service-card, .stat-item');
elementsToAnimate.forEach(element => {
element.style.opacity = '0';
element.style.transform = 'translateY(30px)';
element.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
});
window.addEventListener('scroll', animateOnScroll);
animateOnScroll(); // Run once on load
}
/**
* Form Handling
*/
function initFormHandling() {
const contactForm = document.getElementById('contactForm');
contactForm.addEventListener('submit', handleFormSubmit);
function handleFormSubmit(e) {
e.preventDefault();
const formData = new FormData(contactForm);
const submitBtn = contactForm.querySelector('.submit-btn');
const originalText = submitBtn.innerHTML;
// Show loading state
submitBtn.innerHTML = '<div class="loading"></div> Sending...';
submitBtn.disabled = true;
// Simulate form submission (replace with actual API call)
setTimeout(() => {
// Success state
submitBtn.innerHTML = '✓ Message Sent!';
submitBtn.style.background = '#059669';
// Show success message
showSuccessMessage();
// Reset form
contactForm.reset();
// Reset button after delay
setTimeout(() => {
submitBtn.innerHTML = originalText;
submitBtn.style.background = '';
submitBtn.disabled = false;
}, 3000);
}, 2000);
}
function showSuccessMessage() {
const successDiv = document.createElement('div');
successDiv.className = 'success-message';
successDiv.innerHTML = `
<strong>Thank you!</strong> Your holiday greetings have been sent.
We'll be in touch soon!
`;
contactForm.parentNode.insertBefore(successDiv, contactForm);
setTimeout(() => {
successDiv.remove();
}, 5000);
}
// Real-time form validation
const inputs = contactForm.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('blur', validateField);
input.addEventListener('input', clearErrors);
});
function validateField(e) {
const field = e.target;
const value = field.value.trim();
clearFieldError(field);
if (field.hasAttribute('required') && !value) {
showFieldError(field, 'This field is required');
return false;
}
if (field.type === 'email' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
showFieldError(field, 'Please enter a valid email address');
return false;
}
}
return true;
}
function showFieldError(field, message) {
field.style.borderColor = '#C36A6A';
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.style.color = '#C36A6A';
errorDiv.style.fontSize = '14px';
errorDiv.style.marginTop = '4px';
errorDiv.textContent = message;
field.parentNode.appendChild(errorDiv);
}
function clearFieldError(field) {
field.style.borderColor = '';
const errorDiv = field.parentNode.querySelector('.field-error');
if (errorDiv) {
errorDiv.remove();
}
}
function clearErrors(e) {
clearFieldError(e.target);
}
}
/**
* Festive Cursor Effect (Desktop Only)
*/
function initFestiveCursor() {
// Only enable on desktop to avoid interfering with touch
if (window.innerWidth < 768 || !window.matchMedia('(pointer:fine)').matches) {
return;
}
const cursor = document.createElement('div');
cursor.className = 'festive-cursor';
document.body.appendChild(cursor);
let mouseX = 0;
let mouseY = 0;
let cursorX = 0;
let cursorY = 0;
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
function animateCursor() {
const deltaX = mouseX - cursorX;
const deltaY = mouseY - cursorY;
cursorX += deltaX * 0.1;
cursorY += deltaY * 0.1;
cursor.style.left = cursorX - 10 + 'px';
cursor.style.top = cursorY - 10 + 'px';
requestAnimationFrame(animateCursor);
}
animateCursor();
}
/**
* Section Reveal Animation
*/
function initSectionReveal() {
const sections = document.querySelectorAll('.about, .services, .contact');
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('revealed');
}
});
}, observerOptions);
sections.forEach(section => {
section.classList.add('section-reveal');
observer.observe(section);
});
}
/**
* Header Scroll Effect
*/
function initHeaderScroll() {
const header = document.querySelector('.header');
let lastScrollY = window.scrollY;
window.addEventListener('scroll', () => {
const currentScrollY = window.scrollY;
if (currentScrollY > 100) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
// Hide/show header on scroll
if (currentScrollY > lastScrollY && currentScrollY > 200) {
header.style.transform = 'translateY(-100%)';
} else {
header.style.transform = 'translateY(0)';
}
lastScrollY = currentScrollY;
});
}
/**
* Utility Functions
*/
// Debounce function for performance
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttle function for scroll events
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// Get element offset
function getOffset(element) {
const rect = element.getBoundingClientRect();
return {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX
};
}
// Check if element is in viewport
function isInViewport(element, threshold = 0) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
return (
rect.top >= -threshold &&
rect.left >= -threshold &&
rect.bottom <= windowHeight + threshold &&
rect.right <= windowWidth + threshold
);
}
// Performance optimized scroll listener
const optimizedScrollHandler = throttle(() => {
// Add any scroll-based functionality here
}, 16); // ~60fps
window.addEventListener('scroll', optimizedScrollHandler);
// Handle window resize
const optimizedResizeHandler = debounce(() => {
// Handle resize logic
if (window.innerWidth >= 768) {
// Enable desktop features
} else {
// Enable mobile optimizations
}
}, 250);
window.addEventListener('resize', optimizedResizeHandler);
// Page visibility API for performance
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// Pause animations when page is not visible
document.body.style.animationPlayState = 'paused';
} else {
// Resume animations when page becomes visible
document.body.style.animationPlayState = 'running';
}
});
// Error handling
window.addEventListener('error', (e) => {
console.error('JavaScript Error:', e.error);
// You could send error reports to analytics here
});
// Handle unhandled promise rejections
window.addEventListener('unhandledrejection', (e) => {
console.error('Unhandled Promise Rejection:', e.reason);
e.preventDefault();
});