1$ = jQuery.noConflict(); //makes use of the $ sign for jQuery in WP 2 3/** 4 * Initialize all Swiper sliders on the page. 5 * Each slider must have `data-swiper` on the root element. 6 * Pass per-slider config via `data-options='{"autoplay":{"delay":5000}}'`. 7 */ 8 9document.addEventListener('DOMContentLoaded', function () { 10 document.querySelectorAll('[data-swiper]').forEach(function (root) { 11 let parsed = {}; 12 try { 13 parsed = JSON.parse(root.getAttribute('data-options') || '{}'); 14 } catch (e) {} 15 16 new Swiper(root, Object.assign({ 17 loop: true, 18 pagination: { el: root.querySelector('.swiper-pagination'), clickable: true }, 19 navigation: { 20 nextEl: root.querySelector('.swiper-button-next'), 21 prevEl: root.querySelector('.swiper-button-prev') 22 } 23 }, parsed)); 24 }); 25}); 26 27// ------------------------------------------------ 28// ACCORDION FUNCTION 29// ------------------------------------------------ 30 31jQuery(document).ready(function($) { 32 // Add a click event to the accordion titles 33 $('.accordion-toggle').click(function(event) { 34 // Prevent the default behavior of anchor links if used 35 event.preventDefault(); 36 37 // Toggle the 'active-tab' class on the clicked accordion title 38 $(this).toggleClass('active-tab'); 39 40 // Toggle the visibility of the corresponding accordion content 41 $(this).closest('.accordion-item').find('.accordion-content').toggleClass("open").slideToggle(); 42 }); 43}); 44 45// ------------------------------------------------ 46// TABS FUNCTION 47// ------------------------------------------------ 48document.addEventListener('DOMContentLoaded', function () { 49 const esc = (s) => (window.CSS && CSS.escape ? CSS.escape(s) : String(s).replace(/([ #.;?+*~':"!^$[\](){}/|-])/g, '\\$1')); 50 51 document.querySelectorAll('.tab-menu').forEach(function (menu) { 52 const links = Array.from(menu.querySelectorAll('a[href^="#"]')); 53 if (!links.length) return; 54 55 // Panels = only those IDs referenced by this menu's links 56 const panels = links 57 .map(a => document.getElementById((a.getAttribute('href') || '').slice(1))) 58 .filter(Boolean); 59 60 if (!panels.length) return; 61 62 function show(idx) { 63 links.forEach(a => a.classList.remove('active')); 64 panels.forEach(p => { p.style.display = 'none'; p.classList.remove('is-active'); }); 65 66 const link = links[idx] || links[0]; 67 const id = (link.getAttribute('href') || '').slice(1); 68 const panel = document.getElementById(esc(id)); 69 if (!panel) return; 70 71 link.classList.add('active'); 72 panel.style.display = 'block'; 73 panel.classList.add('is-active'); 74 75 // Refresh only Swiper(s) inside this panel 76 panel.querySelectorAll('.swiper').forEach(el => el.swiper && el.swiper.update()); 77 } 78 79 // Start: prefer a link already marked .active, else URL hash match, else first 80 let start = links.findIndex(a => a.classList.contains('active')); 81 if (start < 0 && location.hash) { 82 start = panels.findIndex(p => ('#' + p.id) === location.hash); 83 } 84 if (start < 0) start = 0; 85 show(start); 86 87 // Clicks for THIS menu only 88 menu.addEventListener('click', function (e) { 89 const a = e.target.closest('a[href^="#"]'); 90 if (!a || !menu.contains(a)) return; 91 const idx = links.indexOf(a); 92 if (idx === -1) return; 93 e.preventDefault(); 94 show(idx); 95 }); 96 }); 97}); 98 99 100// Mobile Menu Toggle 101document.addEventListener('DOMContentLoaded', () => { 102 const btn = document.querySelector('.mobile-menu-btn'); 103 const nav = document.querySelector('.site-nav'); 104 if (!btn) return; 105 106 btn.addEventListener('click', () => { 107 const isActive = btn.classList.toggle('is-active'); 108 btn.setAttribute('aria-expanded', String(isActive)); 109 nav.classList.toggle('open'); 110 }); 111}); 112 113// Counters 114document.addEventListener('DOMContentLoaded', () => { 115 const els = [...document.querySelectorAll('.countdown[data-target]')]; 116 if (!els.length) return; 117 118 const pad = n => String(n).padStart(2, '0'); 119 120 const getParts = ms => { 121 if (ms <= 0) { 122 return { d: '00', h: '00', m: '00', s: '00', done: true }; 123 } 124 const s = Math.floor(ms / 1000); 125 const d = Math.floor(s / 86400); 126 const h = Math.floor((s % 86400) / 3600); 127 const m = Math.floor((s % 3600) / 60); 128 const sec = s % 60; 129 return { d: pad(d), h: pad(h), m: pad(m), s: pad(sec), done: false }; 130 }; 131 132 // Ensure each countdown element has structure with labels
133 els.forEach(el => { 134 if (!el.querySelector('.countdown-part')) { 135 el.innerHTML = ` 136 <span class="countdown-part days"> 137 <span class="value"></span> 138 <span class="label">Days</span> 139 </span> 140 <span class="countdown-part hours"> 141 <span class="value"></span> 142 <span class="label">Hours</span> 143 </span> 144 <span class="countdown-part minutes"> 145 <span class="value"></span> 146 <span class="label">Minutes</span> 147 </span> 148 <span class="countdown-part seconds"> 149 <span class="value"></span> 150 <span class="label">Seconds</span> 151 </span> 152 `; 153 } 154 }); 155 156 const tick = () => { 157 const now = Date.now(); 158 els.forEach(el => { 159 const t = Date.parse(el.dataset.target); 160 if (Number.isNaN(t)) return; 161 const { d, h, m, s, done } = getParts(t - now); 162 163 el.querySelector('.days .value').textContent = d; 164 el.querySelector('.hours .value').textContent = h; 165 el.querySelector('.minutes .value').textContent = m; 166 el.querySelector('.seconds .value').textContent = s; 167 168 if (done) el.classList.add('is-complete'); 169 }); 170 }; 171 172 tick(); 173 const interval = setInterval(() => { 174 tick(); 175 if (els.every(el => el.classList.contains('is-complete'))) clearInterval(interval); 176 }, 1000); 177}); 178 179// Scroll top header out of view and back into view 180document.addEventListener('DOMContentLoaded', () => { 181 const topContent = document.querySelector('.site-header-top-content'); 182 if (!topContent) return; 183 184 const fullHeight = topContent.offsetHeight; // capture its natural height once 185 186 const update = () => { 187 if (window.scrollY > 0) { 188 topContent.style.height = '0'; 189 topContent.style.overflow = 'hidden'; 190 topContent.style.transition = 'height 0.2s ease'; 191 } else { 192 topContent.style.height = fullHeight + 'px'; 193 topContent.style.transition = 'height 0.2s ease'; 194 } 195 }; 196 197 update(); // run on load 198 window.addEventListener('scroll', update); 199}); 200 201// Read More Button For Text-Image Section 202document.addEventListener('DOMContentLoaded', () => { 203 document.addEventListener('click', (e) => { 204 const btn = e.target.closest('.read-more-btn'); 205 const btnText = e.target.closest('.read-more-btn > span'); 206 if (!btn) return; 207 208 const block = btn.closest('.text-image-text .body-text'); 209 if (!block) return; 210 211 const expanded = block.classList.toggle('expanded'); 212 btn.setAttribute('aria-expanded', String(expanded)); 213 btnText.textContent = expanded ? 'Read less' : 'Read more'; 214 }); 215}); 216 217// Site Search 218document.addEventListener("DOMContentLoaded", () => { 219 const toggleBtn = document.querySelector(".site-search-toggle"); 220 const searchForm = document.querySelector("#search-form"); 221 222 if (toggleBtn && searchForm) { 223 toggleBtn.addEventListener("click", () => { 224 searchForm.classList.toggle("is-visible"); 225 }); 226 } 227}); 228 229// ------------------------------------------------ 230// ADD BACKGROUND GRADIENT TO EVERY OTHER SECTION 231// ------------------------------------------------ 232 233document.addEventListener('DOMContentLoaded', () => { 234 const sections = Array.from(document.querySelectorAll('section')); 235 if (!sections.length) return; 236 237 // Clean slate 238 sections.forEach(s => s.classList.remove('gradient-bg')); 239 240 // Find the first hero section (class="hero-slider") 241 const heroIdx = sections.findIndex(s => s.classList.contains('hero-slider')); 242 243 // Start index: if hero exists, skip hero and the next section; otherwise start at 0 244 const start = heroIdx !== -1 ? heroIdx + 2 : 0; 245 246 // Add gradient to every other section from 'start', never to hero sections 247 for (let i = start; i < sections.length; i += 2) { 248 if (!sections[i].classList.contains('hero-slider')) { 249 sections[i].classList.add('gradient-bg'); 250 } 251 } 252}); 253 254// ------------------------------------------------ 255// BLOG FUNCTIONS 256// ------------------------------------------------ 257 258// Read timer for blog posts 259if (document.getElementById("post-copy")) { 260 function readingTime() { 261 const text = document.getElementById("post-copy").innerText; 262 const wpm = 265; 263 const words = text.trim().split(/\s+/).length; 264 const time = Math.ceil(words / wpm); 265 document.getElementById("time-read").innerText = time; 266 } 267 readingTime(); 268} 269 270// wrap images and fig captions in a div within blog post content 271$(".post-content img,.post-content figure").each(function(index, element) { 272 $(element).wrap("<div class='post-img'></div>"); 273}); 274 275// ------------------------------------------------ 276// MODAL FUNCTIONS 277// ------------------------------------------------ 278 279// Add data-fancybox attribute to pop-up/modal elements 280// Then initialize fancybox elements, which allows any element with that attribute 281// to open its target inside a fancybox modal 282 283document.addEventListener('DOMContentLoaded', () => { 284 document.querySelectorAll('.popup-form a').forEach(a => { 285 a.setAttribute('data-fancybox', ''); 286 }); 287 // Pricing cards modal buttons 288 const pricingCardsModalScope = document.querySelector('.cards-pricing-with-modal'); 289 if (pricingCardsModalScope) { 290 pricingCardsModalScope.querySelectorAll('a[href="#pricing-cards-modal"]').forEach((a, idx) => { 291 a.setAttribute('data-fancybox', ''); 292 a.setAttribute('data-type', 'inline'); 293 294 // 0-based index for Swiper 295 a.dataset.slideIndex = String(idx); 296 }); 297 298 // IMPORTANT: handle pricing modal triggers manually so we can set the slide BEFORE opening 299 pricingCardsModalScope.addEventListener('click', (e) => { 300 const a = e.target.closest('a[href="#pricing-cards-modal"][data-sli
300de-index]'); 301 if (!a) return; 302 303 e.preventDefault(); 304 305 const index = Number(a.dataset.slideIndex ?? 0); 306 307 // 1) Move the (already-initialized) swiper while hidden 308 const modal = document.querySelector('#pricing-cards-modal'); 309 const swiperRoot = modal?.querySelector('[data-swiper]'); // this matches your Swiper initializer 310 const swiper = swiperRoot?.swiper; 311 312 if (swiper) { 313 // If loop is on, use slideToLoop so logical slide # maps correctly 314 if (swiper.params?.loop && typeof swiper.slideToLoop === 'function') { 315 swiper.slideToLoop(index, 0); 316 } else { 317 swiper.slideTo(index, 0); 318 } 319 // Hidden sliders sometimes need a refresh once shown; harmless to call now 320 swiper.update(); 321 } else { 322 // Fallback: click the pagination bullet if swiper instance isn't accessible for some reason 323 const bullets = modal?.querySelectorAll('.swiper-pagination .swiper-pagination-bullet') || []; 324 bullets[index]?.click(); 325 } 326 327 // 2) Open the modal AFTER the slide is set 328 if (window.Fancybox) { 329 Fancybox.show( 330 [{ src: '#pricing-cards-modal', type: 'inline' }], 331 { autoFocus: true, dragToClose: true } 332 ); 333 } 334 }); 335 } 336 337 if (window.Fancybox) { 338 Fancybox.bind("[data-fancybox]", { autoFocus: true, dragToClose: true }); 339 } 340}); 341 342// (optional) callable function if you inject links later: 343function addFancyboxToPopupLinks() { 344 document.querySelectorAll('.popup-form a:not([data-fancybox])') 345 .forEach(a => a.setAttribute('data-fancybox', '')); 346} 347 348// ------------------------------------------------ 349// SMOOTH SCROLL BEHAVIOR 350// ------------------------------------------------ 351 352document.addEventListener('DOMContentLoaded', () => { 353 document.querySelectorAll('a[href^="#"]').forEach(link => { 354 355 // If a link has a data-fancybox attribute, ignore it 356 // Otherwise the scroll-to will override it and fancybox won't work properly 357 if (link.hasAttribute('data-fancybox')) return; 358 359 link.addEventListener('click', event => { 360 const href = link.getAttribute('href'); 361 const target = href ? document.querySelector(href) : null; 362 if (!target) return; 363 364 event.preventDefault(); 365 366 const y = 367 target.getBoundingClientRect().top + 368 window.scrollY - 369 80; // header height offset 370 371 window.scrollTo({ 372 top: y, 373 behavior: 'smooth' 374 }); 375 }); 376 }); 377});
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.