@widelab-nc/widehue 1.0.43 → 1.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widelab-nc/widehue",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "Widelab starter template based on Finsweet template + add-ons.",
5
5
  "homepage": "https://widelab.co",
6
6
  "license": "ISC",
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ function customGsapEasing() {
24
24
  // Contact form open animation
25
25
  let contactFormOpen;
26
26
 
27
+
27
28
  function refreshScrollTriggersAfterLoad() {
28
29
  window.addEventListener('load', () => {
29
30
  console.log('[ScrollTrigger] Page fully loaded — refreshing all triggers');
@@ -489,16 +490,12 @@ window.customCursorText = function(e) {
489
490
  });
490
491
  }
491
492
 
493
+
494
+
492
495
  window.heroAnimationOnLoad = function(e) {
493
496
  console.log('heroAnimationOnLoad function started');
494
497
 
495
- let hasPlayedAnimation = sessionStorage.getItem('hasPlayedHeroAnimation');
496
-
497
- if (performance.navigation.type === 1) {
498
- sessionStorage.removeItem('hasPlayedHeroAnimation');
499
- hasPlayedAnimation = null;
500
- console.log('Page reloaded; session flag cleared.');
501
- }
498
+ const hasPlayedAnimation = window.name.includes('heroPlayed');
502
499
 
503
500
  gsap.set('.wideletter', { y: '-150%', scale: 1 });
504
501
 
@@ -530,7 +527,6 @@ window.heroAnimationOnLoad = function(e) {
530
527
  return;
531
528
  }
532
529
 
533
- // Handle video source replacement based on viewport width
534
530
  function updateVideoSources() {
535
531
  const isMobile = window.innerWidth < 992;
536
532
  const mobileMp4 = video.getAttribute('mobile-mp4');
@@ -539,31 +535,22 @@ window.heroAnimationOnLoad = function(e) {
539
535
  const webmSource = video.querySelector('source[type="video/webm"]');
540
536
 
541
537
  if (isMobile) {
542
- if (mobileMp4) {
543
- mp4Source.setAttribute('data-src', mobileMp4);
544
- }
545
- if (mobileWebm) {
546
- webmSource.setAttribute('data-src', mobileWebm);
547
- }
538
+ if (mobileMp4) mp4Source.setAttribute('data-src', mobileMp4);
539
+ if (mobileWebm) webmSource.setAttribute('data-src', mobileWebm);
548
540
  } else {
549
- // Revert to desktop sources if necessary
550
541
  mp4Source.setAttribute('data-src', mp4Source.getAttribute('data-src').replace('mobile-', ''));
551
542
  webmSource.setAttribute('data-src', webmSource.getAttribute('data-src').replace('mobile-', ''));
552
543
  }
553
544
  }
554
545
 
555
- // Initial call to update video sources
556
546
  updateVideoSources();
557
-
558
- // Add event listener for window resize to update video sources
559
547
  window.addEventListener('resize', updateVideoSources);
560
548
 
561
549
  if (video) {
562
- // Prevent the video from autoplaying when lazy-loaded
563
550
  video.setAttribute('data-no-autoplay', 'true');
564
551
  video.addEventListener('loadeddata', () => {
565
552
  if (video.hasAttribute('data-no-autoplay')) {
566
- video.pause(); // Ensure the video is paused if it loads
553
+ video.pause();
567
554
  }
568
555
  });
569
556
  }
@@ -577,7 +564,6 @@ window.heroAnimationOnLoad = function(e) {
577
564
 
578
565
  heroAnimation.to('.navbar_brand_lottie', {
579
566
  duration: 1.5,
580
- // Usuń progress: 1 całkowicie
581
567
  onComplete: function() {
582
568
  console.log('Lottie animation completed');
583
569
  gsap.to('.navbar_brand_lottie', { opacity: 0, duration: 0.25 });
@@ -605,7 +591,7 @@ window.heroAnimationOnLoad = function(e) {
605
591
  ease: 'loader2',
606
592
  }, '+=1');
607
593
 
608
- sessionStorage.setItem('hasPlayedHeroAnimation', 'true');
594
+ window.name += 'heroPlayed;';
609
595
  } else {
610
596
  console.log('Skipping initial hero line animations as they have already been played in this session');
611
597
  }
@@ -635,7 +621,11 @@ window.heroAnimationOnLoad = function(e) {
635
621
  }, '>-0.4');
636
622
 
637
623
  console.log('Animation timeline setup completed');
638
- }
624
+ };
625
+
626
+
627
+
628
+
639
629
 
640
630
 
641
631
  window.briefContact = function(e) {
@@ -1358,18 +1348,15 @@ window.animationTest3 = function(root) {
1358
1348
  });
1359
1349
  };
1360
1350
 
1361
-
1362
1351
  window.animationTest4 = function(root) {
1363
1352
  const hoverWrappers = root.querySelectorAll('.h-wrapper');
1364
1353
  const hoverMasks = root.querySelectorAll('.hover-mask');
1365
1354
  const hoverTitles = root.querySelectorAll('.h-title');
1366
1355
 
1367
- // Stałe rozmiary bg-video
1368
1356
  const VIDEO_WIDTH = 380;
1369
1357
  const VIDEO_HEIGHT = 280;
1370
1358
  const VISIBLE_RATIO = 0.7;
1371
1359
 
1372
- // Ukryj wszystkie maski na starcie
1373
1360
  hoverMasks.forEach(mask => {
1374
1361
  mask.style.display = 'none';
1375
1362
  mask.style.position = 'fixed';
@@ -1378,7 +1365,7 @@ window.animationTest4 = function(root) {
1378
1365
  mask.style.width = '100vw';
1379
1366
  mask.style.height = '100vh';
1380
1367
  mask.style.zIndex = '999';
1381
- mask.style.pointerEvents = 'none'; // nie blokuje hovera
1368
+ mask.style.pointerEvents = 'none';
1382
1369
  });
1383
1370
 
1384
1371
  function resetOpacity() {
@@ -1394,6 +1381,7 @@ window.animationTest4 = function(root) {
1394
1381
  video.style.left = '';
1395
1382
  video.style.top = '';
1396
1383
  video.style.position = '';
1384
+ video.style.zIndex = '';
1397
1385
  });
1398
1386
  }
1399
1387
 
@@ -1418,7 +1406,6 @@ window.animationTest4 = function(root) {
1418
1406
  el.style.position = 'absolute';
1419
1407
  el.style.width = `${VIDEO_WIDTH}px`;
1420
1408
  el.style.height = `${VIDEO_HEIGHT}px`;
1421
- el.style.zIndex = '1001';
1422
1409
  el.style.pointerEvents = 'none';
1423
1410
  }
1424
1411
 
@@ -1434,7 +1421,6 @@ window.animationTest4 = function(root) {
1434
1421
  video.style.width = `${VIDEO_WIDTH}px`;
1435
1422
  video.style.height = `${VIDEO_HEIGHT}px`;
1436
1423
  video.style.position = 'absolute';
1437
- video.style.zIndex = '1001';
1438
1424
  video.style.pointerEvents = 'none';
1439
1425
  });
1440
1426
 
@@ -1451,8 +1437,7 @@ window.animationTest4 = function(root) {
1451
1437
 
1452
1438
  videoTimeline = gsap.timeline();
1453
1439
 
1454
- // Stwórz tablicę indeksów i wymieszaj
1455
- const videoIndexes = Array.from({length: videos.length}, (_, i) => i);
1440
+ const videoIndexes = Array.from({ length: videos.length }, (_, i) => i);
1456
1441
  const shuffledIndexes = videoIndexes.sort(() => Math.random() - 0.5);
1457
1442
 
1458
1443
  shuffledIndexes.forEach((videoIndex, timeIndex) => {
@@ -1460,6 +1445,7 @@ window.animationTest4 = function(root) {
1460
1445
 
1461
1446
  videoTimeline.add(() => {
1462
1447
  placeElementAtRandomVisiblePosition(video, mask);
1448
+ video.style.zIndex = 1001 + timeIndex; // <-- z-index rośnie
1463
1449
  }, timeIndex * 0.75);
1464
1450
 
1465
1451
  videoTimeline.to(video, {
@@ -1492,18 +1478,51 @@ window.animationTest4 = function(root) {
1492
1478
 
1493
1479
 
1494
1480
 
1495
-
1496
1481
  window.pricingInteraction = function(e) {
1482
+ console.log('[pricingInteraction] Function called');
1483
+ console.log('[pricingInteraction] Window width:', window.innerWidth);
1484
+
1497
1485
  // Check if the screen width is 992px or wider
1498
1486
  if (window.innerWidth >= 992) {
1487
+ console.log('[pricingInteraction] Desktop mode - creating pin');
1488
+
1489
+ const tabsContent = document.querySelector('.tabs-content');
1490
+ const tabsMenu = document.querySelector('.tabs-menu');
1491
+
1492
+ console.log('[pricingInteraction] .tabs-content found:', tabsContent);
1493
+ console.log('[pricingInteraction] .tabs-menu found:', tabsMenu);
1494
+
1495
+ if (!tabsContent || !tabsMenu) {
1496
+ console.error('[pricingInteraction] Required elements not found!');
1497
+ return;
1498
+ }
1499
+
1500
+ // Debug computed styles
1501
+ const tabsContentStyles = window.getComputedStyle(tabsContent);
1502
+ const tabsMenuStyles = window.getComputedStyle(tabsMenu);
1503
+ console.log('[pricingInteraction] .tabs-content styles:', {
1504
+ position: tabsContentStyles.position,
1505
+ height: tabsContentStyles.height,
1506
+ display: tabsContentStyles.display,
1507
+ });
1508
+ console.log('[pricingInteraction] .tabs-menu styles:', {
1509
+ position: tabsMenuStyles.position,
1510
+ height: tabsMenuStyles.height,
1511
+ display: tabsMenuStyles.display,
1512
+ });
1513
+
1499
1514
  // Create the pinning effect
1500
- ScrollTrigger.create({
1515
+ const trigger = ScrollTrigger.create({
1501
1516
  trigger: ".tabs-content",
1502
- start: "top-=48px top", // Start pinning 48px after the top of the .tabs-content hits the top of the viewport
1503
- end: "bottom bottom", // Stop pinning when the bottom of the .tabs-content hits the bottom of the viewport
1504
- pin: ".tabs-menu", // The element to pin
1505
- pinSpacing: false // Maintain space for pinned element (optional)
1517
+ start: "top-=48px top",
1518
+ end: "bottom bottom",
1519
+ pin: ".tabs-menu",
1520
+
1506
1521
  });
1522
+
1523
+ console.log('[pricingInteraction] ScrollTrigger created:', trigger);
1524
+ } else {
1525
+ console.log('[pricingInteraction] Mobile mode - skipping pin');
1507
1526
  }
1508
1527
  };
1509
1528
 
@@ -1993,6 +2012,42 @@ window.clientLogosAnimation = function(e) {
1993
2012
  });
1994
2013
  }
1995
2014
 
2015
+ // Hue Awards animation with individual ScrollTriggers
2016
+ window.hueAwardsAnimation = function(e) {
2017
+ const logoItems = e.querySelectorAll('.clients_collection_item_mask.is-3');
2018
+
2019
+ logoItems.forEach((maskEl) => {
2020
+ const imgEl = maskEl.querySelector('.skills-item_img');
2021
+
2022
+ // Set initial height of mask to 100%
2023
+ gsap.set(maskEl, { height: '100%' });
2024
+
2025
+ // Animate mask height to 0% when it enters viewport
2026
+ gsap.to(maskEl, {
2027
+ scrollTrigger: {
2028
+ trigger: maskEl,
2029
+ start: 'top bottom-=25%',
2030
+ },
2031
+ height: '0%',
2032
+ duration: 0.7,
2033
+ ease: 'loader2',
2034
+ });
2035
+
2036
+ // Animate logo opacity (fade in)
2037
+ gsap.from(imgEl, {
2038
+ scrollTrigger: {
2039
+ trigger: maskEl,
2040
+ start: 'top bottom-=25%',
2041
+ },
2042
+ opacity: 0,
2043
+ delay: 0.4,
2044
+ duration: 0.45,
2045
+ ease: 'loader2',
2046
+ });
2047
+ });
2048
+ };
2049
+
2050
+
1996
2051
  // Hero span video on hover
1997
2052
  window.heroSpanVideoOnHover = function(e) {
1998
2053
  window.customCursorText(e);
@@ -2506,6 +2561,42 @@ window.formSubmitAnimation = function(e) {
2506
2561
  observeDisplayChanges();
2507
2562
  }
2508
2563
 
2564
+ // Scrubbed timeline: progress tied to scroll progress
2565
+ window.initBentoRevealScrub = function (container) {
2566
+ // Respect reduced motion
2567
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
2568
+ container.querySelectorAll(':scope > *').forEach(el => {
2569
+ el.style.opacity = 1;
2570
+ el.style.transform = 'none';
2571
+ });
2572
+ return;
2573
+ }
2574
+
2575
+ const items = Array.from(container.children); // direct children only
2576
+
2577
+ // Important: define from state in timeline for scrub to interpolate smoothly
2578
+ const tl = gsap.timeline({
2579
+ scrollTrigger: {
2580
+ trigger: container,
2581
+ start: 'top 90%', // start easing in slightly later
2582
+ end: 'bottom 65%', // full reveal by near the end of container
2583
+ scrub: 0.5, // smoothing; use true for no smoothing
2584
+ markers: false, // uncomment for debugging
2585
+ }
2586
+ });
2587
+
2588
+ tl.fromTo(items,
2589
+ { opacity: 0, y: 28 },
2590
+ {
2591
+ opacity: 1,
2592
+ y: 0,
2593
+ ease: 'power2.out',
2594
+ // Duration is ignored by scrub; timeline length maps to scroll distance.
2595
+ // Stagger distributes the items across the timeline for a nice cascade.
2596
+ stagger: { each: 0.12 }
2597
+ }
2598
+ );
2599
+ };
2509
2600
 
2510
2601
 
2511
2602
  // Refresh Gsap scrollTriggers
@@ -2542,6 +2633,283 @@ window.closeInfoBar = function() {
2542
2633
  }
2543
2634
 
2544
2635
 
2636
+ // Hero 2026 animation - navbar letters on scroll
2637
+ // EXPERIMENTAL: play/reverse version (to revert: replace this whole function with scrub version)
2638
+ window.heroAnimation2026 = function(e) {
2639
+ const wideletters = document.querySelectorAll('.wideletter');
2640
+ const introSection = document.querySelector('[x-init*="introAnimation"]');
2641
+
2642
+ if (!introSection) return;
2643
+
2644
+ // Wideletters animation timeline (paused)
2645
+ const widelettersTimeline = gsap.timeline({ paused: true });
2646
+ widelettersTimeline.fromTo(wideletters,
2647
+ { y: '-150%' },
2648
+ {
2649
+ y: '0%',
2650
+ duration: 0.8,
2651
+ ease: 'power2.out',
2652
+ stagger: 0.05,
2653
+ }
2654
+ );
2655
+
2656
+ // Trigger for wideletters
2657
+ ScrollTrigger.create({
2658
+ trigger: introSection,
2659
+ start: 'bottom bottom-=25%',
2660
+ onEnter: () => widelettersTimeline.play(),
2661
+ onLeaveBack: () => widelettersTimeline.reverse(),
2662
+ });
2663
+
2664
+ // Partner section animation
2665
+ const partnerSection = document.querySelector('#partner');
2666
+ const heroFlex = document.querySelector('.hero-flex');
2667
+ const coverBg = document.querySelector('.cover-bg');
2668
+
2669
+ if (!partnerSection || !heroFlex || !coverBg) return;
2670
+
2671
+ // Partner timeline (cover-bg + wideletters reverse)
2672
+ const partnerTimeline = gsap.timeline({ paused: true });
2673
+
2674
+ // Cover-bg fade-in
2675
+ partnerTimeline.fromTo(coverBg,
2676
+ { opacity: 0 },
2677
+ { opacity: 1, duration: 0.6, ease: 'power2.out' },
2678
+ 0
2679
+ );
2680
+
2681
+ // Reverse wideletters
2682
+ partnerTimeline.to(wideletters, {
2683
+ y: '-150%',
2684
+ duration: 0.6,
2685
+ ease: 'power2.in',
2686
+ stagger: 0.03,
2687
+ }, 0);
2688
+
2689
+ // Trigger for partner timeline
2690
+ ScrollTrigger.create({
2691
+ trigger: partnerSection,
2692
+ start: 'top 80%',
2693
+ onEnter: () => partnerTimeline.play(),
2694
+ onLeaveBack: () => partnerTimeline.reverse(),
2695
+ });
2696
+ };
2697
+
2698
+ // Intro animation on page load
2699
+ window.introAnimation = function(e) {
2700
+ const introBrand = e.querySelector('[intro-brand]');
2701
+ const introSubtitle = e.querySelector('[intro-subtitle]');
2702
+ const widelettersIntro = e.querySelectorAll('.wideletter-intro');
2703
+
2704
+ // Set initial states
2705
+ gsap.set([introBrand, introSubtitle], { opacity: 0 });
2706
+ gsap.set(widelettersIntro, { y: '-150%' });
2707
+
2708
+ // Prevent browser scroll restoration and force scroll to top
2709
+ history.scrollRestoration = 'manual';
2710
+ window.scrollTo(0, 0);
2711
+
2712
+ // Lock scroll: position fixed keeps scrollbar visible but prevents scrolling
2713
+ document.body.style.position = 'fixed';
2714
+ document.body.style.top = '0px';
2715
+ document.body.style.width = '100%';
2716
+
2717
+ // Page load animation - scroll timeline is created AFTER this completes
2718
+ const introTimeline = gsap.timeline({
2719
+ onComplete: () => {
2720
+ // Unlock scroll
2721
+ document.body.style.position = '';
2722
+ document.body.style.top = '';
2723
+ document.body.style.width = '';
2724
+ window.scrollTo(0, 0);
2725
+
2726
+ // Force all letters to final state before creating ScrollTrigger
2727
+ gsap.set(widelettersIntro, { y: '0%', opacity: 1 });
2728
+ gsap.set(introBrand, { opacity: 1 });
2729
+
2730
+ // Small delay to ensure DOM is ready, then create scroll timeline and refresh ScrollTrigger
2731
+ requestAnimationFrame(() => {
2732
+ createScrollTimeline();
2733
+ ScrollTrigger.refresh();
2734
+ });
2735
+ }
2736
+ });
2737
+
2738
+ introTimeline.to(introBrand, {
2739
+ opacity: 1,
2740
+ duration: 0.1,
2741
+ });
2742
+
2743
+ introTimeline.to(widelettersIntro, {
2744
+ y: '0%',
2745
+ duration: 1,
2746
+ ease: 'loader2',
2747
+ stagger: 0.1,
2748
+ });
2749
+
2750
+ // EXPERIMENTAL: play/reverse version (no parallax)
2751
+ function createScrollTimeline() {
2752
+ // Timeline 1: Letters out
2753
+ const lettersOutTimeline = gsap.timeline({ paused: true });
2754
+ lettersOutTimeline.to(widelettersIntro, {
2755
+ y: '-150%',
2756
+ opacity: 0,
2757
+ duration: 0.6,
2758
+ ease: 'power2.in',
2759
+ stagger: 0.03,
2760
+ });
2761
+
2762
+ // Trigger 1: Letters out (at 20% of section)
2763
+ ScrollTrigger.create({
2764
+ trigger: e,
2765
+ start: 'top top-=20%',
2766
+ onEnter: () => lettersOutTimeline.play(),
2767
+ onLeaveBack: () => lettersOutTimeline.reverse(),
2768
+ });
2769
+
2770
+ // Timeline 2: Subtitle in
2771
+ const subtitleInTimeline = gsap.timeline({ paused: true });
2772
+ subtitleInTimeline.fromTo(introSubtitle,
2773
+ { y: '50%', opacity: 0 },
2774
+ { y: '0%', opacity: 1, duration: 0.6, ease: 'power2.out' }
2775
+ );
2776
+
2777
+ // Trigger 2: Subtitle in (at 90% of section)
2778
+ ScrollTrigger.create({
2779
+ trigger: e,
2780
+ start: 'top top-=90%',
2781
+ onEnter: () => subtitleInTimeline.play(),
2782
+ onLeaveBack: () => subtitleInTimeline.reverse(),
2783
+ });
2784
+
2785
+ // Timeline 3: Subtitle out
2786
+ const subtitleOutTimeline = gsap.timeline({ paused: true });
2787
+ subtitleOutTimeline.to(introSubtitle, {
2788
+ opacity: 0,
2789
+ duration: 0.5,
2790
+ ease: 'power2.out',
2791
+ });
2792
+
2793
+ // Trigger 3: Subtitle out (at 140% - well into section scroll)
2794
+ ScrollTrigger.create({
2795
+ trigger: e,
2796
+ start: 'top top-=140%',
2797
+ onEnter: () => subtitleOutTimeline.play(),
2798
+ onLeaveBack: () => subtitleOutTimeline.reverse(),
2799
+ });
2800
+ }
2801
+ };
2802
+
2803
+ // Global GSAP title animation - animates [gsap-title] elements on scroll
2804
+ function gsapTitleAnimation() {
2805
+ const gsapTitles = document.querySelectorAll('[gsap-title]');
2806
+
2807
+ if (gsapTitles.length === 0) return;
2808
+
2809
+ gsapTitles.forEach((titleElement) => {
2810
+ // Split text into lines
2811
+ const splitTitle = new SplitText(titleElement, {
2812
+ type: 'lines',
2813
+ linesClass: 'gsap-title-line',
2814
+ });
2815
+
2816
+ // Create mask wrapper for each line
2817
+ const splitTitleMask = new SplitText(titleElement, {
2818
+ type: 'lines',
2819
+ linesClass: 'gsap-title-line-mask',
2820
+ });
2821
+
2822
+ const lines = titleElement.querySelectorAll('.gsap-title-line');
2823
+
2824
+ if (lines.length === 0) return;
2825
+
2826
+ // Set initial state - lines hidden below
2827
+ gsap.set(lines, { y: '100%' });
2828
+
2829
+ // Create paused timeline
2830
+ const titleTimeline = gsap.timeline({ paused: true });
2831
+
2832
+ titleTimeline.to(lines, {
2833
+ y: '0%',
2834
+ duration: 0.75,
2835
+ stagger: 0.2,
2836
+ ease: 'loader2',
2837
+ });
2838
+
2839
+ // Create ScrollTrigger with play/reverse
2840
+ ScrollTrigger.create({
2841
+ trigger: titleElement,
2842
+ start: 'top 80%',
2843
+ onEnter: () => titleTimeline.play(),
2844
+ onLeaveBack: () => titleTimeline.reverse(),
2845
+ });
2846
+ });
2847
+ }
2848
+
2849
+ // Global GSAP container animation - animates direct children of [gsap-container]
2850
+ function gsapContainerAnimation() {
2851
+ const gsapContainers = document.querySelectorAll('[gsap-container]');
2852
+
2853
+ if (gsapContainers.length === 0) return;
2854
+
2855
+ gsapContainers.forEach((container) => {
2856
+ const children = container.children;
2857
+
2858
+ if (children.length === 0) return;
2859
+
2860
+ // Set initial state
2861
+ gsap.set(children, { y: '4rem', opacity: 0 });
2862
+
2863
+ // Create paused timeline
2864
+ const containerTimeline = gsap.timeline({ paused: true });
2865
+
2866
+ containerTimeline.to(children, {
2867
+ y: '0rem',
2868
+ opacity: 1,
2869
+ duration: 1,
2870
+ stagger: 0.1,
2871
+ ease: 'power3.out',
2872
+ });
2873
+
2874
+ // Create ScrollTrigger with play/reverse
2875
+ ScrollTrigger.create({
2876
+ trigger: container,
2877
+ start: 'top 80%',
2878
+ onEnter: () => containerTimeline.play(),
2879
+ onLeaveBack: () => containerTimeline.reverse(),
2880
+ });
2881
+ });
2882
+ }
2883
+
2884
+ // Global GSAP fade animation - fades in [gsap-fade] elements on scroll
2885
+ function gsapFadeAnimation() {
2886
+ const gsapFadeElements = document.querySelectorAll('[gsap-fade]');
2887
+
2888
+ if (gsapFadeElements.length === 0) return;
2889
+
2890
+ gsapFadeElements.forEach((element) => {
2891
+ // Set initial state
2892
+ gsap.set(element, { opacity: 0 });
2893
+
2894
+ // Create paused timeline
2895
+ const fadeTimeline = gsap.timeline({ paused: true });
2896
+
2897
+ fadeTimeline.to(element, {
2898
+ opacity: 1,
2899
+ duration: 2,
2900
+ ease: 'power3.out',
2901
+ });
2902
+
2903
+ // Create ScrollTrigger with play/reverse
2904
+ ScrollTrigger.create({
2905
+ trigger: element,
2906
+ start: 'top 65%',
2907
+ onEnter: () => fadeTimeline.play(),
2908
+ onLeaveBack: () => fadeTimeline.reverse(),
2909
+ });
2910
+ });
2911
+ }
2912
+
2545
2913
  // !!!!!!!!! Keep this Alpine init at the end !!!!!!!!!
2546
2914
  window.Webflow ||= [];
2547
2915
  window.Webflow.push(() => {
@@ -2565,6 +2933,9 @@ window.Webflow.push(() => {
2565
2933
  contactFormOpenAnimation();
2566
2934
  openContactFormFromURL();
2567
2935
  disableScrollMenu();
2936
+ gsapTitleAnimation();
2937
+ gsapContainerAnimation();
2938
+ gsapFadeAnimation();
2568
2939
  refreshScrollTriggersAfterLoad();
2569
2940
 
2570
2941
  });