PageSourceSearch

https://omidtricot.com/wp-content/themes/bakala/assets/js/gsap/bk-gsap-init.js?ver=1791291911

js omidtricot.com collected 2026-10-10 02:05:01 UTC 82,241 bytes, 1,981 lines download raw bytes

1/**
2 * Bakala Theme - GSAP Controller & Elementor Integration
3 * High-Performance Animation Engine
4 * Version: 1.0.0
5 */
6
7(function ($) {
8    'use strict';
9
10    window.bkGSAP = window.bkGSAP || {};
11
12    const isRTL = $('body').hasClass('rtl') || $('html').attr('dir') === 'rtl';
13
14    // 1. Initialize Plugins
15    function initGSAPCore() {
16        if (typeof gsap === 'undefined') return false;
17
18        if (typeof ScrollTrigger !== 'undefined') {
19            gsap.registerPlugin(ScrollTrigger);
20        }
21        if (typeof Draggable !== 'undefined') {
22            gsap.registerPlugin(Draggable);
23        }
24        return true;
25    }
26
27    initGSAPCore();
28
29    // 2. Smooth Scroll Engine (Lenis + GSAP ScrollTrigger Integration)
30    function initSmoothScroll() {
31        if (typeof Lenis === 'undefined' || typeof gsap === 'undefined') return;
32
33        const vars = window.bk_gsap_vars || {};
34        if (vars.smooth_scroll !== '1' && vars.smooth_scroll !== true) return;
35
36        // Check reduced motion
37        if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
38
39        // Pages without a doctype render in quirks mode, where Lenis can't scroll the document
40        if (document.compatMode === 'BackCompat') return;
41
42        // Check mobile/tablet disable
43        const isMobile = window.innerWidth <= 1024;
44        if (isMobile && vars.smooth_scroll_mobile !== '1' && vars.smooth_scroll_mobile !== true) {
45            return;
46        }
47
48        const duration = parseFloat(vars.smooth_scroll_speed) || 1.1;
49
50        // Determine if an element or its ancestors should bypass Lenis smooth scroll
51        function shouldPreventLenis(node) {
52            if (!node || node === document.documentElement || node === document.body) return false;
53
54            // Do not prevent smooth scroll when interacting with login/register form or account page content on desktop
55            if (typeof node.closest === 'function') {
56                const accountContainer = node.closest('#login-register, .bk_myaccount, .woocommerce-account, .default-page-template');
57                if (accountContainer && !node.closest('.modal.show, [role="dialog"], .swal2-container')) {
58                    return false;
59                }
60            }
61
62            // Direct data-lenis-prevent attributes
63            if (typeof node.hasAttribute === 'function') {
64                if (node.hasAttribute('data-lenis-prevent') ||
65                    node.hasAttribute('data-lenis-prevent-wheel') ||
66                    node.hasAttribute('data-lenis-prevent-touch')) {
67                    return true;
68                }
69            }
70
71            // Check if node is inside any modal, popup, floating contact box, drawer, dropdown, or overlay
72            if (typeof node.closest === 'function') {
73                const preventedSelectors = [
74                    '.modal',
75                    '.modal-dialog',
76                    '.modal-content',
77                    '.modal-body',
78                    '[role="dialog"]',
79                    '[aria-modal="true"]',
80                    '.bakala-float-call',
81                    '.bakala-messangers-block',
82                    '.bakala-messangers-list-container',
83                    '.bakala-messangers-list',
84                    '#bakala-float-call-overlay',
85                    '.offcanvas',
86                    '.offcanvas-body',
87                    '.drawer',
88                    '.c-drawer',
89                    '.cart-drawer',
90                    '.filter-sidebar',
91                    '.c-bottom-sheet',
92                    '.bottom-sheet',
93                    '.c-quick-view',
94                    '.fancybox-container',
95                    '.fancybox-content',
96                    '.fancybox-inner',
97                    '.remodal',
98                    '.remodal-wrapper',
99                    '.micromodal-slide',
100                    '.modal__container',
101                    '.swal2-container',
102                    '.swal2-popup',
103                    '.swal2-content',
104                    '.sweet-alert',
105                    '.select2-dropdown',
106                    '.select2-results',
107                    '.dropdown-menu',
108                    '.bk-dropdown',
109                    '.ui-autocomplete',
110                    '.table-responsive',
111                    '.dataTables_wrapper',
112                    '.dataTables_scrollBody',
113                    '.ps',
114                    '.simplebar-content-wrapper',
115                    '.lenis-prevent',
116                    '.no-smooth-scroll',
117                    '.mini-cart-dropdown',
118                    '.c-header__user-dropdown',
119                    '.c-header__mobile-menu',
120                    '.remodal-gallery-thumbs-container',
121                    '.remodal-gallery-thumbs',
122                    '.bakala_ai_popup',
123                    '.bakala_categories_mobile',
124                    '.bakala_categories_mobile *',
125                    '.menu-container',
126                    '.menu-header',
127                    '.menu-content',
128                    '.menu-category'
129                ].join(',');
130
131                if (node.closest(preventedSelectors)) {
132                    return true;
133                }
134            }
135
136            // Check if element or parent has scrollable overflow
137            let el = node;
138            while (el && el !== document.documentElement && el !== document.body) {
139                try {
140                    const style = window.getComputedStyle(el);
141                    const overflowY = style.overflowY;
142                    const overflowX = style.overflowX;
143                    const canScrollY = (overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight;
144                    const canScrollX = (overflowX === 'auto' || overflowX === 'scroll') && el.scrollWidth > el.clientWidth;
145                    if (canScrollY || canScrollX) {
146                        return true;
147                    }
148                } catch (e) {}
149                el = el.parentElement;
150            }
151
152            return false;
153        }
154
155        try {
156            const lenis = new Lenis({
157                duration: duration,
158                easing: function (t) {
159                    return Math.min(1, 1.001 - Math.pow(2, -10 * t));
160                },
161                smoothWheel: true,
162                syncTouch: false,
163                touchMultiplier: 1.5,
164                infinite: false,
165                prevent: shouldPreventLenis
166            });
167
168            window.bkLenis = lenis;
169
170            // Sync with ScrollTrigger if available
171            if (typeof ScrollTrigger !== 'undefined') {
172                lenis.on('scroll', ScrollTrigger.update);
173
174                gsap.ticker.add(function (time) {
175                    lenis.raf(time * 1000);
176                });
177
178                gsap.ticker.lagSmoothing(0);
179            } else {
180                function raf(time) {
181                    lenis.raf(time);
182                    requestAnimationFrame(raf);
183                }
184                requestAnimationFrame(raf);
185            }
186
187            // Automatically mark scrollable popups and modals with data-lenis-prevent
188            function markScrollableContainers() {
189                $('.modal, .modal-dialog, .modal-content, .modal-body, .bakala-float-call, .bakala-messangers-block, .bakala-messangers-list-container, .bakala-messangers-list, .swal2-container, .swal2-popup, .fancybox-content, .offcanvas, .drawer, .cart-drawer, .c-bottom-sheet, .bottom-sheet, .select2-dropdown, .bakala_ai_popup')
190                    .attr('data-lenis-prevent', 'true');
191            }
192            markScrollableContainers();
193
194            // Observe dynamic modals inserted into DOM
195            if (window.MutationObserver) {
196                const observer = new MutationObserver(function (mutations) {
197                    let shouldUpdate = false;
198                    for (let i = 0; i < mutations.length; i++) {
199                        if (mutations[i].addedNodes && mutations[i].addedNodes.length) {
200                            shouldUpdate = true;
201                            break;
202                        }
203                    }
204                    if (shouldUpdate) {
205                        markScrollableContainers();
206                    }
207                });
208                observer.observe(document.body, { childList: true, subtree: true });
209            }
210
211            // Bootstrap Modal event listener to pause/resume Lenis safely
212            $(document).on('show.bs.modal shown.bs.modal', function (e) {
213                $(e.target).attr('data-lenis-prevent', 'true')
214                           .find('.modal-content, .modal-body, .modal-dialog')
215                           .attr('data-lenis-prevent', 'true');
216                if (window.bkLenis) {
217                    window.bkLenis.stop();
218                }
219            });
220
221            $(document).on('hide.bs.modal hidden.bs.modal', function () {
222                setTimeout(function () {
223                    if (!$('.modal.show').length && !$('.swal2-container').length && !$('.bakala-messangers-block.show-messageners-block').length) {
224                        if (window.bkLenis) {
225                            window.bkLenis.start();
226                        }
227                    }
228                }, 50);
229            });
230
231            // Floating contact button toggle handling
232            $(document).on('click', '.bakala-float-call-btn, #bakala-float-call-overlay, .bakala-float-call-close', function () {
233                setTimeout(function () {
234                    const isCallOpen = $('.bakala-messangers-block').hasClass('show-messageners-block');
235                    if (isCallOpen) {
236                        $('.bakala-float-call, .bakala-messangers-block, .bakala-messangers-list-container, .bakala-messangers-list').attr('data-lenis-prevent', 'true');
237                        if (window.bkLenis) {
238                            window.bkLenis.stop();
239                        }
240                    } else {
241                        if (!$('.modal.show').length && window.bkLenis) {
242                            window.bkLenis.start();
243                        }
244                    }
245                }, 50);
246            });
247
248            // Stop wheel/touch event bubbling from within scrollable containers to avoid interference
249            $(document).on('wheel touchmove', '.bakala-messangers-list-container, .bakala-messangers-block, .modal
249-body, [data-lenis-prevent]', function (e) {
250                e.stopPropagation();
251            });
252
253            // Support anchor scrolling (excluding WooCommerce tabs, bootstrap tabs, and programmatic events)
254            $(document).on('click', 'a[href^="#"]:not([href="#"]):not([href^="#tab-"]):not(.nav-link):not([data-toggle]):not([data-bs-toggle])', function (e) {
255                if (!e || !e.originalEvent) return; // Only respond to real user clicks, not scripts
256                const target = $(this).attr('href');
257                if (target && $(target).length && !$(this).closest('.wc-tabs, .nav-tabs, .products-tabs, .woocommerce-tabs').length) {
258                    e.preventDefault();
259                    lenis.scrollTo(target, { offset: -60, duration: 1.2 });
260                }
261            });
262
263            // Ensure Lenis limits are accurately calculated on window load and dynamic content render
264            $(window).on('load', function () {
265                if (window.bkLenis) {
266                    window.bkLenis.resize();
267                }
268            });
269            setTimeout(function () {
270                if (window.bkLenis) {
271                    window.bkLenis.resize();
272                }
273            }, 300);
274        } catch (err) {
275            console.warn('Bakala GSAP: Lenis init error', err);
276        }
277    }
278
279    // 3. GSAP Product Carousel Engine
280    function initProductCarousel($scope) {
281        if (typeof gsap === 'undefined') return;
282
283        const $container = $scope.find('.bk-gsap-carousel-widget');
284        if (!$container.length) return;
285
286        $container.each(function () {
287            const $widget = $(this);
288            if ($widget.data('bk-gsap-carousel-inited')) return;
289            $widget.data('bk-gsap-carousel-inited', true);
290
291            const $viewport = $widget.find('.bk-gsap-carousel-viewport');
292            const $track = $widget.find('.bk-gsap-carousel-track');
293            const $cards = $track.find('.bk-gsap-product-card');
294            const $prevBtn = $widget.find('.bk-gsap-prev-btn');
295            const $nextBtn = $widget.find('.bk-gsap-next-btn');
296            const $progressBar = $widget.find('.bk-gsap-progress-bar');
297
298            if (!$cards.length) return;
299
300            const isAutoplay = $widget.attr('data-autoplay') === 'yes' || $widget.data('autoplay') === 'yes';
301            const autoplaySpeed = parseInt($widget.attr('data-autoplay-speed') || $widget.data('autoplay-speed'), 10) || 4500;
302            const enable3dTilt = ($widget.attr('data-tilt') === 'yes');
303
304            let currentX = 0;
305            let minX = 0;
306            let maxX = 0;
307            let autoplayTimer = null;
308            let didDrag = false;
309
310            function calculateBounds() {
311                const viewportWidth = $viewport.outerWidth();
312                const trackWidth = $track.outerWidth();
313
314                if (trackWidth <= viewportWidth) {
315                    minX = 0;
316                    maxX = 0;
317                } else {
318                    if (isRTL) {
319                        minX = 0;
320                        maxX = trackWidth - viewportWidth;
321                    } else {
322                        minX = -(trackWidth - viewportWidth);
323                        maxX = 0;
324                    }
325                }
326                updateProgressBar();
327            }
328
329            function updateProgressBar() {
330                if (!$progressBar.length) return;
331                const totalRange = Math.abs(minX - maxX);
332                if (totalRange <= 0) {
333                    $progressBar.css('width', '100%');
334                    return;
335                }
336                const progress = Math.min(1, Math.max(0, Math.abs(currentX) / totalRange));
337                const barWidth = Math.max(15, progress * 100);
338                $progressBar.css('width', barWidth + '%');
339            }
340
341            function moveTo(newX, duration) {
342                duration = typeof duration === 'number' ? duration : 0.55;
343                newX = Math.max(minX, Math.min(maxX, newX));
344                currentX = newX;
345
346                gsap.to($track[0], {
347                    x: currentX,
348                    duration: duration,
349                    ease: 'power2.out',
350                    overwrite: 'auto',
351                    onUpdate: updateProgressBar
352                });
353            }
354
355            function getStepWidth() {
356                const firstCard = $cards.first();
357                const gap = parseInt($track.css('gap'), 10) || 20;
358                return (firstCard.outerWidth() + gap) * 1.5;
359            }
360
361            // Navigation Click Handlers
362            $nextBtn.on('click', function (e) {
363                e.preventDefault();
364                stopAutoplay();
365                const step = getStepWidth();
366                if (isRTL) {
367                    moveTo(currentX + step);
368                } else {
369                    moveTo(currentX - step);
370                }
371                startAutoplay();
372            });
373
374            $prevBtn.on('click', function (e) {
375                e.preventDefault();
376                stopAutoplay();
377                const step = getStepWidth();
378                if (isRTL) {
379                    moveTo(currentX - step);
380                } else {
381                    moveTo(currentX + step);
382                }
383                startAutoplay();
384            });
385
386            // Touch / Mouse Drag Handling with GSAP Draggable
387            if (typeof Draggable !== 'undefined') {
388                Draggable.create($track[0], {
389                    type: 'x',
390                    edgeResistance: 0.75,
391                    dragResistance: 0.1,
392                    cursor: 'grab',
393                    activeCursor: 'grabbing',
394                    bounds: $viewport[0],
395                    allowContextMenu: true,
396                    onPress: function () {
397                        stopAutoplay();
398                        didDrag = false;
399                    },
400                    onDrag: function () {
401                        didDrag = true;
402                        currentX = this.x;
403                        updateProgressBar();
404                    },
405                    onDragEnd: function () {
406                        currentX = this.x;
407                        updateProgressBar();
408                        startAutoplay();
409                    }
410                });
411            }
412
413            // Prevent link click when dragging
414            $cards.find('a').on('click', function (e) {
415                if (didDrag) {
416                    e.preventDefault();
417                    e.stopImmediatePropagation();
418                    return false;
419                }
420            });
421
422            // Autoplay Logic
423            function startAutoplay() {
424                if (!isAutoplay || minX === maxX) return;
425                stopAutoplay();
426                autoplayTimer = setInterval(function () {
427                    const step = getStepWidth();
428                    let targetX;
429
430                    if (isRTL) {
431                        targetX = currentX + step;
432                        if (targetX > maxX) targetX = minX;
433                    } else {
434                        targetX = currentX - step;
435                        if (targetX < minX) targetX = maxX;
436                    }
437                    moveTo(targetX, 0.7);
438                }, autoplaySpeed);
439            }
440
441            function stopAutoplay() {
442                if (autoplayTimer) {
443                    clearInterval(autoplayTimer);
444                    autoplayTimer = null;
445                }
446            }
447
448            $widget.on('mouseenter', stopAutoplay).on('mouseleave', startAutoplay);
449
450            // Clean, subtle Entrance Animation with ScrollTrigger
451            if (typeof ScrollTrigger !== 'undefined') {
452                gsap.fromTo($cards.toArray(), 
453                    {
454                        opacity: 0,
455                        y: 20
456                    },
457                    {
458                        opacity: 1,
459                        y: 0,
460                        duration: 0.5,
461                        stagger: 0.04,
462                        ease: 'power2.out',
463                        scrollTrigger: {
464                            trigger: $widget[0],
465                            start: 'top 90%',
466                            once: true
467                        }
468                    }
469                );
470            }
471
472            // Ensure flat, clean cards without any 3D rotation
473            $cards.each(function() {
474                gsap.set(this, { clearProps: 'rotateX,rotateY,transformPerspective' });
475            });
476
477            // Calculate bounds initially and on resize
478            calculateBounds();
479            startAutoplay();
480            $(window).on('resize', calculateBounds);
481        });
482    }
483
484    // 4. Infinite Marquee Engine
485    function initMarquee($scope) {
486        if (typeof gsap === 'undefined') return;
487
488        const $marquees = $scope.find('.bk-marquee-wrap');
489        if (!$marquees.length) return;
490
491        $marquees.each(function () {
492            const $wrap = $(this);
493            if ($wrap.data('bk-marquee-inited')) return;
494            $wrap.data('bk-marquee-inited', true);
495
496            const $track = $wrap.find('.bk-marquee-track');
497            if (!$track.length) return;
498
499            const speed = parseFloat($wrap.data('speed')) || 25;
500            const direction = $wrap.data('direction') || 'normal';
501            const pauseOnHover = $wrap.data('pause') === 'yes';
502
503            const toXPercent = (direction === 'reverse') ? (isRTL ? -50 : 50) : (isRTL ? 50 : -50);
504
505            const tween = gsap.to($track[0], {
506                xPercent: toXPercent,
507                ease: 'none',
508                duration: speed,
509                repeat: -1
510            });
511
512            if (pauseOnHover) {
513                $wrap.on('mouseenter', function () {
514                    tween.pause();
515                }).on('mouseleave', function () {
516                    tween.play();
517                });
518            }
519        });
520    }
521
522    // 5. Parallax Banner Engine
523    function initParallaxBanner($scope) {
524        if (typeof gsap === 'undefined') return;
525
526        const $banners = $scope.find('.bk-parallax-banner');
527        if (!$banners.length) return;
528
529        $banners.each(function () {
530            const $banner = $(this);
531            if ($banner.data('bk-banner-inited')) return;
532            $banner.data('bk-banner-inited', true);
533
534            const isParallax = $banner.data('parallax') === 'yes';
535            const isFloat = $banner.data('float') === 'yes';
536            const $badge = $banner.find('.bk-banner-floating-badge');
537            const $mainImg = $banner.find('.bk-banner-main-img');
538
539            // Floating soft loop animation
540            if (isFloat && $badge.length) {
541                gsap.to($badge[0], {
542                    y: -8,
543                    duration: 2.2,
544                    repeat: -1,
545                    yoyo: true,
546                    ease: 'sine.inOut'
547                });
548            }
549
550            // ScrollTrigger Parallax
551            if (isParallax && typeof ScrollTrigger !== 'undefined' && $mainImg.length) {
552                gsap.to($mainImg[0], {
553                    y: -25,
554                    ease: 'none',
555                    scrollTrigger: {
556                        trigger: $banner[0],
557                        start: 'top bottom',
558                        end: 'bottom top',
559                        scrub: 1.2
560                    }
561                });
562            }
563        });
564    }
565
566    // 6. Advanced Image Slider Engine
567    function initImageSlider($scope) {
568        if (typeof gsap === 'undefined') return;
569
570        const $sliders = $scope.find('.bk-img-slider-wrap');
571        if (!$sliders.length) return;
572
573        $sliders.each(function () {
574            const $slider = $(this);
575            if ($slider.data('bk-slider-inited')) return;
576            $slider.data('bk-slider-inited', true);
577
578            const $slides = $slider.find('.bk-img-slide');
579            const $dots = $slider.find('.bk-slider-dot');
580            const $prevBtn = $slider.find('.bk-slider-prev');
581            const $nextBtn = $slider.find('.bk-slider-next');
582
583            if (!$slides.length) return;
584
585            let currentIndex = 0;
586            const totalSlides = $slides.length;
587            const effect = $slider.data('effect') || 'slide';
588            const isAutoplay = $slider.data('autoplay') === 'yes';
589            const autoplaySpeed = parseInt($slider.data('speed'), 10) || 5000;
590            let autoplayTimer = null;
591            let isAnimating = false;
592
593            function goToSlide(nextIndex, direction) {
594                if (isAnimating || nextIndex === currentIndex || totalSlides <= 1) return;
595                isAnimating = true;
596
597                const $currentSlide = $slides.eq(currentIndex);
598                const $nextSlide = $slides.eq(nextIndex);
599
600                direction = direction || (nextIndex > currentIndex ? 'next' : 'prev');
601                const moveSign = (direction === 'next') ? (isRTL ? -1 : 1) : (isRTL ? 1 : -1);
602
603                $dots.removeClass('is-active').eq(nextIndex).addClass('is-active');
604
605                if (effect === 'fade') {
606                    gsap.set($nextSlide[0], { opacity: 0, scale: 1.05, visibility: 'visible' });
607                    gsap.to($currentSlide[0], { opacity: 0, scale: 0.96, duration: 0.7, ease: 'power2.inOut' });
608                    gsap.to($nextSlide[0], {
609                        opacity: 1,
610                        scale: 1,
611                        duration: 0.7,
612                        ease: 'power2.inOut',
613                        onComplete: function () {
614                            $currentSlide.removeClass('is-active').css('visibility', 'hidden');
615                            $nextSlide.addClass('is-active');
616                            currentIndex = nextIndex;
617                            isAnimating = false;
618                        }
619                    });
620                } else {
621                    gsap.set($nextSlide[0], { xPercent: 100 * moveSign, opacity: 1, visibility: 'visible' });
622                    gsap.to($currentSlide[0], { xPercent: -100 * moveSign, duration: 0.75, ease: 'power2.inOut' });
623                    gsap.to($nextSlide[0], {
624                        xPercent: 0,
625                        duration: 0.75,
626                        ease: 'power2.inOut',
627                        onComplete: function () {
628                            $currentSlide.removeClass('is-active').css('visibility', 'hidden');
629                            $nextSlide.addClass('is-active');
630                            currentIndex = nextIndex;
631                            isAnimating = false;
632                        }
633                    });
634                }
635            }
636
637            $nextBtn.on('click', function (e) {
638                e.preventDefault();
639                stopAutoplay();
640                const next = (currentIndex + 1) % totalSlides;
641                goToSlide(next, 'next');
642                startAutoplay();
643            });
644
645            $prevBtn.on('click', function (e) {
646                e.preventDefault();
647                stopAutoplay();
648                const prev = (currentIndex - 1 + totalSlides) % totalSlides;
649                goToSlide(prev, 'prev');
650                startAutoplay();
651            });
652
653            $dots.on('click', function () {
654                const target = parseInt($(this).data('slide'), 10);
655                stopAutoplay();
656                goToSlide(target);
657                startAutoplay();
658            });
659
660            function startAutoplay() {
661                if (!isAutoplay || totalSlides <= 1) return;
662                stopAutoplay();
663                autoplayTimer = setInterval(function () {
664                    const next = (currentIndex + 1) % totalSlides;
665                    goToSlide(next, 'next');
666                }, autoplaySpeed);
667            }
668
669            function stopAutoplay() {
670                if (autoplayTimer) {
671                    clearInterval(autoplayTimer);
672                    autoplayTimer = null;
673                }
674            }
675
676            $slider.on('mouseenter', stopAutoplay).on('mouseleave', startAutoplay);
677            startAutoplay();
678        });
679    }
680
681    // 7. Center Stage 3D Layered Under-Cards Deck Slider Engine
682    function initPeekImageSlider($scope) {
683        if (typeof gsap === 'undefined') return;
684
685        const $wraps = $scope.find('.bk-peek-slider-wrap');
686        if (!$wraps.length) return;
687
688        $wraps.each(function () {
689            const $wrap = $(this);
690            if ($wrap.data('bk-peek-inited')) return;
691            $wrap.data('bk-peek-inited', true);
692
693            const $container = $wrap.find('.bk-peek-slider-container');
694            const $slides = $wrap.find('.bk-peek-slide');
695            const $dots = $wrap.find('.bk-peek-dot');
696            const $prevBtn = $wrap.find('.bk-peek-prev');
697            const $nextBtn = $wrap.find('.bk-peek-next');
698
699            const total = $slides.length;
700            if (total <= 1) return;
701
702            let currentIndex = 0;
703            const isAutoplay = $wrap.data('autoplay') === true || $wrap.data('autoplay') === 'true';
704            const speed = parseInt($wrap.data('speed'), 10) || 5000;
705            let timer = null;
706            const isRTL = $('body').hasClass('rtl') || $('html').attr('dir') === 'rtl';
707
708            function updateDeck(animate) {
709                if (animate === undefined) animate = true;
710
711                const contW = $container.width();
712                const isMobile = window.innerWidth <= 768;
713                // Offset percentage for the cards tucked underneath
714                const sideShift = isMobile ? contW * 0.36 : contW * 0.32;
715
716                $dots.removeClass('is-active').eq(currentIndex).addClass('is-active');
717
718                $slides.each(function (i) {
719                    const slide = this;
720                    let diff = i - currentIndex;
721
722                    // Wrap-around for infinite feeling
723                    if (diff > total / 2) diff -= total;
724                    if (diff < -total / 2) diff += total;
725
726                    let targetX = 0;
727                    let targetY = 0;
728                    let targetScale = 0.7;
729                    let targetRotateY = 0;
730                    let targetRotateZ = 0;
731                    let targetOpacity = 0;
732                    let targetBrightness = 0.5;
733                    let zIndex = 1;
734
735                    if (diff === 0) {
736                        // 🌟 Active card in the center on top
737                        targetX = 0;
738                        targetY = 0;
739                        targetScale = 1;
740                        targetRotateY = 0;
741                        targetRotateZ = 0;
742                        targetOpacity = 1;
743                        targetBrightness = 1;
744                        zIndex = 20;
745                        $(slide).addClass('is-active');
746                    } else if (diff === -1 || (currentIndex === 0 && i === total - 1)) {
747                        // 🌟 Previous card placed on the side UNDERNEATH
748                        const directionMult = isRTL ? 1 : -1;
749                        targetX = directionMult * sideShift;
750                        targetY = isMobile ? 8 : 14;
751                        targetScale = isMobile ? 0.88 : 0.85;
752                        targetRotateY = isRTL ? -10 : 10;
753                        targetRotateZ = isRTL ? 2 : -2;
754                        targetOpacity = 0.78;
755                        targetBrightness = 0.7;
756                        zIndex = 10;
757                        $(slide).removeClass('is-active');
758                    } else if (diff === 1 || (currentIndex === total - 1 && i === 0)) {
759                        // 🌟 Next card placed on the opposite side UNDERNEATH
760                        const directionMult = isRTL ? -1 : 1;
761                        targetX = directionMult * sideShift;
762                        targetY = isMobile ? 8 : 14;
763                        targetScale = isMobile ? 0.88 : 0.85;
764                        targetRotateY = isRTL ? 10 : -10;
765                        targetRotateZ = isRTL ? -2 : 2;
766                        targetOpacity = 0.78;
767                        targetBrightness = 0.7;
768                        zIndex = 10;
769                        $(slide).removeClass('is-active');
770                    } else {
771                        // Other cards hidden behind
772                        const directionMult = (diff > 0 ? 1 : -1) * (isRTL ? -1 : 1);
773                        targetX = directionMult * (sideShift * 1.6);
774                        targetY = 25;
775                        targetScale = 0.65;
776                        targetOpacity = 0;
777                        targetBrightness = 0.3;
778                        zIndex = 1;
779                        $(slide).removeClass('is-active');
780                    }
781
782                    if (animate) {
783                        gsap.to(slide, {
784                            xPercent: -50,
785                            yPercent: -50,
786                            x: targetX,
787                            y: targetY,
788                            scale: targetScale,
789                            rotateY: targetRotateY,
790                            rotateZ: targetRotateZ,
791                            opacity: targetOpacity,
792                            filter: `brightness(${targetBrightness})`,
793                            zIndex: zIndex,
794                            duration: 0.65,
795                            ease: 'power3.out'
796                        });
797                    } else {
798                        gsap.set(slide, {
799                            xPercent: -50,
800                            yPercent: -50,
801                            x: targetX,
802                            y: targetY,
803                            scale: targetScale,
804                            rotateY: targetRotateY,
805                            rotateZ: targetRotateZ,
806                            opacity: targetOpacity,
807                            filter: `brightness(${targetBrightness})`,
808                            zIndex: zIndex
809                        });
810                    }
811                });
812            }
813
814            function goTo(idx) {
815                if (idx < 0) idx = total - 1;
816                if (idx >= total) idx = 0;
817                currentIndex = idx;
818                updateDeck(true);
819            }
820
821            // Click side card to bring it to center
822            $slides.on('click', function (e) {
823                const idx = parseInt($(this).data('index'), 10);
824                if (idx !== currentIndex) {
825                    e.preventDefault();
826                    stopAutoplay();
827                    goTo(idx);
828                    startAutoplay();
829                }
830            });
831
832            // Nav buttons (clicking left/right button navigates towards that respective side)
833            $prevBtn.on('click', function (e) {
834                e.preventDefault();
835                stopAutoplay();
836                goTo(isRTL ? currentIndex + 1 : currentIndex - 1);
837                startAutoplay();
838            });
839
840            $nextBtn.on('click', function (e) {
841                e.preventDefault();
842                stopAutoplay();
843                goTo(isRTL ? currentIndex - 1 : currentIndex + 1);
844                startAutoplay();
845            });
846
847            // Pagination dots
848            $dots.on('click', function () {
849                const idx = parseInt($(this).data('index'), 10);
850                stopAutoplay();
851                goTo(idx);
852                startAutoplay();
853            });
854
855            // Touch / Drag swipe
856            let startTouchX = 0;
857            $container.on('touchstart mousedown', function (e) {
858                startTouchX = e.touches ? e.touches[0].clientX : e.clientX;
859            }).on('touchend mouseup', function (e) {
860                const endTouchX = e.changedTouches ? e.changedTouches[0].clientX : e.clientX;
861                const diffX = endTouchX - startTouchX;
862                if (Math.abs(diffX) > 40) {
863                    stopAutoplay();
864                    if (diffX > 0) {
865                        goTo(isRTL ? currentIndex + 1 : currentIndex - 1);
866                    } else {
867                        goTo(isRTL ? currentIndex - 1 : currentIndex + 1);
868                    }
869                    startAutoplay();
870                }
871            });
872
873            // Autoplay
874            function startAutoplay() {
875                if (!isAutoplay || total <= 1) return;
876                stopAutoplay();
877                timer = setInterval(function () {
878                    goTo(currentIndex + 1);
879                }, speed);
880            }
881
882            function stopAutoplay() {
883                if (timer) {
884                    clearInterval(timer);
885                    timer = null;
886                }
887            }
888
889            $wrap.on('mouseenter', stopAutoplay).on('mouseleave', startAutoplay);
890
891            $(window).on('resize', function () {
892                updateDeck(false);
893            });
894
895            setTimeout(function () {
896                updateDeck(false);
897            }, 100);
898
899            startAutoplay();
900        });
901    }
902
903    // 8. Kinetic Text Animation Engine (12 Distinct Effects)
904    function initKineticText($scope) {
905        if (typeof gsap === 'undefined') return;
906
907        const $textWraps = $scope.find('.bk-kinetic-text-wrap');
908        if (!$textWraps.length) return;
909
910        $textWraps.each(function () {
911            const $wrap = $(this);
912            if ($wrap.data('bk-kinetic-inited')) return;
913            $wrap.data('bk-kinetic-inited', true);
914
915            const $heading = $wrap.find('.bk-kinetic-text');
916            if (!$heading.length) return;
917
918            const effect = $wrap.data('effect') || 'glow_rise';
919            const trigger = $wrap.data('trigger') || 'scroll';
920            const duration = parseFloat($wrap.data('duration')) || 0.8;
921            const stagger = parseFloat($wrap.data('stagger')) || 0.07;
922            const rawText = $heading.text().trim();
923
924            if (!rawText) return;
925
926            let $targets;
927
928            if (effect === 'multi_word_chunk') {
929                const words = rawText.split(/\s+/);
930                const chunks = [];
931                for (let i = 0; i < words.length; i += 4) {
932                    chunks.push(words.slice(i, i + 4).join(' '));
933                }
934                $heading.html(chunks.map(c => `<span class="bk-chunk">${c}</span>`).join(' '));
935                $targets = $heading.find('.bk-chunk');
936            } else if (effect === 'wave_twist' || effect === 'spin_y_3d' || effect === 'split_explosion') {
937                const words = rawText.split(/\s+/);
938                $heading.html(words.map(w => {
939                    const chars = Array.from(w).map(ch => `<span class="bk-char">${ch}</span>`).join('');
940                    return `<span class="bk-word">${chars}</span>`;
941                }).join(' '));
942                $targets = $heading.find('.bk-char');
943            } else {
944                const words = rawText.split(/\s+/);
945                $heading.html(words.map(w => `<span class="bk-word">${w}</span>`).join(' '));
946                $targets = $heading.find('.bk-word');
947            }
948
949            const tl = gsap.timeline({
950                paused: trigger === 'scroll' && typeof ScrollTrigger !== 'undefined'
951            });
952
953            switch (effect) {
954                case 'glow_rise':
955                    tl.fromTo($targets.toArray(),
956                        { opacity: 0, y: 35, textShadow: '0 0 20px rgba(59,130,246,0.9)', filter: 'drop-shadow(0 0 12px rgba(59,130,246,0.6))' },
957                        { opacity: 1, y: 0, textShadow: '0 0 0px rgba(59,130,246,0)', filter: 'drop-shadow(0 0 0px transparent)', duration: duration, stagger: stagger, ease: 'power3.out' }
958                    );
959                    break;
960
961                case 'flip_down_3d':
962                    tl.fromTo($targets.toArray(),
963                        { opacity: 0, rotateX: -90, transformOrigin: '50% 0%', y: -20 },
964                        { opacity: 1, rotateX: 0, y: 0, duration: duration, stagger: stagger, ease: 'back.out(1.7)' }
965                    );
966                    break;
967
968                case 'wave_twist':
969                    $targets.each(function (idx) {
970                        const sign = (idx % 2 === 0) ? 1 : -1;
971                        tl.fromTo(this,
972                            { opacity: 0, y: 30 * sign, rotateZ: 18 * sign },
973                            { opacity: 1, y: 0, rotateZ: 0, duration: duration, ease: 'power2.out' },
974                            idx * stagger
975                        );
976                    });
977                    break;
978
979                case 'elastic_bounce':
980                    tl.fromTo($targets.toArray(),
981                        { opacity: 0, scale: 0.1, y: 30 },
982                        { opacity: 1, scale: 1, y: 0, duration: duration * 1.3, stagger: stagger, ease: 'elastic.out(1.2, 0.45)' }
983                    );
984                    break;
985
986                case 'spin_y_3d':
987                    tl.fromTo($targets.toArray(),
988                        { opacity: 0, rotateY: 180, transformOrigin: '50% 50%' },
989                        { opacity: 1, rotateY: 0, duration: duration, stagger: stagger * 0.7, ease: 'power3.out' }
990                    );
991                    break;
992
993                case 'drop_spring':
994                    tl.fromTo($targets.toArray(),
995                        { opacity: 0, y: -70, scaleY: 1.35 },
996                        { opacity: 1, y: 0, scaleY: 1, duration: duration * 1.2, stagger: stagger, ease: 'bounce.out' }
997                    );
998                    break;
999
1000                case 'rtl_typewriter':
1001                    tl.fromTo($targets.toArray(),
1002                        { opacity: 0, x: isRTL ? -25 : 25, skewX: isRTL ? 15 : -15 },
1003                        { opacity: 1, x: 0, skewX: 0, duration: duration * 0.6, stagger: stagger * 1.2, ease: 'power2.out' }
1004                    );
1005                    break;
1006
1007                case 'split_explosion':
1008                    $targets.each(function (idx) {
1009                        const randX = (Math.random() - 0.5) * 80;
1010                        const randY = (Math.random() - 0.5) * 80;
1011                        const randRot = (Math.random() - 0.5) * 90;
1012                        tl.fromTo(this,
1013                            { opacity: 0, x: randX, y: randY, rotate: randRot, scale: 0.2 },
1014                            { opacity: 1, x: 0, y: 0, rotate: 0, scale: 1, duration: duration, ease: 'power4.out' },
1015                            idx * (stagger * 0.6)
1016                        );
1017                    });
1018                    break;
1019
1020                case 'skew_reveal':
1021                    tl.fromTo($targets.toArray(),
1022                        { opacity: 0, skewX: -30, y: 40 },
1023                        { opacity: 1, skewX: 0, y: 0, duration: duration, stagger: stagger, ease: 'power3.out' }
1024                    );
1025                    break;
1026
1027                case 'cinematic_blur':
1028                    tl.fromTo($targets.toArray(),
1029                        { opacity: 0, filter: 'blur(16px)', scale: 1.25 },
1030                        { opacity: 1, filter: 'blur(0px)', scale: 1, duration: duration, stagger: stagger, ease: 'power2.out' }
1031                    );
1032                    break;
1033
1034                case 'multi_word_chunk':
1035                    tl.fromTo($targets.toArray(),
1036                        { opacity: 0, y: 25, filter: 'blur(6px)' },
1037                        { opacity: 1, y: 0, filter: 'blur(0px)', duration: duration, stagger: stagger * 2.2, ease: 'power2.out' }
1038                    );
1039                    break;
1040
1041                case 'roll_in_3d':
1042                    tl.fromTo($targets.toArray(),
1043                        { opacity: 0, rotateX: -110, z: -150, y: 25 },
1044                        { opacity: 1, rotateX: 0, z: 0, y: 0, duration: duration, stagger: stagger, ease: 'power3.out' }
1045                    );
1046                    break;
1047
1048                default:
1049                    tl.fromTo($targets.toArray(),
1050                        { opacity: 0, y: 20 },
1051                        { opacity: 1, y: 0, duration: duration, stagger: stagger, ease: 'power2.out' }
1052                    );
1053            }
1054
1055            const isLoop = $wrap.attr('data-loop') === 'yes' || $wrap.data('loop') === 'yes';
1056            const loopDelay = parseFloat($wrap.attr('data-loop-delay') || $wrap.data('loop-delay')) || 2.5;
1057
1058            if (isLoop) {
1059                tl.to($targets.toArray(), {
1060                    opacity: 0,
1061                    y: -15,
1062                    filter: 'blur(6px)',
1063                    duration: 0.45,
1064                    stagger: 0.02,
1065                    ease: 'power2.in'
1066                }, `+=${loopDelay}`);
1067                tl.repeat(-1);
1068            }
1069
1070            if (trigger === 'scroll' && typeof ScrollTrigger !== 'undefined') {
1071                ScrollTrigger.create({
1072                    trigger: $wrap[0],
1073                    start: 'top 88%',
1074                    once: !isLoop,
1075                    onEnter: function () {
1076                        tl.play();
1077                    }
1078                });
1079            } else {
1080                tl.play();
1081            }
1082        });
1083    }
1084
1085    // 8. 3D Stacking Cards Engine (Clean, Sharp Scroll Pinned Stack)
1086    function initStackingCards($scope) {
1087        if (typeof gsap === 'undefined' || typeof ScrollTrigger === 'undefined') return;
1088
1089        const $wraps = $scope.filter('.bk-stacking-cards-wrap').add($scope.find('.bk-stacking-cards-wrap'));
1090        if (!$wraps.length) return;
1091
1092        $wraps.each(function () {
1093            const $wrap = $(this);
1094            if ($wrap.data('bk-stack-inited') && !(window.elementorFrontend && window.elementorFrontend.isEditMode && window.elementorFrontend.isEditMode())) {
1095                return;
1096            }
1097            $wrap.data('bk-stack-inited', true);
1098
1099            const $cards = $wrap.find('.bk-stack-card');
1100            const total = $cards.length;
1101            if (!total) return;
1102
1103            $cards.each(function (i) {
1104                const card = this;
1105                const nextCard = (i < total - 1) ? $cards[i + 1] : null;
1106
1107                // 1. Entrance Animation: Card enters smaller (scale 0.82) and expands to full size (1.0) as scroll reaches it
1108                ScrollTrigger.create({
1109                    trigger: card,
1110                    start: 'top bottom',
1111                    end: 'top 100px',
1112                    scrub: true,
1113                    onUpdate: function (self) {
1114                        const p = self.progress;
1115                        if (!nextCard || !nextCard._bkStackActive) {
1116                            const scale = 0.82 + (0.18 * p);
1117                            const y = (1 - p) * 35;
1118                            const opacity = 0.5 + (0.5 * p);
1119                            gsap.set(card, {
1120                                scale: scale,
1121                                y: y,
1122                                opacity: opacity,
1123                                transformOrigin: 'top center'
1124                            });
1125                        }
1126                    }
1127                });
1128
1129                // 2. 3D Stacking Exit Animation: When next card scrolls up, previous card scales down into the 3D stack
1130                if (nextCard) {
1131                    ScrollTrigger.create({
1132                        trigger: nextCard,
1133                        start: 'top 65%',
1134                        end: 'top 100px',
1135                        scrub: true,
1136                        onUpdate: function (self) {
1137                            const p = self.progress;
1138                            if (p > 0) {
1139                                nextCard._bkStackActive = true;
1140                                const scale = 1.0 - (0.06 * p);
1141                                const y = -(16 * p);
1142                                const brightness = 1.0 - (0.15 * p);
1143                                gsap.set(card, {
1144                                    scale: scale,
1145                                    y: y,
1146                                    filter: 'brightness(' + brightness + ')',
1147                                    transformOrigin: 'top center'
1148                                });
1149                            } else {
1150                                nextCard._bkStackActive = false;
1151                                gsap.set(card, {
1152                                    scale: 1,
1153                                    y: 0,
1154                                    filter: 'brightness(1)'
1155                                });
1156                            }
1157                        }
1158                    });
1159                }
1160            });
1161        });
1162    }
1163
1164    // 9. Interactive Before/After Comparison Engine
1165    function initBeforeAfter($scope) {
1166        if (typeof gsap === 'undefined') return;
1167
1168        const $wraps = $scope.find('.bk-before-after-wrap');
1169        if (!$wraps.length) return;
1170
1171        $wraps.each(function () {
1172            const $wrap = $(this);
1173            if ($wrap.data('bk-ba-inited')) return;
1174            $wrap.data('bk-ba-inited', true);
1175
1176            const $after = $wrap.find('.bk-ba-after');
1177            const $handle = $wrap.find('.bk-ba-handle');
1178            const initPos = parseFloat($wrap.data('position')) || 50;
1179
1180            function setPosition(percent) {
1181                percent = Math.max(0, Math.min(100, percent));
1182                $after.css({
1183                    'clip-path': 'polygon(0% 0%, ' + percent + '% 0%, ' + percent + '% 100%, 0% 100%)',
1184                    '-webkit-clip-path': 'polygon(0% 0%, ' + percent + '% 0%, ' + percent + '% 100%, 0% 100%)'
1185                });
1186                $handle.css('left', percent + '%');
1187            }
1188
1189            setPosition(initPos);
1190
1191            let isDragging = false;
1192
1193            function getClientX(e) {
1194                if (e.touches && e.touches.length > 0) {
1195                    return e.touches[0].clientX;
1196                }
1197                if (e.changedTouches && e.changedTouches.length > 0) {
1198                    return e.changedTouches[0].clientX;
1199                }
1200                return e.clientX;
1201            }
1202
1203            function onMove(e) {
1204                if (!isDragging) return;
1205                const rect = $wrap[0].getBoundingClientRect();
1206                const clientX = getClientX(e);
1207                const offsetX = clientX - rect.left;
1208                const percent = (offsetX / rect.width) * 100;
1209                setPosition(percent);
1210            }
1211
1212            $wrap.on('mousedown touchstart', function (e) {
1213                isDragging = true;
1214                onMove(e);
1215            });
1216
1217            $(window).on('mousemove touchmove', function (e) {
1218                if (isDragging) {
1219                    onMove(e);
1220                }
1221            }).on('mouseup touchend touchcancel', function () {
1222                isDragging = false;
1223            });
1224        });
1225    }
1226
1227    // 10. Pinned Vertical Feature Showcase Engine (Apple/Stripe Style)
1228    function initPinnedVerticalShowcase($scope) {
1229        const $wraps = $scope.filter('.bk-pinned-vertical-wrap').add($scope.find('.bk-pinned-vertical-wrap'));
1230        if (!$wraps.length) return;
1231
1232        $wraps.each(function () {
1233            const $wrap = $(this);
1234            if ($wrap.data('bk-pv-inited') && !(window.elementorFrontend && window.elementorFrontend.isEditMode && window.elementorFrontend.isEditMode())) {
1235                return;
1236            }
1237            $wrap.data('bk-pv-inited', true);
1238
1239            const $cards = $wrap.find('.bk-pinned-step-card');
1240            const $slides = $wrap.find('.bk-pinned-preview-slide');
1241
1242            if (!$cards.length) return;
1243
1244            function setActive(index) {
1245                $cards.removeClass('is-active');
1246                $cards.filter('[data-step="' + index + '"]').addClass('is-active');
1247                $slides.removeClass('is-active');
1248                $slides.filter('[data-slide="' + index + '"]').addClass('is-active');
1249            }
1250
1251            // Click to manually activate card & slide
1252            $cards.each(function () {
1253                const $card = $(this);
1254                const stepIndex = parseInt($card.attr('data-step'), 10) || 0;
1255                $card.css('cursor', 'pointer').off('click.bkpv').on('click.bkpv', function () {
1256                    setActive(stepIndex);
1257                });
1258            });
1259
1260            // Initial active state
1261            setActive(0);
1262
1263            // Real-time continuous scroll position detector (bulletproof active card detection)
1264            function checkActiveCard() {
1265                const viewportMid = window.innerHeight * 0.5;
1266                let activeIdx = 0;
1267                let minDiff = Infinity;
1268
1269                $cards.each(function (i) {
1270                    const rect = this.getBoundingClientRect();
1271                    const cardMid = rect.top + (rect.height * 0.5);
1272                    const diff = Math.abs(cardMid - viewportMid);
1273                    if (rect.top < window.innerHeight * 0.8 && rect.bottom > window.innerHeight * 0.2) {
1274                        if (diff < minDiff) {
1275                            minDiff = diff;
1276                            activeIdx = i;
1277                        }
1278                    }
1279                });
1280
1281                setActive(activeIdx);
1282            }
1283
1284            $(window).off('scroll.bkpv resize.bkpv').on('scroll.bkpv resize.bkpv', checkActiveCard);
1285            checkActiveCard();
1286
1287            if (typeof ScrollTrigger !== 'undefined') {
1288                $cards.each(function () {
1289                    const card = this;
1290                    const stepIndex = parseInt($(card).attr('data-step'), 10) || 0;
1291
1292                    ScrollTrigger.create({
1293                        trigger: card,
1294                        start: 'top 60%',
1295                        end: 'bottom 40%',
1296                        onEnter: function () {
1297                            setActive(stepIndex);
1298                        },
1299                        onEnterBack: function () {
1300                            setActive(stepIndex);
1301                        }
1302                    });
1303                });
1304            }
1305        });
1306    }
1307
1308    // 11. Exploded Product Showcase Engine (Smooth Sequential Unboxing)
1309    function initExplodedShowcase($scope) {
1310        const $wraps = $scope.filter('.bk-exploded-showcase-wrap').add($scope.find('.bk-exploded-showcase-wrap'));
1311        if (!$wraps.length) return;
1312
1313        $wraps.each(function () {
1314            const $wrap = $(this);
1315            if ($wrap.data('bk-exploded-inited') && !(window.elementorFrontend && window.elementorFrontend.isEditMode && window.elementorFrontend.isEditMode())) {
1316                return;
1317            }
1318            $wrap.data('bk-exploded-inited', true);
1319
1320            // Viewport entrance detector
1321            function checkInView() {
1322                const rect = $wrap[0].getBoundingClientRect();
1323                const windowH = window.innerHeight;
1324                if (rect.top <= windowH * 0.8 && rect.bottom >= windowH * 0.1) {
1325                    $wrap.addClass('is-in-view');
1326                } else if (rect.top > windowH * 0.95 || rect.bottom < 0) {
1327                    $wrap.removeClass('is-in-view');
1328                }
1329            }
1330
1331            $(window).off('scroll.bkexp resize.bkexp').on('scroll.bkexp resize.bkexp', checkInView);
1332            checkInView();
1333
1334            if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
1335                ScrollTrigger.create({
1336                    trigger: $wrap[0],
1337                    start: 'top 75%',
1338                    onEnter: function () {
1339                        $wrap.addClass('is-in-view');
1340                    },
1341                    onLeaveBack: function () {
1342                        $wrap.removeClass('is-in-view');
1343                    }
1344                });
1345            }
1346        });
1347    }
1348
1349    // 13. Glowing SVG Path Timeline Engine
1350    function initGlowingTimeline($scope) {
1351        const $wraps = $scope.filter('.bk-glowing-timeline-wrap').add($scope.find('.bk-glowing-timeline-wrap'));
1352        if (!$wraps.length) return;
1353
1354        $wraps.each(function () {
1355            const $wrap = $(this);
1356            if ($wrap.data('bk-timeline-inited') && !(window.elementorFrontend && window.elementorFrontend.isEditMode && window.elementorFrontend.isEditMode())) {
1357                return;
1358            }
1359            $wrap.data('bk-timeline-inited', true);
1360
1361            const $beam = $wrap.find('.bk-timeline-glow-beam');
1362            const $items = $wrap.find('.bk-timeline-item');
1363
1364            function updateTimeline() {
1365                const wrapRect = $wrap[0].getBoundingClientRect();
1366                const windowH = window.innerHeight;
1367
1368                // Beam height percentage based on scroll progress through the timeline
1369                const totalH = wrapRect.height;
1370                const progressPx = (windowH * 0.7) - wrapRect.top;
1371                const pct = Math.max(0, Math.min(100, (progressPx / totalH) * 100));
1372                if ($beam.length) {
1373                    $beam.css('height', pct + '%');
1374                }
1375
1376                // Check each step item
1377                $items.each(function () {
1378                    const itemRect = this.getBoundingClientRect();
1379                    const $item = $(this);
1380                    const $node = $item.find('.bk-timeline-node');
1381                    const $card = $item.find('.bk-timeline-card');
1382
1383                    if (itemRect.top <= windowH * 0.75) {
1384                        $node.addClass('is-active');
1385                        $card.addClass('is-visible');
1386                    } else {
1387                        $node.removeClass('is-active');
1388                        $card.removeClass('is-visible');
1389                    }
1390                });
1391            }
1392
1393            $(window).off('scroll.bktimeline resize.bktimeline').on('scroll.bktimeline resize.bktimeline', updateTimeline);
1394            updateTimeline();
1395
1396            if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') {
1397                ScrollTrigger.create({
1398                    trigger: $wrap[0],
1399                    start: 'top bottom',
1400                    end: 'bottom top',
1401                    onUpdate: updateTimeline
1402                });
1403            }
1404        });
1405    }
1406
1407    function hexToRgba(colorStr, alpha) {
1408        if (!colorStr) return 'rgba(59, 130, 246, ' + alpha + ')';
1409        colorStr = colorStr.trim();
1410
1411        // If it's rgb or rgba
1412        const rgbMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
1413        if (rgbMatch) {
1414            return 'rgba(' + rgbMatch[1] + ', ' + rgbMatch[2] + ', ' + rgbMatch[3] + ', ' + alpha + ')';
1415        }
1416
1417        // If it's hex
1418        if (colorStr.startsWith('#')) {
1419            let hex = colorStr.slice(1);
1420            if (hex.length === 3) {
1421                hex = hex.split('').map(function (c) { return c + c; }).join('');
1422            }
1423            if (hex.length >= 6) {
1424                const r = parseInt(hex.substring(0, 2), 16);
1425                const g = parseInt(hex.substring(2, 4), 16);
1426                const b = parseInt(hex.substring(4, 6), 16);
1427                return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
1428            }
1429        }
1430
1431        return colorStr;
1432    }
1433
1434    // 13.5. GSAP Brand Showcase Engine (Marquee, 3D Carousel, Bento Tilt)
1435    function initBrandShowcase($scope) {
1436        if (typeof gsap === 'undefined') return;
1437
1438        const $wraps = $scope.find('.bk-gsap-brands-wrap');
1439        if (!$wraps.length) return;
1440
1441        $wraps.each(function () {
1442            const $wrap = $(this);
1443            if ($wrap.data('bk-brands-inited')) return;
1444            $wrap.data('bk-brands-inited', true);
1445
1446            const mode = $wrap.data('mode') || 'marquee';
1447
1448            // --- MODE 1: MARQUEE ---
1449            if (mode === 'marquee') {
1450                const $container = $wrap.find('.bk-brand-marquee-container');
1451                const speed = parseFloat($container.data('speed')) || 30;
1452                const pauseOnHover = $container.data('pause') === 'yes';
1453
1454                $wrap.find('.bk-brand-marquee-row').each(function () {
1455                    const $row = $(this);
1456                    const $track = $row.find('.bk-brand-marquee-track');
1457                    if (!$track.length) return;
1458
1459                    const direction = $row.data('direction') || 'normal';
1460                    const track = $track[0];
1461
1462                    // The track is anchored to one edge of the row (right in RTL, left in LTR) and the
1463                    // duplicate group overflows on the other side. The loop must stay between 0 and
1464                    // ±50% toward that overflow side, otherwise the row slides into empty space.
1465                    gsap.set(track, { xPercent: 0 });
1466                    const rowRect = $row[0].getBoundingClientRect();
1467                    const trackRect = track.getBoundingClientRect();
1468                    const anchoredRight = Math.abs(trackRect.right - rowRect.right) < Math.abs(trackRect.left - rowRect.left);
1469                    const offset = anchoredRight ? 50 : -50;
1470                    const isReverse = direction === 'reverse';
1471
1472                    const tween = gsap.fromTo(track, {
1473                        xPercent: isReverse ? offset : 0
1474                    }, {
1475                        xPercent: isReverse ? 0 : offset,
1476                        ease: 'none',
1477                        duration: speed,
1478                        repeat: -1
1479                    });
1480
1481                    if (pauseOnHover) {
1482                        $row.on('mouseenter', function () {
1483                            tween.pause();
1484                        }).on('mouseleave', function () {
1485                            tween.play();
1486                        });
1487                    }
1488                });
1489            }
1490
1491            // --- MODE 2: CAROUSEL ---
1492            else if (mode === 'carousel') {
1493                const $viewport = $wrap.find('.bk-brand-carousel-viewport');
1494                const $track = $viewport.find('.bk-brand-carousel-track');
1495                const $slides = $track.find('.bk-brand-carousel-slide');
1496                if (!$slides.length) return;
1497
1498                const has3D = $viewport.hasClass('has-3d-depth');
1499                const autoplay = $viewport.data('autoplay') === 'yes';
1500                const autoplaySpeed = parseInt($viewport.data('autoplay-speed'), 10) || 4000;
1501                const $prevBtn = $wrap.find('.bk-brand-nav-prev');
1502                const $nextBtn = $wrap.find('.bk-brand-nav-next');
1503                const $progressBar = $wrap.find('.bk-brand-progress-bar');
1504
1505                let currentIndex = 0;
1506                let autoPlayTimer = null;
1507                let isHovered = false;
1508
1509                function getSlideStep() {
1510                    const firstSlide = $slides.eq(0);
1511                    const slideW = firstSlide.outerWidth(true);
1512                    return slideW || 240;
1513                }
1514
1515                function getMaxIndex() {
1516                    const viewportW = $viewport.width();
1517                    const totalW = $track[0].scrollWidth;
1518                    const step = getSlideStep();
1519                    const maxScroll = Math.max(0, totalW - viewportW);
1520                    return Math.ceil(maxScroll / step);
1521                }
1522
1523                function updateProgress() {
1524                    const maxIdx = getMaxIndex();
1525                    if ($progressBar.length && maxIdx > 0) {
1526                        const pct = Math.min(100, Math.max(10, ((currentIndex + 1) / (maxIdx + 1)) * 100));
1527                        gsap.to($progressBar[0], { width: pct + '%', duration: 0.3, ease: 'power1.out' });
1528                    }
1529
1530                    if (has3D) {
1531                        $slides.each(function (idx) {
1532                            const diff = Math.abs(idx - currentIndex);
1533                            const scale = diff === 0 ? 1 : Math.max(0.88, 1 - (diff * 0.05));
1534                            const opacity = diff > 4 ? 0.4 : Math.max(0.65, 1 - (diff * 0.1));
1535                            gsap.to(this, {
1536                                scale: scale,
1537                                opacity: opacity,
1538                                duration: 0.4,
1539                                ease: 'power2.out'
1540                            });
1541                        });
1542                    }
1543                }
1544
1545                function goToIndex(index, animate) {
1546                    const maxIdx = getMaxIndex();
1547                    currentIndex = Math.max(0, Math.min(index, maxIdx));
1548
1549                    const step = getSlideStep();
1550                    const targetX = (isRTL ? 1 : -1) * (currentIndex * step);
1551
1552                    if (animate !== false) {
1553                        gsap.to($track[0], {
1554                            x: targetX,
1555                            duration: 0.55,
1556                            ease: 'power2.out',
1557                            onUpdate: updateProgress
1558                        });
1559                    } else {
1560                        gsap.set($track[0], { x: targetX });
1561                        updateProgress();
1562                    }
1563                }
1564
1565                // Nav button bindings
1566                $nextBtn.on('click', function (e) {
1567                    e.preventDefault();
1568                    const maxIdx = getMaxIndex();
1569                    if (currentIndex >= maxIdx) {
1570                        goToIndex(0);
1571                    } else {
1572                        goToIndex(currentIndex + 1);
1573                    }
1574                    restartAutoplay();
1575                });
1576
1577                $prevBtn.on('click', function (e) {
1578                    e.preventDefault();
1579                    const maxIdx = getMaxIndex();
1580                    if (currentIndex <= 0) {
1581                        goToIndex(maxIdx);
1582                    } else {
1583                        goToIndex(currentIndex - 1);
1584                    }
1585                    restartAutoplay();
1586                });
1587
1588                // Draggable integration
1589                if (typeof Draggable !== 'undefined') {
1590                    Draggable.create($track[0], {
1591                        type: 'x',
1592                        edgeResistance: 0.85,
1593                        inertia: true,
1594                        cursor: 'grab',
1595                        onPress: function () {
1596                            stopAutoplay();
1597                            $viewport.addClass('is-dragging');
1598                        },
1599                        onRelease: function () {
1600                            $viewport.removeClass('is-dragging');
1601                            restartAutoplay();
1602                        },
1603                        onDrag: function () {
1604                            const step = getSlideStep();
1605                            const currentX = (isRTL ? 1 : -1) * this.x;
1606                            currentIndex = Math.max(0, Math.min(Math.round(currentX / step), getMaxIndex()));
1607                            updateProgress();
1608                        },
1609                        onThrowUpdate: function () {
1610                            const step = getSlideStep();
1611                            const currentX = (isRTL ? 1 : -1) * this.x;
1612                            currentIndex = Math.max(0, Math.min(Math.round(currentX / step), getMaxIndex()));
1613                            updateProgress();
1614                        }
1615                    });
1616                }
1617
1618                // Autoplay
1619                function startAutoplay() {
1620                    if (!autoplay || autoPlayTimer || isHovered) return;
1621                    autoPlayTimer = setInterval(function () {
1622                        if (isHovered) return;
1623                        const maxIdx = getMaxIndex();
1624                        if (currentIndex >= maxIdx) {
1625                            goToIndex(0);
1626                        } else {
1627                            goToIndex(currentIndex + 1);
1628                        }
1629                    }, autoplaySpeed);
1630                }
1631
1632                function stopAutoplay() {
1633                    if (autoPlayTimer) {
1634                        clearInterval(autoPlayTimer);
1635                        autoPlayTimer = null;
1636                    }
1637                }
1638
1639                function restartAutoplay() {
1640                    stopAutoplay();
1641                    startAutoplay();
1642                }
1643
1644                $viewport.on('mouseenter', function () {
1645                    isHovered = true;
1646                    stopAutoplay();
1647                }).on('mouseleave', function () {
1648                    isHovered = false;
1649                    startAutoplay();
1650                });
1651
1652                goToIndex(0, false);
1653                startAutoplay();
1654
1655                $(window).on('resize.bkbrands', function () {
1656                    goToIndex(currentIndex, false);
1657                });
1658            }
1659
1660            // --- MODE 3: GRID / BENTO TILT & SPOTLIGHT ---
1661            else if (mode === 'grid') {
1662                const $grid = $wrap.find('.bk-brand-grid');
1663                const $cards = $grid.find('.bk-brand-card');
1664
1665                // Entrance Stagger with ScrollTrigger
1666                if ($grid.hasClass('has-stagger') && typeof ScrollTrigger !== 'undefined') {
1667                    gsap.to($cards.toArray(), {
1668                        scrollTrigger: {
1669                            trigger: $grid[0],
1670                            start: 'top 85%',
1671                            toggleActions: 'play none none none'
1672                        },
1673                        opacity: 1,
1674                        y: 0,
1675                        duration: 0.55,
1676                        stagger: 0.07,
1677                        ease: 'power2.out'
1678                    });
1679                }
1680
1681                // 3D Tilt & Mouse Spotlight
1682                const hasTilt = $grid.hasClass('has-tilt');
1683                const hasSpotlight = $grid.hasClass('has-spotlight');
1684
1685                if (hasTilt || hasSpotlight) {
1686                    $cards.each(function () {
1687                        const card = this;
1688                        const $card = $(this);
1689                        const inner = card.querySelector('.bk-brand-card-inner');
1690
1691                        let xTo = null;
1692                        let yTo = null;
1693
1694                        if (hasTilt && inner) {
1695                            xTo = gsap.quickTo(inner, 'rotationY', { duration: 0.35, ease: 'power2.out' });
1696                            yTo = gsap.quickTo(inner, 'rotationX', { duration: 0.35, ease: 'power2.out' });
1697                        }
1698
1699                        $card.on('mousemove', function (e) {
1700                            const rect = card.getBoundingClientRect();
1701                            const x = e.clientX - rect.left;
1702                            const y = e.clientY - rect.top;
1703
1704                            if (hasSpotlight) {
1705                                card.style.setProperty('--mouse-x', x + 'px');
1706                                card.style.setProperty('--mouse-y', y + 'px');
1707                            }
1708
1709                            if (hasTilt && xTo && yTo) {
1710                                const centerX = rect.width / 2;
1711                                const centerY = rect.height / 2;
1712                                const rotX = ((y - centerY) / centerY) * -10;
1713                                const rotY = ((x - centerX) / centerX) * 10;
1714                                yTo(rotX);
1715                                xTo(rotY);
1716                            }
1717                        }).on('mouseleave', function () {
1718                            if (hasTilt && xTo && yTo) {
1719                                xTo(0);
1720                                yTo(0);
1721                            }
1722                        });
1723                    });
1724                }
1725            }
1726        });
1727    }
1728
1729    // 14. Magnetic Custom Cursor
1730    function initCustomCursor() {
1731        if (typeof gsap === 'undefined') return;
1732        const vars = window.bk_gsap_vars || {};
1733        if (vars.custom_cursor !== '1' && vars.custom_cursor !== true) return;
1734        if (window.matchMedia('(hover: none), (pointer: coarse)').matches) return;
1735
1736        let $follower = $('.bk-cursor-follower');
1737        let $dot = $('.bk-cursor-dot');
1738
1739        if (!$follower.length) {
1740            $('body').append('<div class="bk-cursor-follower"><span class="bk-cursor-label"></span></div><div class="bk-cursor-dot"></div>');
1741            $follower = $('.bk-cursor-follower');
1742            $dot = $('.bk-cursor-dot');
1743        }
1744
1745        const dotColor = vars.custom_cursor_color || '#3b82f6';
1746        const outlineColor = vars.custom_cursor_outline_color || 'rgba(59, 130, 246, 0.45)';
1747        const hoverBg = hexToRgba(outlineColor, 0.14);
1748
1749        document.documentElement.style.setProperty('--bk-cursor-color', dotColor);
1750        document.documentElement.style.setProperty('--bk-cursor-outline', outlineColor);
1751        document.documentElement.style.setProperty('--bk-cursor-hover-bg', hoverBg);
1752
1753        $dot.css('background-color', dotColor);
1754        $follower.css('border-color', outlineColor);
1755
1756        const setFollowerX = gsap.quickTo($follower[0], 'x', { duration: 0.35, ease: 'power3.out' });
1757        const setFollowerY = gsap.quickTo($follower[0], 'y', { duration: 0.35, ease: 'power3.out' });
1758        const setDotX = gsap.quickTo($dot[0], 'x', { duration: 0.1, ease: 'power2.out' });
1759        const setDotY = gsap.quickTo($dot[0], 'y', { duration: 0.1, ease: 'power2.out' });
1760
1761        $(window).on('mousemove', function (e) {
1762            setFollowerX(e.clientX);
1763            setFollowerY(e.clientY);
1764            setDotX(e.clientX);
1765            setDotY(e.clientY);
1766        });
1767
1768        // Hover scale & custom labels
1769        $(document).on('mouseenter', 'a, button, .bk-magnetic-btn, .bk-slider-btn, .bk-gsap-add-btn', function () {
1770            $follower.addClass('is-hover');
1771        }).on('mouseleave', 'a, button, .bk-magnetic-btn, .bk-slider-btn, .bk-gsap-add-btn', function () {
1772            $follower.removeClass('is-hover has-label');
1773            $follower.find('.bk-cursor-label').text('');
1774        });
1775
1776        $(document).on('mouseenter', '.bk-img-slide, .bk-gsap-card-media', function () {
1777            $follower.addClass('has-label');
1778            $follower.find('.bk-cursor-label').text('مشاهده');
1779        }).on('mouseleave', '.bk-img-slide, .bk-gsap-card-media', function () {
1780            $follower.removeClass('has-label');
1781            $follower.find('.bk-cursor-label').text('');
1782        });
1783
1784        $(document).on('mouseenter', '.bk-before-after-wrap, .bk-gsap-carousel-viewport', function () {
1785            $follower.addClass('has-label');
1786            $follower.find('.bk-cursor-label').text('بکشید');
1787        }).on('mouseleave', '.bk-before-after-wrap, .bk-gsap-carousel-viewport', function () {
1788            $follower.removeClass('has-label');
1789            $follower.find('.bk-cursor-label').text('');
1790        });
1791    }
1792
1793    // 16. Magnetic Buttons
1794    function initMagneticButtons() {
1795        if (typeof gsap === 'undefined') return;
1796        const vars = window.bk_gsap_vars || {};
1797        if (vars.magnetic_buttons !== '1' && vars.magnetic_buttons !== true) return;
1798        if (window.matchMedia('(hover: none)').matches) return;
1799
1800        $(document).on('mousemove', '.bk-magnetic-btn, .bk-slide-btn, .bk-slider-btn, .bk-gsap-nav-btn', function (e) {
1801            const btn = this;
1802            const rect = btn.getBoundingClientRect();
1803            const x = e.clientX - rect.left - rect.width / 2;
1804            const y = e.clientY - rect.top - rect.height / 2;
1805
1806            gsap.to(btn, {
1807                x: x * 0.35,
1808                y: y * 0.35,
1809                duration: 0.3,
1810                ease: 'power2.out',
1811                overwrite: 'auto'
1812            });
1813        }).on('mouseleave', '.bk-magnetic-btn, .bk-slide-btn, .bk-slider-btn, .bk-gsap-nav-btn', function () {
1814            gsap.to(this, {
1815                x: 0,
1816                y: 0,
1817                duration: 0.6,
1818                ease: 'elastic.out(1, 0.3)',
1819                overwrite: 'auto'
1820            });
1821        });
1822    }
1823
1824    // 17. Top Scroll Progress Bar
1825    function initScrollProgressBar() {
1826        const vars = window.bk_gsap_vars || {};
1827        if (vars.scroll_progress !== '1' && vars.scroll_progress !== true) {
1828            $('.bk-scroll-progress-bar').remove();
1829            return;
1830        }
1831
1832        let $bar = $('.bk-scroll-progress-bar');
1833        if (!$bar.length) {
1834            $('body').append('<div class="bk-scroll-progress-bar"></div>');
1835            $bar = $('.bk-scroll-progress-bar');
1836        }
1837
1838        if (vars.scroll_progress_color) {
1839            $bar.css('background', vars.scroll_progress_color);
1840        }
1841
1842        function updateProgress() {
1843            const docHeight = $(document).height() - $(window).height();
1844            if (docHeight <= 0) return;
1845            const progress = ($(window).scrollTop() / docHeight) * 100;
1846            $bar.css('width', Math.min(100, Math.max(0, progress)) + '%');
1847        }
1848
1849        $(window).on('scroll resize', updateProgress);
1850        updateProgress();
1851    }
1852
1853    // 17.5. Mobile Category Tabs Controller
1854    function initMobileCategories() {
1855        $(document).on('click', '.bakala_categories_mobile .menu-header .menu-item', function (e) {
1856            e.preventDefault();
1857            const $this = $(this);
1858            if ($this.hasClass('active')) return;
1859
1860            const targetCategory = $this.attr('data-category') || $this.data('category');
1861            if (!targetCategory) return;
1862
1863            const $container = $this.closest('.bakala_categories_mobile');
1864            $container.find('.menu-header .menu-item').removeClass('active');
1865            $this.addClass('active');
1866
1867            const $allCategories = $container.find('.menu-content .menu-category');
1868            const $target = $container.find('#' + targetCategory);
1869
1870            $allCategories.hide().css('display', 'none');
1871            if ($target.length) {
1872                $target.css({ display: 'flex' }).show();
1873                if (typeof gsap !== 'undefined') {
1874                    gsap.killTweensOf($target[0]);
1875                    gsap.fromTo($target[0],
1876                        { opacity: 0, y: 10 },
1877                        { opacity: 1, y: 0, duration: 0.22, ease: 'power2.out' }
1878                    );
1879                }
1880            }
1881
1882            const menuContent = $container.find('.menu-content')[0];
1883            if (menuContent) {
1884                menuContent.scrollTop = 0;
1885            }
1886
1887            const menuHeader = $container.find('.menu-header')[0];
1888            if (menuHeader) {
1889                const itemTop = $this.position().top + menuHeader.scrollTop;
1890                const targetScroll = itemTop - (menuHeader.clientHeight / 2) + ($this.outerHeight() / 2);
1891                $(menuHeader).stop().animate({ scrollTop: Math.max(0, targetScroll) }, 200);
1892            }
1893        });
1894    }
1895
1896    // 18. Initialization & Elementor Hooks
1897    $(document).ready(function () {
1898        initMobileCategories();
1899
1900        if (!initGSAPCore()) return;
1901
1902        initSmoothScroll();
1903        initCustomCursor();
1904        initMagneticButtons();
1905        initScrollProgressBar();
1906
1907        // Run for initial page elements
1908        initProductCarousel($(document));
1909        initMarquee($(document));
1910        initParallaxBanner($(document));
1911        initImageSlider($(document));
1912        initPeekImageSlider($(document));
1913        initKineticText($(document));
1914        initStackingCards($(document));
1915        initBeforeAfter($(document));
1916        initPinnedVerticalShowcase($(document));
1917        initExplodedShowcase($(document));
1918        initGlowingTimeline($(document));
1919        initBrandShowcase($(document));
1920
1921        // Hook into Elementor Frontend
1922        function registerElementorHooks() {
1923            if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) {
1924                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_gsap_product_carousel.default', function ($scope) { initProductCarousel($scope); });
1925                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_infinite_marquee.default', function ($scope) { initMarquee($scope); });
1926                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_parallax_banner.default', function ($scope) { initParallaxBanner($scope); });
1927                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_image_slider.default', function ($scope) { initImageSlider($scope); });
1928                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_peek_image_slider.default', function ($scope) { initPeekImageSlider($scope); });
1929                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_kinetic_text.default', function ($scope) { initKineticText($scope); });
1930                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_stacking_cards.default', function ($scope) { initStackingCards($scope); });
1931                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_before_after_slider.default', function ($scope) { initBeforeAfter($scope); });
1932                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_pinned_vertical_showcase.default', function ($scope) { initPinnedVerticalShowcase($scope); });
1933                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_exploded_showcase.default', function ($scope) { initExplodedShowcase($scope); });
1934                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_glowing_timeline.default', function ($scope) { initGlowingTimeline($scope); });
1935                elementorFrontend.hooks.addAction('frontend/element_ready/bakala_gsap_brands.default', function ($scope) { initBrandShowcase($scope); });
1936            }
1937        }
1938
1939        if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) {
1940            registerElementorHooks();
1941        } else {
1942            $(window).on('elementor/frontend/init', registerElementorHooks);
1943        }
1944
1945        // 19. Global ScrollTrigger Refresh Handler
1946        function refreshScrollTriggers() {
1947            if (typeof ScrollTrigger !== 'undefined') {
1948                ScrollTrigger.refresh();
1949            }
1950        }
1951
1952        $(window).on('load', refreshScrollTriggers);
1953
1954        if (document.fonts && document.fonts.ready) {
1955            document.fonts.ready.then(refreshScrollTriggers);
1956        }
1957
1958        setTimeout(refreshScrollTriggers, 400);
1959        setTimeout(refreshScrollTriggers, 1200);
1960        setTimeout(refreshScrollTriggers, 2500);
1961    });
1962
1963    // Expose helpers globally
1964    window.bkGSAP.initCore = initGSAPCore;
1965    window.bkGSAP.initProductCarousel = initProductCarousel;
1966    window.bkGSAP.initMarquee = initMarquee;
1967    window.bkGSAP.initParallaxBanner = initParallaxBanner;
1968    window.bkGSAP.initImageSlider = initImageSlider;
1969    window.bkGSAP.initPeekImageSlider = initPeekImageSlider;
1970    window.bkGSAP.initKineticText = initKineticText;
1971    window.bkGSAP.initStackingCards = initStackingCards;
1972    window.bkGSAP.initBeforeAfter = initBeforeAfter;
1973    window.bkGSAP.initPinnedVerticalShowcase = initPinnedVerticalShowcase;
1974    window.bkGSAP.initExplodedShowcase = initExplodedShowcase;
1975    window.bkGSAP.initGlowingTimeline = initGlowingTimeline;
1976    window.bkGSAP.initBrandShowcase = initBrandShowcase;
1977    window.bkGSAP.initCustomCursor = initCustomCursor;
1978    window.bkGSAP.initMagneticButtons = initMagneticButtons;
1979    window.bkGSAP.initScrollProgressBar = initScrollProgressBar;
1980
1981})(jQuery);

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.