PageSourceSearch

https://asheboronc.gov/revize/plugins/zbuilder/v1/website/_assets_/js/scripts.js

js asheboronc.gov collected 2026-09-25 04:33:26 UTC 136,950 bytes, 3,850 lines download raw bytes

1/*-----------------------------------------------------------------------------------
2
3Theme Name: revize.
4Front-end developer: Samir Alley
5Author Design: Samir Alley @samiralley
6Author URI: https://www.revize.com/
7Date: Dec 12 2025
8Version: 1.1987987
9
10-----------------------------------------------------------------------------------*/
11
12RZBarAddButton = RZBarAddButton || function() {}
13
14function RZRenderMini(elementList) {
15  elementList.querySelectorAll(".tiny-deferred").forEach(function(el) {
16	window.RZTinyCarousel(el);
17  });
18  elementList.querySelectorAll(".masonry-deferred").forEach(function(el) {
19   window.RZMasonry(el)
20  });
21}
22
23// Link to correct news pages
24(function () {
25  function fixNewsLinks(root) {
26    root = root || document;
27
28    var links = root.querySelectorAll('.rz-news-card-wrapper a.rz-news-button[href]');
29    for (var i = 0; i < links.length; i++) {
30      var a = links[i];
31      var href = (a.getAttribute('href') || '').trim();
32      if (!href) continue;
33
34      var low = href.toLowerCase();
35      if (low.indexOf('http://') === 0 || low.indexOf('https://') === 0 || href.indexOf('//') === 0) continue;
36      if (low.indexOf('mailto:') === 0 || low.indexOf('tel:') === 0 || href.indexOf('#') === 0) continue;
37
38      if (href.charAt(0) === '/') href = href.substring(1);
39      if (href.indexOf('./') !== 0 && href.indexOf('../') !== 0) href = './' + href;
40
41      a.setAttribute('href', href);
42    }
43  }
44
45// If a "tns-item" is off the screen , any focusable children (links, buttons, etc) will not be focusable. This prevents users from having to tab through every single quicklink/news story and only allows focus the visible elements on the screen.
46// To view more news stories, the user will use the prev/next arrows as intended. Also, tiny slider adds aria-hidden and tabindex=-1 to the "tns-item" elements when they are off screen.
47// If aria-hidden=true is on a parent "tns-item" element, the children cannot be focusable (or aria-hidden="false") or you will get ADA error for: "ARIA hidden element must not be focusable or contain focusable elements".
48function observeSlides() {
49    function updateTabindex() {
50        $(".tns-item").each(function () {
51            const hidden = $(this).attr("aria-hidden") === "true";
52            $(this)
53                .find('a, button, input, select, textarea, summary, iframe, [tabindex]')
54                .each(function () {
55                    if (hidden) {
56                        $(this).attr("tabindex", "-1");
57                    } else {
58                        $(this).removeAttr("tabindex");
59                    }
60                });
61        });
62    }
63
64    const observer = new MutationObserver(updateTabindex);
65
66    $(".tns-item").each(function () {
67        observer.observe(this, {
68            attributes: true,
69            attributeFilter: ["class", "aria-hidden"]
70        });
71    });
72  
73    updateTabindex();
74}
75
76observeSlides();
77  
78  // ADA, Refocus last clicked calendar event
79  function initializeModalFocus() {
80      let $lastFocused = null;
81      const $modal = $("#event-modal");
82  
83      function setupEventHandlers() {
84        // Track last focused event
85        $(document).on("click keydown", ".cal-event", function (e) {
86          if (e.type === "click" || (e.type === "keydown" && e.which === 13)) {
87            $lastFocused = $(this);
88            if (e.type === "keydown") $(this).trigger("click");
89          }
90        });
91  
92        // Handle modal closing
93        const closeModal = () => {
94          $modal.hide();
95          $modal.removeClass("show");
96          $modal.css("display", "none");
97  
98          setTimeout(() => {
99            if ($lastFocused?.length) {
100              $lastFocused.focus();
101            }
102          }, 100);
103        };
104  
105        // Close modal via button or escape key
106        $(document).on("click", "#modal-close-button, .modal-footer .btn-secondary", closeModal);
107  
108        // Handle escape key
109        $(document).on("keydown", function (e) {
110          // Get computed style directly
111          const modalElement = document.getElementById("event-modal");
112          if (!modalElement) return;
113          const computedStyle = window.getComputedStyle(modalElement);
114          const isModalVisible = computedStyle.display !== "none";
115  
116          if (e.key === "Escape" && isModalVisible) {
117            e.preventDefault();
118            closeModal();
119          }
120        });
121      }
122  
123      setupEventHandlers();
124    }
125
126    initializeModalFocus();
127
128  function init() {
129    fixNewsLinks(document);
130
131    var wraps = document.querySelectorAll('.rz-news-card-wrapper');
132    for (var w = 0; w < wraps.length; w++) {
133      (function (wrap) {
134        var obs = new MutationObserver(function () {
135          fixNewsLinks(wrap);
136        });
137        obs.observe(wrap, { childList: true, subtree: true });
138      })(wraps[w]);
139    }
140  }
141
142  if (document.readyState === 'loading') {
143    document.addEventListener('DOMContentLoaded', init);
144  } else {
145    init();
146  }
147})();
148
149// column border animations
150(function () {
151  function initAnimatedBorders() {
152    var cols = document.querySelectorAll('.rz-column-block[data-border-animated="true"]');
153    if (!cols.length) return;
154
155    // If IntersectionObserver isn't supported, just animate immediately
156    if (!('IntersectionObserver' in window)) {
157      cols.forEach(function (el) { el.classList.add('animate-border'); });
158      return;
159    }
160
161    var io = new IntersectionObserver(function (entries) {
162      entries.forEach(function (entry) {
163        if (!entry.isIntersecting) return;
164        entry.target.classList.add('animate-border');
165        io.unobserve(entry.target); 
166      });
167    }, { threshold: 0.2 });
168
169    cols.forEach(function (el) {
170      if (el.classList.contains('animate-border')) return;
171      io.observe(el);
172    });
173  }
174
175  if (document.readyState === 'loading') {
176    document.addEventListener('DOMContentLoaded', initAnimatedBorders);
177  } else {
178    initAnimatedBorders();
179  }
180})();
181
182// Responsive YouTube videos for all
183function wrapYouTubeIframes(context = document) {
184  context
185    .querySelectorAll('iframe[src*="youtube"], iframe[src*="youtu.be"]')
186    .forEach(function (el) {
187      if (el.closest(".ratio") || el.closest(".youtube-background")) return;
188      el.removeAttribute("width");
189      el.removeAttribute("height");
190      const wrapper = document.createElement("div");
191      wrapper.className = "ratio ratio-16x9";
192      el.parentNode.insertBefore(wrapper, el);
193      wrapper.appendChild(el);
194    });
195}
196document.addEventListener("DOMContentLoaded", function () {
197  wrapYouTubeIframes();
198});
199
200document.addEventListener("shown.bs.offcanvas", function (e) {
201  wrapYouTubeIframes(e.target);
202});
203
204// Add "Today" to Catalogue modal hours and hours Module 
205document.addEventListener('DOMContentLoaded', function () {
206  const fullDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
207  const abbrDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
208  const todayIndex = new Date().getDay();
209  const todayFull = fullDays[todayIndex].toLowerCase();
210  const todayAbbr = abbrDays[todayIndex].toLowerCase();
211  const labelHTML = `
212    <small class="today-badge bg-success-subtle text-success-emphasis border-radius py-1 px-2 d-inline-block lh-sm ms-1 rounded">
213      Today
214    </small>
215  `;
216  document.querySelectorAll('.table.hours-of-op-table tbody tr').forEach(row => {
217    const dayCell = row.querySelector('th');
218    if (!dayCell) return;
219    const text = dayCell.textContent.trim().toLowerCase();
220    if (text.startsWith(todayFull) || text.startsWith(todayAbbr)) {
221      dayCell.insertAdjacentHTML('beforeend', labelHTML);
222      row.classList.add('today-row');
223    }
224  });
225});
226
227$(function() {
228  var $builderBtn = $('.builder-btn-wrap');
229  var $emptyPost = $('.post-content .rz-builder-empty-data');
230  var $builderInfo = $('.post-content .rz-builder-info');
231
232  if ($builderBtn.length && $emptyPost.length) {
233    var $cloneBuilderBtn = $builderBtn.clone(true, true);
234    var $emptyPostHeader = $('<h6 class="builder-empty-header mb-3">Click below to add a module or content to this page</h6>');
235    var $wrapper = $('<div class="builder-btn-inserted"></div>');
236    $wrapper.append($emptyPostHeader).append($cloneBuilderBtn);
237    $emptyPost.empty().append($wrapper);
238  }
239
240  if ($builderInfo.length && !$builderInfo.parent().hasClass('container')) {
241    $builderInfo.wrap('<div class="container-fluid add-module-alert"></div>');
242  }
243});
244
245/**
246 *   Place the logo in the middle of the nav
247 **/
248$(function () {
249   const $headerWrap = $('.site-header-wrap');
250   const $nav = $('#nav');
251 
252   const $textLogo = $('.main-logo.main-logo-text').first();
253   const $imageLogo = $('.main-logo.main-logo-image').first();
254 
255   let $logos = $();
256   if ($textLogo.length && $imageLogo.length) {
257     $logos = $textLogo.add($imageLogo);
258   } else if ($imageLogo.length) {
259     $logos = $imageLogo;
260   } else if ($textLogo.length) {
261     $logos = $textLogo;
262   }
263 
264   if (!$logos.length || !$nav.length || !$headerWrap.length) return;
265 
266   const $placeholder = $('<span class="main-logo-placeholder"></span>');
267   $logos.first().before($placeholder);
268 
269   function moveMainLogo() {
270     const moveAllowed = $headerWrap.attr('data-logo-move') === 'true';
271     const isDesktop = window.innerWidth >= 992;
272 
273     if (!moveAllowed || !isDesktop) {
274       if (!$placeholder.next().is($logos.first())) {
275         $logos.detach();
276         $placeholder.after($logos);
277       }
278       return;
279     }
280 
281     const count = $nav.children('li').length;
282     if (!count) return;
283 
284     const middlePoint = Math.floor(((count - 1) / 2));
285     $logos.detach();
286     $logos.insertAfter('#nav > li:nth-child(' + middlePoint + ')');
287   }
288 
289   moveMainLogo();
290   $(window).on('resize', moveMainLogo);
291 });
292
293/**
294 * Fix any mis-rendered Material Icons on the page.
295 * Converts:
296 *   <i class="material-icons|home"></i>
297 *   <i class="material-icons-outlined|airport_shuttle"></i>
298 * to:
299 *   <span class="material-icons">home</span>
300 *   <span class="material-icons-outlined">airport_shuttle</span>
301 */
302document.addEventListener('DOMContentLoaded', () => {
303   const selector = '[class*="material-icons|"], [class*="material-icons-outlined|"]';
304 
305   document.querySelectorAll(selector).forEach(el => {
306     const m = (el.className || '').match(/\b(material-icons(?:-outlined)?)\|([a-z0-9_]+)\b/i);
307     if (!m) return;
308 
309     const klass = m[1];
310     const name  = m[2];
311 
312     const span = document.createElement('i');
313
314     const newClasses = el.className.replace(m[0], klass).trim();
315     span.className = /\brz-btn-icon\b/.test(newClasses)
316       ? newClasses + ' notranslate'
317       : newClasses + ' rz-btn-icon notranslate';
318 
319     span.setAttribute('translate', 'no');
320     span.setAttribute('lang', 'en');
321 
322     span.textContent = name;
323 
324     // copy other attributes (except class/style)
325     for (const attr of [...el.attributes]) {
326       if (attr.name !== 'class' && attr.name !== 'style') {
327         span.setAttribute(attr.name, attr.value);
328       }
329     }
330 
331     if (el.style.cssText) span.style.cssText = el.style.cssText;
332 
333     el.replaceWith(span);
334   });
335 });
336
337if (window.revizeCalendar) {
338	
339	window.revizeCalendar.editformPrefix = "/revize/plugins/zbuilder/v1/website/editforms/revize_calendar";
340	
341	window.revizeCalendar_proto.fns.resetModal = function(eInfo) {
342		$('#event-modal #ics-export').remove();
343		$('#event-modal #modal-event-duration').remove();
344		$('#event-modal .modal-action-buttons').prepend(`<button id="ics-export" onclick="javascript:icalExport.download('${eInfo.title}');return false;" class="btn btn-light flex-fill border-0"><i class="bi bi-calendar2-plus me-1"></i> Add to Calendar</button>`);
345		$('#event-modal .event-modal-header').css('background','none');
346		$('#event-modal .modal-event-date-wrap').css('background-color', window.revizeCalendar.fns.getEventC
346olor(eInfo));
347		$('#event-edit, #event-delete').remove();
348		$('#event-modal .modal-footer .btn').css('background-color', window.revizeCalendar.fns.getEventColor(eInfo));
349		$('#modal-event-location').html('');
350	}
351	
352	window.revizeCalendar.fns.createModalHeader = function(eInfo) {
353		
354		var defaultImage = window.revizeCalendarDefaultImage ?? "./_assets_/plugins/revizeCalendar/images/default-event.jpg";
355		
356		if (eInfo.image !== '') {
357			var $img = $(decodeURIComponent(eInfo.image));
358			var src = $img.attr('src');
359			if (!src || src.indexOf("noimage.gif") >= 0 || src.indexOf("/revize/plugins/") >= 0) {
360				eInfo.image = defaultImage;
361			} else {
362				eInfo.image = src;
363			}
364		}
365		if (eInfo.image === '') {
366			eInfo.image = defaultImage;
367		}
368
369		$('#event-modal .event-modal-header').css('background', "url('" + eInfo.image + "') center center / cover no-repeat");
370	}
371
372	window.revizeCalendar.fns.createModalBody = function(eInfo) {
373		
374		
375		$('#event-modal .event-border-color').css('background-color', window.revizeCalendar.fns.getEventC
375olor(eInfo));
376		$('#modal-event-title').text(eInfo.title);
377		$('#modal-event-description').html(eInfo.desc.replace(/href=\"\.\.\//g,"href=\""));
378		
379		$("#event-modal-dialog").each(function(i, el) {
380			var eventDate = eInfo.start.format('dddd, MMMM D');
381			el.querySelector(".modal-event-date").innerHTML = `<i class="bi bi-calendar4-event me-2"></i>${eventDate}`;
382			
383			var eventTimes = "";
384			if (eInfo.allDay) {
385				if (eInfo.end.isAfter(eInfo.start, 'day')) {
386					eventTimes = '<span class="dur-start">' + eInfo.start.format('MMM Do ') + '</span> '
387					eventTimes += '- <span class="dur-end">' + eInfo.end.format('MMM Do ') + '</span>';
388				} else {
389					eventTimes = "All Day";
390				}
391			} else {
392				eventTimes = '<span class="dur-start">' + eInfo.start.format('hh:mm a') + '</span> ';
393				if (typeof eInfo.options === "string" && eInfo.options.indexOf("\"hide_end_date\":true") === -1) {
394					var endString = eInfo.end.format('hh:mm a');
395					if (eInfo.end.isAfter(eInfo.start, 'year')) {
396						endString = eInfo.end.format(', YYYY ') + endString;
397					}
398					if (eInfo.end.isAfter(eInfo.start, 'day')) {
399						endString = eInfo.end.format('MMM Do ') + endString;
400					}
401					eventTimes += '- <span class="dur-end">' + endString + '</span>';
402				}
403			}
404			
405			el.querySelector(".modal-event-time").innerHTML = `<i class="bi bi-clock me-2"></i>${eventTimes}`;
406			
407			if (eInfo.location !== '' && eInfo.location !== 'none') {
408				el.querySelector(".modal-location").innerHTML = `<i class="bi bi-geo-alt me-2"></i>${eInfo.location}`;
409			}
410
411		});
412		
413		if (eInfo.location !== '' && eInfo.location !== 'none') {
414			$('#modal-event-location').html('<iframe class="rz-business-inner-map" height="175" frameborder="0" style="width:100%;margin:10px 0;border:1px solid '+window.revizeCalendar.fns.getEventColor(eInfo)+'" src=https://www.google.com/maps/embed/v1/place?key=AIzaSyC5cMe92Yw3JgHrkvgIyHgdS2lTsH4C95k&amp;q="'+encodeURIComponent(eInfo.location)+'" allowfullscreen="true"></iframe>')
415		}
416	}
417	
418	window.revizeCalendar.fns.createModalFooter = function(eInfo) {
419		if(eInfo.url.length){
420			$('#modal-event-readmore').attr('href', eInfo.url).show();
421		} else {
422			$('#modal-event-readmore').hide();
423		}
424
425		if(typeof RZ !== 'undefined' && RZ.login && eInfo.edit && typeof eInfo.rid === "string" && eInfo.rid.indexOf("RevizeCalendar") < 0) {
426
427			if (window.revizeCalendar.fns.editPermitted(eInfo)) {
428				$("#modal-event-body").addClass("has-edit-buttons yes-has-edit-buttons");
429				$("#modal-event-body .rz-btns-container").html(`
430					<a id="event-edit" href="${eInfo.edit}" class="rzBtn rzBtn-sm">Edit event</a>
431					<a id="event-delete" href="javascript:RZdelete( \'${eInfo.rid}\', \'rzevents\' )" class="rzBtn rzBtn-sm bg-danger" style="border-color: rgb(180, 7, 7);background:rgb(223, 7, 7)!important;color:#fff!important">Delete event</a>`);
432			}
433		}
434	}
435}
436
437document.querySelectorAll("[data-masonry]").forEach(function(el) {
438	$(el).one('layoutComplete', function(ev, items, c) {
439		if (!items.length) { return; }
440
441		const layout = items[0].layout;
442		ev.target.querySelectorAll("img").forEach(function(img) {
443			img.addEventListener('load', function() { layout.$element.masonry('layout'); });
444		});
445	});
446});
447
448// Immediately Invoked Function Expression (IIFE) to scope jQuery and avoid global conflicts
449(function ($) {
450// Enable strict mode for better error checking and cleaner JavaScript
451  "use strict";
452  
453// jQuery references to commonly used DOM elements for performance and clarity
454  var $window = $(window),
455  $body = $("body"),
456  $html = $("html"),
457  $rzBarWrap = $("#rz-bar-wrap"),
458  $rzBtnsContainer = $(".rz-btns-container"),
459  $siteHeader     = $('.site-header'),
460  $freeformSlider = $('.rz-freeform-slider'),
461  $heroSlider = $('.hero'),
462  $contentWrap    = $('#rz-content-wrap');
463
464/* ===================== 
465 RZ Class + remove revize bar 
466===================== */
467// Check if the Revize (RZ) object exists to adjust body class based on login state
468  if (typeof RZ !== "undefined") {
469    if (RZ.login) {
470      $body.addClass("user-logged-in");
471    } else {
472      $body.addClass("user-not-logged-in"); 
473    }
474  }
475  
476 /* ===================== 
477     UserWay
478    ===================== */
479   (function(d) {
480     const s = d.createElement("script");
481     const cssColor = getComputedStyle(document.documentElement)
482       .getPropertyValue('--userway-color')
483       .trim();
484   
485     s.setAttribute("data-position", 5);
486     s.setAttribute("data-color", cssColor);
487     s.setAttribute("data-account", "sudb1U5E4V");
488     s.setAttribute("src", "https://cdn.userway.org/widget.js");
489     (d.body || d.head).appendChild(s);
490   })(document);
491   
492   document.addEventListener("DOMContentLoaded", () => {
493     const adaBtns = document.querySelectorAll(".rz-ada-btn-element");
494     
495     adaBtns.forEach(btn => {
496       let adaText = btn.querySelector("span");
497       const ignoreText = adaText && adaText.classList.contains("visually-hidden");
498       const hasText = adaText && !ignoreText;
499       const originalText = hasText ? adaText.innerText : null;
500   
501       let spinner;
502       if (!hasText) {
503         spinner = document.createElement("div");
504         spinner.className = "spinner-border text-dark position-absolute w-100 h-100 start-0 top-0 d-none";
505         spinner.role = "status";
506         spinner.setAttribute("aria-hidden", "true");
507         const button = btn.querySelector("button");
508         if (button) {
509           button.classList.add("position-relative");
510           button.appendChild(spinner);
511         }
512       }
513   
514       btn.addEventListener("click", () => {
515         if (hasText) {
516           adaText.innerText = "Loading tools...";
517         }
518   
519         if (!hasText && spinner) {
520           spinner.classList.remove("d-none");
521         }
522   
523         if (typeof UserWay !== "undefined" && typeof UserWay.widgetOpen === "function") {
524           UserWay.widgetOpen();
525         }
526   
527         function observeUwy() {
528           const target = document.querySelector(".uwy");
529           if (!target) {
530             setTimeout(observeUwy, 100);
531             return;
532           }
533   
534           const observer = new MutationObserver(() => {
535             if (target.classList.contains("uon")) {
536               if (hasText) {
537                 adaText.innerText = originalText;
538               }
539               if (!hasText && spinner) {
540                 spinner.classList.add("d-none");
541               }
542               observer.disconnect();
543             }
544           });
545   
546           observer.observe(target, { attributes: true });
547   
548           if (target.classList.contains("uon")) {
549             if (hasText) adaText.innerText = originalText;
550             if (!hasText && spinner) spinner.classList.add("d-none");
551             observer.disconnect();
552           }
553         }
554   
555         observeUwy();
556       });
557     });
558   });
559  
560// Initialize Bootstrap tooltips for elements using [data-bs-toggle='tooltip']
561  const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
562// Initialize Bootstrap tooltips for elements using [data-bs-toggle='tooltip']
563  const tooltipList = [...tooltipTriggerList].map((tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl));
564
565// Photo Gallery Initialization (may include lightbox or slider)
566/* ===================== 
567 Photo Gallery from https://www.lightgalleryjs.com/demos/thumbnails/ 
568===================== */
569document.addEventListener('DOMContentLoaded', function() {
570  document.querySelectorAll('.lightgallery').forEach(function(galleryEl) {
571    lightGallery(galleryEl, {
572      selector: '.rz-gallery-image',
573      plugins: [lgThumbnail, lgVideo],
574      licenseKey: "E85B5C22-0F6A-4E84-8A38-013E4E9E31E2",
575      speed: 500,
576      zoomFromOrigin: false,
577      loop: true,                   // <-- Infinite scrolling!
578      slideEndAnimation: true       // <-- Optional for smooth looping
579    });
580  });
581});
582
583
584  /* ===================== 
585   Mega font 
586  ===================== */  
587  fitty(".mega-font", {
588    minSize: 32,
589    maxSize: 800,
590  });
591 
592/* ===================== 
593     YouTube Background initializer https://github.com/stamat/youtube-background
594    ===================== */
595    function initYouTubeBackgrounds() {
596      if (window.VideoBackgrounds) {
597        new VideoBackgrounds('[data-vbg]');
598      }
599    }
600    
601    initYouTubeBackgrounds();    
602  
603  $(document).on("change", ".selectall", function () {
604    var $dropdown = $(this).closest(".dropdown");
605    var checked = $(this).is(":checked");
606  
607    $dropdown.find(".option").prop("checked", checked);
608    var total = $dropdown.find('input[name="options[]"]:checked').length;
609  
610    $dropdown.find(".dropdown-text").html("(" + total + ") Selected");
611    $dropdown.find(".select-text").html(checked ? " Deselect" : " Select");
612  });
613  
614  $(document).on("change", "input[type='checkbox'].justone", function () {
615    var $dropdown = $(this).closest(".dropdown");
616    var $allOptions = $dropdown.find("input[type='checkbox'].justone");
617    var $selectAll = $dropdown.find(".selectall");
618  
619    var allChecked = $allOptions.length === $allOptions.filter(":checked").length;
620  
621    $selectAll.prop("checked", allChecked);
622    $dropdown.find(".select-text").html(allChecked ? " Deselect" : " Select");
623  
624    var total = $dropdown.find('input[name="options[]"]:checked').length;
625    $dropdown.find(".dropdown-text").html("(" + total + ") Selected");
626  });  
627
628/* ===================== 
629   sticky header 
630  ===================== */
631  $(function() {
632    var stickyHeader = $('[data-sticky="true"]');
633    if (!stickyHeader.length) return;
634  
635    var logoNoScroll = $(".logo-no-scroll");
636    var logoOnScroll = $(".logo-on-scroll");
637  
638    // store the original offset on load
639    var elementOffset = stickyHeader.offset() ? stickyHeader.offset().top : 0;
640  
641    $(window).on("scroll", function () {
642      var scrollTop = $(this).scrollTop();
643  
644      // Always remove sticky when scrolling back above offset
645      if (scrollTop > elementOffset) {
646        stickyHeader.addClass("sticky-top");
647        logoOnScroll.removeClass("d-none");
648        logoNoScroll.addClass("d-none");
649      } else {
650        stickyHeader.removeClass("sticky-top");
651        logoOnScroll.addClass("d-none");
652        logoNoScroll.removeClass("d-none");
653      }
654    });
655  });
656  
657  // $(window).on("resize", function() {
658  //   elementOffset = stickyHeader.offset() ? stickyHeader.offset().top : 0;
659  // });
660
661
662/* ===================== This is for 
663 the case of not having a hero slider on the freeform page and the navigation being set to absolute which causes for 
664overlapping ===================== */
665  // var siteHeader = $('.site-header'),
666  // siteHeaderHeight = siteHeader.height(),
667  // rzFreeformSlider = $('.rz-freeform-slider');
668  // 
669  // if ((siteHeader.css('position') === 'absolute') & (rzFreeformSlider.length === 0)){
670
671/* ===================== 
672 Header is positioned absolute used for hooking 
673===================== */
674  //   $body.addClass("header-is-absolute no-hero-slider");
675  //   $('#rz-content-wrap').css('padding-top', siteHeaderHeight + 'px');
676  // }
677
678/* ===================== This is for 
679       the case of not having a hero slider on the freeform page and the navigation being set to absolute which causes for 
680      overlapping ===================== */  
681  function adjustHeaderPadding() {
682    // Only run above 996px
683    if (window.innerWidth <= 996) {
684      $body.removeClass('header-is-absolute no-hero-slider');
685      $contentWrap.css('padding-top', '');
686      return;
687    }
688  
689    var hdrPos       = $siteHeader.css('position'),
690        hdrH         = $siteHeader.outerHeight() || 0,
691        noSlider     = $freeformSlider.length === 0,
692        noHeroSlider = $heroSlider.length === 0;
693  
694    // Header is positioned absolute used for hooking
695    if (hdrPos === 'absolute' && noSlider && noHeroSlider) {
696      $body.addClass('header-is-absolute no-hero-slider');
697      $contentWrap.css('padding-top', hdrH + 'px');
698    } else {
699      $body.removeClass('header-is-absolute no-hero-slider');
700      $contentWrap.css('padding-top', '');
701    }
702  }
703
704 // run on load and on resize
705 $window.on('load resize', adjustHeaderPadding);
706
707 /* ===================== 
708  Fade out element
709 ===================== */
710 document.addEventListener('DOMContentLoaded', function () {
711   const els = Array.from(document.querySelectorAll('.fade-out-element'));
712   if (!els.length) return;
713 
714   const DEFAULT_START = 100;
715   const DEFAULT_END   = 300;
716 
717   let ticking = false;
718 
719   function update() {
720     const pageY = window.scrollY;
721 
722     els.forEach(el => {
723       const start = Number(el.dataset.fadeStart ?? DEFAULT_START);
724       const end   = Number(el.dataset.fadeEnd   ?? DEFAULT_END);
725 
726       const elTopPageY = pageY + el.getBoundingClientRect().top;
727       const fadeStartY = elTopPageY - start;
728       const fadeEndY   = elTopPageY + end;
729 
730       let opacity;
731       if (pageY <= fadeStartY) {
732         opacity = 1;
733       } else if (pageY >= fadeEndY) {
734         opacity = 0;
735       } else {
736         opacity = 1 - ((pageY - fadeStartY) / (fadeEndY - fadeStartY));
737       }
738 
739       opacity = Math.max(0, Math.min(1, opacity));
740       el.style.opacity = String(opacity);
741       el.style.pointerEvents = opacity === 0 ? 'none' : '';
742     });
743 
744     ticking = false;
745   }
746 
747   function onScroll() {
748     if (!ticking) {
749       ticking = true;
750       requestAnimationFrame(update);
751     }
752   }
753 
754   window.addEventListener('scroll', onScroll, { passive: true });
755   window.addEventListener('resize', onScroll, { passive: true });
756   update();
757 });
758/* ===================== 
759 Font resizer 
760===================== */
761// Function definition: textResizer
762  function textResizer() {
763    var $dec, $inc;
764    $inc = $("#increase-font");
765    $dec = $("#decrease-font");
766    $inc.on("click", function (e) {
767      return $("body").css({
768        "font-size": "+=1.5",
769      });
770    });
771    $dec.on("click", function (e) {
772      return $("body").css({
773        "font-size": "-=1.5",
774      });
775    });
776    return $("#font-resizer a").on("click", function (e) {
777      return e.preventDefault();
778    });
779  }
780  textResizer();
781
782/* ===================== 
783 Clone icons to each navigation link/span 
784===================== */
785  $('[data-nav-icons="true"] #navicons li').each(function () {
786    var navIconIndex = $(this).index();
787    $(this).find(".navicon").prependTo($("#nav>li").eq(navIconIndex).children("a,span"));
788  });
789
790/* ===================== 
791   Add class to active nav item
792  ===================== */  
793// selected page link
794  if ($body.attr('id') === 'homepage') {
795    $('#nav > li:first-child > a').addClass('active-page');
796  }
797  
798  var cleanUrl = window.location.href
799    .split('/')
800    .map(function(part) {
801      return part.replace(/_/g, '');
802    })
803    .join('/');
804  
805  $('#nav > li').each(function() {
806    var link = $(this).find('> a');
807    var linkText = link.text().toLowerCase().replace(/[^a-z]/g, '');
808  
809    if (cleanUrl.includes(linkText)) {
810      link.addClass('active-page');
811    }
812  });    
813
814/* ===================== 
815   If no submenu items add placeholder 
816  ===================== */
817  document.addEventListener('DOMContentLoaded', function () {
818      const submenu = document.querySelector('#submenu');
819      if (!submenu) return;
820  
821      const hasLinks = submenu.querySelectorAll('a[href]').length > 0;
822  
823      if (!hasLinks) {
824          submenu.outerHTML = `
825              <ul id="submenu">
826              <li class="menuLI level0 menuSelectedChild nav-item position-relative" style="display: flex; align-items: center;"><a class="menuA level0 menuSelectedChild nav-link" href="#" style="flex: 1 1 auto;">These links are just placeholders</a></li>
827              <li class="menuLI level0 menuSelectedChild nav-item position-relative" style="display: flex; align-items: center;"><a class="menuA level0 menuSelectedChild nav-link" href="#" style="flex: 1 1 auto;">Add Links or pages here</a></li>
828              <li class="menuLI level0 menuSelectedChild nav-item position-relative" style="display: flex; align-items: center;"><a class="menuA level0 menuSelectedChild nav-link" href="#" style="flex: 1 1 auto;">Click "Edit Submenu"</a></li>
829              </ul>
830          `;
831      }
832  });
833/* ===================== 
834 Keyboard Navigation: Nav, flyout 
835===================== */
836  var isClick = false;
837  $("#nav li a, #submenu  li a, a, button, .toggle, .toggle2").on("focusin", function (e) {
838    if (isClick === false) {
839      $(".focused").removeClass("focused");
840      $(e.currentTarget).parents("#nav li, #submenu li").addClass("focused");
841      $(".opened:not(.focused) ul:visible,.opened2:not(.focused) ul:visible").slideUp().parent().removeClass("opened opened2");
842    } else {
843      $(".focused").removeClass("focused");
844      isClick = false;
845    }
846  });
847
848/* ===================== 
849   for counters from https://codepen.io/fflorim/pen/eYYdRPB
850  ===================== */
851 (function () {
852   function easeOutQuad(t){ return t*(2-t); }
853   function formatNumber(val, decimals, sep){
854     var n = Number(val); if (!isFinite(n)) n = 0;
855     var s = (decimals > 0) ? n.toFixed(decimals) : Math.round(n).toString();
856     if (sep){
857       var parts = s.split('.');
858       parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
859       s = parts.join('.');
860     }
861     return s;
862   }
863   function animate(el){
864     var start = parseFloat(el.getAttribute('data-counter-start')) || 0;
865     var end   = parseFloat(el.getAttribute('data-counter-end'))   || 0;
866     var dur   = parseInt(el.getAttribute('data-counter-duration'),10) || 2000;
867 
868     var decAttr = (el.getAttribute('data-counter-decimals') || '').trim();
869     var dec = decAttr === '' ? 0 : parseInt(decAttr, 10);
870     if (isNaN(dec)) dec = 0;
871     if (dec < 0) dec = 0;
872     if (dec > 2) dec = 2;
873 
874     var sep = el.getAttribute('data-counter-sep') === '1';
875 
876     // Write once up front, then animate.
877     el.textContent = formatNumber(start, dec, sep);
878 
879     var t0;
880     function step(ts){
881       if(!t0) t0 = ts;
882       var p = Math.min(1, (ts - t0)/dur);
883       var eased = easeOutQuad(p);
884       var val = start + (end - start) * eased;
885       el.textContent = formatNumber(val, dec, sep);
886       if (p < 1) requestAnimationFrame(step);
887       else el.textContent = formatNumber(end, dec, sep);
888     }
889     requestAnimationFrame(step);
890   }
891 
892   var counters = document.querySelectorAll('.rz-element-wrapper .rz-counter');
893   if (!counters.length) return;
894 
895   if ('IntersectionObserver' in window) {
896     var io = new IntersectionObserver(function(entries, obs){
897       entries.forEach(function(e){
898         if (e.isIntersecting) {
899           animate(e.target);
900           obs.unobserve(e.target);
901         }
902       });
903     }, { threshold: 0.35 });
904     counters.forEach(function(el){ io.observe(el); });
905   } else {
906     counters.forEach(animate);
907   }
908 })();
909  
910/* ===================== 
911 for revize buttons container 
912===================== */
913  $rzBtnsContainer.each(function () {
914    var $container = $(this);
915    if ($container.html().trim() != "") {
916      $container.closest(".has-edit-buttons").addClass("yes-has-edit-buttons");
917    }
918  });
919  $(".has-edit-buttons").not(".yes-has-edit-buttons").removeClass("has-edit-buttons");
920
921/* ===================== 
922 responsive views from revize bar 
923===================== */
924  var urlParams = new URLSearchParams(window.location.search); //get all parameters
925  var hidebar = urlParams.get("hidebar"); //extract the hidebar parameter - this will return NULL if hidebar isn't a parameter
926  
927  if (hidebar) {
928    //check if hidebar parameter is set to anything
929    $body.removeClass("user-logged-in").addClass("hide-edit-btns");
930  }
931
932/* ===================== 
933 prevent focused class changes on click - This way arrows wont pop when clicking nav links 
934===================== */
935  $("#nav a,#submenu a").on("mousedown", function () {
936    isClick = true;
937  });
938
939/* ===================== 
940  divider's
941 ===================== */
942document.addEventListener('DOMContentLoaded', function() {
943    var animatedDividers = document.querySelectorAll('.rz-animate-on-visible');
944    animatedDividers.forEach(function(divider) {
945        divider.classList.remove(
946            'divider-anim-grow-center', 'divider-anim-grow-left', 'divider-anim-grow-right',
947            'divider-anim-grow-center-vertical', 'divider-anim-grow-top', 'divider-anim-grow-bottom'
948        );
949    });
950    if ('IntersectionObserver' in window) {
951        var observer = new IntersectionObserver(function(entries, obs) {
952            entries.forEach(function(entry) {
953                var el = entry.target;
954                var anim = el.getAttribute('data-anim-class');
955                if (!anim) return;
956                if (entry.isIntersecting) {
957                    el.classList.add(anim);
958                    // Stop observing after first animation
959                    obs.unobserve(el);
960                }
961            });
962        }, { threshold: 0.25 });
963        animatedDividers.forEach(function(divider) {
964            observer.observe(divider);
965        });
966    } else {
967        // Fallback: Add the anim right away
968        animatedDividers.forEach(function(divider) {
969            var anim = divider.getAttribute('data-anim-class');
970            if (anim) divider.classList.add(anim);
971        });
972    }
973});
974 
975 /* ===================== 
976   Create links for accordion's 
977  ===================== */
978document.addEventListener('DOMContentLoaded', function() {
979    // Assign friendly sequential IDs to all accordions on the page (acc_0, acc_1, etc)
980    let accordions = document.querySelectorAll('.accordion');
981    accordions.forEach(function(accordion, accIdx) {
982      // Use existing doc_center_ prefix if it's a doc center
983      let isDocCenter = accordion.closest('.doc-center-wrap') ? true : false;
984      let baseId = isDocCenter ? 'doc_center_' + accIdx : 'acc_' + accIdx;
985      accordion.id = baseId;
986  
987      // Assign friendly, sequential IDs to all items, headings, panels
988      accordion.querySelectorAll('.accordion-item').forEach(function(item, i) {
989        let headingId = baseId + '_heading_' + i;
990        let panelId   = baseId + '_panel_' + i;
991  
992        // Button/heading
993        let button = item.querySelector('.accordion-button');
994        if (button) {
995          button.setAttribute('data-bs-toggle', 'collapse');
996          button.setAttribute('data-bs-target', '#' + panelId);
997          button.setAttribute('aria-controls', panelId);
998          button.setAttribute('aria-expanded', 'false');
999          button.id = headingId;
1000        }
1001  
1002        // Collapse panel
1003        let collapse = item.querySelector('.accordion-collapse');
1004        if (collapse) {
1005          collapse.id = panelId;
1006          collapse.setAttribute('aria-labelledby', headingId);
1007          collapse.setAttribute('data-bs-parent', '#' + baseId);
1008        }
1009      });
1010    });
1011  
1012    // lightGallery: Enable ONLY for doc centers and rz-agendalist-card-wrapper
1013    document.querySelectorAll('.doc-center-wrap,.rz-agendalist-card-wrapper').forEach(function(wrap) {
1014      lightGallery(wrap, { selector: '.open-pdf' });
1015    });
1016  
1017    // File type icons: Only for document centers
1018    var docTypes = {
1019      binary:   ["bin"],
1020      code:     ["css", "js"],
1021      excel:    ["xlsx", "xls"],
1022      image:    ["gif", "jpg", "jpeg", "png", "bmp"],
1023      pdf:      ["pdf"],
1024      play:     ["mp4", "mp3"],
1025      ppt:      ["ppt", "pptx"],
1026      richtext: ["rtf"],
1027      text:     ["txt"],
1028      word:     ["doc", "docx"],
1029      zip:      ["zip"]
1030    };
1031    
1032    document.querySelectorAll(
1033      '.doc-center-wrap .list-group-item > [data-src]'
1034    ).forEach(function(el) {
1035      var src = el.dataset.src || '';
1036    
1037      // Remove query string and hash before checking extension
1038      var cleanSrc = src.split('?')[0].split('#')[0];
1039      var ext = cleanSrc.split('.').pop().toLowerCase();
1040    
1041      var className = "file";
1042    
1043      Object.entries(docTypes).forEach(function([type, arr]) {
1044        if (arr.includes(ext)) {
1045          className = type;
1046        }
1047      });
1048    
1049      var icon = el.querySelector("i[class*='bi-file']");
1050      if (!icon) return;
1051    
1052      icon.className = "";
1053    
1054      // Restore the required Bootstrap Icons base class
1055      icon.classList.add("bi", "fs-5", "lh-1", "py-2");
1056    
1057      if (className !== "file") {
1058        icon.classList.add(
1059          "text-" + className,
1060          "bi-file-earmark-" + className
1061        );
1062      } else {
1063        icon.classList.add("bi-file-earmark");
1064      }
1065    });
1066  
1067    // Update hash when an accordion opens
1068    document.querySelectorAll('.accordion').forEach(function(acc) {
1069      acc.querySelectorAll('.accordion-collapse').forEach(function(collapse) {
1070        collapse.addEventListener('show.bs.collapse', function() {
1071          if (history.replaceState) {
1072            history.replaceState(
1073              null,
1074              '',
1075              window.location.pathname + window.location.search + '#' + collapse.id
1076            );
1077          } else {
1078            location.hash = '#' + collapse.id;
1079          }
1080        });
1081      });
1082    });
1083  
1084    // On load: open panel from hash, if found
1085    var hash = window.location.hash.replace('#', '');
1086    if (hash) {
1087      var el = document.getElementById(hash);
1088      if (el && el.classList.contains('accordion-collapse') && el.closest('.accordion')) {
1089        var bsCollapse = bootstrap.Collapse.getOrCreateInstance(el, { toggle: false });
1090        bsCollapse.show();
1091      }
1092    }
1093  });
1094
1095 /* ===================== 
1096  custom-cursor for gallery
1097 ===================== */
1098 function setupCustomCursorForGallery() {
1099   document.querySelectorAll('.rz-gallery-image').forEach(area => {
1100     // Prevent double init
1101     if (area.__customCursorAttached) return;
1102     area.__customCursorAttached = true;
1103 
1104     const cursor = area.querySelector('.custom-cursor.follow-cursor');
1105   if (!cursor) { return; }
1106     let visible = false;
1107     let target = { x: 0, y: 0 };
1108     let pos = { x: 0, y: 0 };
1109     let initialized = false;
1110 
1111     function animateCursor() {
1112       pos.x += (target.x - pos.x) * 0.8;
1113       pos.y += (target.y - pos.y) * 0.8;
1114       let scale = visible ? 1 : 0;
1115       cursor.style.transform = `translate(-50%, -50%) translate(${pos.x}px, ${pos.y}px) scale(${scale})`;
1116       cursor.style.opacity = visible ? 1 : 0;
1117       requestAnimationFrame(animateCursor);
1118     }
1119     animateCursor();
1120 
1121     area.addEventListener('mouseenter', (e) => {
1122       const rect = area.getBoundingClientRect();
1123       let x = (e && e.clientX) ? (e.clientX - rect.left) : rect.width / 2;
1124       let y = (e && e.clientY) ? (e.clientY - rect.top) : rect.height / 2;
1125       target.x = pos.x = x;
1126       target.y = pos.y = y;
1127       visible = true;
1128       initialized = true;
1129       cursor.classList.add('active');
1130     });
1131 
1132     area.addEventListener('mouseleave', () => {
1133       visible = false;
1134       cursor.classList.remove('active');
1135     });
1136 
1137     area.addEventListener('mousemove', (e) => {
1138       const rect = area.getBoundingClientRect();
1139       target.x = e.clientX - rect.left;
1140       target.y = e.clientY - rect.top;
1141       if (!initialized) {
1142         pos.x = target.x;
1143         pos.y = target.y;
1144         initialized = true;
1145         cursor.classList.add('active');
1146       }
1147     });
1148 
1149     cursor.style.opacity = 0;
1150     cursor.style.transform = `translate(-50%, -50%) scale(0)`;
1151   });
1152 }
1153 
1154 // Initial setup
1155 setupCustomCursorForGallery();
1156 
1157 // Re-apply every time the slider changes slides (Tiny Slider triggers events)
1158 document.querySelectorAll('.tiny-carousel').forEach(carousel => {
1159   carousel.addEventListener('transitionend', setupCustomCursorForGallery);
1160 });
1161 
1162 // OR: Use MutationObserver for *any* DOM change in the gallery
1163 const galleryWrapper = document.querySelector('.lightgallery') || document.querySelector('.tiny-carousel');
1164 if (galleryWrapper) {
1165   const observer = new MutationObserver(() => {
1166     setupCustomCursorForGallery();
1167   });
1168   observer.observe(galleryWrapper, { childList: true, subtree: true });
1169 }
1170
1171  /*!
1172  Name: Estimated Reading Time
1173  Dependencies: jQuery
1174  Author: Michael Lynch
1175  Author URL: http://michaelynch.com
1176  Date Created: August 14, 2013
1177  Licensed under the MIT license
1178  */
1179  
1180  $.fn.estimatedReadingTime = function (options) {
1181    //return if no element was bound
1182    //so chained events can continue
1183    if (!this.length) {
1184      return this;
1185    }
1186    //define default parameters
1187    var defaults = {
1188      readingTimeTarget: "eta",
1189      wordCountTarget: "words",
1190      readingSpeed: 200,
1191      round: true,
1192    };
1193    //define plugin
1194    var plugin = this;
1195    //define element
1196    var el = $(this);
1197    //merge defaults and options
1198    plugin.settings = $.extend({}, defaults, options);
1199    //define vars
1200    var readingTimeTarget = plugin.settings.readingTimeTarget;
1201    var wordCountTarget = plugin.settings.wordCountTarget;
1202    var readingSpeed = plugin.settings.readingSpeed;
1203    var round = plugin.settings.round;
1204    //define text
1205    var text = el.text();
1206    //split text by spaces to define total words
1207    var totalWords = text.split(" ").length;
1208    //define words per second based on words per minute (readingSpeed)
1209    var wordsPerSecond = readingSpeed / 60;
1210    //define total reading time in seconds
1211    var totalReadingTimeSeconds = totalWords / wordsPerSecond;
1212    //define reading time in minutes
1213    var readingTimeMinutes = Math.round(totalReadingTimeSeconds / 60);
1214    //define remaining reading time seconds
1215    var readingTimeSeconds = Math.round(totalReadingTimeSeconds - readingTimeMinutes * 60);
1216    //if round is set to true
1217    if (round === true) {
1218      //if minutes are greater than 0
1219      if (readingTimeMinutes > 0) {
1220        //set reading time by the minute
1221        $("#" + readingTimeTarget).text(readingTimeMinutes + " Min read");
1222      } else {
1223        //set reading time as less than a minute
1224        $("#" + readingTimeTarget).text("Less than a minute read");
1225      }
1226      //if round is set to false
1227    } else {
1228      //format reading time
1229      var readingTime = readingTimeMinutes + ":" + readingTimeSeconds;
1230      //set reading time in minutes and seconds
1231      $("#" + readingTimeTarget).text(readingTime + " Min");
1232    }
1233    
1234    //if word count container isn't blank or undefined
1235    if (wordCountTarget !== "" && wordCountTarget !== undefined) {
1236      //set word count
1237      $("#" + wordCountTarget).text(totalWords);
1238    }
1239  };
1240  
1241  (function ($) {
1242    var ertTimer = null;
1243  
1244    function runERT() {
1245      if (!$.fn.estimatedReadingTime) return;
1246      var $target = $("#news-full-details");
1247      if (!$target.length) return;
1248      $target.estimatedReadingTime();
1249    }
1250  
1251    function scheduleERT(delay) {
1252      clearTimeout(ertTimer);
1253      ertTimer = setTimeout(runERT, delay || 0);
1254    }
1255  
1256    $(function () {
1257      scheduleERT(3000);
1258  
1259      $(document).on("click", ".rz-news-card .rz-news-button", function () {
1260        scheduleERT(300);
1261      });
1262    });
1263  })(jQuery);
1264
1265/* ===================== 
1266 back to top 
1267===================== */
1268  $(window).scroll(function () {
1269    if ($(this).scrollTop() > 500) {
1270      $("#back-to-top").css({
1271        opacity: 1,
1272        transform: "translateX(-1rem)",
1273        transition: "all .5s",
1274      });
1275    } else {
1276      $("#back-to-top").css({
1277        opacity: 0,
1278        transform: "translateX(100px)",
1279        transition: "all .5s",
1280      });
1281    }
1282  });
1283  
1284  $($("a[href='#top']")).click(function () {
1285    $("body,html").animate(
1286    {
1287      scrollTop: 0,
1288    },
1289    800
1290    );
1291    return false;
1292  });
1293
1294/* ===================== 
1295 clone elements in header to offcanvas for mobile
1296  ===================== */
1297// old
1298 // var isClonedHeaderElement = false;
1299 // $(window).on("load resize", function() {
1300 //   if($window.width() < 992) {
1301 //     if(!isClonedHeaderElement) {
1302 //       $('.site-header-wrap .move-to-offcanvas').clone().addClass('d-flex').appendTo('.offcanvasHeaderItems-offcanvas-body');
1303 //       isClonedHeaderElement = true;
1304 //     }
1305 //   }
1306 //   else {
1307 //     if(isClonedHeaderElement) {
1308 //       $('.offcanvasHeaderItems-offcanvas-body .move-to-offcanvas').remove();
1309 //       isClonedHeaderElement = false;
1310 //     }
1311 //   }
1312 // });
1313var isClonedHeaderElement = false;
1314 var savedInlineStyles = new Map();
1315 
1316 $(window).on("load resize", function () {
1317   var isMobile = window.matchMedia("(max-width: 991.98px)").matches;
1318 
1319   if (isMobile) {
1320     if (!isClonedHeaderElement) {
1321       var $toClone = $('.site-header-wrap .move-to-offcanvas');
1322 
1323       $toClone.add($toClone.find('*')).each(function () {
1324         var $el = $(this);
1325         var style = $el.attr('style');
1326         if (style) {
1327           savedInlineStyles.set(this, style);
1328           $el.removeAttr('style');
1329         }
1330       });
1331 
1332       var $clone = $toClone.clone(true, true).addClass('d-flex');
1333       $clone.appendTo('.offcanvasHeaderItems-offcanvas-body');
1334       isClonedHeaderElement = true;
1335     }
1336   } else {
1337     if (isClonedHeaderElement) {
1338       $('.offcanvasHeaderItems-offcanvas-body .move-to-offcanvas').remove();
1339       isClonedHeaderElement = false;
1340 
1341       savedInlineStyles.forEach(function (style, el) {
1342         $(el).attr('style', style);
1343       });
1344       savedInlineStyles.clear();
1345     }
1346   }
1347 });
1348
1349// submenu AKA flyout
1350$(function () {
1351  var isClonedSubnavElement = false;
1352
1353  function handleSubnav() {
1354    if (window.matchMedia('(max-width: 991.98px)').matches) {
1355      if (!isClonedSubnavElement) {
1356        $('#submenu').clone().appendTo('#offcanvasNav .offcanvas-body');
1357        $('#offcanvasNav .offcanvas-body > #submenu')
1358          .prepend('<h5 class="px-3 py-4 mt-2 m-0 w-100">Sub Menu</h5>');
1359        isClonedSubnavElement = true;
1360      }
1361    } else {
1362      if (isClonedSubnavElement) {
1363        $('#offcanvasNav .offcanvas-body > #submenu').remove();
1364        isClonedSubnavElement = false;
1365      }
1366    }
1367  }
1368
1369  $(window).on('load resize', handleSubnav);
1370});
1371
1372
1373(function () {
1374  var root = document.getElementById('submenu');
1375  if (!root) return;
1376  if (!(window.bootstrap && bootstrap.Dropdown)) return;
1377
1378  root.classList.add('nav');
1379  root.style.display = 'flex';
1380  root.style.flexWrap = 'wrap';
1381
1382  var dropdowns = new Map();
1383
1384  Array.from(root.querySelectorAll(':scope > li')).forEach(function (li) {
1385    li.classList.add('nav-item', 'position-relative');
1386    li.style.display = 'flex';
1387    li.style.alignItems = 'center';
1388
1389    var aMain = li.querySelector(':scope > a');
1390    var menu  = li.querySelector(':scope > ul');
1391    if (!aMain) return;
1392
1393    aMain.classList.add('nav-link');
1394    aMain.style.flex = '1 1 auto';
1395
1396    if (!menu) return;
1397
1398    li.classList.add('dropdown' ,'dropend');
1399
1400    var toggler = document.createElement('a');
1401    toggler.href = '#';
1402    toggler.className = 'nav-link dropdown-toggle dropdown-toggle-split ms-auto w-auto d-flex align-items-center justify-content-center px-lg-3';
1403    toggler.setAttribute('role', 'button');
1404    toggler.setAttribute('data-bs-toggle', 'dropdown');
1405    toggler.setAttribute('aria-expanded', 'false');
1406    toggler.setAttribute('aria-label', 'Toggle submenu');
1407    toggler.style.flex = '0 0 auto';
1408    aMain.insertAdjacentElement('afterend', toggler);
1409
1410    menu.classList.add('dropdown-menu');
1411    menu.classList.remove('menuHidden');
1412
1413    Array.from(menu.querySelectorAll(':scope > li')).forEach(function (subLi) {
1414      subLi.classList.remove('menuHidden');
1415      var subA = subLi.querySelector(':scope > a');
1416      if (subA) subA.classList.add('dropdown-item');
1417    });
1418
1419    var dd = new bootstrap.Dropdown(toggler, { autoClose: 'outside' });
1420    dropdowns.set(toggler, dd);
1421
1422    toggler.addEventListener('click', function (e) {
1423      e.preventDefault();
1424      dd.toggle();
1425    });
1426
1427    aMain.addEventListener('click', function (e) {
1428      if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
1429      var href = aMain.getAttribute('href');
1430      if (!href || href === '#' || href.toLowerCase().startsWith('javascript:')) return;
1431      e.stopPropagation();
1432      window.location.href = href;
1433    }, true);
1434
1435    li.addEventListener('mouseenter', function () {
1436      dropdowns.forEach(function (d, other) { if (other !== toggler) d.hide(); });
1437      dd.show();
1438    });
1439    li.addEventListener('mouseleave', function (ev) {
1440      if (!li.contains(ev.relatedTarget)) dd.hide();
1441    });
1442  });
1443
1444  root.addEventListener('show.bs.dropdown', function (ev) {
1445    var tg = ev.target;
1446    dropdowns.forEach(function (dd, otherTg) { if (otherTg !== tg) dd.hide(); });
1447  });
1448})();
1449
1450
1451
1452
1453/* ===================== 
1454 E-Notify Auto submit
1455===================== */  
1456  $.urlParam = function (n) {
1457    var e = new RegExp("[?&]" + n + "=([^]*)").exec(window.location.href);
1458    return null == e ? null : e[1] || 0;
1459  };
1460  var $enotify = $('iframe[src*="/revize/plugins/notify/notify.jsp"]');
1461  if ($enotify.length > 0) {
1462    var emailStr = $.urlParam("email");
1463    if (emailStr != null) {
1464      $enotify.attr("src", $enotify.attr("src") + "&email=" + emailStr);
1465    }
1466  }
1467
1468/* ===================== 
1469 Make sure all calendars have unique ids 
1470===================== */
1471  $('iframe[name="calendar"]').each(function (index, calendar) {
1472    calendar.id = "calendar-" + index;
1473  });
1474
1475/* ===================== 
1476 Start Frame Resizer 
1477===================== */
1478// Function definition: resizeIframe
1479  function resizeIframe(height, frameElement) {
1480    if (frameElement) {
1481      frameElement.height = "";
1482      frameElement.height = height;
1483    }
1484  }
1485  
1486  var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
1487  var eventHandler = window[eventMethod];
1488  var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
1489  eventHandler(messageEvent, function (e) {
1490    if (Array.isArray(e.data)) {
1491      if (e.data[0] === "setCalHeight" || e.data[0] === "setNotifyHeight") {
1492        var frames = document.querySelectorAll("iframe");
1493        for (var i = 0; i < frames.length; i++) {
1494          if (frames[i].contentWindow === e.source) {
1495            resizeIframe(e.data[1], frames[i]);
1496          }
1497        }
1498      }
1499    }
1500  });  
1501
1502/* ===================== 
1503 Alert Close Caching 
1504===================== */
1505  if ($("div.alert").length) {
1506    var hide = window.sessionStorage && parseInt(window.sessionStorage.getItem("alertClosed")) > 1;
1507    if (!hide || $(".user-logged-in").length != 0) {
1508      $("div.alert").addClass("show");
1509    }
1510    $("div.alert .close-alert").on("click", function (e) {
1511      if (window.sessionStorage) {
1512        window.sessionStorage.setItem("alertClosed", parseInt(window.sessionStorage.getItem("alertClosed") || 0) + 1);
1513      }
1514    });
1515  }
1516
1517/* ===================== 
1518 revizeWeather #todo check on live server 
1519===================== */
1520  if (typeof $.fn.revizeWeather !== "undefined") {
1521    (function() {
1522    const zip = (document.body.getAttribute('data-zipcode') || '').trim();
1523    if (!zip) { $('.header-weather-wrap').hide(); return; }
1524  
1525    $.fn.revizeWeather({
1526      zip: zip,
1527      city_name: '',
1528      unit: 'f',
1529      success: function(weather) {
1530        const d = new Date();
1531        const parts = new Intl.DateTimeFormat('en-US', { month: 'long', day: 'numeric', year: 'numeric' }).formatToParts(d);
1532        const month = parts.find(p => p.type === 'month')?.value || '';
1533        const day   = parts.find(p => p.type === 'day')?.value || '';
1534        const year  = parts.find(p => p.type === 'year')?.value || '';
1535        const iso   = d.toISOString().split('T')[0];
1536  
1537        const unit = (weather.unit || 'F').toUpperCase() === 'C' ? 'C' : 'F';
1538        const unitLong = unit === 'C' ? 'degrees Celsius' : 'degrees Fahrenheit';
1539        const dateLabel = `${month} ${day}, ${year}`;
1540  
1541        const html =
1542          '<time class="weather-date" datetime="'+ iso +'" aria-label="'+ dateLabel +'">' +
1543            '<span class="weather-month">' + month + '</span> ' +
1544            '<span class="weather-day">' + day + '</span>, ' +
1545            '<span class="weather-year">' + year + '</span>' +
1546          '</time>' +
1547          '<div class="weather-details d-flex align-items-center gap-2" role="group" aria-label="Current weather">' +
1548            '<span class="weather-temp" aria-label="Temperature ' + weather.temp + ' ' + unitLong + '">' +
1549              weather.temp +
1550              '<span aria-hidden="true">°</span>' +
1551              '<span class="sr-only"> ' + unitLong + '</span>' +
1552            '</span>' +
1553            '<span class="weather-forecast" aria-label="Conditions ' + weather.forecast + '"> ' +
1554              weather.forecast +
1555            '</span>' +
1556            '<i class="fst-normal weather-icon fs-1 ' + weather.icon + '" aria-hidden="true"></i>' +
1557          '</div>';
1558  
1559        $('#weather').html(html);
1560      },
1561      error: function(err) {
1562        $('.header-weather-wrap').hide();
1563        console.log(err);
1564      }
1565    });
1566  })();
1567  }
1568
1569// Function definition: removeCookieString
1570function removeCookieString(value, domain) {
1571  domain = domain ? "domain=" + domain + "; " : "";
1572  return value + "; expires=Thu, 01 Jan 1970 00:00:01 GMT; " + domain + "path=/";
1573}
1574
1575// Function definition: unsetGoogtransCookie
1576function unsetGoogtransCookie() {
1577  for (var domains = window.location.hostname.split("."); domains.length >= 2; domains.shift()) {
1578    var cookieString = removeCookieString("googtrans=unset", domains.join("."));
1579    document.cookie = cookieString;
1580    if (domains.length === 2) {
1581      cookieString = removeCookieString("googtrans=unset", "." + domains.join("."));
1582      document.cookie = cookieString;
1583    }
1584  }
1585  document.cookie = removeCookieString("googtrans=unset");
1586}
1587
1588if (
1589document.cookie.split(";").some(function (item) {
1590  return item.trim().startsWith("googtrans=/auto/en");
1591})
1592) {
1593  unsetGoogtransCookie();
1594}
1595
1596/* ===================== 
1597 Masonry Deferred
1598===================== */
1599window.RZMasonry = function(el) {
1600  var masonryAttr = el.dataset['masonryDeferred'];
1601  var masonryOpts = {};
1602  
1603  if (masonryAttr) {
1604	try {
1605	  masonryOpts = JSON.parse(masonryAttr);
1606	} catch (e) {
1607	  // Bummer
1608	}
1609  }
1610  new Masonry(el, masonryOpts);
1611}
1612
1613/* ===================== 
1614 Tiny Slider 
1615===================== */
1616if (typeof tns !== "undefined") {
1617  
1618  window.RZTinyCarousel = function(el) {
1619    var $el = $(el);
1620    var tinyItemCount = $el.children().length;
1621  
1622    if (tinyItemCount === 0) return;
1623  
1624    var tinyItemData = $el.data("tinyItems") || 1;
1625    var tinyItemDataSm = $el.data("tinyItemsSm") || 1;
1626    var tinyItemDataMd = $el.data("tinyItemsMd") || 1;
1627    var tinyGapData = $el.data("tinyGap") || 0;
1628    var tinyNavData = $el.data("tiny-nav");
1629    var tinyControlsData = $el.data("tiny-controls");
1630    var tinyAutoplayButtonsData = $el.data("tiny-autoplay-buttons");
1631    var tinyAutoplayData = $el.data("tiny-autoplay");
1632    var tinyEdgePaddingData = $el.data("tiny-padding-x");
1633    var tinyModeData = $el.data("tiny-mode");
1634    var tinySpeed = $el.data("tiny-speed") || 300;
1635  
1636    var tinyCar = tns({
1637      container: el,
1638      rewind: true,
1639      items: Math.min(tinyItemCount, tinyItemData),
1640      animateIn: "fadeIn",
1641      animateOut: "fadeOut",
1642      speed: tinySpeed,
1643      responsive: {
1644        0: {
1645          items: Math.min(tinyItemCount, tinyItemDataSm),
1646        },
1647        768: {
1648          items: Math.min(tinyItemCount, tinyItemDataMd),
1649        },
1650        1200: {
1651          items: Math.min(tinyItemCount, tinyItemData),
1652        },
1653      },
1654      lazyload: true,
1655      nav: tinyNavData,
1656      center: false,
1657      gutter: tinyGapData,
1658      edgePadding: tinyEdgePaddingData,
1659      lazyloadSelector: ".tns-lazy-img",
1660      preventScrollOnTouch: "auto",
1661      controls: tinyControlsData,
1662      autoplay: tinyAutoplayData,
1663      mouseDrag: true,
1664      autoplayButtonOutput: tinyAutoplayButtonsData,
1665      mode: tinyModeData,
1666      autoplayText: [
1667        '<i class="bi bi-play-fill"></i>',
1668        '<i class="bi bi-pause-fill"></i>'
1669      ],
1670      controlsText: [
1671        '<i class="bi bi-chevron-left" aria-hidden="true"></i>',
1672        '<i class="bi bi-chevron-right" aria-hidden="true"></i>'
1673      ],
1674    });
1675    
1676    // Mark prev / next nav dots
1677    if (tinyCar && tinyCar.events) {
1678      tinyCar.events.on('indexChanged', function () {
1679        var info = tinyCar.getInfo();
1680        var nav = info && info.navContainer;
1681        if (!nav) return;
1682    
1683        var dots = nav.querySelectorAll('button');
1684        dots.forEach(function (b) {
1685          b.classList.remove('is-prev', 'is-next');
1686        });
1687    
1688        var active = nav.querySelector('.tns-nav-active');
1689        if (!active) return;
1690    
1691        if (active.previousElementSibling) {
1692          active.previousElementSibling.classList.add('is-prev');
1693        }
1694    
1695        if (active.nextElementSibling) {
1696          active.nextElementSibling.classList.add('is-next');
1697        }
1698      });
1699    }
1700
1701    if (tinyCar && typeof tinyCar.getInfo === 'function') {
1702      var navControls = tinyCar.getInfo().navContainer;
1703    
1704      $(navControls).parent().find(".tns-nav,[data-action]").wrapAll('<div class="nav-controls absolute"></div>');
1705    
1706      // Auto-center the active dot on mobile (NO page scroll)
1707      function centerActiveTinyDot(tinyCar) {
1708      
1709        var info = tinyCar.getInfo();
1710        var nav = info && info.navContainer;
1711        if (!nav || nav.scrollWidth <= nav.clientWidth) return;
1712      
1713        var active = nav.querySelector('.tns-nav-active');
1714        if (!active) return;
1715      
1716        requestAnimationFrame(function () {
1717          var navRect = nav.getBoundingClientRect();
1718          var dotRect = active.getBoundingClientRect();
1719      
1720          var current = nav.scrollLeft;
1721          var delta = (dotRect.left - navRect.left) - (nav.clientWidth / 2) + (dotRect.width / 2);
1722          var next = current + delta;
1723      
1724          next = Math.max(0, Math.min(next, nav.scrollWidth - nav.clientWidth));
1725          nav.scrollTo({ left: next, behavior: 'smooth' });
1726        });
1727      }
1728      
1729      // attach
1730      tinyCar.events.on('indexChanged', function () { centerActiveTinyDot(tinyCar); });
1731      // optionally more stable:
1732      tinyCar.events.on('transitionEnd', function () { centerActiveTinyDot(tinyCar); });
1733      
1734      // also center on load (after tns builds nav)
1735      setTimeout(function(){ centerActiveTinyDot(tinyCar); }, 0);
1736
1737    }
1738  };
1739  
1740  $(".tiny-carousel,.tiny-slider").not(".tiny-deferred").each(function (i, el) {
1741    window.RZTinyCarousel(el);
1742  });
1743  
1744  $(".iwantto-slider").each(function (i, el) {
1745    var $el = $(el);
1746    var tinyItemCount = $el.children().length;
1747    var tinyItemData = $el.data("tinyItems") ? $el.data("tinyItems") : 1;
1748    var tinyItemDataSm = $el.data("tinyItemsSm") ? $el.data("tinyItemsSm") : 1;
1749    var tinyItemDataMd = $el.data("tinyItemsMd") ? $el.data("tinyItemsMd") : 1;
1750    var tinyGapData = $el.data("tinyGap") ? $el.data("tinyGap") : 0;
1751    var tinyNavData = $(el).data("tiny-nav");
1752    var tinyControlsData = $(el).data("tiny-controls");
1753    var tinyAutoplayButtonsData = $(el).data("tiny-autoplay-buttons");
1754    var tinyAutoplayData = $(el).data("tiny-autoplay");
1755    var tinyEdgePaddingData = $(el).data("tiny-padding-x");
1756    
1757    tns({
1758      container: el,
1759      items: Math.min(tinyItemCount, tinyItemData),
1760      responsive: {
1761        0: {
1762          items: Math.min(tinyItemCount, tinyItemDataSm),
1763        },
1764        768: {
1765          items: Math.min(tinyItemCount, tinyItemDataMd),
1766        },
1767        1200: {
1768          items: Math.min(tinyItemCount, tinyItemData),
1769        },
1770      },
1771      nav: tinyNavData,
1772      loop: false,
1773      axis:"vertical",
1774      center: true,
1775      gutter: Math.min(tinyGapData, tinyGapData),
1776      edgePadding: tinyEdgePaddingData,
1777      preventScrollOnTouch: "auto",
1778      controls: tinyControlsData,
1779      autoplay: tinyAutoplayData,
1780      autoplayButtonOutput: tinyAutoplayButtonsData,
1781      autoplayText: ['<i class="bi bi-play-fill"></i>', '<i class="bi bi-pause-fill"></i>'],
1782      controlsText: ['<span class="bi bi-chevron-left" aria-hidden="true"></span>Prev', 'Next<span class="bi bi-chevron-right" aria-hidden="true"></span>'],
1783    });
1784  });
1785}
1786/* ===================== 
1787 Sets aria-labels for carousel buttons (Previous/Next) 
1788===================== */
1789(function () {
1790  function applyTnsA11yFix(ctx) {
1791    const $ctx = ctx ? $(ctx) : $(document);
1792
1793    $ctx.find('.tns-controls, .tns-controls button')
1794      .removeAttr('tabindex')
1795      .removeAttr('aria-label');
1796
1797    $ctx.find('.tns-controls button[data-controls="prev"]')
1798      .attr('aria-label', 'Previous Slide');
1799
1800    $ctx.find('.tns-controls button[data-controls="next"]')
1801      .attr('aria-label', 'Next Slide');
1802
1803    $ctx.find('.tns-liveregion')
1804      .attr('aria-live', 'off')     
1805      .attr('aria-atomic', 'false');
1806  }
1807
1808  $(applyTnsA11yFix);
1809
1810  const mo = new MutationObserver(muts => {
1811    for (const m of muts) {
1812      if (!m.addedNodes || !m.addedNodes.length) continue;
1813      for (const n of m.addedNodes) {
1814        if (!(n instanceof HTMLElement)) continue;
1815        if (
1816          n.classList?.contains('tns-controls') ||
1817          n.classList?.contains('tns-liveregion') ||
1818          n.querySelector?.('.tns-controls, .tns-liveregion')
1819        ) {
1820          applyTnsA11yFix(n);
1821        }
1822      }
1823    }
1824  });
1825
1826  mo.observe(document.body, { childList: true, subtree: true });
1827})();
1828
1829// ADA - Prevent focus from offscreen elements, use slider controls to bring more elements into view
1830// #todo 
1831
1832    
1833/* ===================== 
1834 AOS Animations 
1835===================== */
1836AOS.init({
1837  disable: "phone, tablet, mobile", // accepts following values: 'phone', 'tablet', 'mobile', boolean, expression or function
1838  startEvent: "DOMContentLoaded", // name of the event dispatched on the document, that AOS should initialize on
1839  initClassName: "aos-init", // class applied after initialization
1840  animatedClassName: "aos-animate", // class applied on animation
1841  useClassNames: false, // if true, will add content of `data-aos` as classes on scroll
1842  disableMutationObserver: false, // disables automatic mutations' detections (advanced)
1843  debounceDelay: 50, // the delay on debounce used while resizing window (advanced)
1844  throttleDelay: 99, // the delay on throttle used while scrolling the page (advanced)
1845
1846/* ===================== 
1847 Settings that can be overridden on per-element basis, by `data-aos-*` attributes: 
1848===================== */
1849  offset: 0, // offset (in px) from the original trigger point
1850  delay: 400, // values from 0 to 3000, with step 50ms
1851  duration: 400, // values from 0 to 3000, with step 50ms
1852  easing: "ease", // default easing for AOS animations
1853  once: true, // whether animation should happen only once - while scrolling down
1854  mirror: false, // whether elements should animate out while scrolling past them
1855  anchorPlacement: "top-bottom", // defines which position of the element regarding to window should trigger the animation
1856});
1857
1858document.addEventListener('DOMContentLoaded', () => {
1859  const baseDelay = 100;
1860  const letterDelay = 100;
1861  const duration = 1000; // << SLOWER animation!
1862
1863  document.querySelectorAll('.stagger-fadeup').forEach(el => {
1864    const text = el.textContent.trim();
1865    const animation = el.getAttribute('data-aos') || 'fade-up';
1866    let html = '';
1867    let idx = 0;
1868
1869    for (const char of text) {
1870      if (char === ' ') {
1871        html += ' ';
1872      } else {
1873        const delay = baseDelay + idx * letterDelay;
1874        html += `<span style="opacity:1;overflow:hidden;line-height:1;display:inline-block"><span style="opacity:1;overflow:hidden;display:inline-block;" data-aos="${animation}" data-aos-delay="${delay}" data-aos-duration="${duration}" class="aos-init">${char}</span></span>`;
1875        idx++;
1876      }
1877    }
1878    el.innerHTML = html;
1879  });
1880
1881  if (window.AOS) {
1882    AOS.refreshHard();
1883  }
1884});
1885
1886/* ===================== 
1887 Lazy load 
1888===================== */
1889$(function () {
1890  $(".lazy,.rz-card-img,.rz-gallery-image img,.rz-qlink-img,.rz-tile-img,.featured-image-wrap").lazy();
1891});
1892
1893/* ===================== 
1894 Responsive Tables 
1895===================== */
1896$(".post table:not(.layout-table):not(.not-responsive)").wrap('<div class="table-responsive"></div>');
1897$(".layout-table").attr("role", "presentation");
1898
1899/* ===================== 
1900 Preloader 
1901===================== */
1902$window.on("load", function () {
1903  setTimeout(function () {
1904    $body.addClass("loaded");
1905    $(".loader-wrapper").fadeOut();
1906  }, 600);
1907});
1908
1909/* ===================== 
1910 Smooth Scroll
1911===================== */
1912$window.ready(function () {
1913  //#Smooth Scrolling
1914  $('a[href*="#"]')
1915  .not('[href="#"]')
1916  .not('[href*="#collapse"]')
1917  .click(function (event) {
1918    if (location.pathname.replace(/^\//, "") === this.pathname.replace(/^\//, "") && location.hostname === this.hostname) {
1919      var target = $(this.hash);
1920      target = target.length ? target : $("[name=" + this.hash.slice(1) + "]");
1921      if (target.length) {
1922        event.preventDefault();
1923        $("html,body").animate(
1924        {
1925          scrollTop: target.offset().top,
1926        },
1927        1000,
1928        function () {
1929          var $target = $(target);
1930          $target.focus();
1931          if ($target.is(":focus")) {
1932            return false;
1933          } else {
1934            $target.attr("tabindex", "-1");
1935            $target.focus();
1936            $target.removeAttr("tabindex");
1937          }
1938        }
1939        );
1940      }
1941    }
1942  });
1943
1944//play button in video hero
1945document.querySelectorAll('button.play-toggle[role="switch"]').forEach(function(btn) {
1946    // Prefer existing aria-pressed value, fallback to aria-checked, else set to "false"
1947    let checked = "false";
1948    if (btn.hasAttribute('aria-pressed')) {
1949      checked = btn.getAttribute('aria-pressed') === "true" ? "true" : "false";
1950      btn.removeAttribute('aria-pressed');
1951    } else if (btn.hasAttribute('aria-checked')) {
1952      checked = btn.getAttribute('aria-checked') === "true" ? "true" : "false";
1953    }
1954    btn.setAttribute('aria-checked', checked);
1955  
1956    // OPTIONAL: Toggle aria-checked on click for demonstration
1957    btn.addEventListener('click', function() {
1958      let newChecked = btn.getAttribute('aria-checked') === "true" ? "false" : "true";
1959      btn.setAttribute('aria-checked', newChecked);
1960    });
1961  });
1962
1963/* ===================== 
1964 Removes aria-live attribute which reads to screen reader when slide is changed. Also sets aria-hidden to true so this element 
1965===================== */
1966  // is ignored from the dom. - Michael S.
1967  $(".tns-liveregion.tns-visually-hidden").attr("aria-hidden", "true").removeAttr("aria-live");
1968  
1969  }); // Ready
1970})(jQuery);
1971
1972
1973// nav 1.0
1974// Get the threshold from data-mega-at on .site-header-wrap
1975const megaAt = parseInt($('.site-header-wrap').attr('data-mega-at'), 10) || 8;
1976
1977// nav 1.0
1978$("#nav > li > ul").each(function () {
1979  const $submenu = $(this);
1980  const $parentLi = $submenu.parent();
1981  const itemCount = $submenu.find("li").length;
1982
1983  if (itemCount >= megaAt) {
1984
1985    /* ===================== 
1986     Mega menu 
1987    ===================== */
1988    $submenu
1989      .addClass("make-mega dropdown-menu")
1990      .wrapInner('<li class="mega-wrapper d-lg-flex gap-3 w-100"><div class="mega-wrap w-100"><ul class="mega-wrap-inner bg-transparent"></ul></div></li>');
1991    $parentLi.addClass("static dropdown dropdown-center");
1992
1993  } else {
1994
1995    /* ===================== 
1996     Regular dropdown 
1997    ===================== */
1998    $submenu.addClass("normal-menu dropdown-menu");
1999    $parentLi.addClass("relative dropdown");
2000  }
2001});
2002
2003
2004/* ===================== 
2005 Handle nested submenus inside normal-menu items 
2006===================== */
2007  $(".normal-menu ul").each(function () {
2008    const $nestedMenu = $(this);
2009    const $parentLi = $nestedMenu.parent();
2010    $nestedMenu.addClass("dropdown-menu menu-level-3");
2011    $parentLi.addClass("dropdown static dropend");
2012  });
2013
2014/* ===================== 
2015 Apply Bootstrap dropdown behavior to all dropdown links 
2016===================== */
2017  $("#nav .dropdown > a")
2018  .addClass("dropdown-toggle")
2019  .attr({
2020    'data-bs-toggle': "dropdown",
2021    'data-bs-auto-close': "outside",
2022    "aria-expanded": "false",
2023    'data-bs-offset':"0,0"
2024  });
2025
2026(function applyNavToggles() {
2027    function applyToggles() {
2028      const $spans = $("#nav .dropdown > span");
2029      if (!$spans.length) return;
2030  
2031      $spans
2032        .addClass("dropdown-toggle")
2033        .attr({
2034          "data-bs-toggle": "dropdown",
2035          "data-bs-auto-close": "outside",
2036          "aria-expanded": "false",
2037          "data-bs-offset": "0,0",
2038          "role": "button",
2039          "tabindex": "0"
2040        });
2041    }
2042  
2043    applyToggles();
2044  
2045    const nav = document.getElementById("nav");
2046    const root = nav ? nav : document.body;
2047  
2048    const obs = new MutationObserver(() => applyToggles());
2049    obs.observe(root, { childList: true, subtree: true });
2050  })();
2051    
2052// Function definition: initDropdowns
2053  function initDropdowns() {
2054    if (window.innerWidth < 992) return;
2055    
2056    const nav = document.getElementById('nav');
2057    
2058    document.querySelectorAll('#nav .dropdown-toggle').forEach((toggle) => {
2059      const li = toggle.closest('li');
2060      const menu = toggle.nextElementSibling;
2061      
2062      if (!menu || !menu.classList.contains('dropdown-menu')) return;
2063
2064/* ===================== 
2065 Temporarily show the dropdown menu to measure its width 
2066===================== */
2067      menu.style.visibility = 'hidden';
2068      menu.style.display = 'block';
2069      
2070      const dropdownWiderThanNav = menu.offsetWidth > nav.offsetWidth;
2071      const normalMenu = toggle.closest('.normal-menu');
2072
2073/* ===================== 
2074 Restore visibility 
2075===================== */
2076      menu.style.display = '';
2077      menu.style.visibility = '';
2078
2079/* ===================== 
2080 Determine correct reference element 
2081===================== */
2082      let referenceEl;
2083      if (dropdownWiderThanNav) {
2084        referenceEl = nav;
2085      } else if (normalMenu) {
2086        referenceEl = normalMenu;
2087      } else {
2088        referenceEl = li;
2089      }
2090      
2091      new bootstrap.Dropdown(toggle, {
2092        reference: referenceEl,
2093        popperConfig: (defaultBsPopperConfig) => {
2094          return {
2095            ...defaultBsPopperConfig,
2096            modifiers: [
2097            ...(defaultBsPopperConfig.modifiers || []),
2098            {
2099              name: 'preventOverflow',
2100              options: {
2101                boundary: 'viewport',
2102                padding: 0
2103              }
2104            }
2105            ]
2106          };
2107        }
2108      });
2109    });
2110  }
2111
2112/* ===================== 
2113 Run on load if ≥ 992 
2114===================== */
2115  initDropdowns();
2116
2117/* ===================== 
2118 Re-run on resize when crossing the 992px threshold 
2119===================== */
2120  let wasWide = window.innerWidth >= 992;
2121  
2122  window.addEventListener('resize', () => {
2123    const isWide = window.innerWidth >= 992;
2124    if (isWide && !wasWide) {
2125      initDropdowns();
2126    }
2127    wasWide = isWide;
2128  });
2129
2130  document.addEventListener("DOMContentLoaded", function(){
2131
2132/* ===================== 
2133 make it as accordion for smaller screens 
2134===================== */
2135    if (window.innerWidth > 992) {
2136      
2137      document.querySelectorAll('.navbar .dropdown').forEach(function(everyitem){
2138        
2139        everyitem.addEventListener('mouseover', function(e){
2140          
2141          let el_link = this.querySelector('a[data-bs-toggle]');
2142          
2143          if(el_link != null){
2144            let nextEl = el_link.nextElementSibling;
2145            el_link.classList.add('show');
2146            nextEl.classList.add('show');
2147          }
2148          
2149        });
2150        everyitem.addEventListener('mouseleave', function(e){
2151          let el_link = this.querySelector('a[data-bs-toggle]');
2152          
2153          if(el_link != null){
2154            let nextEl = el_link.nextElementSibling;
2155            el_link.classList.remove('show');
2156            nextEl.classList.remove('show');
2157          }
2158          
2159          
2160        })
2161      });
2162      
2163    }
2164
2165/* ===================== 
2166 end if innerWidth 
2167===================== */
2168  });
2169
2170/* ===================== 
2171 DOMContentLoaded  end 
2172===================== */
2173  
2174
2175/* ===================== 
2176 Add 'navbar' class to #nav if menu-onhover is enabled 
2177===================== */
2178  // $('[data-menu-onhover="true"] #nav').addClass('navbar');
2179$(document).ready(function () {
2180    function initMenuHover() {
2181      // Remove previous bindings
2182      $('#nav .dropdown').off('mouseenter mouseleave click');
2183  
2184      if ($('.site-header-wrap').attr('data-menu-onhover') === 'true' && $(window).width() > 992) {
2185        // Hover behavior
2186        $('#nav .dropdown').hover(
2187          function () {
2188            $(this).children('[data-bs-toggle="dropdown"]').dropdown('show');
2189          },
2190          function () {
2191            $(this).children('[data-bs-toggle="dropdown"]').dropdown('hide');
2192          }
2193        );
2194  
2195        // Make parent link clickable
2196        $('#nav .dropdown > a[data-bs-toggle="dropdown"]').on('click', function (e) {
2197          var href = $(this).attr('href');
2198          if (href && href !== '#') {
2199            window.location = href; // Go to the link
2200          }
2201        });
2202      }
2203    }
2204  
2205    initMenuHover();
2206    $(window).on('resize', initMenuHover);
2207  });
2208
2209/* ===================== 
2210 Clone .mega-button elements into their matching mega menu slots 
2211===================== */
2212  const $headerItems = $('#nav > li').not('.nav-border');
2213  
2214  $('.mega-button').each(function () {
2215    const index = $(this).index();
2216    $(this).clone().appendTo(
2217    $headerItems.eq(index).find('.make-mega .mega-wrapper')
2218    );
2219  });
2220
2221/* ===================== 
2222 Create hover border effect under nav items 
2223===================== */
2224  const $siteHeaderNav = $('#nav');
2225
2226 // Only add the border element if it doesn't exist 
2227  if (!$siteHeaderNav.find('.nav-border').length) {
2228    $('<li class="nav-border d-none d-lg-block"></li>').appendTo($siteHeaderNav);
2229  }
2230  
2231  const $border = $('.nav-border');
2232  let borderInitialized = false;
2233
2234 // Set up hover behavior for nav items 
2235  $headerItems.each(function () {
2236    $(this).on('mouseover', function () {
2237      const elRect = this.getBoundingClientRect();
2238      const parentRect = this.parentNode.getBoundingClientRect();
2239      
2240      const left = elRect.left - parentRect.left;
2241      const width = elRect.width;
2242      const paddingOffset = 20; // total 10px on each side
2243      
2244      $border.css({
2245        opacity: 1,
2246        width: width - paddingOffset,
2247        transform: `translateX(${left}px)`
2248      });
2249    });
2250    
2251    $(this).on('mouseout', function () {
2252      $border.css({
2253        opacity: 0,
2254        width: 0
2255      });
2256    });
2257
2258 // Set initial hover position on first item 
2259    if (!borderInitialized) {
2260      $(this).trigger('mouseover').trigger('mouseout');
2261      borderInitialized = true;
2262    }
2263  });
2264  
2265  $('#nav .dropdown-toggle').each(function(){
2266    // Clone the current dropdown-toggle
2267    let clonedToggle = $(this)
2268      .clone()
2269      .addClass('dropdown-header icon-link icon-link-hover d-flex align-items-center')
2270      .removeClass('dropdown-toggle')
2271      .removeAttr('data-bs-toggle data-bs-auto-close aria-expanded data-bs-offset')
2272      .append('<i class="bi bi-arrow-right d-flex align-items-center"></i>');
2273  
2274    // Wrap cloned toggle in <li>
2275    let liWrapper = $('<li class="dropdown-header-item"></li>').append(clonedToggle);
2276  
2277    // Find the dropdown menu
2278    let nextDropdownMenu = $(this).closest('.dropdown').find('.dropdown-menu').first();
2279  
2280    // Prepend the new <li> into the dropdown
2281    if (nextDropdownMenu.length) {
2282      nextDropdownMenu.prepend(liWrapper);
2283    }
2284  });
2285
2286// offcanvas links
2287  const OPENERS = {
2288    jobDetails:       (id) => window.openJobDetails?.(id),
2289    bidDetails:       (id) => window.openBidDetails?.(id),
2290    newsDetails:      (id) => window.openNewsDetails?.(id),
2291    catalogueDetails: (id) => window.openCatalogueDetails?.(id),
2292  };
2293  
2294  const u   = () => new URL(location.href);
2295  const get = (k) => u().searchParams.get(k);
2296  const set = (url,k,v) => (v==null||v==='') ? url.searchParams.delete(k) : url.searchParams.set(k,v);
2297  
2298  let SUPPRESS = false;
2299  
2300  window.pushOcState = function(ocId, id) {
2301    if (SUPPRESS) return;
2302    const url = u();
2303    set(url, 'rz', ocId);
2304    set(url, 'id', (id!=null && id!=='') ? String(id) : '');
2305    history.pushState({ oc: ocId, id: String(id || '') }, '', url);
2306  };
2307  
2308  function tryDeepLinkNow(ocId) {
2309    const url = u();
2310    const oc  = url.searchParams.get('rz');
2311    const id  = url.searchParams.get('id');
2312    if (oc === ocId && id) {
2313      SUPPRESS = true;
2314      OPENERS[ocId]?.(String(id));
2315      setTimeout(() => { SUPPRESS = false; }, 0);
2316    }
2317  }
2318  
2319  // Deep link ?rz=<offcanvasId>&id=<recordId>
2320  function applyDeepLink() {
2321    const ocId = get('rz');
2322    const recId = get('id');
2323    if (!ocId) return;
2324  
2325    SUPPRESS = true;
2326    if (OPENERS[ocId] && recId) {
2327      OPENERS[ocId](recId);
2328    } else {
2329      const el = document.getElementById(ocId);
2330      if (el) bootstrap.Offcanvas.getOrCreateInstance(el).show();
2331    }
2332    setTimeout(() => { SUPPRESS = false; }, 0);
2333  }
2334  
2335  document.addEventListener('show.bs.offcanvas', (ev) => {
2336    if (SUPPRESS) return; 
2337    const ocId = ev.target?.id;
2338    if (!ocId) return;
2339    const recId = ev.relatedTarget?.getAttribute?.('data-id') || ev.relatedTarget?.dataset?.id || '';
2340    const url = u();
2341    set(url, 'rz', ocId);
2342    set(url, 'id', recId);
2343    history.pushState({ oc: ocId, id: recId }, '', url);
2344  });
2345  
2346  document.addEventListener('hide.bs.offcanvas', () => {
2347    if (SUPPRESS) return;
2348    const url = u();
2349    set(url, 'rz', null);
2350    set(url, 'id', null);
2351    history.pushState({}, '', url);
2352  });
2353  
2354  window.addEventListener('popstate', applyDeepLink);
2355  document.addEventListener('DOMContentLoaded', applyDeepLink);
2356  
2357    
2358/* RZ Search/Filter for Document Center + Accordion
2359   - Categories come from accordion headers
2360   - Uses <template class="rz-cat-row"> inside each .rz-element-acc-search-wrapper
2361   - Shows a spinner while filtering, updates result/section counts and per-category badges
2362*/
2363(function () {
2364  const DEBOUNCE_MS = 150;
2365  const LOAD_HANDLES = new WeakMap();
2366  const normalize = (s) => (s || '').toString().toLowerCase().normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
2367  const debounce = (fn, ms) => { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; };
2368  const getNextModule = (searchWrap) => {
2369    const wrapper = searchWrap.closest('.rz-element-wrapper');
2370    if (!wrapper) return null;
2371    const candidates = Array.from(wrapper.querySelectorAll('.doc-center-wrap, .accordion-wrap'));
2372    if (!candidates.length) return null;
2373    const children = Array.from(wrapper.children);
2374    const searchIdx = children.indexOf(searchWrap.closest('.rz-element-search-wrapper'));
2375    let best = null, bestIdx = Infinity;
2376    candidates.forEach((el) => {
2377      const idx = children.indexOf(el);
2378      if (idx > searchIdx && idx < bestIdx) { best = el; bestIdx = idx; }
2379    });
2380    return best || candidates[0] || null;
2381  };
2382  function scheduleWithSpinner(searchRoot, workFn) {
2383    const spinner = searchRoot.querySelector('.rz-loader');
2384    if (!spinner) return;
2385    spinner.classList.remove('d-none', 'fade-out');
2386    spinner.classList.add('fade-in');
2387    const prev = LOAD_HANDLES.get(searchRoot);
2388    if (prev) { cancelAnimationFrame(prev.f1); cancelAnimationFrame(prev.f2); }
2389    const f1 = requestAnimationFrame(() => {
2390      const f2 = requestAnimationFrame(() => {
2391        const start = performance.now();
2392        try { workFn(); }
2393        finally {
2394          const minVisible = 400;
2395          const elapsed = performance.now() - start;
2396          const delay = Math.max(0, minVisible - elapsed);
2397          setTimeout(() => {
2398            spinner.classList.remove('fade-in');
2399            spinner.classList.add('fade-out');
2400            setTimeout(() => spinner.classList.add('d-none'), 400);
2401          }, delay);
2402        }
2403      });
2404      LOAD_HANDLES.set(searchRoot, { f1, f2 });
2405    });
2406    LOAD_HANDLES.set(searchRoot, { f1, f2: 0 });
2407  }
2408  function wireDebounced(searchRoot, apply) {
2409    const debouncedApply = debounce(() => scheduleWithSpinner(searchRoot, apply), DEBOUNCE_MS);
2410    const menu = searchRoot.querySelector('#categoryDropdownMenu');
2411    const input = searchRoot.querySelector('input[type="search"]');
2412    if (input) input.addEventListener('input', debouncedApply);
2413    if (menu) menu.addEventListener('rz-categories-change', debouncedApply);
2414    apply();
2415  }
2416  function updateTopCounts(searchRoot, totalItems, matchedSections) {
2417    const r = searchRoot.querySelector('.rz-results-count');
2418    const s = searchRoot.querySelector('.rz-sections-count');
2419    if (r) r.textContent = String(totalItems);
2420    if (s) s.textContent = String(matchedSections);
2421  }
2422  function buildCategoryUIFromHeaders(searchRoot, headers) {
2423    const menu = searchRoot.querySelector('#categoryDropdownMenu');
2424    const tpl = searchRoot.querySelector('.rz-cat-row');
2425    if (!menu || !tpl) return { getSelected: () => [], map: new Map() };
2426    const keep = Array.from(menu.children).slice(0, 2);
2427    menu.innerHTML = '';
2428    keep.forEach(n => menu.appendChild(n));
2429    const catMap = new Map();
2430    headers.forEach((labelTxt) => {
2431      const clean = (labelTxt || '').trim();
2432      if (!clean) return;
2433      const node = tpl.content.firstElementChild.cloneNode(true);
2434      const cb = node.querySelector('input[type="checkbox"]');
2435      const label = node.querySelector('.rz-cat-label');
2436      const badge = node.querySelector('.rz-cat-badge');
2437      if (cb) cb.value = clean;
2438      if (label) label.textContent = clean;
2439      if (badge) badge.textContent = '0';
2440      catMap.set(clean, { cb, badge });
2441      menu.appendChild(node);
2442    });
2443    const selectAll = menu.querySelector('input.selectall');
2444    if (selectAll) {
2445      selectAll.checked = false;
2446      selectAll.addEventListener('change', () => {
2447        menu.querySelectorAll('input[type="checkbox"]:not(.selectall)').forEach(cb => cb.checked = selectAll.checked);
2448        menu.dispatchEvent(new Event('rz-categories-change'));
2449      });
2450    }
2451    menu.addEventListener('change', (e) => {
2452      if (e.target && e.target.matches('input[type="checkbox"]') && !e.target.classList.contains('selectall')) {
2453        if (selectAll && selectAll.checked) selectAll.checked = false;
2454        menu.dispatchEvent(new Event('rz-categories-change'));
2455      }
2456    });
2457    return { map: catMap, getSelected: () => Array.from(menu.querySelectorAll('input[type="checkbox"]:not(.selectall):checked')).map(cb => cb.value) };
2458  }
2459
2460  function wireDocCenter(searchRoot, docCenter) {
2461    const input    = searchRoot.querySelector('input[type="search"]');
2462    const sections = Array.from(docCenter.querySelectorAll('.accordion-item'));
2463
2464    const sectionObjs = sections.map(sec => {
2465      const btn   = sec.querySelector('.accordion-button');
2466      const panel = sec.querySelector('.accordion-collapse');
2467      const header = (btn ? btn.textContent : sec.textContent).trim();
2468      const items = Array.from(sec.querySelectorAll('.list-group-item')).map(li => {
2469        const nameEl = li.querySelector('.doc-name');
2470        const text = normalize(nameEl ? nameEl.textContent : li.textContent);
2471        return { li, text };
2472      });
2473      const initialOpen = !!(panel && panel.classList.contains('show'));
2474      const totalDocs   = items.length;
2475      return { sec, btn, panel, header, items, initialOpen, totalDocs };
2476    });
2477
2478    const headers = sectionObjs.map(s => s.header).filter(Boolean);
2479    const catUI   = buildCategoryUIFromHeaders(searchRoot, headers);
2480
2481    const apply = () => {
2482      const q            = normalize(input && input.value);
2483      const selected     = catUI.getSelected();
2484      const filterByCat  = selected.length > 0;
2485      const activeFilter = !!q || filterByCat;
2486
2487      catUI.map.forEach(({ badge }) => { if (badge) badge.textContent = '0'; });
2488
2489      let totalShown = 0;
2490      let matchedSections = 0;
2491
2492      sectionObjs.forEach(({ sec, btn, panel, header, items, initialOpen, totalDocs }) => {
2493        let shownInSection = 0;
2494
2495        if (!activeFilter) {
2496          items.forEach(({ li }) => { li.style.removeProperty('display'); });
2497          sec.style.removeProperty('display');
2498          shownInSection = totalDocs;
2499
2500          if (panel && btn) {
2501            panel.classList.toggle('show', initialOpen);
2502            btn.classList.toggle('collapsed', !initialOpen);
2503            btn.setAttribute('aria-expanded', initialOpen ? 'true' : 'false');
2504
2505            const badge =
2506              btn.querySelector('.badge.bg-success-subtle.ms-auto.text-dark.rounded-1') ||
2507              btn.querySelector('.badge');
2508            if (badge) badge.textContent = String(totalDocs);
2509          }
2510        } else {
2511          items.forEach(({ li, text }) => {
2512            let ok = !q || text.includes(q);
2513            if (ok && filterByCat) ok = selected.includes(header);
2514
2515            if (ok) {
2516              li.style.removeProperty('display');
2517              shownInSection++;
2518            } else {
2519              li.style.setProperty('display', 'none', 'important');
2520            }
2521          });
2522
2523          const hasVisible = shownInSection > 0;
2524
2525          if (panel && btn) {
2526            panel.classList.toggle('show', hasVisible);
2527            btn.classList.toggle('collapsed', !hasVisible);
2528            btn.setAttribute('aria-expanded', hasVisible ? 'true' : 'false');
2529
2530            const badge =
2531              btn.querySelector('.badge.bg-success-subtle.ms-auto.text-dark.rounded-1') ||
2532              btn.querySelector('.badge');
2533            if (badge) badge.textContent = shownInSection ? String(shownInSection) : '0';
2534          }
2535
2536          if (hasVisible) {
2537            sec.style.removeProperty('display');
2538          } else {
2539            sec.style.setProperty('display', 'none', 'important');
2540          }
2541        }
2542
2543        const catBadge = catUI.map.get(header)?.badge;
2544        if (catBadge) catBadge.textContent = String(shownInSection);
2545
2546        totalShown += shownInSection;
2547        if (shownInSection > 0) matchedSections++;
2548      });
2549
2550      updateTopCounts(searchRoot, totalShown, matchedSections);
2551    };
2552
2553    wireDebounced(searchRoot, apply);
2554  }
2555
2556  function wireAccordion(searchRoot, accWrap) {
2557    const input = searchRoot.querySelector('input[type="search"]');
2558    const items = Array.from(accWrap.querySelectorAll('.accordion-item')).map(item => {
2559      const btn   = item.querySelector('.accordion-button');
2560      const panel = item.querySelector('.accordion-collapse');
2561      const headerTxt = item.querySelector('.accordion-header')?.textContent?.trim() || '';
2562      const bodyTxt   = panel?.textContent || '';
2563      return {
2564        item, btn, panel,
2565        header: headerTxt,
2566        text: normalize((headerTxt + ' ' + bodyTxt).trim()),
2567        initialOpen: !!(panel && panel.classList.contains('show'))
2568      };
2569    });
2570
2571    const headers = items.map(i => i.header).filter(Boolean);
2572    const catUI = buildCategoryUIFromHeaders(searchRoot, headers);
2573
2574    const apply = () => {
2575      const q = normalize(input && input.value);
2576      const selected = catUI.getSelected();
2577      const filterByCat = selected.length > 0;
2578      const activeFilter = !!q || filterByCat;
2579
2580      catUI.map.forEach(({ badge }) => { if (badge) badge.textContent = '0'; });
2581
2582      let totalShown = 0;
2583      let matchedSections = 0;
2584
2585      items.forEach(({ item, btn, panel, header, text, initialOpen }) => {
2586        let isVisible = false;
2587
2588        if (!activeFilter) {
2589          isVisible = true;
2590          item.style.removeProperty('display');
2591          if (panel && btn) {
2592            panel.classList.toggle('show', initialOpen);
2593            btn.classList.toggle('collapsed', !initialOpen);
2594            btn.setAttribute('aria-expanded', initialOpen ? 'true' : 'false');
2595            const b = btn.querySelector('.badge');
2596            if (b) b.textContent = '';
2597          }
2598        } else {
2599          let ok = !q || text.includes(q);
2600          if (ok && filterByCat) ok = selected.includes(header);
2601
2602          isVisible = ok;
2603
2604          if (ok) item.style.removeProperty('display');
2605          else item.style.setProperty('display', 'none', 'important');
2606
2607          if (panel && btn) {
2608            panel.classList.toggle('show', ok);
2609            btn.classList.toggle('collapsed', !ok);
2610            btn.setAttribute('aria-expanded', ok ? 'true' : 'false');
2611          }
2612        }
2613
2614        if (isVisible) {
2615          totalShown += 1;
2616          matchedSections += 1;
2617          const catBadge = catUI.map.get(header)?.badge;
2618          if (catBadge) catBadge.textContent = '1';
2619        }
2620      });
2621
2622      updateTopCounts(searchRoot, totalShown, matchedSections);
2623    };
2624
2625    wireDebounced(searchRoot, apply);
2626  }
2627
2628  document.querySelectorAll('.rz-element-acc-search-wrapper').forEach((searchWrap) => {
2629    const module = getNextModule(searchWrap);
2630    if (!module) return;
2631    if (module.classList.contains('doc-center-wrap')) {
2632      wireDocCenter(searchWrap, module);
2633    } else if (module.classList.contains('accordion-wrap')) {
2634      wireAccordion(searchWrap, module);
2635    }
2636  });
2637})();
2638
2639/* ======================================================
2640  RZ Element Search + Filter + Pagination + Masonry
2641
2642  - Text search with keyword highlighting
2643  - Optional category filter (checkbox dropdown with counts)
2644  - Optional date range filter via [data-search-date="from"/"to"]
2645  - Supports two modes:
2646      • Classic pagination via data-cards-per-page (default 10)
2647      • Infinite “Show more” mode when data-infinite="true"
2648  - Skips search/pagination when inside tiny slider/carousel
2649  - Non-visible cards are removed from the DOM for performance
2650  - Loader spinner:
2651      • Shown at the TOP for search/filter/sort/pagination
2652      • Shown at the BOTTOM only while "Show more" is loading
2653  - "No results" message with Show more hidden when there are 0 matches
2654  - Infinite mode:
2655      • Appends new cards without destroying Masonry
2656      • Soft Masonry relayout to avoid scroll jumps
2657      • Cards fade/zoom up in (CSS animation) without affecting Masonry layout
2658  - Masonry-safe:
2659      • Transitions on Masonry items are disabled
2660      • Works with RZ.initMasonry / data-masonry / data-masonry-deferred
2661  * Works well *
2662====================================================== */
2663(function () {
2664  // ------------ Helpers ------------
2665
2666  function normalize(str) {
2667    return (str || "")
2668      .toString()
2669      .toLowerCase()
2670      .normalize("NFD")
2671      .replace(/[\u0300-\u036f]/g, "");
2672  }
2673
2674  function safeParseDate(str) {
2675    if (!str) return null;
2676    var d = new Date(str);
2677    return isNaN(d.getTime()) ? null : d;
2678  }
2679
2680  function getDateFromInput(input) {
2681    if (!input || !input.value) return null;
2682    return safeParseDate(input.value);
2683  }
2684
2685  function highlightMatch(el, tokens) {
2686    if (!el || !tokens || !tokens.length) return;
2687
2688    // Ignore any tokens that contain angle brackets – they can break HTML
2689    tokens = tokens.filter(function (t) {
2690      return t && !/[<>]/.test(t);
2691    });
2692    if (!tokens.length) return;
2693
2694    function escapeRegExp(str) {
2695      return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2696    }
2697
2698    // Single regex for all tokens, case-insensitive
2699    var pattern = new RegExp(
2700      "(" + tokens.map(escapeRegExp).join("|") + ")",
2701      "gi"
2702    );
2703
2704    var walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
2705    var textNodes = [];
2706    var node;
2707    while ((node = walker.nextNode())) textNodes.push(node);
2708
2709    textNodes.forEach(function (n) {
2710      var text = n.nodeValue;
2711      if (!pattern.test(text)) {
2712        pattern.lastIndex = 0; // reset for next node
2713        return;
2714      }
2715      pattern.lastIndex = 0;
2716
2717      var frag = document.createDocumentFragment();
2718      var lastIndex = 0;
2719
2720      text.replace(pattern, function (match, _group, offset) {
2721        // text before match
2722        if (offset > lastIndex) {
2723          frag.appendChild(
2724            document.createTextNode(text.slice(lastIndex, offset))
2725          );
2726        }
2727        // highlighted match
2728        var mark = document.createElement("mark");
2729        mark.textContent = match; // keep original casing
2730        frag.appendChild(mark);
2731
2732        lastIndex = offset + match.length;
2733        return match;
2734      });
2735
2736      // trailing text
2737      if (lastIndex < text.length) {
2738        frag.appendChild(document.createTextNode(text.slice(lastIndex)));
2739      }
2740
2741      if (n.parentNode) {
2742        n.parentNode.replaceChild(frag, n);
2743      }
2744    });
2745  }
2746
2747  // Wait until cards (.rz-element) exist in a container
2748  function whenCardsReady(container, cb) {
2749    var current = container.querySelectorAll(".rz-element");
2750    if (current.length) {
2751      cb(Array.prototype.slice.call(current));
2752      return;
2753    }
2754    var mo = new MutationObserver(function () {
2755      var cards = container.querySelectorAll(".rz-element");
2756      if (cards.length) {
2757        mo.disconnect();
2758        cb(Array.prototype.slice.call(cards));
2759      }
2760    });
2761    mo.observe(container, { childList: true, subtree: true });
2762  }
2763
2764  // Masonry helpers
2765  function killMasonry(grid) {
2766    if (!grid) return;
2767    if (grid._masonryInstance && typeof grid._masonryInstance.destroy === "function") {
2768      grid._masonryInstance.destroy();
2769      grid._masonryInstance = null;
2770    }
2771    grid.querySelectorAll(".rz-element").forEach(function (el) {
2772      el.style.position = "";
2773      el.style.left = "";
2774      el.style.top = "";
2775    });
2776    grid.style.height = "";
2777  }
2778
2779  function readMasonryOptions(grid) {
2780    var defaults = { itemSelector: ".rz-element", percentPosition: true };
2781    try {
2782      var raw = grid.getAttribute("data-masonry");
2783      if (!raw) return defaults;
2784      var tmp = document.createElement("div");
2785      tmp.innerHTML = raw;
2786      var decoded = tmp.textContent || tmp.innerText || raw;
2787      var obj = JSON.parse(decoded);
2788      for (var k in defaults) {
2789        if (!obj.hasOwnProperty(k)) obj[k] = defaults[k];
2790      }
2791      return obj;
2792    } catch (e) {
2793      return defaults;
2794    }
2795  }
2796  
2797  function patchMasonryNoTransition(grid) {
2798    if (!grid) return;
2799  
2800    // We only care if data-masonry (or deferred) is present
2801    var raw =
2802      grid.getAttribute("data-masonry") ||
2803      grid.getAttribute("data-masonry-deferred");
2804    if (!raw) return;
2805  
2806    try {
2807      // RZ tends to HTML-encode this, so decode first
2808      var tmp = document.createElement("div");
2809      tmp.innerHTML = raw;
2810      var decoded = tmp.textContent || tmp.innerText || raw;
2811  
2812      var obj = JSON.parse(decoded || "{}");
2813  
2814      // Only touch if not explicitly set
2815      if (obj.transitionDuration == null) {
2816        // 0 = no animation, which prevents "flying in"
2817        obj.transitionDuration = 0;
2818      }
2819  
2820      var json = JSON.stringify(obj);
2821  
2822      // Write back so BOTH RZ.initMasonry and our fallback see it
2823      if (grid.hasAttribute("data-masonry")) {
2824        grid.setAttribute("data-masonry", json);
2825      }
2826      if (grid.hasAttribute("data-masonry-deferred")) {
2827        grid.setAttribute("data-masonry-deferred", json);
2828      }
2829    } catch (e) {
2830      // swallow – worst case, Masonry keeps its default transitions
2831    }
2832  }
2833
2834  function waitImagesOnce(grid, cb) {
2835    if (grid._imgsReadyOnce) return cb();
2836    var imgs = Array.prototype.slice.call(grid.querySelectorAll("img"));
2837    if (!imgs.length) {
2838      grid._imgsReadyOnce = true;
2839      return cb();
2840    }
2841    var pending = imgs.filter(function (img) { return !img.complete; }).length;
2842    if (pending === 0) {
2843      grid._imgsReadyOnce = true;
2844      return cb();
2845    }
2846    function done() {
2847      if (!grid._imgsReadyOnce) {
2848        grid._imgsReadyOnce = true;
2849        cb();
2850      }
2851    }
2852    imgs.forEach(function (img) {
2853      if (img.complete) return;
2854      img.addEventListener("load", function () {
2855        if (--pending <= 0) done();
2856      }, { once: true });
2857      img.addEventListener("error", function () {
2858        if (--pending <= 0) done();
2859      }, { once: true });
2860    });
2861  }
2862
2863  function scheduleMasonryRestore(grid) {
2864    if (!grid) return;
2865    if (grid._mzRestoreT) clearTimeout(grid._mzRestoreT);
2866
2867    function pokeOwners() {
2868      grid.dispatchEvent(new CustomEvent("rz:masonry:requestInit", { bubbles: true }));
2869      grid.dispatchEvent(new CustomEvent("rz:masonry:requestLayout", { bubbles: true }));
2870      if (window.RZ && typeof window.RZ.initMasonry === "function") window.RZ.initMasonry(grid);
2871      else if (typeof window.RZInitMasonry === "function") window.RZInitMasonry(grid);
2872      else if (typeof window.initMasonry === "function") window.initMasonry(grid);
2873    }
2874
2875    function fallbackInit() {
2876      if (grid._masonryInstance || typeof window.Masonry !== "function") return;
2877      var opts = readMasonryOptions(grid);
2878      grid._masonryInstance = new Masonry(grid, opts);
2879      grid._masonryInstance.reloadItems();
2880      grid._masonryInstance.layout();
2881    }
2882
2883    requestAnimationFrame(function () {
2884      setTimeout(function () {
2885        waitImagesOnce(grid, function () {
2886          pokeOwners();
2887          setTimeout(function () {
2888            grid.dispatchEvent(new CustomEvent("rz:masonry:requestLayout", { bubbles: true }));
2889            if (!grid._masonryInstance) fallbackInit();
2890          }, 80);
2891        });
2892      }, 24);
2893    });
2894
2895    grid._mzRestoreT = setTimeout(function () {
2896      pokeOwners();
2897      if (!grid._masonryInstance) fallbackInit();
2898    }, 280);
2899  }
2900  
2901  function softMasonryRelayout(grid) {
2902    if (!grid) return;
2903  
2904    // Ask any RZ Masonry owners to just re-layout, not re-init
2905    grid.dispatchEvent(
2906      new CustomEvent("rz:masonry:requestLayout", { bubbles: true })
2907    );
2908  
2909    // If our fallback instance exists, just reload + layout
2910    if (grid._masonryInstance && typeof grid._masonryInstance.layout === "function") {
2911      if (typeof grid._masonryInstance.reloadItems === "function") {
2912        grid._masonryInstance.reloadItems();
2913      }
2914      grid._masonryInstance.layout();
2915    }
2916  }
2917
2918  // ------------ Main init ------------
2919
2920  function initRZElementSearch() {
2921    // Only run on wrappers that explicitly opt-in via data-rz-search-module
2922    var wrappers = document.querySelectorAll(
2923      '.rz-element-wrapper[data-rz-search-module]'
2924    );
2925
2926    wrappers.forEach(function (wrapper) {
2927
2928      // completely skip any wrapper that is part of an agenda list
2929      if (
2930        wrapper.closest(".rz-agendalist-card-wrapper") ||
2931        wrapper.querySelector(".rz-agendalist-card-wrapper, .rz-agendalist-card")
2932      ) {
2933        return;
2934      }
2935
2936      var cardContainer = wrapper.querySelector(".row");
2937      if (!cardContainer) return;
2938
2939      // Skip tiny slider
2940      var tinyParent = cardContainer.closest(
2941        ".tiny-carousel-wrap, .tiny-slider, .tiny-slider-wrap, .rz-slider"
2942      );
2943      if (tinyParent) return;
2944
2945      // Optional search wrapper + search input
2946      var searchWrapper = wrapper.querySelector(".rz-element-search-wrapper");
2947      var searchInput = searchWrapper
2948        ? searchWrapper.querySelector('input[type="search"]')
2949        : null;
2950
2951      if (!wrapper.style.position) wrapper.style.position = "relative";
2952      if (!cardContainer.style.position) cardContainer.style.position = "relative";
2953
2954      var masonryContainer =
2955        cardContainer.hasAttribute("data-masonry") ||
2956        cardContainer.hasAttribute("data-masonry-deferred")
2957          ? cardContainer
2958          : null;
2959
2960      // Wait until cards exist (RZ may inject them later)
2961      whenCardsReady(cardContainer, function (initialCards) {
2962        if (!initialCards.length) return;
2963        if (wrapper._rzSearchInit) return;
2964        wrapper._rzSearchInit = true;
2965
2966        // detect infinite "show more" mode
2967        var infiniteScrollEnabled = (function () {
2968          var val =
2969            cardContainer.getAttribute("data-infinite") ||
2970            cardContainer.getAttribute("data-Infinite") ||
2971            wrapper.getAttribute("data-infinite") ||
2972            wrapper.getAttribute("data-Infinite") ||
2973            "";
2974          return String(val).toLowerCase() === "true";
2975        })();
2976        
2977        // Disable Masonry transition animation on ALL Masonry grids
2978        // (prevents "flying cards" and lets CSS height transition handle smoothness)
2979        if (masonryContainer) {
2980          patchMasonryNoTransition(masonryContainer);
2981        }
2982
2983        // ------------- Core state -------------
2984
2985        var allCards = initialCards.slice();
2986        allCards.forEach(function (card, idx) {
2987          if (card.__origIndex == null) card.__origIndex = idx;
2988          if (!card.__origHTML) card.__origHTML = card.innerHTML;
2989          if (!card.__dateValue && card.dataset && card.dataset.date) {
2990            card.__dateValue = safeParseDate(card.dataset.date);
2991          }
2992          if (!card.__searchText) {
2993            card.__searchText = normalize(card.textContent || "");
2994          }
2995        });
2996
2997        // Pagination config from data-cards-per-page
2998        var cardsPerPage = (function () {
2999          var raw =
3000            (cardContainer.getAttribute("data-cards-per-page") ||
3001              wrapper.getAttribute("data-cards-per-page") ||
3002              ""
3003            ).trim();
3004
3005          // If attribute missing/empty -> default 10
3006          if (!raw) return 10;
3007
3008          var v = parseInt(raw, 10);
3009          if (!Number.isFinite(v) || v <= 0) return 0; // 0 = no pagination / infinite batch size
3010          return v;
3011        })();
3012
3013        // If infinite mode is enabled, we do NOT show classic pagination
3014        var enablePagination = !infiniteScrollEnabled && cardsPerPage > 0;
3015        var currentPage = 1;
3016        var lastRenderedCount = 0; // for "new items" detection
3017        var isShowMoreLoading = false; 
3018
3019        // Loader + no results
3020        function ensureNoResults() {
3021          var el = wrapper.querySelector(".rz-no-results");
3022          if (!el) {
3023            el = document.createElement("div");
3024            el.className = "rz-no-results w-100 text-center opacity-75";
3025            el.textContent = "No results found.";
3026            el.style.display = "none";
3027            wrapper.appendChild(el);
3028          }
3029          return el;
3030        }
3031
3032        function ensureSpinner() {
3033          var el = wrapper.querySelector(".rz-search-spinner");
3034          if (!el) {
3035            el = document.createElement("div");
3036            el.className = "rz-search-spinner text-center";
3037            el.style.cssText =
3038              "display:none; position:absolute; top:5rem; left:50%; transform:translateX(-50%); z-index:10;";
3039            el.innerHTML =
3040              '<div class="spinner-border text-primary" role="status" style="width:2rem; height:2rem;"><span class="visually-hidden">Searching...</span></div>';
3041            wrapper.appendChild(el);
3042          }
3043          return el;
3044        }
3045
3046        var noResultsEl = ensureNoResults();
3047        var spinnerEl = ensureSpinner();
3048
3049        // show = true/false, fromShowMore = true only when called by "Show more"
3050        function showSpinner(show, fromShowMore) {
3051          if (!spinnerEl) return;
3052        
3053          var isShowMoreMode = !!fromShowMore;
3054        
3055          // Position: bottom for show-more, top for normal filtering/search
3056          if (isShowMoreMode) {
3057            spinnerEl.style.top = "auto";
3058            spinnerEl.style.bottom = "5rem";
3059            spinnerEl.style.transform = "translateX(-50%)";
3060          } else {
3061            spinnerEl.style.top = "5rem";
3062            spinnerEl.style.bottom = "auto";
3063            spinnerEl.style.transform = "translateX(-50%)";
3064          }
3065        
3066          // Clear any previous hide timer
3067          if (spinnerEl._hideTimer) {
3068            clearTimeout(spinnerEl._hideTimer);
3069            spinnerEl._hideTimer = null;
3070          }
3071        
3072          if (show) {
3073            spinnerEl.style.display = "block";
3074            requestAnimationFrame(function () {
3075              spinnerEl.classList.add("show");
3076            });
3077          } else {
3078            // Extra delay ONLY for infinite "show more" mode
3079            var extraDelay = isShowMoreMode ? 300 : 0;
3080        
3081            spinnerEl._hideTimer = setTimeout(function () {
3082              spinnerEl.classList.remove("show");
3083              setTimeout(function () {
3084                spinnerEl.style.display = "none";
3085              }, 400);
3086            }, extraDelay);
3087          }
3088        }
3089
3090        function showNoResults(show) {
3091          if (!noResultsEl) return;
3092          noResultsEl.style.setProperty('display', show ? 'block' : 'none', 'important');
3093        
3094          if (infiniteScrollEnabled) {
3095            var btn  = showMoreBtn || ensureShowMoreButton();
3096            if (btn) {
3097              var wrap = btn.closest('.rz-show-more-wrap');
3098              if (wrap) {
3099                if (show) {
3100                  wrap.classList.remove('d-flex');
3101                  wrap.classList.add('d-none');
3102                }
3103              }
3104            }
3105          }
3106        }
3107
3108        // Category + date controls (all optional if no search wrapper)
3109        var categoryDropdown = searchWrapper
3110          ? searchWrapper.querySelector(".category-dropdown")
3111          : null;
3112        var categoryMenu = categoryDropdown
3113          ? categoryDropdown.querySelector(".dropdown-menu")
3114          : null;
3115
3116        var sortWrapper = searchWrapper
3117          ? searchWrapper.querySelector(".sort-dropdown")
3118          : null;
3119
3120        var dateFromInput = wrapper.querySelector('[data-search-date="from"]');
3121        var dateToInput = wrapper.querySelector('[data-search-date="to"]');
3122
3123        function getSelectedCategories() {
3124          if (!categoryMenu) return [];
3125          var out = [];
3126          categoryMenu.querySelectorAll("input.option.justone:checked").forEach(function (cb) {
3127            var val = (cb.value || "").trim().toLowerCase();
3128            if (val) out.push(val);
3129          });
3130          return out;
3131        }
3132
3133        // ------- Category dropdown with count badges --------
3134        function buildCategoryOptionsOnce() {
3135          if (!categoryMenu || categoryMenu._rzBuilt) return;
3136          categoryMenu._rzBuilt = true;
3137
3138          // Gather all categories + counts from cards' data-category
3139          var allCategories = new Set();
3140          var categoryCounts = Object.create(null);
3141
3142          allCards.forEach(function (card) {
3143            (card.getAttribute("data-category") || "")
3144              .split(",")
3145              .forEach(function (cat) {
3146                var clean = cat.trim();
3147                if (!clean) return;
3148                allCategories.add(clean);
3149                categoryCounts[clean] = (categoryCounts[clean] || 0) + 1;
3150              });
3151          });
3152
3153          // Remove any existing option-link items so we don't duplicate
3154          Array.prototype.slice
3155            .call(categoryMenu.querySelectorAll(".option-link"))
3156            .forEach(function (e) { e.remove(); });
3157
3158          // If no categories at all, remove dropdown
3159          if (!allCategories.size) {
3160            if (categoryDropdown) {
3161              categoryDropdown.remove();
3162              if (searchWrapper) {
3163                var vr = searchWrapper.querySelector(".vr");
3164                if (vr) vr.remove();
3165              }
3166            }
3167            return;
3168          }
3169
3170          // Build list items from categories WITH count badges
3171          Array.from(allCategories)
3172            .sort()
3173            .forEach(function (cat) {
3174              var count = categoryCounts[cat] || 0;
3175              var li = document.createElement("li");
3176              var catEscaped = cat.replace(/"/g, "&quot;");
3177              li.innerHTML =
3178              '<div class="dropdown-item option-link" role="menuitemcheckbox" aria-checked="false" tabindex="0">' +
3179                '<label class="w-100 d-flex align-items-center justify-content-between mb-0">' +
3180                  '<span class="d-flex align-items-center">' +
3181                    '<input id="cat_' + catEscaped.replace(/\s+/g, "_") + '" ' +
3182                      'name="options[]" type="checkbox" ' +
3183                      'class="option justone form-check-input" ' +
3184                      'value="' + catEscaped + '" ' +
3185                      'aria-label="Filter by ' + cat + '">' +
3186                    '<span class="me-2 form-check-label" for="cat_' + catEscaped.replace(/\s+/g, "_") + '">' +
3187                      cat +
3188                    "</span>" +
3189                  "</span>" +
3190                  '<span class="badge text-bg-light" aria-hidden="true">' + count + "</span>" +
3191                "</label>" +
3192              "</div>";
3193              categoryMenu.appendChild(li);
3194
3195              var anchor = li.querySelector("a");
3196              if (anchor) {
3197                anchor.addEventListener("click", function (e) {
3198                  // prevent closing dropdown when clicking label body
3199                  if (e.target === this) e.preventDefault();
3200                });
3201              }
3202            });
3203
3204          var selectAll   = categoryMenu.querySelector(".selectall");
3205          var selectText  = categoryMenu.querySelector(".select-text");
3206          var dropdownTxt = categoryDropdown
3207            ? categoryDropdown.querySelector(".dropdown-text")
3208            : null;
3209
3210          // "Select all" checkbox
3211          if (selectAll) {
3212            selectAll.addEventListener("change", function () {
3213              var checked = this.checked;
3214              categoryMenu
3215                .querySelectorAll("input.option")
3216                .forEach(function (opt) { opt.checked = checked; });
3217
3218              var totalChecked = categoryMenu.querySelectorAll("input.option:checked").length;
3219
3220              if (dropdownTxt) {
3221                dropdownTxt.textContent = totalChecked
3222                  ? "(" + totalChecked + ") Selected"
3223                  : "Categories";
3224              }
3225              if (selectText) {
3226                selectText.textContent = checked ? " Deselect" : " Select";
3227              }
3228
3229              triggerFiltering();
3230            });
3231          }
3232
3233          // Individual category checkboxes
3234         categoryMenu.querySelectorAll("input.option").forEach(function (opt) {
3235           opt.addEventListener("change", function () {
3236             var all = categoryMenu.querySelectorAll("input.option");
3237             var checked = categoryMenu.querySelectorAll("input.option:checked");
3238         
3239             var parent = this.closest('[role="menuitemcheckbox"]');
3240             if (parent) parent.setAttribute("aria-checked", this.checked ? "true" : "false");
3241         
3242             if (selectAll) {
3243               selectAll.checked = all.length === checked.length;
3244             }
3245             if (selectText) {
3246               selectText.textContent =
3247                 selectAll && selectAll.checked ? " Deselect" : " Select";
3248             }
3249             if (dropdownTxt) {
3250               dropdownTxt.textContent = checked.length
3251                 ? "(" + checked.length + ") Selected"
3252                 : "Categories";
3253             }
3254         
3255             triggerFiltering();
3256           });
3257         });
3258
3259        }
3260
3261        // Sort
3262        var currentSort = "default"; // "default" | "newest" | "oldest"
3263        var isFirstRender = true;
3264
3265        function buildSortOnce() {
3266          if (!sortWrapper || sortWrapper._rzBuilt) return;
3267          sortWrapper._rzBuilt = true;
3268
3269          var hasDateCards = allCards.some(function (c) { return !!c.__dateValue; });
3270          if (!hasDateCards) {
3271            sortWrapper.remove();
3272            if (searchWrapper) {
3273              var vr = searchWrapper.querySelector(".vr");
3274              if (vr) vr.remove();
3275            }
3276            return;
3277          }
3278
3279          sortWrapper.querySelectorAll(".dropdown-item").forEach(function (item) {
3280            item.addEventListener("click", function (e) {
3281              e.preventDefault();
3282              sortWrapper
3283                .querySelectorAll(".dropdown-item")
3284                .forEach(function (el) { el.classList.remove("active"); });
3285              item.classList.add("active");
3286
3287              var txt = (item.textContent || "").trim().toLowerCase();
3288              if (txt === "newest") currentSort = "newest";
3289              else if (txt === "oldest") currentSort = "oldest";
3290              else currentSort = "default";
3291
3292              currentPage = 1;
3293              triggerFiltering();
3294            });
3295          });
3296        }
3297
3298        // Pagination UI + Show more
3299        var paginationNav = null;
3300        var paginationList = null;
3301        var showMoreBtn = null; // for infinite mode
3302
3303        function ensurePaginationUI() {
3304          if (!enablePagination) return null;
3305          if (!paginationNav) {
3306            paginationNav = document.createElement("nav");
3307            paginationNav.className = "rz-pagination mt-3";
3308            paginationNav.setAttribute("aria-label", "Search results pagination");
3309            paginationNav.innerHTML =
3310              '<ul class="pagination justify-content-center mb-0"></ul>';
3311            wrapper.appendChild(paginationNav);
3312          }
3313          paginationList = paginationNav.querySelector("ul.pagination");
3314          return paginationNav;
3315        }
3316
3317        function renderPagination(totalMatches) {
3318          if (!enablePagination || cardsPerPage <= 0) {
3319            if (paginationNav) paginationNav.style.display = "none";
3320            return;
3321          }
3322        
3323          const totalPages = Math.max(
3324            1,
3325            Math.ceil(totalMatches / Math.max(1, cardsPerPage))
3326          );
3327          if (totalPages <= 1) {
3328            if (paginationNav) paginationNav.style.display = "none";
3329            return;
3330          }
3331        
3332          ensurePaginationUI();
3333          if (!paginationNav || !paginationList) return;
3334        
3335          currentPage = Math.max(1, Math.min(currentPage, totalPages));
3336          paginationNav.style.display = "";
3337          paginationList.innerHTML = "";
3338        
3339          function addNavButton(labelHTML, targetPage, disabled) {
3340            const li = document.createElement("li");
3341            li.className = "page-item";
3342            if (disabled) li.classList.add("disabled");
3343        
3344            const a = document.createElement("a");
3345            a.className = "page-link";
3346            a.href = "#";
3347            a.innerHTML = labelHTML;
3348        
3349            if (!disabled) {
3350              a.addEventListener("click", function (e) {
3351                e.preventDefault();
3352                if (targetPage === currentPage) return;
3353        
3354                currentPage = targetPage;
3355                showSpinner(true);
3356        
3357                // Scroll to the wrapper top *immediately*,
3358                // before we rebuild the grid.
3359                var top =
3360                  wrapper.getBoundingClientRect().top +
3361                  window.pageYOffset -
3362                  100; // adjust offset as you like
3363                window.scrollTo(0, top); // instant, no smooth
3364        
3365                // Now rebuild the page + Masonry
3366                requestAnimationFrame(function () {
3367                  evaluateAndRender(true); // keepPage = true for currentPage
3368                  showSpinner(false);
3369                });
3370              });
3371            }
3372        
3373            li.appendChild(a);
3374            paginationList.appendChild(li);
3375          }
3376        
3377          // FIRST
3378          addNavButton('<i class="bi bi-chevron-bar-left small"></i>', 1, currentPage === 1);
3379        
3380          // PREV
3381          addNavButton(
3382            '<i class="bi bi-chevron-left small"></i>',
3383            currentPage - 1,
3384            currentPage === 1
3385          );
3386        
3387          // CENTER "3 of 20"
3388          const liCenter = document.createElement("li");
3389          liCenter.className = "page-item disabled";
3390          const spanCenter = document.createElement("span");
3391          spanCenter.className = "page-link rz-page-status";
3392          spanCenter.innerHTML =
3393            '<span class="rz-page-current">' + currentPage + '</span> ' +
3394            '<span class="rz-page-of">of</span> ' +
3395            '<span class="rz-page-total">' + totalPages + '</span>';
3396          liCenter.appendChild(spanCenter);
3397          paginationList.appendChild(liCenter);
3398        
3399          // NEXT
3400          addNavButton(
3401            '<i class="bi bi-chevron-right small"></i>',
3402            currentPage + 1,
3403            currentPage === totalPages
3404          );
3405        
3406          // LAST
3407          addNavButton(
3408            '<i class="bi bi-chevron-bar-right small"></i>',
3409            totalPages,
3410            currentPage === totalPages
3411          );
3412        }
3413
3414        // Show more button (infinite mode) wrapped in div
3415        function ensureShowMoreButton() {
3416          if (!infiniteScrollEnabled) return null;
3417          if (!showMoreBtn) {
3418            var wrapDiv = document.createElement("div");
3419            wrapDiv.className = "rz-show-more-wrap d-flex justify-content-center mt-3";
3420
3421            showMoreBtn = document.createElement("button");
3422            showMoreBtn.type = "button";
3423            showMoreBtn.className = "btn btn-link p-3 px-4 fs-base d-flex gap-1 align-items-center rz-show-more-btn";
3424            showMoreBtn.textContent = "Show more";
3425
3426            wrapDiv.appendChild(showMoreBtn);
3427
3428            // place the wrapper after the card container
3429            cardContainer.parentNode.insertBefore(
3430              wrapDiv,
3431              cardContainer.nextSibling
3432            );
3433          }
3434          return showMoreBtn;
3435        }
3436
3437        function updateShowMoreButton(currentPageVal, totalPagesVal, noResults) {
3438          if (!infiniteScrollEnabled) {
3439            if (showMoreBtn) showMoreBtn.classList.replace('d-flex', 'd-none');
3440            return;
3441          }
3442        
3443          var btn = ensureShowMoreButton();
3444          if (!btn) return;
3445        
3446          var wrap = btn.closest('.rz-show-more-wrap');
3447          if (!wrap) return;
3448        
3449          // 1) No results at all → hide and reset button
3450          if (noResults) {
3451            wrap.classList.remove('d-flex');
3452            wrap.classList.add('d-none');
3453        
3454            btn.disabled = false;
3455            btn.classList.remove('disabled', 'opacity-75');
3456            btn.innerHTML = 'Show more';
3457            return;
3458          }
3459        
3460          // 2) Only one page of results → hide control
3461          if (totalPagesVal <= 1) {
3462            wrap.classList.remove('d-flex');
3463            wrap.classList.add('d-none');
3464            return;
3465          }
3466        
3467          // 3) Multiple pages → show
3468          wrap.classList.remove('d-none');
3469          wrap.classList.add('d-flex');
3470        
3471          if (currentPageVal >= totalPagesVal) {
3472            btn.disabled = true;
3473            btn.classList.add("disabled", "opacity-75");
3474            btn.innerHTML = 'End of list';
3475          } else {
3476            btn.disabled = false;
3477            btn.classList.remove('disabled', 'opacity-75');
3478            btn.innerHTML = 'Show more';
3479          }
3480        }
3481
3482        // Scroll helper: scroll to bottom of the newly added items
3483        function scrollToNewItems(prevCount) {
3484          try {
3485            if (!cardContainer) return;
3486            var cardsNow = cardContainer.querySelectorAll(".rz-element");
3487            if (!cardsNow.length) return;
3488            if (prevCount >= cardsNow.length) return;
3489
3490            // Last new card = last child (bottom of the new batch)
3491            var lastNew = cardsNow[cardsNow.length - 1];
3492            var rect = lastNew.getBoundingClientRect();
3493            var offset = rect.bottom + window.pageYOffset - (window.innerHeight - 120);
3494            window.scrollTo({
3495              top: offset,
3496              behavior: "smooth"
3497            });
3498          } catch (e) {
3499            // fail silently
3500          }
3501        }
3502
3503        // Filtering core
3504        function getActiveFilters() {
3505          var rawQuery = searchInput ? (searchInput.value || "").trim() : "";
3506          var tokensRaw = rawQuery
3507            ? rawQuery.split(/\s+/).filter(function (t) { return t.trim(); })
3508            : [];
3509          var tokensNorm = tokensRaw.map(normalize);
3510
3511          var cats = getSelectedCategories();
3512          var fromDate = getDateFromInput(dateFromInput);
3513          var toDate = getDateFromInput(dateToInput);
3514
3515          return {
3516            rawQuery: rawQuery,
3517            tokensRaw: tokensRaw,
3518            tokensNorm: tokensNorm,
3519            categories: cats,
3520            fromDate: fromDate,
3521            toDate: toDate
3522          };
3523        }
3524
3525        function cardMatchesFilters(card, f) {
3526          // Text
3527          if (f.tokensNorm.length) {
3528            var txt = card.__searchText || normalize(card.textContent || "");
3529            var ok = f.tokensNorm.some(function (tok) {
3530              return txt.indexOf(tok) !== -1;
3531            });
3532            if (!ok) return false;
3533          }
3534
3535          // Categories
3536          if (f.categories.length) {
3537            var attr = (card.getAttribute("data-category") || "")
3538              .split(",")
3539              .map(function (c) { return normalize(c.trim()); })
3540              .filter(Boolean);
3541            if (!attr.length) return false;
3542            var matchCat = f.categories.some(function (c) {
3543              return attr.indexOf(c) !== -1;
3544            });
3545            if (!matchCat) return false;
3546          }
3547
3548          // Date range
3549          if (f.fromDate || f.toDate) {
3550            var d = card.__dateValue;
3551            if (!d) return false;
3552            if (f.fromDate && d < f.fromDate) return false;
3553            if (f.toDate && d > f.toDate) return false;
3554          }
3555
3556          return true;
3557        }
3558
3559        function filterAndSortCards() {
3560          var filters = getActiveFilters();
3561          var filtered = allCards.filter(function (card) {
3562            return cardMatchesFilters(card, filters);
3563          });
3564
3565          filtered.sort(function (a, b) {
3566            if (currentSort === "newest" || currentSort === "oldest") {
3567              var da = a.__dateValue || new Date("1900-01-01");
3568              var db = b.__dateValue || new Date("1900-01-01");
3569              return currentSort === "newest" ? db - da : da - db;
3570            }
3571            return (a.__origIndex || 0) - (b.__origIndex || 0);
3572          });
3573
3574          return { filters: filters, filtered: filtered };
3575        }
3576
3577        function evaluateAndRender(keepPage) {
3578          // Capture previous container height for smooth transition
3579          var previousHeight = cardContainer.offsetHeight || 0;
3580
3581          // reset HTML (remove previous highlights etc.)
3582          allCards.forEach(function (c) {
3583            if (c.__origHTML) c.innerHTML = c.__origHTML;
3584          });
3585
3586          if (!keepPage) {
3587            currentPage = 1;
3588            lastRenderedCount = 0;
3589          }
3590
3591          var result = filterAndSortCards();
3592          var filters = result.filters;
3593          var list = result.filtered;
3594          var total = list.length;
3595
3596          var pageItems = list;
3597          var newItems = []; // used only in infinite/show-more mode
3598
3599          if (infiniteScrollEnabled && cardsPerPage > 0) {
3600            var totalPages = Math.max(
3601              1,
3602              Math.ceil(total / Math.max(1, cardsPerPage))
3603            );
3604            if (currentPage < 1) currentPage = 1;
3605            if (currentPage > totalPages) currentPage = totalPages;
3606
3607            var end = cardsPerPage * currentPage;
3608            pageItems = list.slice(0, end);
3609
3610            var startNew = lastRenderedCount;
3611            if (startNew < 0) startNew = 0;
3612            if (startNew > end) startNew = end;
3613
3614            newItems = list.slice(startNew, Math.min(end, list.length));
3615            lastRenderedCount = Math.min(end, list.length);
3616
3617            if (paginationNav) paginationNav.style.display = "none";
3618            updateShowMoreButton(currentPage, totalPages, total === 0);
3619          } else if (enablePagination && cardsPerPage > 0 && total > cardsPerPage) {
3620            var totalPages2 = Math.max(
3621              1,
3622              Math.ceil(total / Math.max(1, cardsPerPage))
3623            );
3624            if (currentPage < 1) currentPage = 1;
3625            if (currentPage > totalPages2) currentPage = totalPages2;
3626
3627            var start = (currentPage - 1) * cardsPerPage;
3628            var end2 = start + cardsPerPage;
3629            pageItems = list.slice(start, end2);
3630
3631            renderPagination(total);
3632            if (showMoreBtn) showMoreBtn.style.display = "none";
3633          } else {
3634            if (paginationNav) paginationNav.style.display = "none";
3635            if (showMoreBtn) showMoreBtn.style.display = "none";
3636            lastRenderedCount = pageItems.length;
3637          }
3638
3639          // Rebuild DOM for visible cards only
3640          while (cardContainer.firstChild) {
3641            cardContainer.removeChild(cardContainer.firstChild);
3642          }
3643          var frag = document.createDocumentFragment();
3644          pageItems.forEach(function (card) {
3645            frag.appendChild(card);
3646          });
3647          cardContainer.appendChild(frag);
3648          
3649          // 1) Filtering / searching / sorting (any grid)
3650          //    We use the faster animation here.
3651          if (!keepPage && pageItems.length && !isFirstRender) {
3652            pageItems.forEach(function (card) {
3653              card.classList.add("rz-fade-in-fast");
3654              card.addEventListener("animationend", function handler() {
3655                card.classList.remove("rz-fade-in-fast");
3656                card.removeEventListener("animationend", handler);
3657              });
3658            });
3659          }
3660          
3661          // 2) Classic pagination (non-infinite grids):
3662          //    when moving between pages, use the main fade-zoom-up (.rz-fade-in).
3663          if (!infiniteScrollEnabled && enablePagination && keepPage && pageItems.length) {
3664            pageItems.forEach(function (card) {
3665              card.classList.add("rz-fade-in");
3666              card.addEventListener("animationend", function handler() {
3667                card.classList.remove("rz-fade-in");
3668                card.removeEventListener("animationend", handler);
3669              });
3670            });
3671          }
3672          
3673          // 3) Infinite "Show more":
3674          //    only animate the *newly appended* cards as they come in.
3675          if (infiniteScrollEnabled && keepPage && newItems.length) {
3676            newItems.forEach(function (card) {
3677              card.classList.add("rz-fade-in");
3678              card.addEventListener("animationend", function handler() {
3679                card.classList.remove("rz-fade-in");
3680                card.removeEventListener("animationend", handler);
3681              });
3682            });
3683          }
3684
3685
3686          // Highlight visible cards
3687          if (filters.tokensRaw.length) {
3688            pageItems.forEach(function (card) {
3689              highlightMatch(card, filters.tokensRaw);
3690            });
3691          }
3692
3693         showNoResults(total === 0);
3694         
3695         if (masonryContainer) {
3696           // If we already have a Masonry instance (either ours or RZ's),
3697           // just ask it to relayout. This keeps the inline height changing
3698           // smoothly so the CSS `transition: height` can animate it.
3699           if (masonryContainer._masonryInstance) {
3700             softMasonryRelayout(masonryContainer);
3701           } else {
3702             // First time (or if RZ hasn't wired it yet), schedule init.
3703             // After this runs once and Masonry attaches, future calls
3704             // will go through softMasonryRelayout above.
3705             scheduleMasonryRestore(masonryContainer);
3706           }
3707         }
3708         
3709         // ---- Smooth height transition for this grid (Masonry or not) ----
3710         if (!isFirstRender) {
3711           var newHeight = cardContainer.offsetHeight || 0;
3712         
3713           // Only animate if it actually changed
3714           if (previousHeight && newHeight && previousHeight !== newHeight) {
3715             // Start from the old height
3716             cardContainer.style.height = previousHeight + "px";
3717         
3718             // Force reflow so browser picks up the starting value
3719             void cardContainer.offsetHeight;
3720         
3721             // Animate to the new height (CSS handles timing)
3722             cardContainer.style.height = newHeight + "px";
3723         
3724             var onEnd = function (evt) {
3725               if (!evt || evt.propertyName === "height") {
3726                 cardContainer.style.height = ""; // back to auto
3727                 cardContainer.removeEventListener("transitionend", onEnd);
3728               }
3729             };
3730             cardContainer.addEventListener("transitionend", onEnd);
3731           }
3732         }
3733         
3734         isFirstRender = false;
3735
3736
3737        }
3738
3739        // "Show more" click handler (only infinite mode)
3740        if (infiniteScrollEnabled && cardsPerPage > 0) {
3741          var loadingMore = false;
3742          var btn = ensureShowMoreButton();
3743          if (btn) {
3744            // Add icon
3745            btn.innerHTML = 'Show more';
3746        
3747            btn.addEventListener("click", function () {
3748              if (loadingMore || btn.disabled) return;
3749            
3750              var result = filterAndSortCards();
3751              var total = result.filtered.length;
3752              if (!total) return;
3753            
3754              var maxPage = Math.max(
3755                1,
3756                Math.ceil(total / Math.max(1, cardsPerPage))
3757              );
3758            
3759              // If already at the end
3760              if (currentPage >= maxPage) {
3761                updateShowMoreButton(currentPage, maxPage, total === 0);
3762                return;
3763              }
3764            
3765              currentPage++;
3766            
3767              loadingMore = true;
3768              var prevCount = lastRenderedCount;
3769            
3770              // Spinner for Show more → at bottom
3771              showSpinner(true, true);
3772            
3773              requestAnimationFrame(function () {
3774                evaluateAndRender(true); // keepPage = true (fade/zoom only for new batch)
3775            
3776                // Hide spinner (still in Show more context, so same mode)
3777                showSpinner(false, true);
3778            
3779                loadingMore = false;
3780            
3781                // Delay scroll so layout settles
3782                setTimeout(function () {
3783                  scrollToNewItems(prevCount);
3784            
3785                  // If we've reached the end, disable button and update label
3786                  if (currentPage >= maxPage) {
3787                    btn.disabled = true;
3788                    btn.classList.add("disabled", "opacity-75");
3789                    btn.innerHTML = "End of list";
3790                  }
3791                }, 0);
3792              });
3793            });
3794          }
3795        }
3796
3797        // Events
3798        var debounceId;
3799
3800        function triggerFiltering() {
3801          showSpinner(true);
3802          clearTimeout(debounceId);
3803          debounceId = setTimeout(function () {
3804            requestAnimationFrame(function () {
3805              evaluateAndRender(false);
3806              showSpinner(false);
3807            });
3808          }, 160);
3809        }
3810
3811        buildCategoryOptionsOnce();
3812        buildSortOnce();
3813        evaluateAndRender(false); // initial render (no AOS for first batch)
3814
3815        if (searchInput) {
3816          searchInput.addEventListener("input", triggerFiltering);
3817          searchInput.addEventListener("search", triggerFiltering);
3818          searchInput.addEventListener("keydown", function (e) {
3819            if (e.key === "Enter") {
3820              e.preventDefault();
3821              triggerFiltering();
3822              return;
3823            }
3824            if (e.key === "Escape") {
3825              this.value = "";
3826              triggerFiltering();
3827            }
3828          });
3829        }
3830        
3831        var searchForm = wrapper.querySelector("form.rz-element-search-form");
3832        if (searchForm) {
3833          searchForm.addEventListener("submit", function (e) {
3834            e.preventDefault();
3835            triggerFiltering();
3836          });
3837        }
3838
3839        if (dateFromInput) dateFromInput.addEventListener("change", triggerFiltering);
3840        if (dateToInput) dateToInput.addEventListener("change", triggerFiltering);
3841      });
3842    });
3843  }
3844
3845  if (document.readyState === "loading") {
3846    document.addEventListener("DOMContentLoaded", initRZElementSearch);
3847  } else {
3848    initRZElementSearch();
3849  }
3850})();

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.