@widelab-nc/widehue 1.0.44 → 1.0.46

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/src/index.js CHANGED
@@ -7,12 +7,9 @@ import { CustomEase } from "gsap/CustomEase";
7
7
  import { SplitText } from 'gsap/SplitText';
8
8
  import Splide from '@splidejs/splide';
9
9
 
10
-
11
10
  // GSAP plugins register and custom easing
12
11
  gsap.registerPlugin(ScrollTrigger, ScrollToPlugin, CustomEase, SplitText);
13
12
 
14
- console.log('SplitText:', typeof SplitText);
15
- console.log('Registered plugins:', gsap.plugins);
16
13
 
17
14
  function customGsapEasing() {
18
15
  CustomEase.create('loader', '0.83, 0, 0.17, 1');
@@ -27,7 +24,6 @@ let contactFormOpen;
27
24
 
28
25
  function refreshScrollTriggersAfterLoad() {
29
26
  window.addEventListener('load', () => {
30
- console.log('[ScrollTrigger] Page fully loaded — refreshing all triggers');
31
27
  setTimeout(() => {
32
28
  ScrollTrigger.refresh();
33
29
  }, 100); // bufor na przypadkowe opóźnienia, możesz zwiększyć
@@ -66,9 +62,6 @@ function contactFormOpenAnimation() {
66
62
 
67
63
  if (checkboxElement) {
68
64
  checkboxElement.click(); // Click on the checkbox element
69
- console.log(`Clicked on checkbox for ${projectCategoryValue}`);
70
- } else {
71
- console.log(`No checkbox found for ${projectCategoryValue}`);
72
65
  }
73
66
  });
74
67
  });
@@ -91,6 +84,19 @@ const lenis = new Lenis({
91
84
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
92
85
  });
93
86
  function lenisScroll() {
87
+ // Configure ScrollTrigger to use Lenis as the scroller
88
+ ScrollTrigger.scrollerProxy(window, {
89
+ scrollTop(value) {
90
+ if (arguments.length) {
91
+ lenis.scrollTo(value, { immediate: true });
92
+ }
93
+ return lenis.scroll || lenis.actualScroll || window.pageYOffset || 0;
94
+ },
95
+ getBoundingClientRect() {
96
+ return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight };
97
+ }
98
+ });
99
+
94
100
  lenis.on('scroll', ScrollTrigger.update);
95
101
  gsap.ticker.add((time)=>{
96
102
  lenis.raf(time * 1000)
@@ -180,7 +186,6 @@ function revealFooter() {
180
186
  function checkElementExists(selector) {
181
187
  const element = document.querySelector(selector);
182
188
  if (!element) {
183
- console.warn(`Element ${selector} not found - skipping animation`);
184
189
  return false;
185
190
  }
186
191
  return true;
@@ -188,14 +193,12 @@ function checkElementExists(selector) {
188
193
 
189
194
  // Fallback function when loader elements don't exist
190
195
  function initBasicAnimations() {
191
- console.log('Initializing basic animations without loader');
192
196
 
193
197
  if (typeof lenis !== 'undefined') {
194
198
  try {
195
199
  lenis.scrollTo(0, { immediate: true });
196
200
  lenis.start();
197
201
  } catch (e) {
198
- console.warn('Lenis initialization failed:', e);
199
202
  window.scrollTo(0, 0);
200
203
  }
201
204
  } else {
@@ -204,7 +207,6 @@ function initBasicAnimations() {
204
207
  }
205
208
 
206
209
  function pageLoadAnimation() {
207
- console.log('pageLoadAnimation function called');
208
210
 
209
211
  // Check if all required loader elements exist
210
212
  const requiredElements = [
@@ -222,11 +224,9 @@ function pageLoadAnimation() {
222
224
  }
223
225
 
224
226
  const loaderAnimationIn = gsap.timeline({});
225
- console.log('GSAP timeline created');
226
227
 
227
228
  loaderAnimationIn
228
229
  .add(function() {
229
- console.log('First animation step triggered: scrollRestoration and scrollTo');
230
230
  history.scrollRestoration = 'manual';
231
231
 
232
232
  // Safari-safe scroll to top
@@ -234,7 +234,6 @@ function pageLoadAnimation() {
234
234
  try {
235
235
  lenis.scrollTo(0, { immediate: true });
236
236
  } catch (e) {
237
- console.warn('Lenis scrollTo failed, using fallback:', e);
238
237
  window.scrollTo(0, 0);
239
238
  }
240
239
  } else {
@@ -261,7 +260,6 @@ function pageLoadAnimation() {
261
260
  '<'
262
261
  )
263
262
  .add(function() {
264
- console.log('lenis started');
265
263
  if (typeof lenis !== 'undefined' && lenis.start) {
266
264
  lenis.start();
267
265
  }
@@ -270,26 +268,20 @@ function pageLoadAnimation() {
270
268
  display: 'none',
271
269
  duration: 0.1,
272
270
  onComplete: function() {
273
- console.log('.loader_wrapper set to display:none');
274
271
  }
275
272
  }, '>');
276
273
 
277
- console.log('Animation timeline defined');
278
274
 
279
275
  // Update on screen resize - with element existence check
280
276
  window.addEventListener('resize', function () {
281
- console.log('Window resize event triggered');
282
277
  setTimeout(function () {
283
278
  if (document.querySelector('.loader_wrapper')) {
284
- console.log('Set .loader_wrapper to display:none on resize');
285
279
  gsap.set('.loader_wrapper', { display: 'none' });
286
280
  } else {
287
- console.warn('Loader wrapper not found on resize');
288
281
  }
289
282
  }, 50);
290
283
  });
291
284
 
292
- console.log('Resize event listener added');
293
285
  }
294
286
 
295
287
 
@@ -431,22 +423,10 @@ window.standardHeaderScrollAnimation = function() {
431
423
  const leaveBackTimeline = gsap.timeline({ paused: true });
432
424
 
433
425
  enterTimeline
434
- .to('.navbar_brand.scale', { scale: 0.25, duration: 1 })
435
- .to('.wideletter', {
436
- y: '-150%',
437
- duration: 1,
438
- ease: 'loader2',
439
- stagger: 0.1,
440
- }, 0); // 0 to start simultaneously with the previous animation
426
+ .to('.navbar_brand.scale', { scale: 0.25, duration: 1 });
441
427
 
442
428
  leaveBackTimeline
443
- .to('.navbar_brand.scale', { scale: 1, duration: 1 })
444
- .to('.wideletter', {
445
- y: '0%',
446
- duration: 1,
447
- ease: 'loader2',
448
- stagger: 0.1,
449
- }, 0);
429
+ .to('.navbar_brand.scale', { scale: 1, duration: 1 });
450
430
 
451
431
  ScrollTrigger.create({
452
432
  start: 'top+=100 top', // Triggers when the user has scrolled 100px
@@ -493,134 +473,177 @@ window.customCursorText = function(e) {
493
473
 
494
474
 
495
475
  window.heroAnimationOnLoad = function(e) {
496
- console.log('heroAnimationOnLoad function started');
497
-
498
- const hasPlayedAnimation = window.name.includes('heroPlayed');
476
+ const video = document.getElementById('herovideo');
499
477
 
500
478
  gsap.set('.wideletter', { y: '-150%', scale: 1 });
501
479
 
502
480
  if (typeof lenis !== 'undefined') {
503
- console.log('Lenis instance found, attempting to scroll to top');
504
- setTimeout(() => {
505
- lenis.scrollTo(0, { duration: 0, easing: (t) => t });
506
- console.log('Lenis scrollTo action called after delay');
507
- }, 100);
508
- } else {
509
- console.log('Lenis instance not found');
510
- }
511
-
512
- const videoMask = e.querySelector('.hero_headline_ract-mask');
513
- const video = e.querySelector('video.lazyload');
514
- const heroAnimation = gsap.timeline({});
515
- const homeHeroHeading = new SplitText('#heroHeading', {
516
- type: 'lines',
517
- linesClass: 'hero-line',
518
- });
519
- const homeHeroHeadingMask = new SplitText('#heroHeading', {
520
- type: 'lines',
521
- linesClass: 'hero-line-mask',
522
- });
523
-
524
- const heroLines = document.querySelectorAll('.hero-line');
525
- if (heroLines.length === 0) {
526
- console.error('GSAP target .hero-line not found.');
527
- return;
528
- }
529
-
530
- function updateVideoSources() {
531
- const isMobile = window.innerWidth < 992;
532
- const mobileMp4 = video.getAttribute('mobile-mp4');
533
- const mobileWebm = video.getAttribute('mobile-webm');
534
- const mp4Source = video.querySelector('source[type="video/mp4"]');
535
- const webmSource = video.querySelector('source[type="video/webm"]');
536
-
537
- if (isMobile) {
538
- if (mobileMp4) mp4Source.setAttribute('data-src', mobileMp4);
539
- if (mobileWebm) webmSource.setAttribute('data-src', mobileWebm);
540
- } else {
541
- mp4Source.setAttribute('data-src', mp4Source.getAttribute('data-src').replace('mobile-', ''));
542
- webmSource.setAttribute('data-src', webmSource.getAttribute('data-src').replace('mobile-', ''));
543
- }
481
+ setTimeout(() => lenis.scrollTo(0, { duration: 0, easing: (t) => t }), 100);
544
482
  }
545
483
 
546
- updateVideoSources();
547
- window.addEventListener('resize', updateVideoSources);
548
-
484
+ let videoPlayListener = null;
549
485
  if (video) {
486
+ video.pause();
487
+ video.currentTime = 0;
488
+ function updateVideoSources() {
489
+ const isMobile = window.innerWidth < 992;
490
+ const mobileMp4 = video.getAttribute('mobile-mp4');
491
+ const mobileWebm = video.getAttribute('mobile-webm');
492
+ const mp4Source = video.querySelector('source[type="video/mp4"]');
493
+ const webmSource = video.querySelector('source[type="video/webm"]');
494
+ if (mobileMp4 && mp4Source) mp4Source.setAttribute('data-src', mobileMp4);
495
+ if (mobileWebm && webmSource) webmSource.setAttribute('data-src', mobileWebm);
496
+ if (!isMobile && mp4Source) mp4Source.setAttribute('data-src', mp4Source.getAttribute('data-src')?.replace('mobile-', '') || '');
497
+ if (!isMobile && webmSource) webmSource.setAttribute('data-src', webmSource.getAttribute('data-src')?.replace('mobile-', '') || '');
498
+ }
499
+ updateVideoSources();
500
+ window.addEventListener('resize', updateVideoSources);
550
501
  video.setAttribute('data-no-autoplay', 'true');
551
502
  video.addEventListener('loadeddata', () => {
552
503
  if (video.hasAttribute('data-no-autoplay')) {
553
504
  video.pause();
505
+ video.currentTime = 0;
554
506
  }
555
507
  });
508
+ videoPlayListener = () => {
509
+ console.log('[heroAnimationOnLoad] Video play event! currentTime:', video.currentTime);
510
+ if (video.hasAttribute('data-no-autoplay')) {
511
+ console.log('[heroAnimationOnLoad] Video has data-no-autoplay, forcing pause');
512
+ video.pause();
513
+ video.currentTime = 0;
514
+ }
515
+ };
516
+ video.addEventListener('play', videoPlayListener);
517
+ } else {
556
518
  }
557
519
 
558
- if (!hasPlayedAnimation) {
559
- console.log('Initial settings applied to hero lines');
560
-
561
- gsap.set('.navbar_brand_lottie', { opacity: 1 });
562
- gsap.set('#heroHeading', { opacity: 0 });
563
- gsap.set('.hero-line', { y: '100%' });
520
+ function run() {
521
+ const introBrand = document.querySelector('.preloader [intro-brand]');
522
+ const preloader = introBrand ? introBrand.closest('.preloader') : document.querySelector('.preloader');
523
+ const wrapIt = document.querySelector('.wrap-it') || preloader?.closest('.wrap-it') || preloader;
524
+ const introSubtitle = preloader ? preloader.querySelector('[intro-subtitle]') : null;
525
+ const widelettersIntro = introBrand ? introBrand.querySelectorAll('.wideletter-intro') : [];
526
+
527
+ let savedScrollY = 0;
528
+ const preventScroll = (e) => e.preventDefault();
529
+
530
+ function lockScroll() {
531
+ savedScrollY = window.scrollY || document.documentElement.scrollTop;
532
+ document.body.style.position = 'fixed';
533
+ document.body.style.top = `-${savedScrollY}px`;
534
+ document.body.style.left = '0';
535
+ document.body.style.right = '0';
536
+ document.body.style.width = '100%';
537
+ document.body.style.overflow = 'hidden';
538
+ document.documentElement.style.overflow = 'hidden';
539
+ history.scrollRestoration = 'manual';
540
+ if (typeof lenis !== 'undefined' && lenis.stop) lenis.stop();
541
+ document.addEventListener('wheel', preventScroll, { passive: false });
542
+ document.addEventListener('touchmove', preventScroll, { passive: false });
543
+ }
564
544
 
565
- heroAnimation.to('.navbar_brand_lottie', {
566
- duration: 1.5,
567
- onComplete: function() {
568
- console.log('Lottie animation completed');
569
- gsap.to('.navbar_brand_lottie', { opacity: 0, duration: 0.25 });
570
- }
571
- });
545
+ // Lock scroll IMMEDIATELY at the start - before any animation
546
+ lockScroll();
547
+
548
+ function unlockScroll() {
549
+ document.body.style.position = '';
550
+ document.body.style.top = '';
551
+ document.body.style.left = '';
552
+ document.body.style.right = '';
553
+ document.body.style.width = '';
554
+ document.body.style.overflow = '';
555
+ document.documentElement.style.overflow = '';
556
+ document.removeEventListener('wheel', preventScroll);
557
+ document.removeEventListener('touchmove', preventScroll);
558
+ window.scrollTo(0, savedScrollY);
559
+ if (typeof lenis !== 'undefined' && lenis.start) lenis.start();
560
+ }
572
561
 
573
- heroAnimation.to('#heroHeading', {
574
- opacity: 1,
575
- duration: 0.1,
576
- ease: 'none',
577
- }, '+=0.1');
562
+ const heroAnimation = gsap.timeline({});
563
+
564
+ if (preloader && introBrand && introSubtitle && widelettersIntro.length > 0) {
565
+ gsap.set(widelettersIntro, { y: '-150%' });
566
+
567
+ heroAnimation.to({}, { duration: 0.1 });
568
+ heroAnimation.set('.grab', { opacity: 1 });
569
+ heroAnimation.set(introBrand, { opacity: 1 });
570
+ heroAnimation.to(widelettersIntro, { y: '0%', duration: 1, ease: 'loader2', stagger: 0.1 });
571
+ heroAnimation.to({}, { duration: 1 });
572
+ heroAnimation.to(widelettersIntro, { y: '-150%', opacity: 0, duration: 0.6, ease: 'power2.in', stagger: 0.03 });
573
+ heroAnimation.fromTo(introSubtitle, { y: '50%', opacity: 0 }, { y: '0%', opacity: 1, duration: 0.6, ease: 'power2.out' });
574
+ heroAnimation.to({}, { duration: 2 });
575
+ // 5. .wrap-it: height 100vh → 0vh (2s). Video play na START animacji height.
576
+ // Set position: absolute before height animation to prevent body height change
577
+ // NOTE: lockScroll() is already called at the start of run() function
578
+ heroAnimation.set(wrapIt, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100vh', overflow: 'hidden', zIndex: 9999 });
579
+ heroAnimation.to(wrapIt, {
580
+ height: '0vh',
581
+ duration: 2,
582
+ ease: 'power2.out',
583
+ onStart: () => {
584
+ if (video) {
585
+ video.currentTime = 0;
586
+ // Remove attribute and play listener before playing to prevent conflicts
587
+ video.removeAttribute('data-no-autoplay');
588
+ if (videoPlayListener) {
589
+ video.removeEventListener('play', videoPlayListener);
590
+ }
591
+ video.play().then(() => {
592
+ }).catch(err => {
593
+ });
594
+ }
595
+ },
596
+ onComplete: () => {
597
+ unlockScroll();
598
+ gsap.set(wrapIt, { visibility: 'hidden', pointerEvents: 'none' });
599
+ // Refresh ScrollTrigger after height animation completes to recalculate positions
600
+ ScrollTrigger.refresh();
601
+ },
602
+ });
603
+ } else if (wrapIt) {
604
+ // NOTE: lockScroll() is already called at the start of run() function
605
+ heroAnimation.set(wrapIt, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100vh', overflow: 'hidden', zIndex: 9999 });
606
+ heroAnimation.to(wrapIt, {
607
+ height: '0vh',
608
+ duration: 2,
609
+ ease: 'power2.out',
610
+ onStart: () => {
611
+ if (video) {
612
+ video.currentTime = 0;
613
+ video.removeAttribute('data-no-autoplay');
614
+ if (videoPlayListener) {
615
+ video.removeEventListener('play', videoPlayListener);
616
+ }
617
+ video.play();
618
+ }
619
+ },
620
+ onComplete: () => {
621
+ unlockScroll();
622
+ gsap.set(wrapIt, { visibility: 'hidden', pointerEvents: 'none' });
623
+ // Refresh ScrollTrigger after height animation completes to recalculate positions
624
+ ScrollTrigger.refresh();
625
+ },
626
+ });
627
+ }
578
628
 
579
- heroAnimation.fromTo('.hero-line', {
580
- y: '100%'
581
- }, {
582
- duration: 0.75,
583
- y: '0%',
584
- stagger: 0.2,
585
- ease: 'loader2',
586
- }, '<');
629
+ heroAnimation.from('#heroBottomWrapper', { opacity: 0, duration: 1, ease: 'loader2' }, '>-0.4');
587
630
 
588
- heroAnimation.to('#heroHeading', {
589
- opacity: 0,
590
- duration: 0.3,
591
- ease: 'loader2',
592
- }, '+=1');
631
+ // Literki IN as last step in timeline (initial state)
632
+ const wideletters = document.querySelectorAll('.wideletter');
633
+ if (wideletters.length > 0) {
634
+ heroAnimation.fromTo(wideletters,
635
+ { y: '-150%', opacity: 0 },
636
+ { y: '0%', opacity: 1, duration: 1, ease: 'loader2', stagger: 0.1 },
637
+ '>-0.6'
638
+ );
639
+ }
640
+ }
593
641
 
594
- window.name += 'heroPlayed;';
642
+ if (document.readyState === 'complete') {
643
+ run();
595
644
  } else {
596
- console.log('Skipping initial hero line animations as they have already been played in this session');
645
+ window.addEventListener('load', run);
597
646
  }
598
-
599
- heroAnimation.to('.preloader', {
600
- scaleY: 0,
601
- display: "none",
602
- duration: 1,
603
- ease: 'loader',
604
- });
605
-
606
- heroAnimation.fromTo('.wideletter',
607
- { y: '-150%', scale: 1 },
608
- { y: '0%', duration: 1, ease: 'loader2', stagger: 0.1 },
609
- '-=0.5')
610
- .call(() => {
611
- if (video) {
612
- video.removeAttribute('data-no-autoplay');
613
- video.play();
614
- }
615
- }, null, '<');
616
-
617
- heroAnimation.from('#heroBottomWrapper', {
618
- opacity: 0,
619
- duration: 1,
620
- ease: 'loader2',
621
- }, '>-0.4');
622
-
623
- console.log('Animation timeline setup completed');
624
647
  };
625
648
 
626
649
 
@@ -745,7 +768,6 @@ window.textScroll = function(e) {
745
768
  // Ensure .hero-line elements are created
746
769
  const heroLines = document.querySelectorAll('.hero-line');
747
770
  if (heroLines.length === 0) {
748
- console.error('GSAP target .hero-line not found.');
749
771
  return;
750
772
  }
751
773
 
@@ -1479,16 +1501,33 @@ window.animationTest4 = function(root) {
1479
1501
 
1480
1502
 
1481
1503
  window.pricingInteraction = function(e) {
1504
+
1482
1505
  // Check if the screen width is 992px or wider
1483
1506
  if (window.innerWidth >= 992) {
1507
+
1508
+ const tabsContent = document.querySelector('.tabs-content');
1509
+ const tabsMenu = document.querySelector('.tabs-menu');
1510
+
1511
+ console.log('[pricingInteraction] .tabs-content found:', tabsContent);
1512
+ console.log('[pricingInteraction] .tabs-menu found:', tabsMenu);
1513
+
1514
+ if (!tabsContent || !tabsMenu) {
1515
+ return;
1516
+ }
1517
+
1518
+ // Debug computed styles
1519
+ const tabsContentStyles = window.getComputedStyle(tabsContent);
1520
+ const tabsMenuStyles = window.getComputedStyle(tabsMenu);
1521
+
1484
1522
  // Create the pinning effect
1485
- ScrollTrigger.create({
1523
+ const trigger = ScrollTrigger.create({
1486
1524
  trigger: ".tabs-content",
1487
- start: "top-=48px top", // Start pinning 48px after the top of the .tabs-content hits the top of the viewport
1488
- end: "bottom bottom", // Stop pinning when the bottom of the .tabs-content hits the bottom of the viewport
1489
- pin: ".tabs-menu", // The element to pin
1490
- pinSpacing: false // Maintain space for pinned element (optional)
1525
+ start: "top-=48px top",
1526
+ end: "bottom bottom",
1527
+ pin: ".tabs-menu",
1528
+
1491
1529
  });
1530
+
1492
1531
  }
1493
1532
  };
1494
1533
 
@@ -1503,7 +1542,6 @@ window.caseStudyInteraction = function(e) {
1503
1542
  // Video mask animation
1504
1543
  const mask = e.querySelector('.portfolio_item_image-mask');
1505
1544
  if (!mask) {
1506
- console.error('Mask element not found in:', e);
1507
1545
  return;
1508
1546
  }
1509
1547
 
@@ -1526,7 +1564,6 @@ window.caseStudyInteraction = function(e) {
1526
1564
  if (window.innerWidth > 992) {
1527
1565
  videoElements.forEach(videoElement => {
1528
1566
  if (!videoElement) {
1529
- console.error('No video element found in:', e);
1530
1567
  return;
1531
1568
  }
1532
1569
 
@@ -2290,7 +2327,6 @@ window.heroSliders = function(e) {
2290
2327
  },
2291
2328
  };
2292
2329
  const slider = new Splide(e, config).mount();
2293
- console.log("Splide instance:", slider);
2294
2330
 
2295
2331
  if (window.innerWidth > 992) {
2296
2332
  slider.on('click', function () {
@@ -2527,6 +2563,42 @@ window.formSubmitAnimation = function(e) {
2527
2563
  observeDisplayChanges();
2528
2564
  }
2529
2565
 
2566
+ // Scrubbed timeline: progress tied to scroll progress
2567
+ window.initBentoRevealScrub = function (container) {
2568
+ // Respect reduced motion
2569
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
2570
+ container.querySelectorAll(':scope > *').forEach(el => {
2571
+ el.style.opacity = 1;
2572
+ el.style.transform = 'none';
2573
+ });
2574
+ return;
2575
+ }
2576
+
2577
+ const items = Array.from(container.children); // direct children only
2578
+
2579
+ // Important: define from state in timeline for scrub to interpolate smoothly
2580
+ const tl = gsap.timeline({
2581
+ scrollTrigger: {
2582
+ trigger: container,
2583
+ start: 'top 90%', // start easing in slightly later
2584
+ end: 'bottom 65%', // full reveal by near the end of container
2585
+ scrub: 0.5, // smoothing; use true for no smoothing
2586
+ markers: false, // uncomment for debugging
2587
+ }
2588
+ });
2589
+
2590
+ tl.fromTo(items,
2591
+ { opacity: 0, y: 28 },
2592
+ {
2593
+ opacity: 1,
2594
+ y: 0,
2595
+ ease: 'power2.out',
2596
+ // Duration is ignored by scrub; timeline length maps to scroll distance.
2597
+ // Stagger distributes the items across the timeline for a nice cascade.
2598
+ stagger: { each: 0.12 }
2599
+ }
2600
+ );
2601
+ };
2530
2602
 
2531
2603
 
2532
2604
  // Refresh Gsap scrollTriggers
@@ -2563,6 +2635,254 @@ window.closeInfoBar = function() {
2563
2635
  }
2564
2636
 
2565
2637
 
2638
+ // Hero 2026 animation - navbar letters on scroll
2639
+ window.heroAnimation2026 = function(e) {
2640
+ const wideletters = document.querySelectorAll('.wideletter');
2641
+ if (wideletters.length === 0) return;
2642
+
2643
+ // Timeline for letters OUT
2644
+ const lettersOutTimeline = gsap.timeline({ paused: true });
2645
+ lettersOutTimeline.fromTo(wideletters,
2646
+ { y: '0%', opacity: 1 },
2647
+ {
2648
+ y: '-150%',
2649
+ opacity: 0,
2650
+ duration: 0.6,
2651
+ ease: 'power2.in',
2652
+ stagger: 0.03,
2653
+ }
2654
+ );
2655
+
2656
+ // Trigger: .update26 top reaches 80% viewport → letters OUT, scroll back → reverse
2657
+ const update26 = document.querySelector('.update26');
2658
+ if (!update26) return;
2659
+
2660
+ ScrollTrigger.create({
2661
+ trigger: update26,
2662
+ start: 'top 80%',
2663
+ onEnter: () => lettersOutTimeline.play(),
2664
+ onLeaveBack: () => lettersOutTimeline.reverse(),
2665
+ });
2666
+ };
2667
+
2668
+ // Intro animation on page load
2669
+ window.introAnimation = function(e) {
2670
+ const introBrand = e.querySelector('[intro-brand]');
2671
+ const introSubtitle = e.querySelector('[intro-subtitle]');
2672
+ const widelettersIntro = e.querySelectorAll('.wideletter-intro');
2673
+
2674
+ // Set initial states
2675
+ gsap.set([introBrand, introSubtitle], { opacity: 0 });
2676
+ gsap.set(widelettersIntro, { y: '-150%' });
2677
+
2678
+ // Prevent browser scroll restoration and force scroll to top
2679
+ history.scrollRestoration = 'manual';
2680
+ window.scrollTo(0, 0);
2681
+
2682
+ // Lock scroll: position fixed keeps scrollbar visible but prevents scrolling
2683
+ document.body.style.position = 'fixed';
2684
+ document.body.style.top = '0px';
2685
+ document.body.style.width = '100%';
2686
+
2687
+ // Page load animation - scroll timeline is created AFTER this completes
2688
+ const introTimeline = gsap.timeline({
2689
+ onComplete: () => {
2690
+ // Unlock scroll
2691
+ document.body.style.position = '';
2692
+ document.body.style.top = '';
2693
+ document.body.style.width = '';
2694
+ window.scrollTo(0, 0);
2695
+
2696
+ // Force all letters to final state before creating ScrollTrigger
2697
+ gsap.set(widelettersIntro, { y: '0%', opacity: 1 });
2698
+ gsap.set(introBrand, { opacity: 1 });
2699
+
2700
+ // Small delay to ensure DOM is ready, then create scroll timeline and refresh ScrollTrigger
2701
+ requestAnimationFrame(() => {
2702
+ createScrollTimeline();
2703
+ ScrollTrigger.refresh();
2704
+ });
2705
+ }
2706
+ });
2707
+
2708
+ introTimeline.to(introBrand, {
2709
+ opacity: 1,
2710
+ duration: 0.1,
2711
+ });
2712
+
2713
+ introTimeline.to(widelettersIntro, {
2714
+ y: '0%',
2715
+ duration: 1,
2716
+ ease: 'loader2',
2717
+ stagger: 0.1,
2718
+ });
2719
+
2720
+ // EXPERIMENTAL: play/reverse version (no parallax)
2721
+ function createScrollTimeline() {
2722
+ // Timeline 1: Letters out
2723
+ const lettersOutTimeline = gsap.timeline({ paused: true });
2724
+ lettersOutTimeline.to(widelettersIntro, {
2725
+ y: '-150%',
2726
+ opacity: 0,
2727
+ duration: 0.6,
2728
+ ease: 'power2.in',
2729
+ stagger: 0.03,
2730
+ });
2731
+
2732
+ // Trigger 1: Letters out (at 20% of section)
2733
+ ScrollTrigger.create({
2734
+ trigger: e,
2735
+ start: 'top top-=20%',
2736
+ onEnter: () => lettersOutTimeline.play(),
2737
+ onLeaveBack: () => lettersOutTimeline.reverse(),
2738
+ });
2739
+
2740
+ // Timeline 2: Subtitle in
2741
+ const subtitleInTimeline = gsap.timeline({ paused: true });
2742
+ subtitleInTimeline.fromTo(introSubtitle,
2743
+ { y: '50%', opacity: 0 },
2744
+ { y: '0%', opacity: 1, duration: 0.6, ease: 'power2.out' }
2745
+ );
2746
+
2747
+ // Trigger 2: Subtitle in (at 90% of section)
2748
+ ScrollTrigger.create({
2749
+ trigger: e,
2750
+ start: 'top top-=90%',
2751
+ onEnter: () => subtitleInTimeline.play(),
2752
+ onLeaveBack: () => subtitleInTimeline.reverse(),
2753
+ });
2754
+
2755
+ // Timeline 3: Subtitle out
2756
+ const subtitleOutTimeline = gsap.timeline({ paused: true });
2757
+ subtitleOutTimeline.to(introSubtitle, {
2758
+ opacity: 0,
2759
+ duration: 0.5,
2760
+ ease: 'power2.out',
2761
+ });
2762
+
2763
+ // Trigger 3: Subtitle out (at 140% - well into section scroll)
2764
+ ScrollTrigger.create({
2765
+ trigger: e,
2766
+ start: 'top top-=140%',
2767
+ onEnter: () => subtitleOutTimeline.play(),
2768
+ onLeaveBack: () => subtitleOutTimeline.reverse(),
2769
+ });
2770
+ }
2771
+ };
2772
+
2773
+ // Global GSAP title animation - animates [gsap-title] elements on scroll
2774
+ function gsapTitleAnimation() {
2775
+ const gsapTitles = document.querySelectorAll('[gsap-title]');
2776
+
2777
+ if (gsapTitles.length === 0) return;
2778
+
2779
+ gsapTitles.forEach((titleElement) => {
2780
+ // Split text into lines
2781
+ const splitTitle = new SplitText(titleElement, {
2782
+ type: 'lines',
2783
+ linesClass: 'gsap-title-line',
2784
+ });
2785
+
2786
+ // Create mask wrapper for each line
2787
+ const splitTitleMask = new SplitText(titleElement, {
2788
+ type: 'lines',
2789
+ linesClass: 'gsap-title-line-mask',
2790
+ });
2791
+
2792
+ const lines = titleElement.querySelectorAll('.gsap-title-line');
2793
+
2794
+ if (lines.length === 0) return;
2795
+
2796
+ // Set initial state - lines hidden below
2797
+ gsap.set(lines, { y: '100%' });
2798
+
2799
+ // Create paused timeline
2800
+ const titleTimeline = gsap.timeline({ paused: true });
2801
+
2802
+ titleTimeline.to(lines, {
2803
+ y: '0%',
2804
+ duration: 0.75,
2805
+ stagger: 0.2,
2806
+ ease: 'loader2',
2807
+ });
2808
+
2809
+ // Create ScrollTrigger with play only
2810
+ ScrollTrigger.create({
2811
+ trigger: titleElement,
2812
+ start: 'top 80%',
2813
+ animation: titleTimeline,
2814
+ toggleActions: 'play',
2815
+ invalidateOnRefresh: true,
2816
+ });
2817
+ });
2818
+ }
2819
+
2820
+ // Global GSAP container animation - animates direct children of [gsap-container]
2821
+ function gsapContainerAnimation() {
2822
+ const gsapContainers = document.querySelectorAll('[gsap-container]');
2823
+
2824
+ if (gsapContainers.length === 0) return;
2825
+
2826
+ gsapContainers.forEach((container) => {
2827
+ const children = container.children;
2828
+
2829
+ if (children.length === 0) return;
2830
+
2831
+ // Set initial state
2832
+ gsap.set(children, { y: '4rem', opacity: 0 });
2833
+
2834
+ // Create paused timeline
2835
+ const containerTimeline = gsap.timeline({ paused: true });
2836
+
2837
+ containerTimeline.to(children, {
2838
+ y: '0rem',
2839
+ opacity: 1,
2840
+ duration: 1,
2841
+ stagger: 0.1,
2842
+ ease: 'power3.out',
2843
+ });
2844
+
2845
+ // Create ScrollTrigger with play only
2846
+ ScrollTrigger.create({
2847
+ trigger: container,
2848
+ start: 'top 80%',
2849
+ animation: containerTimeline,
2850
+ toggleActions: 'play',
2851
+ invalidateOnRefresh: true,
2852
+ });
2853
+ });
2854
+ }
2855
+
2856
+ // Global GSAP fade animation - fades in [gsap-fade] elements on scroll
2857
+ function gsapFadeAnimation() {
2858
+ const gsapFadeElements = document.querySelectorAll('[gsap-fade]');
2859
+
2860
+ if (gsapFadeElements.length === 0) return;
2861
+
2862
+ gsapFadeElements.forEach((element) => {
2863
+ // Set initial state
2864
+ gsap.set(element, { opacity: 0 });
2865
+
2866
+ // Create paused timeline
2867
+ const fadeTimeline = gsap.timeline({ paused: true });
2868
+
2869
+ fadeTimeline.to(element, {
2870
+ opacity: 1,
2871
+ duration: 2,
2872
+ ease: 'power3.out',
2873
+ });
2874
+
2875
+ // Create ScrollTrigger with play only
2876
+ ScrollTrigger.create({
2877
+ trigger: element,
2878
+ start: 'top 65%',
2879
+ animation: fadeTimeline,
2880
+ toggleActions: 'play',
2881
+ invalidateOnRefresh: true,
2882
+ });
2883
+ });
2884
+ }
2885
+
2566
2886
  // !!!!!!!!! Keep this Alpine init at the end !!!!!!!!!
2567
2887
  window.Webflow ||= [];
2568
2888
  window.Webflow.push(() => {
@@ -2586,6 +2906,14 @@ window.Webflow.push(() => {
2586
2906
  contactFormOpenAnimation();
2587
2907
  openContactFormFromURL();
2588
2908
  disableScrollMenu();
2909
+ // Wait for DOM to be fully ready before creating ScrollTriggers
2910
+ setTimeout(() => {
2911
+ gsapTitleAnimation();
2912
+ gsapContainerAnimation();
2913
+ gsapFadeAnimation();
2914
+ // Refresh ScrollTrigger after creating all triggers to sync with Lenis
2915
+ ScrollTrigger.refresh();
2916
+ }, 200);
2589
2917
  refreshScrollTriggersAfterLoad();
2590
2918
 
2591
2919
  });