PageSourceSearch

https://www.glenaeon.nsw.edu.au/static/src/scripts/animations.js?v=1.2

js glenaeon.nsw.edu.au collected 2026-10-08 20:52:40 UTC 123,639 bytes, 3,566 lines download raw bytes

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.