//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" не знайдений на сторінці.');
}
}
});
});
}
});
}