View file app.js

File size: 4.51Kb
/**
 * ByMAS Landing — app.js
 * Scroll reveal, burger menu, number counters, online counter
 */

/* ── Scroll reveal ─────────────────────────────── */
const revealObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible');
      revealObserver.unobserve(entry.target);
    }
  });
}, { threshold: 0.12 });

document.querySelectorAll('.reveal, .tl-item').forEach((el) => {
  revealObserver.observe(el);
});

/* ── Header shadow on scroll ───────────────────── */
const header = document.getElementById('header');
window.addEventListener('scroll', () => {
  header.style.boxShadow = window.scrollY > 10
    ? '0 2px 20px rgba(0,0,0,.12)'
    : '0 2px 2px #c4c4c4';
}, { passive: true });

/* ── Burger menu ────────────────────────────────── */
const burger     = document.getElementById('burger');
const mobileNav  = document.getElementById('mobileNav');
const mobileClose = document.getElementById('mobileClose');

function openMobileNav() {
  mobileNav.classList.add('open');
  document.body.style.overflow = 'hidden';
}

function closeMobileNav() {
  mobileNav.classList.remove('open');
  document.body.style.overflow = '';
}

burger.addEventListener('click', openMobileNav);
mobileClose.addEventListener('click', closeMobileNav);

document.querySelectorAll('.mob-link, .mobile-nav a').forEach((link) => {
  link.addEventListener('click', closeMobileNav);
});

/* ── Smooth anchor scroll ───────────────────────── */
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
  anchor.addEventListener('click', (e) => {
    const target = document.querySelector(anchor.getAttribute('href'));
    if (target) {
      e.preventDefault();
      target.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  });
});

/* ── Number counter animation ───────────────────── */
function animateCounter(el, target, duration = 1600) {
  const raw    = target.replace(/\s/g, '');
  const num    = parseInt(raw, 10);
  const start  = performance.now();

  function update(now) {
    const elapsed  = now - start;
    const progress = Math.min(elapsed / duration, 1);
    // easeOutQuart
    const ease     = 1 - Math.pow(1 - progress, 4);
    const current  = Math.floor(ease * num);
    el.textContent = current.toLocaleString('ru-RU');
    if (progress < 1) requestAnimationFrame(update);
    else el.textContent = num.toLocaleString('ru-RU');
  }

  requestAnimationFrame(update);
}

const counterMap = {
  cnt1: '14900',
  cnt2: '618187',
  cnt3: '183957',
  cnt4: '166610',
  cnt5: '95857',
  cnt6: '86815',
  cnt7: '84569',
};

const counterObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const id     = entry.target.id;
      const target = counterMap[id];
      if (target) animateCounter(entry.target, target);
      counterObserver.unobserve(entry.target);
    }
  });
}, { threshold: 0.4 });

Object.keys(counterMap).forEach((id) => {
  const el = document.getElementById(id);
  if (el) counterObserver.observe(el);
});

/* ── Fake "online" counter ──────────────────────── */
const onlineEl = document.getElementById('onlineCount');
if (onlineEl) {
  let base = 247;
  setInterval(() => {
    const delta = Math.floor(Math.random() * 7) - 3;
    base = Math.max(210, Math.min(310, base + delta));
    onlineEl.textContent = base;
  }, 3500);
}

/* ── Stagger hero stat numbers ─────────────────── */
document.querySelectorAll('.stat-num').forEach((el, i) => {
  el.style.opacity = '0';
  el.style.transform = 'translateY(12px)';
  el.style.transition = 'opacity .5s, transform .5s';
  setTimeout(() => {
    el.style.opacity = '1';
    el.style.transform = 'translateY(0)';
  }, 600 + i * 120);
});

/* ── Stagger feature cards ──────────────────────── */
document.querySelectorAll('.feature-card').forEach((card, i) => {
  card.style.transitionDelay = `${i * 60}ms`;
});

/* ── Stagger cat cards ──────────────────────────── */
document.querySelectorAll('.cat-card').forEach((card, i) => {
  card.style.transitionDelay = `${i * 50}ms`;
});