// DOM Content Loaded document.addEventListener('DOMContentLoaded', function() { // Initialize all components initNavigation(); initScrollAnimations(); initCounters(); initTestimonials(); initTabs(); initAccordion(); initFAQ(); initContactForm(); initSmoothScrolling(); }); // Navigation functionality function initNavigation() { const navToggle = document.getElementById('nav-toggle'); const navMenu = document.getElementById('nav-menu'); const navLinks = document.querySelectorAll('.nav-link'); const header = document.getElementById('header'); // Mobile menu toggle if (navToggle && navMenu) { // Added navMenu check navToggle.addEventListener('click', () => { navMenu.classList.toggle('active'); const icon = navToggle.querySelector('i'); icon.classList.toggle('fa-bars'); icon.classList.toggle('fa-times'); }); } // Close mobile menu when clicking on links navLinks.forEach(link => { link.addEventListener('click', () => { if (navMenu && navMenu.classList.contains('active')) { // Added navMenu check navMenu.classList.remove('active'); const icon = navToggle.querySelector('i'); icon.classList.add('fa-bars'); icon.classList.remove('fa-times'); } }); }); // Header scroll effect if (header) { // Added header check window.addEventListener('scroll', () => { if (window.scrollY > 80) { // Adjusted scroll threshold header.style.background = 'rgba(255, 255, 255, 0.98)'; header.style.boxShadow = '0 2px 15px rgba(0, 0, 0, 0.1)'; } else { header.style.background = 'rgba(255, 255, 255, 0.95)'; header.style.boxShadow = 'none'; } }); } } // Scroll animations function initScrollAnimations() { const animatedElements = document.querySelectorAll('section, .service-card, .stat-item, .step, .cert-item, .tech-item, .testimonial, .standard-card, .team-member, .case-study, .pricing-card, .differentiator-card, .insight-card, .client-logo'); // Added new card types const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries, observer) => { // Added observer to unobserve entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('fade-in-up'); observer.unobserve(entry.target); // Unobserve after animation } }); }, observerOptions); animatedElements.forEach(el => { observer.observe(el); }); } // Counter animations function initCounters() { const counters = document.querySelectorAll('.stat-number'); if (counters.length === 0) return; const animateCounter = (counter) => { const target = parseInt(counter.getAttribute('data-target')); const duration = 2000; // Animation duration in ms const increment = target / (duration / 16); // 16ms per frame (approx 60fps) let current = 0; const updateCounter = () => { if (current < target) { current += increment; counter.textContent = Math.ceil(Math.min(current, target)); // Ensure it doesn't exceed target requestAnimationFrame(updateCounter); } else { counter.textContent = target; // Final set to target } }; updateCounter(); }; const counterObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { animateCounter(entry.target); observer.unobserve(entry.target); } }); }, { threshold: 0.5 }); // Trigger when 50% visible counters.forEach(counter => { counterObserver.observe(counter); }); } // Testimonials slider function initTestimonials() { const testimonials = document.querySelectorAll('.testimonial'); const navBtns = document.querySelectorAll('.testimonial-nav .nav-btn'); // More specific selector if (testimonials.length === 0 || navBtns.length === 0) return; let currentSlide = 0; let slideInterval; function showSlide(index) { testimonials.forEach((testimonial, i) => { testimonial.classList.toggle('active', i === index); }); navBtns.forEach((btn, i) => { btn.classList.toggle('active', i === index); }); } function nextSlide() { currentSlide = (currentSlide + 1) % testimonials.length; showSlide(currentSlide); } navBtns.forEach((btn, index) => { btn.addEventListener('click', () => { currentSlide = index; showSlide(currentSlide); clearInterval(slideInterval); // Stop auto-rotate on manual navigation slideInterval = setInterval(nextSlide, 7000); // Restart auto-rotate }); }); showSlide(currentSlide); // Show initial slide slideInterval = setInterval(nextSlide, 5000); // Auto-rotate testimonials } // Tabs functionality function initTabs() { const tabContainers = document.querySelectorAll('.expertise-tabs'); // Target specific tab systems if (tabContainers.length === 0) return; tabContainers.forEach(container => { const tabBtns = container.querySelectorAll('.tab-btn'); const tabPanels = container.querySelectorAll('.tab-panel'); tabBtns.forEach(btn => { btn.addEventListener('click', () => { const targetTab = btn.getAttribute('data-tab'); tabBtns.forEach(b => b.classList.remove('active')); tabPanels.forEach(panel => panel.classList.remove('active')); btn.classList.add('active'); const targetPanel = container.querySelector(`#${targetTab}`); if (targetPanel) targetPanel.classList.add('active'); }); }); // Activate the first tab by default if (tabBtns.length > 0 && tabPanels.length > 0) { tabBtns[0].classList.add('active'); tabPanels[0].classList.add('active'); } }); } // Accordion functionality function initAccordion() { const accordionContainers = document.querySelectorAll('.assessment-accordion'); // Target specific accordions if (accordionContainers.length === 0) return; accordionContainers.forEach(container => { const accordionItems = container.querySelectorAll('.accordion-item'); accordionItems.forEach(item => { const header = item.querySelector('.accordion-header'); if (!header) return; header.addEventListener('click', () => { const isActive = item.classList.contains('active'); // Option: Close other items when one is opened // accordionItems.forEach(accItem => { // if (accItem !== item) accItem.classList.remove('active'); // }); item.classList.toggle('active'); // Toggle current item }); }); // Optionally open the first item by default // if(accordionItems.length > 0) accordionItems[0].classList.add('active'); }); } // FAQ functionality (similar to accordion, but can be separate if behavior differs) function initFAQ() { const faqLists = document.querySelectorAll('.faq-list'); if (faqLists.length === 0) return; faqLists.forEach(list => { const faqItems = list.querySelectorAll('.faq-item'); faqItems.forEach(item => { const question = item.querySelector('.faq-question'); if (!question) return; question.addEventListener('click', () => { const isActive = item.classList.contains('active'); // Option: Close other FAQ items when one is opened // faqItems.forEach(faqItem => { // if (faqItem !== item) faqItem.classList.remove('active'); // }); item.classList.toggle('active'); }); }); }); } // Contact form functionality function initContactForm() { const contactForm = document.getElementById('contact-form'); if (contactForm) { contactForm.addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(contactForm); const data = Object.fromEntries(formData.entries()); // Use .entries() for wider compatibility if (validateForm(data, contactForm)) { // Pass form for targeted error display const submitBtn = contactForm.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Sending...'; submitBtn.disabled = true; // Simulate form submission (replace with actual API call) setTimeout(() => { showNotification('Thank you! Your assessment request has been submitted. We will contact you within 24 hours.', 'success'); contactForm.reset(); // Clear any previous error messages clearFormErrors(contactForm); submitBtn.textContent = originalText; submitBtn.disabled = false; }, 2000); } }); } } // Form validation function validateForm(data, formElement) { clearFormErrors(formElement); // Clear previous errors const errors = []; if (!data.name || data.name.trim().length < 2) { errors.push({ field: 'name', message: 'Please enter a valid name (at least 2 characters).' }); } if (!data.email || !isValidEmail(data.email)) { errors.push({ field: 'email', message: 'Please enter a valid email address.' }); } if (!data.service) { errors.push({ field: 'service', message: 'Please select a service type.' }); } // Optional: phone number validation if (data.phone && !/^\+?[0-9\s-()]{7,20}$/.test(data.phone.trim())) { errors.push({ field: 'phone', message: 'Please enter a valid phone number.' }); } if (errors.length > 0) { const errorMessages = errors.map(err => err.message); showNotification(errorMessages.join('\n'), 'error'); // Display errors next to fields (optional) errors.forEach(err => displayFieldError(formElement, err.field, err.message)); return false; } return true; } function clearFormErrors(formElement) { const errorMessages = formElement.querySelectorAll('.form-error-message'); errorMessages.forEach(msg => msg.remove()); const errorInputs = formElement.querySelectorAll('.input-error'); errorInputs.forEach(input => input.classList.remove('input-error')); } function displayFieldError(formElement, fieldName, message) { const inputField = formElement.querySelector(`[name="${fieldName}"]`); if (inputField) { inputField.classList.add('input-error'); // const errorSpan = document.createElement('span'); // errorSpan.className = 'form-error-message'; // errorSpan.textContent = message; // errorSpan.style.color = '#e74c3c'; // errorSpan.style.fontSize = '0.85rem'; // inputField.parentNode.insertBefore(errorSpan, inputField.nextSibling); } } // Email validation function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(String(email).toLowerCase()); } // Notification system function showNotification(message, type = 'info') { const existingNotification = document.querySelector('.notification-popup'); // Changed class if (existingNotification) { existingNotification.remove(); } const notification = document.createElement('div'); notification.className = `notification-popup notification-${type}`; // Changed class notification.innerHTML = `
${message}
`; // Styles are now in CSS (added below) document.body.appendChild(notification); const closeBtn = notification.querySelector('.notification-popup-close'); closeBtn.addEventListener('click', () => { notification.style.animation = 'slideOutRight 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); }); setTimeout(() => { if (notification.parentNode) { notification.style.animation = 'slideOutRight 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); } }, 5000); } // Smooth scrolling for anchor links function initSmoothScrolling() { const links = document.querySelectorAll('a[href^="#"]'); links.forEach(link => { link.addEventListener('click', function(e) { const targetId = this.getAttribute('href'); // Ensure it's a valid ID selector and not just "#" if (targetId.length > 1 && targetId.startsWith('#')) { const targetSection = document.querySelector(targetId); if (targetSection) { e.preventDefault(); // Prevent default only if target exists const header = document.getElementById('header'); const headerHeight = header ? header.offsetHeight : 0; const targetPosition = targetSection.offsetTop - headerHeight - 20; // 20px offset window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } } }); }); } // Utility functions (can be expanded) function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Add CSS for notifications and form errors const dynamicStyles = document.createElement('style'); dynamicStyles.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } .notification-popup { position: fixed; top: 20px; right: 20px; background: #3498db; /* Default info */ color: white; padding: 1rem 1.5rem; border-radius: 5px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); z-index: 10000; max-width: 400px; animation: slideInRight 0.3s ease forwards; } .notification-popup.notification-success { background: #27ae60; } .notification-popup.notification-error { background: #e74c3c; } .notification-popup-content { display: flex; align-items: center; justify-content: space-between; gap: 1rem; } .notification-popup-message { white-space: pre-line; } .notification-popup-close { background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; padding: 0; line-height: 1; } .notification-popup-close:hover { opacity: 0.8; } .form-error-message { display: block; color: #e74c3c; font-size: 0.85rem; margin-top: 0.25rem; } .input-error { border-color: #e74c3c !important; /* Override default focus */ } `; document.head.appendChild(dynamicStyles); // Performance optimization for scroll events window.addEventListener('scroll', throttle(() => { // Could add parallax effects or other scroll-dependent features here }, 16)); // Approx 60fps // Resize handler window.addEventListener('resize', debounce(() => { // Recalculate dimensions or adjust layouts if necessary // For example, if using JS for element heights that depend on viewport }, 250)); // Preload critical images (example, update paths as needed) function preloadImages() { const criticalImages = [ '/placeholder.svg?height=40&width=120&text=ATIS+Logo', // Nav logo '/placeholder.svg?height=400&width=600&text=Elevator+Assessment' // Hero image // Add other important above-the-fold images if any ]; criticalImages.forEach(src => { const img = new Image(); img.src = src; }); } // Initialize preloading // preloadImages(); // Uncomment if images are external and paths are correct // Basic error handling for JS window.addEventListener('error', function(event) { console.error('Unhandled JavaScript Error:', event.error || event.message); // Optionally, send error reports to a logging service }); window.addEventListener('unhandledrejection', function(event) { console.error('Unhandled Promise Rejection:', event.reason); }); // Service Worker registration (if building a PWA) // if ('serviceWorker' in navigator) { // window.addEventListener('load', () => { // navigator.serviceWorker.register('/sw.js') // .then(registration => console.log('ServiceWorker registration successful with scope: ', registration.scope)) // .catch(error => console.log('ServiceWorker registration failed: ', error)); // }); // }