1//*************** Clients logos marquee animation *************** 2window.addEventListener("load", () => { 3 4 document.querySelectorAll(".client-logos").forEach((list, index) => { 5 6 const maxScroll = list.scrollWidth - list.parentElement.clientWidth; 7 const offset = 20; 8 9 const startX = index % 2 === 0 ? offset : -(maxScroll - offset); 10 const endX = index % 2 === 0 ? -(maxScroll - offset) : offset; 11 12 gsap.set(list, { 13 x: index === 0 ? 20 : -maxScroll - 20 14 }); 15 16 gsap.to(list, { 17 x: index === 0 ? -maxScroll - 20 : 20, 18 duration: 50, 19 ease: "ease", 20 repeat: -1, 21 yoyo: true 22 }); 23 24 }); 25 ScrollTrigger.refresh(); 26}); 27//*************** Clients logos marquee animation *************** 28 29//*************** All over the page UnboundIA animation *************** 30// (function ($) { 31// document.addEventListener("DOMContentLoaded", () => { 32 33// gsap.registerPlugin(SplitText, ScrollTrigger); 34// function fadeInAnime() { 35 36// $(".ub_animefade").each(function () { 37 38// const section = $(this); 39 40// const ub_animefadeInLeft = section.find(".ub_animefadeInLeft"); 41// const ub_animefadeInLeftSlow = section.find(".ub_animefadeInLeftSlow"); 42// const ub_animefadeInRight = section.find(".ub_animefadeInRight"); 43// const ub_animefadeInRightSlow = section.find(".ub_animefadeInRightSlow"); 44// const ub_animefadeInUp = section.find(".ub_animefadeInUp"); 45// const ub_animefadeInUpImage = section.find(".ub_animefadeInUpImage"); 46// const ub_animefadeInImageGroup = section.find(".ub_animefadeInImage"); 47// const ub_animefadeInUpImageGroup = section.find(".ub_animefadeInUpImageGroup"); 48// const ub_animefadeIn = section.find(".ub_animefadeIn"); 49// const ub_animefadeInSlowup = section.find(".ub_animefadeInSlowup"); 50// const ub_line = section.find(".ub_line"); 51// const ub_animeLeftToRight = section.find(".ub_animeLeftToRight"); 52// const splitElement = section.find(".ub_split_text"); 53// const ub_rewrite_text = section.find(".ub_rewrite_text") 54// const ub_revel_text = section.find(".ub_revel_text") 55// const ub_revel_textRTL = section.find(".ub_revel_textRTL") 56 57// let splitText; 58 59// if (splitElement.length) { 60// splitText = SplitText.create(splitElement[0], { 61// type: "words" 62// }); 63// } 64 65// if (ub_animefadeInLeft.length) { 66// gsap.fromTo(ub_animefadeInLeft, { 67// x: -200, 68// opacity: 0, 69// }, 70// { 71// x:0, 72// opacity: 1, 73// stagger: 0.06, 74// duration:1, 75// ease: "power2.out", 76// scrollTrigger: { 77// trigger: section[0], 78// start: "top 90%", 79// toggleActions: "play play play reverse", 80// markers: false 81// } 82// }); 83// // gsap.from(ub_animefadeInLeft, { 84// // x: -200, 85// // opacity: 0, 86// // stagger: 0.06, 87// // duration:1, 88// // ease: "power2.out", 89// // scrollTrigger: { 90// // trigger: section[0], 91// // start: "top 90%", 92// // toggleActions: "play play play reverse", 93// // markers: false 94// // } 95// // }); 96// } 97 98// if (ub_animefadeInRight.length) { 99// gsap.fromTo(ub_animefadeInRight, { 100// x: 200, 101// opacity: 0, 102// },{ 103// x: 0, 104// opacity: 1, 105// stagger: 0.06, 106// duration: 1, 107// ease: "power2.out", 108// scrollTrigger: { 109// trigger: section[0], 110// start: "top 90%", 111// toggleActions: "play play play reverse", 112// markers: false 113// } 114// }); 115// } 116 117// if (ub_animefadeInLeftSlow.length) { 118// gsap.from(ub_animefadeInLeftSlow, { 119// x: -200, 120// opacity: 0, 121// duration: 2, 122// ease: "power2.out", 123// scrollTrigger: { 124// trigger: section[0], 125// start: "top 90%", 126// toggleActions: "play play play reverse", 127// markers: false 128// } 129// }); 130 131// gsap.utils.toArray(".delay-1").forEach((el) => { 132// gsap.set(el, { delay: 0.5 }); 133// }); 134// } 135 136// if (ub_animefadeInUp.length) { 137// gsap.fromTo(ub_animefadeInUp, { 138// y: 200, 139// opacity: 0, 140// },{ 141// y: 0, 142// opacity: 1, 143// stagger: 0.06, 144// duration: 1, 145// ease: "power2.out", 146// scrollTrigger: { 147// trigger: section[0], 148// start: "top center", 149// toggleActions: "play play play reverse", 150// markers: false 151// } 152// }); 153// } 154 155// /* 156// * Images that carry the class directly. 157// * FIX: this previously targeted ub_animefadeInUp, 158// * so the images never animated. 159// */ 160// if (ub_animefadeInUpImage.length) { 161// gsap.from(ub_animefadeInUpImage, { 162// y: 200, 163// opacity: 0, 164// stagger: 0.06, 165// duration: 0.5, 166// ease: "power2.out", 167// scrollTrigger: { 168// trigger: section[0], 169// start: "top center", 170// toggleActions: "play play play reverse", 171// markers: false 172// } 173// }); 174// } 175 176// if (ub_animefadeInUpImageGroup.length) { 177// ub_animefadeInUpImageGroup.each(function () { 178 179// const groupImages = $(this).find("img"); 180 181// if (!groupImages.length) { 182// return; 183// } 184 185// gsap.fromTo(groupImages, { 186// y: 200, 187// opacity: 0, 188// } 189// ,{ 190// y: 0, 191// opacity: 1, 192// stagger: 0.5, 193// duration: 0.5, 194// ease: "power2.out", 195// scrollTrigger: { 196// trigger: this, 197// start: "top 85%", 198// toggleActions: "play none none reverse", 199// markers: false 200// } 201// }); 202// }); 203// } 204// if (ub_animefadeInImageGroup.length) { 205// ub_animefadeInImageGroup.each(function () { 206 207// const groupImages = $(this).find("img"); 208 209// if (!groupImages.length) { 210// return; 211// } 212 213// gsap.from(groupImages, { 214// // y: 100,
215// opacity: 0, 216// stagger: 0.5, 217// duration: 0.3, 218// ease: "power2.out", 219// scrollTrigger: { 220// trigger: this, 221// start: "top 85%", 222// toggleActions: "play none none reverse", 223// markers: false 224// } 225// }); 226// }); 227// } 228 229// if (ub_animefadeInSlowup.length) { 230// gsap.from(ub_animefadeInSlowup, { 231// y: 200, 232// opacity: 0, 233// stagger: 0.06, 234// duration: 2, 235// ease: "power2.out", 236// scrollTrigger: { 237// trigger: section[0], 238// start: "top center", 239// toggleActions: "play play play reverse", 240// markers: false 241// } 242// }); 243// } 244 245// if (ub_animeLeftToRight.length) { 246// gsap.fromTo( 247// ub_animeLeftToRight, 248// { 249// width: "0%" 250// }, 251// { 252// width: "100%", 253// stagger: 1, 254// duration: 6, 255// ease: "power2.out", 256// scrollTrigger: { 257// trigger: section[0], 258// start: "top center", 259// toggleActions: "play play play reverse", 260// markers: false 261// } 262// } 263// ); 264// } 265 266// if (splitElement.length) { 267// gsap.fromTo(splitElement, 268// {opacity:0}, 269// {opacity:1, 270// scrollTrigger: { 271// trigger: section[0], 272// start: "top 85%", 273// toggleActions: "play play play reverse" 274// } 275// } 276// ) 277// gsap.fromTo( 278// splitText.words, 279// { 280// opacity: 0, 281// y: 24, 282// filter: "blur(6px)" 283// }, 284// { 285// opacity: 1, 286// y: 0, 287// filter: "blur(0px)", 288// stagger: 0.10, 289// duration: 0.5, 290// ease: "power3.out", 291// delay: 0.15, 292// scrollTrigger: { 293// trigger: splitElement[0], 294// start: "top 85%", 295// toggleActions: "play play play reverse" 296// } 297// } 298// ); 299// } 300 301// if (ub_line.length) { 302// gsap.from(ub_line, { 303// y: 30, 304// opacity: 0, 305// stagger: 0.2, 306// duration: 0.8, 307// ease: "power2.out", 308// scrollTrigger: { 309// trigger: ub_line[0], 310// start: "top 90%", 311// toggleActions: "play play play reverse", 312// markers:false 313// } 314// }); 315// } 316// if (ub_rewrite_text.length) { 317// const newText = ub_rewrite_text.data("text"); 318// gsap.to(ub_rewrite_text, { 319// text: newText, 320// duration: 2, 321// ease: "none", 322// scrollTrigger: { 323// trigger: ub_rewrite_text[0], 324// start: "top 90%", 325// toggleActions: "play play play reverse" 326// } 327// }); 328 329// } 330// if (ub_revel_text.length) { 331// const newText = ub_rewrite_text.data("text"); 332// gsap.from(ub_revel_text, { 333// clipPath: "inset(0 100% 0 0)", 334// duration: 1.2, 335// ease: "power3.inOut", 336// scrollTrigger: { 337// trigger: ub_revel_text[0], 338// start: "top 90%", 339// toggleActions: "play play play reverse" 340// } 341// }); 342 343// } 344// if (ub_revel_textRTL.length) { 345// // const newText = ub_rewrite_text.data("text"); 346// gsap.from(ub_revel_textRTL, { 347// clipPath: "inset(0px 0px 0px 100%)", 348// duration: 1.2, 349// ease: "power3.inOut", 350// scrollTrigger: { 351// trigger: ub_revel_textRTL[0], 352// start: "top 90%", 353// toggleActions: "play play play reverse" 354// } 355// }); 356 357// } 358// }); 359 360// if (document.body.classList.contains("single")) { 361// const ub_line = gsap.utils.toArray( 362// ".blog-container p, .blog-container li, .blog-container h2, .blog-container h3, .blog-container h4, .blog-container img, .blog-container table,.blog-container .auth-quote" 363// ); 364 365// if (ub_line.length) { 366// ub_line.forEach((element) => { 367// gsap.from(element, { 368// y: 30, 369// opacity: 0, 370// duration: 0.8, 371// ease: "power2.out", 372// scrollTrigger: { 373// trigger: element, 374// start: "top 90%", 375// toggleActions: "play none none reverse", 376// markers: false 377// } 378// }); 379// }); 380// } 381// } 382 383// } 384 385 386// fadeInAnime(); 387 388// }); 389// ScrollTrigger.refresh(); 390// })(jQuery); 391//*************** All over the page UnboundIA animation *************** 392 393 394//*************** About Us page - leaders section fadein next slide on scroll *************** 395document.addEventListener("DOMContentLoaded", () => { 396 397 const wrap = document.querySelector("#images"); 398 if (!wrap) return; 399 400 const items = gsap.utils.toArray("#images .leaders-img-container"); 401 if (!items.length) return; 402 403 const figures = items.map(it => it.querySelector("figure")); 404 const bgs = items.map(it => it.querySelector(".leaders-bg")); 405 406 /* 407 * Timing (seconds) 408 */ 409 const HOLD = 1; 410 const IN = 0.5; 411 const OUT = 0.5; 412 const OVERLAP = 0; 413 414 /* 415 * How far the image travels (%). 416 */ 417 const SHIFT = 20; 418 419 /* 420 * Image direction: 421 * even (0,2,4) => image from LEFT 422 * odd (1,3,5) => image from RIGHT 423 */ 424 const dirFor = i => (i % 2 === 0 ? -1 : 1); 425 426 /* 427 * BG reveals from the OPPOSITE side 428 */ 429 const bgHidden = i => 430 dirFor(i) < 0 431 ? "inset(0% 0% 0% 100%)" 432 : "inset(0% 100% 0% 0%)"; 433 434 const bgShown = "inset(0% 0% 0% 0%)"; 435 436 /* 437 * Initial hidden state 438 */
439 items.forEach((it, i) => { 440 gsap.set(it, { autoAlpha: 0 }); 441 442 gsap.set(figures[i], { 443 xPercent: dirFor(i) * SHIFT, 444 autoAlpha: 0 445 }); 446 447 gsap.set(bgs[i], { 448 clipPath: bgHidden(i), 449 webkitClipPath: bgHidden(i), 450 autoAlpha: 1 451 }); 452 }); 453 454 /* 455 * Create timeline paused initially. 456 */ 457 const tl = gsap.timeline({ 458 repeat: -1, 459 paused: true 460 }); 461 462 const total = items.length; 463 464 for (let i = 0; i < total; i++) { 465 466 // --- ENTER CARD --- 467 tl.add("enter" + i); 468 469 tl.set(items[i], { 470 autoAlpha: 1 471 }, "enter" + i); 472 473 // Image enters 474 tl.to(figures[i], { 475 xPercent: 0, 476 autoAlpha: 1, 477 duration: IN, 478 ease: "power3.out" 479 }, "enter" + i); 480 481 // Background reveals 482 tl.to(bgs[i], { 483 clipPath: bgShown, 484 webkitClipPath: bgShown, 485 duration: IN, 486 ease: "power3.out" 487 }, "enter" + i); 488 489 // --- HOLD --- 490 tl.to({}, { 491 duration: HOLD 492 }); 493 494 // --- EXIT --- 495 tl.to(items[i], { 496 autoAlpha: 0, 497 duration: OUT, 498 ease: "power2.inOut" 499 }); 500 501 // Reset current card 502 tl.set(figures[i], { 503 xPercent: dirFor(i) * SHIFT, 504 autoAlpha: 0 505 }); 506 507 tl.set(bgs[i], { 508 clipPath: bgHidden(i), 509 webkitClipPath: bgHidden(i) 510 }); 511 512 // Next card 513 tl.add( 514 "enter" + ((i + 1) % total), 515 "-=" + OVERLAP 516 ); 517 } 518 519 /* 520 * START ANIMATION WHEN #images 521 * REACHES CENTER OF VIEWPORT 522 */ 523 ScrollTrigger.create({ 524 trigger: wrap, 525 start: "top center", 526 527 onEnter: () => { 528 tl.play(); 529 }, 530 531 onEnterBack: () => { 532 tl.play(); 533 }, 534 535 onLeaveBack: () => { 536 tl.pause(); 537 } 538 }); 539 540}); 541//*************** About Us page - leaders section fadein next slide on scroll *************** 542 543//*************** Home Page - Proof in Numbers Section**************************** 544document.addEventListener("DOMContentLoaded", function () { 545 546 const section = document.querySelector(".home-feture-scrollTween-sec"); 547 const track = document.querySelector(".feture-scrollTween-row"); 548 const targetFigure = document.querySelector(".prf-in-num-col-5 figure"); 549 550 if (!track || !section || !targetFigure) { 551 return; 552 } 553 554 const mm = gsap.matchMedia(); 555 556 mm.add("(min-width: 769px)", () => { 557 558 const culturalScroll = gsap.to(section, { 559 x: () => { 560 const figureRect = targetFigure.getBoundingClientRect(); 561 const distance = figureRect.right - window.innerWidth; 562 return -Math.max(0, distance); 563 }, 564 565 ease: "none", 566 567 scrollTrigger: { 568 trigger: track, 569 pin: section, 570 start: "top top", 571 572 end: () => { 573 const figureRect = targetFigure.getBoundingClientRect(); 574 const distance = figureRect.right - window.innerWidth; 575 return `+=${Math.max(0, distance)}`; 576 }, 577 578 scrub: 2, 579 anticipatePin: 1, 580 markers: false, 581 invalidateOnRefresh: true 582 } 583 }); 584 585 return () => { 586 culturalScroll.kill(); 587 }; 588 }); 589 590 window.addEventListener("load", function () { 591 ScrollTrigger.refresh(); 592 }); 593 594}); 595//*************** Home Page - Proof in Numbers Section**************************** 596 597//*************** Case study single page - cards horizontal scroll Section****************** 598document.addEventListener("DOMContentLoaded", function () { 599 const sliderSection = document.querySelector(".case-study-text-slider"); 600 const singleCard = document.querySelector(".case-study-single-card"); 601 602 if (!sliderSection) { 603 return; 604 } 605 606 const sliderViewport = sliderSection.querySelector(".main-content-slider"); 607 const sliderWrapper = sliderSection.querySelector(".case-study-swiper"); 608 const slides = sliderSection.querySelectorAll(".case-study-slide"); 609 610 if (!sliderViewport || !sliderWrapper || !slides.length) { 611 return; 612 } 613 614 function getHeaderHeight() { 615 const header = 616 document.querySelector("header") || 617 document.querySelector(".site-header") || 618 document.querySelector(".header"); 619 if (!header) { 620 return 0; 621 } 622 return header.getBoundingClientRect().height; 623 } 624 625 function updateBlurSlides(progress) { 626 const totalSlides = slides.length; 627 let visibleSlides = 3; 628 629 if (window.innerWidth < 768) { 630 visibleSlides = 1; 631 } else if (window.innerWidth < 1024) { 632 visibleSlides = 2; 633 } 634 635 if (totalSlides <= visibleSlides) {
636 slides.forEach(function (slide) { 637 slide.classList.remove("is-blur-slide"); 638 slide.classList.add("is-visible-slide"); 639 }); 640 return; 641 } 642 643 const maxStartIndex = totalSlides - visibleSlides; 644 const currentIndex = Math.round(progress * maxStartIndex); 645 646 slides.forEach(function (slide, index) { 647 slide.classList.remove("is-visible-slide", "is-blur-slide"); 648 649 if (index >= currentIndex && index < currentIndex + visibleSlides) { 650 slide.classList.add("is-visible-slide"); 651 } else { 652 slide.classList.add("is-blur-slide"); 653 } 654 }); 655 } 656 657 function initCaseStudyHorizontalScroll() { 658 const oldTrigger = ScrollTrigger.getById("caseStudyHorizontalScroll"); 659 if (oldTrigger) { 660 oldTrigger.kill(); 661 } 662 663 gsap.set(sliderWrapper, { x: 0 }); 664 665 const viewportWidth = sliderViewport.getBoundingClientRect().width; 666 const wrapperWidth = sliderWrapper.scrollWidth; 667 const extraScroll = 60; 668 const actualScrollDistance = wrapperWidth - viewportWidth; 669 const scrollDistance = actualScrollDistance + extraScroll; 670 671 if (actualScrollDistance <= 0) { 672 updateBlurSlides(0); 673 return; 674 } 675 676 updateBlurSlides(0); 677 678 const screenWidth = window.innerWidth; 679 680 /* 681 * Above 1430px â pin the entire .case-study-single-card, 682 * centered vertically in the viewport, 683 * triggered at top (no header-height offset). 684 * 1024â1429px â pin .case-study-text-slider only. 685 * Below 1024px â pin the entire .case-study-single-card, 686 * centered vertically in the viewport, 687 * triggered at top (no header-height offset). 688 */ 689 690 const isMidRange = screenWidth >= 1024 && screenWidth <= 1429; 691 692 var triggerEl, startValue; 693 694 if (isMidRange) { 695 // Mid-range: slider section only 696 triggerEl = sliderSection; 697 startValue = "center center"; 698 } else { 699 // Large desktop AND below 1024: whole card block 700 triggerEl = singleCard ? singleCard : sliderSection; 701 702 /* 703 * Centred vertically, measured against the top of the 704 * viewport â no header-height compensation. 705 */ 706 startValue = "center center"; 707 } 708 709 gsap.to(sliderWrapper, { 710 x: -scrollDistance, 711 ease: "none", 712 713 scrollTrigger: { 714 id: "caseStudyHorizontalScroll", 715 trigger: triggerEl, 716 start: startValue, 717 end: "+=" + scrollDistance, 718 pin: true, 719 pinSpacing: true, 720 scrub: 1, 721 anticipatePin: 1, 722 invalidateOnRefresh: true, 723 markers:false, 724 onUpdate: function (self) { 725 var slideProgress = Math.min( 726 self.progress * (scrollDistance / actualScrollDistance), 727 1 728 ); 729 updateBlurSlides(slideProgress); 730 } 731 } 732 }); 733 734 ScrollTrigger.refresh(); 735 } 736 737 setTimeout(function () { 738 initCaseStudyHorizontalScroll(); 739 }, 100); 740 741 var resizeTimer; 742 window.addEventListener("resize", function () { 743 clearTimeout(resizeTimer); 744 resizeTimer = setTimeout(function () { 745 initCaseStudyHorizontalScroll(); 746 }, 300); 747 }); 748 749 ScrollTrigger.refresh(); 750}); 751//*************** Case study single page - cards horizontal scroll Section****************** 752 753//*************** Single - thinking list section -all over the page ****************** 754document.addEventListener('DOMContentLoaded', function () { 755 function initReveal() { 756 const thinking = document.querySelector('.thinking-list'); 757 758 if (!thinking) return; 759 760 const rows = thinking.querySelectorAll('.thinking-item'); 761 762 gsap.set(rows, { 763 opacity: 0, 764 y: -80 765 }); 766 767 gsap.to(rows, { 768 opacity: 1, 769 y: 0, 770 duration: 0.85, 771 ease: 'power3.out', 772 stagger: { 773 each: 0.13, 774 from: 'start' 775 }, 776 scrollTrigger: { 777 trigger: thinking, 778 start: 'top 88%', 779 once: true, 780 // markers: true 781 } 782 }); 783 } 784 785 initReveal(); 786 ScrollTrigger.refresh(); 787}); 788//*************** Single - thinking list section -all over the page ****************** 789 790//*************** Single - roundup post expert panel horizontally scroll ***************// 791document.addEventListener("DOMContentLoaded", function () { 792 793 if ( 794 typeof gsap === "undefined" || 795 typeof ScrollTrigger === "undefined" 796 ) { 797 console.warn("GSAP or ScrollTrigger is not available."); 798 return; 799 } 800 801 gsap.registerPlugin(ScrollTrigger); 802 803 const section = document.querySelector(".expert-panel"); 804 805 if (!section) { 806 return; 807 } 808 809 const list = section.querySelector(".expert-list"); 810 811 if (!list) { 812 return; 813 } 814 815 const cards = gsap.utils.toArray( 816 list.querySelectorAll(".scroll-section") 817 ); 818 819 if (!cards.length) { 820 return; 821 } 822 823 let expertTween = null; 824 825 function initExpertHorizontalScroll() { 826 827 /* 828 * Remove previous ScrollTrigger 829 */ 830 if (expertTween) { 831 expertTween.scrollTrigger?.kill(); 832 expertTween.kill(); 833 expertTween = null; 834 } 835 836 const oldTrigger = ScrollTrigger.getById( 837 "expertHorizontalScroll" 838 ); 839 840 if (oldTrigger) { 841 oldTrigger.kill(); 842 } 843 844 /* 845 * Reset transform 846 */ 847 gsap.set(list, { 848 x: 0 849 }); 850 851 /* 852 * Force layout calculation 853 */ 854 ScrollTrigger.refresh(); 855 856 /* 857 * ========================================== 858 * GET ACTUAL POSITIONS 859 * ========================================== 860 */ 861 862 const sectionRect = section.getBoundingClientRect(); 863 864 const firstCard = cards[0]; 865 const lastCard = cards[cards.length - 1]; 866 867 if (!firstCard || !lastCard) { 868 return; 869 } 870 871 const firstRect = firstCard.getBoundingClientRect();
872 const lastRect = lastCard.getBoundingClientRect(); 873 874 /* 875 * How much the list needs to move 876 * so the LAST card reaches the right side. 877 */ 878 const finalX = 879 sectionRect.right - lastRect.right; 880 881 /* 882 * Make sure the first card starts 883 * completely inside the viewport. 884 * 885 * If your existing CSS is already correct, 886 * this will normally be 0. 887 */ 888 let startX = 0; 889 890 if (firstRect.left < sectionRect.left) { 891 startX = sectionRect.left - firstRect.left; 892 } 893 894 /* 895 * ========================================== 896 * HORIZONTAL ANIMATION 897 * ========================================== 898 */ 899 900 expertTween = gsap.fromTo( 901 list, 902 903 { 904 x: startX 905 }, 906 907 { 908 x: finalX, 909 910 ease: "none", 911 912 scrollTrigger: { 913 914 id: "expertHorizontalScroll", 915 916 trigger: section, 917 918 start: "center center", 919 920 /* 921 * IMPORTANT: 922 * End exactly when the horizontal 923 * movement is finished. 924 */ 925 end: function () { 926 927 const distance = Math.abs( 928 finalX - startX 929 ); 930 931 return "+=" + distance; 932 }, 933 934 scrub: 0.8, 935 936 pin: true, 937 938 pinSpacing: true, 939 940 anticipatePin: 1, 941 942 invalidateOnRefresh: true, 943 944 markers: false 945 } 946 } 947 ); 948 } 949 950 /* 951 * ========================================== 952 * INITIALIZE 953 * ========================================== 954 */ 955 956 initExpertHorizontalScroll(); 957 958 /* 959 * ========================================== 960 * WAIT FOR IMAGES 961 * ========================================== 962 */ 963 964 window.addEventListener("load", function () { 965 966 setTimeout(function () { 967 968 initExpertHorizontalScroll(); 969 970 ScrollTrigger.refresh(); 971 972 }, 300); 973 974 }); 975 976 /* 977 * ========================================== 978 * RESIZE 979 * ========================================== 980 */ 981 982 let resizeTimer; 983 984 window.addEventListener("resize", function () { 985 986 clearTimeout(resizeTimer); 987 988 resizeTimer = setTimeout(function () { 989 990 initExpertHorizontalScroll(); 991 992 ScrollTrigger.refresh(); 993 994 }, 250); 995 996 }); 997 998}); 999//*************** Single - roundup post expert panel horizontally scroll ***************// 1000 1001 1002 1003// ####Soma#################################################################################################################################### 1004// ######################################################################################################################################## 1005// ######################################################################################################################################## 1006// ######################################################################################################################################## 1007 1008//*************** Home Page OurUsps Section **************************** 1009document.addEventListener("DOMContentLoaded", function () { 1010 // return; 1011 const section = document.querySelector(".our-usps-sec"); 1012 1013 if (!section) { 1014 return; 1015 } 1016 const track = section.closest(".our-usps-track") || section.parentElement; 1017 1018 if ( 1019 typeof gsap === "undefined" || 1020 typeof ScrollTrigger === "undefined" 1021 ) { 1022 return; 1023 } 1024 1025 gsap.registerPlugin(ScrollTrigger); 1026 1027 const rows = Array.from( 1028 section.querySelectorAll(".our-usps-container") 1029 ); 1030 1031 if (!rows.length) { 1032 return; 1033 } 1034 1035 const items = rows.map(function (row, index) { 1036 1037 return { 1038 row: row, 1039 image: row.querySelector(".our-usps-img-col"), 1040 content: row.querySelector(".text-container"), 1041 icon: row.querySelector(".our-usps-img-svg"), 1042 index: index 1043 }; 1044 1045 }).filter(function (item) { 1046 1047 return item.image && item.content; 1048 1049 }); 1050 1051 if (!items.length) { 1052 return; 1053 } 1054 1055 1056 /* 1057 |-------------------------------------------------------------------------- 1058 | INITIAL STATE 1059 |-------------------------------------------------------------------------- 1060 */ 1061
1062 items.forEach(function (item) { 1063 1064 gsap.set(item.row, { 1065 opacity: 1, 1066 zIndex: item.index + 1 1067 }); 1068 1069 // IMAGE starts from LEFT 1070 gsap.set(item.image, { 1071 x: -120, 1072 y: 0, 1073 opacity: 0 1074 }); 1075 1076 // CONTENT starts from RIGHT 1077 gsap.set(item.content, { 1078 x: 120, 1079 y: 0, 1080 opacity: 0 1081 }); 1082 1083 // ICON starts from BOTTOM 1084 if (item.icon) { 1085 gsap.set(item.icon, { 1086 x: 0, 1087 y: 120, 1088 opacity: 0, 1089 rotation: 0 1090 }); 1091 } 1092 1093 }); 1094 1095 1096 /* 1097 |-------------------------------------------------------------------------- 1098 | SHOW ITEM 1099 |-------------------------------------------------------------------------- 1100 */ 1101 1102 function showItem(timeline, item) { 1103 1104 // IMAGE â LEFT â CENTER 1105 timeline.to(item.image, { 1106 x: 0, 1107 y: 0, 1108 opacity: 1, 1109 duration: 1, 1110 ease: "power3.out" 1111 }); 1112 1113 // CONTENT â RIGHT â CENTER 1114 timeline.to(item.content, { 1115 x: 0, 1116 y: 0, 1117 opacity: 1, 1118 duration: 1, 1119 ease: "power3.out" 1120 }, "<0.15"); 1121 1122 // ICON â BOTTOM â CENTER 1123 if (item.icon) { 1124 1125 timeline.to(item.icon, { 1126 x: 0, 1127 y: 0, 1128 opacity: 1, 1129 rotation: 0, 1130 duration: 0.9, 1131 ease: "power3.out" 1132 }, "<0.15"); 1133 1134 } 1135 1136 } 1137 1138 1139 /* 1140 |-------------------------------------------------------------------------- 1141 | HIDE ITEM 1142 |-------------------------------------------------------------------------- 1143 */ 1144 1145 function hideItem(timeline, item) { 1146 1147 // IMAGE â CENTER â LEFT 1148 timeline.to(item.image, { 1149 x: -120, 1150 y: 0, 1151 opacity: 0, 1152 duration: 0.8, 1153 ease: "power2.inOut" 1154 }); 1155 1156 // CONTENT â CENTER â RIGHT 1157 timeline.to(item.content, { 1158 x: 120, 1159 y: 0, 1160 opacity: 0, 1161 duration: 0.8, 1162 ease: "power2.inOut" 1163 }, "<"); 1164 1165 // ICON â CENTER â BOTTOM 1166 if (item.icon) { 1167 1168 timeline.to(item.icon, { 1169 x: 0, 1170 y: 120, 1171 opacity: 0, 1172 rotation: 0, 1173 duration: 0.7, 1174 ease: "power2.inOut" 1175 }, "<"); 1176 1177 } 1178 1179 } 1180 1181 1182 /* 1183 |-------------------------------------------------------------------------- 1184 | FIRST ITEM â ALREADY VISIBLE 1185 |-------------------------------------------------------------------------- 1186 */ 1187 1188 const firstItem = items[0]; 1189 1190 gsap.set(firstItem.row, { 1191 opacity: 1, 1192 zIndex: 10 1193 }); 1194 1195 gsap.set(firstItem.image, { 1196 x: 0, 1197 y: 0, 1198 opacity: 1 1199 }); 1200 1201 gsap.set(firstItem.content, { 1202 x: 0, 1203 y: 0, 1204 opacity: 1 1205 }); 1206 1207 if (firstItem.icon) { 1208 1209 gsap.set(firstItem.icon, { 1210 x: 0, 1211 y: 0, 1212 opacity: 1, 1213 rotation: 0 1214 }); 1215 1216 } 1217 1218 1219 /* 1220 |-------------------------------------------------------------------------- 1221 | MAIN TIMELINE 1222 |-------------------------------------------------------------------------- 1223 */ 1224 1225 const tl = gsap.timeline({ 1226 1227 scrollTrigger: { 1228 trigger: track, 1229 start: "top top", 1230 end: "bottom bottom", 1231 // pin: true, 1232 scrub: 1, 1233 // pinSpacing: true, 1234 // anticipatePin: 1, 1235 invalidateOnRefresh: true, 1236 markers:false 1237 } 1238 1239 }); 1240 1241 1242 /* 1243 |-------------------------------------------------------------------------- 1244 | NEXT ITEMS 1245 |-------------------------------------------------------------------------- 1246 */ 1247 const stopTimes = [0]; 1248 for (let i = 1; i < items.length; i++) { 1249 1250 const previousItem = items[i - 1]; 1251 const currentItem = items[i]; 1252 1253 1254 /* 1255 * HOLD FIRST/CURRENT ITEM 1256 */ 1257 tl.to({}, { 1258 duration: 1 1259 }); 1260 1261 1262 /* 1263 * HIDE PREVIOUS 1264 */ 1265 hideItem(tl, previousItem); 1266 1267 1268 /* 1269 * Move previous behind 1270 */ 1271 tl.set(previousItem.row, { 1272 zIndex: 1 1273 }); 1274 1275 1276 /* 1277 * Bring current row forward 1278 */ 1279 tl.set(currentItem.row, { 1280 opacity: 1, 1281 zIndex: 10 1282 }); 1283 1284 1285 /* 1286 * SHOW CURRENT 1287 */ 1288 showItem(tl, currentItem); 1289 stopTimes.push(tl.duration()); 1290 } 1291 1292 1293 /* 1294 |-------------------------------------------------------------------------- 1295 | NAVIGATION DOTS 1296 |-------------------------------------------------------------------------- 1297 */ 1298
1299 const dotsWrap = document.createElement("div"); 1300 dotsWrap.className = "usps-dots"; 1301 1302 const dots = items.map(function (item, i) { 1303 1304 const dot = document.createElement("button"); 1305 1306 dot.type = "button"; 1307 dot.className = "usps-dot" + (i === 0 ? " is-active" : ""); 1308 dot.setAttribute("aria-label", "Go to slide " + (i + 1)); 1309 1310 dot.addEventListener("click", function () { 1311 scrollToIndex(i); 1312 // jQuery('.usps-dot').removeClass('is-active') 1313 // this.classList.add('is-active'); 1314 // console.log(this); 1315 }); 1316 1317 dotsWrap.appendChild(dot); 1318 1319 return dot; 1320 1321 }); 1322 1323 section.appendChild(dotsWrap); 1324 1325 1326 function scrollToIndex(i) { 1327 1328 const st = tl.scrollTrigger; 1329 const total = tl.duration(); 1330 1331 if (!st || !total) { 1332 return; 1333 } 1334 1335 // keep the last stop just inside the pin so it doesn't unpin 1336 const time = Math.min(stopTimes[i], total - 0.001); 1337 const y = st.start + (st.end - st.start) * (time / total); 1338 1339 window.scrollTo({ 1340 top: y, 1341 behavior: "smooth" 1342 }); 1343 1344 } 1345 1346 1347 function setActiveDot(progress) { 1348 1349 const total = tl.duration(); 1350 1351 if (!total) { 1352 return; 1353 } 1354 1355 const time = progress * total; 1356 1357 let active = 0; 1358 let best = Infinity; 1359 1360 stopTimes.forEach(function (t, i) { 1361 1362 const distance = Math.abs(t - time); 1363 1364 if (distance < best) { 1365 best = distance; 1366 active = i; 1367 } 1368 1369 }); 1370 1371 dots.forEach(function (dot, i) { 1372 dot.classList.toggle("is-active", i === active); 1373 }); 1374 1375 } 1376 1377 tl.eventCallback("onUpdate", function () { 1378 setActiveDot(tl.progress()); 1379 }); 1380 ScrollTrigger.refresh(); 1381 1382}); 1383//*************** Home Page OurUsps Section **************************** 1384 1385//*************** Single - Thinking list image show on hover Section **************************** 1386jQuery(document).ready(function ($) { 1387 1388 let activeItem = null; 1389 let activeImage = null; 1390 1391 const imageMap = new Map(); 1392 1393 const IMAGE_WIDTH = 280; 1394 const IMAGE_HEIGHT = 180; 1395 const GAP = 15; 1396 1397 function initializeItem(item) { 1398 1399 // Already initialized 1400 if (imageMap.has(item)) { 1401 return; 1402 } 1403 1404 const image = item.querySelector('.cursor-thumbnail'); 1405 1406 if (!image) { 1407 return; 1408 } 1409 1410 imageMap.set(item, image); 1411 1412 document.body.appendChild(image); 1413 1414 image.style.position = 'fixed'; 1415 1416 image.style.left = '0px'; 1417 image.style.top = '0px'; 1418 1419 image.style.width = IMAGE_WIDTH + 'px'; 1420 image.style.height = IMAGE_HEIGHT + 'px'; 1421 1422 image.style.zIndex = '999999'; 1423 1424 image.style.pointerEvents = 'none'; 1425 1426 image.style.overflow = 'hidden'; 1427 1428 image.style.opacity = '0'; 1429 1430 image.style.visibility = 'hidden'; 1431 1432 image.style.transform = 'none'; 1433 1434 image.style.translate = 'none'; 1435 1436 const img = image.querySelector('img'); 1437 1438 if (img) { 1439 1440 img.style.width = '196px'; 1441 img.style.height = '124px'; 1442 1443 img.style.display = 'block'; 1444 1445 img.style.objectFit = 'cover'; 1446 } 1447 1448 } 1449 1450 function initializeAllItems() { 1451 1452 const items = document.querySelectorAll( 1453 '.thinking-item' 1454 ); 1455 1456 items.forEach(function (item) { 1457 1458 initializeItem(item); 1459 1460 }); 1461 1462 } 1463 1464 initializeAllItems(); 1465 1466 const observer = new MutationObserver(function (mutations) { 1467 1468 mutations.forEach(function (mutation) { 1469 1470 mutation.addedNodes.forEach(function (node) { 1471 1472 if (node.nodeType !== 1) { 1473 return; 1474 } 1475 1476 if ( 1477 node.matches && 1478 node.matches('.thinking-item') 1479 ) { 1480 1481 initializeItem(node); 1482 1483 } 1484 1485 if (node.querySelectorAll) { 1486 1487 const newItems = 1488 node.querySelectorAll( 1489 '.thinking-item' 1490 ); 1491
1492 newItems.forEach(function (item) { 1493 1494 initializeItem(item); 1495 1496 }); 1497 1498 } 1499 1500 }); 1501 1502 }); 1503 1504 }); 1505 1506 observer.observe(document.body, { 1507 childList: true, 1508 subtree: true 1509 }); 1510 1511 document.addEventListener('mousemove', function (e) { 1512 1513 let currentItem = null; 1514 const items = document.querySelectorAll( 1515 '.thinking-item' 1516 ); 1517 1518 items.forEach(function (item) { 1519 1520 const rect = 1521 item.getBoundingClientRect(); 1522 1523 1524 if ( 1525 e.clientX >= rect.left && 1526 e.clientX <= rect.right && 1527 e.clientY >= rect.top && 1528 e.clientY <= rect.bottom 1529 ) { 1530 1531 currentItem = item; 1532 1533 } 1534 1535 }); 1536 1537 if (currentItem !== activeItem) { 1538 1539 if (activeImage) { 1540 1541 activeImage.style.opacity = '0'; 1542 1543 activeImage.style.visibility = 'hidden'; 1544 1545 } 1546 1547 activeItem = currentItem; 1548 1549 if (!activeItem) { 1550 1551 activeImage = null; 1552 1553 return; 1554 1555 } 1556 1557 activeImage = 1558 imageMap.get(activeItem); 1559 1560 if (!activeImage) { 1561 1562 initializeItem(activeItem); 1563 1564 activeImage = 1565 imageMap.get(activeItem); 1566 1567 } 1568 1569 1570 if (!activeImage) { 1571 1572 return; 1573 1574 } 1575 1576 activeImage.style.visibility = 1577 'visible'; 1578 1579 activeImage.style.opacity = 1580 '1'; 1581 1582 } 1583 1584 if (activeImage) { 1585 1586 let x = 1587 e.clientX + GAP; 1588 1589 let y = 1590 e.clientY + GAP; 1591 1592 if ( 1593 x + IMAGE_WIDTH > 1594 window.innerWidth 1595 ) { 1596 1597 x = 1598 e.clientX - 1599 IMAGE_WIDTH - 1600 GAP; 1601 1602 } 1603 1604 if ( 1605 y + IMAGE_HEIGHT > 1606 window.innerHeight 1607 ) { 1608 1609 y = 1610 e.clientY - 1611 IMAGE_HEIGHT - 1612 GAP; 1613 1614 } 1615 1616 if (x < 0) { 1617 1618 x = 10; 1619 1620 } 1621 1622 if (y < 0) { 1623 1624 y = 10; 1625 1626 } 1627 1628 activeImage.style.left = 1629 x + 'px'; 1630 1631 activeImage.style.top = 1632 y + 'px'; 1633 1634 } 1635 1636 }); 1637 1638 document.addEventListener('mouseleave', function () { 1639 1640 if (activeImage) { 1641 1642 activeImage.style.opacity = '0'; 1643 1644 activeImage.style.visibility = 'hidden'; 1645 1646 } 1647 1648 activeItem = null; 1649 1650 activeImage = null; 1651 1652 }); 1653 1654 window.addEventListener('scroll', function () { 1655 1656 if (activeImage) { 1657 1658 activeImage.style.opacity = '0'; 1659 1660 activeImage.style.visibility = 'hidden'; 1661 1662 } 1663 1664 activeItem = null; 1665 1666 activeImage = null; 1667 1668 }, { 1669 passive: true 1670 }); 1671 1672 ScrollTrigger.refresh(); 1673}); 1674//*************** Single - Thinking list image show on hover Section **************************** 1675 1676//*************** MS and DS listical Page horizontal scroll **************************** 1677document.addEventListener("DOMContentLoaded", function () { 1678 gsap.registerPlugin(ScrollTrigger); 1679 1680 const section = document.querySelector( 1681 ".marketing-services-horizontal-sec" 1682 ); 1683 1684 const slider = section?.querySelector( 1685 ".services-horizontal-slider" 1686 ); 1687 1688 const wrapper = slider?.querySelector( 1689 ".swiper-wrapper" 1690 ); 1691 1692 if (!section || !slider || !wrapper) { 1693 return; 1694 } 1695 1696 const triggerId = 1697 "marketingScroll-" + 1698 Math.random().toString(36).slice(2, 9); 1699 1700 let mobileSwiper = null; 1701 let desktopTween = null; 1702 let currentMode = null; 1703 let resizeTimer = null; 1704 1705 1706 /* 1707 * ========================================== 1708 * HELPERS 1709 * ========================================== 1710 */ 1711 1712 // â MODIFIED: renamed for clarity 1713 const isBelowBreakpoint = () => window.innerWidth < 991; 1714 1715 const getSlides = () => 1716 Array.from( 1717 wrapper.querySelectorAll(".swiper-slide") 1718 ); 1719 1720 1721 /* 1722 * ========================================== 1723 * CLEANUP GSAP (kill horizontal scroll) 1724 * ========================================== 1725 */ 1726 1727 function cleanupDesktop() { 1728 1729 if (desktopTween) { 1730 desktopTween.kill(); 1731 desktopTween = null; 1732 } 1733 1734 const trigger = 1735 ScrollTrigger.getById(triggerId); 1736 1737 if (trigger) { 1738 trigger.kill(true); 1739 } 1740 1741 // â ADDED: kill ALL ScrollTriggers on this section
1742 ScrollTrigger.getAll().forEach(function (st) { 1743 if (st.vars.trigger === section) { 1744 st.kill(true); 1745 } 1746 }); 1747 1748 gsap.killTweensOf(wrapper); 1749 1750 gsap.set(wrapper, { 1751 clearProps: "all" 1752 }); 1753 1754 section.style.removeProperty("height"); 1755 section.style.removeProperty("min-height"); 1756 section.style.removeProperty("display"); 1757 section.style.removeProperty("align-items"); 1758 1759 // â ADDED: clear wrapper inline styles GSAP may leave behind 1760 wrapper.style.removeProperty("transform"); 1761 wrapper.style.removeProperty("width"); 1762 wrapper.style.removeProperty("gap"); 1763 wrapper.style.removeProperty("padding-right"); 1764 1765 getSlides().forEach(function (slide) { 1766 [ 1767 "width", 1768 "flex", 1769 "filter", 1770 "opacity", 1771 "visibility", 1772 "transform", 1773 "z-index" 1774 ].forEach(function (property) { 1775 slide.style.removeProperty(property); 1776 }); 1777 }); 1778 1779 // â ADDED: remove pin-spacer div GSAP leaves in the DOM 1780 const pinSpacer = section.parentElement; 1781 if ( 1782 pinSpacer && 1783 pinSpacer.classList.contains("pin-spacer") 1784 ) { 1785 pinSpacer.parentElement.insertBefore( 1786 section, 1787 pinSpacer 1788 ); 1789 pinSpacer.remove(); 1790 } 1791 } 1792 1793 1794 /* 1795 * ========================================== 1796 * DESTROY MOBILE SWIPER 1797 * ========================================== 1798 */ 1799 1800 /* 1801 * ========================================== 1802 * DESTROY MOBILE SWIPER 1803 * ========================================== 1804 */ 1805 1806 function destroyMobile() { 1807 1808 if (!mobileSwiper) { 1809 return; 1810 } 1811 1812 mobileSwiper.destroy(true, true); 1813 mobileSwiper = null; 1814 1815 // â Remove Swiper duplicate slides FIRST (before cleaning styles) 1816 wrapper 1817 .querySelectorAll(".swiper-slide-duplicate") 1818 .forEach(function (dup) { 1819 dup.remove(); 1820 }); 1821 1822 // â Strip ALL inline styles Swiper sets on wrapper 1823 wrapper.removeAttribute("style"); 1824 1825 // â Strip ALL inline styles Swiper sets on slider 1826 slider.removeAttribute("style"); 1827 1828 // â Remove Swiper classes from slider 1829 slider.classList.remove( 1830 "swiper-initialized", 1831 "swiper-horizontal", 1832 "swiper-vertical", 1833 "swiper-backface-hidden" 1834 ); 1835 1836 // â Clean every slide completely 1837 getSlides().forEach(function (slide) { 1838 1839 // Remove ALL inline styles (width, margin-right, etc.) 1840 slide.removeAttribute("style"); 1841 1842 slide.classList.remove( 1843 "swiper-slide-active", 1844 "swiper-slide-next", 1845 "swiper-slide-prev", 1846 "swiper-slide-duplicate", 1847 "swiper-slide-duplicate-active", 1848 "swiper-slide-duplicate-next", 1849 "swiper-slide-duplicate-prev", 1850 "swiper-slide-visible", 1851 "merk-demd-slider" 1852 ); 1853 1854 // â Remove Swiper data attributes 1855 slide.removeAttribute("data-swiper-slide-index"); 1856 slide.removeAttribute("role"); 1857 slide.removeAttribute("aria-label"); 1858 }); 1859 } 1860 1861 1862 /* 1863 * ========================================== 1864 * MOBILE SWIPER (below 991) 1865 * ========================================== 1866 */ 1867 1868 function createMobile() { 1869 1870 // â First: kill GSAP completely 1871 cleanupDesktop(); 1872 1873 getSlides().forEach(function (slide, index) { 1874 slide.classList.remove("merk-demd-slider"); 1875 1876 if (index % 2 === 0) { 1877 slide.classList.add("merk-demd-slider"); 1878 } 1879 }); 1880 1881 mobileSwiper = new Swiper(slider, { 1882 1883 slidesPerView: 1.5, 1884 spaceBetween: 76, 1885 breakpoints: { 1886 500: { 1887 slidesPerView: 2.5, 1888 spaceBetween: 70 1889 }, 1890 768: { 1891 slidesPerView: 3.5, 1892 spaceBetween: 70 1893 } 1894 }, 1895 loop: true, 1896 centeredSlides: false, 1897 1898 slidesOffsetBefore: 0, 1899 slidesOffsetAfter: 0, 1900 1901 speed: 700, 1902 1903 autoplay: { 1904 delay: 3000, 1905 disableOnInteraction: false, 1906 pauseOnMouseEnter: false 1907 }, 1908 1909 allowTouchMove: true, 1910 1911 resistance: true, 1912 resistanceRatio: 0, 1913 1914 watchOverflow: false, 1915 1916 observer: true, 1917 observeParents: true, 1918 1919 on: { 1920 init: function () { 1921 this.update(); 1922 this.autoplay.start(); 1923 } 1924 } 1925 1926 }); 1927 1928 currentMode = "mobile"; 1929 } 1930 1931 1932 /* 1933 * ========================================== 1934 * DESKTOP GSAP (above 991) 1935 * ========================================== 1936 */ 1937 1938 function createDesktop() { 1939 1940 // â First: kill Swiper completely 1941 destroyMobile(); 1942 1943 // â Then: clean any stale GSAP state 1944 cleanupDesktop(); 1945 1946 const slides = getSlides(); 1947 1948 const sliderWidth = slider.clientWidth; 1949 1950 const columns = 1951 window.innerWidth < 1024 1952 ? 2.5 1953 : 3.5; 1954 1955 const slideWidth = sliderWidth / columns; 1956
1957 // slides.forEach(function (slide) { 1958 // slide.style.width = slideWidth + "px"; 1959 // slide.style.flex = "0 0 " + slideWidth + "px"; 1960 // }); 1961 1962 wrapper.style.gap = "0"; 1963 wrapper.style.paddingRight = "0"; 1964 1965 // Force layout recalculation 1966 void wrapper.offsetWidth; 1967 1968 const scrollDistance = 1969 Math.max( 1970 0, 1971 wrapper.scrollWidth - slider.clientWidth 1972 ); 1973 1974 if (scrollDistance <= 0) { 1975 currentMode = "desktop"; 1976 return; 1977 } 1978 1979 const header = document.querySelector("header"); 1980 const headerHeight = header?.offsetHeight || 0; 1981 1982 if (window.innerWidth < 1024) { 1983 Object.assign(section.style, { 1984 height: "100vh", 1985 minHeight: "100vh", 1986 display: "flex", 1987 alignItems: "center" 1988 }); 1989 } 1990 1991 desktopTween = gsap.to(wrapper, { 1992 x: -scrollDistance, 1993 ease: "none", 1994 scrollTrigger: { 1995 id: triggerId, 1996 trigger: section, 1997 start: "top " + headerHeight + "px", 1998 end: "+=" + scrollDistance, 1999 pin: true, 2000 pinSpacing: true, 2001 scrub: 1, 2002 anticipatePin: 1, 2003 invalidateOnRefresh: true 2004 } 2005 }); 2006 2007 currentMode = "desktop"; 2008 } 2009 2010 2011 /* 2012 * ========================================== 2013 * INITIALIZE (mode router) 2014 * ========================================== 2015 */ 2016 2017 function initialize() { 2018 2019 var newMode = isBelowBreakpoint() 2020 ? "mobile" 2021 : "desktop"; 2022 2023 // â MODIFIED: same mode â just refresh, don't rebuild 2024 if (currentMode === newMode) { 2025 2026 if (newMode === "mobile" && mobileSwiper) { 2027 mobileSwiper.update(); 2028 } 2029 2030 if (newMode === "desktop") { 2031 ScrollTrigger.refresh(); 2032 } 2033 2034 return; 2035 } 2036 2037 // â MODIFIED: always tear down BOTH before switching 2038 destroyMobile(); 2039 cleanupDesktop(); 2040 2041 if (newMode === "mobile") { 2042 createMobile(); 2043 } else { 2044 createDesktop(); 2045 } 2046 } 2047 2048 2049 /* 2050 * ========================================== 2051 * INITIAL LOAD 2052 * ========================================== 2053 */ 2054 2055 function initAfterLoad() { 2056 requestAnimationFrame(function () { 2057 requestAnimationFrame(function () { 2058 initialize(); 2059 }); 2060 }); 2061 } 2062 2063 if (document.readyState === "complete") { 2064 initAfterLoad(); 2065 } else { 2066 window.addEventListener( 2067 "load", 2068 initAfterLoad, 2069 { once: true } 2070 ); 2071 } 2072 2073 2074 /* 2075 * ========================================== 2076 * RESPONSIVE RESIZE 2077 * ========================================== 2078 */ 2079 2080 window.addEventListener( 2081 "resize", 2082 function () { 2083 2084 clearTimeout(resizeTimer); 2085 2086 resizeTimer = setTimeout( 2087 function () { 2088 2089 var newMode = isBelowBreakpoint() 2090 ? "mobile" 2091 : "desktop"; 2092
2093 // Breakpoint crossed â full rebuild 2094 if (newMode !== currentMode) { 2095 currentMode = null; 2096 initialize(); 2097 return; 2098 } 2099 2100 // Same mode â lightweight refresh 2101 if ( 2102 newMode === "mobile" && 2103 mobileSwiper 2104 ) { 2105 mobileSwiper.update(); 2106 } else if (newMode === "desktop") { 2107 // â ADDED: recalculate slide widths on resize 2108 createDesktop(); 2109 } 2110 2111 }, 2112 250 2113 ); 2114 2115 } 2116 ); 2117 2118}); 2119//*************** MS and DS listical Page horizontal scroll **************************** 2120 2121//***************home page recognised sec*************// 2122document.addEventListener("DOMContentLoaded", function () { 2123 2124 if (typeof Swiper === "undefined") return; 2125 2126 document.querySelectorAll(".recognised-grid").forEach(function (grid) { 2127 2128 if (window.innerWidth > 767) return; 2129 2130 const slides = [...grid.children]; 2131 2132 grid.classList.remove( 2133 "is-layout-grid", 2134 "wp-container-core-group-is-layout-9d260ee2" 2135 ); 2136 2137 grid.classList.add("swiper"); 2138 2139 const wrapper = document.createElement("div"); 2140 wrapper.className = "swiper-wrapper"; 2141 2142 slides.forEach(function (slide) { 2143 slide.classList.add("swiper-slide"); 2144 wrapper.appendChild(slide); 2145 }); 2146 2147 grid.appendChild(wrapper); 2148 2149 const pagination = document.createElement("div"); 2150 pagination.className = "recognised-pagination"; 2151 grid.parentNode.appendChild(pagination); 2152 2153 new Swiper(grid, { 2154 slidesPerView: 1, 2155 loop: true, 2156 speed: 800, 2157 autoplay: { 2158 delay: 2000, 2159 disableOnInteraction: false 2160 }, 2161 pagination: { 2162 el: pagination, 2163 clickable: true 2164 } 2165 }); 2166 2167 }); 2168}); 2169//***************home page recognised sec*************// 2170 2171// ####Gopi#################################################################################################################################### 2172// ######################################################################################################################################## 2173// ######################################################################################################################################## 2174// ######################################################################################################################################## 2175// ######################################################################################################################################## 2176 2177//*************** Testimonial section Icon drop animation section **************************** 2178document.addEventListener("DOMContentLoaded", function () { 2179 /* 2180 ============================================================ 2181 GSAP CHECK 2182 ============================================================ 2183 */ 2184 // return; 2185 if ( 2186 typeof gsap === "undefined" || 2187 typeof ScrollTrigger === "undefined" 2188 ) { 2189 console.warn("GSAP / ScrollTrigger is not loaded."); 2190 return; 2191 } 2192 gsap.registerPlugin(ScrollTrigger); 2193 2194 const icons = gsap.utils.toArray(".cilent-icon"); 2195 if (!icons.length) { 2196 return; 2197 } 2198 2199 const iconSettings = [ 2200 { 2201 selector: ".cilent-greenflower-des", 2202 drop: -500, 2203 rotation: -360, 2204 duration: 1.2 2205 }, 2206 { 2207 selector: ".cilent-pink-des", 2208 drop: -450, 2209 rotation: 0, 2210 duration: 1 2211 }, 2212 { 2213 selector: ".cilent-lightblue-des", 2214 drop: -500, 2215 rotation: 360, 2216 duration: 1.1 2217 }, 2218 { 2219 selector: ".cilent-fourcricles-des", 2220 drop: -600, 2221 rotation: 0, 2222 duration: 1.3 2223 }, 2224 { 2225 selector: ".cilent-love-des", 2226 drop: -450, 2227 rotation: -180, 2228 duration: 1 2229 }, 2230 { 2231 selector: ".cilent-green-des", 2232 drop: -900, 2233 rotation: 0, 2234 duration: 1.1 2235 } 2236 ]; 2237 /* 2238 ============================================================ 2239 INITIAL STATE 2240 ============================================================ 2241 */
2242 iconSettings.forEach(function (item) { 2243 const icon = document.querySelector(item.selector); 2244 if (!icon) return; 2245 gsap.set(icon, { 2246 y: item.drop, 2247 opacity: 0, 2248 rotation: item.rotation, 2249 scale: 0.8, 2250 transformOrigin: "50% 50%" 2251 }); 2252 }); 2253 /* 2254 ============================================================ 2255 ENTRANCE ANIMATION 2256 ============================================================ 2257 */ 2258 const entranceTimeline = gsap.timeline({ 2259 scrollTrigger: { 2260 2261 trigger: ".client-testimonial-sec", 2262 start: "top top", 2263 toggleActions: "play none none reverse", 2264 markers: false, 2265 stagger:1, 2266 scrub:false 2267 } 2268 }); 2269 iconSettings.forEach(function (item, index) { 2270 const icon = document.querySelector(item.selector); 2271 if (!icon) return; 2272 entranceTimeline.to( 2273 icon, 2274 { 2275 y: 0, 2276 opacity: 1, 2277 rotation: 0, 2278 scale: 1, 2279 duration: item.duration, 2280 ease: "back.out(1.7)" 2281 }, 2282 /* 2283 One after another 2284 */ 2285 index * 0.12 2286 ); 2287 }); 2288 2289 const idleAnimations = []; 2290 entranceTimeline.eventCallback( 2291 "onComplete", 2292 function () { 2293 icons.forEach(function (icon, index) { 2294 const direction = 2295 index % 2 === 0 ? 1 : -1; 2296 const idle = gsap.to(icon, { 2297 y: "+=" + (5 + (index % 3) * 3), 2298 rotation: 2299 "+=" + (direction * (2 + (index % 3))), 2300 duration: 2301 4 + (index % 3), 2302 ease: "sine.inOut", 2303 yoyo: true, 2304 repeat: -1 2305 }); 2306 idleAnimations.push(idle); 2307 }); 2308 } 2309 ); 2310 2311 const RADIUS = 220; 2312 2313 const quickAnimations = icons.map(function (icon) { 2314 return { 2315 x: gsap.quickTo( 2316 icon, 2317 "x", 2318 { 2319 duration: 0.45, 2320 ease: "power3.out" 2321 } 2322 ), 2323 y: gsap.quickTo( 2324 icon, 2325 "y", 2326 { 2327 duration: 0.45, 2328 ease: "power3.out" 2329 } 2330 ), 2331 rotation: gsap.quickTo( 2332 icon, 2333 "rotation", 2334 { 2335 duration: 0.5, 2336 ease: "power3.out" 2337 } 2338 ), 2339 scale: gsap.quickTo( 2340 icon, 2341 "scale", 2342 { 2343 duration: 0.4, 2344 ease: "power3.out" 2345 } 2346 ) 2347 }; 2348 }); 2349 2350 let raf = null; 2351 const pointer = { 2352 x: -9999, 2353 y: -9999 2354 }; 2355 2356 function applyMouseEffect() { 2357 raf = null; 2358 icons.forEach(function (icon, index) { 2359 const rect = 2360 icon.getBoundingClientRect(); 2361 /* 2362 Icon center 2363 */ 2364 const iconX = 2365 rect.left + 2366 rect.width / 2; 2367 const iconY = 2368 rect.top + 2369 rect.height / 2; 2370 2371 const dx = 2372 iconX - pointer.x; 2373 const dy = 2374 iconY - pointer.y; 2375 const distance = 2376 Math.sqrt( 2377 dx * dx + 2378 dy * dy 2379 ); 2380 /* 2381 Mouse is close to icon 2382 */ 2383 if (distance < RADIUS) { 2384 /* 2385 1 = very close 2386 0 = edge of radius 2387 */ 2388 const force = 2389 1 - distance / RADIUS; 2390 /* 2391 Direction away from mouse 2392 */ 2393 const angle = 2394 Math.atan2(dy, dx); 2395 /* 2396 Movement strength 2397 */ 2398 const moveX = 2399 Math.cos(angle) * 2400 force * 2401 35; 2402 const moveY = 2403 Math.sin(angle) * 2404 force * 2405 25; 2406 /* 2407 Move icon 2408 */ 2409 quickAnimations[index].x( 2410 moveX 2411 ); 2412 quickAnimations[index].y( 2413 moveY 2414 ); 2415 /* 2416 Small shake/rotation 2417 */ 2418 quickAnimations[index].rotation( 2419 force * 2420 (index % 2 === 0 ? 5 : -5) 2421 ); 2422 /* 2423 Slight scale 2424 */ 2425 quickAnimations[index].scale( 2426 1 + force * 0.08 2427 ); 2428 } else { 2429 /* 2430 Return to original position 2431 */ 2432 quickAnimations[index].x(0); 2433 quickAnimations[index].y(0); 2434 quickAnimations[index].rotation(0); 2435 quickAnimations[index].scale(1); 2436 } 2437 }); 2438 } 2439 /* 2440 ============================================================ 2441 MOUSE MOVE 2442 ============================================================ 2443 */ 2444 function onMouseMove(event) { 2445 // console.log(event); 2446 pointer.x = 2447 event.clientX; 2448 pointer.y = 2449 event.clientY; 2450 /* 2451 Only request one animation frame 2452 */ 2453 if (!raf) { 2454 raf = 2455 requestAnimationFrame( 2456 applyMouseEffect 2457 ); 2458 } 2459 } 2460 2461 const section = 2462 document.querySelector( 2463 ".client-quotes__bg" 2464 ) || 2465 document.querySelector( 2466 ".cilent-icon" 2467 )?.parentElement; 2468 let listening = false;
2469 function setMouseListener(enabled) { 2470 if (enabled === listening) { 2471 return; 2472 } 2473 listening = enabled; 2474 if (enabled) { 2475 window.addEventListener( 2476 "mousemove", 2477 onMouseMove, 2478 { 2479 passive: true 2480 } 2481 ); 2482 } else { 2483 window.removeEventListener( 2484 "mousemove", 2485 onMouseMove 2486 ); 2487 /* 2488 Move pointer outside the active area 2489 */ 2490 pointer.x = -9999; 2491 pointer.y = -9999; 2492 if (!raf) { 2493 raf = 2494 requestAnimationFrame( 2495 applyMouseEffect 2496 ); 2497 } 2498 } 2499 } 2500 2501 if ( 2502 section && 2503 "IntersectionObserver" in window 2504 ) { 2505 const observer = 2506 new IntersectionObserver( 2507 function (entries) { 2508 setMouseListener( 2509 entries[0].isIntersecting 2510 ); 2511 }, 2512 { 2513 rootMargin: "150px" 2514 } 2515 ); 2516 observer.observe(section); 2517 } else { 2518 setMouseListener(true); 2519 } 2520 /* 2521 ============================================================ 2522 PAUSE IDLE ANIMATION WHEN TAB IS HIDDEN 2523 ============================================================ 2524 */ 2525 document.addEventListener( 2526 "visibilitychange", 2527 function () { 2528 idleAnimations.forEach( 2529 function (animation) { 2530 if (document.hidden) { 2531 animation.pause(); 2532 } else { 2533 animation.resume(); 2534 } 2535 } 2536 ); 2537 } 2538 ); 2539 /* 2540 ============================================================ 2541 REFRESH AFTER PAGE LOAD 2542 ============================================================ 2543 */ 2544 window.addEventListener( 2545 "load", 2546 function () { 2547 ScrollTrigger.refresh(); 2548 } 2549 ); 2550 ScrollTrigger.refresh(); 2551 }); 2552//*************** Testimonial section Icon drop animation section **************************** 2553 2554//*************** About Us Cultural section **************************** 2555document.addEventListener("DOMContentLoaded", function () { 2556 2557 const track = document.querySelector(".cultural-sec-track"); 2558 const section = document.querySelector(".cultural-sec"); 2559 const heading = document.querySelector(".company-culture-row h2"); 2560 if (!track || !section) { 2561 return; 2562 } 2563 const culturalScroll = gsap.to(section, { 2564 x: () => { 2565 return -(section.scrollWidth - window.innerWidth); 2566 }, 2567 2568 ease: "none", 2569 scrollTrigger: { 2570 trigger: track, 2571 pin: ".cultural-sec-wrapper", 2572 start: "bottom bottom", 2573 end: () => { 2574 return `+=${section.scrollWidth - window.innerWidth}`; 2575 }, 2576 scrub: 2, 2577 anticipatePin: 1, 2578 markers: false, 2579 invalidateOnRefresh: true 2580 } 2581 }); 2582 if (window.innerWidth >= 991) { 2583 gsap.to(heading, { 2584 y: -300, 2585 opacity:0, 2586 ease: "none", 2587 2588 scrollTrigger: { 2589 trigger: track, 2590 start: "bottom bottom", 2591 end: () => { 2592 return `+=${section.scrollWidth - window.innerWidth}`; 2593 }, 2594 scrub: 0.5, 2595 invalidateOnRefresh: true 2596 } 2597 }); 2598 } 2599 window.addEventListener("load", function () { 2600 ScrollTrigger.refresh(); 2601 }); 2602}); 2603//***************About us cultural section **************************** 2604 2605//*************** Map section **************************** 2606document.addEventListener("DOMContentLoaded", (event) => { 2607 // return; 2608 function rotateGlobeScroll() { 2609 let mapSection = document.querySelector(".map-section"); 2610 if (!mapSection) return; 2611 let svgImage = mapSection.querySelector(".map-svg-container svg"); 2612 let locationPaths = svgImage.querySelectorAll(".location"); 2613 gsap.from(".map-section .map-container", { 2614 y: 200, 2615 opacity: 0, 2616 scrollTrigger: { 2617 trigger: mapSection, 2618 start: "top 90%", 2619 scrub: true, 2620 }, 2621 }); 2622 ScrollTrigger.matchMedia({ 2623 // Tab 2624 "(max-width: 1099px)": function () { 2625 let tl = gsap.timeline({ 2626 scrollTrigger: { 2627 trigger: mapSection, 2628 start: "top 80%", 2629 end: "+=50%",
2630 pin: false, 2631 pinSpacing: false, 2632 scrub: true, 2633 stagger: 0.5, 2634 markers: false, 2635 }, 2636 }); 2637 tl.from(svgImage, { opacity: 0 }); 2638 tl.from( 2639 svgImage, 2640 { 2641 rotate: -90, 2642 transformOrigin: "50% 50%", 2643 ease: "power3.inOut", 2644 duration: 2, 2645 }, 2646 1, 2647 ); 2648 tl.from( 2649 locationPaths, 2650 { 2651 y: -200, 2652 opacity: 0, 2653 ease: "power3.out", 2654 duration: 2, 2655 stagger: 0.7, 2656 }, 2657 2, 2658 ); 2659 }, 2660 2661 // Desktop 2662 "(min-width: 1100px)": function () { 2663 let tl = gsap.timeline({ 2664 scrollTrigger: { 2665 trigger: ".map-section", 2666 start: "top top", 2667 end: "+=200%", 2668 pin: true, 2669 pinSpacing: true, 2670 anticipatePin: 1, 2671 scrub: true, 2672 stagger: 0.5, 2673 markers: false, 2674 }, 2675 }); 2676 2677 tl.from(svgImage, { opacity: 0 }); 2678 tl.from( 2679 svgImage, 2680 { 2681 rotate: -90, 2682 transformOrigin: "50% 50%", 2683 ease: "power3.inOut", 2684 duration: 2, 2685 }, 2686 1, 2687 ); 2688 tl.from( 2689 locationPaths, 2690 { 2691 y: -200, 2692 opacity: 0, 2693 ease: "power3.out", 2694 duration: 2, 2695 stagger: 0.7, 2696 }, 2697 2, 2698 ); 2699 }, 2700 }); 2701 } 2702 rotateGlobeScroll(); 2703 ScrollTrigger.refresh(); 2704}) 2705//*************** Map section **************************** 2706 2707//*************** section next to map section revel **************************** 2708document.addEventListener("DOMContentLoaded", () => { 2709 const pinSection = document.querySelector(".map-client-scroll-sec"); 2710 if (!pinSection) return; 2711 2712 ScrollTrigger.matchMedia({ 2713 "(min-width: 1100px)": function () { 2714 gsap.set(pinSection, { marginTop: "-100vh" }); 2715 2716 const tl = gsap.timeline({ 2717 scrollTrigger: { 2718 trigger: pinSection, 2719 start: "top top", 2720 end: "+=100%", 2721 pin: true, 2722 pinSpacing: true, 2723 anticipatePin: 1, 2724 scrub: 1, 2725 markers: false, 2726 }, 2727 }); 2728 } 2729 }); 2730}); 2731//*************** section next to map section revel **************************** 2732 2733// #####Work Module ################################################################################################################################### 2734// ######################################################################################################################################## 2735// ######################################################################################################################################## 2736// ######################################################################################################################################## 2737 2738//*************** card shuffle on scroll **************************** 2739document.addEventListener('DOMContentLoaded', function () { 2740 2741 //*************** Home, MS and DS listical Page card shuffle on scroll **************************** 2742 (function () { 2743 2744 if (!document.body.querySelector('#case-study-work-results')) { 2745 2746 var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 2747 if (prefersReducedMotion) return; 2748 2749 var sections = Array.prototype.slice.call(document.querySelectorAll('.ub-work')); 2750 if (!sections.length) return; 2751 2752 if (window.innerWidth < 767) { 2753 mobileInit(sections); 2754 } else { 2755 desktopInit(sections); 2756 } 2757 2758 /* ============================================================== 2759 MOBILE â auto-scrolling infinite carousel (logic only, no CSS) 2760 ============================================================== */ 2761 function mobileInit(sections) { 2762 sections.forEach(setupMobileCarousel); 2763 } 2764 2765 function setupMobileCarousel(section) { 2766 var cards = Array.prototype.slice.call(section.querySelectorAll('.ub-work__card')); 2767 if (!cards.length) return; 2768 2769 var content = section.querySelector('.ub-work__content'); 2770 var pin = section.querySelector('.ub-work__pin'); 2771 2772 // Clear any stale inline styles from desktop logic 2773 if (content) content.style.cssText = ''; 2774 cards.forEach(function (card) { 2775 var bg = card.getAttribute('style') || ''; 2776 card.style.cssText = bg; 2777 }); 2778 2779 // --- Build carousel DOM --- 2780 var viewport = document.createElement('div'); 2781 viewport.className = 'ub-work-mob-viewport'; 2782 2783 var track = document.createElement('div'); 2784 track.className = 'ub-work-mob-track'; 2785 2786 // Move original cards into track 2787 cards.forEach(function (card) { track.appendChild(card); }); 2788 2789 // Clone all cards for seamless infinite loop 2790 cards.forEach(function (card) { 2791 var clone = card.cloneNode(true); 2792 clone.setAttribute('data-clone', 'true'); 2793 clone.setAttribute('aria-hidden', 'true'); 2794 track.appendChild(clone); 2795 }); 2796 2797 viewport.appendChild(track); 2798 pin.appendChild(viewport); 2799 2800 // --- Dots ---
2801 var dotsWrap = document.createElement('div'); 2802 dotsWrap.className = 'ub-work-mob-dots'; 2803 cards.forEach(function (_, i) { 2804 var dot = document.createElement('button'); 2805 dot.className = 'ub-work-mob-dot' + (i === 0 ? ' active' : ''); 2806 dot.setAttribute('aria-label', 'Go to slide ' + (i + 1)); 2807 dot.addEventListener('click', function () { 2808 goTo(i, true); 2809 restartAuto(); 2810 }); 2811 dotsWrap.appendChild(dot); 2812 }); 2813 viewport.after(dotsWrap); 2814 2815 var dots = Array.prototype.slice.call(dotsWrap.querySelectorAll('.ub-work-mob-dot')); 2816 2817 // --- State --- 2818 var current = 0; 2819 var total = cards.length; 2820 var autoTimer = null; 2821 var transitioning = false; 2822 var AUTO_DELAY = 3500; 2823 var TRANS_MS = 700; 2824 2825 function cardStep() { 2826 var c = track.children[0]; 2827 if (!c) return window.innerWidth; 2828 var gap = parseFloat(getComputedStyle(track).gap) || 0; 2829 return c.offsetWidth + gap; 2830 } 2831 2832 function updateDots(activeIndex) { 2833 dots.forEach(function (d, i) { 2834 d.classList.toggle('active', i === activeIndex); 2835 }); 2836 } 2837 2838 function slideTo(index, animated) { 2839 if (animated) { 2840 track.style.transition = 'transform ' + TRANS_MS + 'ms cubic-bezier(.4,0,.2,1)'; 2841 transitioning = true; 2842 } else { 2843 track.style.transition = 'none'; 2844 transitioning = false; 2845 } 2846 track.style.transform = 'translateX(' + (-index * cardStep()) + 'px)'; 2847 updateDots(((index % total) + total) % total); 2848 } 2849 2850 track.addEventListener('transitionend', function () { 2851 transitioning = false; 2852 if (current >= total) { 2853 current = current % total; 2854 slideTo(current, false); 2855 } 2856 }); 2857 2858 function goTo(index, animated) { 2859 if (transitioning) return; 2860 current = index; 2861 slideTo(current, animated); 2862 } 2863 2864 function advance() { 2865 if (transitioning) return; 2866 current++; 2867 slideTo(current, true); 2868 } 2869 2870 function startAuto() { stopAuto(); autoTimer = setInterval(advance, AUTO_DELAY); } 2871 function stopAuto() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } } 2872 function restartAuto() { stopAuto(); startAuto(); } 2873 2874 // --- Touch / swipe --- 2875 var touchStartX = 0; 2876 var touchDiffX = 0; 2877 var swiping = false; 2878 var dragOffset = 0; 2879 2880 viewport.addEventListener('touchstart', function (e) { 2881 if (transitioning) return; 2882 stopAuto(); 2883 touchStartX = e.touches[0].clientX; 2884 touchDiffX = 0; 2885 swiping = false; 2886 track.style.transition = 'none'; 2887 }, { passive: true }); 2888 2889 viewport.addEventListener('touchmove', function (e) { 2890 if (transitioning) return; 2891 touchDiffX = e.touches[0].clientX - touchStartX; 2892 if (!swiping && Math.abs(touchDiffX) > 8) swiping = true; 2893 if (swiping) { 2894 dragOffset = touchDiffX; 2895 track.style.transform = 'translateX(' + (-current * cardStep() + dragOffset) + 'px)'; 2896 } 2897 }, { passive: true }); 2898 2899 viewport.addEventListener('touchend', function () { 2900 if (swiping && !transitioning) { 2901 var threshold = cardStep() * 0.2; 2902 if (dragOffset < -threshold) { 2903 current++; 2904 if (current >= total * 2) current = total; 2905 } else if (dragOffset > threshold && current > 0) { 2906 current--; 2907 } 2908 slideTo(current, true); 2909 } 2910 dragOffset = 0; 2911 swiping = false; 2912 restartAuto(); 2913 }); 2914 2915 // Pause when tab hidden 2916 document.addEventListener('visibilitychange', function () { 2917 if (document.hidden) stopAuto(); else startAuto(); 2918 }); 2919 2920 // Recalc on resize while still mobile 2921 window.addEventListener('resize', function () { 2922 if (window.innerWidth >= 767) return; 2923 slideTo(current, false); 2924 }); 2925 2926 // --- Init --- 2927 slideTo(0, false); 2928 startAuto(); 2929 } 2930 2931 2932 /* ============================================================== 2933 DESKTOP â original scroll-driven card-stack (unchanged) 2934 ============================================================== */ 2935 function desktopInit(sections) { 2936 2937 var CARD_START_RATIO = 5 / 4; 2938 var CARD_END_RATIO = 16 / 9; 2939 var OVERLAPPED_SCALE = 0.85; 2940 var CONTENT_EXIT_MULTIPLIER = 1.3; 2941 var CARD0_VISIBLE_FRACTION = 2 / 3; 2942 var ACTIVE_FRACTION = 0.5; 2943 2944 var instances = sections.map(function (section) { 2945 var cards = Array.prototype.slice.call(section.querySelectorAll('.ub-work__card')); 2946 cards.forEach(function (card, i) { card.style.setProperty('--card-z', String(i + 1)); }); 2947 return { 2948 section: section, 2949 content: section.querySelector('.ub-work__content'), 2950 cards: cards, 2951 cardCount: cards.length, 2952 card0RestOffsetX: null, 2953 lastSegment: -1, 2954 dots: null,
2955 dotsWrap: null 2956 }; 2957 }); 2958 2959 function measureCard0Offset(instance) { 2960 if (!instance.cards.length) return; 2961 var card0 = instance.cards[0]; 2962 var previousAspect = card0.style.aspectRatio; 2963 card0.style.aspectRatio = String(CARD_START_RATIO); 2964 var cardWidth = card0.offsetWidth; 2965 card0.style.aspectRatio = previousAspect; 2966 var restLeftEdge = window.innerWidth - cardWidth * CARD0_VISIBLE_FRACTION; 2967 var centeredLeftEdge = (window.innerWidth - cardWidth) / 2; 2968 instance.card0RestOffsetX = restLeftEdge - centeredLeftEdge; 2969 } 2970 2971 function buildDots(instance) { 2972 if (instance.cardCount < 2) return; 2973 2974 var host = instance.section.querySelector('.ub-work__pin') || instance.section; 2975 2976 var wrap = document.createElement('div'); 2977 wrap.className = 'ub-work-dots'; 2978 2979 instance.cards.forEach(function (_, i) { 2980 var dot = document.createElement('button'); 2981 dot.type = 'button'; 2982 dot.className = 'ub-work-dot' + (i === 0 ? ' active' : ''); 2983 dot.setAttribute('aria-label', 'Go to card ' + (i + 1)); 2984 dot.setAttribute('data-index', String(i)); 2985 dot.setAttribute('data-active', i === 0 ? 'true' : 'false'); 2986 dot.addEventListener('click', function () { 2987 // updateDots(instance, i + 0.5); // paint immediately 2988 scrollToCard(instance, i); 2989 }); 2990 wrap.appendChild(dot); 2991 }); 2992 2993 host.appendChild(wrap); 2994 2995 instance.dotsWrap = wrap; 2996 instance.dots = Array.prototype.slice.call(wrap.children); 2997 } 2998 2999 function scrollToCard(instance, index) { 3000 var rect = instance.section.getBoundingClientRect(); 3001 var scrollableDistance = rect.height - window.innerHeight; 3002 if (scrollableDistance <= 0) return; 3003 3004 var sectionTop = rect.top + window.scrollY; 3005 3006 // same resting point the CTA targets: card fully in, not yet exiting 3007 var targetLocal = index + (ACTIVE_FRACTION + 1) / 2; 3008 var targetProgress = Math.min(1, targetLocal / instance.cardCount); 3009 var DOT_SCROLL_DURATION_MS = 600; 3010 animatedScrollTo(sectionTop + targetProgress * scrollableDistance, DOT_SCROLL_DURATION_MS); 3011 } 3012 3013 function updateDots(instance, segmentPosition) { 3014 if (!instance.dots) return; 3015 3016 var active = Math.min( 3017 instance.cardCount - 1, 3018 Math.max(0, Math.floor(segmentPosition)) 3019 ); 3020 3021 if (active === instance.activeDot) return; 3022 instance.activeDot = active; 3023 3024 instance.dots.forEach(function (dot, i) { 3025 var on = (i === active); 3026 dot.classList.toggle('active', on); 3027 dot.setAttribute('data-active', on ? 'true' : 'false'); 3028 }); 3029 } 3030 3031 function update(instance) { 3032 var rect = instance.section.getBoundingClientRect(); 3033 var scrollableDistance = rect.height - window.innerHeight; 3034 var overallProgress = scrollableDistance <= 0 3035 ? 0 3036 : Math.min(1, Math.max(0, -rect.top / scrollableDistance)); 3037 3038 var segmentPosition = overallProgress * instance.cardCount; 3039 if (segmentPosition === instance.lastSegment) return; 3040 instance.lastSegment = segmentPosition; 3041 3042 updateDots(instance, segmentPosition); 3043 3044 if (instance.card0RestOffsetX === null) measureCard0Offset(instance); 3045 3046 var currentIndex = Math.min(instance.cardCount - 1, Math.floor(segmentPosition)); 3047 var rawLocalProgress = Math.min(1, segmentPosition - currentIndex); 3048 var localProgress = Math.min(1, rawLocalProgress / ACTIVE_FRACTION); 3049 3050 if (instance.content) { 3051 var contentRaw = Math.min(1, segmentPosition); 3052 var contentProgress = Math.min(1, contentRaw / ACTIVE_FRACTION); 3053 var contentWidth = instance.content.offsetWidth; 3054 var contentX = -contentProgress * contentWidth * CONTENT_EXIT_MULTIPLIER; 3055 instance.content.style.transform = 3056 'translateY(-50%) translateX(' + contentX + 'px)'; 3057 instance.content.style.opacity = String(Math.max(0, 1 - contentProgress * 1.4)); 3058 instance.content.style.pointerEvents = contentProgress >= 0.99 ? 'none' : 'auto'; 3059 } 3060 3061 instance.cards.forEach(function (card, i) { 3062 var offsetX = 0, offsetY = 0, scale = 1, opacity = 1, ratio, copyOpacity = 0; 3063 3064 if (i > currentIndex) { 3065 ratio = CARD_START_RATIO; 3066 copyOpacity = 0; 3067 if (i === 0) offsetX = instance.card0RestOffsetX; 3068 else offsetY = window.innerHeight; 3069 } else if (i === currentIndex) { 3070 ratio = CARD_START_RATIO + (CARD_END_RATIO - CARD_START_RATIO) * localProgress; 3071 copyOpacity = localProgress; 3072 if (i === 0) offsetX = instance.card0RestOffsetX * (1 - localProgress); 3073 else offsetY = window.innerHeight * (1 - localProgress); 3074 } else if (i === currentIndex - 1) { 3075 ratio = CARD_END_RATIO; 3076 scale = 1 - (1 - OVERLAPPED_SCALE) * localProgress; 3077 opacity = 1 - localProgress; 3078 copyOpacity = opacity; 3079 } else { 3080 ratio = CARD_END_RATIO; 3081 scale = OVERLAPPED_SCALE; 3082 opacity = 0; 3083 copyOpacity = 0; 3084 } 3085 3086 card.style.transform = 3087 'translate(calc(-50% + ' + offsetX + 'px), calc(-50% + ' + offsetY + 'px)) scale(' + scale + ')'; 3088 card.style.aspectRatio = String(ratio); 3089 card.style.opacity = String(opacity); 3090 card.style.pointerEvents = (i === currentIndex && localProgress >= 1) ? 'auto' : 'none'; 3091 var cardCopy = card.querySelector('.ub-work__card-copy'); 3092 if (cardCopy) cardCopy.style.opacity = String(copyOpacity); 3093 }); 3094 3095 instance.section.style.setProperty('--progress', String(overallProgress)); 3096 } 3097 3098 function init(instance) { 3099 instance.section.style.setProperty('--segment-count', Str
3099ing(instance.cardCount)); 3100 } 3101 3102 var CTA_SCROLL_DURATION_MS = 1400; 3103 var activeScrollAnim = null; 3104 3105 function cancelScrollAnim() { 3106 activeScrollAnim = null; 3107 } 3108 3109 function animatedScrollTo(targetY, duration) { 3110 var startY = window.scrollY; 3111 var distance = targetY - startY; 3112 var startTime = null; 3113 var token = {}; 3114 3115 activeScrollAnim = token; 3116 3117 function step(timestamp) { 3118 if (activeScrollAnim !== token) return; // superseded or cancelled 3119 if (startTime === null) startTime = timestamp; 3120 3121 var t = Math.min(1, (timestamp - startTime) / duration); 3122 var eased = 1 - Math.pow(1 - t, 3); // easeOutCubic 3123 3124 window.scrollTo(0, startY + distance * eased); 3125 3126 if (t < 1) requestAnimationFrame(step); 3127 else activeScrollAnim = null; 3128 } 3129 3130 requestAnimationFrame(step); 3131 } 3132 3133 window.addEventListener('wheel', cancelScrollAnim, { passive: true }); 3134 window.addEventListener('touchstart', cancelScrollAnim, { passive: true }); 3135 3136 instances.forEach(function (instance) { 3137 var cta = instance.section.querySelector('.ub-work__cta'); 3138 if (!cta) return; 3139 cta.addEventListener('click', function (e) { 3140 var rect = instance.section.getBoundingClientRect(); 3141 var scrollableDistance = rect.height - window.innerHeight; 3142 if (scrollableDistance <= 0) return; 3143 e.preventDefault(); 3144 var sectionTop = rect.top + window.scrollY; 3145 var targetLocal = (ACTIVE_FRACTION + 1) / 2; 3146 var targetProgress = targetLocal / instance.cardCount; 3147 animatedScrollTo(sectionTop + targetProgress * scrollableDistance, CTA_SCROLL_DURATION_MS); 3148 }); 3149 }); 3150 3151 var ticking = false; 3152 function onScrollOrResize() { 3153 if (ticking) return; 3154 ticking = true; 3155 requestAnimationFrame(function () { 3156 instances.forEach(update); 3157 ticking = false; 3158 }); 3159 } 3160 3161 function forceRemeasure() { 3162 instances.forEach(function (instance) { 3163 instance.card0RestOffsetX = null; 3164 instance.lastSegment = -1; 3165 }); 3166 instances.forEach(update); 3167 } 3168 3169 window.addEventListener('scroll', onScrollOrResize, { passive: true }); 3170 window.addEventListener('resize', forceRemeasure); 3171 3172 instances.forEach(buildDots); 3173 instances.forEach(init); 3174 instances.forEach(update); 3175 3176 if (document.readyState === 'complete') { 3177 forceRemeasure(); 3178 } else { 3179 window.addEventListener('load', forceRemeasure); 3180 } 3181 } 3182 3183 } 3184 })(); 3185 //*************** Home, MS and DS listical Page card shuffle on scroll **************************** 3186 3187 //*************** Case study listical Page card shuffle on scroll **************************** 3188 (function () { 3189 var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 3190 if (prefersReducedMotion) return; 3191 3192 var sections = Array.prototype.slice.call(document.querySelectorAll('#case-study-work-results .ub-work')); 3193 if (!sections.length) return; 3194 3195 if (window.innerWidth < 767) { 3196 mobileInit(sections); 3197 } else { 3198 desktopInit(sections); 3199 } 3200 3201 /* ============================================================== 3202 MOBILE â auto-scrolling infinite carousel 3203 ============================================================== */ 3204 function mobileInit(sections) { 3205 sections.forEach(setupMobileCarousel); 3206 } 3207 3208 function setupMobileCarousel(section) { 3209 var cards = Array.prototype.slice.call(section.querySelectorAll('.ub-work__card')); 3210 if (!cards.length) return; 3211 3212 var pin = section.querySelector('.ub-work__pin') || section; 3213 3214 // Clear any stale inline styles from desktop logic 3215 cards.forEach(function (card) { 3216 var bg = card.getAttribute('style') || ''; 3217 card.style.cssText = bg; 3218 }); 3219 3220 // --- Build carousel DOM --- 3221 var viewport = document.createElement('div'); 3222 viewport.className = 'ub-work-mob-viewport'; 3223 3224 var track = document.createElement('div'); 3225 track.className = 'ub-work-mob-track'; 3226 3227 // Move original cards into track 3228 cards.forEach(function (card) { track.appendChild(card); }); 3229 3230 // Clone all cards for seamless infinite loop 3231 cards.forEach(function (card) { 3232 var clone = card.cloneNode(true); 3233 clone.setAttribute('data-clone', 'true'); 3234 clone.setAttribute('aria-hidden', 'true'); 3235 track.appendChild(clone); 3236 }); 3237 3238 viewport.appendChild(track); 3239 pin.appendChild(viewport); 3240 3241 // --- Dots ---
3242 var dotsWrap = document.createElement('div'); 3243 dotsWrap.className = 'ub-work-mob-dots'; 3244 cards.forEach(function (_, i) { 3245 var dot = document.createElement('button'); 3246 dot.className = 'ub-work-mob-dot' + (i === 0 ? ' active' : ''); 3247 dot.setAttribute('aria-label', 'Go to slide ' + (i + 1)); 3248 dot.addEventListener('click', function () { 3249 goTo(i, true); 3250 restartAuto(); 3251 }); 3252 dotsWrap.appendChild(dot); 3253 }); 3254 viewport.after(dotsWrap); 3255 3256 var dots = Array.prototype.slice.call(dotsWrap.querySelectorAll('.ub-work-mob-dot')); 3257 3258 // --- State --- 3259 var current = 0; 3260 var total = cards.length; 3261 var autoTimer = null; 3262 var transitioning = false; 3263 var AUTO_DELAY = 3500; 3264 var TRANS_MS = 700; 3265 3266 function cardStep() { 3267 var c = track.children[0]; 3268 if (!c) return window.innerWidth; 3269 var gap = parseFloat(getComputedStyle(track).gap) || 0; 3270 return c.offsetWidth + gap; 3271 } 3272 3273 function updateDots(activeIndex) { 3274 dots.forEach(function (d, i) { 3275 d.classList.toggle('active', i === activeIndex); 3276 }); 3277 } 3278 3279 function slideTo(index, animated) { 3280 if (animated) { 3281 track.style.transition = 'transform ' + TRANS_MS + 'ms cubic-bezier(.4,0,.2,1)'; 3282 transitioning = true; 3283 } else { 3284 track.style.transition = 'none'; 3285 transitioning = false; 3286 } 3287 track.style.transform = 'translateX(' + (-index * cardStep()) + 'px)'; 3288 updateDots(((index % total) + total) % total); 3289 } 3290 3291 track.addEventListener('transitionend', function () { 3292 transitioning = false; 3293 if (current >= total) { 3294 current = current % total; 3295 slideTo(current, false); 3296 } 3297 }); 3298 3299 function goTo(index, animated) { 3300 if (transitioning) return; 3301 current = index; 3302 slideTo(current, animated); 3303 } 3304 3305 function advance() { 3306 if (transitioning) return; 3307 current++; 3308 slideTo(current, true); 3309 } 3310 3311 function startAuto() { stopAuto(); autoTimer = setInterval(advance, AUTO_DELAY); } 3312 function stopAuto() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } } 3313 function restartAuto() { stopAuto(); startAuto(); } 3314 3315 // --- Touch / swipe --- 3316 var touchStartX = 0; 3317 var touchDiffX = 0; 3318 var swiping = false; 3319 var dragOffset = 0; 3320 3321 viewport.addEventListener('touchstart', function (e) { 3322 if (transitioning) return; 3323 stopAuto(); 3324 touchStartX = e.touches[0].clientX; 3325 touchDiffX = 0; 3326 swiping = false; 3327 track.style.transition = 'none'; 3328 }, { passive: true }); 3329 3330 viewport.addEventListener('touchmove', function (e) { 3331 if (transitioning) return; 3332 touchDiffX = e.touches[0].clientX - touchStartX; 3333 if (!swiping && Math.abs(touchDiffX) > 8) swiping = true; 3334 if (swiping) { 3335 dragOffset = touchDiffX; 3336 track.style.transform = 'translateX(' + (-current * cardStep() + dragOffset) + 'px)'; 3337 } 3338 }, { passive: true }); 3339 3340 viewport.addEventListener('touchend', function () { 3341 if (swiping && !transitioning) { 3342 var threshold = cardStep() * 0.2; 3343 if (dragOffset < -threshold) { 3344 current++; 3345 if (current >= total * 2) current = total; 3346 } else if (dragOffset > threshold && current > 0) { 3347 current--; 3348 } 3349 slideTo(current, true); 3350 } 3351 dragOffset = 0; 3352 swiping = false; 3353 restartAuto(); 3354 }); 3355 3356 // Pause when tab hidden 3357 document.addEventListener('visibilitychange', function () { 3358 if (document.hidden) stopAuto(); else startAuto(); 3359 }); 3360 3361 // Recalc on resize while still mobile 3362 window.addEventListener('resize', function () { 3363 if (window.innerWidth >= 767) return; 3364 slideTo(current, false); 3365 }); 3366 3367 // --- Init --- 3368 slideTo(0, false); 3369 startAuto(); 3370 } 3371 3372 3373 /* ============================================================== 3374 DESKTOP â scroll-driven card-stack + navigation dots 3375 ============================================================== */ 3376 function desktopInit(sections) { 3377 3378 var CARD_START_RATIO = 5 / 4; 3379 var CARD_END_RATIO = 16 / 9; 3380 var OVERLAPPED_SCALE = 0.85; 3381 var ACTIVE_FRACTION = 0.5; 3382 var DOT_SCROLL_DURATION_MS = 600; 3383 3384 var instances = sections.map(function (section) { 3385 var cards = Array.prototype.slice.call(section.querySelectorAll('.ub-work__card')); 3386 cards.forEach(function (card, i) { card.style.setProperty('--card-z', String(i + 1)); }); 3387 return { 3388 section: section, 3389 cards: cards, 3390 cardCount: cards.length, 3391 lastSegment: -1, 3392 dots: null, 3393 activeDot: -1 3394 }; 3395 }); 3396 3397 3398 /* ---------- smooth scroll helper ---------- */ 3399 3400 var activeScrollAnim = null; 3401 3402 function cancelScrollAnim() { 3403 activeScrollAnim = null; 3404 } 3405 3406 function animatedScrollTo(targetY, duration) { 3407 var startY = window.scrollY; 3408 var distance = targetY - startY; 3409 var startTime = null; 3410 var token = {}; 3411 3412 activeScrollAnim = token; 3413 3414 function step(timestamp) { 3415 if (activeScrollAnim !== token) return; 3416 if (startTime === null) startTime = timestamp; 3417 3418 var t = Math.min(1, (timestamp - startTime) / duration); 3419 var eased = 1 - Math.pow(1 - t, 3); 3420 3421 window.scrollTo(0, startY + distance * eased); 3422 3423 if (t < 1) requestAnimationFrame(step); 3424 else activeScrollAnim = null; 3425 } 3426 3427 requestAnimationFrame(step); 3428 } 3429 3430 window.addEventListener('wheel', cancelScrollAnim, { passive: true }); 3431 window.addEventListener('touchstart', cancelScrollAnim, { passive: true }); 3432 3433 3434 /* ---------- navigation dots ---------- */ 3435 3436 function buildDots(instance) { 3437 if (instance.cardCount < 2) return; 3438 3439 var host = instance.section.querySelector('.ub-work__pin') || instance.section; 3440 3441 var wrap = document.createElement('div'); 3442 wrap.className = 'ub-work-dots'; 3443 3444 instance.cards.forEach(function (_, i) { 3445 var dot = document.createElement('button'); 3446 dot.type = 'button'; 3447 dot.className = 'ub-work-dot' + (i === 0 ? ' active' : ''); 3448 dot.setAttribute('aria-label', 'Go to card ' + (i + 1)); 3449 dot.setAttribute('data-active', i === 0 ? 'true' : 'false'); 3450 3451 dot.addEventListener('click', function () { 3452 // updateDots(instance, i + 0.5); 3453 scrollToCard(instance, i); 3454 }); 3455 3456 wrap.appendChild(dot); 3457 }); 3458 3459 host.appendChild(wrap); 3460 instance.dots = Array.prototype.slice.call(wrap.children); 3461 } 3462 3463 function scrollToCard(instance, index) { 3464 var rect = instance.section.getBoundingClientRect(); 3465 var scrollableDistance = rect.height - window.innerHeight; 3466 if (scrollableDistance <= 0) return; 3467 3468 var sectionTop = rect.top + window.scrollY; 3469 3470 // land where the card has fully scaled in but hasn't started handing off
3471 var targetLocal = index + (ACTIVE_FRACTION + 1) / 2; 3472 var targetProgress = Math.min(1, targetLocal / instance.cardCount); 3473 3474 animatedScrollTo(sectionTop + targetProgress * scrollableDistance, DOT_SCROLL_DURATION_MS); 3475 } 3476 3477 function updateDots(instance, segmentPosition) { 3478 if (!instance.dots) return; 3479 3480 var active = Math.min( 3481 instance.cardCount - 1, 3482 Math.max(0, Math.floor(segmentPosition)) 3483 ); 3484 3485 if (active === instance.activeDot) return; 3486 instance.activeDot = active; 3487 3488 instance.dots.forEach(function (dot, i) { 3489 var on = (i === active); 3490 dot.classList.toggle('active', on); 3491 dot.setAttribute('data-active', on ? 'true' : 'false'); 3492 }); 3493 } 3494 3495 3496 /* ---------- card stack ---------- */ 3497 3498 function update(instance) { 3499 var rect = instance.section.getBoundingClientRect(); 3500 var scrollableDistance = rect.height - window.innerHeight; 3501 var overallProgress = scrollableDistance <= 0 3502 ? 0 3503 : Math.min(1, Math.max(0, -rect.top / scrollableDistance)); 3504 3505 var segmentPosition = overallProgress * instance.cardCount; 3506 if (segmentPosition === instance.lastSegment) return; 3507 instance.lastSegment = segmentPosition; 3508 3509 updateDots(instance, segmentPosition); 3510 3511 var currentIndex = Math.min(instance.cardCount - 1, Math.floor(segmentPosition)); 3512 var rawLocalProgress = Math.min(1, segmentPosition - currentIndex); 3513 var localProgress = Math.min(1, rawLocalProgress / ACTIVE_FRACTION); 3514 3515 instance.cards.forEach(function (card, i) { 3516 var offsetY = 0, scale = 1, opacity = 1, ratio, copyOpacity = 0; 3517 3518 if (i > currentIndex) { 3519 ratio = CARD_START_RATIO; 3520 copyOpacity = 0; 3521 offsetY = window.innerHeight; 3522 } else if (i === currentIndex) { 3523 ratio = CARD_START_RATIO + (CARD_END_RATIO - CARD_START_RATIO) * localProgress; 3524 copyOpacity = localProgress; 3525 if (i > 0) offsetY = window.innerHeight * (1 - localProgress); 3526 } else if (i === currentIndex - 1) { 3527 ratio = CARD_END_RATIO; 3528 scale = 1 - (1 - OVERLAPPED_SCALE) * localProgress; 3529 opacity = 1 - localProgress; 3530 copyOpacity = opacity; 3531 } else { 3532 ratio = CARD_END_RATIO; 3533 scale = OVERLAPPED_SCALE; 3534 opacity = 0; 3535 copyOpacity = 0; 3536 } 3537 3538 card.style.transform = 3539 'translate(-50%, calc(-50% + ' + offsetY + 'px)) scale(' + scale + ')'; 3540 card.style.aspectRatio = String(ratio); 3541 card.style.opacity = String(opacity); 3542 card.style.pointerEvents = (i === currentIndex && localProgress >= 1) ? 'auto' : 'none'; 3543 var cardCopy = card.querySelector('.ub-work__card-copy'); 3544 if (cardCopy) cardCopy.style.opacity = String(copyOpacity); 3545 }); 3546 3547 instance.section.style.setProperty('--progress', String(overallProgress)); 3548 } 3549 3550 function init(instance) { 3551 instance.section.style.setProperty('--segment-count', String(instance.cardCount)); 3552 } 3553 3554 function forceRemeasure() { 3555 instances.forEach(function (instance) { 3556 instance.lastSegment = -1; 3557 instance.activeDot = -1; 3558 }); 3559 instances.forEach(update); 3560 } 3561 3562 var ticking = false; 3563 function onScrollOrResize() { 3564 if (ticking) return; 3565 ticking = true; 3566 requestAnimationFrame(function () { 3567 instances.forEach(update); 3568 ticking = false; 3569 }); 3570 } 3571 3572 window.addEventListener('scroll', onScrollOrResize, { passive: true }); 3573 window.addEventListener('resize', forceRemeasure); 3574 3575 instances.forEach(buildDots); 3576 instances.forEach(init); 3577 instances.forEach(update); 3578 } 3579 3580 })(); 3581 //*************** Case study listical Page card shuffle on scroll **************************** 3582 3583}) 3584//*************** card shuffle on scroll ****************************
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.