/**
* 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);
})();