PageSourceSearch

https://ethicallifeworld.com/wp-content/themes/ethical-life/js/modules/pageTransitionModule.js?v=37

js ethicallifeworld.com collected 2026-10-05 00:58:17 UTC 23,778 bytes, 703 lines download raw bytes

1import { scrollControl } from './scrollModule.js';
2
3// Custom eases here 
4// https://gsap.com/docs/v3/Eases/
5CustomEase.create("elastic", ".2, 1.33, .25 ,1");
6
7// Transition ease vars for text animations
8let transitionEase = "power1.out"; // Fade transition ease
9
10// This gives a delay on the enter animations first time the page loads
11// IMPORTANT: Change below to 0.2 when testing so you dont have to wait for the animations while deving.
12const DEFAULT_FIRST_LOAD_DELAY = 0.2;
13
14// Flag to track first load
15let isFirstLoad = true;
16let firstLoadDelay = DEFAULT_FIRST_LOAD_DELAY;
17
18// This is the enter page transtion first load
19window.scrollTo(0, 0);
20
21export function initLoader() {
22    scrollControl.stop();
23
24    $('.loader, [loader-overlay]').addClass('active');
25
26
27    if (document.querySelector('[data-barba-namespace="home"]')) {
28        
29        $('body').addClass('doc-loaded');
30        // Check if this is truly the first visit (your existing function)
31        function isFirstVisit() {
32            if (sessionStorage.getItem('loaderShown')) {
33                return false;
34            }
35            
36            const referrer = document.referrer;
37            const currentDomain = window.location.hostname;
38            
39            if (referrer && referrer.includes(currentDomain)) {
40                return false;
41            }
42            
43            if (performance.navigation && performance.navigation.type === 1) {
44                return false;
45            }
46            
47            if (performance.getEntriesByType('navigation')[0]?.type === 'reload') {
48                return false;
49            }
50            
51            return true;
52        }
53        
54        // Handle overlay and nav classes based on visit type
55        const overlay = document.querySelector('[loader-overlay]');
56        const navContainer = document.querySelector('.nav-container');
57        
58        if (!isFirstVisit()) {
59            // Remove loader from DOM if it exists
60            $('.loader').remove();
61            $('.remove-second-visit').remove();
62
63            
64            // SUBSEQUENT VISITS:
65            // Keep d-none on overlay (it stays hidden)
66            // Remove nav-home from nav container
67            if (navContainer && navContainer.classList.contains('nav-home')) {
68                navContainer.classList.remove('nav-home');
69            }
70            
71            // Show nav immediately and trigger page transition
72            gsap.set('.nav', { opacity: 1 });
73            pageTransitionEnter();
74            
75           
76
77            
78            return; // Exit early
79        }
80        
81        // FIRST VISIT:
82        // Remove d-none from overlay (so it shows)
83        if (overlay && overlay.classList.contains('d-none')) {
84            overlay.classList.remove('d-none');
85        }
86        
87        // Keep nav-home class (it's already there from PHP)
88        // No action needed for nav-container
89        
90        // Mark that loader has been shown
91        sessionStorage.setItem('loaderShown', 'true');
92        console.log('✅ First visit detected - showing loader and keeping nav-home');
93        
94        // Total number of images to load
95        const allFrameCount = 470;
96        
97        // Get bottle canvas animation setup ready
98        const canvas = document.getElementById("hero-lightpass-2");
99        const context = canvas.getContext("2d");
100        //let isMobile = window.innerWidth <= 990;
101    
102        function setCanvasSize() {
103            const dpr = window.devicePixelRatio || 1;
104            
105            // Get window dimensions but enforce minimum width
106            let canvasWidth = Math.max(window.innerWidth, 1200); // Minimum width of 900px
107            let canvasHeight = window.innerHeight;
108            
109            // Set internal canvas dimensions (accounting for DPR for sharpness)
110            canvas.width = canvasWidth * dpr;
111            canvas.height = canvasHeight * dpr;
112            
113            // Set display size via CSS
114            canvas.style.width = `${canvasWidth}px`;
115            canvas.style.height = `${canvasHeight}px`;
116            
117            // Scale the context to account for the DPR
118            const context = canvas.getContext('2d');
119            context.scale(dpr, dpr);
120        }
121        setCanvasSize();
122    
123        const frameCount = 161;
124        const images = [];
125        const airpods = { frame: 0 };
126    
127        const basePath = canvas.getAttribute('img-path');
128    
129        const currentFrame = (index) => {
130            //const imagePath = isMobile ? 'ethical_linear_croped' : 'ethical_linear_croped';
131            const imagePath = 'S1_';
132            return `${basePath}${imagePath}${index.toString().padStart(5, '0')}.webp`;
133        };
134        
135        for (let i = 0; i < frameCount; i++) {
136            const img = new Image();
137            img.src = currentFrame(i);
138            images.push(img);
139        }
140        window.addEventListener("resize", () => {
141            //isMobile = window.innerWidth <= 990;
142            setCanvasSize();
143            render();
144        });
145    
146        function render() {
147            const dpr = window.devicePixelRatio || 1;
148            const canvas = document.getElementById('hero-lightpass-2');
149            const context = canvas.getContext('2d');
150            
151            // Clear using the CSS dimensions
152            context.clearRect(0, 0, canvas.width/dpr, canvas.height/dpr);
153          
154            let img = images[Math.round(airpods.frame)];
155            
156            let imgRatio = img.width / img.height;
157            let canvasRatio = (canvas.width/dpr) / (canvas.height/dpr);
158          
159            let drawWidth, drawHeight, offsetX, offsetY;
160          
161            if (canvasRatio > imgRatio) {
162                // Canvas is wider than the image (relative to their heights)
163                drawHeight = canvas.height/dpr;
164                drawWidth = drawHeight * imgRatio;
165                offsetX = ((canvas.width/dpr) - drawWidth) / 2; // Center horizontally
166                offsetY = 0; // Top aligned
167            } else {
168                // Canvas is taller than the image (relative to their widths)
169                drawWidth = canvas.width/dpr;
170                drawHeight = drawWidth / imgRatio;
171                offsetX = ((canvas.width/dpr) - drawWidth) / 2; // Center horizontally here too
172                offsetY = 0;
172 // Top aligned - this is the change from your original code
173            }
174            
175            context.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
176        }
177        
178    
179        
180        images[0].onload = () => {
181            setTimeout(render, 500);
182        };
183    
184        // Animate logo and loader first
185        gsap.from(".logo-outer", {
186            duration: 1,
187            width: '0',
188            ease: 'power2.inOut',
189        });
190        gsap.from(".logo-text path", {
191            duration: 0.6,
192            opacity: 0,
193            stagger: 0.05,
194            ease: 'power1.inOut',
195            onComplete: () => {
196                $(".logo-outer").addClass("logo-animation-complete");
197            }
198        });
199    
200        gsap.from(".loading, .counter", {
201            duration: 1,
202            opacity: 0,
203            delay: 0.4,
204            stagger: 0.05,
205            ease: 'power1.inOut',
206        });
207    
208        // Simple loader state
209        const loaderState = {
210            imagesLoaded: 0,
211            animationTriggered: false,
212            loadingComplete: false,
213            lastPercent: 0 // Variable to store the last progress percent
214        };
215    
216        // Initialize counter at 0
217        updateCounterDisplay(0);
218    
219        // Start image loading process
220        setTimeout(() => {
221            console.log('Starting image preload process for all ' + allFrameCount + ' frames');
222            
223            // Create an array to track all images
224            const allImages = [];
225            let loadedCount = 0;
226            
227            // Preload all 525 images and track progress
228            for (let i = 0; i < allFrameCount; i++) {
229                const img = new Image();
230                img.src = currentFrame(i);
231                
232                // Track load events
233                img.onload = img.onerror = (event) => {
234                    loadedCount++;
235                    
236                    // Calculate percentage based on actual loaded images
237                    const percent = Math.floor((loadedCount / allFrameCount) * 100);
238                    
239                    // Log every 5% or so
240                    if (percent % 5 === 0 && percent !== loaderState.lastPercent) {
241                        //console.log(`Loading progress: ${loadedCount}/${allFrameCount} (${percent}%)`);
242                        loaderState.lastPercent = percent;
243                    }
244                    
245                    // Log errors
246                    if (event.type === 'error') {
247                        console.log('Image failed to load:', img.src);
248                    }
249                    
250                    // Update the counter display
251                    updateCounterDisplay(percent);
252                    
253                    // When all images are loaded
254                    if (loadedCount >= allFrameCount) {
255                        console.log('✅ All ' + allFrameCount + ' frames loaded successfully');
256                        loaderState.loadingComplete = true;
257                        // Initialize final transition once everything is loaded
258                        loaderInit();
259                    }
260                };
261                
262                allImages.push(img);
263            }
264            
265            // Also monitor any other images in the page content
266            $('#imagesLoaded').imagesLoaded()
267                .always(function(instance) {
268                    //console.log('Content images loaded.');
269                })
270                .done(function(instance) {
271                    //console.log('All content images successfully loaded.');
272                })
273                .fail(function(instance) {
274                    console.log('At least one content image failed to load.');
275                })
276                .progress(function(instance, image) {
277                    let total = instance.images.length;
278                    let loaded = instance.progressedCount;
279                    
280                    // Log more details for debugging
281                    //console.log('Content image loaded:', image.img.src);
282                    //console.log('Content loaded:', loaded, 'Total:', total);
283                    if (!image.isLoaded) {
284                        console.log('Content image failed to load:', image.img.src);
285                    }
286                });
287                
288        }, 500);
289    
290        // Function to update the counter display
291        function updateCounterDisplay(percent) {
292            percent = Math.floor(Math.max(0, Math.min(100, percent)));
293            
294            // Format counter with individual spans for animation
295            let formattedPercentWithSpans = percent.toString()
296                .padStart(2, '0')
297                .split('')
298                .map(char => `<span>${char}</span>`)
299                .join('');
300            
301            $('.counter').html(formattedPercentWithSpans);
302            document.body.style.setProperty('--loader-counter', `${percent}`);
303            
304            //console.log('Counter updated:', percent);
305            
306            // Trigger SVG animation at 40%
307            if (percent >= 70 && !loaderState.animationTriggered) {
308                loaderState.animationTriggered = true;
309                //console.log('Triggering SVG animation at 40%');
310                
311                gsap.to("[loader-svg-out] img", {
312                    duration: 1.5,
313                    filter: 'blur(0px)',
314                    opacity: 1,
315                    stagger: 1,
316                    ease: 'power1.out',
317                });
318
319                gsap.to("[loader-bottle-outer]", {
320                    opacity: 1,
321                    duration: 1,
322                    ease: 'power1.out'
323                });
324            }
325            
326            // Update counter width - use a fresh tween for each update
327            gsap.to(".counter-width", {
328                width: `${percent * 0.4}vw`, 
329                duration: 0.3,
330                ease: "power1.out"
331            });
332        }
333    
334        // Final animation sequence
335        function loaderInit() {
336            console.log('✔ Starting loaderInit animation sequence');
337            
338            function startAnimation() {
339                gsap.to(airpods, {
340                    frame: frameCount - 1,
341                    duration: 1,
342                    ease: "power2.inOut",
343                    repeat: 0,
344                    onUpdate: () => {
345                        airpods.frame = Math.min(frameCount - 1, Math.max(0, airpods.frame));
346                        render();
347                    },
348                });
349            }
350            
351            // Start a timeline
352            var tl = gsap.timeline();
353    
354            // First functions to run
355            tl.call(function() {
356                setTimeout(() => {
357                    window.scrollTo(0, 0);
358                }, 300);
359                scrollControl.stop();
360            });
361    
362            // Change cursor to wait from the get go. We will remove after the animation
363            tl.set("html", {
364                cursor: "wait"
365            });
366    
367            /** START CANVAS SCRUB **/
368            tl.call(function() {
369                console.log('Starting bottle animation');
370                startAnimation();
371            }, null, 1.6);
372    
373            tl.to("[loader-bottle]", {
374                duration: 2.5,
375                scale: 1,
376                ease: 'power3.inOut'
377            }, 1);
378    
379            tl.from("[loader-bottle-outer]", {
380                //xPercent: -6,
381                rotate: 10,
382                duration: 1,
383                ease: "expo.inOut",
384            }, 1.5);
385    
386            tl.to("[loader-bottle-outer-secondary]", {
387                opacity: 0,
388                filter: 'blur(20px)',
389                scale: 3,
390                duration: 1,
391                ease: 'power4.out'
392            }, 2.4);
393    
394    
395            // Spinning text
396            tl.to("[loader-svg-out]", {
397                scale: 3,
398                opacity: 0,
399                duration: 2,
400                rotate: 100,
401                filter: 'blur(30px)',
402                ease: 'expo.inOut'
403            }, 1);
404    
405            tl.to("[counter-out]", {
406                scale: 2,
407                filter: 'blur(30px)',
408                opacity: 0,
409                duration: 2,
410                ease: 'expo.inOut'
411            }, 1);
412    
413    
414    
415    
416            tl.to("[loader-bg]", {
417                opacity: 0,
418                duration: 2,
419                ease: 'power1.out'
420            }, 1.6);
421    
422    
423            tl.to(".nav", {
424                opacity: 1,
425                ease: 'power3.inOut'
426            }, 3);
427                
428            /** END OF INNER ANIMATION ELEMENTS **/
429    
430            // This is the end of the loader animation
431            const transitionOffset = 3.1;
432            tl.to(".loader", {
433                opacity: 0,
434                duration: 0,
435                ease: 'power1.out',
436            }, transitionOffset);
437    
438            // This is the end of the loader animation and changes the cursor to normal
439            tl.set("html", {
440                cursor: "auto",
441            }, transitionOffset);
442    
443            // And this brings in the first page transition
444            tl.call(function() {
445                console.log('Loader complete, starting page transition');
446                pageTransitionEnter();
447                $('.loader').remove();
448            }, null, transitionOffset);
449        }
450    }
451
452    if (!document.querySelector('[data-barba-namespace="home"]')) {
453
454            $('body').addClass('doc-loaded');
455            
456            var tl = gsap.timeline();
457
458            tl.to("[loader-bg]", {
459                opacity: 0,
460                duration: 0,
461                ease: 'power1.out'
462            }, 0);
463                    
464            /** END OF INNER ANIMATION ELEMENTS **/
465            tl.to(".loader", {
466                opacity: 0,
467                duration: 0.6,
468                ease: 'power1.out',
469            }, 0.6);
470    
471            // This is the end of the loader animation and changes the cursor to normal
472            tl.set("html", {
473                cursor: "auto",
474            }, 0.6);
475    
476            // And this brings in the first page transition
477            tl.call(function() {
478                console.log('Loader complete, starting page transition');
479                pageTransitionEnter();
480                //$('.loader').remove();
481            }, null, 0.6);
482    }
483}
484
485// This is the enter page transition - also enter after first loader
486export function pageTransitionEnter(data) {
487    $('main').addClass('pageTransitionEnter');
488
489    
490    // Change bodu color wthis this attribute data-bg="rgb(0, 50, 50)" in main element.
491    let bgColor = document.querySelector('main').getAttribute('data-bg');
492    document.body.style.backgroundColor = bgColor;
493    
494    // Start timeline for below animations
495    var tl = gsap.timeline();
496
497    // Reason for this lenis stop is to bring in the page animations first
498    tl.call(function() {
499        scrollControl.stop();
500    });
501    
502    //Etical life paths
503    tl.from(".pathAnim1", {
504        duration: 0.6,
505        opacity: 0,
506        yPercent: 100,
507        ease: "back.out(1.3)",
508        delay: 0.5
509    }, 0);
510    tl.from(".pathAnim2", {
511        duration: 0.6,
512        opacity: 0,
513        yPercent: 100,
514        ease: "back.out(1.3)",
515        delay: 0.5
516    }, 0.07);
517    tl.from(".pathAnim3", {
518        duration: 0.6,
519        opacity: 0,
520        yPercent: 100,
521        ease: "back.out(1.3)",
522        delay: 0.5
523    }, 0.14);
524    tl.from(".pathAnim4", {
525        duration: 0.6,
526        opacity: 0,
527        yPercent: 100,
528        ease: "back.out(1.3)",
529        delay: 0.5
530    }, 0.21);
531    tl.from(".pathAnim5", {
532        duration: 0.6,
533        opacity: 0,
534        yPercent: 100,
535        ease: "back.out(1.3)",
536        delay: 0.5
537    }, 0.28);
538
539
540    // Chars
541    if ($('[transition-chars]').length) {
542        if(document.querySelector('[transition-chars]')) {
543            tl.set("[transition-chars] .single-char-inner", { 
544                opacity: 0,
545                rotate: 0.001,
546                scale: 1.2,
547                filter: 'blur(20px)',
548            }, 0);
549        }
550        $('[transition-chars]').each(function () {
551            let delay = parseFloat($(this).attr('transition-chars')) || 0.4;
552            let element = $(this).find('.single-char-inner');
553            tl.to(element, { 
554                opacity: 1,
555                filter: 'blur(0px)',
556                scale: 1,
557                stagger: 0.04,
558                duration: 0.8,
559                ease: transitionEase,
560            }, delay + firstLoadDelay);
561        });
562    }
563    
564
565    // Words
566    if ($('[transition-words]').length) {
567        if(document.querySelector('[transition-words]')) {
568            tl.set("[transition-words] .single-word-inner", { 
569            yPercent: 120,
570            rotate: 6,
571            }, 0);
572        }
573        $('[transition-words]').each(function () {
574            let delay = parseFloat($(this).attr('transition-words')) || 0;
575            let element = $(this).find('.single-word-inner');
576            tl.to(element, { 
577                yPercent: 0,
578                rotate: 0.001,
579                stagger: 0.05,
580                duration: 1,
581                ease: transitionEase,
582            }, delay + firstLoadDelay);
583        });
584    }
585    
586    
587  
588    // Fade
589    if ($('[transition-fade]').length) {
590        $('[transition-fade]').each(function () {
591            let delay = parseFloat($(this).attr('transition-fade')) || 0.4;
592            let element = $(this);
593
594            tl.from(element, {
595                opacity: 0,
596                duration: 0.6,
597                ease: transitionEase,
598            }, delay + firstLoadDelay);
599        });
600    }
601    
602    // Fade out
603    if ($('[transition-fade-out]').length) {
604        $('[transition-fade-out]').each(function () {
605            let delay = parseFloat($(this).attr('transition-fade-out')) || 0.4;
606            let element = $(this);
607
608            tl.to(element, {
609                opacity: 0,
610                duration: 0.6,
611                ease: transitionEase,
612            }, delay + firstLoadDelay);
613        });
614    }
615    
616    // Fade blur
617    if ($('[transition-fade-blur]').length) {
618        $('[transition-fade-blur]').each(function () {
619            let delay = parseFloat($(this).attr('transition-fade-blur')) || 0;
620            let element = $(this);
621
622            tl.from(element, {
623                opacity: 0,
624                filter: 'blur(20px)',
625                duration: 0.4,
626                ease: transitionEase,
627            }, delay + firstLoadDelay);
628        });
629    }
630
631
632    if ($('[transition-fade-gummies]').length) {
633        // Get all elements at once instead of looping through them individually
634        let elements = $('[transition-fade-gummies]');
635        
636        tl.from(elements, {
637            opacity: 0,
638            scale: 3,
639            filter: 'blur(20px)',
640            duration: 0.8,
641            delay:0,
642            ease: "power3.inOut",
643            stagger: {
644                amount: 0.5, 
645                from: 'random'
646            },
647        }, 0);
648    }
649    
650    // Fade blur stagger
651    if ($('[transition-fade-blur-stagger]').length) {
652        $('[transition-fade-blur-stagger]').each(function () {
653            let delay = parseFloat($(this).attr('transition-fade')) || 0;
654            let element = $(this).find('[stagger-item]');
655            tl.from(element, {
656                opacity: 0,
657                stagger: {
658                    amount: 0.5, 
659                    from: 'random'
660                },
661                filter: 'blur(20px)',
662                duration: 0.4,
663                scale:0,
664                ease: transitionEase,
665            }, delay + firstLoadDelay);
666        });
667    }
668
669
670    tl.call(function() {
671        // Now enable lenis again
672        console.log('Page transition complete, enabling scroll');
673        scrollControl.start();
674        ScrollTrigger.refresh();
675        $('body').removeClass('first-load');
676   
677
678    }, null, "0.4");
679
680    // Change firstLoadDelay after the first load
681    firstLoadDelay = isFirstLoad ? 0 : DEFAULT_FIRST_LOAD_DELAY;
682    
683}
684
685// This is the leave page transition
686export function pageTransitionLeave(data) {
687
688    $('main').removeClass('pageTransitionEnter');
689    $('main').addClass('pageTransitionLeave');
690
691    scrollControl.stop();
692        
693    // Start timeline for below animations
694    var tl = gsap.timeline();
695    tl.to(".content", {
696        duration: 0.6,
697        filter: 'blur(80px)',
698        scaleX: 1.5,
699        opacity: 0,
700        ease: 'expo.inOut',
701        delay: 0
702    }, 0);
703}

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.