/* ═══════════════════════════════════════════════════════════════
УНИВЕРСАЛЬНЫЙ INTERSECTION OBSERVER
Вставить в Zero Block → JS или в блок «HTML-код» Tilda.
Принцип: вешается на .animate-on-scroll
При попадании в viewport → добавляет .is-visible
При prefers-reduced-motion → классы добавляются мгновенно
════════════════════════════════════════════════════════════ */
(function () {
'use strict';
var prefersReduced =
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* ── УНИВЕРСАЛЬНЫЙ SCROLL-OBSERVER ── */
var scrollEls = document.querySelectorAll('.animate-on-scroll');
if (prefersReduced) {
/* Мгновенно показываем всё без анимации */
scrollEls.forEach(function (el) {
el.style.opacity = '1';
el.style.transform = 'none';
el.style.transition = 'none';
});
} else if ('IntersectionObserver' in window) {
var observer = new IntersectionObserver(
function (entries, obs) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
obs.unobserve(entry.target);
}
});
},
{
threshold: 0.15, /* 15% элемента в viewport */
rootMargin: '0px 0px -40px 0px'
/* Небольшой отступ снизу — элемент
появляется чуть позже нижнего края */
}
);
scrollEls.forEach(function (el) { observer.observe(el); });
} else {
/* Fallback для старых браузеров */
scrollEls.forEach(function (el) {
el.classList.add('is-visible');
});
}
/* ── FAQ-АККОРДЕОН ── */
document.querySelectorAll('.faq-trigger').forEach(function (btn) {
btn.addEventListener('click', function () {
var item = btn.closest('.faq-item');
var body = item.querySelector('.faq-body');
var isOpen = item.classList.contains('is-open');
/* Закрываем все */
document.querySelectorAll('.faq-item.is-open').forEach(function (openItem) {
var openBody = openItem.querySelector('.faq-body');
openItem.classList.remove('is-open');
openBody.style.height = '0';
});
/* Открываем текущий (если был закрыт) */
if (!isOpen) {
item.classList.add('is-open');
body.style.height = body.scrollHeight + 'px';
/* После transition сбрасываем до 'auto' —
корректно работает при ресайзе окна */
body.addEventListener('transitionend', function onEnd() {
if (item.classList.contains('is-open')) {
body.style.height = 'auto';
}
body.removeEventListener('transitionend', onEnd);
});
}
});
});
/* ── COUNT-UP ДЛЯ STAT-ЦИФР ──
Применять к элементам: 0
Zero Block или HTML-блок Tilda. JS обязателен. */
function easeOut(t) {
return 1 - Math.pow(1 - t, 3); /* Cubic ease-out */
}
function animateCount(el) {
var target = parseInt(el.dataset.target, 10);
var duration = 1200; /* --duration-cinematic */
var start = null;
function step(timestamp) {
if (!start) start = timestamp;
var progress = Math.min((timestamp - start) / duration, 1);
el.textContent = Math.floor(easeOut(progress) * target);
if (progress < 1) {
requestAnimationFrame(step);
} else {
el.textContent = target; /* Точное финальное значение */
}
}
requestAnimationFrame(step);
}
if (!prefersReduced) {
var countEls = document.querySelectorAll('.count-up');
if ('IntersectionObserver' in window) {
var countObserver = new IntersectionObserver(
function (entries, obs) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
animateCount(entry.target);
obs.unobserve(entry.target);
}
});
},
{ threshold: 0.5 }
);
countEls.forEach(function (el) { countObserver.observe(el); });
}
} else {
/* При reduced-motion: сразу показать финальное значение */
document.querySelectorAll('.count-up').forEach(function (el) {
el.textContent = el.dataset.target;
});
}
/* ── STICKY CTA (мобильный) ── */
var stickyCta = document.querySelector('.sticky-cta');
var heroSection = document.querySelector('.section--hero');
var finalCta = document.querySelector('.section--final-cta');
if (stickyCta && heroSection) {
var stickyObserver = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
/* Показываем когда Hero ушёл из viewport */
if (entry.target === heroSection) {
if (!entry.isIntersecting) {
stickyCta.classList.add('is-visible');
} else {
stickyCta.classList.remove('is-visible');
}
}
/* Скрываем когда финальный CTA в viewport */
if (finalCta && entry.target === finalCta) {
if (entry.isIntersecting) {
stickyCta.classList.remove('is-visible');
}
}
});
},
{ threshold: 0 }
);
stickyObserver.observe(heroSection);
if (finalCta) stickyObserver.observe(finalCta);
}
}());