/*! ------------------------------------------------ * Project Name: Rayo - Digital Agency & Personal Portfolio HTML Template * Project Description: Elevate your digital presence with Rayo - dynamic and stylish HTML template designed for creative agencies and personal brands. With modern layouts, smooth interactions and a polished aesthetic, Rayo template helps showcase projects, services and expertise with clarity and impact. * Tags: mix_design, resume, portfolio, personal page, cv, template, one page, responsive, html5, css3, creative, clean, agency, studio * Version: 1.0.0 * Build Date: July 2025 * Last Update: July 2025 * This product is available exclusively on Themeforest * Author: mix_design * Author URI: https://themeforest.net/user/mix_design * File name: app.js * ------------------------------------------------ * ------------------------------------------------ * Table of Contents * ------------------------------------------------ * * 01. Loader & Loading Animation * 02. Lenis Scroll Plugin * 03. Typed.js Plugin * 04. Header Scroll Behavior * 05. Hero #02 Scroll Out Animation * 06. Hero #07 Scroll Out Animation * 07. Hero #08 Scroll Out Animation * 08. SVG Fallback * 09. Chrome Smooth Scroll * 10. Images Moving Ban * 11. Detecting Mobile/Desktop * 12. Smooth Scrolling * 13. Menu & Hamburger * 14. Menu Accordion * 15. Layout Masonry * 16. Accordion * 17. Magnific Popup Video * 18. Mailchimp Subscribe Form * 19. Contact Form * 20. Parallax - Ukiyo Images & Video * 21. Pinned Images * 22. Stacking Cards * 23. Animation - Buttons Common * 24. Animation - Text Reveal * 25. Animation - Scroll Universal * 26. Animation - Images Reveal on Hover * 27. Swiper Slider - Testimonials #01 * 28. Swiper Slider - Testimonials #02 * 29. Swiper Slider - Inner Pages Demo * 30. CountUp - All Counters Options * 31. Marquee - Two Lines * 32. Marquee - One Line To Right * 33. Marquee - One Line To Left * 34. SVG DOM Injection * 35. Color Switch * 36. Scroll to Top Button * 37. Parallax Universal * * ------------------------------------------------ * Table of Contents End * ------------------------------------------------ */ gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(Flip); // --------------------------------------------- // // Loader & Loading Animation Start // --------------------------------------------- // const content = document.querySelector('body'); const imgLoad = imagesLoaded(content); const loadingWrap = document.querySelector('.loading-wrap'); const loadingItems = loadingWrap.querySelectorAll('.loading__item'); const fadeInItems = document.querySelectorAll('.loading__fade'); const circle = document.querySelector('.loader__circle .progress'); const radius = 115; // le nouveau rayon const circumference = 2 * Math.PI * radius; circle.style.strokeDasharray = circumference; circle.style.strokeDashoffset = circumference; let loadedCount = 0; const totalCount = imgLoad.images.length; imgLoad.on('progress', () => { loadedCount++; const progress = loadedCount / totalCount; const offset = circumference - (progress * circumference); gsap.to(circle, { strokeDashoffset: offset, duration: 0.4, ease: "power2.out" }); }); imgLoad.on('done', () => { hideLoader(); // pageAppearance(); // à définir si tu veux }); function hideLoader() { gsap.to(".loader__wrapper", { duration: 1, opacity: 0, y: "-100%", ease: "power4.inOut", delay: 0.5 }); setTimeout(() => { document.getElementById("loader").classList.add("loaded"); }, 1500); } function pageAppearance() { gsap.set(loadingItems, { opacity: 0 }) gsap.to(loadingItems, { duration: 1.1, ease: 'power4', startAt: {y: 120}, y: 0, opacity: 1, delay: 0.8, stagger: 0.08 }, '>-=1.1'); gsap.set(fadeInItems, { opacity: 0 }); gsap.to(fadeInItems, { duration: 0.8, ease: 'none', opacity: 1, delay: 3.2 }); } // --------------------------------------------- // // Loader & Loading Animation End // --------------------------------------------- // // --------------------------------------------- // // Lenis Scroll Plugin Start // --------------------------------------------- // const lenis = new Lenis(); lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add((time) => { lenis.raf(time * 1000) }); gsap.ticker.lagSmoothing(0); // --------------------------------------------- // // Lenis Scroll Plugin End // --------------------------------------------- // $(window).on("load", function() { "use strict"; document.querySelectorAll('#main-menu a[href*="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const targetId = this.getAttribute('href').split('#')[1]; const targetElement = document.getElementById(targetId); if (targetElement) { const offset = 100; // en pixels const targetPosition = targetElement.getBoundingClientRect().top + window.scrollY - offset; window.openMenu(false); // Close the menu if it's open lenis.scrollTo(targetPosition, { duration: 1.2 }); } }); }); // --------------------------------------------- // // Typed.js Plugin Settings Start // --------------------------------------------- // var animatedHeadline = $(".animated-type"); if(animatedHeadline.length){ var typed = new Typed('#typed', { stringsElement: '#typed-strings', showCursor: true, cursorChar: '_', loop: true, typeSpeed: 70, backSpeed: 30, backDelay: 2500 }); } // --------------------------------------------- // // Typed.js Plugin Settings End // --------------------------------------------- // }); // mini-logic (vanilla) document.querySelectorAll('[data-select]').forEach(root => { const btn = root.querySelector('[data-select-toggle]'); const list = root.querySelector('[data-select-list]'); const hidden = root.querySelector('select'); const open = () => { list.hidden = false; btn.setAttribute('aria-expanded','true'); list.focus(); }; const close = () => { list.hidden = true; btn.setAttribute('aria-expanded','false'); // pas de focus ici ! }; btn.addEventListener('click', () => list.hidden ? open() : close()); list.addEventListener('click', e => { const opt = e.target.closest('[role="option"]'); if (!opt) return; hidden.value = opt.dataset.value; btn.textContent = opt.textContent; list.querySelectorAll('[aria-selected="true"]').forEach(el => el.removeAttribute('aria-selected')); opt.setAttribute('aria-selected','true'); close(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && !list.hidden) close(); }); }); $(window).on("scroll", function() { // --------------------------------------------- // // Header Scroll Behavior Start // --------------------------------------------- // if($(window).scrollTop() > 10) { $(".mxd-header").addClass("is-hidden"); } else { $(".mxd-header").removeClass("is-hidden"); } // --------------------------------------------- // // Header Scroll Behavior End // --------------------------------------------- // }); // --------------------------------------------- // // Hero #02 Scroll Out Animation Start // --------------------------------------------- // // Hero #02 scroll animated elements hero02FadeOutEl = document.querySelectorAll(".hero-02-static-anim-el"); hero02FadeOutEl.forEach((element) => { let hero02fadeOutTl = gsap.timeline({ scrollTrigger: { trigger: ".hero-02-static__tl-trigger", start: "top 14%", end: "top 0.2%", scrub: { scrub: true, ease: "sine", }, // markers: true }, }); hero02fadeOutTl.fromTo(element, { transform: "translate3d(0, 0, 0)", scaleY: 1, opacity: 1 }, { transform: "translate3d(0, -5rem, 0)", scaleY: 1.3, opacity: 0 }); }); // Hero #02 pinned screen fadeOutEl = document.querySelectorAll(".hero-02-fade-out-scroll"); fadeOutEl.forEach((element) => { let fadeOutTl = gsap.timeline({ scrollTrigger: { trigger: ".mxd-pinned-fullscreen__tl-trigger", start: "top 80%", end: "top 10%", scrub: { scrub: true, ease: "sine", }, // markers: true }, }); fadeOutTl.fromTo(element, { opacity: 1 }, { opacity: 0 }); }); // --------------------------------------------- // // Hero #02 Scroll Out Animation End // --------------------------------------------- // // --------------------------------------------- // // Hero #07 Scroll Out Animation Start // --------------------------------------------- // // Hero #07 scroll animated elements hero07FadeOutEl = document.querySelectorAll(".hero-07-slide-out-scroll"); hero07FadeOutEl.forEach((element) => { let hero07fadeOutTl = gsap.timeline({ scrollTrigger: { trigger: ".mxd-hero-07__tl-trigger", start: "top 86%", end: "top 10%", scrub: { scrub: true, ease: "power4.out", }, // markers: true }, }); hero07fadeOutTl.fromTo(element, { transform: "translate3d(0, 0, 0)", scaleY: 1, // opacity: 1 }, { transform: "translate3d(0, -26rem, 0)", scaleY: 0.8, // opacity: 0 }); }); // Hero #07 small scroll-out elements fadeOutEl = document.querySelectorAll(".hero-07-fade-out-scroll"); fadeOutEl.forEach((element) => { let fadeOutTl = gsap.timeline({ scrollTrigger: { trigger: ".mxd-hero-07__tl-trigger", start: "top 70%", end: "top 40%", scrub: { scrub: true, ease: "elastic.out(1,0.3)", }, // markers: true }, }); fadeOutTl.fromTo(element, { opacity: 1, transform: "translate3d(0, 0, 0)" }, { opacity: 0, transform: "translate3d(0, -10rem, 0)"}); }); // --------------------------------------------- // // Hero #07 Scroll Out Animation End // --------------------------------------------- // // --------------------------------------------- // // Hero #08 Scroll Out Animation Start // --------------------------------------------- // hero07FadeOutEl = document.querySelectorAll(".hero-08-slide-out-scroll"); hero07ScaleOutEl = document.querySelectorAll(".hero-08-scale-out-scroll"); hero07FadeOutEl.forEach((element) => { let hero07fadeOutTl = gsap.timeline({ scrollTrigger: { trigger: ".mxd-hero-08__tl-trigger", start: "top 80%", end: "top 40%", scrub: { scrub: true, ease: "power4.inOut", }, // markers: true }, }); hero07fadeOutTl.fromTo(element, { transform: "translate3d(0, 0, 0)", // scaleY: 1, opacity: 1 }, { transform: "translate3d(0, -5rem, 0)", // scaleY: 0.8, opacity: 0 }); }); hero07ScaleOutEl.forEach((element) => { let hero07scaleOutTl = gsap.timeline({ scrollTrigger: { trigger: ".mxd-hero-08__tl-trigger", start: "top 40%", end: "top 10%", scrub: { scrub: true, ease: "power4.inOut", }, // markers: true }, }); hero07scaleOutTl.fromTo(element, { transform: "translate3d(0, 0, 0)", scaleY: 1, opacity: 1 }, { transform: "translate3d(0, -5rem, 0)", scaleY: 1.2, opacity: 0 }); }); // --------------------------------------------- // // Hero #08 Scroll Out Animation End // --------------------------------------------- // $(function() { // --------------------------------------------- // // SVG Fallback Start // --------------------------------------------- // if(!Modernizr.svg) { $("img[src*='svg']").attr("src", function() { return $(this).attr("src").replace(".svg", ".png"); }); }; // --------------------------------------------- // // SVG Fallback End // --------------------------------------------- // // --------------------------------------------- // // Chrome Smooth Scroll Start // --------------------------------------------- // try { $.browserSelector(); if($("html").hasClass("chrome")) { $.smoothScroll(); } } catch(err) { }; // --------------------------------------------- // // Chrome Smooth Scroll End // --------------------------------------------- // // --------------------------------------------- // // Images Moving Ban Start // --------------------------------------------- // $("img, a").on("dragstart", function(event) { event.preventDefault(); }); // --------------------------------------------- // // Images Moving Ban End // --------------------------------------------- // // --------------------------------------------- // // Detecting Mobile/Desktop Start // --------------------------------------------- // var isMobile = false; if( /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { $('html').addClass('touch'); isMobile = true; } else { $('html').addClass('no-touch'); isMobile = false; } //IE, Edge var isIE = /MSIE 9/i.test(navigator.userAgent) || /rv:11.0/i.test(navigator.userAgent) || /MSIE 10/i.test(navigator.userAgent) || /Edge\/\d+/.test(navigator.userAgent); // --------------------------------------------- // // Detecting Mobile/Desktop End // --------------------------------------------- // // --------------------------------------------- // // Smooth Scrolling Start // --------------------------------------------- // $('a[href*="#"]').not('[href="#"]').not('[href="#0"]').not('#main-menu a[href*="#"]').click(function(event) { if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) { var target = $(this.hash); target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); if (target.length) { event.preventDefault(); $('html, body').animate({ scrollTop: target.offset().top }, 1000, function() { var $target = $(target); $target.focus(); if ($target.is(":focus")) { return false; } else { $target.attr('tabindex','-1'); $target.focus(); }; }); } } }); // --------------------------------------------- // // Smooth Scrolling End // --------------------------------------------- // // --------------------------------------------- // // Menu & Hamburger Start // --------------------------------------------- // $(".mxd-nav__wrap").each(function() { let hamburgerEl = $(this).find(".mxd-nav__hamburger"); let navLineEl = $(this).find(".hamburger__line"); let menuContainEl = $(this).find(".mxd-menu__contain"); let flipItemEl = $(this).find(".hamburger__base"); let menuWrapEl = $(this).find(".mxd-menu__wrapper"); let menuBaseEl = $(this).find(".mxd-menu__base"); let menuLinkEl = $(this).find(".mxd-menu__link"); let menuItem = $(this).find(".main-menu__item"); let videoEl = $(this).find(".menu-promo__video"); let fadeInEl = $(this).find(".menu-fade-in"); let flipDuration = 0.6; function flip(forwards) { let state = Flip.getState(flipItemEl); if (forwards) { flipItemEl.appendTo(menuContainEl); } else { flipItemEl.appendTo(hamburgerEl); } Flip.from(state, { ease: "power4.inOut", duration: 0.8 }); } let tl = gsap.timeline({ paused: true }); tl.set(menuWrapEl, { display: "flex" }); tl.from(menuBaseEl, { opacity: 0, duration: flipDuration, ease: "none", onStart: () => { flip(true); } }); tl.to(navLineEl.eq(0), { y: 5, duration: 0.16 }, "<") tl.to(navLineEl.eq(1), { y: -5, duration: 0.16 }, "<") tl.to(navLineEl.eq(0), { rotate: 45, duration: 0.16 }, 0.2) tl.to(navLineEl.eq(1), { rotate: -45, duration: 0.16 }, 0.2) tl.add("fade-in-up") .from(menuItem, { opacity: 0, yPercent: 50, duration: 0.2, stagger: {amount: 0.2}, onReverseComplete: () => { flip(false); } }, "fade-in-up") .from(videoEl, { opacity: 0, yPercent: 20, duration: 0.2, }, "fade-in-up"); tl.from(fadeInEl, { opacity: 0, duration: 0.3, }); function openMenu(open) { console.log("openMenu", open); if (open) { tl.timeScale(1).play(); hamburgerEl.addClass("nav-open"); } else { tl.timeScale(1).reverse(); hamburgerEl.removeClass("nav-open"); } } window.openMenu = openMenu; hamburgerEl.on("click", function() { event.preventDefault(); if ($(this).hasClass("nav-open")) { openMenu(false); } else { openMenu(true); } }); menuBaseEl.on("click", function () { openMenu(false); }); $(document).on("keydown", function (e) { if (e.key === "Escape") { openMenu(false); } }); window.addEventListener("beforeunload", (event) => { openMenu(false); }); }); // --------------------------------------------- // // Menu & Hamburger End // --------------------------------------------- // // --------------------------------------------- // // Header/Menu Z-index Change Start // --------------------------------------------- // $(".mxd-nav__hamburger").on("click", function() { if ($(".mxd-nav__hamburger").hasClass("nav-open")) { $(".mxd-header").addClass("menu-is-visible"); } else { setTimeout(function() { $(".mxd-header").removeClass("menu-is-visible"); }, 1100); } }); // --------------------------------------------- // // Header/Menu Z-index Change End // --------------------------------------------- // // --------------------------------------------- // // Menu Accordion Start // --------------------------------------------- // var Accordion = function(el, multiple) { this.el = el || {}; this.multiple = multiple || false; var links = this.el.find('.main-menu__toggle'); links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown) } Accordion.prototype.dropdown = function(e) { var $el = e.data.el; $this = $(this), $next = $this.next(); $next.slideToggle(); $this.parent().toggleClass('open'); if (!e.data.multiple) { $el.find('.submenu').not($next).slideUp().parent().removeClass('open'); }; } var accordion = new Accordion($('#main-menu'), false); // --------------------------------------------- // // Menu Accordion End // --------------------------------------------- // // --------------------------------------------- // // Layout Masonry After Each Image Loads Start // --------------------------------------------- // $('.mxd-projects-masonry__gallery').imagesLoaded().progress( function() { $('.mxd-projects-masonry__gallery').masonry('layout'); ScrollTrigger.refresh(); }); // --------------------------------------------- // // Layout Masonry After Each Image Loads End // --------------------------------------------- // // --------------------------------------------- // // Accordion Start // --------------------------------------------- // $(".mxd-accordion__title").on("click", function(e) { e.preventDefault(); var $this = $(this); if (!$this.hasClass("accordion-active")) { $(".mxd-accordion__content").slideUp(400); $(".mxd-accordion__title").removeClass("accordion-active"); $('.mxd-accordion__arrow').removeClass('accordion-rotate'); } $this.toggleClass("accordion-active"); $this.next().slideToggle(); $('.mxd-accordion__arrow',this).toggleClass('accordion-rotate'); }); // --------------------------------------------- // // Accordion End // --------------------------------------------- // // --------------------------------------------- // // Magnific Popup Video Start // --------------------------------------------- // $('#showreel-trigger').magnificPopup({ type: 'iframe', mainClass: 'mfp-fade', removalDelay: 160, preloader: false, fixedContentPos: false, callbacks: { beforeOpen: function() { $('body').addClass('overflow-hidden'); lenis.stop(); }, close: function() { $('body').removeClass('overflow-hidden'); lenis.start(); } } }); // --------------------------------------------- // // Magnific Popup Video End // --------------------------------------------- // // --------------------------------------------- // // Mailchimp Subscribe Form Start // --------------------------------------------- // $('.notify-form').ajaxChimp({ callback: mailchimpCallback, url: 'https://club.us10.list-manage.com/subscribe/post?u=e8d650c0df90e716c22ae4778&id=54a7906900&f_id=00b64ae4f0' }); function mailchimpCallback(resp) { if(resp.result === 'success') { $('.notify').find('.form').addClass('is-hidden'); $('.notify').find('.subscription-ok').addClass('is-visible'); setTimeout(function() { // Done Functions $('.notify').find('.subscription-ok').removeClass('is-visible'); $('.notify').find('.form').delay(300).removeClass('is-hidden'); $('.notify-form').trigger("reset"); }, 5000); } else if(resp.result === 'error') { $('.notify').find('.form').addClass('is-hidden'); $('.notify').find('.subscription-error').addClass('is-visible'); setTimeout(function() { // Done Functions $('.notify').find('.subscription-error').removeClass('is-visible'); $('.notify').find('.form').delay(300).removeClass('is-hidden'); $('.notify-form').trigger("reset"); }, 5000); } }; // --------------------------------------------- // // Mailchimp Subscribe Form End // --------------------------------------------- // // --------------------------------------------- // // Contact Form Start // --------------------------------------------- // $("#contact-form").submit(function() { //Change var th = $(this); $.ajax({ type: "POST", url: "mail.php", //Change data: th.serialize() }).done(function() { $('.contact').find('.form').addClass('is-hidden'); $('.contact').find('.form__reply').addClass('is-visible'); setTimeout(function() { // Done Functions $('.contact').find('.form__reply').removeClass('is-visible'); $('.contact').find('.form').delay(300).removeClass('is-hidden'); th.trigger("reset"); }, 5000); }); return false; }); // --------------------------------------------- // // Contact Form End // --------------------------------------------- // }); // --------------------------------------------- // // Parallax - Ukiyo Images & Video Start // --------------------------------------------- // const images = document.querySelectorAll(".parallax-img"); const imagesSmall = document.querySelectorAll(".parallax-img-small"); const video = document.querySelectorAll(".parallax-video"); new Ukiyo(images,{ scale: 1.5, speed: 1.5, externalRAF: false, }); new Ukiyo(imagesSmall,{ scale: 1.2, speed: 1.5, externalRAF: false }); new Ukiyo(video,{ scale: 1.5, speed: 1.5, externalRAF: false }); // --------------------------------------------- // // Parallax - Ukiyo Images & Video End // --------------------------------------------- // // --------------------------------------------- // // Pinned Images (for Services Block) Start // --------------------------------------------- // $(".mxd-pinned").each(function (index) { let childTriggers = $(this).find(".mxd-pinned__text-item"); let childTargets = $(this).find(".mxd-pinned__img-item"); function makeItemActive(index) { childTriggers.removeClass('is-active'); childTargets.removeClass('is-active'); childTriggers.eq(index).addClass('is-active'); childTargets.eq(index).addClass('is-active'); } makeItemActive(0); childTriggers.each(function (index) { ScrollTrigger.create({ trigger: $(this), start: "top center", end: "bottom center", onToggle: (isActive) => { if (isActive) { makeItemActive(index); } } }) }); }); // --------------------------------------------- // // Pinned Images (for Services Block) End // --------------------------------------------- // // --------------------------------------------- // // Stacking Cards Start // --------------------------------------------- // document.querySelectorAll('.stack-wrapper').forEach((wrapper) => { const cards = wrapper.querySelectorAll('.stack-item'); const stickySpace = wrapper.querySelector('.stack-offset'); const animation = gsap.timeline(); let cardHeight; if(cards.length) { function initCards(){ animation.clear(); cardHeight = cards[0].offsetHeight; cards.forEach((card, index) => { if(index > 0){ gsap.set(card, {y:index * cardHeight}); animation.to(card, {y:0, duration: index*0.5, ease: "none"},0); } }); }; initCards(); ScrollTrigger.create({ trigger: wrapper, start: "top top", pin: true, end: ()=>`+=${(cards.length * cardHeight) + (stickySpace ? stickySpace.offsetHeight : 0)}`, scrub: true, animation: animation, // markers: true, invalidateOnRefresh: true }); ScrollTrigger.addEventListener("refreshInit", initCards); } }); // --------------------------------------------- // // Stacking Cards End // --------------------------------------------- // // --------------------------------------------- // // Animation - Buttons Common Start // --------------------------------------------- // let elements = document.querySelectorAll(".btn-anim .btn-caption"); elements.forEach((element) => { let innerText = element.innerText; element.innerHTML = ""; let textContainer = document.createElement("div"); textContainer.classList.add("btn-anim__block"); for (let letter of innerText) { let span = document.createElement("span"); span.innerText = letter.trim() === "" ? "\xa0" : letter; span.classList.add("btn-anim__letter"); textContainer.appendChild(span); } element.appendChild(textContainer); element.appendChild(textContainer.cloneNode(true)); }); elements.forEach((element) => { element.addEventListener("mouseover", () => { element.classList.remove("play"); }) }); // --------------------------------------------- // // Animation - Buttons Common End // --------------------------------------------- // // --------------------------------------------- // // Animation - Text Reveal Start // --------------------------------------------- // const splitTypes = document.querySelectorAll(".reveal-type"); splitTypes.forEach((char,i) => { const text = new SplitType(char, { types: 'words, chars' }); gsap.from(text.chars, { scrollTrigger: { trigger: char, start: 'top 80%', end: 'top 20%', scrub: true, markers: false }, opacity: 0.2, stagger: 0.1 }); }); const animInUp = document.querySelectorAll(".reveal-in-up"); animInUp.forEach((char,i) => { const text = new SplitType(char); gsap.from(text.chars, { scrollTrigger: { trigger: char, start: 'top 90%', end: 'top 20%', scrub: true, // markers: true }, transformOrigin: "top left", //rotationY: 90, y: 10, stagger: 0.2, delay: 0.2, duration: 2, //opacity: 0.2 }); }); // --------------------------------------------- // // Animation - Text Reveal End // --------------------------------------------- // // --------------------------------------------- // // Animation - Scroll Universal Animations Start // --------------------------------------------- // // Scroll Rotating Animation const animateRotation = document.querySelectorAll(".animate-rotation"); animateRotation.forEach((section) => { var value = $(section).data("value"); gsap.fromTo(section, { ease: 'sine', rotate: 0, }, { rotate: value, scrollTrigger: { trigger: section, scrub: true, toggleActions: 'play none none reverse', } }); }); //Scroll Animation In Up const animateInUp = document.querySelectorAll(".anim-uni-in-up"); animateInUp.forEach((element) => { gsap.fromTo(element, { opacity: 0, y: 50, ease: 'sine', }, { y: 0, opacity: 1, scrollTrigger: { trigger: element, toggleActions: 'play none none reverse', } }); }); // Scroll Animation Scale In const animateInUpFront = document.querySelectorAll(".anim-uni-scale-in"); animateInUpFront.forEach((element) => { gsap.fromTo(element, { opacity: 1, y: 50, //x: 70, scale: 1.2, ease: 'sine', }, { y: 0, x: 0, opacity: 1, scale: 1, scrollTrigger: { trigger: element, toggleActions: 'play none none reverse', } }); }); // Scroll Animation Scale In Right const animateInUpRight = document.querySelectorAll(".anim-uni-scale-in-right"); animateInUpRight.forEach((element) => { gsap.fromTo(element, { opacity: 1, y: 50, x: -70, scale: 1.2, ease: 'sine', duration: 5 }, { y: 0, x: 0, opacity: 1, scale: 1, scrollTrigger: { trigger: element, toggleActions: 'play none none reverse', } }); }); // Scroll Animation Scale In Left const animateInUpLeft = document.querySelectorAll(".anim-uni-scale-in-left"); animateInUpLeft.forEach((element) => { gsap.fromTo(element, { opacity: 1, y: 50, x: 70, scale: 1.2, ease: 'sine', }, { y: 0, x: 0, opacity: 1, scale: 1, scrollTrigger: { trigger: element, toggleActions: 'play none none reverse', } }); }); // Grid Animation 2 cards if(document.querySelector(".animate-card-2")) { gsap.set(".animate-card-2", {y: 50, opacity: 0}); ScrollTrigger.batch(".animate-card-2", { interval: 0.1, batchMax: 2, duration: 3, onEnter: batch => gsap.to(batch, { opacity: 1, y: 0, ease: 'sine', stagger: {each: 0.15, grid: [1, 2]}, overwrite: true }), onLeave: batch => gsap.set(batch, {opacity: 1, y: 0, overwrite: true}), onEnterBack: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: 0.15, overwrite: true}), onLeaveBack: batch => gsap.set(batch, {opacity: 0, y: 50, overwrite: true}) }); ScrollTrigger.addEventListener("refreshInit", () => gsap.set(".animate-card-2", {y: 0, opacity: 1})); }; // Grid Animation 3 cards if(document.querySelector(".animate-card-3")) { gsap.set(".animate-card-3", {y: 50, opacity: 0}); ScrollTrigger.batch(".animate-card-3", { interval: 0.1, batchMax: 3, duration: 3, onEnter: batch => gsap.to(batch, { opacity: 1, y: 0, ease: 'sine', stagger: {each: 0.15, grid: [1, 3]}, overwrite: true }), onLeave: batch => gsap.set(batch, {opacity: 1, y: 0, overwrite: true}), onEnterBack: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: 0.15, overwrite: true}), onLeaveBack: batch => gsap.set(batch, {opacity: 0, y: 50, overwrite: true}) }); ScrollTrigger.addEventListener("refreshInit", () => gsap.set(".animate-card-3", {y: 0, opacity: 1})); }; // Grid Animation 4 cards if(document.querySelector(".animate-card-4")) { gsap.set(".animate-card-4", {y: 50, opacity: 0}); ScrollTrigger.batch(".animate-card-4", { interval: 0.1, batchMax: 4, delay: 1000, onEnter: batch => gsap.to(batch, { opacity: 1, y: 0, ease: 'sine', stagger: {each: 0.15, grid: [1, 4]}, overwrite: true }), onLeave: batch => gsap.set(batch, {opacity: 1, y: 0, overwrite: true}), onEnterBack: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: 0.15, overwrite: true}), onLeaveBack: batch => gsap.set(batch, {opacity: 0, y: 50, overwrite: true}) }); ScrollTrigger.addEventListener("refreshInit", () => gsap.set(".animate-card-4", {y: 0, opacity: 1})); }; // Grid Animation 5 cards if(document.querySelector(".animate-card-5")) { gsap.set(".animate-card-5", {y: 50, opacity: 0}); ScrollTrigger.batch(".animate-card-5", { interval: 0.1, batchMax: 5, delay: 1000, onEnter: batch => gsap.to(batch, { opacity: 1, y: 0, ease: 'sine', stagger: {each: 0.15, grid: [1, 5]}, overwrite: true }), onLeave: batch => gsap.set(batch, {opacity: 1, y: 0, overwrite: true}), onEnterBack: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: 0.15, overwrite: true}), onLeaveBack: batch => gsap.set(batch, {opacity: 0, y: 50, overwrite: true}) }); ScrollTrigger.addEventListener("refreshInit", () => gsap.set(".animate-card-5", {y: 0, opacity: 1})); }; // Top to Bottom Animation toBottomEl = document.querySelectorAll(".anim-top-to-bottom"); toBottomEl.forEach((element) => { let toBottomTl = gsap.timeline({ scrollTrigger: { trigger: ".fullwidth-text__tl-trigger", start: "top 99%", end: "top 24%", scrub: { scrub: true, ease: "none" }, // markers: true }, }); toBottomTl.fromTo(element, { transform: "translate3d(0, -100%, 0)" }, { transform: "translate3d(0, 0, 0)" }); }); // Zoom In / Zoom Out Container Animations const docStyle = getComputedStyle(document.documentElement); const zoomInContainer = document.querySelectorAll(".anim-zoom-in-container"); const zoomOutContainer = document.querySelectorAll(".anim-zoom-out-container"); // Zoom In zoomInContainer.forEach((element) => { let zoomInBlockTl = gsap.timeline({ scrollTrigger: { trigger: element, start: "top 82%", end: "top 14%", scrub: { scrub: true, ease: "power4.inOut" }, // markers: true }, }); zoomInBlockTl.fromTo(element, { borderRadius: '200px', transform: "scale3d(0.94, 1, 1)" }, { borderRadius: docStyle.getPropertyValue("--_radius-l"), transform: "scale3d(1, 1, 1)" }); }); // Zoom Out zoomOutContainer.forEach((element) => { let zoomOutBlockTl = gsap.timeline({ scrollTrigger: { trigger: element, start: "top 82%", end: "top 14%", scrub: { scrub: true, ease: "power4.inOut" }, // markers: true }, }); zoomOutBlockTl.fromTo(element, { borderRadius: '200px', transform: "scale3d(1.14, 1, 1)" }, { borderRadius: docStyle.getPropertyValue("--_radius-l"), transform: "scale3d(1, 1, 1)", }); }); // --------------------------------------------- // // Animation - Scroll Universal Animations End // --------------------------------------------- // // --------------------------------------------- // // Animation - Images Reveal on Hover Start // --------------------------------------------- // const link = document.querySelectorAll('.hover-reveal__item'); const linkHoverReveal = document.querySelectorAll('.hover-reveal__content'); const linkImages = document.querySelectorAll('.hover-reveal__image'); for(let i = 0; i < link.length; i++) { link[i].addEventListener('mousemove', (e) => { linkHoverReveal[i].style.opacity = 1; linkHoverReveal[i].style.transform = `translate(-80%, -50% )`; linkImages[i].style.transform = 'scale(1, 1)'; linkHoverReveal[i].style.left = e.clientX + "px"; }) link[i].addEventListener('mouseleave', (e) => { linkHoverReveal[i].style.opacity = 0; linkHoverReveal[i].style.transform = `translate(-80%, -50%)`; linkImages[i].style.transform = 'scale(1, 1.4)'; }) } // --------------------------------------------- // // Animation - Images Reveal on Hover End // --------------------------------------------- // // --------------------------------------------- // // Swiper Slider - Testimonials #01 Start // --------------------------------------------- // const testimonialsSlider = document.querySelector("testimonials-slider"); if (!testimonialsSlider) { const swiper = new Swiper('.swiper-testimonials', { slidesPerView: 'auto', grabCursor: true, spaceBetween: 30, autoplay: true, delay: 3000, speed: 1000, loop: true, parallax: true, loopFillGroupWithBlank: true, pagination: { el: ".swiper-pagination", type: "fraction", }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, }); }; // --------------------------------------------- // // Swiper Slider - Testimonials #01 End // --------------------------------------------- // // --------------------------------------------- // // Swiper Slider - Testimonials #02 Start // --------------------------------------------- // const testimonialsSlider2 = document.querySelector("testimonials-slider-2"); if (!testimonialsSlider2) { const swiper = new Swiper('.swiper-testimonials-2', { slidesPerView: 1, grabCursor: true, effect: 'fade', spaceBetween: 30, autoplay: true, delay: 3000, speed: 1000, loop: true, parallax: true, loopFillGroupWithBlank: true, pagination: { el: ".swiper-pagination", type: "fraction", }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, }); }; // --------------------------------------------- // // Swiper Slider - Testimonials #02 End // --------------------------------------------- // // --------------------------------------------- // // Swiper Slider - Inner Pages Demo Start // --------------------------------------------- // const innerDemoSlider = document.querySelector("mxd-demo-swiper"); if (!innerDemoSlider) { const swiper = new Swiper('.mxd-demo-swiper', { breakpoints: { 640: { slidesPerView: 1, spaceBetween: 30, }, 768: { slidesPerView: 3, spaceBetween: 30, }, 1600: { slidesPerView: 3, spaceBetween: 30, }, }, loop: true, parallax: true, autoplay: { disableOnInteraction: false, enabled: true }, grabCursor: true, speed: 600, centeredSlides: true, keyboard: { enabled: true }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, }); }; // --------------------------------------------- // // Swiper Slider - Inner Pages Demo End // --------------------------------------------- // // --------------------------------------------- // // CountUp - All Counters Options Start // --------------------------------------------- // const optionsNormal = { enableScrollSpy: true }; const optionsDecimal = { decimalPlaces: 1, enableScrollSpy: true }; const optionsDecimalTwo = { decimalPlaces: 2, enableScrollSpy: true }; const optionsPercent = { suffix: '%', enableScrollSpy: true }; const optionsK = { suffix: 'K', enableScrollSpy: true }; const optionsPlus = { suffix: '+', enableScrollSpy: true }; // --------------------------------------------- // // CountUp - All Counters Options End // --------------------------------------------- // // --------------------------------------------- // // Marquee - Two Lines Start // --------------------------------------------- // const initMarquees = () => { const items = [...document.querySelectorAll(".marquee--gsap")]; if (items) { const marqueeObject = { top: { el: null, width: 0 }, bottom: { el: null, width: 0 } }; items.forEach((itemBlock) => { marqueeObject.top.el = itemBlock.querySelector(".marquee__top"); marqueeObject.bottom.el = itemBlock.querySelector(".marquee__bottom"); marqueeObject.top.width = marqueeObject.top.el.offsetWidth; marqueeObject.bottom.width = marqueeObject.bottom.el.offsetWidth; marqueeObject.top.el.innerHTML += marqueeObject.top.el.innerHTML; marqueeObject.bottom.el.innerHTML += marqueeObject.bottom.el.innerHTML; let dirFromLeft = "-=50%"; let dirFromRight = "+=50%"; let master = gsap .timeline() .add(marquee(marqueeObject.top.el, 30, dirFromLeft), 0) .add(marquee(marqueeObject.bottom.el, 30, dirFromRight), 0); let tween = gsap.to(master, { duration: 1.5, timeScale: 1, paused: true }); let timeScaleClamp = gsap.utils.clamp(1, 6); ScrollTrigger.create({ start: 0, end: "max", onUpdate: (self) => { master.timeScale(timeScaleClamp(Math.abs(self.getVelocity() / 200))); tween.invalidate().restart(); } }); }); } }; const marquee = (item, time, direction) => { let mod = gsap.utils.wrap(0, 50); return gsap.to(item, { duration: time, ease: "none", x: direction, modifiers: { x: (x) => (direction = mod(parseFloat(x)) + "%") }, repeat: -1 }); }; initMarquees(); // --------------------------------------------- // // Marquee - Two Lines End // --------------------------------------------- // // --------------------------------------------- // // Marquee - One Line To Right Start // --------------------------------------------- // const initMarquee = () => { const items = [...document.querySelectorAll(".marquee-right--gsap")]; if (!items.length) return; items.forEach((itemBlock) => { const el = itemBlock.querySelector(".marquee__toright"); if (!el) return; // Contenu dupliqué pour le loop el.innerHTML += el.innerHTML; const dirFromRight = "+=50%"; const anim = marqueeRight(el, 30, dirFromRight); // <- tween principal const master = gsap.timeline().add(anim, 0); // Tween "kick" pour les changements de vitesse de scroll const tween = gsap.to(master, { duration: 1.5, timeScale: 1, paused: true }); const timeScaleClamp = gsap.utils.clamp(1, 6); let isDragging = false; ScrollTrigger.create({ start: 0, end: "max", onUpdate: (self) => { if (isDragging) return; const ts = timeScaleClamp(Math.abs(self.getVelocity() / 200)); master.timeScale(ts); tween.invalidate().restart(); }, }); // ----- GRAB / DRAG ----- addGrab(el, itemBlock, { // largeur réelle d’un cycle (on peut prendre offsetWidth du parent qui masque) widthPx: el.offsetWidth / 2, // une “moitié” = 50% de ton contenu (un cycle) onDragStart: () => { isDragging = true; master.pause(); }, onDragMove: (dxPx) => { // dx px → % → décalage temporel sur le tween // 50% de déplacement correspondent à une durée de `anim.duration()` const deltaPercent = (dxPx / (el.offsetWidth / 2)) * 50; // normalisation const shiftTime = (deltaPercent / 50) * anim.duration(); // en secondes const wrapTime = gsap.utils.wrap(0, anim.duration()); // on recule/avance le temps de l’anim (pas la position x !) master.time(wrapTime(master.time() + shiftTime)); }, onDragEnd: (velocityPxPerMs) => { // Repart dans le sens du geste, vitesse bornée const sign = velocityPxPerMs >= 0 ? 1 : -1; const speed = timeScaleClamp(Math.min(Math.abs(velocityPxPerMs) * 30, 10)); master.timeScale(sign * Math.max(1, speed)).resume(); gsap.to(master, { duration: 0.8, timeScale: sign * 1, ease: "power2.out" }); isDragging = false; }, }); }); }; const marqueeRight = (item, time, direction) => { let mod = gsap.utils.wrap(0, 50); return gsap.to(item, { duration: time, ease: "none", x: direction, modifiers: { x: (x) => (direction = mod(parseFloat(x)) + "%"), }, repeat: -1, }); }; const addGrab = (targetEl, hitArea, { widthPx, onDragStart, onDragMove, onDragEnd }) => { hitArea.style.cursor = "grab"; let isDown = false; let lastX = 0; let lastT = 0; let velocity = 0; // px/ms const onPointerDown = (e) => { isDown = true; hitArea.setPointerCapture?.(e.pointerId); hitArea.style.cursor = "grabbing"; lastX = e.clientX; lastT = performance.now(); velocity = 0; onDragStart?.(e); }; const onPointerMove = (e) => { if (!isDown) return; const now = performance.now(); const dx = e.clientX - lastX; const dt = now - lastT || 16; const instV = dx / dt; velocity = velocity * 0.7 + instV * 0.3; // 🔑 on ne touche PAS au x de l’élément : onDragMove?.(dx); lastX = e.clientX; lastT = now; }; const onPointerUp = (e) => { if (!isDown) return; isDown = false; hitArea.releasePointerCapture?.(e.pointerId); hitArea.style.cursor = "grab"; onDragEnd?.(velocity); }; const prevent = (ev) => ev.preventDefault(); hitArea.addEventListener("pointerdown", onPointerDown, { passive: true }); window.addEventListener("pointermove", onPointerMove, { passive: true }); window.addEventListener("pointerup", onPointerUp, { passive: true }); hitArea.addEventListener("dragstart", prevent); hitArea.addEventListener("mousedown", () => document.body.classList.add("no-select")); window.addEventListener("mouseup", () => document.body.classList.remove("no-select")); }; initMarquee(); // --------------------------------------------- // // Marquee - One Line To Right End // --------------------------------------------- // // --------------------------------------------- // // Marquee - One Line To Left Start // --------------------------------------------- // const initMarqueeLeft = () => { const items = [...document.querySelectorAll(".marquee-left--gsap")]; if (!items.length) return; items.forEach((itemBlock) => { const el = itemBlock.querySelector(".marquee__toleft"); if (!el) return; // duplique le contenu pour le loop el.innerHTML += el.innerHTML; const dirFromLeft = "-=50%"; const anim = marqueeLeft(el, 30, dirFromLeft); // tween principal (vers la gauche) const master = gsap.timeline().add(anim, 0); // tween "kick" pour les variations de vitesse au scroll const tween = gsap.to(master, { duration: 1.5, timeScale: 1, paused: true }); const timeScaleClamp = gsap.utils.clamp(1, 6); let isDragging = false; ScrollTrigger.create({ start: 0, end: "max", onUpdate: (self) => { if (isDragging) return; const ts = timeScaleClamp(Math.abs(self.getVelocity() / 200)); master.timeScale(ts); // forward = défilement vers la gauche tween.invalidate().restart(); }, }); // Grab / Drag (mêmes gestures que la version droite, sens inversé à l’inertie) addGrab(el, itemBlock, { widthPx: el.offsetWidth / 2, onDragStart: () => { isDragging = true; master.pause(); }, onDragMove: (dxPx) => { // Inversion du déplacement pour que le drag soit naturel à gauche const deltaPercent = ((-dxPx) / (el.offsetWidth / 2)) * 50; const shiftTime = (deltaPercent / 50) * anim.duration(); const wrapTime = gsap.utils.wrap(0, anim.duration()); master.time(wrapTime(master.time() + shiftTime)); }, onDragEnd: (velocityPxPerMs) => { // inverser le signe pour que le "fling" suive le geste en mode gauche const sign = velocityPxPerMs >= 0 ? -1 : 1; const speed = timeScaleClamp(Math.min(Math.abs(velocityPxPerMs) * 30, 10)); master.timeScale(sign * Math.max(1, speed)).resume(); gsap.to(master, { duration: 0.8, timeScale: sign * 1, ease: "power2.out" }); isDragging = false; }, }); }); }; const marqueeLeft = (item, time, direction) => { let mod = gsap.utils.wrap(0, 50); return gsap.to(item, { duration: time, ease: "none", x: direction, modifiers: { x: (x) => (direction = mod(parseFloat(x)) + "%") }, repeat: -1 }); }; initMarqueeLeft(); // --------------------------------------------- // // Marquee - One Line To Left End // --------------------------------------------- // // --------------------------------------------- // // SVG DOM Injection Start // --------------------------------------------- // var mySVGsToInject = document.querySelectorAll('img.inject-me'); var injectorOptions = { evalScripts: 'once', pngFallback: 'assets/png', each: function (svg) { } }; SVGInjector(mySVGsToInject, injectorOptions, function (totalSVGsInjected) { console.log('We injected ' + totalSVGsInjected + ' SVG(s)!'); }); // --------------------------------------------- // // SVG DOM Injection End // --------------------------------------------- // // --------------------------------------------- // // Color Switch Start // --------------------------------------------- // const themeBtn = document.querySelector('#color-switcher'); function getCurrentTheme(){ let theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; localStorage.getItem('template.theme') ? theme = localStorage.getItem('template.theme') : null; return theme; } function loadTheme(theme){ const root = document.querySelector(':root'); if(theme === "light"){ themeBtn.innerHTML = ``; } else { themeBtn.innerHTML = ``; } root.setAttribute('color-scheme', `${theme}`); }; themeBtn.addEventListener('click', () => { let theme = getCurrentTheme(); if(theme === 'dark'){ theme = 'light'; } else { theme = 'dark'; } localStorage.setItem('template.theme', `${theme}`); loadTheme(theme); }); window.addEventListener('DOMContentLoaded', () => { loadTheme(getCurrentTheme()); }); // --------------------------------------------- // // Color Switch End // --------------------------------------------- // // --------------------------------------------- // // Scroll to Top Button Start // --------------------------------------------- // const toTop = document.querySelector(".btn-to-top"); $(".btn-to-top").each(function() { toTop.addEventListener("click", function(event){ event.preventDefault() }); toTop.addEventListener("click", () => gsap.to(window, { scrollTo: 0, ease: 'power4.inOut', duration: 1.3, })); gsap.set(toTop, { opacity: 0 }); gsap.to(toTop, { opacity: 1, autoAlpha: 1, scrollTrigger: { trigger: "body", start: "top -20%", end: "top -20%", toggleActions: "play none reverse none" } }); }); // --------------------------------------------- // // Scroll to Top Button End // --------------------------------------------- // // ------------------------------------------------------------------------------ // // Parallax Universal (apply parallax effect to any element with a data-speed attribute) Start // ------------------------------------------------------------------------------ // gsap.to("[data-speed]", { y: (i, el) => (1 - parseFloat(el.getAttribute("data-speed"))) * ScrollTrigger.maxScroll(window) , ease: "none", scrollTrigger: { start: 0, end: "max", invalidateOnRefresh: true, scrub: 0 } }); // --------------------------------------------- // // Parallax Universal End // --------------------------------------------- // const cursor = document.querySelector('.cursor'); const cursorInner = document.querySelector('.cursor-inner'); let mouseX = 0, mouseY = 0; let currentX = 0, currentY = 0; let isClicking = false; // état du clic document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); document.addEventListener('mousedown', () => { isClicking = true; }); document.addEventListener('mouseup', () => { isClicking = false; }); // hover sur liens/boutons document.querySelectorAll('a, button').forEach(el => { el.addEventListener('mouseenter', () => cursor.classList.add('hover')); el.addEventListener('mouseleave', () => cursor.classList.remove('hover')); }); // optionnel : activer un halo sur des zones spécifiques document.querySelectorAll('.halo-zone').forEach(el => { el.addEventListener('mouseenter', () => cursor.classList.add('halo')); el.addEventListener('mouseleave', () => cursor.classList.remove('halo')); }); new Swiper('.swiper-outils', { loop: true, pagination: { el: '.swiper-pagination', clickable: true, }, slidesPerView: 1, autoplay: { delay: 3000, }, }); new Swiper('.swiper-expertise', { loop: true, pagination: { el: '.swiper-pagination', clickable: true, }, slidesPerView: 1, autoplay: { delay: 3000, }, }); function animate() { currentX += (mouseX - currentX) * 0.03; currentY += (mouseY - currentY) * 0.03; // translation fluide sans transition CSS cursor.style.transform = `translate3d(${currentX}px, ${currentY}px, 0) translate(20%, 20%)`; // scale géré avec transition CSS cursorInner.style.transform = `scale(${isClicking ? 0.5 : 1})`; requestAnimationFrame(animate); } animate();