1let currentScroll = 0; 2let isScrollingDown = true; 3function initMarqueeAnimation() { 4 if (!document.querySelector('.marquee__part')) return; 5 let tween = gsap.to(".marquee__part", { 6 xPercent: -100, 7 repeat: -1, 8 duration: 10, 9 ease: "linear" 10 }).totalProgress(0.5); 11 gsap.set(".marquee__inner", { xPercent: -50 }); 12 window.addEventListener("scroll", function () { 13 if (window.pageYOffset > currentScroll) { 14 isScrollingDown = true; 15 } else { 16 isScrollingDown = false; 17 } 18 gsap.to(tween, { 19 timeScale: isScrollingDown ? 1 : -1, 20 duration: 0.3, 21 ease: "power2.out" 22 }); 23 currentScroll = window.pageYOffset; 24 }); 25} 26const VIDEO_SECTION_SELECTOR = '.video-testimonials'; 27const CLIP_REVEAL_DURATION = 0.9; 28const CARD_STAGGER = 0.1; 29let videoTestimonialsAnimationsInitialized = false; 30const hasScrollTrigger = typeof ScrollTrigger !== 'undefined'; 31if (hasScrollTrigger) { 32 gsap.registerPlugin(ScrollTrigger); 33} 34const reduceMotionMediaQuery = typeof window !== 'undefined' && typeof window.matchMedia === 'function' 35 ? window.matchMedia('(prefers-reduced-motion: reduce)') 36 : null; 37function initVideoTestimonialsAnimations() { 38 if (videoTestimonialsAnimationsInitialized) return; 39 const sections = document.querySelectorAll(VIDEO_SECTION_SELECTOR + ', .moments-section, .arc-slider-stage'); 40 if (!sections.length || typeof gsap === 'undefined') return; 41 videoTestimonialsAnimationsInitialized = true; 42 sections.forEach(section => { 43 setupVideoCardsReveal(section); 44 setupMobileSlideAnimations(section); 45 setupTextHideOnPlay(section); 46 }); 47 setupFeatureCardsReveal(); 48} 49function setupVideoCardsReveal(section) { 50 const desktopCards = section.querySelectorAll('.cards.desktop-only .video-card, .cards.desktop-only .card'); 51 const mobileCards = section.querySelectorAll('.video-testimonials-swiper.mobile-only .video-card, .video-testimonials-swiper.mobile-only .card'); 52 const allCards = [...desktopCards, ...mobileCards]; 53 if (allCards.length > 0) { 54 allCards.forEach(card => { 55 card.classList.add('video-reveal-animation', 'card-clip-reveal'); 56 }); 57 } 58} 59function setupFeatureCardsReveal() { 60 const featureCards = document.querySelectorAll('.features-cards-slider .feature-card'); 61 if (featureCards.length > 0) { 62 featureCards.forEach(card => { 63 const isInWhatWeDoSection = card.closest('.what-we-do-section'); 64 if (isInWhatWeDoSection) { 65 card.classList.add('feature-reveal-animation'); 66 } else { 67 card.classList.add('feature-reveal-animation', 'card-clip-reveal'); 68 } 69 }); 70 } 71} 72function setupMobileSlideAnimations(section) { 73 let sliderEl = section.querySelector('.video-testimonials-swiper .swiper'); 74 if (!sliderEl) return; 75 const cardContents = sliderEl.querySelectorAll('.video-card-content, .card-content'); 76 cardContents.forEach(content => { 77 gsap.set(content, { y: 70, opacity: 0 }); 78 }); 79 const animateActiveSlide = () => { 80 const activeSlide = sliderEl.querySelector('.swiper-slide-active'); 81 if (!activeSlide) return; 82 const cardContent = activeSlide.querySelector('.moments-card .video-card-content, .moments-card .card-content, .video-card-content, .card-content'); 83 if (!cardContent) return; 84 gsap.to(cardContent, { 85 y: 0, 86 opacity: 1, 87 duration: 0.4, 88 ease: 'back.out(1.7)' 89 }); 90 }; 91 const hideContent = content => { 92 if (!content) return; 93 gsap.to(content, { 94 y: 40, 95 opacity: 0, 96 duration: 0.35, 97 ease: 'power2.inOut' 98 }); 99 }; 100 let currentSlideContent = null; 101 const attachToSwiper = () => { 102 const swiperInstance = sliderEl.swiper; 103 if (!swiperInstance) return false; 104 const setCurrentContent = () => { 105 const activeSlide = sliderEl.querySelector('.swiper-slide-active'); 106 if (!activeSlide) { 107 currentSlideContent = null; 108 return; 109 } 110 currentSlideContent = activeSlide.querySelector('.moments-card .video-card-content, .moments-card .card-content, .video-card-content, .card-content') || null; 111 }; 112 setCurrentContent(); 113 if (currentSlideContent) { 114 gsap.set(currentSlideContent, { y: 0, opacity: 1 }); 115 } 116 swiperInstance.on('slideChangeTransitionStart', () => { 117 hideContent(currentSlideContent); 118 });
119 swiperInstance.on('slideChangeTransitionEnd', () => { 120 setCurrentContent(); 121 animateActiveSlide(); 122 }); 123 setCurrentContent(); 124 animateActiveSlide(); 125 return true; 126 }; 127 if (!attachToSwiper()) { 128 const poll = setInterval(() => { 129 if (attachToSwiper()) { 130 clearInterval(poll); 131 } 132 }, 200); 133 } else { 134 const activeSlide = sliderEl.querySelector('.swiper-slide-active'); 135 if (activeSlide) { 136 const cardContent = activeSlide.querySelector('.moments-card .video-card-content, .moments-card .card-content, .video-card-content, .card-content'); 137 if (cardContent) { 138 gsap.set(cardContent, { y: 0, opacity: 1 }); 139 } 140 } 141 } 142} 143function setupTextHideOnPlay(section) { 144 const buttons = section.querySelectorAll('.video-card-play-button, .play-button'); 145 if (!buttons.length) return; 146 buttons.forEach(button => { 147 const card = button.closest('.video-card, .card'); 148 const cardInfo = card?.querySelector('.video-card-info, .card-info'); 149 if (cardInfo) { 150 gsap.set(cardInfo, { x: 0, opacity: 1 }); 151 } 152 }); 153 const observer = new MutationObserver(mutations => { 154 mutations.forEach(mutation => { 155 if (mutation.type !== 'attributes' || mutation.attributeName !== 'class') return; 156 const button = mutation.target; 157 const card = button.closest('.video-card, .card'); 158 const cardInfo = card?.querySelector('.video-card-info, .card-info'); 159 if (!cardInfo) return; 160 const isActive = button.classList.contains('is-active'); 161 gsap.to(cardInfo, { 162 x: isActive ? -40 : 0, 163 opacity: isActive ? 0 : 1, 164 duration: 0.35, 165 ease: 'power2.out' 166 }); 167 }); 168 }); 169 buttons.forEach(button => { 170 observer.observe(button, { attributes: true, attributeFilter: ['class'] }); 171 }); 172} 173function initFeaturedCardsScrollEffect() { 174 const featuresSlider = document.querySelector('.features-cards-slider'); 175 if (!featuresSlider) return; 176 const cards = featuresSlider.querySelectorAll('.feature-card'); 177 if (!cards.length) return; 178 cards.forEach((card, index) => { 179 gsap.timeline({ 180 scrollTrigger: { 181 trigger: card, 182 start: 'top bottom', 183 end: 'bottom top', 184 scrub: 1, 185 onUpdate: (self) => { 186 const progress = self.progress; 187 const moveAmount = (progress - 0.5) * 20; 188 gsap.set(card, { 189 y: moveAmount, 190 scale: 1 - Math.abs(progress - 0.5) * 0.05, 191 }); 192 } 193 } 194 }); 195 }); 196 //console.log(`Added scroll effect to ${cards.length} feature cards`); 197} 198function initFooterBrandReveal() { 199 if (typeof gsap === 'undefined' || !hasScrollTrigger) { 200 return; 201 } 202 const prefersReducedMotion = reduceMotionMediaQuery ? reduceMotionMediaQuery.matches : false; 203 const innerItems = Array.from(document.querySelectorAll('.footer__brand-inner')); 204 const outerItems = Array.from(document.querySelectorAll('.footer__brand')) 205 .filter((item) => !item.querySelector('.footer__brand-inner')); 206 const brandItems = [...innerItems, ...outerItems]; 207 if (!brandItems.length) { 208 return; 209 } 210 brandItems.forEach((item) => { 211 if (item.dataset.footerBrandAnimated === 'true') { 212 return; 213 } 214 const mediaEl = item.querySelector('object, svg, img'); 215 if (mediaEl) { 216 item.dataset.footerBrandAnimated = 'true'; 217 const isMobile = window.innerWidth <= 768; 218 if (!isMobile) { 219 gsap.set(item, { overflow: 'hidden' }); 220 } 221 if (prefersReducedMotion) { 222 gsap.set(mediaEl, { yPercent: 0, clearProps: 'transform' }); 223 return; 224 } 225 const mobileStart = 100; 226 const mobileEnd = 0; 227 const desktopStart = 120; 228 const desktopEnd = 0; 229 const initialYPercent = isMobile ? mobileStart : desktopStart; 230 const finalYPercent = isMobile ? mobileEnd : desktopEnd; 231 gsap.set(mediaEl, { yPercent: initialYPercent }); 232 const mobileScrollStart = 'top 90%'; 233 const mobileScrollEnd = 'top 40%'; 234 const desktopScrollStart = 'top 90%'; 235 const desktopScrollEnd = 'top 30%'; 236 const scrollStart = isMobile ? mobileScrollStart : desktopScrollStart; 237 const scrollEnd = isMobile ? mobileScrollEnd : desktopScrollEnd; 238 gsap.timeline({ 239 scrollTrigger: { 240 trigger: item, 241 start: scrollStart, 242 end: scrollEnd, 243 scrub: true 244 } 245 }).to(mediaEl, { 246 yPercent: finalYPercent, 247 duration: 2.6, 248 ease: 'back.out(1)' 249 }); 250 return; 251 } 252 let textSpan = item.querySelector('.footer__brand-text'); 253 if (!textSpan) { 254 const textContent = item.textContent; 255 if (!textContent || !textContent.trim()) { 256 return; 257 } 258 item.textContent = ''; 259 textSpan = document.createElement('span'); 260 textSpan.className = 'footer__brand-text'; 261 textSpan.textContent = textContent; 262 item.appendChild(textSpan); 263 } 264 if (textSpan.dataset.split !== 'true') { 265 const text = textSpan.textContent || ''; 266 textSpan.textContent = ''; 267 const fragment = document.createDocumentFragment(); 268 Array.from(text).forEach((char) => { 269 const wrap = document.createElement('span'); 270 wrap.className = 'footer__brand-char-wrap'; 271 const inner = document.createElement('span'); 272 inner.className = 'footer__brand-char'; 273 inner.textContent = char; 274 wrap.appendChild(inner); 275 fragment.appendChild(wrap); 276 }); 277 textSpan.appendChild(fragment); 278 textSpan.dataset.split = 'true'; 279 } 280 const chars = textSpan.querySelectorAll('.footer__brand-char'); 281 item.dataset.footerBrandAnimated = 'true'; 282 if (prefersReducedMotion) { 283 gsap.set(chars, { yPercent: 0, clearProps: 'transform' }); 284 return; 285 } 286 const isMobile = window.innerWidth <= 768; 287 const initialYPercent = isMobile ? 80 : 120; 288 gsap.set(chars, { yPercent: initialYPercent }); 289 gsap.timeline({ 290 scrollTrigger: { 291 trigger: item, 292 start: 'top 90%', 293 end: 'top 30%', 294 scrub: true 295 } 296 }).to(chars, { 297 yPercent: 0, 298 duration: 2.6, 299 stagger: 0, 300 ease: 'back.out(1)' 301 });
302 }); 303} 304function initDeviderLineAnimation() { 305 if (typeof gsap === 'undefined') { 306 return; 307 } 308 const sections = document.querySelectorAll('.devider'); 309 if (!sections.length) { 310 return; 311 } 312 sections.forEach((section, index) => { 313 if (section.dataset.deviderAnimated === 'true') { 314 return; 315 } 316 const svg = section.querySelector('svg'); 317 const clipPath = svg ? svg.querySelector('clipPath') : null; 318 const revealRect = svg ? svg.querySelector('.devider__reveal') : null; 319 const clipTarget = svg ? svg.querySelector('[clip-path]') : null; 320 if (!svg || !clipPath || !revealRect || !clipTarget) { 321 return; 322 } 323 section.dataset.deviderAnimated = 'true'; 324 const viewBox = svg.viewBox && svg.viewBox.baseVal; 325 const maxWidth = viewBox && viewBox.width ? viewBox.width : 0; 326 const maxHeight = viewBox && viewBox.height ? viewBox.height : 0; 327 const targetWidth = maxWidth || 1239; 328 const targetHeight = maxHeight || 22; 329 const clipId = `devider-clip-${index}`; 330 clipPath.id = clipId; 331 clipTarget.setAttribute('clip-path', `url(#${clipId})`); 332 if (typeof ScrollTrigger === 'undefined') { 333 revealRect.setAttribute('width', targetWidth); 334 revealRect.setAttribute('height', targetHeight); 335 return; 336 } 337 gsap.registerPlugin(ScrollTrigger); 338 gsap.set(revealRect, { 339 attr: { 340 width: 0, 341 height: targetHeight 342 } 343 }); 344 gsap.to(revealRect, { 345 attr: { 346 width: targetWidth 347 }, 348 ease: 'none', 349 scrollTrigger: { 350 trigger: section, 351 start: 'top bottom', 352 end: 'bottom 30%', 353 scrub: true 354 } 355 }); 356 }); 357} 358function initAboutSeparatorAnimation() { 359 if (typeof gsap === 'undefined' || !hasScrollTrigger) { 360 return; 361 } 362 const separators = document.querySelectorAll('.about-glenaeon-section__separator'); 363 if (!separators.length) return; 364 separators.forEach((separator, index) => { 365 const separatorSvg = separator.querySelector('svg'); 366 const revealRect = separator.querySelector('.about-glenaeon-section__separator-reveal'); 367 if (!separatorSvg || !revealRect) return; 368 const clipPath = separatorSvg.querySelector('clipPath'); 369 const clipTarget = separatorSvg.querySelector('[clip-path]'); 370 if (clipPath && clipTarget) { 371 const clipId = `about-separator-clip-${index}`; 372 clipPath.id = clipId; 373 clipTarget.setAttribute('clip-path', `url(#${clipId})`); 374 } 375 const viewBox = separatorSvg.viewBox && separatorSvg.viewBox.baseVal; 376 const maxWidth = viewBox && viewBox.width ? viewBox.width : 0;
377 const maxHeight = viewBox && viewBox.height ? viewBox.height : 0; 378 gsap.set(revealRect, { 379 attr: { 380 width: 0, 381 height: maxHeight || 22 382 } 383 }); 384 gsap.to(revealRect, { 385 attr: { 386 width: maxWidth || 1239 387 }, 388 ease: 'none', 389 scrollTrigger: { 390 trigger: separator, 391 start: 'top bottom', 392 end: 'bottom 30%', 393 scrub: true 394 } 395 }); 396 }); 397} 398function initAnimations() { 399 if (typeof gsap === 'undefined') { 400 return; 401 } 402 initMarqueeAnimation(); 403 initVideoTestimonialsAnimations(); 404 initRevealAnimations(); 405 initFooterBrandReveal(); 406 initDeviderLineAnimation(); 407 initAboutSeparatorAnimation(); 408 if (typeof ScrollTrigger !== 'undefined') { 409 requestAnimationFrame(() => { 410 ScrollTrigger.refresh(); 411 }); 412 } 413} 414function initRevealAnimations() { 415 const videoItems = document.querySelectorAll('.video-reveal-animation'); 416 const featureItems = document.querySelectorAll('.feature-reveal-animation'); 417 const allAnimationItems = [...videoItems, ...featureItems]; 418 if (!allAnimationItems.length) return; 419 const clipItems = document.querySelectorAll('.card-clip-reveal'); 420 if (clipItems.length > 0) { 421 gsap.set('.card-clip-reveal', { 422 '--clip-value': '100%', 423 }); 424 } 425 function animation_def({ card, ease_default = 'power1.out', index = 0, is_static = false } = {}) { 426 if (!is_static) { 427 gsap.to(card, { 428 duration: 0.7, ease: ease_default, x: 0, y: 0, delay: index * 0.1 429 }); 430 } 431 gsap.to(card, { 432 duration: 0.5, ease: ease_default, autoAlpha: 1, delay: index * 0.1 + 0.1 433 }); 434 } 435 function animateRevealElements(elements) { 436 elements.forEach((card, index) => { 437 if (card.classList.contains('card-clip-reveal')) { 438 gsap.fromTo(card, { 439 '--clip-value': '100%', 440 }, { 441 duration: 1.1, 442 ease: 'power3.out', 443 '--clip-value': '0%', 444 delay: index * 0.2, 445 onStart: function () { 446 card.classList.add('animation-started'); 447 }, 448 onComplete: function () { 449 card.classList.add('clip-animation-complete', 'animation-finished'); 450 } 451 }); 452 } 453 }); 454 } 455 ScrollTrigger.batch('.video-reveal-animation, .feature-reveal-animation', { 456 start: 'top 70%', 457 once: true, 458 onEnter: elements => { 459 animateRevealElements(elements); 460 } 461 }); 462} 463if (typeof ScrollTrigger !== 'undefined') { 464 window.addEventListener('resize', () => { 465 }); 466 document.addEventListener('visibilitychange', () => { 467 if (!document.hidden) { 468 requestAnimationFrame(() => { 469 }); 470 } 471 }); 472} 473document.addEventListener('DOMContentLoaded', initAnimations); 474if (document.readyState === 'loading') { 475 document.addEventListener('DOMContentLoaded', initAnimations); 476} else { 477 initAnimations(); 478} 479class GSAPAnimations { 480 constructor() { 481 this.defaults = { 482 delay: 0, 483 duration: 1.25, 484 start: 'top 80%', 485 ease: { 486 fade: 'power2.out', 487 zoom: 'power2.out', 488 zoomIn: 'back.out(1.05)', 489 slide: 'power2.out', 490 clip: 'power3.out', 491 chars: 'expo.out' 492 } 493 }; 494 this.init(); 495 } 496 init() { 497 if (typeof gsap === 'undefined' || !gsap.registerPlugin) { 498 console.warn('GSAP not loaded'); 499 return; 500 } 501 gsap.registerPlugin(ScrollTrigger); 502 this.setupAnimations(); 503 } 504 setupAnimations() { 505 const elements = document.querySelectorAll('[data-gsap]'); 506 const isMobile = window.matchMedia('(max-width: 991px)').matches; 507 elements.forEach(el => { 508 const mobileAnimation = el.getAttribute('data-gsap-mobile'); 509 const desktopAnimation = el.getAttribute('data-gsap'); 510 const animationType = (isMobile && mobileAnimation) ? mobileAnimation : desktopAnimation; 511 const staggerAttr = el.getAttribute('data-gsap-stagger'); 512 const mobileDelayAttr = el.getAttribute('data-gsap-mobile-delay'); 513 const mobileStartAttr = el.getAttribute('data-gsap-mobile-start'); 514 const mobileEndAttr = el.getAttribute('data-gsap-mobile-end'); 515 const desktopDelay = parseFloat(el.getAttribute('data-gsap-delay')) || this.defaults.delay; 516 const config = { 517 delay: isMobile && mobileDelayAttr !== null ? parseFloat(mobileDelayAttr) : desktopDelay, 518 duration: parseFloat(el.getAttribute('data-gsap-duration')) || this.defaults.duration, 519 stagger: staggerAttr ? parseFloat(staggerAttr) : null, 520 start: isMobile && mobileStartAttr !== null ? mobileStartAttr : (el.getAttribute('data-gsap-start') || this.defaults.start), 521 end: isMobile && mobileEndAttr !== null ? mobileEndAttr : (el.getAttribute('data-gsap-end') || null), 522 cardsStart: el.getAttribute('data-gsap-cards-start') || null, 523 mobileCardsStart: el.getAttribute('data-gsap-mobile-cards-start') || null, 524 ease: el.getAttribute('data-gsap-ease') || null 525 };
526 try { 527 switch (animationType) { 528 case 'none': 529 break; 530 case 'fade-up': 531 this.fadeUp(el, config); 532 break; 533 case 'fade-up-children': 534 this.fadeUpChildren(el, config); 535 break; 536 case 'fade-down': 537 this.fadeDown(el, config); 538 break; 539 case 'fade-left': 540 this.fadeLeft(el, config); 541 break; 542 case 'fade-right': 543 this.fadeRight(el, config); 544 break; 545 case 'fade-in': 546 this.fadeIn(el, config); 547 break; 548 case 'scroll-fade-stagger': 549 this.scrollFadeStagger(el, config); 550 break; 551 case 'zoom-in': 552 this.zoomIn(el, config); 553 break; 554 case 'zoom-out': 555 this.zoomOut(el, config); 556 break; 557 case 'clip-reveal': 558 this.clipReveal(el, config); 559 break; 560 case 'clip-reveal-up': 561 this.clipRevealUp(el, config); 562 break; 563 case 'clip-reveal-center': 564 this.clipRevealCenter(el, config); 565 break; 566 case 'writing-text': 567 this.writingText(el, config); 568 break; 569 case 'chars': 570 this.charsAnimation(el, config); 571 break; 572 case 'lines': 573 this.linesAnimation(el, config); 574 break; 575 case 'slide-left': 576 this.slideLeft(el, config); 577 break; 578 case 'slide-right': 579 this.slideRight(el, config); 580 break; 581 case 'slide-up': 582 this.slideUp(el, config); 583 break; 584 case 'slide-down': 585 this.slideDown(el, config); 586 break; 587 case 'scale-up': 588 this.scaleUp(el, config); 589 break; 590 case 'what-we-do-header': 591 this.whatWeDoHeader(el, config); 592 break; 593 case 'what-we-do-cards': 594 this.whatWeDoCards(el, config); 595 break; 596 case 'core-values': 597 this.coreValues(el, config); 598 break; 599 case 'core-values-timeline': 600 this.coreValuesTimeline(el, config); 601 break; 602 case 'hero-about': 603 this.heroAbout(el, config); 604 break; 605 case 'hero-home': 606 this.heroHome(el, config); 607 break; 608 case 'section-title': 609 this.sectionTitle(el, config); 610 break; 611 case 'tilt-card': 612 this.tiltCard(el, config); 613 break; 614 case 'hero-learning': 615 this.heroLearning(el, config); 616 break; 617 case 'hero-preschool': 618 this.heroPreschool(el, config); 619 break; 620 case 'hero-wellbeing': 621 this.heroWellbeing(el, config); 622 break; 623 case 'masked-image-content': 624 this.maskedImageContent(el, config); 625 break; 626 case 'three-campuses': 627 this.threeCampuses(el, config); 628 break; 629 case 'news-card': 630 this.newsCard(el, config); 631 break; 632 case 'news-cards-clip': 633 this.newsCardsClip(el, config); 634 break; 635 case 'btn-clip-reveal': 636 this.btnClipReveal(el, config); 637 break; 638 case 'wellbeing-timeline': 639 this.wellbeingTimeline(el, config); 640 break; 641 case 'three-stages-timeline': 642 this.threeStagesTimeline(el, config); 643 break; 644 case 'stagger-content': 645 this.staggerContent(el, config); 646 break; 647 case 'scroll-lines': 648 this.scrollLines(el, config); 649 break; 650 case 'animate-hero': 651 this.animateHero(el, config); 652 break; 653 case 'scroll-text-reveal': 654 this.scrollTextReveal(el, config); 655 break; 656 default: 657 console.warn(`Unknown animation type: ${animationType}`); 658 } 659 } catch (error) { 660 console.error(`Error initializing animation "${animationType}" on element:`, el, error); 661 } 662 });
663 } 664 buildScrollTrigger(el, config) { 665 return { 666 trigger: el, 667 start: config.start, 668 toggleActions: 'play none none none' 669 }; 670 } 671 staggerContent(el, config) { 672 if (!el) return; 673 const paragraphs = el.querySelectorAll('p'); 674 if (!paragraphs.length) return; 675 const stagger = config.stagger || 0.15; 676 const duration = config.duration || 0.8; 677 const delay = config.delay || 0; 678 gsap.set(paragraphs, { 679 opacity: 0, 680 y: 30 681 }); 682 gsap.to(paragraphs, { 683 opacity: 1, 684 y: 0, 685 duration: duration, 686 stagger: stagger, 687 delay: delay, 688 ease: 'power2.out', 689 scrollTrigger: { 690 trigger: el, 691 start: config.start || 'top 80%', 692 toggleActions: 'play none none none' 693 } 694 }); 695 } 696 scrollLines(el, config) { 697 if (!el) return; 698 const paragraphs = el.tagName === 'P' ? [el] : el.querySelectorAll('p'); 699 if (!paragraphs.length) return; 700 const allLines = []; 701 paragraphs.forEach(p => { 702 const lines = this.splitLines(p); 703 allLines.push(...lines); 704 }); 705 if (!allLines.length) return; 706 gsap.set(allLines, { 707 opacity: 0, 708 y: 25 709 }); 710 const tl = gsap.timeline({ 711 scrollTrigger: { 712 trigger: el, 713 start: config.start || 'top 80%', 714 end: 'bottom 60%', 715 scrub: 1, 716 once: true 717 } 718 }); 719 allLines.forEach((line, index) => { 720 const start = index / allLines.length; 721 const duration = 1 / allLines.length; 722 tl.to(line, { 723 opacity: 1, 724 y: 0, 725 duration: duration, 726 ease: 'none' 727 }, start); 728 }); 729 } 730 scrollTextReveal(el, config) { 731 if (!el) return; 732 const words = this.splitWords(el); 733 if (!words.length) return; 734 gsap.from(words, { 735 opacity: 0, 736 ease: 'none', 737 stagger: 0.5, 738 scrollTrigger: { 739 trigger: el, 740 start: config.start || 'top 80%', 741 end: 'bottom 20%', 742 scrub: true 743 } 744 }); 745 } 746 splitWords(el) { 747 if (!el) return []; 748 if (el.dataset.wordsSplit === 'true') { 749 return Array.from(el.querySelectorAll('.scroll-word')); 750 } 751 const text = el.textContent; 752 if (!text.trim()) return []; 753 el.innerHTML = ''; 754 const words = text.split(/(\s+)/); 755 const wordElements = []; 756 words.forEach(word => { 757 if (word.trim()) { 758 const span = document.createElement('span'); 759 span.className = 'scroll-word'; 760 span.textContent = word; 761 el.appendChild(span); 762 wordElements.push(span); 763 } else if (word) { 764 el.appendChild(document.createTextNode(word)); 765 } 766 }); 767 el.dataset.wordsSplit = 'true'; 768 return wordElements; 769 } 770 fadeUp(el, config) { 771 const children = el.children.length > 0 ? Array.from(el.children) : null; 772 const target = children && config.stagger ? children : el; 773 gsap.set(target, { y: 50, autoAlpha: 0 }); 774 gsap.to(target, 775 { 776 y: 0, 777 autoAlpha: 1, 778 duration: config.duration, 779 ease: config.ease || this.defaults.ease.fade, 780 delay: config.delay, 781 stagger: children && config.stagger ? config.stagger : 0, 782 scrollTrigger: this.buildScrollTrigger(el, config) 783 } 784 ); 785 } 786 fadeUpChildren(el, config) { 787 if (!el) return; 788 const richTextSelectors = 'p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, figure, table'; 789 const children = Array.from(el.querySelectorAll(richTextSelectors)); 790 if (!children.length) { 791 this.fadeUp(el, config); 792 return; 793 } 794 const duration = config.duration || 0.7; 795 const ease = config.ease || this.defaults.ease.fade; 796 gsap.set(children, { y: 30, autoAlpha: 0 }); 797 ScrollTrigger.batch(children, { 798 start: config.start || 'top bottom-=80', 799 once: true, 800 onEnter: batch => { 801 batch.forEach((child, index) => { 802 gsap.to(child, { 803 y: 0, 804 autoAlpha: 1, 805 duration, 806 ease, 807 delay: index * 0.1 808 }); 809 }); 810 } 811 });
812 } 813 fadeDown(el, config) { 814 const children = el.children.length > 0 ? Array.from(el.children) : null; 815 const target = children && config.stagger ? children : el; 816 gsap.set(target, { y: -50, autoAlpha: 0 }); 817 gsap.to(target, 818 { 819 y: 0, 820 autoAlpha: 1, 821 duration: config.duration, 822 ease: config.ease || this.defaults.ease.fade, 823 delay: config.delay, 824 stagger: children && config.stagger ? config.stagger : 0, 825 scrollTrigger: this.buildScrollTrigger(el, config) 826 } 827 ); 828 } 829 fadeLeft(el, config) { 830 const children = el.children.length > 0 ? Array.from(el.children) : null; 831 const target = children && config.stagger ? children : el; 832 gsap.set(target, { x: -50, autoAlpha: 0 }); 833 gsap.to(target, 834 { 835 x: 0, 836 autoAlpha: 1, 837 duration: config.duration, 838 ease: config.ease || this.defaults.ease.fade, 839 delay: config.delay, 840 stagger: children && config.stagger ? config.stagger : 0, 841 scrollTrigger: this.buildScrollTrigger(el, config) 842 } 843 ); 844 } 845 fadeRight(el, config) { 846 const children = el.children.length > 0 ? Array.from(el.children) : null; 847 const target = children && config.stagger ? children : el; 848 gsap.set(target, { x: 50, autoAlpha: 0 }); 849 gsap.to(target, 850 { 851 x: 0, 852 autoAlpha: 1, 853 duration: config.duration, 854 ease: config.ease || this.defaults.ease.fade, 855 delay: config.delay, 856 stagger: children && config.stagger ? config.stagger : 0, 857 scrollTrigger: this.buildScrollTrigger(el, config) 858 } 859 ); 860 } 861 fadeIn(el, config) { 862 const children = el.children.length > 0 ? Array.from(el.children) : null; 863 const target = children && config.stagger ? children : el; 864 gsap.set(target, { autoAlpha: 0 }); 865 gsap.to(target, 866 { 867 autoAlpha: 1, 868 duration: config.duration, 869 ease: config.ease || this.defaults.ease.fade, 870 delay: config.delay, 871 stagger: children && config.stagger ? config.stagger : 0, 872 scrollTrigger: this.buildScrollTrigger(el, config) 873 } 874 ); 875 } 876 scrollFadeStagger(el, config) { 877 if (!el) return; 878 const items = el.children.length > 0 ? Array.from(el.children) : [el]; 879 if (!items.length) return; 880 const stagger = Number.isFinite(config.stagger) ? config.stagger : 0.15; 881 const duration = Number.isFinite(config.duration) ? config.duration : 0.8; 882 gsap.set(items, { 883 autoAlpha: 0 884 }); 885 gsap.to(items, { 886 autoAlpha: 1, 887 duration, 888 stagger, 889 ease: config.ease || 'none', 890 delay: config.delay, 891 scrollTrigger: { 892 trigger: el, 893 start: config.start || 'top 80%', 894 end: config.end || 'bottom 70%', 895 scrub: 1, 896 once: true 897 } 898 }); 899 } 900 zoomIn(el, config) { 901 const children = el.children.length > 0 ? Array.from(el.children) : null; 902 const target = children && config.stagger ? children : el; 903 gsap.set(target, { scale: 0.7, autoAlpha: 0 }); 904 gsap.to(target, 905 { 906 scale: 1, 907 autoAlpha: 1, 908 duration: 1.9, 909 ease: config.ease || this.defaults.ease.zoomIn, 910 delay: config.delay, 911 stagger: children && config.stagger ? config.stagger : 0, 912 scrollTrigger: this.buildScrollTrigger(el, config) 913 } 914 ); 915 } 916 zoomOut(el, config) { 917 const children = el.children.length > 0 ? Array.from(el.children) : null; 918 const target = children && config.stagger ? children : el; 919 gsap.set(target, { scale: 1.3 }); 920 gsap.to(target, 921 { 922 scale: 1, 923 duration: config.duration, 924 ease: config.ease || this.defaults.ease.zoom, 925 delay: config.delay, 926 stagger: children && config.stagger ? config.stagger : 0, 927 scrollTrigger: this.buildScrollTrigger(el, config) 928 } 929 ); 930 } 931 slideLeft(el, config) { 932 const children = el.children.length > 0 ? Array.from(el.children) : null; 933 const target = children && config.stagger ? children : el; 934 gsap.set(target, { x: -200, autoAlpha: 0 }); 935 gsap.to(target, 936 { 937 x: 0, 938 autoAlpha: 1, 939 duration: config.duration, 940 ease: config.ease || this.defaults.ease.slide, 941 delay: config.delay, 942 stagger: children && config.stagger ? config.stagger : 0, 943 scrollTrigger: this.buildScrollTrigger(el, config) 944 } 945 ); 946 } 947 slideRight(el, config) { 948 const children = el.children.length > 0 ? Array.from(el.children) : null; 949 const target = children && config.stagger ? children : el; 950 gsap.set(target, { x: 200, autoAlpha: 0 }); 951 gsap.to(target, 952 { 953 x: 0, 954 autoAlpha: 1, 955 duration: config.duration, 956 ease: config.ease || this.defaults.ease.slide, 957 delay: config.delay, 958 stagger: children && config.stagger ? config.stagger : 0, 959 scrollTrigger: this.buildScrollTrigger(el, config) 960 } 961 ); 962 } 963 slideUp(el, config) { 964 const children = el.children.length > 0 ? Array.from(el.children) : null; 965 const target = children && config.stagger ? children : el; 966 gsap.set(target, { y: 100, autoAlpha: 0 }); 967 gsap.to(target, 968 { 969 y: 0, 970 autoAlpha: 1, 971 duration: config.duration, 972 ease: config.ease || this.defaults.ease.slide, 973 delay: config.delay, 974 stagger: children && config.stagger ? config.stagger : 0, 975 scrollTrigger: this.buildScrollTrigger(el, config) 976 } 977 ); 978 } 979 slideDown(el, config) { 980 const children = el.children.length > 0 ? Array.from(el.children) : null; 981 const target = children && config.stagger ? children : el; 982 gsap.set(target, { y: -100, autoAlpha: 0 }); 983 gsap.to(target, 984 { 985 y: 0, 986 autoAlpha: 1, 987 duration: config.duration, 988 ease: config.ease || this.defaults.ease.slide, 989 delay: config.delay, 990 stagger: children && config.stagger ? config.stagger : 0, 991 scrollTrigger: this.buildScrollTrigger(el, config) 992 } 993 ); 994 } 995 scaleUp(el, config) { 996 const children = el.children.length > 0 ? Array.from(el.children) : null; 997 const target = children && config.stagger ? children : el; 998 gsap.set(target, { scale: 0, transformOrigin: 'center center' }); 999 gsap.to(target, 1000 { 1001 scale: 1, 1002 duration: config.duration, 1003 ease: config.ease || this.defaults.ease.zoom,
1004 delay: config.delay, 1005 stagger: children && config.stagger ? config.stagger : 0, 1006 scrollTrigger: this.buildScrollTrigger(el, config) 1007 } 1008 ); 1009 } 1010 clipReveal(el, config) { 1011 const children = el.children.length > 0 ? Array.from(el.children) : null; 1012 const target = children && config.stagger ? children : el; 1013 if (children && config.stagger) { 1014 children.forEach(child => child.classList.add('card-clip-reveal')); 1015 } else { 1016 el.classList.add('card-clip-reveal'); 1017 } 1018 gsap.set(target, { '--clip-value': '100%' }); 1019 gsap.to(target, 1020 { 1021 '--clip-value': '0%', 1022 duration: config.duration, 1023 ease: config.ease || this.defaults.ease.clip, 1024 delay: config.delay, 1025 stagger: children && config.stagger ? config.stagger : 0, 1026 scrollTrigger: this.buildScrollTrigger(el, config), 1027 onComplete: () => { 1028 if (children && config.stagger) { 1029 children.forEach(child => child.classList.add('clip-animation-complete')); 1030 } else { 1031 el.classList.add('clip-animation-complete'); 1032 } 1033 } 1034 } 1035 ); 1036 } 1037 clipRevealUp(el, config) { 1038 if (!el) return; 1039 const children = el.children.length > 0 ? Array.from(el.children) : null; 1040 const target = children && config.stagger ? children : el; 1041 gsap.set(target, { 1042 clipPath: 'inset(100% 0 0 0)', 1043 webkitClipPath: 'inset(100% 0 0 0)', 1044 y: 24, 1045 autoAlpha: 1 1046 }); 1047 gsap.to(target, { 1048 clipPath: 'inset(0% 0 0 0)', 1049 webkitClipPath: 'inset(0% 0 0 0)', 1050 y: 0, 1051 duration: config.duration, 1052 ease: config.ease || this.defaults.ease.clip, 1053 delay: config.delay, 1054 stagger: children && config.stagger ? config.stagger : 0, 1055 scrollTrigger: this.buildScrollTrigger(el, config) 1056 }); 1057 } 1058 clipRevealCenter(el, config) { 1059 if (!el) return; 1060 const targets = el.querySelectorAll(` 1061.three-campuses-section__card-image-wrapper img, 1062.three-campuses-section .masked-image-content-block-wrap .image-wrap img 1063`); 1064 if (!targets.length) return; 1065 const hasStartAttr = el.hasAttribute('data-gsap-start'); 1066 const hasDurationAttr = el.hasAttribute('data-gsap-duration'); 1067 const hasEaseAttr = el.hasAttribute('data-gsap-ease'); 1068 const hasDelayAttr = el.hasAttribute('data-gsap-delay'); 1069 const start = hasStartAttr ? config.start : 'top 55%'; 1070 const duration = hasDurationAttr && Number.isFinite(config.duration) ? config.duration : 0.9; 1071 const ease = hasEaseAttr ? config.ease : 'none'; 1072 const delay = hasDelayAttr && Number.isFinite(config.delay) ? config.delay : 0; 1073 targets.forEach((img) => { 1074 if (img.dataset.clipRevealInit === 'true') return; 1075 img.dataset.clipRevealInit = 'true'; 1076 const triggerElement = img.closest('.three-campuses-section__card') || img.closest('.col-lg-4') || img; 1077 gsap.set(img, { willChange: 'transform, clip-path' }); 1078 gsap.fromTo(img, 1079 { 1080 clipPath: 'inset(50% 0 50% 0)', 1081 y: 30, 1082 opacity: 1 1083 }, 1084 { 1085 clipPath: 'inset(0% 0 0% 0)', 1086 y: 0, 1087 opacity: 1, 1088 duration, 1089 ease, 1090 delay, 1091 scrollTrigger: { 1092 trigger: triggerElement, 1093 start, 1094 toggleActions: 'play none none none', 1095 once: true 1096 } 1097 } 1098 ); 1099 }); 1100 } 1101 writingText(el, config) { 1102 if (!el) return; 1103 const prefersReducedMotion = window.matchMedia 1104 ? window.matchMedia('(prefers-reduced-motion: reduce)').matches 1105 : false; 1106 const scriptWrite = this.prepareHeroScriptWriting(el); 1107 const target = scriptWrite && scriptWrite.text 1108 ? scriptWrite.text 1109 : el.querySelector('.hero-script-text') || el; 1110 const hasEaseAttr = el.hasAttribute('data-gsap-ease'); 1111 const hasDelayAttr = el.hasAttribute('data-gsap-delay'); 1112 const ease = hasEaseAttr ? config.ease : 'power2.out'; 1113 const delay = hasDelayAttr && Number.isFinite(config.delay) ? config.delay : 0; 1114 const chars = this.splitChars(target); 1115 if (prefersReducedMotion) { 1116 gsap.set(chars.length ? chars : target, { clearProps: 'transform,opacity' }); 1117 return; 1118 } 1119 if (chars.length) { 1120 gsap.set(chars, { opacity: 0, y: 8 }); 1121 gsap.to(chars, { 1122 opacity: 1, 1123 y: 0, 1124 duration: 0.35, 1125 ease, 1126 stagger: 0.04, 1127 delay, 1128 scrollTrigger: this.buildScrollTrigger(el, config), 1129 onComplete: () => { 1130 gsap.set(chars, { clearProps: 'transform,will-change' }); 1131 } 1132 }); 1133 } 1134 } 1135 charsAnimation(el, config) { 1136 const chars = this.splitChars(el); 1137 if (chars.length === 0) return; 1138 const hasStartAttr = el.hasAttribute('data-gsap-start'); 1139 const hasDurationAttr = el.hasAttribute('data-gsap-duration'); 1140 const hasStaggerAttr = el.hasAttribute('data-gsap-stagger'); 1141 const hasEaseAttr = el.hasAttribute('data-gsap-ease'); 1142 const start = hasStartAttr ? config.start : 'top 75%'; 1143 const duration = hasDurationAttr && Number.isFinite(config.duration) ? config.duration : 0.9; 1144 const stagger = hasStaggerAttr && Number.isFinite(config.stagger) ? config.stagger : 0.02; 1145 const ease = hasEaseAttr ? config.ease : this.defaults.ease.chars; 1146 gsap.set(chars, { 1147 opacity: 0, 1148 yPercent: 90, 1149 rotateX: 70, 1150 transformOrigin: '50% 100%' 1151 });
1152 const timeline = gsap.timeline({ 1153 scrollTrigger: { 1154 trigger: el, 1155 start, 1156 end: 'bottom 50%', 1157 scrub: 1, 1158 toggleActions: 'play none none reverse' 1159 } 1160 }); 1161 timeline.to(chars, { 1162 opacity: 1, 1163 yPercent: 0, 1164 rotateX: 0, 1165 ease, 1166 duration, 1167 stagger, 1168 delay: config.delay 1169 }); 1170 } 1171 linesAnimation(el, config) { 1172 const lines = this.splitLines(el); 1173 if (lines.length === 0) return; 1174 gsap.set(lines, { 1175 y: 30, 1176 autoAlpha: 0 1177 }); 1178 const timeline = gsap.timeline({ 1179 scrollTrigger: this.buildScrollTrigger(el, config) 1180 }); 1181 timeline.to(lines, { 1182 y: 0, 1183 autoAlpha: 1, 1184 ease: config.ease || this.defaults.ease.fade, 1185 duration: config.duration, 1186 stagger: config.stagger || 0.15, 1187 delay: config.delay 1188 }); 1189 } 1190 whatWeDoHeader(el, config) { 1191 if (!el) return; 1192 const leftTitle = el.querySelector('.section-title-left'); 1193 const rightTitle = el.querySelector('.section-title-right'); 1194 const logo = el.querySelector('.masked-logo-group img'); 1195 if (!leftTitle || !rightTitle || !logo) return; 1196 gsap.set(leftTitle, { x: -100, opacity: 0 }); 1197 gsap.set(rightTitle, { x: 100, opacity: 0 }); 1198 gsap.set(logo, { scale: 0.7, opacity: 0 }); 1199 const tl = gsap.timeline({ 1200 scrollTrigger: { 1201 trigger: el, 1202 start: 'top 60%', 1203 end: 'bottom 40%', 1204 toggleActions: 'play none none none', 1205 once: true 1206 } 1207 }); 1208 tl.to(leftTitle, { 1209 x: 0, 1210 opacity: 1, 1211 duration: 0.7, 1212 ease: 'power2.out' 1213 }, 0.3); 1214 tl.to(rightTitle, { 1215 x: 0, 1216 opacity: 0.5, 1217 duration: 0.7, 1218 ease: 'power2.out' 1219 }, 0.6); 1220 tl.to(logo, { 1221 scale: 1.2, 1222 opacity: 1, 1223 duration: 0.7, 1224 ease: 'none' 1225 }, 0); 1226 } 1227 whatWeDoCards(el, config) { 1228 if (!el) return; 1229 const cards = el.classList.contains('feature-card') 1230 ? [el] 1231 : Array.from(el.querySelectorAll('.feature-card')); 1232 if (!cards.length) return; 1233 cards.forEach((card, index) => { 1234 const outerFrame = card.querySelector('.five-as-section__image-outer-circle img'); 1235 const innerCircle = card.querySelector('.five-as-section__image-inner-circle img'); 1236 if (outerFrame && innerCircle) { 1237 gsap.set([outerFrame, innerCircle], { 1238 scale: 0.4, 1239 opacity: 0 1240 }); 1241 gsap.to([outerFrame, innerCircle], { 1242 scale: 1, 1243 opacity: 1, 1244 duration: 1.2, 1245 ease: 'back.out(1.7)', 1246 scrollTrigger: { 1247 trigger: card, 1248 start: 'top 70%', 1249 once: true 1250 }, 1251 delay: index * 0.15 1252 }); 1253 } 1254 }); 1255 } 1256 coreValues(el, config) { 1257 if (!el) return; 1258 const items = Array.from(el.querySelectorAll('.core-value-item')); 1259 if (!items.length) return; 1260 const stagger = Number.isFinite(config.stagger) ? config.stagger : 0.2; 1261 const duration = Number.isFinite(config.duration) ? config.duration : 1.25; 1262 const delay = Number.isFinite(config.delay) ? config.delay : 0; 1263 const iconEase = config.ease || this.defaults.ease.zoomIn; 1264 const textEase = config.ease || this.defaults.ease.slide; 1265 items.forEach(item => { 1266 const icon = item.querySelector('.core-value-icon-wrapper'); 1267 const text = item.querySelector('.core-value-text'); 1268 if (icon) { 1269 gsap.set(icon, { scale: 0.4, autoAlpha: 0 }); 1270 } 1271 if (text) { 1272 gsap.set(text, { y: 30, autoAlpha: 0 }); 1273 } 1274 }); 1275 const tl = gsap.timeline({ 1276 scrollTrigger: this.buildScrollTrigger(el, config) 1277 }); 1278 items.forEach((item, index) => { 1279 const icon = item.querySelector('.core-value-icon-wrapper'); 1280 const text = item.querySelector('.core-value-text'); 1281 const start = delay + (index * stagger); 1282 if (icon) { 1283 tl.to(icon, { 1284 scale: 1, 1285 autoAlpha: 1, 1286 duration, 1287 ease: iconEase 1288 }, start); 1289 } 1290 if (text) { 1291 tl.to(text, { 1292 y: 0, 1293 autoAlpha: 1, 1294 duration, 1295 ease: textEase 1296 }, start + 0.05); 1297 } 1298 });
1299 } 1300 coreValuesTimeline(el, config) { 1301 if (!el) return; 1302 const titleScript = el.querySelector('.core-values-title-script'); 1303 const titleBold = el.querySelector('.core-values-title-bold'); 1304 const subtitle = el.querySelector('.core-values-subtitle'); 1305 const description = el.querySelector('.core-values-description'); 1306 const items = Array.from(el.querySelectorAll('.core-value-item')); 1307 const maskedImageBlock = el.querySelector('.masked-image-content-block'); 1308 if (titleScript) { 1309 gsap.set(titleScript, { 1310 opacity: 0, 1311 y: 50, 1312 rotationX: 15, 1313 transformOrigin: 'left center' 1314 }); 1315 } 1316 if (titleBold) { 1317 gsap.set(titleBold, { 1318 opacity: 0, 1319 y: 60, 1320 rotationX: 15, 1321 transformOrigin: 'left center' 1322 }); 1323 } 1324 if (subtitle) { 1325 gsap.set(subtitle, { 1326 opacity: 0, 1327 y: 30 1328 }); 1329 } 1330 if (description) { 1331 gsap.set(description, { 1332 opacity: 0, 1333 y: 25 1334 }); 1335 } 1336 items.forEach(item => { 1337 const icon = item.querySelector('.core-value-icon-wrapper'); 1338 const title = item.querySelector('.core-value-title'); 1339 const desc = item.querySelector('.core-value-description'); 1340 if (icon) { 1341 gsap.set(icon, { 1342 scale: 0.7, 1343 opacity: 0, 1344 }); 1345 } 1346 if (title) { 1347 gsap.set(title, { 1348 opacity: 0, 1349 y: 50, 1350 }); 1351 } 1352 if (desc) { 1353 gsap.set(desc, { 1354 opacity: 0, 1355 y: 50 1356 }); 1357 } 1358 }); 1359 if (maskedImageBlock) { 1360 gsap.set(maskedImageBlock, { 1361 scale: 0.7, 1362 opacity: 0, 1363 }); 1364 } 1365 const valuesList = el.querySelector('.core-values-list'); 1366 const titleGroup = el.querySelector('.core-values-title-group'); 1367 const isMobile = window.matchMedia('(max-width: 991px)').matches; 1368 if (isMobile) { 1369 const tlImage = gsap.timeline({ 1370 scrollTrigger: { 1371 trigger: el, 1372 start: 'top 75%', 1373 toggleActions: 'play none none none' 1374 } 1375 }); 1376 if (maskedImageBlock) { 1377 tlImage.to(maskedImageBlock, { 1378 scale: 1, 1379 opacity: 1, 1380 duration: 1, 1381 ease: 'power3.out' 1382 }, 0); 1383 } 1384 const tlTitle = gsap.timeline({ 1385 scrollTrigger: { 1386 trigger: titleGroup || el, 1387 start: 'top 80%', 1388 toggleActions: 'play none none none' 1389 } 1390 }); 1391 if (titleScript) { 1392 tlTitle.to(titleScript, { 1393 opacity: 1, 1394 y: 0, 1395 rotationX: 0, 1396 duration: 0.8, 1397 ease: 'power3.out' 1398 }, 0); 1399 } 1400 if (titleBold) { 1401 tlTitle.to(titleBold, { 1402 opacity: 1, 1403 y: 0, 1404 rotationX: 0, 1405 duration: 0.8, 1406 ease: 'power3.out' 1407 }, 0.1); 1408 } 1409 if (subtitle) { 1410 tlTitle.to(subtitle, { 1411 opacity: 1, 1412 y: 0, 1413 duration: 0.7, 1414 ease: 'power2.out' 1415 }, 0.25); 1416 } 1417 if (description) { 1418 tlTitle.to(description, { 1419 opacity: 1, 1420 y: 0, 1421 duration: 0.7, 1422 ease: 'power2.out' 1423 }, 0.4); 1424 } 1425 const itemTimelines = items.map((item) => { 1426 const icon = item.querySelector('.core-value-icon-wrapper'); 1427 const title = item.querySelector('.core-value-title'); 1428 const desc = item.querySelector('.core-value-description'); 1429 const tl = gsap.timeline({ 1430 scrollTrigger: { 1431 trigger: item, 1432 start: 'top 85%', 1433 toggleActions: 'play none none none' 1434 } 1435 }); 1436 if (icon) { 1437 tl.to(icon, { 1438 scale: 1, 1439 opacity: 1, 1440 duration: 0.6, 1441 ease: 'back.out(1.4)' 1442 }, 0); 1443 } 1444 if (title) { 1445 tl.to(title, { 1446 opacity: 1, 1447 y: 0, 1448 duration: 0.5, 1449 ease: 'power3.out' 1450 }, 0); 1451 } 1452 if (desc) { 1453 tl.to(desc, { 1454 opacity: 1, 1455 y: 0, 1456 duration: 0.5, 1457 ease: 'power2.out' 1458 }, 0.05); 1459 } 1460 return tl; 1461 }); 1462 return { tlImage, tlTitle, itemTimelines }; 1463 } else { 1464 const tl1 = gsap.timeline({ 1465 scrollTrigger: { 1466 trigger: el, 1467 start: 'top 55%', 1468 toggleActions: 'play none none none' 1469 } 1470 }); 1471 if (titleScript) { 1472 tl1.to(titleScript, { 1473 opacity: 1, 1474 y: 0, 1475 rotationX: 0, 1476 duration: 0.8, 1477 ease: 'power3.out' 1478 }, 0); 1479 } 1480 if (titleBold) { 1481 tl1.to(titleBold, { 1482 opacity: 1, 1483 y: 0, 1484 rotationX: 0, 1485 duration: 0.8, 1486 ease: 'power3.out' 1487 }, 0.02); 1488 } 1489 if (subtitle) { 1490 tl1.to(subtitle, { 1491 opacity: 1, 1492 y: 0, 1493 duration: 0.7, 1494 ease: 'power2.out' 1495 }, 0.3); 1496 } 1497 if (description) { 1498 tl1.to(description, { 1499 opacity: 1, 1500 y: 0, 1501 duration: 0.7, 1502 ease: 'power2.out' 1503 }, 0.45); 1504 } 1505 const tl2 = gsap.timeline({ 1506 scrollTrigger: { 1507 trigger: valuesList || el, 1508 start: 'top 85%', 1509 toggleActions: 'play none none none' 1510 } 1511 }); 1512 if (maskedImageBlock) { 1513 tl2.to(maskedImageBlock, { 1514 scale: 1, 1515 opacity: 1, 1516 y: 0, 1517 rotation: 0, 1518 duration: 1.2, 1519 ease: 'back.out(0.5)' 1520 }, 0); 1521 } 1522 const itemStagger = 0.25;
1523 items.forEach((item, index) => { 1524 const icon = item.querySelector('.core-value-icon-wrapper'); 1525 const title = item.querySelector('.core-value-title'); 1526 const desc = item.querySelector('.core-value-description'); 1527 const start = 0.2 + (index * itemStagger); 1528 if (icon) { 1529 tl2.to(icon, { 1530 scale: 1, 1531 opacity: 1, 1532 rotation: 0, 1533 duration: 0.7, 1534 ease: 'back.out(1.4)' 1535 }, start); 1536 } 1537 if (title) { 1538 tl2.to(title, { 1539 opacity: 1, 1540 y: 0, 1541 duration: 0.6, 1542 ease: 'power3.out' 1543 }, start); 1544 } 1545 if (desc) { 1546 tl2.to(desc, { 1547 opacity: 1, 1548 y: 0, 1549 duration: 0.6, 1550 ease: 'power2.out' 1551 }, start); 1552 } 1553 }); 1554 return { tl1, tl2 }; 1555 } 1556 } 1557 heroAbout(el, config) { 1558 if (!el) return; 1559 const lines = [ 1560 el.querySelector('.about-hero__line--handwritten'), 1561 el.querySelector('.about-hero__line--creative'), 1562 el.querySelector('.about-hero__line--individuals') 1563 ].filter(Boolean); 1564 if (!lines.length) return; 1565 const waitForLoaded = el.hasAttribute('data-gsap-wait-loaded'); 1566 const hasDurationAttr = el.hasAttribute('data-gsap-duration'); 1567 const hasStaggerAttr = el.hasAttribute('data-gsap-stagger'); 1568 const hasEaseAttr = el.hasAttribute('data-gsap-ease'); 1569 const hasDelayAttr = el.hasAttribute('data-gsap-delay'); 1570 const duration = hasDurationAttr && Number.isFinite(config.duration) ? config.duration : 0.8; 1571 const stagger = hasStaggerAttr && Number.isFinite(config.stagger) ? config.stagger : 0.55; 1572 const ease = hasEaseAttr ? config.ease : 'circ.inOut'; 1573 const delay = hasDelayAttr && Number.isFinite(config.delay) ? config.delay : 0; 1574 const offsets = [-100, 100, -100]; 1575 const setInitialState = () => { 1576 gsap.set(lines, { 1577 opacity: 0, 1578 xPercent: index => offsets[index % offsets.length], 1579 willChange: 'transform, opacity' 1580 }); 1581 }; 1582 const play = () => { 1583 if (el.dataset.heroAnimated === 'true') return; 1584 const tl = gsap.timeline({ 1585 defaults: { ease }, 1586 delay 1587 }); 1588 tl.to(lines, { 1589 opacity: 1, 1590 xPercent: 0, 1591 duration, 1592 stagger 1593 }, 0); 1594 tl.eventCallback('onComplete', () => { 1595 el.dataset.heroAnimated = 'true'; 1596 }); 1597 }; 1598 setInitialState(); 1599 if (waitForLoaded && document.body && !document.body.classList.contains('loaded')) { 1600 const observer = new MutationObserver(() => { 1601 if (document.body.classList.contains('loaded')) { 1602 observer.disconnect(); 1603 play(); 1604 } 1605 }); 1606 observer.observe(document.body, { attributes: true, attributeFilter: ['class'] }); 1607 return; 1608 } 1609 play(); 1610 } 1611 heroHome(el, config) { 1612 if (!el) return; 1613 const slide = this.getActiveHeroSlide(el); 1614 if (!slide) return; 1615 const SCRIPT = { duration: 0.35, stagger: 0.04, ease: 'power2.out' }; 1616 const BOLD = { duration: 1.1, stagger: 0.07, ease: 'back.out(1.7)', yPercent: 100 }; 1617 const script = slide.querySelector('.hero__title-script'); 1618 const main = slide.querySelector('.hero__title-main'); 1619 if (script) script.style.cssText = 'display:block;overflow:visible;opacity:0'; 1620 if (main) main.style.cssText = 'display:block;overflow:visible;opacity:0'; 1621 const setup = () => { 1622 const scriptWrite = script ? this.prepareHeroScriptWriting(script) : null; 1623 const boldChars = main ? this.splitHeroChars(main, { includeInner: true, includeSrOnly: true }) : []; 1624 const scriptChars = scriptWrite?.text ? this.splitChars(scriptWrite.text) : []; 1625 if (scriptChars.length) { 1626 gsap.set(scriptChars, { opacity: 0, y: 8 }); 1627 } 1628 if (boldChars.length) { 1629 gsap.set(boldChars, { yPercent: BOLD.yPercent, opacity: 0 }); 1630 } 1631 if (script) script.style.opacity = '1'; 1632 if (main) main.style.opacity = '1'; 1633 const play = () => { 1634 if (el.dataset.heroAnimated === 'true') return; 1635 el.dataset.heroAnimated = 'true';
1636 const delay = el.hasAttribute('data-gsap-delay') ? config.delay : 0; 1637 const tl = gsap.timeline({ delay }); 1638 const scriptDuration = scriptChars.length 1639 ? SCRIPT.duration + SCRIPT.stagger * (scriptChars.length - 1) 1640 : 0; 1641 if (scriptChars.length) { 1642 tl.to(scriptChars, { 1643 duration: SCRIPT.duration, 1644 ease: SCRIPT.ease, 1645 opacity: 1, 1646 y: 0, 1647 stagger: SCRIPT.stagger, 1648 onComplete: () => { 1649 gsap.set(scriptChars, { clearProps: 'transform,will-change' }); 1650 } 1651 }, 0); 1652 } 1653 if (boldChars.length) { 1654 tl.to(boldChars, { 1655 duration: BOLD.duration, 1656 ease: BOLD.ease, 1657 opacity: 1, 1658 yPercent: 0, 1659 stagger: BOLD.stagger 1660 }, scriptDuration * 0.8); 1661 } 1662 }; 1663 if (el.hasAttribute('data-gsap-wait-loaded') && !document.body.classList.contains('loaded')) { 1664 const obs = new MutationObserver(() => { 1665 if (document.body.classList.contains('loaded')) { obs.disconnect(); play(); } 1666 }); 1667 obs.observe(document.body, { attributes: true, attributeFilter: ['class'] }); 1668 return; 1669 } 1670 play(); 1671 }; 1672 if (document.fonts && document.fonts.ready) { 1673 document.fonts.ready.then(setup); 1674 } else { 1675 setup(); 1676 } 1677 } 1678 sectionTitle(el, config) { 1679 if (!el) return; 1680 const SCRIPT = { duration: 0.35, stagger: 0.04, ease: 'power2.out' }; 1681 const BOLD = { duration: 0.7, stagger: 0.02, ease: 'expo.out', yPercent: 90, rotateX: 70 }; 1682 const script = el.querySelector('[class*="script"]'); 1683 const bold = el.querySelector('[class*="bold"]'); 1684 const scriptWrite = script ? this.prepareHeroScriptWriting(script) : null; 1685 const scriptTarget = scriptWrite?.text ?? script?.querySelector('.hero-script-text'); 1686 const scriptChars = scriptTarget ? this.splitChars(scriptTarget) : []; 1687 const boldChars = bold ? this.splitChars(bold) : []; 1688 if (scriptChars.length) { 1689 gsap.set(scriptChars, { opacity: 0, y: 8 }); 1690 } 1691 if (boldChars.length) { 1692 gsap.set(boldChars, { opacity: 0, yPercent: BOLD.yPercent, rotateX: BOLD.rotateX, transformOrigin: '50% 100%' }); 1693 } 1694 const scriptDuration = scriptChars.length 1695 ? SCRIPT.duration + SCRIPT.stagger * (scriptChars.length - 1) 1696 : 0; 1697 const tl = gsap.timeline({ 1698 scrollTrigger: { 1699 trigger: el, 1700 start: config.start || 'top 80%', 1701 toggleActions: 'play none none none' 1702 }, 1703 delay: config.delay 1704 }); 1705 if (scriptChars.length) { 1706 tl.to(scriptChars, { 1707 duration: SCRIPT.duration, 1708 ease: SCRIPT.ease, 1709 opacity: 1, 1710 y: 0, 1711 stagger: SCRIPT.stagger, 1712 onComplete: () => { 1713 gsap.set(scriptChars, { clearProps: 'transform,will-change' }); 1714 } 1715 }, 0); 1716 } 1717 if (boldChars.length) { 1718 tl.to(boldChars, { 1719 duration: BOLD.duration, 1720 ease: BOLD.ease, 1721 opacity: 1, 1722 yPercent: 0, 1723 rotateX: 0, 1724 stagger: BOLD.stagger 1725 }, scriptChars.length ? scriptDuration * 0.85 : 0); 1726 } 1727 } 1728 wellbeingTimeline(el, config) { 1729 if (!el) return; 1730 try { 1731 const isMobile = window.matchMedia('(max-width: 991px)').matches; 1732 const section = el.closest('.wellbeing-section'); 1733 const container = el.closest('.container-fluid') || section; 1734 const imageWrapper = el.querySelector('.five-as-section__image-wrapper') || container?.querySelector('.five-as-section__image-wrapper'); 1735 const titleGroup = el.querySelector('.wellbeing-title-group') || container?.querySelector('.wellbeing-title-group'); 1736 const content = el.querySelector('.wellbeing-content') || container?.querySelector('.wellbeing-content'); 1737 const triggerElement = section && section.getBoundingClientRect().height > 100 ? section : el; 1738 const headings = content ? content.querySelectorAll('.wellbeing-text-heading') : []; 1739 const textBodies = content ? content.querySelectorAll('.wellbeing-text-body') : []; 1740 const fadeUpElements = [];
1741 headings.forEach(el => fadeUpElements.push(el)); 1742 textBodies.forEach(el => { 1743 if (el.tagName === 'P') { 1744 fadeUpElements.push(el); 1745 } else { 1746 const blockChildren = Array.from(el.children).filter(child => { 1747 const tag = child.tagName; 1748 return tag && !['BR', 'SCRIPT', 'STYLE'].includes(tag); 1749 }); 1750 blockChildren.forEach(child => fadeUpElements.push(child)); 1751 } 1752 }); 1753 const ZOOM_IN = { duration: 1.9, ease: 'back.out(1.05)', scaleFrom: 0.7 }; 1754 const SCRIPT = { duration: 0.35, stagger: 0.04, ease: 'power2.out' }; 1755 const BOLD = { duration: 0.7, stagger: 0.02, ease: 'expo.out', yPercent: 90, rotateX: 70 }; 1756 const FADE_UP = { duration: 1.25, ease: 'power2.out', yFrom: 50 }; 1757 if (imageWrapper) { 1758 gsap.set(imageWrapper, { scale: ZOOM_IN.scaleFrom, autoAlpha: 0, visibility: 'hidden' }); 1759 } else { 1760 console.warn('wellbeingTimeline: Image wrapper not found'); 1761 } 1762 let scriptWrite = null; 1763 let chars = []; 1764 let scriptChars = []; 1765 if (titleGroup) { 1766 try { 1767 const script = titleGroup.querySelector('[class*="script"]'); 1768 const bold = titleGroup.querySelector('[class*="bold"]'); 1769 if (script) { 1770 scriptWrite = this.prepareHeroScriptWriting(script); 1771 } 1772 if (bold) { 1773 chars = this.splitChars(bold) || []; 1774 } 1775 scriptChars = scriptWrite?.text ? this.splitChars(scriptWrite.text) : []; 1776 if (scriptChars.length) { 1777 gsap.set(scriptChars, { opacity: 0, y: 8 }); 1778 } 1779 if (chars && chars.length) { 1780 gsap.set(chars, { opacity: 0, yPercent: BOLD.yPercent, rotateX: BOLD.rotateX, transformOrigin: '50% 100%' }); 1781 } 1782 } catch (err) { 1783 console.warn('Error setting up title animation:', err); 1784 } 1785 } 1786 const scriptDuration = scriptChars.length ? SCRIPT.duration + SCRIPT.stagger * (scriptChars.length - 1) : 0; 1787 if (fadeUpElements && fadeUpElements.length > 0) { 1788 try { 1789 gsap.set(fadeUpElements, { y: FADE_UP.yFrom, autoAlpha: 0 }); 1790 } catch (err) { 1791 console.warn('Error setting initial states for fade-up elements:', err); 1792 } 1793 } 1794 if (!imageWrapper && !titleGroup && (!fadeUpElements || fadeUpElements.length === 0)) { 1795 console.warn('wellbeingTimeline: No elements found to animate'); 1796 return; 1797 } 1798 if (isMobile) { 1799 const tl = gsap.timeline({ 1800 scrollTrigger: { 1801 trigger: triggerElement, 1802 start: config.start || 'top 80%', 1803 toggleActions: 'play none none none', 1804 once: true 1805 }, 1806 delay: config.delay 1807 }); 1808 if (imageWrapper) { 1809 tl.to(imageWrapper, { 1810 scale: 1, 1811 autoAlpha: 1, 1812 duration: ZOOM_IN.duration, 1813 ease: ZOOM_IN.ease 1814 }); 1815 } 1816 if (titleGroup) { 1817 const titleStart = imageWrapper ? ZOOM_IN.duration * 0.5 : 0; 1818 if (scriptChars.length) { 1819 tl.to(scriptChars, { 1820 duration: SCRIPT.duration, 1821 ease: SCRIPT.ease, 1822 opacity: 1, 1823 y: 0, 1824 stagger: SCRIPT.stagger, 1825 onComplete: () => gsap.set(scriptChars, { clearProps: 'transform,will-change' }) 1826 }, titleStart); 1827 } 1828 if (chars.length) { 1829 tl.to(chars, { 1830 duration: BOLD.duration, 1831 ease: BOLD.ease, 1832 opacity: 1, 1833 yPercent: 0, 1834 rotateX: 0, 1835 stagger: BOLD.stagger 1836 }, scriptChars.length ? titleStart + scriptDuration * 0.9 : titleStart); 1837 } 1838 } 1839 if (fadeUpElements && fadeUpElements.length) { 1840 const contentStart = titleGroup 1841 ? (scriptChars.length ? ZOOM_IN.duration * 0.5 + scriptDuration * 0.9 + BOLD.duration * 0.5 : ZOOM_IN.duration * 0.5 + BOLD.duration * 0.5) 1842 : (imageWrapper ? ZOOM_IN.duration * 0.5 : 0); 1843 tl.to(fadeUpElements, { 1844 y: 0, 1845 autoAlpha: 1, 1846 duration: FADE_UP.duration, 1847 ease: FADE_UP.ease, 1848 stagger: 0.1 1849 }, contentStart); 1850 } 1851 if (tl.duration() === 0) { 1852 console.warn('wellbeingTimeline (mobile): Timeline created but no animations added'); 1853 } 1854 } else { 1855 const tl = gsap.timeline({ 1856 scrollTrigger: { 1857 trigger: triggerElement, 1858 start: config.start || 'top 60%', 1859 toggleActions: 'play none none none', 1860 once: true 1861 }, 1862 delay: config.delay 1863 }); 1864 if (imageWrapper) { 1865 tl.to(imageWrapper, { 1866 scale: 1, 1867 autoAlpha: 1, 1868 duration: ZOOM_IN.duration, 1869 ease: ZOOM_IN.ease 1870 }, 0); 1871 } 1872 if (titleGroup) { 1873 if (scriptChars.length) { 1874 tl.to(scriptChars, { 1875 duration: SCRIPT.duration, 1876 ease: SCRIPT.ease, 1877 opacity: 1, 1878 y: 0, 1879 stagger: SCRIPT.stagger, 1880 onComplete: () => gsap.set(scriptChars, { clearProps: 'transform,will-change' }) 1881 }, 0); 1882 } 1883 if (chars.length) { 1884 tl.to(chars, { 1885 duration: BOLD.duration, 1886 ease: BOLD.ease, 1887 opacity: 1, 1888 yPercent: 0, 1889 rotateX: 0, 1890 stagger: BOLD.stagger 1891 }, scriptChars.length ? scriptDuration * 0.9 : 0); 1892 } 1893 } 1894 if (fadeUpElements && fadeUpElements.length) { 1895 const contentStart = Math.max( 1896 imageWrapper ? ZOOM_IN.duration * 0.5 : 0, 1897 titleGroup ? (scriptChars.length ? scriptDuration * 0.9 + BOLD.duration * 0.5 : BOLD.duration * 0.5) : 0 1898 ); 1899 tl.to(fadeUpElements, { 1900 y: 0, 1901 autoAlpha: 1, 1902 duration: FADE_UP.duration, 1903 ease: FADE_UP.ease, 1904 stagger: 0.1 1905 }, contentStart); 1906 } 1907 if (tl.duration() === 0) { 1908 console.warn('wellbeingTimeline (desktop): Timeline created but no animations added'); 1909 } 1910 } 1911 } catch (error) { 1912 console.error('Error in wellbeingTimeline animation:', error); 1913 return; 1914 } 1915 } 1916 threeStagesTimeline(el, config) { 1917 if (!el) return; 1918 try { 1919 const title = el.querySelector('.title'); 1920 const content = el.querySelector('.three-stages-content'); 1921 const descriptionParagraphs = content ? content.querySelectorAll('.three-stages-description p') : []; 1922 const listElement = content ? content.querySelector('.three-stages-list') : null; 1923 const fadeUpElements = []; 1924 descriptionParagraphs.forEach(p => fadeUpElements.push(p)); 1925 if (listElement) { 1926 fadeUpElements.push(listElement); 1927 } 1928 const SCRIPT = { duration: 1.1, ease: 'power2.out' }; 1929 const BOLD = { duration: 1.2, stagger: 0.02, ease: 'expo.out', yPercent: 90, rotateX: 70 }; 1930 const FADE_UP = { duration: 1.25, ease: 'power2.out', yFrom: 50 }; 1931 let scriptWrite = null; 1932 let chars = []; 1933 if (title) { 1934 try { 1935 const script = title.querySelector('[class*="script"]'); 1936 const bold = title.querySelector('[class*="bold"]'); 1937 if (script) { 1938 scriptWrite = this.prepareHeroScriptWriting(script); 1939 } 1940 if (bold) { 1941 chars = this.splitChars(bold) || []; 1942 } 1943 if (scriptWrite?.text) { 1944 gsap.set(scriptWrite.text, { clipPath: 'inset(-0.4em 100% -0.4em -0.3em)', opacity: 0 }); 1945 } 1946 if (chars && chars.length) { 1947 gsap.set(chars, { opacity: 0, yPercent: BOLD.yPercent, rotateX: BOLD.rotateX, transformOrigin: '50% 100%' }); 1948 } 1949 } catch (err) { 1950 console.warn('Error setting up title animation:', err); 1951 } 1952 } 1953 if (fadeUpElements && fadeUpElements.length > 0) { 1954 try { 1955 gsap.set(fadeUpElements, { y: FADE_UP.yFrom, autoAlpha: 0 }); 1956 } catch (err) { 1957 console.warn('Error setting initial states for fade-up elements:', err); 1958 } 1959 } 1960 if (!title && (!fadeUpElements || fadeUpElements.length === 0)) { 1961 console.warn('threeStagesTimeline: No elements found to animate'); 1962 return; 1963 } 1964 const tl = gsap.timeline({ 1965 scrollTrigger: { 1966 trigger: el, 1967 start: config.start || 'top 80%', 1968 toggleActions: 'play none none none', 1969 once: true 1970 }, 1971 delay: config.delay 1972 }); 1973 if (title) { 1974 if (scriptWrite?.text) { 1975 tl.to(scriptWrite.text, { 1976 duration: SCRIPT.duration, 1977 ease: SCRIPT.ease, 1978 opacity: 1, 1979 clipPath: 'inset(-0.4em 0% -0.4em -0.3em)' 1980 }); 1981 } 1982 if (chars && chars.length) { 1983 tl.to(chars, { 1984 duration: BOLD.duration, 1985 ease: BOLD.ease, 1986 opacity: 1, 1987 yPercent: 0, 1988 rotateX: 0, 1989 stagger: BOLD.stagger 1990 }, scriptWrite ? SCRIPT.duration * 0.9 : 0); 1991 } 1992 } 1993 if (fadeUpElements && fadeUpElements.length) { 1994 const contentStart = title 1995 ? (scriptWrite ? SCRIPT.duration * 0.9 + BOLD.duration * 0.5 : SCRIPT.duration * 0.5) 1996 : 0; 1997 const listElement = content?.querySelector('.three-stages-list'); 1998 const staggerValue = listElement?.getAttribute('data-gsap-stagger') 1999 ? parseFloat(listElement.getAttribute('data-gsap-stagger')) 2000 : 0.1; 2001 tl.to(fadeUpElements, { 2002 y: 0, 2003 autoAlpha: 1, 2004 duration: FADE_UP.duration, 2005 ease: FADE_UP.ease, 2006 stagger: staggerValue 2007 }, contentStart); 2008 } 2009 if (tl.duration() === 0) { 2010 console.warn('threeStagesTimeline: Timeline created but no animations added'); 2011 } 2012 } catch (error) { 2013 console.error('Error in threeStagesTimeline animation:', error); 2014 return; 2015 } 2016 } 2017 tiltCard(el, config) { 2018 if (!el) return; 2019 const isMobile = window.matchMedia('(max-width: 991px)').matches; 2020 const cards = Array.from(el.children); 2021 if (!cards.length) return; 2022 if (isMobile) { 2023 this.tiltCardMobile(el, cards, config); 2024 } else { 2025 this.tiltCardDesktop(el, cards, config); 2026 } 2027 } 2028 tiltCardDesktop(el, cards, config) { 2029 const CARD = { 2030 offsetX: 700, 2031 offsetY: -300, 2032 rotation: 85, 2033 duration: 2.7, 2034 stagger: 1, 2035 ease: 'power3.out' 2036 }; 2037 const CONTENT = { 2038 y: 40, 2039 duration: 0.4, 2040 ease: 'power2.out', 2041 delay: 0 2042 }; 2043 cards.forEach((card) => { 2044 gsap.set(card, { 2045 x: CARD.offsetX, 2046 y: CARD.offsetY, 2047 rotation: CARD.rotation, 2048 autoAlpha: 0, 2049 transformOrigin: '50% 50%' 2050 }); 2051 const content = card.querySelector('[class*="content"]'); 2052 if (content) { 2053 gsap.set(content, { y: CONTENT.y, autoAlpha: 0 }); 2054 } 2055 });
2056 const tl = gsap.timeline({ 2057 scrollTrigger: { 2058 trigger: el, 2059 start: config.start || 'top 40%', 2060 toggleActions: 'play none none none' 2061 }, 2062 delay: config.delay 2063 }); 2064 const firstCardStart = (cards.length - 1) * CARD.stagger; 2065 const contentStartTime = firstCardStart + CARD.duration * 0.6; 2066 cards.forEach((card, index) => { 2067 const reverseIndex = cards.length - 1 - index; 2068 const cardStartTime = index * CARD.stagger; 2069 tl.to(card, { 2070 x: 0, 2071 y: 0, 2072 rotation: 0, 2073 autoAlpha: 1, 2074 duration: CARD.duration, 2075 ease: CARD.ease 2076 }, cardStartTime); 2077 const content = card.querySelector('[class*="content"]'); 2078 if (content) { 2079 tl.to(content, { 2080 y: 0, 2081 autoAlpha: 1, 2082 duration: CONTENT.duration, 2083 ease: CONTENT.ease 2084 }, contentStartTime + CONTENT.delay + index * 0.15); 2085 } 2086 }); 2087 } 2088 tiltCardMobile(el, cards, config) { 2089 gsap.registerPlugin(ScrollTrigger); 2090 const CARD = { 2091 duration: 1.1, 2092 ease: "power3.out", 2093 fromScale: 1.02 2094 }; 2095 const CONTENT = { 2096 y: 30, 2097 duration: 0.5, 2098 ease: "power2.out", 2099 delay: 0.15 2100 }; 2101 const supportsClip = CSS && CSS.supports && CSS.supports("clip-path", "inset(0% 0% 0% 0%)"); 2102 (cards || []).forEach((card, index) => { 2103 if (!card || !card.parentNode) return; 2104 const wrapper = document.createElement("div"); 2105 wrapper.style.cssText = "position:relative;width:100%;height:100%;"; 2106 card.parentNode.insertBefore(wrapper, card); 2107 wrapper.appendChild(card); 2108 const revealFromRight = index % 2 === 0; 2109 const clipFromLeftStart = "inset(0% 100% 0% 0%)"; 2110 const clipFromRightStart = "inset(0% 0% 0% 100%)"; 2111 const clipEnd = "inset(0% 0% 0% 0%)"; 2112 const clipStart = revealFromRight ? clipFromRightStart : clipFromLeftStart; 2113 const content = card.querySelector('[class*="content"]'); 2114 const tl = gsap.timeline({ 2115 scrollTrigger: { 2116 trigger: wrapper, 2117 start: "top 55%", 2118 toggleActions: "play none none none" 2119 } 2120 }); 2121 if (supportsClip) { 2122 gsap.set(card, { 2123 autoAlpha: 1, 2124 opacity: 1, 2125 scale: CARD.fromScale, 2126 clipPath: clipStart, 2127 WebkitClipPath: clipStart, 2128 willChange: "clip-path, transform" 2129 }); 2130 tl.to(card, { 2131 clipPath: clipEnd, 2132 WebkitClipPath: clipEnd, 2133 scale: 1, 2134 duration: CARD.duration, 2135 ease: CARD.ease 2136 }, 0); 2137 } else { 2138 gsap.set(card, { autoAlpha: 0, y: 30 }); 2139 tl.to(card, { autoAlpha: 1, y: 0, duration: 0.8, ease: "power2.out" }, 0); 2140 } 2141 if (content) { 2142 gsap.set(content, { y: CONTENT.y, autoAlpha: 0 }); 2143 tl.to(content, { 2144 y: 0, 2145 autoAlpha: 1, 2146 duration: CONTENT.duration, 2147 ease: CONTENT.ease 2148 }, CONTENT.delay); 2149 } 2150 }); 2151 } 2152 heroPreschool(el, config) { 2153 if (!el || el.dataset.heroAnimated === 'true') return; 2154 const slide = this.getActiveHeroSlide(el); 2155 if (!slide) return; 2156 const prefersReducedMotion = window.matchMedia 2157 ? window.matchMedia('(prefers-reduced-motion: reduce)').matches 2158 : false; 2159 const script = slide.querySelector('.hero__title-script'); 2160 const main = slide.querySelector('.hero__title-main'); 2161 const scriptLine = script ? script.querySelector('.preschool-hero-line') : null; 2162 const mainLine = main ? main.querySelector('.preschool-hero-line') : null; 2163 const scriptTarget = scriptLine || script; 2164 const mainTarget = mainLine || main; 2165 const description = slide.querySelector('.hero__description'); 2166 const playButton = el.querySelector('.play-button'); 2167 const HERO_TEXT_KNOBS = { 2168 scriptDuration: 1.4, 2169 scriptGap: 0, 2170 scriptEase: 'power3.out', 2171 scriptTrackingFrom: '0.22em', 2172 scriptTrackingTo: '-0.009em',
2173 boldStartRatio: 0.5, 2174 boldDuration: 0.9, 2175 boldStagger: 0.06, 2176 boldEase: 'back.inOut(2.7)', 2177 boldFromY: 100, 2178 descriptionDuration: 0.8, 2179 descriptionGap: 0.3, 2180 descriptionEase: 'power3.out', 2181 descriptionFromY: 34, 2182 playButtonDuration: 0.7, 2183 playButtonGap: 0.1, 2184 playButtonEase: 'power2.out', 2185 playButtonScaleFrom: 0.08, 2186 playButtonOpacityFrom: 0 2187 }; 2188 const boldChars = mainTarget ? this.splitHeroChars(mainTarget) : []; 2189 if (!scriptTarget && !boldChars.length && !description && !playButton) { 2190 el.dataset.heroAnimated = 'true'; 2191 return; 2192 } 2193 if (script && scriptTarget !== script) { 2194 gsap.set(script, { opacity: 1 }); 2195 } 2196 if (main) { 2197 gsap.set(main, { opacity: 1 }); 2198 } 2199 if (prefersReducedMotion) { 2200 if (scriptTarget) { 2201 gsap.set(scriptTarget, { opacity: 1, clearProps: 'letter-spacing' }); 2202 } 2203 if (boldChars.length) { 2204 gsap.set(boldChars, { yPercent: 0, opacity: 1, clearProps: 'will-change' }); 2205 } 2206 if (description) { 2207 gsap.set(description, { opacity: 1, clearProps: 'transform' }); 2208 } 2209 if (playButton) { 2210 gsap.set(playButton, { opacity: 1, scale: 1, clearProps: 'transform' }); 2211 } 2212 el.dataset.heroAnimated = 'true'; 2213 return; 2214 } 2215 const delay = el.hasAttribute('data-gsap-delay') ? config.delay : 0; 2216 const tl = gsap.timeline({ delay }); 2217 const boldStart = scriptTarget 2218 ? (HERO_TEXT_KNOBS.scriptDuration * HERO_TEXT_KNOBS.boldStartRatio) + HERO_TEXT_KNOBS.scriptGap 2219 : 0; 2220 if (scriptTarget) { 2221 gsap.set(scriptTarget, { 2222 opacity: 0, 2223 letterSpacing: HERO_TEXT_KNOBS.scriptTrackingFrom, 2224 willChange: 'letter-spacing, opacity' 2225 }); 2226 tl.to(scriptTarget, { 2227 duration: HERO_TEXT_KNOBS.scriptDuration, 2228 opacity: 1, 2229 letterSpacing: HERO_TEXT_KNOBS.scriptTrackingTo, 2230 ease: HERO_TEXT_KNOBS.scriptEase 2231 }, 0); 2232 } 2233 if (boldChars.length) { 2234 gsap.set(boldChars, { 2235 yPercent: HERO_TEXT_KNOBS.boldFromY, 2236 opacity: 0, 2237 willChange: 'transform, opacity' 2238 }); 2239 tl.to(boldChars, { 2240 duration: HERO_TEXT_KNOBS.boldDuration, 2241 opacity: 1, 2242 yPercent: 0, 2243 stagger: HERO_TEXT_KNOBS.boldStagger, 2244 ease: HERO_TEXT_KNOBS.boldEase 2245 }, boldStart); 2246 } 2247 if (description) { 2248 gsap.set(description, { 2249 opacity: 0, 2250 y: HERO_TEXT_KNOBS.descriptionFromY, 2251 willChange: 'transform, opacity' 2252 }); 2253 tl.to(description, { 2254 duration: HERO_TEXT_KNOBS.descriptionDuration, 2255 opacity: 1, 2256 y: 0, 2257 ease: HERO_TEXT_KNOBS.descriptionEase 2258 }, `-=${HERO_TEXT_KNOBS.descriptionGap}`); 2259 } 2260 if (playButton) { 2261 gsap.set(playButton, { 2262 opacity: HERO_TEXT_KNOBS.playButtonOpacityFrom, 2263 scale: HERO_TEXT_KNOBS.playButtonScaleFrom, 2264 transformOrigin: '50% 50%', 2265 willChange: 'transform, opacity' 2266 }); 2267 tl.to(playButton, { 2268 duration: HERO_TEXT_KNOBS.playButtonDuration, 2269 opacity: 1, 2270 scale: 1, 2271 ease: HERO_TEXT_KNOBS.playButtonEase 2272 }, `>+${HERO_TEXT_KNOBS.playButtonGap}`); 2273 } 2274 tl.eventCallback('onComplete', () => { 2275 if (scriptTarget) { 2276 gsap.set(scriptTarget, { clearProps: 'will-change' }); 2277 } 2278 if (boldChars.length) { 2279 gsap.set(boldChars, { clearProps: 'will-change' }); 2280 } 2281 if (description) { 2282 gsap.set(description, { clearProps: 'will-change' }); 2283 } 2284 if (playButton) { 2285 gsap.set(playButton, { clearProps: 'will-change' }); 2286 } 2287 el.dataset.heroAnimated = 'true'; 2288 }); 2289 } 2290 animateHero(el, config) { 2291 if (!el) return; 2292 const slide = this.getActiveHeroSlide(el); 2293 if (!slide) return; 2294 const SCRIPT = { duration: 0.35, stagger: 0.04, ease: 'power2.out' }; 2295 const BOLD = { duration: 0.9, stagger: 0.04, ease: 'back.out(1.7)', yPercent: 100 }; 2296 const DESKTOP_GAP = 0.3; 2297 const DESCRIPTION = { duration: 0.2, ease: 'none', fromY: 10 }; 2298 const ANN = { duration: 0.55, ease: 'power2.out', fromY: 14, gap: 0.08 }; 2299 const BUTTON = { duration: 0.8, ease: 'back.out(1.3)', scaleFrom: 0.7 }; 2300 const NAVIGATION = { duration: 1.1, ease: 'back.out(1.3)', stagger: 0.2, fromX: 50 }; 2301 const MOBILE_DESC_GAP = 0.3; 2302 const MOBILE_ACTIONS = { duration: 0.5, ease: 'power1.out', fromY: 50, stagger: 0.15, gap: 0 }; 2303 const script = slide.querySelector('.hero__title-script'); 2304 const main = slide.querySelector('.hero__title-main'); 2305 const descriptions = slide.querySelectorAll('.hero__description'); 2306 const playButton = el.querySelector('.play-button'); 2307 const audioButton = el.querySelector('.audio-btn'); 2308 const navigation = el.querySelector('.arrow-navigation'); 2309 const annSlider = el.querySelector('.hero__ann'); 2310 const mobileActions = el.querySelector('.hero__mobile-actions'); 2311 if (script) script.style.cssText = 'display:block;overflow:visible;opacity:0'; 2312 if (main) main.style.cssText = 'display:block;overflow:visible;opacity:0'; 2313 descriptions.forEach(desc => { if (desc) desc.style.opacity = '0'; });
2314 if (playButton) playButton.style.opacity = '0'; 2315 if (audioButton) audioButton.style.opacity = '0'; 2316 if (navigation) navigation.style.opacity = '0'; 2317 if (annSlider) annSlider.style.opacity = '0'; 2318 if (mobileActions) mobileActions.style.opacity = '0'; 2319 const setup = () => { 2320 const scriptWrite = script ? this.prepareHeroScriptWriting(script) : null; 2321 const boldChars = main ? this.splitHeroChars(main, { includeInner: true, includeSrOnly: true }) : []; 2322 const scriptChars = scriptWrite?.text ? this.splitChars(scriptWrite.text) : []; 2323 if (scriptChars.length) { 2324 gsap.set(scriptChars, { opacity: 0, y: 8 }); 2325 } 2326 if (boldChars.length) { 2327 gsap.set(boldChars, { yPercent: BOLD.yPercent, opacity: 0 }); 2328 } 2329 descriptions.forEach(desc => { 2330 if (desc) gsap.set(desc, { y: DESCRIPTION.fromY }); 2331 }); 2332 if (playButton) { 2333 gsap.set(playButton, { scale: BUTTON.scaleFrom, transformOrigin: '50% 50%' }); 2334 } 2335 if (audioButton) { 2336 gsap.set(audioButton, { scale: BUTTON.scaleFrom, transformOrigin: '50% 50%' }); 2337 } 2338 if (navigation) { 2339 gsap.set(navigation.querySelectorAll('.arrow-navigation__arrow'), { x: NAVIGATION.fromX }); 2340 } 2341 if (annSlider) { 2342 gsap.set(annSlider, { autoAlpha: 0, y: ANN.fromY, willChange: 'transform, opacity' }); 2343 } 2344 if (script) script.style.opacity = '1'; 2345 if (main) main.style.opacity = '1'; 2346 const play = () => { 2347 if (el.dataset.heroAnimated === 'true') return; 2348 el.dataset.heroAnimated = 'true'; 2349 const delay = el.hasAttribute('data-gsap-delay') ? config.delay : 0; 2350 const tl = gsap.timeline({ delay }); 2351 const isMobile = window.matchMedia('(max-width: 991px)').matches; 2352 if (scriptChars.length) { 2353 tl.to(scriptChars, { 2354 duration: SCRIPT.duration, 2355 ease: SCRIPT.ease, 2356 opacity: 1, 2357 y: 0, 2358 stagger: SCRIPT.stagger, 2359 onComplete: () => gsap.set(scriptChars, { clearProps: 'transform,will-change' }) 2360 }, 0); 2361 } 2362 const scriptDuration = scriptChars.length ? SCRIPT.duration + SCRIPT.stagger * (scriptChars.length - 1) : 0; 2363 if (boldChars.length) { 2364 tl.to(boldChars, { 2365 duration: BOLD.duration, 2366 ease: BOLD.ease, 2367 opacity: 1, 2368 yPercent: 0, 2369 stagger: BOLD.stagger 2370 }, scriptDuration * 0.8); 2371 } 2372 if (isMobile) { 2373 descriptions.forEach(desc => { 2374 if (desc) { 2375 tl.to(desc, { 2376 duration: DESCRIPTION.duration, 2377 autoAlpha: 1, 2378 y: 0, 2379 ease: DESCRIPTION.ease 2380 }, `-=${MOBILE_DESC_GAP}`); 2381 } 2382 }); 2383 if (annSlider) { 2384 tl.to(annSlider, { 2385 duration: ANN.duration, 2386 autoAlpha: 1, 2387 y: 0, 2388 ease: ANN.ease 2389 }, `>+${ANN.gap}`); 2390 tl.set(annSlider, { clearProps: 'will-change' }); 2391 } 2392 if (mobileActions) { 2393 const children = Array.from(mobileActions.children); 2394 gsap.set(children, { y: MOBILE_ACTIONS.fromY, autoAlpha: 0 }); 2395 tl.to(mobileActions, { autoAlpha: 1, duration: 0.01 }, `>+${MOBILE_ACTIONS.gap}`); 2396 tl.to(children, { 2397 duration: MOBILE_ACTIONS.duration, 2398 autoAlpha: 1, 2399 y: 0, 2400 ease: MOBILE_ACTIONS.ease, 2401 stagger: MOBILE_ACTIONS.stagger 2402 }, '<'); 2403 } 2404 } else { 2405 tl.addLabel('secondary', `-=${DESKTOP_GAP}`); 2406 descriptions.forEach(desc => { 2407 if (desc) { 2408 tl.to(desc, { 2409 duration: DESCRIPTION.duration, 2410 autoAlpha: 1, 2411 y: 0, 2412 ease: DESCRIPTION.ease 2413 }, 'secondary'); 2414 } 2415 }); 2416 if (annSlider) { 2417 tl.to(annSlider, { 2418 duration: ANN.duration, 2419 autoAlpha: 1, 2420 y: 0, 2421 ease: ANN.ease 2422 }, `secondary+=${DESCRIPTION.duration + ANN.gap}`); 2423 tl.set(annSlider, { clearProps: 'will-change' }); 2424 } 2425 const actionButton = playButton || audioButton; 2426 if (actionButton) { 2427 tl.to(actionButton, { 2428 duration: BUTTON.duration, 2429 opacity: 1, 2430 scale: 1, 2431 ease: BUTTON.ease 2432 }, 'secondary'); 2433 } 2434 if (navigation) { 2435 const navButtons = navigation.querySelectorAll('.arrow-navigation__arrow'); 2436 if (navButtons.length) { 2437 const navTiming = annSlider && annSlider.contains(navigation) 2438 ? `secondary+=${DESCRIPTION.duration + ANN.gap}` 2439 : 'secondary'; 2440 tl.to(navigation, { opacity: 1, duration: 0.01 }, navTiming); 2441 tl.to(navButtons, { 2442 duration: NAVIGATION.duration, 2443 opacity: 1, 2444 x: 0, 2445 ease: NAVIGATION.ease, 2446 stagger: NAVIGATION.stagger 2447 }, navTiming); 2448 } 2449 } 2450 } 2451 }; 2452 if (el.hasAttribute('data-gsap-wait-loaded') && !document.body.classList.contains('loaded')) { 2453 const obs = new MutationObserver(() => { 2454 if (document.body.classList.contains('loaded')) { obs.disconnect(); play(); } 2455 }); 2456 obs.observe(document.body, { attributes: true, attributeFilter: ['class'] }); 2457 return; 2458 } 2459 play(); 2460 }; 2461 if (document.fonts && document.fonts.ready) { 2462 document.fonts.ready.then(setup); 2463 } else { 2464 setup(); 2465 } 2466 } 2467 heroLearning(el, config) { 2468 if (!el || el.dataset.heroAnimated === 'true') return; 2469 const slide = this.getActiveHeroSlide(el); 2470 if (!slide) return; 2471 const prefersReducedMotion = window.matchMedia 2472 ? window.matchMedia('(prefers-reduced-motion: reduce)').matches 2473 : false; 2474 const elements = { 2475 title: slide.querySelector('.hero__title-line'), 2476 script: slide.querySelector('.hero__title-script'), 2477 main: slide.querySelector('.hero__title-main'), 2478 description: slide.querySelector('.hero__description') 2479 }; 2480 if (elements.title) { 2481 elements.title.style.opacity = '1'; 2482 } 2483 if (elements.script) { 2484 elements.script.style.display = 'block'; 2485 elements.script.style.overflow = 'visible'; 2486 } 2487 if (elements.main) { 2488 elements.main.style.display = 'block'; 2489 elements.main.style.overflow = 'visible'; 2490 } 2491 const HERO_BOLD_ANIM = { 2492 yFromPercent: 100, 2493 duration: 1.2, 2494 stagger: 0.1, 2495 ease: 'power4.out' 2496 }; 2497 const scriptWrite = elements.script ? this.prepareHeroScriptWriting(elements.script) : null; 2498 const boldChars = elements.main 2499 ? this.splitHeroChars(elements.main, { includeInner: true, includeSrOnly: true }) 2500 : []; 2501 if (elements.title) { 2502 gsap.set(elements.title, { perspective: 800 }); 2503 } 2504 if (scriptWrite && scriptWrite.text) { 2505 const clipStart = 'inset(-0.4em 100% -0.4em -0.3em)'; 2506 gsap.set(scriptWrite.text, { 2507 clipPath: clipStart, 2508 webkitClipPath: clipStart, 2509 opacity: 0, 2510 y: 6, 2511 willChange: 'clip-path, transform, opacity' 2512 }); 2513 } 2514 if (boldChars.length) { 2515 gsap.set(boldChars, { 2516 yPercent: HERO_BOLD_ANIM.yFromPercent, 2517 opacity: 0, 2518 willChange: 'transform, opacity' 2519 }); 2520 } 2521 if (elements.description) { 2522 gsap.set(elements.description, { 2523 opacity: 0, 2524 y: 14, 2525 scale: 0.98, 2526 transformOrigin: 'left top', 2527 willChange: 'transform, opacity' 2528 }); 2529 } 2530 if (prefersReducedMotion) { 2531 const reducedTargets = [elements.title, elements.description].filter(
2531Boolean); 2532 if (reducedTargets.length) { 2533 gsap.set(reducedTargets, { opacity: 1, clearProps: 'transform' }); 2534 } 2535 if (scriptWrite && scriptWrite.text) { 2536 gsap.set(scriptWrite.text, { 2537 opacity: 1, 2538 y: 0, 2539 clearProps: 'clip-path,webkitClipPath,transform,opacity' 2540 }); 2541 } 2542 if (boldChars.length) { 2543 gsap.set(boldChars, { 2544 yPercent: 0, 2545 opacity: 1, 2546 clearProps: 'will-change' 2547 }); 2548 } 2549 el.dataset.heroAnimated = 'true'; 2550 return; 2551 } 2552 if (!scriptWrite && !boldChars.length && !elements.description) { 2553 el.dataset.heroAnimated = 'true'; 2554 return; 2555 } 2556 const delay = el.hasAttribute('data-gsap-delay') ? config.delay : 0; 2557 const tl = gsap.timeline({ 2558 defaults: { ease: 'power2.out' }, 2559 delay 2560 }); 2561 const scriptDuration = 1.1; 2562 const scriptGap = 0.2; 2563 const scriptEnd = scriptWrite ? scriptDuration + scriptGap : 0; 2564 if (scriptWrite && scriptWrite.text) { 2565 const clipEnd = 'inset(-0.4em 0% -0.4em -0.3em)'; 2566 tl.to(scriptWrite.text, { 2567 duration: scriptDuration, 2568 ease: 'power2.out', 2569 opacity: 1, 2570 y: 0, 2571 clipPath: clipEnd, 2572 webkitClipPath: clipEnd 2573 }, 0); 2574 } 2575 if (boldChars.length) { 2576 tl.add(() => { 2577 gsap.killTweensOf(boldChars); 2578 gsap.to(boldChars, { 2579 duration: HERO_BOLD_ANIM.duration, 2580 opacity: 1, 2581 ease: HERO_BOLD_ANIM.ease, 2582 yPercent: 0, 2583 stagger: HERO_BOLD_ANIM.stagger, 2584 onComplete: () => { 2585 gsap.set(boldChars, { clearProps: 'will-change' }); 2586 } 2587 }); 2588 }, scriptEnd); 2589 } 2590 if (elements.description) { 2591 tl.to(elements.description, { 2592 opacity: 1, 2593 y: 0, 2594 scale: 1, 2595 duration: 0.9, 2596 ease: 'power2.out' 2597 }, scriptEnd ? scriptEnd + 0.25 : 0.2); 2598 } 2599 const clearTargets = []; 2600 if (scriptWrite && scriptWrite.text) { 2601 clearTargets.push(scriptWrite.text); 2602 } 2603 if (elements.description) { 2604 clearTargets.push(elements.description); 2605 } 2606 if (clearTargets.length) { 2607 tl.set(clearTargets, { clearProps: 'will-change' }); 2608 } 2609 gsap.delayedCall(4, () => { 2610 if (el.dataset.heroAnimated === 'true') return; 2611 if (scriptWrite && scriptWrite.text) { 2612 gsap.set(scriptWrite.text, { 2613 opacity: 1, 2614 y: 0, 2615 clearProps: 'clip-path,webkitClipPath,transform,opacity' 2616 }); 2617 } 2618 if (boldChars.length) { 2619 gsap.set(boldChars, { 2620 yPercent: 0, 2621 opacity: 1, 2622 clearProps: 'will-change' 2623 }); 2624 } 2625 if (elements.description) { 2626 gsap.set(elements.description, { opacity: 1, clearProps: 'transform' }); 2627 } 2628 el.dataset.heroAnimated = 'true'; 2629 }); 2630 tl.eventCallback('onComplete', () => { 2631 el.dataset.heroAnimated = 'true'; 2632 }); 2633 } 2634 heroWellbeing(el, config) { 2635 if (!el) return; 2636 const textBlocks = el.querySelectorAll('.wellbeing-hero-text'); 2637 if (!textBlocks.length) return; 2638 const prefersReducedMotion = window.matchMedia 2639 ? window.matchMedia('(prefers-reduced-motion: reduce)').matches 2640 : false; 2641 const allChars = []; 2642 textBlocks.forEach((block) => { 2643 const chars = this.splitWellbeingChars(block); 2644 block.style.opacity = '1'; 2645 if (chars.length) { 2646 allChars.push(...chars); 2647 } 2648 }); 2649 if (!allChars.length) return; 2650 if (prefersReducedMotion || typeof ScrollTrigger === 'undefined') { 2651 allChars.forEach((char) => { 2652 char.style.opacity = '1'; 2653 char.style.transform = 'none'; 2654 }); 2655 return; 2656 } 2657 const hasStartAttr = el.hasAttribute('data-gsap-start'); 2658 const hasDurationAttr = el.hasAttribute('data-gsap-duration'); 2659 const hasStaggerAttr = el.hasAttribute('data-gsap-stagger'); 2660 const hasEaseAttr = el.hasAttribute('data-gsap-ease'); 2661 const hasDelayAttr = el.hasAttribute('data-gsap-delay'); 2662 const start = hasStartAttr ? config.start : 'top 80%'; 2663 const duration = hasDurationAttr && Number.isFinite(config.duration) ? config.duration : 0.6; 2664 const stagger = hasStaggerAttr && Number.isFinite(config.stagger) ? config.stagger : 0.05; 2665 const ease = hasEaseAttr ? config.ease : 'power2.out';
2666 const delay = hasDelayAttr && Number.isFinite(config.delay) ? config.delay : 0; 2667 gsap.set(allChars, { opacity: 0, y: 20 }); 2668 gsap.to(allChars, { 2669 scrollTrigger: { 2670 trigger: el, 2671 start, 2672 toggleActions: 'play reverse play reverse' 2673 }, 2674 opacity: 1, 2675 y: 0, 2676 stagger, 2677 duration, 2678 ease, 2679 delay 2680 }); 2681 } 2682 maskedImageContent(el, config) { 2683 if (!el) return; 2684 const TRIGGER_START = 'top 50%'; 2685 const TRIGGER_START_MOBILE = 'top 80%'; 2686 const imageWrap = el.querySelector('.masked-image-content-block-wrap'); 2687 const contentBlock = el.querySelector('.masked-image-content-block__content'); 2688 const scriptText = el.querySelector('.masked-image-content-block__title-script'); 2689 const boldText = el.querySelector('.masked-image-content-block__title-bold'); 2690 const description = el.querySelector('.masked-image-content-block__description'); 2691 const cta = el.querySelector('.masked-image-content-block__cta'); 2692 const SCRIPT = { duration: 0.18, stagger: 0.022, ease: 'power2.out' }; 2693 const BOLD_CHARS = { duration: 0.35, stagger: 0.01, ease: 'expo.out' }; 2694 const scriptChars = scriptText ? this.splitChars(scriptText) : []; 2695 const boldChars = boldText ? this.splitChars(boldText) : []; 2696 if (imageWrap) { 2697 gsap.set(imageWrap, { scale: 0.7, autoAlpha: 0 }); 2698 } 2699 if (scriptChars.length) { 2700 gsap.set(scriptChars, { opacity: 0, y: 8 }); 2701 } 2702 if (boldChars.length) { 2703 gsap.set(boldChars, { opacity: 0, yPercent: 90, rotateX: 70, transformOrigin: '50% 100%' }); 2704 } 2705 if (description) { 2706 gsap.set(description, { y: 50, autoAlpha: 0 }); 2707 } 2708 if (cta) { 2709 gsap.set(cta, { 2710 autoAlpha: 1, 2711 clipPath: 'inset(0 100% 0 0)', 2712 webkitClipPath: 'inset(0 100% 0 0)', 2713 willChange: 'clip-path' 2714 }); 2715 let ctaText = cta.querySelector('span'); 2716 if (!ctaText && cta.textContent) { 2717 const text = cta.textContent.trim(); 2718 cta.innerHTML = `<span>${text}</span>`; 2719 ctaText = cta.querySelector('span'); 2720 } 2721 if (ctaText) { 2722 gsap.set(ctaText, { 2723 clipPath: 'inset(0 100% 0 0)', 2724 webkitClipPath: 'inset(0 100% 0 0)', 2725 display: 'inline-block', 2726 willChange: 'clip-path' 2727 }); 2728 } 2729 } 2730 const isMobile = window.matchMedia('(max-width: 1024px)').matches || window.matchMedia('(pointer: coarse)').matches; 2731 const mobileStart = TRIGGER_START_MOBILE; 2732 if (isMobile) { 2733 if (imageWrap) { 2734 gsap.to(imageWrap, { 2735 scale: 1, 2736 autoAlpha: 1, 2737 duration: 1.3, 2738 ease: 'back.out(1.1)', 2739 scrollTrigger: { 2740 trigger: imageWrap, 2741 start: mobileStart, 2742 toggleActions: 'play none none none' 2743 }, 2744 delay: config.delay || 0 2745 }); 2746 } 2747 if (contentBlock) { 2748 const textDuration = 1; 2749 const stagger = 0.2; 2750 const textEase = 'power2.out'; 2751 const contentTl = gsap.timeline({ 2752 scrollTrigger: { 2753 trigger: contentBlock, 2754 start: mobileStart, 2755 toggleActions: 'play none none none' 2756 }, 2757 delay: config.delay || 0 2758 }); 2759 if (scriptChars.length) { 2760 contentTl.to(scriptChars, { 2761 opacity: 1, 2762 y: 0, 2763 duration: SCRIPT.duration, 2764 ease: SCRIPT.ease, 2765 stagger: SCRIPT.stagger, 2766 onComplete: () => gsap.set(scriptChars, { clearProps: 'transform,will-change' }) 2767 }, 0); 2768 } 2769 const scriptDurationMobile = scriptChars.length ? SCRIPT.duration + SCRIPT.stagger * (scriptChars.length - 1) : 0; 2770 if (boldChars.length) { 2771 contentTl.to(boldChars, { 2772 opacity: 1, 2773 yPercent: 0, 2774 rotateX: 0, 2775 duration: BOLD_CHARS.duration, 2776 ease: BOLD_CHARS.ease, 2777 stagger: BOLD_CHARS.stagger, 2778 onComplete: () => gsap.set(boldChars, { clearProps: 'transform,will-change' }) 2779 }, scriptDurationMobile * 0.9); 2780 } 2781 const boldDurationMobile = boldChars.length ? BOLD_CHARS.duration + BOLD_CHARS.stagger * (boldChars.length - 1) : 0; 2782 const descStartMobile = scriptDurationMobile * 0.9 + boldDurationMobile * 0.5; 2783 if (description) { 2784 contentTl.to(description, { 2785 y: 0, 2786 autoAlpha: 1, 2787 duration: textDuration, 2788 ease: textEase 2789 }, descStartMobile); 2790 } 2791 if (cta) { 2792 const ctaText = cta.querySelector('span'); 2793 contentTl.to(cta, { 2794 clipPath: 'inset(0 0% 0 0)', 2795 webkitClipPath: 'inset(0 0% 0 0)', 2796 duration: 0.6, 2797 ease: 'none', 2798 force3D: true, 2799 autoRound: false 2800 }, stagger * 3); 2801 if (ctaText) { 2802 contentTl.to(ctaText, { 2803 clipPath: 'inset(0 0% 0 0)', 2804 webkitClipPath: 'inset(0 0% 0 0)', 2805 duration: 0.9, 2806 ease: 'none', 2807 force3D: true, 2808 autoRound: false 2809 }, stagger * 3 + 0.65); 2810 } 2811 } 2812 } 2813 } else { 2814 const startPosition = TRIGGER_START; 2815 const desktopDelay = config.delay || 0; 2816 const tl = gsap.timeline({ 2817 scrollTrigger: { 2818 trigger: el, 2819 start: startPosition, 2820 toggleActions: 'play none none none' 2821 }, 2822 delay: desktopDelay 2823 }); 2824 const imageDuration = 1.3; 2825 const textDuration = 1; 2826 const stagger = 0.3; 2827 const textEase = 'power2.out'; 2828 if (imageWrap) { 2829 tl.to(imageWrap, { 2830 scale: 1, 2831 autoAlpha: 1, 2832 duration: imageDuration, 2833 ease: 'back.out(1.1)' 2834 }, 0); 2835 } 2836 const scriptStart = imageDuration * 0.3; 2837 if (scriptChars.length) { 2838 tl.to(scriptChars, { 2839 opacity: 1, 2840 y: 0, 2841 duration: SCRIPT.duration, 2842 ease: SCRIPT.ease, 2843 stagger: SCRIPT.stagger, 2844 onComplete: () => gsap.set(scriptChars, { clearProps: 'transform,will-change' }) 2845 }, scriptStart); 2846 } 2847 const scriptDuration = scriptChars.length ? SCRIPT.duration + SCRIPT.stagger * (scriptChars.length - 1) : 0; 2848 const boldStart = scriptStart + scriptDuration * 0.9; 2849 if (boldChars.length) {
2850 tl.to(boldChars, { 2851 opacity: 1, 2852 yPercent: 0, 2853 rotateX: 0, 2854 duration: BOLD_CHARS.duration, 2855 ease: BOLD_CHARS.ease, 2856 stagger: BOLD_CHARS.stagger, 2857 onComplete: () => gsap.set(boldChars, { clearProps: 'transform,will-change' }) 2858 }, boldStart); 2859 } 2860 const boldDuration = boldChars.length ? BOLD_CHARS.duration + BOLD_CHARS.stagger * (boldChars.length - 1) : 0; 2861 const descStart = boldStart + boldDuration * 0.5; 2862 const ctaStart = descStart + stagger; 2863 if (description) { 2864 tl.to(description, { 2865 y: 0, 2866 autoAlpha: 1, 2867 duration: textDuration, 2868 ease: textEase 2869 }, descStart); 2870 } 2871 if (cta) { 2872 const ctaText = cta.querySelector('span'); 2873 tl.to(cta, { 2874 clipPath: 'inset(0 0% 0 0)', 2875 webkitClipPath: 'inset(0 0% 0 0)', 2876 duration: 0.6, 2877 ease: 'none', 2878 force3D: true, 2879 autoRound: false 2880 }, ctaStart); 2881 if (ctaText) { 2882 tl.to(ctaText, { 2883 clipPath: 'inset(0 0% 0 0)', 2884 webkitClipPath: 'inset(0 0% 0 0)', 2885 duration: 0.9, 2886 ease: 'none', 2887 force3D: true, 2888 autoRound: false 2889 }, ctaStart + 0.65); 2890 } 2891 } 2892 } 2893 } 2894 threeCampuses(el, config) { 2895 if (!el) return; 2896 const T = { 2897 titleStart: config.start || 'top 55%', 2898 cardsStart: config.cardsStart || 'top 55%', 2899 mobileCardStart: config.mobileCardsStart || 'top 45%', 2900 titleScriptDuration: 0.7, 2901 titleBold: { duration: 0.9, stagger: 0.02, start: 0.3, ease: 'expo.out' }, 2902 description: { start: 1.2, duration: 0.8, y: 50, ease: 'power2.out' }, 2903 cardIn: { x: 700, y: -300, rotation: 85, duration: 1.1, ease: 'power3.out' }, 2904 cardInMobile: { xEven: 400, xOdd: -400, y: -200, rotationEven: 45, rotationOdd: -45, duration: 1.1, ease: 'power3.out' }, 2905 contentIn: { scaleFromDesktop: 0.5, scaleFromMobile: 0.5, duration: 0.5, ease: 'power2.out' }, 2906 contentStartOffset: -0.9, 2907 desktopGapAfterContent: -0.2 2908 }; 2909 const mobileContentStartOffset = -0.6; 2910 const contentPos = T.contentStartOffset >= 0 2911 ? `>+=${T.contentStartOffset}` 2912 : `>-${Math.abs(T.contentStartOffset)}`; 2913 const mobileContentPos = mobileContentStartOffset >= 0 2914 ? `>+=${mobileContentStartOffset}` 2915 : `>-${Math.abs(mobileContentStartOffset)}`; 2916 const titleWrapper = el.querySelector('.three-campuses-section__title-wrapper'); 2917 const description = titleWrapper?.querySelector('.lead') || el.querySelector('.lead, .three-campuses-section__description'); 2918 const cards = Array.from(el.querySelectorAll('.three-campuses-section__card')) || []; 2919 const isMobile = window.matchMedia('(max-width: 1024px)').matches || window.matchMedia('(pointer: coarse)').matches; 2920 if (description) gsap.set(description, { y: T.description.y, autoAlpha: 0, willChange: 'transform,opacity' }); 2921 cards.forEach((card, i) => { 2922 card.style.pointerEvents = 'none'; 2923 const content = card.querySelector('.three-campuses-section__card-content'); 2924 if (isMobile) { 2925 const isEven = i % 2 === 0; 2926 gsap.set(card, { 2927 x: isEven ? T.cardInMobile.xEven : T.cardInMobile.xOdd, 2928 y: T.cardInMobile.y, 2929 rotation: isEven ? T.cardInMobile.rotationEven : T.cardInMobile.rotationOdd, 2930 autoAlpha: 0, 2931 transformOrigin: '50% 50%', 2932 willChange: 'transform,opacity' 2933 }); 2934 if (content) { 2935 gsap.set(content, { 2936 scale: T.contentIn.scaleFromMobile, 2937 autoAlpha: 0, 2938 transformOrigin: '50% 50%', 2939 willChange: 'transform,opacity' 2940 }); 2941 } 2942 } else { 2943 gsap.set(card, { 2944 x: T.cardIn.x, 2945 y: T.cardIn.y, 2946 rotation: T.cardIn.rotation, 2947 autoAlpha: 0, 2948 transformOrigin: '50% 50%', 2949 willChange: 'transform,opacity' 2950 }); 2951 if (content) { 2952 gsap.set(content, { 2953 scale: T.contentIn.scaleFromDesktop, 2954 autoAlpha: 0, 2955 transformOrigin: '50% 50%', 2956 willChange: 'transform,opacity' 2957 }); 2958 } 2959 } 2960 });
2961 const titleTl = gsap.timeline({ 2962 scrollTrigger: { trigger: el, start: T.titleStart, toggleActions: 'play none none none' }, 2963 delay: config.delay || 0 2964 }); 2965 if (titleWrapper) { 2966 const script = titleWrapper.querySelector('[class*="script"]'); 2967 const bold = titleWrapper.querySelector('[class*="bold"]'); 2968 if (script) { 2969 const scriptWrite = this.prepareHeroScriptWriting(script); 2970 if (scriptWrite?.text) { 2971 gsap.set(scriptWrite.text, { clipPath: 'inset(-0.4em 100% -0.4em -0.3em)', opacity: 0 }); 2972 titleTl.to(scriptWrite.text, { duration: T.titleScriptDuration, ease: 'power2.out', opacity: 1, clipPath: 'inset(-0.4em 0% -0.4em -0.3em)' }, 0); 2973 } 2974 } 2975 if (bold) { 2976 const chars = this.splitChars(bold); 2977 if (chars.length) { 2978 gsap.set(chars, { opacity: 0, yPercent: 90, rotateX: 70, transformOrigin: '50% 100%' }); 2979 titleTl.to(chars, { duration: T.titleBold.duration, ease: T.titleBold.ease, opacity: 1, yPercent: 0, rotateX: 0, stagger: T.titleBold.stagger }, T.titleBold.start); 2980 } 2981 } 2982 } 2983 if (description) { 2984 titleTl.to(description, { y: 0, autoAlpha: 1, duration: T.description.duration, ease: T.description.ease }, T.description.start); 2985 } 2986 if (isMobile) { 2987 cards.forEach((card, i) => { 2988 const content = card.querySelector('.three-campuses-section__card-content'); 2989 const cardTl = gsap.timeline({ 2990 scrollTrigger: { 2991 trigger: card, 2992 start: T.mobileCardStart, 2993 toggleActions: 'play none none none', 2994 id: `mobile-campus-card-${i}` 2995 } 2996 }); 2997 cardTl.to(card, { 2998 x: 0, 2999 y: 0, 3000 rotation: 0, 3001 autoAlpha: 1, 3002 duration: T.cardInMobile.duration, 3003 ease: T.cardInMobile.ease 3004 }); 3005 if (content) { 3006 cardTl.to(content, { 3007 scale: 1, 3008 autoAlpha: 1, 3009 duration: T.contentIn.duration, 3010 ease: T.contentIn.ease, 3011 onComplete: () => { 3012 card.style.pointerEvents = ''; 3013 } 3014 }, mobileContentPos); 3015 } else { 3016 cardTl.eventCallback('onComplete', () => { 3017 card.style.pointerEvents = ''; 3018 }); 3019 } 3020 }); 3021 } else { 3022 const cardsTrigger = el.querySelector('.image-cards-row') || cards[0] || el; 3023 const cardsTl = gsap.timeline({ 3024 scrollTrigger: { trigger: cardsTrigger, start: T.cardsStart, toggleActions: 'play none none none' }, 3025 onComplete: () => { 3026 cards.forEach(card => { 3027 card.style.pointerEvents = ''; 3028 }); 3029 } 3030 }); 3031 let t = 0; 3032 cards.forEach((card) => { 3033 const content = card.querySelector('.three-campuses-section__card-content'); 3034 cardsTl.to(card, { 3035 x: 0, 3036 y: 0, 3037 rotation: 0, 3038 autoAlpha: 1, 3039 duration: T.cardIn.duration, 3040 ease: T.cardIn.ease 3041 }, t); 3042 t += Math.max(0, T.cardIn.duration + T.contentStartOffset); 3043 if (content) { 3044 cardsTl.to(content, { 3045 scale: 1, 3046 autoAlpha: 1, 3047 duration: T.contentIn.duration, 3048 ease: T.contentIn.ease 3049 }, t); 3050 t += T.contentIn.duration + T.desktopGapAfterContent; 3051 } 3052 }); 3053 } 3054 } 3055 normalizeWhitespace(text) { 3056 return text.replace(/\s+/g, ' ').trim(); 3057 } 3058 getActiveHeroSlide(el) { 3059 if (!el) return null; 3060 const heroSlider = el.querySelector('.hero__slider'); 3061 if (!heroSlider) return null; 3062 return heroSlider.querySelector('.swiper-slide-active') || heroSlider.querySelector('.swiper-slide'); 3063 } 3064 splitHeroChars(element, options = {}) { 3065 if (!element) return []; 3066 if (element.dataset.split === 'true' && Array.isArray(element.__heroChars)) { 3067 return element.__heroChars; 3068 } 3069 const text = this.normalizeWhitespace(element.textContent || ''); 3070 if (!text) return []; 3071 const savedNodes = Array.from(element.childNodes); 3072 element.textContent = ''; 3073 element.dataset.split = 'true'; 3074 element.classList.add('hero-anim-text'); 3075 element.setAttribute('role', 'text'); 3076 element.setAttribute('aria-label', text); 3077 if (options.includeSrOnly) { 3078 const srText = document.createElement('span'); 3079 srText.className = options.srClass || 'visually-hidden'; 3080 srText.textContent = text; 3081 element.appendChild(srText); 3082 } 3083 const container = options.includeInner ? document.createElement('span') : element; 3084 if (options.includeInner) { 3085 container.className = 'hero-anim-text__inner'; 3086 container.setAttribute('aria-hidden', 'true'); 3087 element.appendChild(container); 3088 } 3089 const chars = [];
3090 let currentWordEl = null; 3091 const processNode = (node) => { 3092 if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BR') { 3093 currentWordEl = null; 3094 container.appendChild(document.createElement('br')); 3095 } else if (node.nodeType === Node.TEXT_NODE) { 3096 const tokens = node.textContent.split(/(\s+)/); 3097 tokens.forEach(token => { 3098 if (!token) return; 3099 if (/^\s+$/.test(token)) { 3100 currentWordEl = null; 3101 container.appendChild(document.createTextNode(' ')); 3102 return; 3103 } 3104 if (!currentWordEl) { 3105 currentWordEl = document.createElement('span'); 3106 currentWordEl.className = 'hero-anim-text__word'; 3107 currentWordEl.setAttribute('aria-hidden', 'true'); 3108 container.appendChild(currentWordEl); 3109 } 3110 Array.from(token).forEach(char => { 3111 const charEl = document.createElement('span'); 3112 charEl.className = 'hero-anim-text__char'; 3113 charEl.textContent = char; 3114 charEl.setAttribute('aria-hidden', 'true'); 3115 currentWordEl.appendChild(charEl); 3116 chars.push(charEl); 3117 }); 3118 }); 3119 } else if (node.nodeType === Node.ELEMENT_NODE) { 3120 Array.from(node.childNodes).forEach(processNode); 3121 } 3122 }; 3123 savedNodes.forEach(processNode); 3124 element.__heroChars = chars; 3125 return chars; 3126 } 3127 prepareHeroScriptWriting(element) { 3128 if (!element || element.dataset.scriptWrite === 'true') return null; 3129 const text = this.normalizeWhitespace(element.textContent || ''); 3130 if (!text) return null; 3131 const savedHTML = element.innerHTML; // preserve <br> and inline tags 3132 element.textContent = ''; 3133 element.dataset.scriptWrite = 'true'; 3134 element.setAttribute('aria-label', text); // plain text for accessibility 3135 element.setAttribute('role', 'text'); 3136 const textSpan = document.createElement('span'); 3137 textSpan.className = 'hero-script-text'; 3138 textSpan.innerHTML = savedHTML; // use innerHTML to keep <br> 3139 textSpan.setAttribute('aria-hidden', 'true'); 3140 element.appendChild(textSpan); 3141 return { container: element, text: textSpan }; 3142 } 3143 splitWellbeingChars(element) { 3144 if (!element) return []; 3145 if (element.querySelector('.wellbeing-hero-char')) { 3146 return Array.from(element.querySelectorAll('.wellbeing-hero-char')); 3147 } 3148 const text = element.textContent || ''; 3149 const tokens = text.split(/(\s+)/); 3150 const fragment = document.createDocumentFragment(); 3151 tokens.forEach((token) => { 3152 if (!token) return; 3153 if (/^\s+$/.test(token)) { 3154 fragment.appendChild(document.createTextNode(token)); 3155 return; 3156 } 3157 const word = document.createElement('span'); 3158 word.className = 'wellbeing-hero-word'; 3159 for (let i = 0; i < token.length; i += 1) { 3160 const span = document.createElement('span'); 3161 span.className = 'wellbeing-hero-char'; 3162 span.textContent = token[i]; 3163 word.appendChild(span); 3164 } 3165 fragment.appendChild(word); 3166 }); 3167 element.textContent = ''; 3168 element.appendChild(fragment); 3169 return Array.from(element.querySelectorAll('.wellbeing-hero-char')); 3170 } 3171 newsCardsClip(el, config) { 3172 if (!el) return; 3173 const T = { 3174 duration: 0.5, 3175 stagger: 0.2, 3176 ease: 'none', 3177 start: 'top 60%', 3178 yOffset: 30 3179 }; 3180 const isImageWrapper = el.classList.contains('news-detail-content-section__image-wrapper'); 3181 const cardItems = Array.from(el.querySelectorAll('.latest-news-item, .contact-card-item')); 3182 const imageFrames = Array.from(el.querySelectorAll('.image-frame')); 3183 if (isImageWrapper && cardItems.length === 0 && imageFrames.length === 0) { 3184 gsap.set(el, { 3185 clipPath: 'inset(0 0 100% 0)', 3186 webkitClipPath: 'inset(0 0 100% 0)', 3187 y: T.yOffset, 3188 willChange: 'transform, clip-path' 3189 }); 3190 gsap.to(el, { 3191 clipPath: 'inset(0% 0 0% 0)', 3192 webkitClipPath: 'inset(0% 0 0% 0)', 3193 y: 0, 3194 duration: T.duration, 3195 ease: T.ease, 3196 scrollTrigger: { 3197 trigger: el, 3198 start: T.start, 3199 toggleActions: 'play none none none' 3200 }, 3201 onComplete: () => { 3202 gsap.set(el, { clearProps: 'will-change,clip-path,webkitClipPath' });
3203 } 3204 }); 3205 return; 3206 } 3207 if (imageFrames.length > 0 && cardItems.length === 0) { 3208 imageFrames.forEach(frame => { 3209 gsap.set(frame, { 3210 clipPath: 'inset(0 0 100% 0)', 3211 webkitClipPath: 'inset(0 0 100% 0)', 3212 y: T.yOffset, 3213 willChange: 'transform, clip-path' 3214 }); 3215 }); 3216 const groupFramesByRow = (frames) => { 3217 if (frames.length === 0) return []; 3218 const tolerance = 10; 3219 const rows = []; 3220 let currentRow = [frames[0]]; 3221 let currentTop = frames[0].getBoundingClientRect().top; 3222 for (let i = 1; i < frames.length; i++) { 3223 const frameTop = frames[i].getBoundingClientRect().top; 3224 if (Math.abs(frameTop - currentTop) <= tolerance) { 3225 currentRow.push(frames[i]); 3226 } else { 3227 rows.push(currentRow); 3228 currentRow = [frames[i]]; 3229 currentTop = frameTop; 3230 } 3231 } 3232 if (currentRow.length > 0) { 3233 rows.push(currentRow); 3234 } 3235 return rows; 3236 }; 3237 const rows = groupFramesByRow(imageFrames); 3238 rows.forEach((row, rowIndex) => { 3239 const triggerElement = row[0]; 3240 gsap.to(row, { 3241 clipPath: 'inset(0% 0 0% 0)', 3242 webkitClipPath: 'inset(0% 0 0% 0)', 3243 y: 0, 3244 duration: T.duration, 3245 ease: T.ease, 3246 stagger: T.stagger, 3247 scrollTrigger: { 3248 trigger: triggerElement, 3249 start: T.start, 3250 toggleActions: 'play none none none' 3251 }, 3252 onComplete: () => { 3253 if (rowIndex === rows.length - 1) { 3254 imageFrames.forEach(frame => { 3255 gsap.set(frame, { clearProps: 'will-change,clip-path,webkitClipPath' }); 3256 }); 3257 } 3258 } 3259 }); 3260 }); 3261 return; 3262 } 3263 if (cardItems.length === 0) return; 3264 const groupCardsByRow = (cards) => { 3265 if (cards.length === 0) return []; 3266 const tolerance = 10; 3267 const rows = []; 3268 let currentRow = [cards[0]]; 3269 let currentTop = cards[0].getBoundingClientRect().top; 3270 for (let i = 1; i < cards.length; i++) { 3271 const cardTop = cards[i].getBoundingClientRect().top; 3272 if (Math.abs(cardTop - currentTop) <= tolerance) { 3273 currentRow.push(cards[i]); 3274 } else { 3275 rows.push(currentRow); 3276 currentRow = [cards[i]]; 3277 currentTop = cardTop; 3278 } 3279 } 3280 if (currentRow.length > 0) { 3281 rows.push(currentRow); 3282 } 3283 return rows; 3284 }; 3285 const rows = groupCardsByRow(cardItems); 3286 const cardClipStart = 'inset(-12% -8% 110% -8%)'; 3287 const cardClipEnd = 'inset(-12% -8% 0% -8%)'; 3288 cardItems.forEach(card => { 3289 const cardElement = card.querySelector('.latest-news-grid-section__card, .contact-card') || card; 3290 gsap.set(cardElement, { 3291 clipPath: cardClipStart, 3292 webkitClipPath: cardClipStart, 3293 y: T.yOffset, 3294 willChange: 'transform, clip-path' 3295 }); 3296 }); 3297 rows.forEach((row, rowIndex) => { 3298 const triggerElement = row[0]; 3299 const rowCardElements = row.map(card => card.querySelector('.latest-news-grid-section__card, .contact-card') || card); 3300 gsap.to(rowCardElements, { 3301 clipPath: cardClipEnd, 3302 webkitClipPath: cardClipEnd, 3303 y: 0, 3304 duration: T.duration, 3305 ease: T.ease, 3306 stagger: T.stagger, 3307 scrollTrigger: { 3308 trigger: triggerElement, 3309 start: T.start, 3310 toggleActions: 'play none none none' 3311 }, 3312 onComplete: () => { 3313 if (rowIndex === rows.length - 1) { 3314 cardItems.forEach(card => { 3315 const cardElement = card.querySelector('.latest-news-grid-section__card, .contact-card') || card; 3316 gsap.set(cardElement, { clearProps: 'will-change,clip-path,webkitClipPath' }); 3317 }); 3318 } 3319 } 3320 }); 3321 }); 3322 } 3323 btnClipReveal(el, config) { 3324 if (!el) return; 3325 const T = { 3326 fillDuration: 0.6, 3327 textDuration: 0.9, 3328 textDelay: 0.65, 3329 ease: 'power1.out', 3330 start: config.start || 'top 80%' 3331 }; 3332 const btn = el.tagName === 'A' || el.tagName === 'BUTTON' ? el : el.querySelector('a, button'); 3333 if (!btn) return; 3334 gsap.set(btn, { 3335 autoAlpha: 1, 3336 clipPath: 'inset(0 100% 0 0)', 3337 webkitClipPath: 'inset(0 100% 0 0)', 3338 willChange: 'clip-path' 3339 });
3340 let btnText = btn.querySelector('span'); 3341 if (!btnText && btn.textContent) { 3342 const text = btn.textContent.trim(); 3343 btn.innerHTML = `<span>${text}</span>`; 3344 btnText = btn.querySelector('span'); 3345 } 3346 if (btnText) { 3347 gsap.set(btnText, { 3348 clipPath: 'inset(0 100% 0 0)', 3349 webkitClipPath: 'inset(0 100% 0 0)', 3350 display: 'inline-block', 3351 willChange: 'clip-path' 3352 }); 3353 } 3354 const tl = gsap.timeline({ 3355 scrollTrigger: { 3356 trigger: btn, 3357 start: T.start, 3358 toggleActions: 'play none none none' 3359 }, 3360 delay: config.delay || 0 3361 }); 3362 tl.to(btn, { 3363 clipPath: 'inset(0 0% 0 0)', 3364 webkitClipPath: 'inset(0 0% 0 0)', 3365 duration: T.fillDuration, 3366 ease: 'none', 3367 force3D: true, 3368 autoRound: false 3369 }); 3370 if (btnText) { 3371 tl.to(btnText, { 3372 clipPath: 'inset(0 0% 0 0)', 3373 webkitClipPath: 'inset(0 0% 0 0)', 3374 duration: T.textDuration, 3375 ease: 'none', 3376 force3D: true, 3377 autoRound: false 3378 }, T.textDelay); 3379 } 3380 } 3381 splitChars(el) { 3382 if (!el) return []; 3383 if (el.querySelector('.gsap-char')) { 3384 return Array.from(el.querySelectorAll('.gsap-char')); 3385 } 3386 const childNodes = Array.from(el.childNodes); // save before clearing 3387 el.textContent = ''; 3388 const chars = []; 3389 let wordWrapper = null; 3390 const processText = (text) => { 3391 [...text].forEach((char) => { 3392 const isSpace = /\s/.test(char); 3393 if (isSpace) { 3394 wordWrapper = null; 3395 el.appendChild(document.createTextNode(' ')); 3396 return; 3397 } 3398 if (!wordWrapper) { 3399 wordWrapper = document.createElement('span'); 3400 wordWrapper.className = 'gsap-word'; 3401 wordWrapper.style.display = 'inline-block'; 3402 wordWrapper.style.whiteSpace = 'nowrap'; 3403 wordWrapper.style.transformOrigin = '50% 100%'; 3404 el.appendChild(wordWrapper); 3405 } 3406 const span = document.createElement('span'); 3407 span.className = 'gsap-char'; 3408 span.textContent = char; 3409 span.style.display = 'inline-block'; 3410 span.style.transformOrigin = '50% 100%'; 3411 wordWrapper.appendChild(span); 3412 chars.push(span); 3413 }); 3414 }; 3415 childNodes.forEach((node) => { 3416 if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BR') { 3417 wordWrapper = null; 3418 el.appendChild(document.createElement('br')); 3419 } else if (node.nodeType === Node.TEXT_NODE) { 3420 processText(node.textContent); 3421 } 3422 }); 3423 return chars; 3424 } 3425 splitLines(el) { 3426 if (!el) return []; 3427 if (el.querySelector('.gsap-line')) { 3428 return Array.from(el.querySelectorAll('.gsap-line')); 3429 } 3430 const text = el.textContent || ''; 3431 const words = text.split(/\s+/).filter(word => word.length > 0); 3432 el.textContent = ''; 3433 const wordElements = []; 3434 words.forEach((word, index) => { 3435 const span = document.createElement('span'); 3436 span.textContent = word; 3437 span.style.display = 'inline-block'; 3438 wordElements.push(span); 3439 el.appendChild(span); 3440 if (index < words.length - 1) { 3441 el.appendChild(document.createTextNode(' ')); 3442 } 3443 }); 3444 const lines = []; 3445 let currentLine = []; 3446 let currentTop = null; 3447 wordElements.forEach((wordEl) => { 3448 const top = wordEl.offsetTop; 3449 if (currentTop === null || top === currentTop) { 3450 currentLine.push(wordEl); 3451 currentTop = top; 3452 } else { 3453 if (currentLine.length > 0) { 3454 lines.push(currentLine); 3455 } 3456 currentLine = [wordEl]; 3457 currentTop = top; 3458 } 3459 }); 3460 if (currentLine.length > 0) { 3461 lines.push(currentLine); 3462 } 3463 el.textContent = ''; 3464 const lineElements = []; 3465 lines.forEach((lineWords, lineIndex) => { 3466 const lineWrapper = document.createElement('span'); 3467 lineWrapper.className = 'gsap-line'; 3468 lineWrapper.style.display = 'block'; 3469 lineWrapper.style.overflow = 'hidden'; 3470 lineWords.forEach((wordEl, wordIndex) => { 3471 lineWrapper.appendChild(wordEl); 3472 if (wordIndex < lineWords.length - 1) { 3473 lineWrapper.appendChild(document.createTextNode(' ')); 3474 } 3475 }); 3476 el.appendChild(lineWrapper); 3477 lineElements.push(lineWrapper); 3478 });
3479 return lineElements; 3480 } 3481} 3482document.addEventListener('DOMContentLoaded', () => { 3483 new GSAPAnimations(); 3484}); 3485function initFooterParticles() { 3486 const isMobile = window.innerWidth <= 768 || 'ontouchstart' in window || navigator.maxTouchPoints > 0; 3487 if (isMobile) return; 3488 const canvas = document.getElementById('footer-particles-canvas'); 3489 if (!canvas) return; 3490 const ctx = canvas.getContext('2d'); 3491 const footer = document.querySelector('.site-footer'); 3492 if (!footer) return; 3493 const particles = []; 3494 let hue = 0; 3495 let isMouseOverFooter = false; 3496 function resizeCanvas() { 3497 canvas.width = footer.offsetWidth; 3498 canvas.height = footer.offsetHeight; 3499 } 3500 class Particle { 3501 constructor(x, y, hue) { 3502 this.x = x; 3503 this.y = y; 3504 this.size = Math.random() * 6 + 3; 3505 this.speedX = (Math.random() - 0.5) * 3; 3506 this.speedY = (Math.random() - 0.5) * 3; 3507 this.hue = hue; 3508 this.life = 1; 3509 } 3510 update() { 3511 this.x += this.speedX; 3512 this.y += this.speedY; 3513 this.speedX *= 0.96; 3514 this.speedY *= 0.96; 3515 this.life -= 0.008; 3516 this.size *= 0.98; 3517 } 3518 draw() { 3519 ctx.save(); 3520 ctx.globalAlpha = this.life * 0.7; 3521 ctx.fillStyle = `hsl(${this.hue}, 90%, 55%)`; 3522 ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2, this.size, this.size); 3523 ctx.restore(); 3524 } 3525 } 3526 function getMousePositionInFooter(e) { 3527 const rect = footer.getBoundingClientRect(); 3528 return { 3529 x: e.clientX - rect.left, 3530 y: e.clientY - rect.top 3531 }; 3532 } 3533 footer.addEventListener('mouseenter', function () { 3534 isMouseOverFooter = true; 3535 }); 3536 footer.addEventListener('mouseleave', function () { 3537 isMouseOverFooter = false; 3538 }); 3539 footer.addEventListener('mousemove', function (e) { 3540 if (!isMouseOverFooter) return; 3541 const pos = getMousePositionInFooter(e); 3542 for (let i = 0; i < 30; i++) { 3543 particles.push(new Particle(pos.x, pos.y, hue)); 3544 } 3545 hue += 2; 3546 if (hue > 360) hue = 0; 3547 }); 3548 window.addEventListener('resize', resizeCanvas); 3549 function animate() { 3550 ctx.globalCompositeOperation = 'destination-out'; 3551 ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; 3552 ctx.fillRect(0, 0, canvas.width, canvas.height); 3553 ctx.globalCompositeOperation = 'source-over'; 3554 for (let i = particles.length - 1; i >= 0; i--) { 3555 particles[i].update(); 3556 particles[i].draw(); 3557 if (particles[i].life <= 0) { 3558 particles.splice(i, 1); 3559 } 3560 } 3561 requestAnimationFrame(animate); 3562 } 3563 resizeCanvas(); 3564 animate(); 3565} 3566document.addEventListener('DOMContentLoaded', initFooterParticles);
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.