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.