1if (window.lucide) window.lucide.createIcons(); 2 3const menuButton = document.querySelector('.menu-toggle'); 4const navigation = document.querySelector('.desktop-nav'); 5const navGroups = [...document.querySelectorAll('.nav-group')]; 6 7const closeMegaMenus = (exceptGroup) => { 8 navGroups.forEach((group) => { 9 if (group === exceptGroup) return; 10 group.classList.remove('is-open'); 11 group.querySelector('.nav-trigger')?.setAttribute('aria-expanded', 'false'); 12 }); 13}; 14 15navGroups.forEach((group) => { 16 const trigger = group.querySelector('.nav-trigger'); 17 const setOpen = (isOpen) => { 18 closeMegaMenus(isOpen ? group : null); 19 group.classList.toggle('is-open', isOpen); 20 trigger.setAttribute('aria-expanded', String(isOpen)); 21 }; 22 23 trigger.addEventListener('click', () => { 24 if (window.innerWidth > 980) setOpen(true); 25 else setOpen(!group.classList.contains('is-open')); 26 }); 27 group.addEventListener('mouseenter', () => { 28 if (window.innerWidth > 980) setOpen(true); 29 }); 30 group.addEventListener('mouseleave', () => { 31 if (window.innerWidth > 980) setOpen(false); 32 }); 33}); 34 35document.addEventListener('click', (event) => { 36 if (!event.target.closest('.nav-group')) closeMegaMenus(); 37}); 38 39document.addEventListener('keydown', (event) => { 40 if (event.key !== 'Escape') return; 41 const openTrigger = document.querySelector('.nav-group.is-open .nav-trigger'); 42 closeMegaMenus(); 43 openTrigger?.focus(); 44}); 45 46if (menuButton && navigation) { 47 const closeMenu = () => { 48 menuButton.setAttribute('aria-expanded', 'false'); 49 navigation.classList.remove('is-open'); 50 document.body.classList.remove('menu-open'); 51 closeMegaMenus(); 52 }; 53 54 menuButton.addEventListener('click', () => { 55 const isOpen = menuButton.getAttribute('aria-expanded') === 'true'; 56 menuButton.setAttribute('aria-expanded', String(!isOpen)); 57 navigation.classList.toggle('is-open', !isOpen); 58 document.body.classList.toggle('menu-open', !isOpen); 59 }); 60 61 navigation.addEventListener('click', (event) => { 62 if (event.target.closest('a')) closeMenu(); 63 }); 64 65 window.addEventListener('resize', () => { 66 if (window.innerWidth > 980) closeMenu(); 67 }); 68} 69 70const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 71const hero = document.querySelector('.hero'); 72const heroSlides = [...document.querySelectorAll('.hero-slide')]; 73const heroBackdrops = [...document.querySelectorAll('.hero-backdrop')]; 74const carouselDots = [...document.querySelectorAll('.carousel-dot')]; 75 76if (hero && heroSlides.length > 1) { 77 let activeSlide = 0; 78 let autoplayTimer; 79 let pointerStart = 0; 80 81 const showSlide = (index) => { 82 activeSlide = (index + heroSlides.length) % heroSlides.length; 83 heroSlides.forEach((slide, slideIndex) => { 84 const isActive = slideIndex === activeSlide; 85 slide.classList.toggle('is-active', isActive); 86 slide.setAttribute('aria-hidden', String(!isActive)); 87 heroBackdrops[slideIndex]?.classList.toggle('is-active', isActive); 88 carouselDots[slideIndex]?.classList.toggle('is-active', isActive); 89 if (isActive) carouselDots[slideIndex]?.setAttribute('aria-current', 'true'); 90 else carouselDots[slideIndex]?.removeAttribute('aria-current'); 91 }); 92 }; 93 94 const stopAutoplay = () => clearInterval(autoplayTimer); 95 const startAutoplay = () => { 96 stopAutoplay(); 97 if (!reducedMotion && !document.hidden) { 98 autoplayTimer = setInterval(() => showSlide(activeSlide + 1), 6000); 99 } 100 }; 101 const selectSlide = (index) => { 102 showSlide(index); 103 startAutoplay(); 104 }; 105 106 hero.querySelector('.carousel-prev')?.addEventListener('click', () => selectSlide(activeSlide - 1)); 107 hero.querySelector('.carousel-next')?.addEventListener('click', () => selectSlide(activeSlide + 1)); 108 carouselDots.forEach((dot, index) => dot.addEventListener('click', () => selectSlide(index))); 109 hero.addEventListener('keydown', (event) => { 110 if (event.key === 'ArrowLeft') selectSlide(activeSlide - 1); 111 if (event.key === 'ArrowRight') selectSlide(activeSlide + 1); 112 }); 113 hero.addEventListener('mouseenter', stopAutoplay); 114 hero.addEventListener('mouseleave', startAutoplay); 115 hero.addEventListener('focusin', stopAutoplay); 116 hero.addEventListener('focusout', (event) => {
117 if (!hero.contains(event.relatedTarget)) startAutoplay(); 118 }); 119 hero.addEventListener('pointerdown', (event) => { pointerStart = event.clientX; }); 120 hero.addEventListener('pointerup', (event) => { 121 const distance = event.clientX - pointerStart; 122 if (Math.abs(distance) > 55) selectSlide(activeSlide + (distance < 0 ? 1 : -1)); 123 }); 124 document.addEventListener('visibilitychange', () => document.hidden ? stopAutoplay() : startAutoplay()); 125 startAutoplay(); 126} 127 128const revealItems = document.querySelectorAll('.reveal'); 129 130const priorityTabs = [...document.querySelectorAll('.priority-tab')]; 131const priorityPanels = [...document.querySelectorAll('.priority-panel')]; 132 133if (priorityTabs.length && priorityPanels.length) { 134 const selectPriority = (index, moveFocus = false) => { 135 priorityTabs.forEach((tab, tabIndex) => { 136 const isActive = tabIndex === index; 137 tab.classList.toggle('is-active', isActive); 138 tab.setAttribute('aria-selected', String(isActive)); 139 tab.tabIndex = isActive ? 0 : -1; 140 if (moveFocus && isActive) tab.focus(); 141 }); 142 priorityPanels.forEach((panel, panelIndex) => { 143 const isActive = panelIndex === index; 144 panel.classList.toggle('is-active', isActive); 145 panel.hidden = !isActive; 146 }); 147 }; 148
149 priorityTabs.forEach((tab, index) => { 150 tab.addEventListener('click', () => selectPriority(index)); 151 tab.addEventListener('keydown', (event) => { 152 if (!['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return; 153 event.preventDefault(); 154 let nextIndex = index; 155 if (event.key === 'Home') nextIndex = 0; 156 else if (event.key === 'End') nextIndex = priorityTabs.length - 1; 157 else if (event.key === 'ArrowDown' || event.key === 'ArrowRight') nextIndex = (index + 1) % priorityTabs.length; 158 else nextIndex = (index - 1 + priorityTabs.length) % priorityTabs.length; 159 selectPriority(nextIndex, true); 160 }); 161 }); 162} 163 164if (reducedMotion || document.hidden || !('IntersectionObserver' in window)) { 165 revealItems.forEach((item) => item.classList.add('is-visible')); 166} else { 167 const revealObserver = new IntersectionObserver((entries, observer) => { 168 entries.forEach((entry) => { 169 if (!entry.isIntersecting) return; 170 entry.target.classList.add('is-visible'); 171 observer.unobserve(entry.target); 172 }); 173 }, { threshold: 0.12 }); 174 175 revealItems.forEach((item) => revealObserver.observe(item)); 176} 177 178const year = document.querySelector('[data-current-year]'); 179if (year) year.textContent = new Date().getFullYear(); 180 181const backToTop = document.querySelector('.back-to-top'); 182 183if (backToTop) { 184 const updateBackToTop = () => { 185 backToTop.classList.toggle('is-visible', window.scrollY > 320); 186 }; 187 188 window.addEventListener('scroll', updateBackToTop, { passive: true }); 189 backToTop.addEventListener('click', () => { 190 backToTop.classList.remove('is-visible'); 191 window.scrollTo({ top: 0, behavior: reducedMotion ? 'auto' : 'smooth' }); 192 }); 193 updateBackToTop(); 194} 195 196const resourceFilters = [...document.querySelectorAll('[data-resource-filter]')]; 197const resourceCards = [...document.querySelectorAll('[data-resource-type]')]; 198 199const customerViewport = document.querySelector('.customer-carousel-viewport'); 200const customerTrack = document.querySelector('.customer-carousel-track'); 201const customerSlides = [...document.querySelectorAll('.customer-slide')]; 202 203if (customerViewport && customerTrack && customerSlides.length) { 204 let customerIndex = 0; 205 let customerTimer; 206 const visibleCustomers = () => window.innerWidth <= 680 ? 1 : window.innerWidth <= 980 ? 2 : 4; 207 const showCustomer = (index) => { 208 const maxIndex = Math.max(0, customerSlides.length - visibleCustomers()); 209 customerIndex = Math.min(Math.max(index, 0), maxIndex); 210 customerTrack.style.transform = `translateX(-${customerIndex * (100 / visibleCustomers())}%)`; 211 }; 212 const startCustomerCarousel = () => { 213 window.clearInterval(customerTimer); 214 if (!reducedMotion) customerTimer = window.setInterval(() => { 215 const maxIndex = Math.max(0, customerSlides.length - visibleCustomers()); 216 showCustomer(customerIndex >= maxIndex ? 0 : customerIndex + 1); 217 }, 4200); 218 }; 219 document.querySelector('[data-customer-prev]')?.addEventListener('click', () => { showCustomer(customerIndex - 1); startCustomerCarousel(); }); 220 document.querySelector('[data-customer-next]')?.addEventListener('click', () => { showCustomer(customerIndex + 1); startCustomerCarousel(); }); 221 customerViewport.addEventListener('mouseenter', () => window.clearInterval(customerTimer)); 222 customerViewport.addEventListener('mouseleave', startCustomerCarousel); 223 customerViewport.addEventListener('focusin', () => window.clearInterval(customerTimer)); 224 customerViewport.addEventListener('focusout', startCustomerCarousel); 225 window.addEventListener('resize', () => showCustomer(customerIndex)); 226 showCustomer(0); 227 startCustomerCarousel(); 228} 229 230resourceFilters.forEach((filterButton) => { 231 filterButton.addEventListener('click', () => { 232 const selectedType = filterButton.dataset.resourceFilter; 233 resourceFilters.forEach((button) => button.classList.toggle('is-active', button === filterButton)); 234 resourceCards.forEach((card) => { 235 card.hidden = selectedType !== 'all' && card.dataset.resourceType !== selectedType; 236 }); 237 }); 238}); 239 240const contactEndpoint = 'https://formspree.io/f/mlezaqok'; 241 242document.querySelectorAll('.contact-form').forEach((form) => { 243 const status = document.createElement('p'); 244 status.className = 'form-status form-field-full'; 245 status.setAttribute('role', 'status'); 246 form.append(status); 247 248 form.addEventListener('submit', async (event) => { 249 event.preventDefault(); 250 const submitButton = form.querySelector('button[type="submit"]'); 251 submitButton?.setAttribute('disabled', ''); 252 status.textContent = 'Sending your enquiry...'; 253 254 try { 255 const response = await fetch(form.action || contactEndpoint, { 256 method: 'POST', 257 body: new FormData(form), 258 headers: { Accept: 'application/json' } 259 }); 260 if (!response.ok) throw new Error('Contact submission failed'); 261 form.reset(); 262 status.textContent = 'Thank you. Your enquiry has been sent successfully.'; 263 } catch { 264 status.textContent = 'We could not send your enquiry. Please try again.'; 265 } finally { 266 submitButton?.removeAttribute('disabled'); 267 } 268 }); 269}); 270 271const submitContactAndOpen = async (form, pdfUrl, statusElement) => { 272 const submitButton = form.querySelector('button[type="submit"]'); 273 form.querySelector('.pdf-access-link')?.remove(); 274 submitButton?.setAttribute('disabled', ''); 275 if (statusElement) statusElement.textContent = 'Submitting your details...'; 276 277 const formData = new FormData(form); 278 formData.set('requested_pdf', pdfUrl); 279 formData.set('_subject', 'IIIOT PDF access request'); 280 281 try { 282 const response = await fetch(form.action || contactEndpoint, { 283 method: 'POST', 284 body: formData, 285 headers: { Accept: 'application/json' } 286 }); 287 if (!response.ok) throw new Error('Contact submission failed'); 288 const pdfLink = document.createElement('a'); 289 pdfLink.className = 'button button-primary pdf-access-link'; 290 pdfLink.href = pdfUrl; 291 pdfLink.target = '_blank'; 292 pdfLink.rel = 'noopener noreferrer'; 293 pdfLink.dataset.pdfAuthorized = 'true'; 294 pdfLink.textContent = 'View PDF in new tab'; 295 form.append(pdfLink); 296 pdfLink.focus(); 297 if (statusElement) statusElement.textContent = 'Thank you. Your PDF is ready.'; 298 return true; 299 } catch { 300 if (statusElement) statusElement.textContent = 'We could not submit your details. Please try again.'; 301 return false;
302 } finally { 303 submitButton?.removeAttribute('disabled'); 304 } 305}; 306 307document.querySelectorAll('form[data-download-url]').forEach((form) => { 308 const status = document.createElement('p'); 309 status.className = 'form-status'; 310 status.setAttribute('role', 'status'); 311 form.append(status); 312 form.addEventListener('submit', async (event) => { 313 event.preventDefault(); 314 await submitContactAndOpen(form, form.dataset.downloadUrl, status); 315 }); 316}); 317 318const pdfGate = document.createElement('dialog'); 319pdfGate.className = 'pdf-gate'; 320pdfGate.setAttribute('aria-labelledby', 'pdf-gate-title'); 321pdfGate.innerHTML = `<div class="pdf-gate-head"><div><p class="eyebrow">Resource access</p><h2 id="pdf-gate-title">Enter your details to view this PDF</h2></div><button class="pdf-gate-close" type="button" aria-label="Close PDF access form">×</button></div><p class="pdf-gate-copy">Submit your contact information and the requested document will open in a new tab.</p><form action="${contactEndpoint}" method="post"><div class="pdf-gate-fields"><label>Full name<input name="name" autocomplete="name" required></label><label>Work email<input name="email" type="email" autocomplete="email" required></label><label>Company<input name="company" autocomplete="organization" required></label><label>Phone<input name="phone" type="tel" autocomplete="tel" required></label></div><label class="pdf-gate-consent"><input name="contact_consent" type="checkbox" value="Yes" required><span>I agree that IIIOT may contact me about this resource and related solutions.</span></label><button class="button button-primary" type="submit">Submit and view PDF</button><p class="form-status" role="status"></p></form>`; 322document.body.append(pdfGate); 323 324let requestedPdf = ''; 325document.addEventListener('click', (event) => { 326 const pdfLink = event.target.closest('a[href]'); 327 if (!pdfLink || pdfLink.dataset.pdfAuthorized || !pdfLink.href.split(/[?#]/)[0].toLowerCase().endsWith('.pdf')) return; 328 event.preventDefault(); 329 requestedPdf = pdfLink.href; 330 pdfGate.querySelector('.form-status').textContent = ''; 331 pdfGate.showModal(); 332}); 333 334pdfGate.querySelector('.pdf-gate-close').addEventListener('click', () => pdfGate.close()); 335pdfGate.addEventListener('click', (event) => { 336 if (event.target === pdfGate) pdfGate.close(); 337}); 338pdfGate.querySelector('form').addEventListener('submit', async (event) => { 339 event.preventDefault(); 340 await submitContactAndOpen(event.currentTarget, requestedPdf, pdfGate.querySelector('.form-status')); 341}); 342 343const externalProductPages = { 344 iiotalytics: 'https://iiotalytics.com/Products/IIoTalytics-Veda', 345 industrvalytics: 'https://iiotalytics.com/Products/IndustrValytics', 346 manufactgenai: 'https://iiotalytics.com/Products/ManufactGenAI', 347 immersivexr: 'https://iiotalytics.com/Products/ImmersiveXR', 348 xtraceability: 'https://iiotalytics.com/Products/XTraceability', 349 'video-analytics': 'https://iiotalytics.com/Products/IndustrValytics' 350}; 351const productRoute = window.location.pathname.match(/\/products\/([^/]+)(?:\/index\.html)?\/?$/); 352const productActions = document.querySelector('.product-hero-actions'); 353 354if (productRoute && productActions && externalProductPages[productRoute[1]]) { 355 const productDetailsLink = document.createElement('a'); 356 productDetailsLink.className = 'button button-ghost'; 357 productDetailsLink.href = externalProductPages[productRoute[1]]; 358 productDetailsLink.target = '_blank'; 359 productDetailsLink.rel = 'noopener noreferrer'; 360 productDetailsLink.textContent = 'More product details'; 361 productActions.append(productDetailsLink); 362} 363 364const liveChatScript = document.createElement('script'); 365liveChatScript.src = 'https://mylivechat.com/chatinline.aspx?hccid=18708004'; 366liveChatScript.async = true; 367liveChatScript.defer = true; 368liveChatScript.dataset.cfasync = 'false'; 369document.body.append(liveChatScript);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.