PageSourceSearch

https://www.bplus.co.th/assets/js/theme.js?v=20260923001

js bplus.co.th collected 2026-10-02 01:09:41 UTC 33,638 bytes, 937 lines download raw bytes

1'use strict';
2var theme = {
3  /**
4   * Theme's components/functions list
5   * Comment out or delete the unnecessary component.
6   * Some components have dependencies (plugins).
7   * Do not forget to remove dependency from src/js/vendor/ and recompile.
8   */
9  init: function () {
10    theme.pageLoad();
11    theme.stickyHeader();
12    theme.subMenu();
13    theme.offCanvas();
14    theme.isotope();
15    theme.onepageHeaderOffset();
16    theme.spyScroll();
17    theme.anchorSmoothScroll();
18    theme.svgInject();
19    theme.backgroundImage();
20    theme.backgroundImageMobile();
21    theme.imageHoverOverlay();
22    theme.rellax();
23    theme.scrollCue();
24    theme.swiperSlider();
25    theme.lightbox();
26    theme.plyr();
27    theme.progressBar();
28    theme.loader();
29    theme.pageProgress();
30    theme.counterUp();
31    theme.bsTooltips();
32    theme.bsPopovers();
33    theme.bsModal();
34    theme.iTooltip();
35    theme.forms();
36    theme.passVisibility();
37    theme.pricingSwitcher();
38    theme.textRotator();
39    theme.codeSnippet();
40    },
41    pageLoad: () => {
42        var collapsedBtn = document.querySelectorAll(".collapsed");
43        if (collapsedBtn == null) return;
44        collapsedBtn.forEach(e => {
45            e.addEventListener('click', event => {
46                event.preventDefault();
47            })
48        });
49
50        var accordionBtn = document.querySelectorAll(".accordion-button");
51        if (accordionBtn == null) return;
52        accordionBtn.forEach(e => {
53            e.addEventListener('click', event => {
54                event.preventDefault();
55            })
56        });
57    },
58  /**
59   * Sticky Header
60   * Enables sticky behavior on navbar on page scroll
61   * Requires assets/js/vendor/headhesive.min.js
62  */
63  stickyHeader: () => {
64    var navbar = document.querySelector(".navbar");
65    if (navbar == null) return;
66    var options = {
67      offset: 350,
68      offsetSide: 'top',
69      classes: {
70        clone: 'navbar-clone fixed',
71        stick: 'navbar-stick',
72        unstick: 'navbar-unstick',
73      },
74      onStick: function() {
75        var navbarClonedClass = this.clonedElem.classList;
76        if (navbarClonedClass.contains('transparent') && navbarClonedClass.contains('navbar-dark')) {
77          this.clonedElem.className = this.clonedElem.className.replace("navbar-dark","navbar-light");
78        }
79      }
80    };
81    var banner = new Headhesive('.navbar', options);
82  },
83  /**
84   * Sub Menus
85   * Enables multilevel dropdown
86   */
87  subMenu: () => {
88    (function($bs) {
89      const CLASS_NAME = 'has-child-dropdown-show';
90      $bs.Dropdown.prototype.toggle = function(_original) {
91          return function() {
92              document.querySelectorAll('.' + CLASS_NAME).forEach(function(e) {
93                  e.classList.remove(CLASS_NAME);
94              });
95              let dd = this._element.closest('.dropdown').parentNode.closest('.dropdown');
96              for (; dd && dd !== document; dd = dd.parentNode.closest('.dropdown')) {
97                  dd.classList.add(CLASS_NAME);
98              }
99              return _original.call(this);
100          }
101      }($bs.Dropdown.prototype.toggle);
102      document.querySelectorAll('.dropdown').forEach(function(dd) {
103          dd.addEventListener('hide.bs.dropdown', function(e) {
104              if (this.classList.contains(CLASS_NAME)) {
105                  this.classList.remove(CLASS_NAME);
106                  e.preventDefault();
107              }
108              e.stopPropagation();
109          });
110      });
111    })(bootstrap);
112  },
113  /**
114   * Offcanvas
115   * Enables offcanvas-nav, closes offcanvas on anchor clicks, focuses on input in search offcanvas
116   */
117  offCanvas: () => {
118    var navbar = document.querySelector(".navbar");
119    if (navbar == null) return;
120    const navOffCanvasBtn = document.querySelectorAll(".offcanvas-nav-btn");
121    const navOffCanvas = document.querySelector('.navbar:not(.navbar-clone) .offcanvas-nav');
122    const bsOffCanvas = new bootstrap.Offcanvas(navOffCanvas, {scroll: true});
123    const scrollLink = document.querySelectorAll('.onepage .navbar li a.scroll');
124    const searchOffcanvas = document.getElementById('offcanvas-search');
125    navOffCanvasBtn.forEach(e => {
126      e.addEventListener('click', event => {
127        event.preventDefault();
128        bsOffCanvas.show();
129      })
130    });
131    scrollLink.forEach(e => {
132      e.addEventListener('click', event => {
133        bsOffCanvas.hide();
134      })
135    });
136    if(searchOffcanvas != null) {
137      searchOffcanvas.addEventListener('shown.bs.offcanvas', function () {
138        document.getElementById("search-form").focus();
139      });
140    }
141  },
142  /**
143   * Isotope
144   * Enables isotope grid layout and filtering
145   * Requires assets/js/vendor/isotope.pkgd.min.js
146   * Requires assets/js/vendor/imagesloaded.pkgd.min.js
147   */
148  isotope: () => {
149    var grids = document.querySelectorAll('.grid');
150    if(grids != null) {
151      grids.forEach(g => {
152        var grid = g.querySelector('.isotope');
153        var filtersElem = g.querySelector('.isotope-filter');
154        var buttonGroups = g.querySelectorAll('.isotope-filter');
155        var iso = new Isotope(grid, {
156          itemSelector: '.item',
157          layoutMode: 'masonry',
158          masonry: {
159            columnWidth: grid.offsetWidth / 12
160          },
161          percentPosition: true,
162          transitionDuration: '0.7s'
163        });
164        imagesLoaded(grid).on("progress", function() {
165          iso.layout({
166            masonry: {
167              columnWidth: grid.offsetWidth / 12
168            }
169          })
170        }),
171        window.addEventListener("resize", function() {
172          iso.arrange({
173            masonry: {
174              columnWidth: grid.offsetWidth / 12
175            }
176          });
177        }, true);
178        if(filtersElem != null) {
179          filtersElem.addEventListener('click', function(event) {
180            if(!matchesSelector(event.target, '.filter-item')) {
181              return;
182            }
183            var filterValue = event.target.getAttribute('data-filter');
184            iso.arrange({
185              filter: filterValue
186            });
187          });
188          for(var i = 0, len = buttonGroups.length; i < len; i++) {
189            var buttonGroup = buttonGroups[i];
190            buttonGroup.addEventListener('click', function(event) {
191              if(!matchesSelector(event.target, '.filter-item')) {
192                return;
193              }
194              buttonGroup.querySelector('.active').classList.remove('active');
195              event.target.classList.add('active');
196            });
197          }
198        }
199      });
200    }
201  },
202  /**
203   * Onepage Header Offset
204   * Adds an offset value to anchor point equal to sticky header height on a onepage
205   */
206  onepageHeaderOffset: () => {
207    var navbar = document.querySelector(".navbar");
208    if (navbar == null) return;
209    const header_height = document.querySelector(".navbar").offsetHeight;
210    const shrinked_header_height = 75;
211    const sections = document.querySelectorAll(".onepage section");
212    sections.forEach(section => {
213      section.style.paddingTop = shrinked_header_height + 'px';
214      section.style.marginTop = '-' + shrinked_header_height + 'px';
215    });
216    const first_section = document.querySelector(".onepage section:first-of-type");
217    if(first_section != null) {
218      first_section.style.paddingTop = header_height + 'px';
219      first_section.style.marginTop = '-' + header_height + 'px';
220    }
221  },
222  /**
223   * Spy Scroll
224   * Highlights the active nav link while scrolling through sections
225   */
226  spyScroll: () => {
227    let section = document.querySelectorAll('section[id]');
228    let navLinks = document.querySelectorAll('.nav-link.scroll');
229    window.onscroll = () => {
230      section.forEach(sec => {
231        let top = window.scrollY; //returns the number of pixels that the document is currently scrolled vertically.
232        let offset = sec.offsetTop - 0; //returns the distance of the outer border of the current element relative to the inner border of the top of the offsetParent, the closest positioned ancestor element
233        let height = sec.offsetHeight; //returns the height of an element, including vertical padding and borders, as an integer
234        let id = sec.getAttribute('id'); //gets the value of an attribute of an element
235        if (top >= offset && top < offset + height) {
236          navLinks.forEach(links => {
237            links.classList.remove('active');
238            document.querySelector(`.nav-link.scroll[href*=${id}]`).classList.add('active');
239            //[att*=val] Represents an element with the att attribute whose value contains at least one instance of the substring "val". If "val" is the empty string then the selector does not represent anything.
240          });
241        }
242      });
243    }
244  },
245  /**
246   * Anchor Smooth Scroll
247   * Adds smooth scroll animation to links with .scroll class
248   * Requires assets/js/vendor/smoothscroll.js
249   */
250  anchorSmoothScroll: () => {
251    const links = document.querySelectorAll(".scroll");
252    for(const link of links) {
253      link.addEventListener("click", clickHandler);
254    }
255    function clickHandler(e) {
256      e.preventDefault();
257      this.blur();
258      const href = this.getAttribute("href");
259      const offsetTop = document.querySelector(href).offsetTop;
260      scroll({
261        top: offsetTop,
262        behavior: "smooth"
263      });
264    }
265  },
266  /**
267   * SVGInject
268   * Replaces an img element with an inline SVG so you can apply colors to your SVGs
269   * Requires assets/js/vendor/svg-inject.min.js
270   */
271  svgInject: () => {
272    SVGInject.setOptions({
273      onFail: function(img, svg) {
274        img.classList.remove('svg-inject');
275      }
276    });
277    document.addEventListener('DOMContentLoaded', function() {
278      SVGInject(document.querySelectorAll('img.svg-inject'), {
279        useCache: true
280      });
281    });
282  },
283  /**
284   * Background Image
285   * Adds a background image link via data attribute "data-image-src"
286   */
287  backgroundImage: () => {
288    var bg = document.querySelectorAll(".bg-image");
289    for(var i = 0; i < bg.length; i++) {
290      var url = bg[i].getAttribute('data-image-src');
291      bg[i].style.backgroundImage = "url('" + url + "')";
292    }
293  },
294  /**
295   * Background Image Mobile
296   * Adds .mobile class to background images on mobile devices for styling purposes
297   */
298  backgroundImageMobile: () => {
299    var isMobile = (navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i)) ? true : false;
300    if(isMobile) {
301      document.querySelectorAll(".image-wrapper").forEach(e => {
302        e.classList.add("mobile")
303      })
304    }
305  },
306  /**
307   * Image Hover Overlay
308   * Adds span.bg inside .overlay for simpler markup and styling purposes
309   */
310  imageHoverOverlay: () => {
311    var overlay = document.querySelectorAll('.overlay > a, .overlay > span');
312    for(var i = 0; i < overlay.length; i++) {
313      var overlay_bg = document.createElement('span');
314      overlay_bg.className = "bg";
315      overlay[i].appendChild(overlay_bg);
316    }
317  },
318  /**
319   * Rellax.js
320   * Adds parallax animation to shapes and elements
321   * Requires assets/js/vendor/rellax.min.js
322   */
323  rellax: () => {
324    if(document.querySelector(".rellax") != null) {
325      window.onload = function() {
326        var rellax = new Rellax('.rellax', {
327          speed: 2,
328          center: true,
329          breakpoints: [576, 992, 1201]
330        });
331        var projects_overflow = document.querySelectorAll('.projects-overflow');
332        imagesLoaded(projects_overflow, function() {
333          rellax.refresh();
334        });
335      }
336    }
337  },
338  /**
339   * scrollCue.js
340   * Enables showing elements by scrolling
341   * Requires assets/js/vendor/scrollCue.min.js
342   */
343  scrollCue: () => {
344    scrollCue.init({
345      interval: -400,
346      duration: 700,
347      percentage: 0.8
348    });
349    scrollCue.update();
350  },
351  /**
352   * Swiper Slider
353   * Enables carousels and sliders
354   * Requires assets/js/vendor/swiper-bundle.min.js
355   */
356  swiperSlider: function() {
357    var carousel = document.querySelectorAll('.swiper-container');
358    for(var i = 0; i < carousel.length; i++) {
359      var slider1 = carousel[i];
360      slider1.classList.add('swiper-container-' + i);
361      var controls = document.createElement('div');
362      controls.className = "swiper-controls";
363      var pagi = document.createElement('div');
364      pagi.className = "swiper-pagination";
365      var navi = document.createElement('div');
366      navi.className = "swiper-navigation";
367      var prev = document.createElement('div');
368      prev.className = "swiper-button swiper-button-prev";
369      var next = document.createElement('div');
370      next.className = "swiper-button swiper-button-next";
371      slider1.appendChild(controls);
372      controls.appendChild(navi);
373      navi.appendChild(prev);
374      navi.appendChild(next);
375      controls.appendChild(pagi);
376      var sliderEffect = slider1.getAttribute('data-effect') ? slider1.getAttribute('data-effect') : 'slide';
377      if (slider1.getAttribute('data-items-auto') === 'true') {
378        var slidesPerViewInit = "auto";
379        var breakpointsInit = null;
380      } else {
381        var sliderItems = slider1.getAttribute('data-items') ? slider1.getAttribute('data-items') : 3; // items in all devices
382        var sliderItemsXs = slider1.getAttribute('data-items-xs') ? slider1.getAttribute('data-items-xs') : 1; // start - 575
383        var sliderItemsSm = slider1.getAttribute('data-items-sm') ? slider1.getAttribute('data-items-sm') : Number(sliderItemsXs); // 576 - 767
384        var sliderItemsMd = slider1.getAttribute('data-items-md') ? slider1.getAttribute('data-items-md') : Number(sliderItemsSm); // 768 - 991
385        var sliderItemsLg = slider1.getAttribute('data-items-lg') ? slider1.getAttribute('data-items-lg') : Number(sliderItemsMd); // 992 - 1199
386        var sliderItemsXl = slider1.getAttribute('data-items-xl') ? slider1.getAttribute('data-items-xl') 
386: Number(sliderItemsLg); // 1200 - end
387        var sliderItemsXxl = slider1.getAttribute('data-items-xxl') ? slider1.getAttribute('data-items-xxl') : Number(sliderItemsXl); // 1500 - end
388        var slidesPerViewInit = sliderItems;
389        var breakpointsInit = {
390          0: {
391            slidesPerView: Number(sliderItemsXs)
392          },
393          576: {
394            slidesPerView: Number(sliderItemsSm)
395          },
396          768: {
397            slidesPerView: Number(sliderItemsMd)
398          },
399          992: {
400            slidesPerView: Number(sliderItemsLg)
401          },
402          1200: {
403            slidesPerView: Number(sliderItemsXl)
404          },
405          1400: {
406            slidesPerView: Number(sliderItemsXxl)
407          }
408        }
409      }
410      var sliderSpeed = slider1.getAttribute('data-speed') ? slider1.getAttribute('data-speed') : 500;
411      var sliderAutoPlay = slider1.getAttribute('data-autoplay') !== 'false';
412      var sliderAutoPlayTime = slider1.getAttribute('data-autoplaytime') ? slider1.getAttribute('data-autoplaytime') : 5000;
413      var sliderAutoHeight = slider1.getAttribute('data-autoheight') === 'true';
414      var sliderResizeUpdate = slider1.getAttribute('data-resizeupdate') !== 'false';
415      var sliderAllowTouchMove = slider1.getAttribute('data-drag') !== 'false';
416      var sliderReverseDirection = slider1.getAttribute('data-reverse') === 'true';
417      var sliderMargin = slider1.getAttribute('data-margin') ? slider1.getAttribute('data-margin') : 30;
418      var sliderLoop = slider1.getAttribute('data-loop') === 'true';
419      var sliderCentered = slider1.getAttribute('data-centered') === 'true';
420      var sliderWatchOverflow = slider1.getAttribute('data-watchoverflow') !== 'false';
421      var swiper = slider1.querySelector('.swiper:not(.swiper-thumbs)');
422      var swiperTh = slider1.querySelector('.swiper-thumbs');
423      var sliderTh = new Swiper(swiperTh, {
424        slidesPerView: 5,
425        spaceBetween: 10,
426        loop: false,
427        threshold: 2,
428        slideToClickedSlide: true
429      });
430      if (slider1.getAttribute('data-thumbs') === 'true') {
431        var thumbsInit = sliderTh;
432        var swiperMain = document.createElement('div');
433        swiperMain.className = "swiper-main";
434        swiper.parentNode.insertBefore(swiperMain, swiper);
435        swiperMain.appendChild(swiper);
436        slider1.removeChild(controls);
437        swiperMain.appendChild(controls);
438      } else {
439        var thumbsInit = null;
440      }
441      var slider = new Swiper(swiper, {
442        on: {
443          beforeInit: function() {
444            if(slider1.getAttribute('data-nav') !== 'true' && slider1.getAttribute('data-dots') !== 'true') {
445              controls.remove();
446            }
447            if(slider1.getAttribute('data-dots') !== 'true') {
448              pagi.remove();
449            }
450            if(slider1.getAttribute('data-nav') !== 'true') {
451              navi.remove();
452            }
453          },
454          init: function() {
455            if(slider1.getAttribute('data-autoplay') !== 'true') {
456              this.autoplay.stop();
457            }
458            this.update();
459          }
460        },
461        autoplay: {
462          delay: sliderAutoPlayTime,
463          disableOnInteraction: false,
464          reverseDirection: sliderReverseDirection,
465          // opt-in per slider: data-pauseonhover="true" (promotion page S2, 2026-09-23); every other slider keeps the old behaviour
466          pauseOnMouseEnter: slider1.getAttribute('data-pauseonhover') === 'true'
467        },
468        allowTouchMove: sliderAllowTouchMove,
469        speed: parseInt(sliderSpeed),
470        slidesPerView: slidesPerViewInit,
471        loop: sliderLoop,
472        centeredSlides: sliderCentered,
473        spaceBetween: Number(sliderMargin),
474        effect: sliderEffect,
475        autoHeight: sliderAutoHeight,
476        watchOverflow: sliderWatchOverflow,
477        grabCursor: true,
478        resizeObserver: false,
479        updateOnWindowResize: sliderResizeUpdate,
480        breakpoints: breakpointsInit,
481        pagination: {
482          el: carousel[i].querySelector('.swiper-pagination'),
483          clickable: true
484        },
485        navigation: {
486          prevEl: slider1.querySelector('.swiper-button-prev'),
487          nextEl: slider1.querySelector('.swiper-button-next'),
488        },
489        thumbs: {
490          swiper: thumbsInit,
491        },
492      });
493    }
494  },
495  /**
496   * GLightbox
497   * Enables lightbox functionality
498   * Requires assets/js/vendor/glightbox.js
499   */
500  lightbox: () => {
501    const lightbox = GLightbox({
502      selector: '*[data-glightbox]',
503      touchNavigation: true,
504      loop: false,
505      zoomable: false,
506      autoplayVideos: true,
507      moreLength: 0,
508      slideExtraAttributes: {
509        poster: ''
510      },
511      plyr: {
512        css: '',
513        js: '',
514        config: {
515          ratio: '',
516          fullscreen: {
517            enabled: false,
518            iosNative: false
519          },
520          youtube: {
521            noCookie: true,
522            rel: 0,
523            showinfo: 0,
524            iv_load_policy: 3
525          },
526          vimeo: {
527            byline: false,
528            portrait: false,
529            title: false,
530            transparent: false
531          }
532        }
533      },
534    });
535  },
536  /**
537   * Plyr
538   * Enables media player
539   * Requires assets/js/vendor/plyr.js
540   */
541  plyr: () => {
542    var players = Plyr.setup('.player', {
543      loadSprite: true,
544      fullscreen: { enabled: true, iosNative: true },
545      muted: false
546    });
547  },
548  /**
549   * Progressbar
550   * Enables animated progressbars
551   * Requires assets/js/vendor/progressbar.min.js
552   * Requires assets/js/vendor/noframework.waypoints.min.js
553   */
554  progressBar: () => {
555    const pline = document.querySelectorAll(".progressbar.line");
556    const pcircle = document.querySelectorAll(".progressbar.semi-circle");
557    pline.forEach(e => {
558      var line = new ProgressBar.Line(e, {
559        strokeWidth: 6,
560        trailWidth: 6,
561        duration: 3000,
562        easing: 'easeInOut',
563        text: {
564          style: {
565            color: 'inherit',
566            position: 'absolute',
567            right: '0',
568            top: '-30px',
569            padding: 0,
570            margin: 0,
571            transform: null
572          },
573          autoStyleContainer: false
574        },
575        step: (state, line) => {
576          line.setText(Math.round(line.value() * 100) + ' %');
577        }
578      });
579      var value = e.getAttribute('data-value') / 100;
580      new Waypoint({
581        element: e,
582        handler: function() {
583          line.animate(value);
584        },
585        offset: 'bottom-in-view',
586      })
587    });
588    pcircle.forEach(e => {
589      var circle = new ProgressBar.SemiCircle(e, {
590        strokeWidth: 6,
591        trailWidth: 6,
592        duration: 2000,
593        easing: 'easeInOut',
594        step: (state, circle) => {
595          circle.setText(Math.round(circle.value() * 100));
596        }
597      });
598      var value = e.getAttribute('data-value') / 100;
599      new Waypoint({
600        element: e,
601        handler: function() {
602          circle.animate(value);
603        },
604        offset: 'bottom-in-view',
605      })
606    });
607  },
608  /**
609   * Loader
610   * 
611   */
612  loader: () => {
613    var preloader = document.querySelector('.page-loader');
614    if(preloader != null) {
615      document.body.onload = function(){
616        setTimeout(function() {
617          if( !preloader.classList.contains('done') )
618          {
619            preloader.classList.add('done');
620          }
621        }, 1000)
622      }
623    }
624  },
625  /**
626   * Page Progress
627   * Shows page progress on the bottom right corner of pages
628   */
629  pageProgress: () => {
630    var progressWrap = document.querySelector('.progress-wrap');
631    if(progressWrap != null) {
632      var progressPath = document.querySelector('.progress-wrap path');
633      var pathLength = progressPath.getTotalLength();
634      var offset = 50;
635      progressPath.style.transition = progressPath.style.WebkitTransition = 'none';
636      progressPath.style.strokeDasharray = pathLength + ' ' + pathLength;
637      progressPath.style.strokeDashoffset = pathLength;
638      progressPath.getBoundingClientRect();
639      progressPath.style.transition = progressPath.style.WebkitTransition = 'stroke-dashoffset 10ms linear';
640      window.addEventListener("scroll", function(event) {
641        var scroll = document.body.scrollTop || document.documentElement.scrollTop;
642        var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
643        var progress = pathLength - (scroll * pathLength / height);
644        progressPath.style.strokeDashoffset = progress;
645        var scrollElementPos = document.body.scrollTop || document.documentElement.scrollTop;
646        if(scrollElementPos >= offset) {
647          progressWrap.classList.add("active-progress")
648        } else {
649          progressWrap.classList.remove("active-progress")
650        }
651      });
652      progressWrap.addEventListener('click', function(e) {
653        e.preventDefault();
654        window.scroll({
655          top: 0, 
656          left: 0,
657          behavior: 'smooth'
658        });
659      });
660    }
661  },
662  /**
663   * Counter Up
664   * Counts up to a targeted number when the number becomes visible
665   * Requires assets/js/vendor/counterup.min.js
666   * Requires assets/js/vendor/noframework.waypoints.min.js
667   */
668  counterUp: () => {
669    var counterUp = window.counterUp["default"];
670    const counters = document.querySelectorAll(".counter");
671    counters.forEach(el => {
672      new Waypoint({
673        element: el,
674        handler: function() {
675          counterUp(el, {
676            duration: 1000,
677            delay: 50
678          })
679          this.destroy()
680        },
681        offset: 'bottom-in-view',
682      })
683    });
684  },
685  /**
686   * Bootstrap Tooltips
687   * Enables Bootstrap tooltips
688   * Requires Poppers library
689   */
690  bsTooltips: () => {
691    var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
692    var tooltipList = tooltipTriggerList.map(function(tooltipTriggerEl) {
693      return new bootstrap.Tooltip(tooltipTriggerEl, {
694        trigger: 'hover'
695      })
696    });
697    var tooltipTriggerWhite = [].slice.call(document.querySelectorAll('[data-bs-toggle="white-tooltip"]'))
698    var tooltipWhite = tooltipTriggerWhite.map(function(tooltipTriggerEl) {
699      return new bootstrap.Tooltip(tooltipTriggerEl, {
700        customClass: 'white-tooltip',
701        trigger: 'hover',
702        placement: 'left'
703      })
704    })
705  },
706  /**
707   * Bootstrap Popovers
708   * Enables Bootstrap popovers
709   * Requires Poppers library
710   */
711  bsPopovers: () => {
712    var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
713    var popoverList = popoverTriggerList.map(function(popoverTriggerEl) {
714      return new bootstrap.Popover(popoverTriggerEl)
715    })
716  },
717  /**
718   * Bootstrap Modal
719   * Enables Bootstrap modal popup
720   */
721  bsModal: () => {
722    if(document.querySelector(".modal-popup") != null) {
723      var myModalPopup = new bootstrap.Modal(document.querySelector('.modal-popup'));
724      setTimeout(function() {
725        myModalPopup.show();
726      }, 200);
727    }
728    // Fixes jumping of page progress caused by modal
729    var innerWidth = window.innerWidth;
730    var clientWidth = document.body.clientWidth;
731    var scrollSize = innerWidth - clientWidth;
732    var myModalEl = document.querySelectorAll('.modal');
733    var navbarFixed = document.querySelector('.navbar.fixed');
734    var pageProgress = document.querySelector('.progress-wrap');
735    function setPadding() {
736      if(navbarFixed != null) {
737        navbarFixed.style.paddingRight = scrollSize + 'px';
738      }
739      if(pageProgress != null) {
740        pageProgress.style.marginRight = scrollSize + 'px';
741      }
742    }
743    function removePadding() {
744      if(navbarFixed != null) {
745        navbarFixed.style.paddingRight = '';
746      }
747      if(pageProgress != null) {
748       pageProgress.style.marginRight = '';
749      }
750    }
751    myModalEl.forEach(myModalEl => {
752      myModalEl.addEventListener('show.bs.modal', function(e) {
753        setPadding();
754      })
755      myModalEl.addEventListener('hidden.bs.modal', function(e) {
756        removePadding();
757      })
758    });
759  },
760  /**
761   * iTooltip
762   * Enables custom tooltip style for image hover docs/elements/hover.html
763   * Requires assets/js/vendor/itooltip.min.js
764   */
765  iTooltip: () => {
766    var tooltip = new iTooltip('.itooltip')
767    tooltip.init({
768      className: 'itooltip-inner',
769      indentX: 15,
770      indentY: 15,
771      positionX: 'right',
772      positionY: 'bottom'
773    })
774  },
775  /**
776   * Form Validation and Contact Form submit
777   * Bootstrap validation - Only sends messages if form has class ".contact-form" and is validated and shows success/fail messages
778   */
779  forms: () => {
780    (function() {
781      "use strict";
782      window.addEventListener("load", function() {
783        var forms = document.querySelectorAll(".needs-validation");
784        var inputRecaptcha = document.querySelector("input[data-recaptcha]"); 
785        window.verifyRecaptchaCallback = function (response) {
786          inputRecaptcha.value = response; 
787          inputRecaptcha.dispatchEvent(new Event("change"));
788        }
789        window.expiredRecaptchaCallback = function () {
790          var inputRecaptcha = document.querySelector("input[data-recaptcha]"); 
791          inputRecaptcha.value = ""; 
792          inputRecaptcha.dispatchEvent(new Event("change"));
793        }
794        var validation = Array.prototype.filter.call(forms, function(form) {
795          form.addEventListener("submit", function(event) {
796            if(form.checkValidity() === false) {
797              event.preventDefault();
798              event.stopPropagation();
799            }
800            form.classList.add("was-validated");
801            if(form.checkValidity() === true) {
802              event.preventDefault();
803              form.classList.remove("was-validated");
804              // Send message only if the form has class .contact-form
805              var isContactForm = form.classList.contains('contact-form');
806              if(isContactForm) {
807                var data = new FormData(form);
808                var alertClass = 'alert-danger';
809                fetch("assets/php/contact.php", {
810                  method: "post",
811                  body: data
812                }).then((data) => {
813                  if(data.ok) {
814                    alertClass = 'alert-success';
815                  }
816                  return data.text();
817                }).then((txt) => {
818                  var alertBox = '<div class="alert ' + alertClass + ' alert-dismissible fade show"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' + txt + '</div>';
819                  if(alertClass && txt) {
820                    form.querySelector(".messages").insertAdjacentHTML('beforeend', alertBox);
821                    form.reset();
822                    grecaptcha.reset();
823                  }
824                }).catch((err) => {
825                  console.log(err);
826                });
827              }
828            }
829          }, false);
830        });
831      }, false);
832    })();
833  },
834  /**
835   * Password Visibility Toggle
836   * Toggles password visibility in password input
837   */
838  passVisibility: () => {
839    let pass = document.querySelectorAll('.password-field');
840    for (let i = 0; i < pass.length; i++) {
841      let passInput = pass[i].querySelector('.form-control');
842      let passToggle = pass[i].querySelector('.password-toggle > i');
843      passToggle.addEventListener('click', (e) => {
844        if (passInput.type === "password") {
845          passInput.type = "text";
846          passToggle.classList.remove('uil-eye');
847          passToggle.classList.add('uil-eye-slash');
848        } else {
849          passInput.type = "password";
850          passToggle.classList.remove('uil-eye-slash'); 
851          passToggle.classList.add('uil-eye');
852        } 
853      }, false);
854    }
855  },
856  /**
857   * Pricing Switcher
858   * Enables monthly/yearly switcher seen on pricing tables
859   */
860  pricingSwitcher: () => {
861    if(document.querySelector(".pricing-switchers") != null) {
862      const wrapper = document.querySelectorAll(".pricing-wrapper");
863      wrapper.forEach(wrap => {
864        const switchers = wrap.querySelector(".pricing-switchers");
865        const switcher = wrap.querySelectorAll(".pricing-switcher");
866        const price = wrap.querySelectorAll(".price");
867        switchers.addEventListener("click", (e) => {
868          switcher.forEach(s => {
869            s.classList.toggle("pricing-switcher-active");
870          });
871          price.forEach(p => {
872            p.classList.remove("price-hidden");
873            p.classList.toggle("price-show");
874            p.classList.toggle("price-hide");
875          });
876        });
877      });
878    }
879  },
880  /**
881   * ReplaceMe.js
882   * Enables text rotator
883   * Requires assets/js/vendor/replaceme.min.js
884   */
885  textRotator: () => {
886    if(document.querySelector(".rotator-zoom") != null) {
887      var replace = new ReplaceMe(document.querySelector('.rotator-zoom'), {
888        animation: 'animate__animated animate__zoomIn',
889        speed: 2500,
890        separator: ',',
891        clickChange: false,
892        loopCount: 'infinite'
893      });
894    }
895    if(document.querySelector(".rotator-fade") != null) {
896      var replace = new ReplaceMe(document.querySelector('.rotator-fade'), {
897        animation: 'animate__animated animate__fadeInDown',
898        speed: 2500,
899        separator: ',',
900        clickChange: false,
901        loopCount: 'infinite'
902      });
903    }
904  },
905  /**
906   * Clipboard.js
907   * Enables clipboard on docs
908   * Requires assets/js/vendor/clipboard.min.js
909   */
910  codeSnippet: () => {
911    var btnHtml = '<button type="button" class="btn btn-sm btn-white rounded-pill btn-clipboard">Copy</button>'
912    document.querySelectorAll('.code-wrapper-inner').forEach(function(element) {
913      element.insertAdjacentHTML('beforebegin', btnHtml)
914    })
915    var clipboard = new ClipboardJS('.btn-clipboard', {
916      target: function(trigger) {
917        return trigger.nextElementSibling
918      }
919    })
920    clipboard.on('success', event => {
921      event.trigger.textContent = 'Copied!';
922      event.clearSelection();
923      setTimeout(function () {
924        event.trigger.textContent = 'Copy';
925      }, 2000);
926    });
927    var copyIconCode = new ClipboardJS('.btn-copy-icon');
928    copyIconCode.on('success', function(event) {
929      event.clearSelection();
930      event.trigger.textContent = 'Copied!';
931      window.setTimeout(function() {
932        event.trigger.textContent = 'Copy';
933      }, 2300);
934    });
935  },
936}
937theme.init();

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.