/** * Navier Instruments Theme - Main JavaScript * * @package Navier_Instruments */ (function() { 'use strict'; /** * Header scroll behavior */ const header = document.querySelector('.site-header'); let lastScrollTop = 0; let ticking = false; function updateHeader(scrollTop) { if (!header) return; // Add scrolled class if (scrollTop > 50) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } // Hide/show header on scroll if (scrollTop > lastScrollTop && scrollTop > 200) { header.classList.add('hidden'); } else { header.classList.remove('hidden'); } lastScrollTop = scrollTop; } window.addEventListener('scroll', function() { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; if (!ticking) { window.requestAnimationFrame(function() { updateHeader(scrollTop); ticking = false; }); ticking = true; } }); /** * Mobile menu toggle */ const menuToggle = document.querySelector('.menu-toggle'); const navigation = document.querySelector('.main-navigation'); if (menuToggle && navigation) { menuToggle.addEventListener('click', function() { const isExpanded = menuToggle.getAttribute('aria-expanded') === 'true'; menuToggle.setAttribute('aria-expanded', !isExpanded); navigation.classList.toggle('active'); document.body.classList.toggle('menu-open'); }); // Close menu when clicking outside document.addEventListener('click', function(e) { if (!navigation.contains(e.target) && !menuToggle.contains(e.target)) { menuToggle.setAttribute('aria-expanded', 'false'); navigation.classList.remove('active'); document.body.classList.remove('menu-open'); } }); // Close menu on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && navigation.classList.contains('active')) { menuToggle.setAttribute('aria-expanded', 'false'); navigation.classList.remove('active'); document.body.classList.remove('menu-open'); } }); } /** * Smooth scroll for anchor links */ document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { const href = this.getAttribute('href'); if (href === '#') return; const target = document.querySelector(href); if (target) { e.preventDefault(); const headerHeight = header ? header.offsetHeight : 0; const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerHeight; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); // Close mobile menu if open if (navigation && navigation.classList.contains('active')) { menuToggle.setAttribute('aria-expanded', 'false'); navigation.classList.remove('active'); document.body.classList.remove('menu-open'); } } }); }); /** * Scroll animations (Intersection Observer) */ const animateElements = document.querySelectorAll('.animate'); if (animateElements.length > 0 && 'IntersectionObserver' in window) { const animationObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animated'); animationObserver.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); animateElements.forEach(element => { animationObserver.observe(element); }); } else { // Fallback for browsers without IntersectionObserver animateElements.forEach(element => { element.classList.add('animated'); }); } /** * Contact form AJAX submission */ const contactForm = document.getElementById('navier-contact-form'); const formResponse = document.getElementById('form-response'); if (contactForm && typeof navierData !== 'undefined') { contactForm.addEventListener('submit', function(e) { e.preventDefault(); const submitButton = contactForm.querySelector('button[type="submit"]'); const originalButtonText = submitButton.innerHTML; // Disable button and show loading state submitButton.disabled = true; submitButton.innerHTML = ' Sending...'; // Collect form data const formData = new FormData(contactForm); formData.append('action', 'navier_contact'); formData.append('nonce', navierData.nonce); // Send AJAX request fetch(navierData.ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin' }) .then(response => response.json()) .then(data => { formResponse.style.display = 'block'; if (data.success) { formResponse.innerHTML = '
' + data.data.message + '
'; contactForm.reset(); } else { formResponse.innerHTML = '
' + data.data.message + '
'; } }) .catch(error => { formResponse.style.display = 'block'; formResponse.innerHTML = '
An error occurred. Please try again.
'; }) .finally(() => { submitButton.disabled = false; submitButton.innerHTML = originalButtonText; }); }); } /** * Dropdown menu keyboard navigation */ const menuItems = document.querySelectorAll('.nav-menu > li'); menuItems.forEach(item => { const link = item.querySelector('a'); const submenu = item.querySelector('.sub-menu'); if (submenu) { // Show submenu on focus link.addEventListener('focus', () => { item.classList.add('focus'); }); // Hide submenu when focus leaves the item item.addEventListener('focusout', (e) => { if (!item.contains(e.relatedTarget)) { item.classList.remove('focus'); } }); // Toggle submenu with keyboard link.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); item.classList.toggle('open'); } }); } }); /** * Back to top button */ const createBackToTop = () => { const button = document.createElement('button'); button.className = 'back-to-top'; button.setAttribute('aria-label', 'Back to top'); button.innerHTML = ''; // Styles button.style.cssText = ` position: fixed; bottom: 30px; right: 30px; width: 50px; height: 50px; background: var(--navier-primary); color: white; border: none; border-radius: 50%; cursor: pointer; opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 999; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 14px rgba(10, 77, 140, 0.3); `; document.body.appendChild(button); // Show/hide on scroll window.addEventListener('scroll', () => { if (window.pageYOffset > 500) { button.style.opacity = '1'; button.style.visibility = 'visible'; } else { button.style.opacity = '0'; button.style.visibility = 'hidden'; } }); // Scroll to top on click button.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); // Hover effect button.addEventListener('mouseenter', () => { button.style.transform = 'translateY(-3px)'; button.style.boxShadow = '0 6px 20px rgba(10, 77, 140, 0.4)'; }); button.addEventListener('mouseleave', () => { button.style.transform = 'translateY(0)'; button.style.boxShadow = '0 4px 14px rgba(10, 77, 140, 0.3)'; }); }; createBackToTop(); /** * Lazy loading images with blur-up effect */ const lazyImages = document.querySelectorAll('img[loading="lazy"]'); lazyImages.forEach(img => { img.style.transition = 'filter 0.3s ease'; if (img.complete) { img.style.filter = 'blur(0)'; } else { img.style.filter = 'blur(10px)'; img.addEventListener('load', () => { img.style.filter = 'blur(0)'; }); } }); /** * Add loading state to buttons */ document.querySelectorAll('form button[type="submit"]').forEach(button => { button.addEventListener('click', function() { const form = this.closest('form'); if (form && form.checkValidity()) { this.classList.add('loading'); } }); }); /** * Counter animation for stats */ const animateCounter = (element, target, duration = 2000) => { let start = 0; const increment = target / (duration / 16); const suffix = element.textContent.replace(/[0-9]/g, ''); const step = () => { start += increment; if (start < target) { element.textContent = Math.floor(start) + suffix; requestAnimationFrame(step); } else { element.textContent = target + suffix; } }; step(); }; // Animate stats when they come into view const statValues = document.querySelectorAll('.hero-stat-value, .about-badge-value'); if (statValues.length > 0 && 'IntersectionObserver' in window) { const statsObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const element = entry.target; const text = element.textContent; const number = parseInt(text.replace(/\D/g, '')); if (!isNaN(number)) { animateCounter(element, number); } statsObserver.unobserve(element); } }); }, { threshold: 0.5 }); statValues.forEach(stat => { statsObserver.observe(stat); }); } /** * Form validation enhancement */ document.querySelectorAll('.form-input, .form-textarea').forEach(input => { input.addEventListener('blur', function() { if (this.value.trim() !== '') { this.classList.add('filled'); } else { this.classList.remove('filled'); } // Custom validation feedback if (!this.validity.valid) { this.classList.add('error'); } else { this.classList.remove('error'); } }); }); /** * Scroll Reveal Animation System */ function initScrollReveal() { const revealElements = document.querySelectorAll('[data-reveal]'); if (revealElements.length === 0) return; const observerOptions = { root: null, rootMargin: '0px 0px -50px 0px', threshold: 0.1 }; const revealObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const el = entry.target; const delay = el.dataset.delay || 0; setTimeout(() => { el.classList.add('revealed'); }, parseInt(delay)); revealObserver.unobserve(el); } }); }, observerOptions); revealElements.forEach(el => { revealObserver.observe(el); }); } /** * Temperature Bar Animation */ function initTempBarAnimation() { const tempBars = document.querySelectorAll('.temp-bar-fill'); if (tempBars.length === 0) return; const observerOptions = { threshold: 0.5 }; const tempObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate'); tempObserver.unobserve(entry.target); } }); }, observerOptions); tempBars.forEach(bar => { tempObserver.observe(bar); }); } /** * Parallax Background Effect */ function initParallax() { const parallaxBgs = document.querySelectorAll('[data-parallax-bg]'); if (parallaxBgs.length === 0) return; let ticking = false; function updateParallax() { const scrolled = window.pageYOffset; parallaxBgs.forEach(el => { const rect = el.getBoundingClientRect(); const speed = 0.3; if (rect.bottom > 0 && rect.top < window.innerHeight) { const yPos = -(scrolled * speed); el.style.backgroundPositionY = `calc(50% + ${yPos}px)`; } }); ticking = false; } window.addEventListener('scroll', function() { if (!ticking) { window.requestAnimationFrame(updateParallax); ticking = true; } }); } /** * Smooth Scroll to Anchor */ function initSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { const targetId = this.getAttribute('href'); if (targetId === '#') return; const target = document.querySelector(targetId); if (target) { e.preventDefault(); const headerHeight = document.querySelector('.site-header')?.offsetHeight || 0; const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerHeight - 20; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } }); }); } /** * Image Tilt Effect (hover) */ function initTiltEffect() { const tiltElements = document.querySelectorAll('[data-tilt]'); tiltElements.forEach(el => { el.addEventListener('mousemove', function(e) { const rect = el.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) / 20; const rotateY = (centerX - x) / 20; el.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`; }); el.addEventListener('mouseleave', function() { el.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale(1)'; }); }); } /** * Device Screen Scroll Animation * Changes device screen content based on scroll position */ function initDeviceScrollAnimation() { const section = document.getElementById('device-scroll-section'); if (!section) return; const slides = section.querySelectorAll('.screen-slide'); const dots = section.querySelectorAll('.scroll-dot'); if (slides.length === 0) return; let currentSlide = 0; let isAnimating = false; let lastScrollY = window.pageYOffset; // Function to change slide function changeSlide(index) { if (index < 0 || index >= slides.length || isAnimating) return; if (index === currentSlide) return; isAnimating = true; // Remove active from all slides.forEach(s => s.classList.remove('active')); dots.forEach(d => d.classList.remove('active')); // Add active to new slide slides[index].classList.add('active'); dots[index].classList.add('active'); currentSlide = index; setTimeout(() => { isAnimating = false; }, 600); } // Scroll-based animation using IntersectionObserver const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // Section is visible, start scroll listening window.addEventListener('scroll', handleScroll); } else { // Section not visible, stop listening window.removeEventListener('scroll', handleScroll); } }); }, { threshold: 0.3 }); observer.observe(section); // Handle scroll to change slides function handleScroll() { const rect = section.getBoundingClientRect(); const sectionTop = rect.top; const sectionHeight = rect.height; const windowHeight = window.innerHeight; // Calculate progress through section (0 to 1) const scrollProgress = 1 - ((sectionTop + sectionHeight/2) / windowHeight); // Determine which slide to show based on scroll progress const slideIndex = Math.min( slides.length - 1, Math.max(0, Math.floor(scrollProgress * slides.length * 1.5)) ); changeSlide(slideIndex); } // Allow clicking on dots dots.forEach((dot, index) => { dot.addEventListener('click', () => { changeSlide(index); }); }); // Auto-advance if section is visible but not scrolling let autoAdvanceTimer; function startAutoAdvance() { stopAutoAdvance(); autoAdvanceTimer = setInterval(() => { const nextSlide = (currentSlide + 1) % slides.length; changeSlide(nextSlide); }, 3000); } function stopAutoAdvance() { if (autoAdvanceTimer) { clearInterval(autoAdvanceTimer); } } // Start auto-advance when section is in view const autoObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { startAutoAdvance(); } else { stopAutoAdvance(); } }); }, { threshold: 0.5 }); autoObserver.observe(section); } /** * Initialize all features */ function initializeAll() { // Prevent double initialization if (document.body.classList.contains('loaded')) return; // Add loaded class to body for any CSS transitions document.body.classList.add('loaded'); // Check for reduced motion preference if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { document.querySelectorAll('.animate').forEach(el => { el.classList.add('animated'); }); // Still reveal elements, just without animation document.querySelectorAll('[data-reveal]').forEach(el => { el.classList.add('revealed'); }); } else { // Initialize scroll reveal animations initScrollReveal(); initTempBarAnimation(); initParallax(); initTiltEffect(); initDeviceScrollAnimation(); } // Always init smooth scroll initSmoothScroll(); console.log('Navier theme initialized successfully'); } /** * Initialize on DOM ready or immediately if already loaded */ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeAll); } else { // DOM is already ready initializeAll(); } // Also try on window load as fallback window.addEventListener('load', initializeAll); })();