var Webflow = Webflow || []; gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(CustomEase); if(window.innerWidth > 991) { const split = new SplitType("[text-split]", { types: "lines, words, chars", tagName: "span" }); } CustomEase.create("ease-out-circ", "M0,0 C0.075,0.82 0.165,1 1,1"); //Lenis functionality const lenis = new Lenis({ prevent: (el) => el.classList.contains("w-reset") }); lenis.on('scroll', (e) => { }); lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add((time)=>{ lenis.raf(time * 1000) }); gsap.ticker.lagSmoothing(0); //utility gsap scrollTrigger function createScrollTrigger(trigger, tl, startPercent) { ScrollTrigger.create({ trigger: trigger, start: `top ${startPercent}%`, // на це значення орієнтується onEnter(), onEnterBack(); end: "bottom top", // на це значення орієнтується onLeave() onEnter: () => { tl.play(); } }); } //utility handle "y" position function calculateY(index, percentage, el) { const direction = index % 2 === 0 ? -1 : 1; const elementHeight = el.offsetHeight; const offset = elementHeight * (percentage / 100); return Math.round(direction * offset); } //скрол на гору після оновлення сторінки /* Webflow.push(function () { history.scrollRestoration = "manual"; window.addEventListener('load', function() { window.scrollTo(0, 0); }); }); */ //слайдер Authors Swiper Webflow.push( function () { const swiperContainer = document.querySelector('.swiper-authors'); if(swiperContainer) { const spaceBetweenInRem = 2.6; const spaceBetweenInRemTablet = 0; const spaceBetweenInRemMobile = 0; const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize); const spaceBetweenInPx = spaceBetweenInRem * fontSize; const spaceBetweenInPxTablet = spaceBetweenInRemTablet * fontSize; const spaceBetweenInPxMobile = spaceBetweenInRemMobile * fontSize; const swiperAuthors = new Swiper(swiperContainer, { loop: true, direction: 'horizontal', slidesPerView: "auto", breakpoints: { // when window width is >= 240px 240: { spaceBetween: spaceBetweenInPxMobile }, // when window width is >= 480px 480: { spaceBetween: spaceBetweenInPxTablet }, // when window width is >= 992px 992: { spaceBetween: spaceBetweenInPx } }, navigation: { nextEl: '.swiper_btn-next-authors', prevEl: '.swiper_btn-prev-authors', } }); } }); //слайдер Reviews Swiper Webflow.push( function () { const swiperContainer = document.querySelector('.swiper-reviews'); if(swiperContainer) { const spaceBetweenInRem = 0.97; const spaceBetweenInRemTablet = 0; const spaceBetweenInRemMobile = 0; const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize); const spaceBetweenInPx = spaceBetweenInRem * fontSize; const spaceBetweenInPxTablet = spaceBetweenInRemTablet * fontSize; const spaceBetweenInPxMobile = spaceBetweenInRemMobile * fontSize; const swiperReviews = new Swiper(swiperContainer, { loop: true, direction: 'horizontal', slidesPerView: "auto", breakpoints: { // when window width is >= 240px 240: { spaceBetween: spaceBetweenInPxMobile }, // when window width is >= 480px 480: { spaceBetween: spaceBetweenInPxTablet }, // when window width is >= 992px 992: { spaceBetween: spaceBetweenInPx } }, navigation: { nextEl: '.swiper_btn-next-reviews', prevEl: '.swiper_btn-prev-reviews', } }); } }); //заборона скролу при відркитті модальних вікон і меню Webflow.push(function () { const scrollDisableElements = document.querySelectorAll('[scroll-disable-element]'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { document.body.classList.add('overflow-hidden'); lenis.stop(); } else { document.body.classList.remove('overflow-hidden'); lenis.start(); } }); }); scrollDisableElements.forEach(element => { observer.observe(element); }); }); //анімація модальних вікон Webflow.push(function () { const modals = document.querySelectorAll('[data-modal]'); if(modals.length > 0) { modals.forEach(modal => { const modalName = modal.getAttribute('data-modal'); const openTriggers = document.querySelectorAll(`[data-modal-open="${modalName}"]`); const closeTriggers = modal.querySelectorAll('[data-modal-close]'); const texts = modal.querySelectorAll('[text-split-modal]'); const modalContent = modal.querySelector('[data-modal-content]'); let isOpen = false; //hover на хрестик const closeIcon = modal.querySelector('.close-icon'); const crossLines = closeIcon.querySelectorAll('.close-icon_line'); closeIcon.addEventListener('mouseenter', () => { crossLines.forEach((line, index) => { gsap.to(line, { duration: 0.6, rotation: () => (index % 2) ? 135 : -135, ease: "expo.out" }); }); }); closeIcon.addEventListener('mouseleave', () => { crossLines.forEach((line, index) => { gsap.to(line, { duration: 0.6, rotation: () => (index % 2) ? 45 : -45, ease: "expo.out" }); }); }); function modalOpen () { if(isOpen) return; isOpen = true; gsap.set(modal, {display: "flex"}); //саме так, бо якщо зміна display відбудеться після виклику SplitType - він розрахує все в одну line. const tl = gsap.timeline(); //tl.to(modal, {display: "flex"}); через gsap.set, а не так if(modal.querySelector('[data-modal-bg]')) { tl.fromTo(modal.querySelector('[data-modal-bg]'), {opacity: 0, backdropFilter: "blur(0px)"}, {opacity: 1, backdropFilter: "blur(5px)", duration: 1.4, ease: "ease-out-circ"}); } if(modalContent) { tl.fromTo(modalContent, {opacity: 0, filter: "blur(20px)"}, {opacity: 1, filter: "blur(0px)", duration: 0.8, ease: "ease-out-circ"}, "-=1.3") tl.fromTo(modalContent, {yPercent: 20}, {yPercent: 0, duration: 0.8, ease: "ease-out-circ"}, "<"); } if(texts.length > 0) { new SplitType(texts, { types: "lines, words, chars", tagName: "span" }); texts.forEach((text, index) => { const textDelay = index * 0.5; const lines = text.querySelectorAll('.line'); lines.forEach((line, index) => { const delay = index * 0.15; const letters = line.querySelectorAll('.char'); //willChange transform краще відпрацьовує, коли заданий в css. М'якше tl.from(letters, {yPercent: 130, duration: 1.3, stagger: {amount: 0.35}, ease: "ease-out-circ", delay: delay}, `${0.2 + textDelay}`) .from(letters, {duration: 1.2, scaleY: 1.3, ease: "power1.out"}, "<"); }); }); } closeTriggers.forEach(trigger => { trigger.addEventListener("click", (e) => { e.stopPropagation(); const tween = tl.tweenFromTo(2.5, 0, { ease: "circ.out", onComplete: () => { gsap.set(modal, {display: "none"}); isOpen = false; } }); tween.timeScale(3); }, {once: true}); }); } openTriggers.forEach(trigger => { trigger.addEventListener("click", modalOpen); }); }); } }); // анімація сцени 2-ї секції Webflow.push(function () { const container = document.querySelector("[data-scene-container]"); if(container) { const bg = container.querySelector("[data-scene-bg]"); const main = container.querySelector("[data-scene-main]"); const objects = document.querySelectorAll("[data-object-parallax]"); if(objects.length > 0) { const tl = gsap.timeline({ defaults: { ease: "power3.inOut" }, scrollTrigger: { trigger: container, start: "top 100%", end: "bottom 70%", scrub: 3, //toggleActions: "play none none none", markers: false } }); tl.to(bg, {scale: 1.1}) .fromTo(main, {scale: 1.2, yPercent: -5, backdropFilter: "invert(0%)"}, {scale: 1.1, yPercent: 5, backdropFilter: "invert(100%)"}, "<"); objects.forEach(object => { const paramsArr = object.dataset.objectParallax.split(";"); const [x, y, scale] = paramsArr; tl.from(object, {xPercent: x, yPercent: y, scale: scale, willChange: "transform"}, "0"); }); } } }); //анімація перезавантаження сторінки Webflow.push(function () { const pageCover = document.querySelector('.page-cover'); if(pageCover) { const tl = gsap.timeline(); tl.to(pageCover, {autoAlpha: 0, duration: 0.5, ease: "ease.out", delay: 0.3}); } }); if (window.innerWidth > 991) { // анімація меню Webflow.push(function () { let lastScrollTop = 0; // Остання позиція скрола let scrollTimeout; let isHoveringMenu = false; let isMenuVisible = false; const menu = document.querySelector('.menu'); const menuWrapper = document.querySelector('.menu-wrapper'); const tl = gsap.timeline({ defaults: { ease: "power4.out" } }); function showMenu() { if (!isMenuVisible) { tl.clear(); tl.to(menu, {y: "0%", duration: 1, opacity: 1, rotateX: "0deg", scaleX: 1, borderColor: "#d5d5d5", backdropFilter: "blur(5px)"}) .to("[data-menu=button]", {duration: 0.6, opacity: 1, scale: 1, stagger: {amount: 0.6, from: "start"}}, "-=0.7"); isMenuVisible = true; } } function hideMenu() { if (isMenuVisible) { tl.clear(); tl.to("[data-menu=button]", {duration: 0.5, opacity: 0, scale: 0.9, stagger: {amount: 0.5, from: "end"}}) .to(menu, {duration: 1.3, opacity: 0, scaleX: 0, borderColor: "transparent", backdropFilter: "blur(0px)"}, "-=0.5"); isMenuVisible = false; } } function resetTimeout() { clearTimeout(scrollTimeout); if (!isHoveringMenu) { scrollTimeout = setTimeout(() => { hideMenu(); }, 2000); } } window.addEventListener("scroll", () => { const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop; if (currentScrollTop < lastScrollTop) { showMenu(); resetTimeout(); } lastScrollTop = currentScrollTop; }); menu.addEventListener("mouseenter", () => { clearTimeout(scrollTimeout); isHoveringMenu = true; }); menu.addEventListener("mouseleave", () => { isHoveringMenu = false; resetTimeout(); }); menuWrapper.addEventListener( "mouseenter", () => { showMenu(); resetTimeout(); }) }); // анімація кнопок в меню Webflow.push(function () { const buttons = document.querySelectorAll(".flex-h > [data-menu=button]"); buttons.forEach(button => { const bubble = button.querySelector(".menu_item-bg"); const tlEnter = gsap.timeline({paused: true}); const tlLeave = gsap.timeline({paused: true}); tlEnter.to(bubble, {height: "4rem", left: "-1px", right: "-1px", borderColor: "rgba(80, 80, 80, 0.8)", top: "50%", yPercent: -50, duration: 0.2, ease: "circ.out"}) .to(button, {backgroundColor: "rgba(204, 204, 204, 0.8)", ease: "power4.out"}, "0"); tlLeave.to(bubble, {height: "3rem", left: "0px", right: "0px", borderColor: "#d5d5d5", top: "0%", yPercent: 0, duration: 0.2, ease: "circ.out"}) .to(button, {backgroundColor: "rgba(255, 255, 255, 0.8)", ease: "power4.out"}, "0"); button.addEventListener("mouseenter", () => { tlLeave.pause(0); tlEnter.play(); }); button.addEventListener("mouseleave", () => { tlEnter.pause(0); gsap.set(bubble, {height: "4rem", left: "-1px", right: "-1px", borderColor: "rgba(80, 80, 80, 0.8)", top: "50%", yPercent: -50}); gsap.set(button, {backgroundColor: "rgba(204, 204, 204, 0.8)"}); tlLeave.play(); }); }); }); // анімація соціальних кнопок Webflow.push(function () { const socialIcons = document.querySelectorAll("[data-social-icon]"); if(socialIcons.length > 0) { const activeIcon = socialIcons[1]; const activeHoverZone = activeIcon.closest(".social-block"); gsap.set(activeIcon, { scale: 1, rotateZ: -9, yPercent: -20 }); gsap.set(activeHoverZone, { backgroundColor: "#2c28f7" }); socialIcons.forEach(icon => { const hoverZone = icon.closest(".social-block"); hoverZone.addEventListener("mouseenter", () => { if (icon !== activeIcon) { gsap.to(activeIcon, {scale: 0.4, rotateZ: 0, yPercent: 0, duration: 0.4, ease: "power2.inOut"}); gsap.to(activeHoverZone, {backgroundColor: "#101010", duration: 0.4, ease: "power2.inOut"}); } gsap.to(icon, {scale: 1, rotateZ: -9, yPercent: -20, duration: 0.4, ease: "power2.inOut"}); gsap.to(hoverZone, {backgroundColor: "#2c28f7", duration: 0.4, ease: "power2.inOut"}); }); hoverZone.addEventListener("mouseleave", () => { if (icon === activeIcon) { gsap.to(activeIcon, {scale: 1, rotateZ: -9, yPercent: -20, duration: 0.4, ease: "power2.inOut"}); gsap.to(activeHoverZone, {backgroundColor: "#2c28f7", duration: 0.4, ease: "power2.inOut"}); } gsap.to(icon, {scale: 0.4, rotateZ: 0, yPercent: 0, duration: 0.4, ease: "power2.inOut"}); gsap.to(hoverZone, {backgroundColor: "#101010", duration: 0.4, ease: "power2.inOut"}); }); }); } }); //анімація ховерів на картки Webflow.push(function () { const cards = document.querySelectorAll("[data-card]"); if(cards.length > 0) { cards.forEach(card => { const staticText = card.querySelector("[data-static-text]"); const divider = card.querySelector("[data-divider]"); const hoverText = card.querySelector("[data-hover-text]"); const offset = hoverText.offsetWidth + 10; const rect = card.querySelector("[data-rect-cover]"); const img = card.querySelector(".course-image"); const tl = gsap.timeline({paused: true}); const svgTl = gsap.timeline({paused: true, defaults: {ease: "expo.out"}}); tl.to(staticText.querySelectorAll(".char"), {yPercent: -130, scaleY: 1.3, stagger: {each: 0.03, from: "start"}, duration: 0.5, ease: "power4.inOut"}) .to(divider, {duration: 0.4, x: offset, ease: "power4.inOut"}, "-=0.4") .to(divider, {duration: 0.4, rotationX: 90, transformOrigin: "center bottom", ease: "power4.inOut"}, "-=0.1") .from(hoverText.querySelectorAll(".char"), {yPercent: 130, scaleY: 1.3, stagger: {amount: 0.15, from: "start"}, duration: 0.4, ease: "power4.inOut"}, "-=0.7") .to(card, {borderColor: "#333", borderRadius: "0", ease: "circ.inOut", duration: 0.5}, "0"); if(rect) tl.from(rect, {duration: 0.5, width: "0%", ease: "power3.inOut"}, "0"); if(img) { const moveChildren = img.querySelectorAll("[data-move]"); const drawChildren = img.querySelectorAll("[data-draw]"); const scaleChildern = img.querySelectorAll("[data-scale]"); const colorChildern = img.querySelectorAll("[data-color]"); if(moveChildren.length > 0) { svgTl.to(moveChildren, { duration: 0.5, yPercent: (index) => moveChildren[index].dataset.move }, "0"); } if(drawChildren.length > 0) { drawChildren.forEach(child => { child.style.strokeDashoffset = 0; child.style.strokeDasharray = child.getTotalLength(); }); svgTl.to(drawChildren, { duration: 1, strokeDashoffset: (index) => { const offset = 2 * drawChildren[index].getTotalLength(); return offset; } }, "0"); } if(colorChildern.length > 0) { svgTl.to(colorChildern, { fill: (index) => colorChildern[index].dataset.color, duration: 0.5, stroke: "transparent" }, "0"); } if(scaleChildern.length > 0) { svgTl.to(scaleChildern, { duration: 0.5, scale: (index) => scaleChildern[index].dataset.scale, transformOrigin: "center center", }, "0"); } } card.addEventListener("mouseenter", () => { tl.timeScale(1).play(); svgTl.timeScale(1).play(); }); card.addEventListener("mouseleave", () => { tl.timeScale(1.8).reverse(); svgTl.tweenTo(0, {duration: 0.7, ease: "expo.out"}); }); gsap.set([hoverText, rect], { opacity: 1 }); }); } }); // анімація ховеру на кнопки зі стрілками Webflow.push(function () { const buttons = document.querySelectorAll("[data-animation='saw']"); if(buttons.length > 0) { buttons.forEach(button => { const letters = button.querySelectorAll(".char"); const ellipse = button.querySelector(".button_bg"); const ellipseColor = window.getComputedStyle(ellipse).backgroundColor; const arrows = button.querySelectorAll(".button_arrow"); letters.forEach((el, index) => { const letter = el.textContent; el.setAttribute("data-letter", letter); }); //yPercent (еслив пересчете на px - получается дробное число) и дробные числа размывают текст при трансформации const tlEnter = gsap.timeline({paused: true}); tlEnter.to(letters, { y: (index) => calculateY(0, 100, letters[index]), duration: 0.4, ease: "circ.out", stagger: {amount: 0.3, from: "start"}, willChange: "transform", onComplete: function() { gsap.set(letters, {clearProps: "y"}); } }); if (arrows.length > 0) { tlEnter.to(arrows, { y: (index) => calculateY(0, 100, arrows[index]), duration: 0.3, ease: "circ.out", stagger: {amount: 0.2, from: "start"}, willChange: "transform" }, "-=0.3"); } tlEnter.to(ellipse, { keyframes: [ {backgroundImage: "linear-gradient(135deg, #101010, white)", ease: "sine.out", duration: 0.4}, {backgroundImage: "linear-gradient(45deg, white, #101010)", ease: "sine.out", duration: 0.4}, {backgroundImage: `linear-gradient(180deg, ${ellipseColor}, ${ellipseColor})`, ease: "sine.out", duration: 0.4} ] }, "0"); const tlLeave = gsap.timeline({paused: true}); tlLeave.to(letters, { y: (index) => calculateY(index, 100, letters[index]), duration: 0.4, ease: "circ.out", stagger: {amount: 0.3, from: "end"}, willChange: "transform", onComplete: function() { gsap.set(letters, {clearProps: "y"}); } }); if (arrows.length > 0) { tlLeave.to(arrows, { y: (index) => calculateY(0, 100, arrows[index]), duration: 0.3, ease: "circ.out", stagger: {amount: 0.2, from: "start"}, willChange: "transform" }, "<"); } tlLeave.to(ellipse, { keyframes: [ {backgroundImage: "linear-gradient(135deg, #101010, white)", ease: "sine.out", duration: 0.3}, {backgroundImage: "linear-gradient(45deg, white, #101010)", ease: "sine.out", duration: 0.3}, {backgroundImage: `linear-gradient(180deg, ${ellipseColor}, ${ellipseColor})`, ease: "sine.out", duration: 0.3} ] }, "<"); button.addEventListener("mouseenter", () => { tlLeave.pause(0); tlEnter.play(); }); button.addEventListener("mouseleave", () => { tlEnter.pause(0); tlLeave.play(); }); }); } }); // анімація ховеру на круглі кнопки Webflow.push(function () { const buttons = document.querySelectorAll("[data-animation='bubble']"); if(buttons.length > 0) { buttons.forEach(button => { const letters = button.querySelectorAll(".char"); const borderColor = window.getComputedStyle(button).borderColor; const circle = button.querySelector(".button-round_bg"); const tlEnter = gsap.timeline({paused: true}); if(letters.length > 0) { tlEnter.to(letters, { yPercent: 0, scaleY: 1, opacity: 1, duration: 0.8, delay: 0.4, willChange: "transform", ease: "expo.out", stagger: {amount: 0.2, from: "start"}}); } tlEnter.to(circle, { keyframes: { scaleX: [1, 1.1, 0.9, 1.05, 0.98, 1], scaleY: [1, 0.9, 1.1, 0.95, 1.02, 1], ease: "power2.out" }, duration: 1.7, ease: "power4.out", filter: "blur(8px)", backdropFilter: "blur(40px)", willChange: "transform", }, "0") .to(button, { borderColor: "transparent", duration: 1.5, ease: "power4.out" }, "0"); const tlLeave = gsap.timeline({paused: true}); tlLeave.to(circle, { keyframes: { scaleX: [1.02, 1.07, 0.95, 1.03, 0.98, 1], scaleY: [0.98, 0.93, 1.05, 0.97, 1.02, 1], ease: "power1.out" }, duration: 2, ease: "sine.inOut", filter: "blur(0px)", backdropFilter: "blur(0px)", willChange: "transform", }); tlLeave.fromTo(circle, { filter: "blur(8px)", backdropFilter: "blur(40px)", }, { duration: 1, ease: "sine.inOut", filter: "blur(0px)", backdropFilter: "blur(0px)" }, '0'); tlLeave.fromTo(button, { borderColor: "transparent", }, { borderColor: borderColor, duration: 1.5, ease: "sine.out" }, "0"); button.addEventListener("mouseenter", () => { if(letters.length > 0) gsap.set(letters, {opacity: 0, yPercent: 90, scaleY: 1.3}); tlLeave.pause(0); tlEnter.play(); }); button.addEventListener("mouseleave", () => { tlEnter.pause(0); tlLeave.play(); if(letters.length > 0) gsap.set(letters, {opacity: 1, yPercent: 0, scaleY: 1}); }); gsap.set(circle, {filter: "blur(0px)", backdropFilter: "blur(0px)"}); gsap.set(button, {borderColor: borderColor}); }); } }); // анімація ховеру на кнопки з підкресленням Webflow.push(function () { const buttons = document.querySelectorAll(".links-button"); if(buttons.length > 0) { buttons.forEach(button => { const underline = button.querySelector(".button-line"); const tl = gsap.timeline({paused: true}); tl.to(underline, {height: "120%", top: "-20%", duration: 0.4, ease: "power4.inOut"}) .to(underline, {backdropFilter: "invert(100%)", backgroundColor: "transparent", duration: 0, ease: "power4.inOut"}, "0") .to(button, {paddingLeft: "2rem", duration: 0.2, ease: "sine.inOutt"}, "-=0.4") .to(button, {paddingRight: "2rem", duration: 0.2, ease: "power4.inOut"}, "0"); button.addEventListener("mouseenter", () => { tl.play(); }); button.addEventListener("mouseleave", () => { tl.reverse(); }); }); } }); // анімація ховеру на звичайні лінки в футері Webflow.push(function () { const links = document.querySelectorAll("[data-animation='offset']"); if(links.length > 0) { links.forEach(link => { const tl = gsap.timeline({paused: true}); tl.to(link, {x: "5px", textDecoration: "underline #d5d5d5", duration: 0.2, ease: "sine.out"}); link.addEventListener("mouseenter", () => { tl.play(); }); link.addEventListener("mouseleave", () => { tl.reverse(); }); }); } }); // анімація ховеру на картки дипломних проектів студентів Webflow.push(function () { const covers = document.querySelectorAll("[data-case-media]"); if(covers.length > 0) { covers.forEach(cover => { const img = cover.querySelector("img"); const desc = cover.nextElementSibling; const card = cover.parentElement; const tlEnter = gsap.timeline({paused: true, defaults: {duration: 0.8, ease: "expo.out"}}); const tlLeave = gsap.timeline({paused: true, defaults: {duration: 0.8, ease: "expo.out"}}); gsap.set(card, {border: "2px solid transparent", borderRadius: "30px"}); if(img && desc && card) { tlEnter.to(img, {scale: 0.92, y: "0.5rem", borderRadius: "30px", willChange: "transform"}) .to(desc, {paddingLeft: "2rem", y: "-1.5rem", willChange: "transform"}, "<") .to(card, {border: "2px solid #d5d5d5"}, "<"); tlLeave.to(img, {scale: 1, y: "0", borderRadius: "0px", willChange: "transform"}) .to(desc, {paddingLeft: "0", y: "0rem", willChange: "transform"}, "<") .to(card, {border: "2px solid transparent"}, "<"); card.addEventListener("mouseenter", () => { tlLeave.pause(); tlEnter.timeScale(1).play(0); }); card.addEventListener("mouseleave", () => { tlEnter.pause(); tlLeave.timeScale(1.5).play(0); }); } }); } }); //анімація паралаксу візитівок Webflow.push(function () { const cards = document.querySelectorAll("[data-business-card]"); if(cards.length > 0) { cards.forEach(card => { const y = card.dataset.translateY || 0; const rotation = card.dataset.rotation || 0; const tl = gsap.timeline({ scrollTrigger: { trigger: card, start: "top 90%", end: "bottom -60%", scrub: 2 } }); tl.to(card, {y: y, rotationZ: rotation, ease: "linear"}); }); } }); //анімація заголовків Webflow.push(function () { const curtainAnimatedTexts = document.querySelectorAll("[text-animation=curtain]"); if(curtainAnimatedTexts.length > 0) { curtainAnimatedTexts.forEach(text => { const lines = text.querySelectorAll(".line"); lines.forEach((line, index) => { const delay = index * 0.15; const tl = gsap.timeline({paused: true}); const chars = line.querySelectorAll(".char"); tl.from(chars, {yPercent: 130, duration: 1.8, stagger: {amount: 0.35}, ease: "ease-out-circ", delay: delay}) .from(chars, {duration: 1.7, scaleY: 1.3, ease: "power1.out"}, "<"); createScrollTrigger(text, tl, 70); }); }); gsap.set("[text-animation='curtain']", { opacity: 1 }); } }); //анімація появи тегів Webflow.push(function () { const tagsContainers = document.querySelectorAll("[data-tags-container]"); tagsContainers.forEach(container => { const delay = container.dataset.delay || 0; const start = container.dataset.start || 70; const tags = container.querySelectorAll(".tag"); const tl = gsap.timeline({paused: true, delay: delay}); tl.from(tags, { x:-30, duration: 0.5, stagger: {each: 0.1}, ease: "circ.out", willChange: "transform" }) .from(tags, { opacity: 0, duration: 0.2, stagger: {each: 0.1}, ease: "circ.out" }, "0"); createScrollTrigger(container, tl, start); }); }); //анімація svg на сторінках курсів Webflow.push(function () { const svgs = document.querySelectorAll("[data-course-svg]"); if(svgs.length > 0) { svgs.forEach(svg => { const children = svg.querySelectorAll("*"); const objects = Array.from(children).filter(child => child.tagName.toUpperCase() === "LINE" || child.tagName.toUpperCase() === "PATH" || child.tagName.toUpperCase() === "RECT" || child.tagName.toUpperCase() === "CIRCLE" ); if(objects.length > 0) { objects.forEach(object => { object.style.strokeDashoffset = object.getTotalLength(); object.style.strokeDasharray = object.getTotalLength(); const tween = gsap.to(object, { scrollTrigger: { trigger: svg, start: "top 90%" }, strokeDashoffset: 0, duration: 2, ease: "expo.out" }); }); } }); } }); //анімація svg-кола на сторінках курсів Webflow.push(function () { const svg = document.querySelector("[data-circle-course-svg]"); if(svg) { const line = svg.querySelector("line"); const circle = svg.querySelector("circle"); const tl = gsap.timeline({paused: true, scrollTrigger: { trigger: svg, start: "top bottom", end: "bottom 40%", scrub: 2 } }); tl.to(line, {attr: {stroke: "white", y2: "600"}, ease: "power1.out"}) .to(circle, {attr: {r: "100"}, ease: "power1.out"}, "0"); } }); // анімація головного екрану Webflow.push(function () { const title = document.querySelector("[data-startscreen-title]"); if (title) { const lines = title.querySelectorAll(".line"); const tl = gsap.timeline(); if(lines) { lines.forEach((line, index) => { const chars = line.querySelectorAll(".char"); const delay = 0.6 + index * 0.2; tl.from(chars, {yPercent: 130, duration: 1.8, stagger: {amount: 0.35}, ease: "ease-out-circ", delay: delay}, "0") .from(chars, {duration: 1.7, scaleY: 1.3, ease: "power1.out"}, "<"); }); gsap.set(title, { opacity: 1 }); } } }); } if(window.innerWidth <= 991) { //анімація меню Webflow.push(function () { const menu = document.querySelector('[data-mobile-menu]'); if(menu) { const trigger = document.querySelector("[data-menu-trigger]"); const texts = menu.querySelectorAll('[text-split-modal]'); const menuTextWrapper = trigger.querySelector(".header-menu-btn_wrapper"); const menuTextOpen = trigger.querySelector(".menu--open-text"); const menuTextClose = trigger.querySelector(".menu--close-text"); const menuArrow = trigger.querySelector(".button_arrow"); const tl = gsap.timeline(); function menuOpen () { gsap.set(menu, {display: "flex"}); tl.clear(); tl.fromTo(menu, {yPercent: -100}, {yPercent: 0, duration: 1.6, ease: "expo.out"}); tl.fromTo(menu, {filter: "blur(20px)"}, {filter: "blur(0px)", duration: 0.8, ease: "sine.inOut"}, "<"); if(texts.length > 0) { new SplitType(texts, { types: "lines, words, chars", tagName: "span" }); texts.forEach((text, index) => { const textDelay = index * 0.1; const lines = text.querySelectorAll('.line'); lines.forEach((line, index) => { const delay = index * 0.15; const letters = line.querySelectorAll('.char'); tl.from(letters, {yPercent: 130, duration: 1.3, stagger: {amount: 0.35}, ease: "ease-out-circ", delay: delay}, `${0.6 + textDelay}`) .from(letters, {duration: 1.2, scaleY: 1.3, ease: "power1.out"}, "<"); }); }); } if(menuTextWrapper && menuTextOpen && menuTextClose && menuArrow) { tl.fromTo([menuTextOpen, menuTextClose], {yPercent: 0}, {yPercent: -100, duration: 0.6, ease: "expo.out"}, "0.2") .fromTo(menuTextWrapper, {width: menuTextOpen.offsetWidth}, {width: menuTextClose.offsetWidth, duration: 0.3, ease: "power1.inOut"}, "0") .fromTo(menuArrow, {rotation: 0}, {rotation: -270, duration: 0.6, ease: "expo.out"}, "0.1") .fromTo(trigger, {color: "#101010", backgroundColor: "white"}, {color: "white", backgroundColor: "#333", duration: 0.6, ease: "power1.out"}, "0"); } tl.play(); } trigger.addEventListener("click", function() { this.x = ((this.x || 0) + 1) % 2; if(this.x) { menuOpen(); } else { const tween = tl.tweenFromTo(1.6, 0, { ease: "circ.out", onComplete: () => { gsap.set(menu, {display: "none"}); } }); tween.timeScale(2); } }); } }); }