165 lines
5.4 KiB
JavaScript
165 lines
5.4 KiB
JavaScript
/* ============================================================
|
|
main.js — Scroll animations, counters, FAQ, BA slider
|
|
============================================================ */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
/* --- Scroll animation (IntersectionObserver) ------------ */
|
|
function initScrollAnimations() {
|
|
const els = document.querySelectorAll('[data-animate]');
|
|
if (!els.length) return;
|
|
|
|
const obs = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('in-view');
|
|
obs.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
|
|
|
|
els.forEach(el => obs.observe(el));
|
|
}
|
|
|
|
/* --- Animated counters ---------------------------------- */
|
|
function animateCounter(el) {
|
|
const target = parseFloat(el.dataset.count);
|
|
const suffix = el.dataset.suffix || '';
|
|
const prefix = el.dataset.prefix || '';
|
|
const decimals = (target % 1 !== 0) ? 1 : 0;
|
|
const duration = 1600;
|
|
const start = performance.now();
|
|
|
|
function tick(now) {
|
|
const elapsed = now - start;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
const ease = 1 - Math.pow(1 - progress, 3);
|
|
const value = target * ease;
|
|
el.textContent = prefix + value.toFixed(decimals) + suffix;
|
|
if (progress < 1) requestAnimationFrame(tick);
|
|
}
|
|
|
|
requestAnimationFrame(tick);
|
|
}
|
|
|
|
function initCounters() {
|
|
const counters = document.querySelectorAll('[data-count]');
|
|
if (!counters.length) return;
|
|
|
|
const obs = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
animateCounter(entry.target);
|
|
obs.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.3 });
|
|
|
|
counters.forEach(el => obs.observe(el));
|
|
}
|
|
|
|
/* --- FAQ accordion -------------------------------------- */
|
|
function initFAQ() {
|
|
document.querySelectorAll('.faq-item').forEach(item => {
|
|
const q = item.querySelector('.faq-question');
|
|
if (!q) return;
|
|
|
|
q.addEventListener('click', () => {
|
|
const isOpen = item.classList.contains('open');
|
|
// close all
|
|
document.querySelectorAll('.faq-item.open').forEach(i => {
|
|
i.classList.remove('open');
|
|
});
|
|
if (!isOpen) item.classList.add('open');
|
|
});
|
|
});
|
|
}
|
|
|
|
/* --- Before / After slider ------------------------------ */
|
|
function initBASlider(slider) {
|
|
const handle = slider.querySelector('.ba-handle');
|
|
const beforeWrap = slider.querySelector('.ba-before-wrap');
|
|
if (!handle || !beforeWrap) return;
|
|
|
|
let dragging = false;
|
|
|
|
function setPosition(clientX) {
|
|
const rect = slider.getBoundingClientRect();
|
|
const rawPct = (clientX - rect.left) / rect.width;
|
|
const pct = Math.min(Math.max(rawPct, 0.02), 0.98);
|
|
beforeWrap.style.width = (pct * 100) + '%';
|
|
handle.style.left = (pct * 100) + '%';
|
|
}
|
|
|
|
handle.addEventListener('mousedown', () => { dragging = true; });
|
|
handle.addEventListener('touchstart', () => { dragging = true; }, { passive: true });
|
|
|
|
window.addEventListener('mousemove', e => { if (dragging) setPosition(e.clientX); });
|
|
window.addEventListener('touchmove', e => { if (dragging) setPosition(e.touches[0].clientX); }, { passive: true });
|
|
|
|
window.addEventListener('mouseup', () => { dragging = false; });
|
|
window.addEventListener('touchend', () => { dragging = false; });
|
|
|
|
slider.addEventListener('click', e => setPosition(e.clientX));
|
|
}
|
|
|
|
function initBASliders() {
|
|
document.querySelectorAll('.ba-slider').forEach(initBASlider);
|
|
}
|
|
|
|
/* --- Smooth scroll for anchor links --------------------- */
|
|
function initSmoothScroll() {
|
|
document.querySelectorAll('a[href^="#"]').forEach(a => {
|
|
a.addEventListener('click', e => {
|
|
const id = a.getAttribute('href').slice(1);
|
|
const target = document.getElementById(id);
|
|
if (!target) return;
|
|
e.preventDefault();
|
|
const headerH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-h')) || 72;
|
|
const top = target.getBoundingClientRect().top + window.scrollY - headerH - 16;
|
|
window.scrollTo({ top, behavior: 'smooth' });
|
|
});
|
|
});
|
|
}
|
|
|
|
/* --- Video hero fallback -------------------------------- */
|
|
function initHeroVideo() {
|
|
const video = document.querySelector('.hero-video-wrap video');
|
|
if (!video) return;
|
|
video.play().catch(() => {
|
|
// autoplay blocked — poster image is visible; nothing to do
|
|
});
|
|
}
|
|
|
|
/* --- Testimonial auto-scroll (on mobile) ---------------- */
|
|
function initTestimonialScroll() {
|
|
const track = document.querySelector('.testimonial-track');
|
|
if (!track) return;
|
|
|
|
let isPaused = false;
|
|
track.addEventListener('mouseenter', () => { isPaused = true; });
|
|
track.addEventListener('mouseleave', () => { isPaused = false; });
|
|
|
|
// keyboard scroll within track
|
|
track.setAttribute('tabindex', '0');
|
|
}
|
|
|
|
/* --- Boot ---------------------------------------------- */
|
|
function boot() {
|
|
initScrollAnimations();
|
|
initCounters();
|
|
initFAQ();
|
|
initBASliders();
|
|
initSmoothScroll();
|
|
initHeroVideo();
|
|
initTestimonialScroll();
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', boot);
|
|
} else {
|
|
boot();
|
|
}
|
|
})();
|