PluginProbe
Easy Elements for Elementor – Addons & Website Templates / 1.5.3
Easy Elements for Elementor – Addons & Website Templates v1.5.3
1.5.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.6 1.4.7 1.4.8 1.4.5 1.4.4 1.4.3 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 All 47 releases
easy-elements / assets / js / custom.js

custom.js in Easy Elements for Elementor – Addons & Website Templates 1.5.3, at assets/js/custom.js

1,639 lines 59.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function($) {
2 "use strict";
3 function initBackPostSlider($scope) {
4 var $slider = $scope.find('.eel-all-slider');
5 if (!$slider.length) {
6 return;
7 }
8 // The 'global' hook also fires for the section/container that wraps the
9 // widgets, so when two slider widgets sit together $scope can match more
10 // than one .eel-all-slider. Handle each one in its own widget scope to
11 // avoid mixing slides/navigation between the two sliders.
12 if ($slider.length > 1) {
13 $slider.each(function () {
14 var $own = $(this).closest('.elementor-widget');
15 initBackPostSlider($own.length ? $own : $(this).parent());
16 });
17 return;
18 }
19 var loop = $slider.data('loop') === true || $slider.data('loop') === 'true';
20 var autoplay = $slider.data('autoplay') === true || $slider.data('autoplay') === 'true';
21 var autoplayDelay = parseInt($slider.data('autoplay-delay'), 10) || 3000;
22 var slidesPerView = parseInt($slider.data('slides-per-view'), 10) || 3;
23 var spvSkin1 = parseInt($slider.data('slides-per-view-skin1'), 10);
24 var spvSkin4 = parseInt($slider.data('slides-per-view-skin4'), 10);
25 var spvSkin11 = parseInt($slider.data('slides-per-view-skin11'), 10);
26 var spvSpecificDefault = parseInt($slider.data('slides-per-view-default'), 10);
27 var finalSlidesPerView;
28 if (spvSkin1) {
29 finalSlidesPerView = spvSkin1;
30 } else if (spvSkin4) {
31 finalSlidesPerView = spvSkin4;
32 } else if (spvSkin11) {
33 finalSlidesPerView = spvSkin11;
34 } else if (spvSpecificDefault) {
35 finalSlidesPerView = spvSpecificDefault;
36 } else {
37 finalSlidesPerView = slidesPerView;
38 }
39 var speed = parseInt($slider.data('speed'), 10) || 600;
40 var centeredSlides = $slider.data('centered-slides') === true || $slider.data('centered-slides') === 'true';
41 var freeMode = $slider.data('free-mode') === true || $slider.data('free-mode') === 'true';
42 var spaceBetween = parseInt($slider.data('space-between'), 10) || 0;
43 var slidesPerViewTablet = parseInt($slider.data('slides-per-view-tablet'), 10) || 2;
44 var slidesPerViewMobile = parseInt($slider.data('slides-per-view-mobile'), 10) || 1;
45
46 // Image carousel Skin 05: one large image on top, thumbnail strip below.
47 var eelCarouselThumbs = $slider.hasClass('eel-carousel-thumbs-on');
48 if (eelCarouselThumbs) {
49 slidesPerView = 1;
50 finalSlidesPerView = 1;
51 slidesPerViewTablet = 1;
52 slidesPerViewMobile = 1;
53 centeredSlides = false;
54 }
55 var paginationType = $slider.data('pagination') !== 'none' ? $slider.data('pagination') : false;
56 var navigation = $slider.data('navigation') === true || $slider.data('navigation') === 'true';
57 var effect = $slider.data('effect') || 'slide';
58 var cubeShadow = $slider.data('cube-shadow') === true || $slider.data('cube-shadow') === 'true';
59 var coverflowRotate = parseInt($slider.data('coverflow-rotate'), 10) || 50;
60 var coverflowStretch = parseInt($slider.data('coverflow-stretch'), 10) || 0;
61
62 // Find navigation elements within the scope
63 var $nextButton = $scope.find('.swiper-next');
64 var $prevButton = $scope.find('.swiper-prev');
65 var $pagination = $scope.find('.swiper-pagination');
66
67 // Prevent multiple instances and duplicated handlers if Elementor re-runs init
68 if ($slider[0] && $slider[0].swiper) {
69 try { $slider[0].swiper.destroy(true, true); } catch (e) {}
70 }
71 if ($nextButton.length) { $nextButton.off('click.easyStopAuto click.easyFadeNav'); }
72 if ($prevButton.length) { $prevButton.off('click.easyStopAuto click.easyFadeNav'); }
73
74 var hasProgressBar = $pagination.hasClass('swiper-pagination-progressbar');
75 var kbDirection = $slider.data('kenburns-direction') || 'in';
76
77 var effectOptions = {};
78
79 if (effect === 'cube') {
80 effectOptions = {
81 cubeEffect: {
82 shadow: cubeShadow,
83 slideShadows: cubeShadow,
84 shadowOffset: 20,
85 shadowScale: 0.94,
86 }
87 };
88 } else if (effect === 'coverflow') {
89 effectOptions = {
90 coverflowEffect: {
91 rotate: coverflowRotate,
92 stretch: coverflowStretch,
93 depth: 100,
94 modifier: 1,
95 slideShadows: true,
96 }
97 };
98 } else if (effect === 'flip') {
99 effectOptions = {
100 flipEffect: {
101 slideShadows: true,
102 limitRotation: true,
103 }
104 };
105 } else if (effect === 'cards') {
106 effectOptions = {
107 cardsEffect: {
108 slideShadows: true,
109 rotate: true,
110 perSlideOffset: 8,
111 }
112 };
113 } else if (effect === 'creative') {
114 var creativeStyle = $slider.data('creative-style') || 'default';
115 if (creativeStyle === 'zoom') {
116 effectOptions = {
117 creativeEffect: {
118 limitProgress: true,
119 prev: { scale: 1.1, opacity: 0, translate: [0, 0, 0] },
120 next: { scale: 1.6, opacity: 0, translate: [0, 0, 0] },
121 },
122 };
123 } else {
124 effectOptions = {
125 creativeEffect: {
126 limitProgress: true,
127 prev: { shadow: true, translate: [0, 0, -400] },
128 next: { translate: ['100%', 0, 0] },
129 },
130 };
131 }
132 } else if (effect === 'fade') {
133 effectOptions = {
134 fadeEffect: { crossFade: true }
135 };
136 } else if (effect === 'kenburns') {
137 effectOptions = {
138 effect: 'fade',
139 fadeEffect: { crossFade: true },
140 on: {
141 init: function () {
142 var swiper = this;
143 setTimeout(function() {
144 var $activeSlide = $(swiper.el).find('.swiper-slide-active');
145 $activeSlide.find('.hero-content-wrapper').addClass('eel-text-animated');
146 $activeSlide.find('.hero-slide-background img').addClass('eel-kb-active-' + kbDirection);
147 }, 200);
148 },
149 slideChange: function () {
150 var $allSlides = $(this.el).find('.swiper-slide');
151 $allSlides.find('.hero-content-wrapper').removeClass('eel-text-animated');
152 $allSlides.find('.hero-slide-background img').removeClass('eel-kb-active-in eel-kb-active-out');
153 },
154 transitionEnd: function () {
155 var swiper = this;
156 var $activeSlide = $(swiper.el).find('.swiper-slide-active');
157 $activeSlide.find('.hero-content-wrapper').addClass('eel-text-animated');
158 $activeSlide.find('.hero-slide-background img').addClass('eel-kb-active-' + kbDirection);
159 }
160 }
161 };
162 }
163
164 // Loop handling tuned to avoid double-advance on loop edges
165 var totalSlides = $slider.find('.swiper-slide').length;
166 var isFade = effect === 'fade';
167 // Skin 11 shows one testimonial per view, so use its effective slides-per-view for the loop math.
168 var isSkin11ForLoop = $slider.find('.ee--tstml-inner-wrap.skin11').length > 0;
169 var visibleSlides = isFade ? 1 : (isSkin11ForLoop ? finalSlidesPerView : slidesPerView);
170 var canLoop = totalSlides > visibleSlides;
171 var loopConfig = {};
172 if (loop && (canLoop || isFade)) {
173 loopConfig = {
174 loop: true,
175 loopAdditionalSlides: isFade ? totalSlides : 20,
176 loopedSlides: isFade ? totalSlides : Math.max(Math.ceil(visibleSlides), Math.min(totalSlides, 10))
177 };
178 } else {
179 loopConfig = { loop: false, rewind: !!loop };
180 }
181
182 // Skin 11 (testimonials): build a looping, clickable avatar thumbnail carousel synced to the main slider.
183 var eelTstmlSkin11 = $slider.find('.ee--tstml-inner-wrap.skin11').length > 0;
184 var eelThumbsSwiper = null;
185 if (eelTstmlSkin11) {
186 // Remove any thumbnail strip left over from a previous Elementor (re)init,
187 // so the click/sync wiring always references the live main swiper.
188 var $eelOldThumbs = $scope.find('.eel-tstml-thumbs');
189 if ($eelOldThumbs.length) {
190 if ($eelOldThumbs[0].swiper) { try { $eelOldThumbs[0].swiper.destroy(true, true); } catch (e) {} }
191 $eelOldThumbs.remove();
192 }
193 $scope.find('.eel-tstml-thumb-meta').remove();
194 var eelTstmlAvatars = [], eelTstmlNames = [], eelTstmlDesigs = [];
195 $slider.find('.swiper-slide').not('.swiper-slide-duplicate').each(function () {
196 var src = $(this).find('.eel-picture img').attr('src');
197 if (src) {
198 eelTstmlAvatars.push(src);
199 eelTstmlNames.push(($(this).find('.eel-name').first().text() || '').trim());
200 eelTstmlDesigs.push(($(this).find('.eel-designation').first().text() || '').trim());
201 }
202 });
203 if (eelTstmlAvatars.length) {
204 var eelThumbsHtml = '<div class="eel-tstml-thumbs swiper"><div class="swiper-wrapper">';
205 eelTstmlAvatars.forEach(function (src, i) {
206 eelThumbsHtml += '<div class="swiper-slide" data-eel-thumb-index="' + i + '"><span class="eel-thumb-img"><img src="' + src + '" alt="" /></span></div>';
207 });
208 eelThumbsHtml += '</div></div>';
209 var $eelThumbs = $(eelThumbsHtml);
210 $slider.after($eelThumbs);
211 var $eelThumbMeta = $('<div class="eel-tstml-thumb-meta"><div class="eel-thumb-name"></div><div class="eel-thumb-designation"></div></div>');
212 $eelThumbs.after($eelThumbMeta);
213 try {
214 eelThumbsSwiper = new Swiper($eelThumbs[0], {
215 loop: eelTstmlAvatars.length > 3,
216 centeredSlides: true,
217 slidesPerView: Math.min(3, eelTstmlAvatars.length),
218 spaceBetween: 14,
219 allowTouchMove: false,
220 simulateTouch: false,
221 touchStartPreventDefault: false,
222 watchSlidesProgress: true,
223 breakpoints: {
224 0: { slidesPerView: Math.min(3, eelTstmlAvatars.length) },
225 768: { slidesPerView: Math.min(3, eelTstmlAvatars.length) }
226 }
227 });
228 } catch (e) { eelThumbsSwiper = null; }
229 }
230 }
231
232 var swiper = new Swiper($slider[0], {
233 ...loopConfig,
234 speed: speed,
235 effect: effect,
236 normalizeSlideIndex: true,
237 preventInteractionOnTransition: true,
238 simulateTouch: true,
239 touchRatio: 1,
240 grabCursor: true,
241 mousewheel: hasProgressBar ? {
242 enabled: true,
243 sensitivity: 1,
244 thresholdDelta: 50,
245 thresholdTime: 200,
246 releaseOnEdges: true,
247 invert: false,
248 forceToAxis: false,
249 eventsTarget: 'container'
250 } : false,
251 autoplay: autoplay ? {
252 delay: autoplayDelay,
253 disableOnInteraction: isFade ? true : false,
254 } : false,
255 slidesPerView: visibleSlides,
256 spaceBetween: isFade ? 0 : spaceBetween,
257 centeredSlides: isFade ? false : centeredSlides,
258 freeMode: freeMode,
259 pagination: paginationType && $pagination.length ? {
260 el: $pagination[0],
261 clickable: true,
262 type: paginationType,
263 renderBullet: function (index, className) {
264 let num = (index + 1).toString().padStart(2, '0');
265 return `<span class="${className}">${num}</span>`;
266 }
267 } : false,
268
269 // Progress bar for mouse scroll - only when swiper-pagination-progressbar class is present
270 progressbar: hasProgressBar ? {
271 el: '.swiper-progressbar',
272 type: 'progressbar',
273 } : false,
274
275 navigation: (!isFade) && navigation && ($nextButton.length || $prevButton.length) ? {
276 nextEl: $nextButton.length ? $nextButton[0] : null,
277 prevEl: $prevButton.length ? $prevButton[0] : null,
278 } : false,
279 breakpoints: {
280 0: {
281 slidesPerView: isFade ? 1 : slidesPerViewMobile,
282 },
283 768: {
284 slidesPerView: isFade ? 1 : slidesPerViewTablet,
285 },
286 1024: {
287 slidesPerView: isFade ? 1 : finalSlidesPerView,
288 },
289 },
290 ...effectOptions,
291 });
292
293 // Skin 11: wire avatar thumbnails to control the main slider and keep active state in sync.
294 if (eelTstmlSkin11 && eelThumbsSwiper) {
295 var $eelThumbsRoot = $(eelThumbsSwiper.el);
296 var eelGoToSlide = function (idx) {
297 if (isNaN(idx)) { return; }
298 var main = $slider[0] && $slider[0].swiper;
299 if (!main || !main.params) { return; }
300 if (loop && (canLoop || isFade)) { main.slideToLoop(idx); }
301 else { main.slideTo(idx); }
302 };
303 $eelThumbsRoot.on('click.eelThumb', '.swiper-slide', function () {
304 eelGoToSlide(parseInt($(this).attr('data-eel-thumb-index'), 10));
305 });
306 var eelSyncThumbs = function () {
307 var active = swiper.realIndex || 0;
308 if ( $eelThumbMeta ) {
309 $eelThumbMeta.find('.eel-thumb-name').text(eelTstmlNames[active] || '');
310 $eelThumbMeta.find('.eel-thumb-designation').text(eelTstmlDesigs[active] || '');
311 }
312 $eelThumbsRoot.find('.swiper-slide').removeClass('swiper-slide-thumb-active')
313 .filter('[data-eel-thumb-index="' + active + '"]').addClass('swiper-slide-thumb-active');
314 if (eelThumbsSwiper.params && eelThumbsSwiper.params.loop) {
315 try { eelThumbsSwiper.slideToLoop(active); } catch (e) {}
316 } else {
317 try { eelThumbsSwiper.slideTo(active); } catch (e) {}
318 }
319 };
320 swiper.on('slideChange', eelSyncThumbs);
321 eelSyncThumbs();
322 }
323
324 // Image carousel Skin 05: build a thumbnail strip synced to the main slider.
325 if (eelCarouselThumbs) {
326 var $eelOldCThumbs = $scope.find('.eel-carousel-thumbs-wrap');
327 if ($eelOldCThumbs.length) {
328 var $eelOldCThumbsInner = $eelOldCThumbs.find('.eel-carousel-thumbs');
329 if ($eelOldCThumbsInner[0] && $eelOldCThumbsInner[0].swiper) {
330 try { $eelOldCThumbsInner[0].swiper.destroy(true, true); } catch (e) {}
331 }
332 $eelOldCThumbs.remove();
333 }
334 var eelCThumbImgs = [];
335 $slider.find('.swiper-slide').not('.swiper-slide-duplicate').each(function () {
336 var src = $(this).find('img').attr('src');
337 if (src) { eelCThumbImgs.push(src); }
338 });
339 if (eelCThumbImgs.length) {
340 var eelCThumbsHtml = '<div class="eel-carousel-thumbs swiper"><div class="swiper-wrapper">';
341 eelCThumbImgs.forEach(function (src, i) {
342 eelCThumbsHtml += '<div class="swiper-slide" data-eel-thumb-index="' + i + '"><span class="eel-cthumb-img"><img src="' + src + '" alt="" /></span></div>';
343 });
344 eelCThumbsHtml += '</div></div>';
345
346 var eelArrowPrev = '<div class="eel-cthumbs-arrow eel-cthumbs-prev" role="button" tabindex="0" aria-label="Previous"><svg viewBox="0 0 24 24" width="20" height="20"><path d="M15 18l-6-6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>';
347 var eelArrowNext = '<div class="eel-cthumbs-arrow eel-cthumbs-next" role="button" tabindex="0" aria-label="Next"><svg viewBox="0 0 24 24" width="20" height="20"><path d="M9 18l6-6-6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>';
348
349 var $eelCWrap = $('<div class="eel-carousel-thumbs-wrap"></div>');
350 var $eelCPrev = $(eelArrowPrev);
351 var $eelCThumbs = $(eelCThumbsHtml);
352 var $eelCNext = $(eelArrowNext);
353 $eelCWrap.append($eelCPrev).append($eelCThumbs).append($eelCNext);
354 $slider.after($eelCWrap);
355
356 var eelCThumbSpace = parseInt($slider.data('thumbs-space'), 10);
357 if (isNaN(eelCThumbSpace)) { eelCThumbSpace = 12; }
358 var eelCThumbsSwiper = null;
359 try {
360 eelCThumbsSwiper = new Swiper($eelCThumbs[0], {
361 spaceBetween: eelCThumbSpace,
362 slidesPerView: Math.min(4, eelCThumbImgs.length),
363 watchSlidesProgress: true,
364 slideToClickedSlide: true,
365 breakpoints: {
366 0: { slidesPerView: Math.min(2, eelCThumbImgs.length) },
367 480: { slidesPerView: Math.min(3, eelCThumbImgs.length) },
368 768: { slidesPerView: Math.min(4, eelCThumbImgs.length) }
369 }
370 });
371 } catch (e) { eelCThumbsSwiper = null; }
372
373 var eelCGoTo = function (idx) {
374 if (isNaN(idx)) { return; }
375 var main = $slider[0] && $slider[0].swiper;
376 if (!main) { return; }
377 if (loop && (canLoop || isFade)) { main.slideToLoop(idx); }
378 else { main.slideTo(idx); }
379 };
380 $eelCThumbs.on('click.eelCThumb', '.swiper-slide', function () {
381 eelCGoTo(parseInt($(this).attr('data-eel-thumb-index'), 10));
382 });
383 var eelCSync = function () {
384 var active = swiper.realIndex || 0;
385 $eelCThumbs.find('.swiper-slide').removeClass('swiper-slide-thumb-active')
386 .filter('[data-eel-thumb-index="' + active + '"]').addClass('swiper-slide-thumb-active');
387 if (eelCThumbsSwiper) { try { eelCThumbsSwiper.slideTo(active); } catch (e) {} }
388 };
389 swiper.on('slideChange', eelCSync);
390 eelCSync();
391
392 $eelCPrev.on('click.eelCThumb', function () {
393 var main = $slider[0] && $slider[0].swiper;
394 if (main) { main.slidePrev(); }
395 });
396 $eelCNext.on('click.eelCThumb', function () {
397 var main = $slider[0] && $slider[0].swiper;
398 if (main) { main.slideNext(); }
399 });
400 }
401 }
402
403 // Fade: attach custom navigation so Next/Prev work
404 if (isFade && ($nextButton.length || $prevButton.length)) {
405 var isTransitioning = false;
406 swiper.on('transitionStart', function () { isTransitioning = true; });
407 swiper.on('transitionEnd', function () { isTransitioning = false; });
408 if ($nextButton.length) {
409 $nextButton.off('click.easyFadeNav').on('click.easyFadeNav', function (e) {
410 e.preventDefault();
411 e.stopPropagation();
412 if (swiper && swiper.autoplay && swiper.autoplay.stop) { swiper.autoplay.stop(); }
413 if (isTransitioning || (swiper && swiper.animating)) return;
414 swiper.slideNext();
415 });
416 }
417 if ($prevButton.length) {
418 $prevButton.off('click.easyFadeNav').on('click.easyFadeNav', function (e) {
419 e.preventDefault();
420 e.stopPropagation();
421 if (swiper && swiper.autoplay && swiper.autoplay.stop) { swiper.autoplay.stop(); }
422 if (isTransitioning || (swiper && swiper.animating)) return;
423 swiper.slidePrev();
424 });
425 }
426 }
427
428 // Non-fade: if loop+autoplay, stop autoplay on manual nav to avoid double-advance
429 if (!isFade && loop && autoplay) {
430 if ($nextButton.length) {
431 $nextButton.on('click.easyStopAuto', function () {
432 if (swiper && swiper.autoplay && swiper.autoplay.stop) {
433 swiper.autoplay.stop();
434 }
435 });
436 }
437 if ($prevButton.length) {
438 $prevButton.on('click.easyStopAuto', function () {
439 if (swiper && swiper.autoplay && swiper.autoplay.stop) {
440 swiper.autoplay.stop();
441 }
442 });
443 }
444 }
445 }
446
447
448 if (!window.EEL_AddWidgetTypeBodyClass) {
449 window.EEL_AddWidgetTypeBodyClass = true;
450
451 function addWidgetTypeToBody($scope) {
452 if (!$scope || !$scope.length) return;
453
454 var widgetType = $scope.data('widget_type');
455 if (!widgetType) return;
456
457 var safeWidgetType = widgetType
458 .toString()
459 .replace(/[^a-z0-9_-]/gi, '-')
460 .toLowerCase();
461
462 var className = 'easy-eel-has-widget-' + safeWidgetType;
463
464 if (!document.body.classList.contains(className)) {
465 document.body.classList.add(className);
466 }
467 }
468
469 /* -------------------------------------------------
470 * Elementor Hooks
471 * ------------------------------------------------- */
472 $(window).on('elementor/frontend/init', function () {
473
474 // Slider init
475 elementorFrontend.hooks.addAction(
476 'frontend/element_ready/global',
477 initBackPostSlider
478 );
479
480 // Widget type → body class
481 elementorFrontend.hooks.addAction(
482 'frontend/element_ready/global',
483 function ($scope) {
484 addWidgetTypeToBody($scope);
485 }
486 );
487
488 });
489 }
490
491
492 $(window).on('elementor/frontend/init', function () {
493
494 function checkStickySection() {
495 const stickySection = document.querySelector('.eel-sticky-section');
496 if (stickySection) {
497 document.body.classList.add('sticky-enabled-overlap');
498 } else {
499 document.body.classList.remove('sticky-enabled-overlap');
500 }
501 }
502
503 checkStickySection();
504
505 const observer = new MutationObserver(checkStickySection);
506 observer.observe(document.body, { childList: true, subtree: true });
507 });
508
509
510
511 $(window).on('elementor/frontend/init', function () {
512 function initReveal_ad($scope) {
513 const revealElements = $scope.find(".eel-img-advance-wrap");
514 const revealAnimate = (el) => {
515 const target = el.querySelector(".eel-advance-img-main");
516 if(target && !target.classList.contains("el-reveal-animate")) {
517 target.classList.add("el-reveal-animate");
518 }
519 };
520 const isEditor = window.elementorFrontend && elementorFrontend.isEditMode && elementorFrontend.isEditMode();
521 if(isEditor){
522 revealElements.each(function(){
523 revealAnimate(this);
524 });
525 } else {
526 const observer = new IntersectionObserver((entries, obs) => {
527 entries.forEach(entry => {
528 if(entry.isIntersecting){
529 revealAnimate(entry.target);
530 obs.unobserve(entry.target);
531 }
532 });
533 }, { threshold: 0.3 });
534
535 revealElements.each(function(){
536 observer.observe(this);
537 });
538
539 // Lenis safe
540 if (window.Lenis && typeof window.Lenis.raf === 'function') {
541 window.Lenis.raf(() => {});
542 }
543 }
544 }
545
546 elementorFrontend.hooks.addAction(
547 'frontend/element_ready/eel-advance-image.default',
548 initReveal_ad
549 );
550
551 function initReveal($scope) {
552 const revealElements = $scope.find(".eel-img-reveal-wrap");
553
554 const revealAnimate = (el) => {
555 const target = el.querySelector(".eel-reveal-img-main");
556 if(target && !target.classList.contains("el-reveal-animate")) {
557 target.classList.add("el-reveal-animate");
558 }
559 };
560
561 const isEditor = window.elementorFrontend && elementorFrontend.isEditMode && elementorFrontend.isEditMode();
562
563 if(isEditor){
564 revealElements.each(function(){
565 revealAnimate(this);
566 });
567 } else {
568 const observer = new IntersectionObserver((entries, obs) => {
569 entries.forEach(entry => {
570 if(entry.isIntersecting){
571 revealAnimate(entry.target);
572 obs.unobserve(entry.target);
573 }
574 });
575 }, { threshold: 0.3 });
576
577 revealElements.each(function(){
578 observer.observe(this);
579 });
580
581 // Lenis safe
582 if (window.Lenis && typeof window.Lenis.raf === 'function') {
583 window.Lenis.raf(() => {});
584 }
585 }
586 }
587
588 elementorFrontend.hooks.addAction(
589 'frontend/element_ready/eel-image-reveal.default',
590 initReveal
591 );
592
593 });
594
595
596 $(window).on("elementor/frontend/init", function () {
597 var EasyJarallax = function ($scope, $) {
598 if (typeof jarallax === 'undefined') return;
599
600 var $wrappers = $scope.hasClass('eele-has-jarallax') ? $scope : $scope.find('.eele-has-jarallax, .eele-inner-jarallax');
601 if (!$wrappers.length) return;
602
603 $wrappers.each(function () {
604 var $wrapper = $(this);
605 var bg = $wrapper.data('jarallax-bg');
606 var speed = $wrapper.data('jarallax-speed') || 0.5;
607 if (!bg) return;
608
609 // Destroy previous instance if re-rendering in editor
610 var $oldJarallax = $wrapper.find('.jarallax');
611 if ($oldJarallax.length) {
612 $oldJarallax.each(function () {
613 jarallax(this, 'destroy');
614 });
615 $oldJarallax.remove();
616 }
617
618 var $div = $('<div>', { 'class': 'jarallax eele-jarallax-bg', 'data-speed': speed });
619 var $img = $('<img>', { 'class': 'jarallax-img', src: bg, alt: '', decoding: 'async' });
620 $div.append($img);
621 $wrapper.append($div);
622
623 jarallax($div[0], {
624 speed: speed,
625 imgPosition: '50% 50%',
626 imgRepeat: 'no-repeat',
627 imgSize: 'cover',
628 zIndex: -100
629 });
630 });
631 };
632
633 // Image widget parallax
634 var EasyJarallaxImage = function ($scope, $) {
635 if (typeof jarallax === 'undefined') return;
636 if (!$scope.hasClass('eele-has-jarallax-img-yes')) return;
637
638 var $img = $scope.find('.elementor-widget-container img').first();
639 if (!$img.length || !$img.attr('src')) return;
640
641 var $container = $scope.find('.elementor-widget-container');
642
643 // If already wrapped from a previous run (editor re-render), unwrap to start clean
644 if ($img.parent('.eele-img-jarallax-wrap').length) {
645 try { jarallax($img.parent()[0], 'destroy'); } catch (e) {}
646 $img.unwrap();
647 $img.removeClass('jarallax-img');
648 }
649
650 // Clean up any leftover empty jarallax bg divs from older buggy code
651 $container.find('> .eele-jarallax-bg, > a > .eele-jarallax-bg').each(function () {
652 try { jarallax(this, 'destroy'); } catch (e) {}
653 }).remove();
654
655 // Wrap the original image so it becomes the parallax source itself.
656 // Do NOT add `eele-jarallax-bg` — that class has global CSS
657 // (position:absolute; z-index:-1) for container background parallax,
658 // which would hide the image entirely.
659 $img.addClass('jarallax-img');
660 $img.wrap('<div class="jarallax jarallax-container eele-img-jarallax-wrap"></div>');
661 var $div = $img.parent();
662
663 function initParallax() {
664 // Give the wrapper a size so it doesn't collapse once jarallax positions
665 // the img absolutely. User-set width/height (via panel) uses !important
666 // and will still override these fallbacks.
667 var nW = $img[0].naturalWidth;
668 var nH = $img[0].naturalHeight;
669 if (nW && nH) {
670 if (!$div[0].style.width) $div.css('width', '100%');
671 if (!$div[0].style.height) $div.css('aspect-ratio', nW + ' / ' + nH);
672 } else if (!$div[0].style.height) {
673 var fallbackH = $div[0].getBoundingClientRect().height;
674 if (fallbackH > 0) $div.css('height', fallbackH + 'px');
675 }
676
677 jarallax($div[0], {
678 speed: 0.5,
679 imgPosition: '50% 50%',
680 imgRepeat: 'no-repeat',
681 imgSize: 'cover',
682 zIndex: 0
683 });
684 }
685
686 if ($img[0].complete && $img[0].naturalWidth > 0) {
687 initParallax();
688 } else {
689 $img.one('load', initParallax);
690 }
691 };
692
693 elementorFrontend.hooks.addAction("frontend/element_ready/global", EasyJarallax);
694 elementorFrontend.hooks.addAction("frontend/element_ready/image.default", EasyJarallaxImage);
695 elementorFrontend.hooks.addAction("frontend/element_ready/eel-advance-image.default", EasyJarallaxImage);
696
697 // --- Lenis integration for Jarallax ---
698 function syncJarallaxWithLenis() {
699 if (window.lenis && typeof jarallax !== 'undefined') {
700 window.lenis.on('scroll', function () {
701 jarallax(document.querySelectorAll('.jarallax'), 'onScroll');
702 });
703 } else {
704 setTimeout(syncJarallaxWithLenis, 200);
705 }
706 }
707 syncJarallaxWithLenis();
708 });
709
710
711 jQuery(window).on('elementor/frontend/init', function() {
712
713 elementorFrontend.hooks.addAction('frontend/element_ready/eel-video-popup.default', function($scope, $) {
714
715 const wrapper = $scope.find('.eel-video-popup-wrapper');
716 const overlay = wrapper.find('.eel-video-overlay');
717 // Find video element (try with class first, then without)
718 let videoEl = wrapper.find('video.eel-normal-video');
719 if (!videoEl.length) {
720 videoEl = wrapper.find('video');
721 }
722 // Find iframe element (try with class first, then without)
723 let iframeEl = wrapper.find('iframe.eel-normal-video');
724 if (!iframeEl.length) {
725 iframeEl = wrapper.find('iframe');
726 }
727
728 // Helper function to get YouTube embed URL
729 function getYouTubeEmbedUrl(url) {
730 const regExp = /(?:v=|\/)([0-9A-Za-z_-]{11}).*/;
731 const match = url.match(regExp);
732 if (match && match[1]) {
733 return 'https://www.youtube.com/embed/' + match[1] + '?autoplay=1';
734 }
735 return null;
736 }
737
738 // Helper function to get Vimeo embed URL
739 function getVimeoEmbedUrl(url) {
740 const match = url.match(/(\d+)/);
741 if (match && match[1]) {
742 return 'https://player.vimeo.com/video/' + match[1] + '?autoplay=1';
743 }
744 return null;
745 }
746
747 // Helper function to open lightbox
748 function openLightbox(videoType, videoUrl, popupId, animation) {
749 const lightboxOverlay = $('#' + popupId);
750 const iframeWrapper = lightboxOverlay.find('.eel-video-popup-iframe-wrapper');
751 const lightboxContent = lightboxOverlay.find('.eel-video-popup-content');
752
753 if (!lightboxOverlay.length) return;
754
755 // Prevent duplicate opening if already active
756 if (lightboxOverlay.hasClass('active')) {
757 return;
758 }
759
760 let embedHtml = '';
761
762 if (videoType === 'youtube') {
763 const embedUrl = getYouTubeEmbedUrl(videoUrl);
764 if (embedUrl) {
765 embedHtml = '<iframe src="' + embedUrl + '" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>';
766 }
767 } else if (videoType === 'vimeo') {
768 const embedUrl = getVimeoEmbedUrl(videoUrl);
769 if (embedUrl) {
770 embedHtml = '<iframe src="' + embedUrl + '" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>';
771 }
772 } else if (videoType === 'self_hosted') {
773 embedHtml = '<video src="' + videoUrl + '" controls autoplay></video>';
774 }
775
776 if (embedHtml) {
777 iframeWrapper.html(embedHtml);
778
779 // Apply animation if set
780 if (animation && animation.trim() !== '') {
781 // Remove any existing animation classes
782 const currentClasses = lightboxContent.attr('class') || '';
783 const animationClasses = currentClasses.match(/\banimated\s+\S+/g);
784 if (animationClasses) {
785 lightboxContent.removeClass(animationClasses.join(' '));
786 }
787 // Add new animation classes
788 lightboxContent.addClass('animated ' + animation.trim());
789 } else {
790 // Remove animation classes if no animation
791 const currentClasses = lightboxContent.attr('class') || '';
792 const animationClasses = currentClasses.match(/\banimated\s+\S+/g);
793 if (animationClasses) {
794 lightboxContent.removeClass(animationClasses.join(' '));
795 }
796 }
797
798 lightboxOverlay.addClass('active');
799 $('body').css('overflow', 'hidden');
800 }
801 }
802
803 // Close lightbox handler
804 $(document).on('click', '.eel-video-popup-close', function(e) {
805 e.preventDefault();
806 const lightboxOverlay = $(this).closest('.eel-video-popup-overlay');
807 const lightboxContent = lightboxOverlay.find('.eel-video-popup-content');
808 lightboxOverlay.removeClass('active');
809 lightboxContent.removeClass('animated');
810 lightboxOverlay.find('.eel-video-popup-iframe-wrapper').html('');
811 $('body').css('overflow', '');
812 });
813
814 // Close lightbox on overlay click
815 $(document).on('click', '.eel-video-popup-overlay', function(e) {
816 if ($(e.target).hasClass('eel-video-popup-overlay')) {
817 const lightboxContent = $(this).find('.eel-video-popup-content');
818 $(this).removeClass('active');
819 lightboxContent.removeClass('animated');
820 $(this).find('.eel-video-popup-iframe-wrapper').html('');
821 $('body').css('overflow', '');
822 }
823 });
824
825 // Close lightbox on ESC key
826 $(document).on('keydown', function(e) {
827 if (e.keyCode === 27) { // ESC key
828 $('.eel-video-popup-overlay.active').each(function() {
829 const lightboxContent = $(this).find('.eel-video-popup-content');
830 $(this).removeClass('active');
831 lightboxContent.removeClass('animated');
832 $(this).find('.eel-video-popup-iframe-wrapper').html('');
833 $('body').css('overflow', '');
834 });
835 }
836 });
837
838 if (overlay.length) {
839 // Remove existing handler to prevent duplicate
840 overlay.off('click.eelVideoOverlay');
841 overlay.on('click.eelVideoOverlay', function(e) {
842 e.preventDefault();
843 e.stopPropagation();
844
845 const $overlay = $(this);
846 const useLightbox = $overlay.data('lightbox') === 'yes';
847 const videoType = $overlay.data('video-type');
848 const videoUrl = $overlay.data('video-url');
849 const popupId = $overlay.data('popup-id');
850 const animation = $overlay.data('animation') || '';
851
852 // If lightbox is enabled, open lightbox
853 if (useLightbox && videoType && videoUrl && popupId) {
854 // Check if lightbox is already open to prevent duplicate
855 const lightboxOverlay = $('#' + popupId);
856 if (lightboxOverlay.hasClass('active')) {
857 return;
858 }
859 openLightbox(videoType, videoUrl, popupId, animation);
860 return;
861 }
862
863 // Otherwise, play inline (original behavior)
864 // Self-hosted video play
865 if (videoEl.length) {
866 const video = videoEl.get(0);
867
868 // Function to play video
869 const playVideo = function() {
870 const playPromise = video.play();
871 if (playPromise !== undefined) {
872 playPromise.catch(function(error) {
873 console.log('Video play failed:', error);
874 // If play fails due to autoplay policy, try with muted
875 if (error.name === 'NotAllowedError' && !video.muted) {
876 video.muted = true;
877 video.play().then(function() {
878 // Unmute after play starts (if user wants sound)
879 setTimeout(function() {
880 video.muted = false;
881 }, 100);
882 }).catch(function(err) {
883 console.log('Video play with mute also failed:', err);
884 });
885 }
886 });
887 }
888 };
889
890 // Always try to load and play
891 if (video.readyState >= 2) {
892 // Video is already loaded, play immediately
893 playVideo();
894 } else {
895 // Video not loaded, load it first then play
896 video.load();
897 // Wait for video to be ready to play
898 const tryPlay = function() {
899 if (video.readyState >= 2) {
900 playVideo();
901 } else {
902 video.addEventListener('canplay', playVideo, { once: true });
903 video.addEventListener('loadeddata', playVideo, { once: true });
904 }
905 };
906 tryPlay();
907 }
908 }
909
910 // YouTube / Vimeo iframe autoplay
911 if (iframeEl.length) {
912 let src = iframeEl.attr('src');
913 if (src) {
914 // Check if it's YouTube or Vimeo
915 const isYouTube = src.indexOf('youtube.com') !== -1 || src.indexOf('youtu.be') !== -1;
916 const isVimeo = src.indexOf('vimeo.com') !== -1;
917
918 if (isYouTube || isVimeo) {
919 try {
920 // Use URL object for proper parsing
921 const url = new URL(src);
922
923 if (isYouTube) {
924 // YouTube: add autoplay, preserve original mute setting
925 url.searchParams.set('autoplay', '1');
926 // Only set mute=1 if it was already in the URL (preserve original setting)
927 // If mute was not in original URL, don't add it
928 if (!url.searchParams.has('mute')) {
929 // Don't add mute if it wasn't there originally
930 } else {
931 // Keep the original mute value
932 }
933 // Reload iframe with new src
934 iframeEl.attr('src', url.toString());
935 } else if (isVimeo) {
936 // Vimeo: add autoplay (Vimeo doesn't need mute for autoplay)
937 url.searchParams.set('autoplay', '1');
938 // Reload iframe with new src
939 iframeEl.attr('src', url.toString());
940 }
941 } catch (e) {
942 // Fallback for browsers that don't support URL constructor
943 // Remove existing autoplay parameter if present
944 src = src.replace(/[?&]autoplay=[^&]*/g, '');
945
946 if (isYouTube) {
947 // Check if mute parameter exists in original URL
948 const hasMute = /[?&]mute=/.test(src);
949 // Add autoplay=1
950 const separator = src.indexOf('?') === -1 ? '?' : '&';
951 src += separator + 'autoplay=1';
952 // Only add mute if it was already there (preserve original setting)
953 // Don't add mute if it wasn't in original URL
954 } else if (isVimeo) {
955 // Vimeo: just add autoplay=1
956 const separator = src.indexOf('?') === -1 ? '?' : '&';
957 src += separator + 'autoplay=1';
958 }
959
960 // Update src without clearing first
961 iframeEl.attr('src', src);
962 }
963 }
964 }
965 }
966
967 // Remove overlay after a small delay to ensure video starts
968 setTimeout(function() {
969 overlay.fadeOut(300, function() {
970 $(this).remove();
971 });
972 }, 200);
973 });
974 }
975
976 // Handle popup button clicks (for display_type = 'popup')
977 const popupBtn = wrapper.find('.entro-all-video-popup');
978 if (popupBtn.length) {
979 // If the global popup (from pro plugin) exists, let eel-popup.js handle it
980 // to avoid transform/motion effect containment issues with position:fixed
981 const globalPopup = document.querySelector('.entro-all-video-popup-wrap');
982 if (globalPopup) {
983 // Global popup exists — do not bind per-widget handler.
984 // The pro plugin's eel-popup.js will handle it since it's already in body.
985 } else {
986 // No global popup — use per-widget overlay, moved to body
987 const popupOverlay = wrapper.find('.eel-video-popup-overlay');
988 if (popupOverlay.length && !popupOverlay.data('moved-to-body')) {
989 popupOverlay.data('moved-to-body', true);
990 popupOverlay.appendTo('body');
991 }
992
993 // Remove existing handler to prevent duplicate
994 popupBtn.off('click.eelVideoPopup');
995 popupBtn.on('click.eelVideoPopup', function(e) {
996 e.preventDefault();
997 e.stopPropagation();
998 const $btn = $(this);
999 const videoType = $btn.data('video-type');
1000 const videoUrl = $btn.data('video-src');
1001 const popupId = $btn.data('popup-id');
1002 const lightboxOverlay = $('#' + popupId);
1003 const animation = lightboxOverlay.data('animation') || '';
1004
1005 if (videoType && videoUrl && popupId) {
1006 // Check if lightbox is already open to prevent duplicate
1007 if (lightboxOverlay.hasClass('active')) {
1008 return;
1009 }
1010 openLightbox(videoType, videoUrl, popupId, animation);
1011 }
1012 });
1013 }
1014 }
1015 });
1016
1017 });
1018
1019 // Single Page Navigation
1020 document.addEventListener('DOMContentLoaded', function () {
1021 const navWrappers = document.querySelectorAll('.eel-single-nav-list');
1022 if (!navWrappers.length) return;
1023
1024 navWrappers.forEach(function (wrapper) {
1025
1026 const navLinks = wrapper.querySelectorAll('.eel-nav-item a');
1027 const navItems = wrapper.querySelectorAll('.eel-nav-item');
1028
1029 if (!navLinks.length) return;
1030
1031 wrapper.addEventListener('click', function (e) {
1032
1033 const link = e.target.closest('.eel-nav-item a');
1034 if (!link || !wrapper.contains(link)) return;
1035
1036 const href = link.getAttribute('href');
1037
1038 if (!href || href === '#' || !href.startsWith('#') || href.length === 1) {
1039 return;
1040 }
1041
1042 const target = document.querySelector(href);
1043 if (!target) return;
1044
1045 e.preventDefault();
1046
1047 target.scrollIntoView({
1048 behavior: 'smooth',
1049 block: 'start'
1050 });
1051 });
1052
1053 /* ========= SCROLL → ACTIVE CHANGE ========= */
1054 const sectionsMap = new Map();
1055
1056 navLinks.forEach(link => {
1057 const href = link.getAttribute('href');
1058
1059 if (!href || href === '#' || !href.startsWith('#') || href.length === 1) {
1060 return;
1061 }
1062
1063 const section = document.querySelector(href);
1064 if (section) {
1065 sectionsMap.set(section, href);
1066 }
1067 });
1068
1069 function setActive(href) {
1070 navItems.forEach(item => item.classList.remove('active'));
1071
1072 navLinks.forEach(link => {
1073 if (link.getAttribute('href') === href) {
1074 link.closest('.eel-nav-item')?.classList.add('active');
1075 }
1076 });
1077 }
1078
1079 const observer = new IntersectionObserver(
1080 (entries) => {
1081 entries.forEach(entry => {
1082 if (entry.isIntersecting) {
1083 const id = sectionsMap.get(entry.target);
1084 if (id) setActive(id);
1085 }
1086 });
1087 },
1088 {
1089 root: null,
1090 rootMargin: '-40% 0px -40% 0px',
1091 threshold: 0.1
1092 }
1093 );
1094
1095 sectionsMap.forEach((id, section) => {
1096 observer.observe(section);
1097 });
1098
1099 });
1100
1101 });
1102
1103 // Sticky Navigation single page
1104
1105 function applyStickyToContainer($container) {
1106 if ($container.find('.elementor-widget-eel-single-nav').length === 0) {
1107 return;
1108 }
1109
1110 $container.addClass('eel-single-nav-sticky-enabled');
1111 }
1112
1113 /* ===== Frontend ===== */
1114 $(window).on('elementor/frontend/init', function () {
1115 elementorFrontend.hooks.addAction(
1116 'frontend/element_ready/container',
1117 function ($scope) {
1118 applyStickyToContainer($scope);
1119 }
1120 );
1121 });
1122
1123 /* ===== Editor ===== */
1124 if (window.elementor && elementor.isEditMode()) {
1125 elementor.hooks.addAction('editor:init', function () {
1126 elementorFrontend.hooks.addAction(
1127 'frontend/element_ready/container',
1128 function ($scope) {
1129 applyStickyToContainer($scope);
1130 }
1131 );
1132 });
1133 }
1134
1135 // Mobile Menu Sidebar
1136 if ($('.sidebar-on-mobile-single-nav').length) {
1137 // Sidebar toggle
1138 $('.eel-nav-menu-icon').on('click', function(e){
1139 e.stopPropagation();
1140 $('.sidebar-on-mobile-single-nav').toggleClass('easyel-open');
1141 $('body').toggleClass('sidebar-on-mobile-single-nav-active');
1142 });
1143 // Click outside to close sidebar
1144 $(document).on('click', function(){
1145 $('.sidebar-on-mobile-single-nav').removeClass('easyel-open');
1146 $('body').removeClass('sidebar-on-mobile-single-nav-active');
1147 $('.sidebar-on-mobile-single-nav ul.sub-menu').slideUp(300);
1148 $('.sidebar-on-mobile-single-nav .sub-arrow').removeClass('active').attr('aria-expanded', false);
1149 });
1150
1151 $('.sidebar-on-mobile-single-nav, .eel-nav-menu').on('click', 'li:not(.menu-item-has-children) a', function(e){
1152 $('.sidebar-on-mobile-single-nav li a, .eel-nav-menu li a').removeClass('eel-active');
1153 $(this).addClass('eel-active');
1154 $('.sidebar-on-mobile-single-nav').removeClass('easyel-open');
1155 $('body').removeClass('sidebar-on-mobile-single-nav-active');
1156 $('body').removeClass('sidebar-on-mobile-active');
1157 });
1158
1159 // Prevent closing when clicking inside sidebar
1160 $('.sidebar-on-mobile-single-nav').on('click', function(e){
1161 e.stopPropagation();
1162 });
1163 }
1164
1165 jQuery(document).ready(function($){
1166 var $allSidebars = $('.sidebar-on-mobile-mega-nav');
1167 var $body = $('body');
1168
1169 if(!$allSidebars.length) return;
1170
1171 /* ========== Inject submenu icon ========== */
1172 $allSidebars.find('li.menu-item-has-children').each(function(){
1173 if(!$(this).children('.submenu-parent-icon').length){
1174 var svgIcon = '<span class="submenu-parent-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M201.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 338.7 54.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg></span>';
1175 $(this).prepend(svgIcon);
1176 }
1177 });
1178
1179 // Open sidebar — match hamburger to its own sidebar via data attributes
1180 $(document).on('click', '.eel-mega-menu-icon-mobile', function(e){
1181 e.preventDefault();
1182 e.stopPropagation();
1183 var targetId = $(this).attr('data-mega-target');
1184 var $sidebar;
1185 if (targetId) {
1186 $sidebar = $('.sidebar-on-mobile-mega-nav[data-mega-id="' + targetId + '"]');
1187 }
1188 // Fallback for old markup without data attributes
1189 if (!$sidebar || !$sidebar.length) {
1190 $sidebar = $allSidebars.first();
1191 }
1192 $allSidebars.not($sidebar).removeClass('easyel-open');
1193 $sidebar.addClass('easyel-open');
1194 $body.addClass('sidebar-on-mobile-mega-nav-active');
1195 });
1196
1197 // Close sidebar (icon)
1198 $(document).on('click', '.eel-mega-menu-icon-close', function(e){
1199 e.preventDefault();
1200 e.stopPropagation();
1201 $(this).closest('.sidebar-on-mobile-mega-nav').removeClass('easyel-open');
1202 $body.removeClass('sidebar-on-mobile-mega-nav-active');
1203 });
1204
1205 // Click outside sidebar (overlay)
1206 $(document).on('click', function(){
1207 $allSidebars.removeClass('easyel-open');
1208 $body.removeClass('sidebar-on-mobile-mega-nav-active');
1209 });
1210
1211 // Prevent sidebar clicks from closing
1212 $(document).on('click', '.sidebar-on-mobile-mega-nav', function(e){
1213 if (!$(e.target).closest('.eel-mega-menu-icon-close').length) {
1214 e.stopPropagation();
1215 }
1216 });
1217
1218 /* ========== Submenu toggle ========== */
1219 $(document).on('click', '.sidebar-on-mobile-mega-nav .submenu-parent-icon', function(e){
1220 e.preventDefault();
1221 e.stopPropagation();
1222
1223 var $li = $(this).closest('li.menu-item-has-children');
1224 var $submenu = $li.children('ul.sub-menu');
1225
1226 // close other submenus
1227 $li.siblings('li.menu-item-has-children')
1228 .removeClass('eel-sub-open')
1229 .children('ul.sub-menu').slideUp(300);
1230
1231 $li.siblings('li.menu-item-has-children')
1232 .children('.submenu-parent-icon').removeClass('active');
1233
1234 // toggle current
1235 $submenu.slideToggle(300);
1236 $li.toggleClass('eel-sub-open');
1237 $(this).toggleClass('active');
1238 });
1239 });
1240
1241 /* ========== Mega menu responsive breakpoint ========== */
1242 /* Replaces the per-widget inline <style> media queries. Each widget passes
1243 its breakpoint via data-mega-breakpoint and we toggle view classes on the
1244 widget root, so the show/hide rules live in the stylesheet, not inline. */
1245 jQuery(document).ready(function($){
1246 var $wraps = $('.eel-mega-menu-wrap[data-mega-breakpoint]');
1247 if (!$wraps.length) return;
1248
1249 function applyMegaBreakpoint(){
1250 var width = window.innerWidth;
1251 $wraps.each(function(){
1252 var bp = parseInt($(this).attr('data-mega-breakpoint'), 10);
1253 if (!bp) return;
1254 var isMobile = width <= bp;
1255 $(this).closest('.elementor-element')
1256 .toggleClass('eel-mega-view-mobile', isMobile)
1257 .toggleClass('eel-mega-view-desktop', !isMobile);
1258 });
1259 }
1260
1261 var resizeTimer;
1262 $(window).on('resize.eelMegaBreakpoint orientationchange.eelMegaBreakpoint', function(){
1263 clearTimeout(resizeTimer);
1264 resizeTimer = setTimeout(applyMegaBreakpoint, 150);
1265 });
1266
1267 applyMegaBreakpoint();
1268 });
1269
1270 jQuery(window).on('elementor/frontend/init', function() {
1271 elementorFrontend.hooks.addAction('frontend/element_ready/eel-vertical-navigation.default', function($scope) {
1272
1273 var $sidebar = $scope.find('.sidebar-on-mobile-vertical-nav');
1274 var $body = jQuery('body');
1275
1276 if($sidebar.length) {
1277 $sidebar.find('li.menu-item-has-children').each(function(){
1278 if(!jQuery(this).children('.submenu-parent-icon').length){
1279 var svgIcon = '<span class="submenu-parent-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M201.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 338.7 54.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg></span>';
1280 jQuery(this).prepend(svgIcon);
1281 }
1282 });
1283
1284 $scope.on('click', '.eel-vertical-menu-icon-mobile', function(e){
1285 e.preventDefault();
1286 e.stopPropagation();
1287 $sidebar.addClass('easyel-open');
1288 $body.addClass('sidebar-on-mobile-vertical-nav-active');
1289 });
1290
1291 $scope.on('click', '.eel-vertical-menu-icon-close', function(e){
1292 e.preventDefault();
1293 e.stopPropagation();
1294 closeSidebar();
1295 });
1296
1297 jQuery(document).on('click', function(e){
1298 if (!jQuery(e.target).closest('.sidebar-on-mobile-vertical-nav, .eel-vertical-menu-icon-mobile').length) {
1299 closeSidebar();
1300 }
1301 });
1302
1303 function closeSidebar() {
1304 $sidebar.removeClass('easyel-open');
1305 $body.removeClass('sidebar-on-mobile-vertical-nav-active');
1306 }
1307
1308 /* ========== Submenu Toggle (Accordion) ========== */
1309 $sidebar.find('.submenu-parent-icon').off('click').on('click', function(e){
1310 e.preventDefault();
1311 e.stopPropagation();
1312
1313 var $this = jQuery(this);
1314 var $li = $this.closest('li.menu-item-has-children');
1315 var $submenu = $li.children('ul.sub-menu');
1316 $li.siblings('li.menu-item-has-children').each(function(){
1317 var $sibling = jQuery(this);
1318 $sibling.removeClass('eel-sub-open is-open-vertical');
1319 $sibling.children('ul.sub-menu').slideUp(300);
1320 $sibling.children('.submenu-parent-icon').removeClass('active');
1321 $sibling.find('> a').removeClass('eel-vertical-open');
1322 });
1323 if ($li.hasClass('eel-sub-open')) {
1324 $submenu.slideUp(300);
1325 $li.removeClass('eel-sub-open is-open-vertical');
1326 $this.removeClass('active');
1327 $li.find('> a').removeClass('eel-vertical-open');
1328 } else {
1329 $submenu.slideDown(300);
1330 $li.addClass('eel-sub-open is-open-vertical');
1331 $this.addClass('active');
1332 $li.find('> a').addClass('eel-vertical-open');
1333 }
1334 });
1335 }
1336 });
1337 });
1338
1339 })(jQuery);
1340
1341 (function ($) {
1342 function eelMegaNavigationInit() {
1343 $('.elementor-widget-eel-mega-navigation')
1344 .parent()
1345 .addClass('parant-eel-mega-static');
1346 }
1347 // Frontend
1348 $(window).on('load', function () {
1349 eelMegaNavigationInit();
1350 });
1351
1352 // Elementor Editor
1353 $(window).on('elementor/frontend/init', function () {
1354 if (elementorFrontend.isEditMode()) {
1355 eelMegaNavigationInit();
1356 }
1357 elementorFrontend.hooks.addAction(
1358 'frontend/element_ready/eel-mega-navigation.default',
1359 eelMegaNavigationInit
1360 );
1361 });
1362
1363 })(jQuery);
1364
1365
1366
1367 (function($){
1368
1369 let eelObserverAttached = false;
1370
1371 function updateMegaMenuActive() {
1372 const $mega = $('.easyel--elementor-template-mega-menu');
1373 if (!$mega.length) return;
1374
1375 const $parent = $mega.parent();
1376
1377 if ($mega.hasClass('easyel-mega--current')) {
1378 $parent.addClass('easyel-mega--current-active menu-item-has-children');
1379 } else {
1380 $parent.removeClass('easyel-mega--current-active menu-item-has-children');
1381 }
1382 }
1383
1384 function updateElementorPositions() {
1385 if ($('.easyel-mega--current-active').length) {
1386 $('header .elementor-element').css('position', 'static');
1387 $('.easyel-mega--current-active .elementor-element').css('position', '');
1388 } else {
1389 $('header .elementor-element').css('position', '');
1390 }
1391 }
1392
1393 function attachObserverOnce() {
1394 if (eelObserverAttached) return;
1395 eelObserverAttached = true;
1396
1397 const observer = new MutationObserver(() => {
1398 updateMegaMenuActive();
1399 updateElementorPositions();
1400 });
1401
1402 observer.observe(document.body, {
1403 attributes: true,
1404 subtree: true,
1405 attributeFilter: ['class']
1406 });
1407 }
1408
1409 function initMegaMenuJS(scope) {
1410 if (!scope.find('.elementor-widget-eel-navigation-menu').length) return;
1411
1412 updateMegaMenuActive();
1413 updateElementorPositions();
1414 attachObserverOnce();
1415 }
1416
1417 // Frontend
1418 $(window).on('load', function(){
1419 initMegaMenuJS($(document.body));
1420 });
1421
1422 // Elementor Editor
1423 $(window).on('elementor/frontend/init', function () {
1424 elementorFrontend.hooks.addAction(
1425 'frontend/element_ready/eel-navigation-menu.default',
1426 function($scope){
1427 initMegaMenuJS($scope);
1428 }
1429 );
1430 });
1431
1432
1433 // Image Comparison
1434 var eelImageBeforeAfter = function ($scope, $) {
1435 var $container = $scope.find(".eel_comparison-container");
1436 if (!$container.length) {
1437 return;
1438 }
1439 $container.each(function () {
1440 var $this = $(this);
1441 var offset = $this.data("offset") || 0.5;
1442 var orientation = $this.data("orientation") || "horizontal";
1443 var before_text = $this.data("before_label") || "Before";
1444 var after_text = $this.data("after_label") || "After";
1445 var initeel_comparison = function() {
1446 if ($.isFunction($.fn.eel_comparison)) {
1447 $this.eel_comparison({
1448 default_offset_pct: offset,
1449 orientation: orientation,
1450 before_label: before_text,
1451 after_label: after_text,
1452 no_overlay: false,
1453 move_slider_on_hover: false,
1454 click_to_move: true
1455 });
1456 }
1457 };
1458 if (document.readyState === 'complete') {
1459 initeel_comparison();
1460 } else {
1461 $(window).on('load', initeel_comparison);
1462 }
1463 });
1464 };
1465 $(window).on('elementor/frontend/init', function () {
1466 elementorFrontend.hooks.addAction('frontend/element_ready/eel-image-before-after.default', eelImageBeforeAfter);
1467 });
1468
1469
1470 jQuery(window).on('elementor/frontend/init', function() {
1471 elementorFrontend.hooks.addAction('frontend/element_ready/eel-vertical-navigation.default', function($scope) {
1472 const menuContainer = $scope.find('.eel-vertical-verticalmenu.eel_click');
1473 if (menuContainer.length) {
1474 const icons = $scope.find('.eel-vertical-menu-area .submenu-parent-icon');
1475
1476 icons.on('click', function(e) {
1477 e.preventDefault();
1478 e.stopPropagation();
1479
1480 const $this = jQuery(this);
1481 const parentLi = $this.closest('li');
1482 const subMenu = parentLi.find('> .sub-menu.eel_click');
1483 const parentLink = $this.closest('a');
1484
1485 if (subMenu.length === 0) return;
1486
1487 if (parentLi.hasClass('is-open-vertical')) {
1488 subMenu.css('max-height', subMenu[0].scrollHeight + 'px');
1489 setTimeout(() => {
1490 subMenu.css({
1491 'max-height': '0',
1492 'opacity': '0'
1493 });
1494 }, 10);
1495
1496 parentLi.removeClass('is-open-vertical');
1497 parentLink.removeClass('eel-vertical-open');
1498 } else {
1499 const siblings = parentLi.siblings('.is-open-vertical');
1500 siblings.each(function() {
1501 const siblingSub = jQuery(this).find('> .sub-menu.eel_click');
1502 siblingSub.css({
1503 'max-height': '0',
1504 'opacity': '0'
1505 });
1506 jQuery(this).removeClass('is-open-vertical');
1507 jQuery(this).find('> a').removeClass('eel-vertical-open');
1508 });
1509
1510 parentLi.addClass('is-open-vertical');
1511 parentLink.addClass('eel-vertical-open');
1512 subMenu.css({
1513 'opacity': '1',
1514 'max-height': subMenu[0].scrollHeight + 'px'
1515 });
1516
1517 subMenu.one('transitionend', function() {
1518 if (parentLi.hasClass('is-open-vertical')) {
1519 subMenu.css('max-height', 'none');
1520 }
1521 });
1522 }
1523 });
1524 }
1525 });
1526 });
1527
1528 })(jQuery);
1529
1530
1531
1532
1533 // Sticky Header (navigation-menu widget).
1534 // Only runs when the widget enabled "Sticky Header" — in that case the PHP
1535 // localizes `eelStickyHeaderSettings` to this handle. When the option is off
1536 // the variable is absent, so this block no-ops even though custom.js loads on
1537 // every page.
1538 (function($) {
1539 function initStickyHeaderScript($scope) {
1540 // Gate: bail unless the nav widget turned sticky on.
1541 if (typeof eelStickyHeaderSettings === 'undefined' || !eelStickyHeaderSettings.enableSticky) {
1542 return;
1543 }
1544
1545 // Skip on mobile — the margin-top mutation on scroll causes layout
1546 // shift that delays Elementor's IntersectionObserver, making motion
1547 // fx widgets load late.
1548 if (window.innerWidth < 768) return;
1549
1550 var header = $('header.easy-site-header');
1551 var page = $('#page');
1552 var topbar = $('.topbar-sticky-hide-yes');
1553 var isFixedHeader = $('body').is('.easyel-fixed-header, .rivora-sidebar-menu');
1554
1555 if (!header.length || !page.length) {
1556 return;
1557 }
1558
1559 header.addClass("eel-sticky-header-on");
1560
1561 function updatePaddingAndMargin(scrollDirection, scroll) {
1562 var headerHeight = header.outerHeight();
1563 var topbarHeight = topbar.length ? topbar.outerHeight() : 0;
1564 var isTopSticky = header.hasClass('eel-fixed-top-sticky');
1565
1566 // page padding-top
1567 if (typeof eelStickyHeaderSettings !== 'undefined' && eelStickyHeaderSettings.enablePadding) {
1568 if (!isFixedHeader) {
1569 page.css('padding-top', headerHeight + 'px');
1570 } else {
1571 page.css('padding-top', '');
1572 }
1573 }
1574
1575 // fixed-top-sticky
1576 if (isTopSticky) {
1577 if (scroll > 100) {
1578 header.addClass('eel--fixed-top-sticky');
1579 header.css('margin-top', `-${topbarHeight}px`);
1580 } else {
1581 header.removeClass('eel--fixed-top-sticky');
1582 header.css('margin-top', '0px');
1583 }
1584 return;
1585 }
1586
1587 // sticky behavior
1588 if (header.hasClass('eel-up-scroll')) {
1589 header.css('margin-top', `-${topbarHeight}px`);
1590 } else {
1591 header.css('margin-top', '0px');
1592 }
1593 }
1594
1595 let lastScroll = 0;
1596 const stickyScrollThreshold = 50;
1597
1598 function sticky_header() {
1599 let scroll = $(window).scrollTop();
1600 let isTopSticky = header.hasClass('eel-fixed-top-sticky');
1601 let scrollDirection = scroll > lastScroll ? 'down' : 'up';
1602
1603 if (isTopSticky) {
1604 header.addClass('eel-sticky-header');
1605 updatePaddingAndMargin(scrollDirection, scroll);
1606 lastScroll = scroll;
1607 return;
1608 }
1609
1610 if (scroll > stickyScrollThreshold) {
1611 header.addClass('eel-sticky-header');
1612 if (scroll > lastScroll) {
1613 header.removeClass('eel-up-scroll').addClass('eel-down-scroll');
1614 } else {
1615 header.removeClass('eel-down-scroll').addClass('eel-up-scroll');
1616 }
1617 } else {
1618 header.removeClass('eel-sticky-header eel-up-scroll eel-down-scroll');
1619 }
1620
1621 updatePaddingAndMargin(scrollDirection, scroll);
1622 lastScroll = scroll;
1623 }
1624
1625 $(document).ready(function() {
1626 updatePaddingAndMargin('up', 0);
1627 sticky_header();
1628 });
1629
1630 $(window).on('scroll resize', function() {
1631 sticky_header();
1632 });
1633 }
1634
1635 $(window).on('elementor/frontend/init', function() {
1636 elementorFrontend.hooks.addAction('frontend/element_ready/global', initStickyHeaderScript);
1637 });
1638 })(jQuery);
1639