//console.log("stage"); // Initialize Lenis const lenis = new Lenis(); // Use requestAnimationFrame to continuously update the scroll function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); //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); } const isDesktop = window.innerWidth > 991 ? true : false; var Webflow = Webflow || []; //заборона скролу при відркитті модальних вікон + анімація хіро секції Webflow.push(function() { const scrollDisableElements = document.querySelectorAll('[scroll-disable-element]'); const observerReady = new Promise((resolve) => { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { lenis.stop(); } else { lenis.start(); } }); resolve(); // Сообщаем, что наблюдатель готов }); scrollDisableElements.forEach(element => { observer.observe(element); }); }); observerReady.then(() => { if (["/", "/en", "/ru"].some(path => window.location.pathname === path)) { //анімація hero Webflow.push(function() { const words = document.querySelectorAll("[data-hero-animation='title'] span"); const other = document.querySelectorAll("[data-hero-animation='hidden']"); const [navigation] = performance.getEntriesByType('navigation'); gsap.set(words, { autoAlpha: 1 }); if (navigation.type === 'reload') { gsap.set(other, { autoAlpha: 1 }); gsap.set(words, { color: "var(--black)" }); } else { lenis.stop(); const tl = gsap.timeline({ defaults: { ease: "none", delay: 0.3 } }); const delay = 0.7; // Задержка между кадрами tl.to(words[0], { keyframes: [ { color: "var(--black-60)", "-webkit-text-stroke": "0px transparent", duration: 0 }, { color: "var(--white)", "-webkit-text-stroke": "1px black", duration: 0, delay: delay }, { color: "var(--blue)", "-webkit-text-stroke": "0px transparent", duration: 0, delay: delay }, { color: "#fff", "-webkit-text-stroke": "1px black", duration: 0, delay: delay }, { color: "#101010", "-webkit-text-stroke": "0px transparent", duration: 2, ease: "expo.out", delay: delay, onStart: () => { lenis.start(); gsap.to(other, { autoAlpha: 1, duration: 3, ease: "expo.out" }); } } ], ease: "power3.out" }) .to(words[1], { keyframes: [ { color: "var(--blue)", "-webkit-text-stroke": "0px transparent", duration: 0 }, { color: "var(--black-60)", "-webkit-text-stroke": "0px transparent", duration: 0, delay: delay }, { color: "var(--white)", "-webkit-text-stroke": "1px black", duration: 0, delay: delay }, { color: "#fff", "-webkit-text-stroke": "1px black", duration: 0, delay: delay }, { color: "#101010", "-webkit-text-stroke": "0px transparent", duration: 2, ease: "expo.out", delay: delay } ], ease: "power3.out" }, "0") .to(words[2], { keyframes: [ { color: "var(--white)", "-webkit-text-stroke": "1px black", duration: 0 }, { color: "var(--blue)", "-webkit-text-stroke": "0px transparent", duration: 0, delay: delay }, { color: "var(--black-60)", "-webkit-text-stroke": "0px transparent", duration: 0, delay: delay }, { color: "#fff", "-webkit-text-stroke": "1px black", duration: 0, delay: delay }, { color: "#101010", "-webkit-text-stroke": "0px transparent", duration: 2, ease: "expo.out", delay: delay } ], ease: "power3.out" }, "0"); } }); } }); }); //page reload animation Webflow.push(function() { const pageCover = document.querySelector('.page-cover'); if (pageCover) { const tl = gsap.timeline(); tl.to(pageCover, { autoAlpha: 0, duration: 0.8, ease: "sine.out", delay: (window.innerWidth > 479) ? 0.1 : 0.2, onComplete: () => { pageCover.remove(); } }); } }); //button hover animation Webflow.push(function() { const buttons = document.querySelectorAll('[data-button-animation]'); function hoverAnimation(tl) { const arrow = this.querySelector('.button_arrow'); const texts = this.querySelectorAll('.button_text-wrapper p'); tl.clear(); tl.fromTo(arrow, { xPercent: 0, y: 0, scale: 1, rotateZ: 0 }, { xPercent: -105, y: () => calculateY(0, 105, arrow), ease: "power3.out", willChange: "transform", clearProps: "willChange", duration: 0.3 }) .fromTo(texts, { y: 0 }, { y: (index) => calculateY(0, 106, texts[index]), ease: "power3.out", duration: 0.3 }, 0) .fromTo(this.querySelector('.button_text-wrapper'), { x: 0 }, { x: "-0.75rem", ease: "power3.out", duration: 0.3 }, 0); tl.play(); this.addEventListener('mouseleave', () => tl.tweenTo(0, { duration: 0.2, ease: "power3.out" }), { once: true }); } if (isDesktop) { buttons.forEach(button => { const tl = gsap.timeline({ paused: true, defaults: {} }); button.addEventListener('mouseenter', hoverAnimation.bind(button, tl)); }); } }); //menu animation Webflow.push(function() { const header = document.querySelector('.header'); window.scrollY > 20 ? header.classList.add('header--box-shadow') : header.classList.remove('header--box-shadow'); window.addEventListener('scroll', () => { if (window.scrollY > 20) { header.classList.add('header--box-shadow'); } else { header.classList.remove('header--box-shadow'); } }); const menu = document.querySelector('[data-menu-animation="menu"]'); const openTrigger = document.querySelector('[data-menu-animation="open-trigger"]'); const cover = document.querySelector('[data-menu-animation="cover"]'); const squares = Array.from(cover.querySelectorAll(".squares_square")) .filter(square => window.getComputedStyle(square).display !== "none" && window.getComputedStyle(square.parentElement).display !== "none" ); let isAnimating = false; if (cover && squares) { gsap.set(squares, { scale: 1 }); gsap.set(menu, { xPercent: -100 }); const tlShow = gsap.timeline({ paused: true }); const tlHide = gsap.timeline({ paused: true }); tlShow.set(menu, { display: "block", xPercent: -100, opacity: 1 }).set(squares, { backgroundColor: "#F5F5F5" }) .to(menu, { xPercent: 0, duration: 1.3, willChange: "transform", clearProps: "willChange", ease: "circ.out" }) .to(squares, { scale: 0, duration: 1.2, ease: "power4.out", stagger: { amount: isDesktop ? 0.4 : 0.45, from: "random" } }, 0.2) .to(openTrigger.querySelectorAll('.header_menu-text'), { yPercent: "+=100", duration: 0.8, ease: "power2.out", willChange: "transform", clearProps: "willChange" }, 0) .eventCallback("onStart", () => { isAnimating = true; }) .eventCallback("onComplete", () => { isAnimating = false; }); tlHide.set(squares, { backgroundColor: "white" }) .to(squares, { scale: 1, duration: 1, ease: "power4.out", stagger: { amount: 0.4, from: "random" } }, 0) .to(menu, { opacity: 0, duration: 0.7, ease: "power4.inOut" }, 0.7) .to(openTrigger.querySelectorAll('.header_menu-text'), { yPercent: "-=100", duration: 0.8, ease: "circ.inOut", willChange: "transform", clearProps: "willChange" }, 0) .set(menu, { display: "none" }) .eventCallback("onStart", () => { isAnimating = true; }) .eventCallback("onComplete", () => { isAnimating = false; }); openTrigger.addEventListener("click", function() { if (isAnimating) return; this.x = ((this.x || 0) + 1) % 2; if (this.x) { tlShow.restart(); } else { tlHide.restart(); } }); } }); //reviews functionality Webflow.push(function() { const thumbs = document.querySelectorAll('[data-reviews-animation="thumbnail"]'); const reviews = document.querySelectorAll('[data-reviews-animation="review"]'); const cover = document.querySelector('[data-reviews-animation="cover"]'); let currentThumb; if (cover) { const squares = cover.querySelectorAll(".squares_square"); const tl = gsap.timeline({ paused: true }); function changeReview(index) { if (currentThumb !== this) { const currentReview = [...reviews].find(review => review === reviews[index]); tl.clear(); tl.call(() => { thumbs.forEach(thumb => thumb.classList.remove('is--active')); }) .fromTo(squares, { scale: 0 }, { scale: 1, stagger: { amount: 0.3, from: "random" } }) .call(() => { reviews.forEach(review => review.classList.add("display-none")); currentReview.classList.remove("display-none"); this.classList.add("is--active"); }) .to(squares, { scale: 0, stagger: { amount: 0.3, from: "center" } }) tl.play(); currentThumb = this; } } thumbs.forEach((thumb, index) => { thumb.addEventListener('click', changeReview.bind(thumb, index)); }); } }); //recaptcha functionality Webflow.push(function() { // Select all form blocks based on the specified attribute const formBlocks = document.querySelectorAll( '[data-recaptcha-element="form-block"]' ); const serverUrl = "https://lindgren-recaptcha-verification.netlify.app/.netlify/functions/verify"; const siteKey = "6LdA3sAqAAAAAAnodsRmuwnUFcbVfRSdQ6z5ci0X"; formBlocks.forEach((formBlock) => { const form = formBlock.querySelector("form"); const pageNameInput = form.querySelector('[data-input="page-name"]'); const formNameInput = form.querySelector('[data-input="form-name"]'); const solutionsInput = form.querySelector('[data-input="solutions"]') const solutions = [...(form.querySelector('#solutions-list')?.children || [])]; if (pageNameInput) { pageNameInput.value = window.location.pathname; } if (formNameInput) { formNameInput.value = form.getAttribute("data-name"); } // Hide the ReCAPTCHA badge if the attribute is set const style = document.createElement("style"); style.innerHTML = ".grecaptcha-badge { visibility: hidden; }"; document.head.appendChild(style); form.addEventListener("submit", function(event) { event.preventDefault(); if (solutionsInput && solutions.length > 0) { solutions.forEach(solution => { const checkbox = solution.querySelector('input'); if (checkbox.checked) solutionsInput.value += `${checkbox.dataset.name}, `; }); solutionsInput.value = solutionsInput.value.slice(0, -2); console.log(solutionsInput.value); } async function handleFormSubmission() { try { // Execute ReCAPTCHA and retrieve the token const token = await grecaptcha.execute(siteKey, { action: "submit" }); // console.log(token); // Serialize form data into a simple object let serializedData = {}; new FormData(form).forEach((value, key) => { serializedData[key] = value; }); const response = await fetch(serverUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token, formData: serializedData }), }); const data = await response.json(); // Display success or error messages based on the response if (data.formResponse && data.formResponse.status === "success") { // Log the response to the console console.log(data); } } catch (error) { console.error("Error:", error); } } handleFormSubmission(); // Execute the form submission process }); }); }); //form validation functionality Webflow.push(function() { function initializeForm(form) { // Hide errors in this form form.querySelectorAll('[wr-type="error"]').forEach(el => el.style.display = 'none'); form.querySelectorAll('.error').forEach(el => el.classList.remove('error')); let hasFormErrors = false; const fieldError = function(field) { // Show error message const errorEl = field.parentNode.querySelector('[wr-type="error"]'); if (errorEl) errorEl.style.display = 'block'; // Add error state to this field field.classList.add('error'); hasFormErrors = true; }; const phoneInput = form.querySelector('input[type="tel"]'); if (phoneInput) { phoneInput.addEventListener('input', (e) => { e.target.value = e.target.value.replace(/[^0-9+]/g, ''); }); } const formErrorState = form.parentNode.querySelector('.w-form-fail'); formErrorState.style.display = 'none'; const submitButton = form.querySelector('[data-submit-button=true]'); const originalSubmitText = submitButton.querySelector('p').textContent; const loadingText = "Waiting..."; const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.attributeName === 'style') { const isFormHidden = form.style.display === 'none'; const isErrorVisible = formErrorState.style.display !== 'none'; if (isFormHidden || isErrorVisible) { submitButton.style.pointerEvents = 'auto'; submitButton.querySelectorAll("p").forEach(par => { par.textContent = originalSubmitText; }); } } }); }); observer.observe(form, { attributes: true }); observer.observe(formErrorState, { attributes: true }); // Click on the Submit button for this form form.querySelectorAll('[data-submit-button="true"]').forEach(submitBtn => { submitBtn.addEventListener('click', function(e) { e.preventDefault(); hasFormErrors = false; // Check each required field in this form form.querySelectorAll('[wr-type="required-field"]').forEach(field => { if (field.getAttribute('type') === 'checkbox' && !field.checked) { // Checkbox const checkboxUI = field.parentNode.querySelector('.w-checkbox-input'); if (checkboxUI) fieldError(checkboxUI); } else if (field.querySelector('input[type="radio"]')) { // Radio buttons group const radioGroup = field.querySelectorAll('input[type="radio"]'); // Get all radio buttons in this group const isChecked = Array.from(radioGroup).some(radio => radio.checked); // Check if at least one is selected if (!isChecked) { // If no radio button is selected, mark the parent as an error fieldError(field); } } else if (field.value.length === 0) { // If this field is empty fieldError(field); } else if (field.getAttribute('type') === 'email' && !(/^[a-zA-Z][a-zA-Z0-9._-]*@[a-zA-Z][a-zA-Z0-9._-]*\.[a-zA-Z]{2,}$/.test(field.value))) { fieldError(field); } }); // Submit this form if there are no errors if (!hasFormErrors) { this.style.pointerEvents = 'none'; this.querySelectorAll("p").forEach(par => { par.textContent = loadingText; }); const submitEvent = new Event('submit', { bubbles: true, cancelable: true }); form.dispatchEvent(submitEvent); } }); }); // function which remove errors from field const clearErrors = function() { const errorElements = this.parentNode.querySelectorAll('.error'); errorElements.forEach(el => el.classList.remove('error')); const errorMsg = this.parentNode.querySelector('[wr-type="error"]'); if (errorMsg) errorMsg.style.display = 'none'; hasFormErrors = false; }; // add handlers to remove errors from fields form.querySelectorAll('[wr-type="required-field"], [type="checkbox"]').forEach(field => { if (field.querySelector('input[type="radio"]')) { field.querySelectorAll('input[type="radio"]').forEach(radio => { radio.addEventListener('change', clearErrors.bind(field)); }); } else { field.addEventListener('keypress', clearErrors); field.addEventListener('blur', clearErrors); } }); // handle form submission by pressing Enter form.querySelectorAll('input, textarea').forEach(field => { field.addEventListener('keypress', function(e) { if (e.keyCode === 13) { e.preventDefault(); const changeEvent = new Event('change'); this.dispatchEvent(changeEvent); const submitBtn = form.querySelector('[data-submit-button="true"]'); if (submitBtn) submitBtn.click(); } }); }); } document.querySelectorAll('form[data-functional-form]').forEach(form => { initializeForm(form); }); }); // Adding alt and title to images Webflow.push(function() { function addAttributesToImages() { const images = document.querySelectorAll('img'); const pageTitle = document.title; images.forEach((img, index) => { if (img.hasAttribute('decorative')) return; const photoNumber = index + 1; img.setAttribute('title', `${pageTitle}`); if (!img.hasAttribute('alt') || img.getAttribute('alt') === '') { img.setAttribute('alt', `${pageTitle} - N ${photoNumber}`); } }); } addAttributesToImages(); }); //marquee Webflow.push(function() { const marquees = document.querySelectorAll('[data-marquee'); if (marquees.length > 0) { marquees.forEach(marquee => { $(marquee).marquee({ direction: 'left', duration: 24000, gap: 0, delayBeforeStart: 0, duplicated: true, startVisible: true, pauseOnHover: false }); }); } }); if (["/", "/en", "/ru", "/expertise/webflow-development", "/en/expertise/webflow-development", "/ru/expertise/webflow-development", "/expertise/technical-seo-optimization-on-webflow", "/en/expertise/technical-seo-optimization-on-webflow", "/ru/expertise/technical-seo-optimization-on-webflow", "/expertise/landing-page-development", "/en/expertise/landing-page-development", "/ru/expertise/landing-page-development", "/expertise/website-design-development", "/en/expertise/website-design-development", "/ru/expertise/website-design-development", "/expertise/corporate-website", "/en/expertise/corporate-website", "/ru/expertise/corporate-website", "/services/branding", "/en/services/branding", "/ru/services/branding" ].some(path => window.location.pathname === path)) { //features swiper Webflow.push(function() { function toggleThumb(prevThumb, activeThumb) { let tl = gsap.timeline({ onComplete: () => { tl.kill(); tl = null; } }); tl.set(activeThumb, { opacity: 0 }) .to(prevThumb, { opacity: "0", duration: 0.2 }) .set(prevThumb, { display: "none" }) .set(activeThumb, { display: "block" }) .to(activeThumb, { opacity: "1", duration: 0.6 }, 0); } const swiper = new Swiper('.features-slider', { loop: false, //preventInteractionOnTransition: true, effect: "cards", grabCursor: true, initialSlide: isDesktop ? 4 : 2, cardsEffect: { rotate: false, slideShadows: false, perSlideRotate: 0, perSlideOffset: 10 }, navigation: { nextEl: '.features-slider_button-prev', prevEl: '.features-slider_button-next', }, on: { init: function() { //якщо к-сть слайдів буде різною постійно - то краще використовувати це замість параметру initialSlide //this.slideTo(this.slides.length - 1, 0, false) }, slideChange: function() { if (window.innerWidth > 991) { const activeIndex = this.activeIndex; const prevIndex = this.previousIndex; let activeThumb, prevThumb; const thumbs = document.querySelectorAll(".feature-thumb"); thumbs.forEach((thumb, index) => { if (index === thumbs.length - (activeIndex + 1)) { activeThumb = thumb; } else if (index === thumbs.length - (prevIndex + 1)) { prevThumb = thumb; } }); toggleThumb(prevThumb, activeThumb); } } } }); }); //awards animation Webflow.push(function() { if (isDesktop && ["/", "/en", "/ru"].some(path => window.location.pathname === path)) { const container = document.querySelector('[data-awards-animation="container"]'); const awardNames = container.querySelectorAll('.award_name'); const awardCovers = container.querySelectorAll('.award_cover'); let prevAwardCover = container.querySelector("[data-awards-animation='initial-cover']"); let prevAwardName = container.querySelector("[data-awards-animation='initial-name']"); //awardCovers.forEach((cover, index) => cover.style.left = `${index * 1}rem`); function hoverOnAward(index) { const awardCover = awardCovers[index]; prevAwardName.classList.remove('is--active'); this.classList.add('is--active'); prevAwardCover.classList.add('display-none'); awardCover.classList.remove('display-none'); prevAwardCover = awardCover; prevAwardName = this; } awardNames.forEach((awardName, index) => { awardName.addEventListener("mouseenter", hoverOnAward.bind(awardName, index)); }) } }); //"we can" animation Webflow.push(function() { const thumbsProps = { glass: { url: "https://cdn.prod.website-files.com/672cb8cc84787156bdcf17a5/675aec0a3fef3a2fdb04b710_glass.svg", class: "unique-style-5", duration: 1, stagger: 0.2, message: { ua: "Дозволити собі
келих вина ввечері", en: "Allow yourself a glass of
wine in the evening", ru: "Разрешить себе
бокал вина вечером" }, descriptor: { ua: "Бо у нас все під контролем", en: "Development under control", ru: "Потому что у нас все под контролем" } }, popcorn: { url: "https://cdn.prod.website-files.com/672cb8cc84787156bdcf17a5/675aeb51a1c01371b8811e8f_popcorn.svg", class: "unique-style-2", duration: 6, stagger: 0.4, message: { ua: "Подивитись улюблений
серіал", en: "Watch your favorite
TV show", ru: "Посмотреть любимый
сериал" }, descriptor: { ua: "Тому що все йде за планом", en: "Everything is according to plan", ru: "Потому что все идет по плану" } }, cat: { url: "https://cdn.prod.website-files.com/672cb8cc84787156bdcf17a5/675aeb51a5a77e9877c58b6f_cat.svg", class: "unique-style-3", duration: 1, stagger: 0.1, message: { ua: "Пограти зі своїм
улюбленцем", en: "Play with your
favorite pet", ru: "Поиграть со своим
любимцем" }, descriptor: { ua: "Тепер ти маєш на це час", en: "Now you have time for it", ru: "Теперь у тебя есть на это время" } }, foot: { url: "https://cdn.prod.website-files.com/672cb8cc84787156bdcf17a5/675aeb51679e9450750934b2_foot.svg", class: "unique-style-4", duration: 10, stagger: 0.6, message: { ua: "Зайнятись
спортом", en: "Engage in
sports", ru: "Заняться
спортом", }, descriptor: { ua: "Поки ми робимо твій проект", en: "While we are making your project", ru: "Пока мы делаем твой проект" } }, family: { url: "https://cdn.prod.website-files.com/672cb8cc84787156bdcf17a5/675aeb51807fade3fa95854a_family.svg", class: "unique-style-5", duration: 1, stagger: 0.1, message: { ua: "Провести час з родиною
на вихідних", en: "Spend time with family
on weekends", ru: "Провести время с семьей
на выходных" }, descriptor: { ua: "А не наодинці з багами", en: "And not alone with the troubles", ru: "А не наедине с багами" } } } const defineContentByLanguage = (prop) => { const path = window.location.pathname; if (path.includes('/en')) { return prop.en; } else if (path.includes('/ru')) { return prop.ru; } else { return prop.ua; } } const thumbs = document.querySelectorAll('[data-can-animation="thumb"]'); const scene = document.querySelector('[data-can-animation="scene"]'); const objs = scene.querySelectorAll('[data-can-animation]'); const message = document.querySelector('[data-can-animation="message"]'); const descriptor = document.querySelector('[data-can-animation="descriptor"]'); const rect = document.querySelector('[data-can-animation="rect"]'); let currentTimeline = null; let isEven = true; if (Object.keys(thumbsProps).length === thumbs.length + 1) { thumbs.forEach((thumb) => { thumb.addEventListener('click', function() { const img = this.querySelector('img'); const prevObj = [...objs].find(obj => obj.offsetParent !== null); const thumbUrl = thumbsProps[prevObj.dataset.canAnimation].url; const className = thumbsProps[prevObj.dataset.canAnimation].class; const currentObj = [...objs].find(obj => obj.dataset.canAnimation === img.id); const currentMsg = thumbsProps[currentObj.dataset.canAnimation].message; const currentDesc = thumbsProps[currentObj.dataset.canAnimation].descriptor; const curves = Array.from(currentObj.querySelectorAll('*')).filter(child => ['path', 'circle', 'line', 'rect', 'ellipse'].includes(child.tagName.toLowerCase()) ); const duration = thumbsProps[currentObj.dataset.canAnimation].duration; const stagger = thumbsProps[currentObj.dataset.canAnimation].stagger; if (currentTimeline) { currentTimeline.kill(); currentTimeline = null; } currentTimeline = gsap.timeline({ onComplete: function() { currentTimeline.kill(); currentTimeline = null; } }) .call(() => { currentObj.style.display = "block"; curves.forEach(curve => { const length = curve.getTotalLength(); curve.style.strokeDasharray = length + "px"; curve.style.strokeDashoffset = length + "px"; }); }) .set(prevObj, { display: "none" }) .call(() => { img.className = ""; img.id = prevObj.dataset.canAnimation; message.innerHTML = defineContentByLanguage(currentMsg); descriptor.innerHTML = defineContentByLanguage(currentDesc); }) .set(img, { attr: { src: thumbUrl, srcset: thumbUrl, class: className } }) .to(curves, { strokeDashoffset: "0px", duration: duration, stagger: { each: stagger }, ease: "power3.out" }) .to(rect, { keyframes: { scale: [1, 0.8, 1], }, rotate: () => { const rotation = isEven ? "+=90deg" : "-=90deg"; isEven = !isEven; return rotation; }, duration: 1, ease: "power3.out" }, "<") }); }); } }); } const path = window.location.pathname; const regex = /\/about$/; if (regex.test(path)) { gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(MotionPathPlugin); Webflow.push(function() { const bullit = document.querySelector('.about-progress_bullit-dynamic'); const path = document.querySelector('#progress-about'); let isPoint1Hide = true, isPoint2Hide = true, isPoint3Hide = true, isPoint4Hide = true; const points = document.querySelectorAll('.about-progress_point'); const elementsOfLastPoint = points[3].querySelectorAll('.about-progress_bullit-wrap'); const originalColors = Array.from(elementsOfLastPoint).map(el => getComputedStyle(el).borderColor); const tl = gsap.timeline(); tl.to(bullit, { motionPath: { path: path, align: path, alignOrigin: [0.5, 0.5], start: 1, end: 0 }, scrollTrigger: { trigger: path, start: "top 50%", end: "bottom 60%", scrub: 1, markers: false, toggleActions: "play none none none", onUpdate: (self) => { const progress = self.progress; if (progress > 0.23 && isPoint1Hide) { isPoint1Hide = false; gsap.set(points[0], { display: "flex" }); gsap.timeline() .fromTo(points[0], { opacity: 0 }, { opacity: 1, duration: 0.5 }) .fromTo(points[0].querySelector('.about-progress_bullit-static'), { scale: 0.9, borderColor: "#ffffff" }, { scale: 1.1, borderColor: "transparent", duration: 1, repeat: -1, yoyo: true, ease: "none" }) } else if (progress > 0.48 && isPoint2Hide) { isPoint2Hide = false; gsap.set(points[1], { display: "flex" }); gsap.timeline() .fromTo(points[1], { opacity: 0 }, { opacity: 1, duration: 0.5 }) .fromTo(points[1].querySelector('.about-progress_bullit-static'), { scale: 0.9, borderColor: "#ffffff" }, { scale: 1.1, borderColor: "transparent", duration: 1, repeat: -1, yoyo: true, ease: "none" }) } else if (progress > 0.79 && isPoint3Hide) { isPoint3Hide = false; gsap.set(points[2], { display: "flex" }); gsap.timeline() .fromTo(points[2], { opacity: 0 }, { opacity: 1, duration: 0.5 }) .fromTo(points[2].querySelector('.about-progress_bullit-static'), { scale: 0.9, borderColor: "#ffffff" }, { scale: 1.1, borderColor: "transparent", duration: 1, repeat: -1, yoyo: true, ease: "none" }) } else if (progress === 1 && isPoint4Hide) { gsap.to(bullit, { opacity: 0, duration: 0.5, delay: 0.5 }) isPoint4Hide = false; gsap.set(points[3], { display: "flex" }); gsap.timeline() .fromTo(points[3], { opacity: 0 }, { opacity: 1, duration: 0.5 }) .fromTo(points[3].querySelectorAll('.about-progress_bullit-wrap, .about-progress_bullit-static'), { scale: 0.9, }, { scale: 1.1, duration: 1, repeat: -1, yoyo: true, ease: "none" }) .fromTo(points[3].querySelectorAll('.about-progress_bullit-wrap'), { borderColor: "#ffffff", }, { borderColor: (i) => originalColors[i], duration: 1, repeat: -1, yoyo: true, ease: "none" }, "<") } }, }, ease: "none", }); }); } if (["/services/website-development", "/services/web-applications-development", "/services/branding"] .some(path => window.location.pathname.includes(path))) { // steps functionality Webflow.push(function() { const steps = document.querySelectorAll('[data-step-process = container]'); steps.forEach(step => { const tooltip = step.querySelector('[data-step-process = tooltip]'); const richtext = step.querySelector('[data-step-process = richtext]'); const name = step.querySelector('[data-step-process = name]'); const button = step.querySelector('[data-step-process = button]'); if (button) { button.addEventListener('mouseenter', function() { gsap.set(tooltip, { display: 'block' }); gsap.set(step, { color: window.getComputedStyle(name).borderTopColor, zIndex: "1" }); gsap.set(richtext, { opacity: 1 }); }); button.addEventListener('mouseleave', function() { gsap.set(tooltip, { display: 'none' }); gsap.set(step, { color: "#101010", zIndex: "0" }); gsap.set(richtext, { opacity: 0 }); }); } }); }); } if (window.location.pathname.includes("/reviews")) { //marquee page reviews Webflow.push(function() { $('#marquee').marquee({ direction: 'left', duration: 24000, gap: 0, delayBeforeStart: 0, duplicated: true, startVisible: true, pauseOnHover: false }); }); } if (window.location.pathname.includes("/articles/")) { //articles modifications functionality Webflow.push(function() { const processPlaceholders = (articleBlock) => { const childElements = articleBlock.children; Array.from(childElements).forEach((child) => { const matches = child.textContent.matchAll(/{\{(.*?)}}/g); for (const match of matches) { const placeholderValue = match[1].trim(); // Перевіряємо значення на валідність для margin-bottom const isValidMargin = /^-?\d+(\.\d+)?(px|em|rem|%)$/.test(placeholderValue); if (isValidMargin) { child.style.marginBottom = placeholderValue; } else if (placeholderValue === 'large') { if (child.tagName === 'P') { child.classList.add('paragraph-size-large'); } } else if (placeholderValue === 'list-gap') { if (child.tagName === 'UL' || child.tagName === 'OL') { child.classList.add('list-gap'); } } else if (placeholderValue === 'huge') { if (child.tagName === 'P') { child.classList.add('paragraph-size-huge'); } } else if (placeholderValue === 'bg-grey') { if (child.tagName === 'BLOCKQUOTE') { child.classList.add('blockquote-grey'); } } else { console.warn(`Некоректне значення "${placeholderValue}" в елементі:`, child); } // Видаляємо плейсхолдери з тексту (всі, окрім позначки для case if (placeholderValue !== 'case') child.innerHTML = child.innerHTML.replace(match[0], ''); } }); }; const articleBlocks = document.querySelectorAll('.article_rich-text'); if (articleBlocks.length > 0) { // Запускаємо функцію-обробник для всіх плейсхолдерів articleBlocks.forEach((articleBlock) => { processPlaceholders(articleBlock); }); // Після цього переходимо до обробки плейсхолдера {\{case}} articleBlocks.forEach((articleBlock) => { const childElements = articleBlock.children; Array.from(childElements).forEach((child) => { const caseMatch = child.textContent.match(/{\{case}}/); if (caseMatch) { const caseBlock = document.querySelector('[data-article="case"]'); if (caseBlock) { const clonedCaseBlock = caseBlock.cloneNode(true); caseBlock.remove(); child.replaceWith(clonedCaseBlock); } else { console.warn('Блок з атрибутом data-article="case" не знайдений на сторінці.'); } } }); }); } }); }