--- **main.js** ```javascript /* ========================================================================== Неделя Моды Поволжья 2026 — main.js ========================================================================== */ (function () { 'use strict'; /* ---------- DOM refs ---------- */ var header = document.querySelector('.site-header'); var burgerBtn = document.querySelector('.burger'); var mobileMenu = document.getElementById('mobileMenu'); var mobileLinks = mobileMenu ? mobileMenu.querySelectorAll('a') : []; var particlesEl = document.getElementById('particles'); /* ---------- Mobile menu ---------- */ function toggleMenu() { if (!burgerBtn || !mobileMenu) return; var isOpen = mobileMenu.classList.contains('open'); burgerBtn.classList.toggle('active'); mobileMenu.classList.toggle('open'); document.body.style.overflow = isOpen ? '' : 'hidden'; } if (burgerBtn) { burgerBtn.addEventListener('click', toggleMenu); } for (var i = 0; i < mobileLinks.length; i++) { mobileLinks[i].addEventListener('click', function () { if (mobileMenu.classList.contains('open')) { toggleMenu(); } }); } /* ---------- Header scroll state ---------- */ var lastScroll = 0; var headerThreshold = 40; function onScroll() { var y = window.pageYOffset || document.documentElement.scrollTop; if (y > headerThreshold) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } lastScroll = y; } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); /* ---------- Smooth scroll for anchor links ---------- */ var allLinks = document.querySelectorAll('a[href^="#"]'); for (var j = 0; j < allLinks.length; j++) { allLinks[j].addEventListener('click', function (e) { var id = this.getAttribute('href'); if (!id || id === '#') return; var target = document.querySelector(id); if (target) { e.preventDefault(); var offset = header.offsetHeight + 16; var top = target.getBoundingClientRect().top + window.pageYOffset - offset; window.scrollTo({ top: top, behavior: 'smooth' }); } }); } /* ---------- Scroll reveal (IntersectionObserver) ---------- */ var revealElements = document.querySelectorAll( '.section-label, .section-title, .body-text, .btn-primary, .btn-ghost, ' + '.about-image-frame, .about-stats, .card, .venue-card, .contact-card, ' + '.model-cta-text, .model-cta-deco, .footer-inner, .hero-content > *' ); /* Assign reveal classes */ for (var k = 0; k < revealElements.length; k++) { revealElements[k].classList.add('reveal'); } /* Stagger cards */ var cardGroups = document.querySelectorAll('.cards-grid, .contacts-grid, .about-stats'); for (var g = 0; g < cardGroups.length; g++) { var children = cardGroups[g].children; for (var c = 0; c < children.length; c++) { children[c].classList.add('reveal', 'reveal-delay-' + (c + 1)); } } /* Hero content stagger */ var heroItems = document.querySelectorAll('.hero-content > *'); for (var h = 0; h < heroItems.length; h++) { heroItems[h].classList.add('reveal', 'reveal-delay-' + (h + 1)); } var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if ('IntersectionObserver' in window && !prefersReducedMotion) { var revealObserver = new IntersectionObserver(function (entries) { for (var e = 0; e < entries.length; e++) { if (entries[e].isIntersecting) { entries[e].target.classList.add('visible'); revealObserver.unobserve(entries[e].target); } } }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); for (var r = 0; r < revealElements.length; r++) { revealObserver.observe(revealElements[r]); } } else { /* Fallback: show everything immediately */ for (var f = 0; f < revealElements.length; f++) { revealElements[f].classList.add('visible'); } } /* ---------- Hero particles ---------- */ if (particlesEl && !prefersReducedMotion) { var canvas = document.createElement('canvas'); particlesEl.appendChild(canvas); var ctx = canvas.getContext('2d'); var particles = []; var particleCount = 50; var raf; function resizeCanvas() { canvas.width = particlesEl.offsetWidth; canvas.height = particlesEl.offsetHeight; } function Particle() { this.reset(true); } Particle.prototype.reset = function (initial) { this.x = Math.random() * canvas.width; this.y = initial ? Math.random() * canvas.height : canvas.height + 10; this.size = Math.random() * 2 + 0.5; this.speedY = -(Math.random() * 0.4 + 0.15); this.speedX = (Math.random() - 0.5) * 0.3; this.opacity = Math.random() * 0.5 + 0.1; this.fadeSpeed = Math.random() * 0.003 + 0.001; this.phase = Math.random() * Math.PI * 2; }; Particle.prototype.update = function () { this.y += this.speedY; this.x += this.speedX + Math.sin(this.phase) * 0.1; this.phase += 0.01; this.opacity -= this.fadeSpeed; if (this.opacity <= 0 || this.y < -10) { this.reset(false); } }; Particle.prototype.draw = function () { ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fillStyle = 'rgba(200,169,110,' + this.opacity + ')'; ctx.fill(); }; function initParticles() { resizeCanvas(); particles = []; for (var p = 0; p < particleCount; p++) { particles.push(new Particle()); } } function animateParticles() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (var p = 0; p < particles.length; p++) { particles[p].update(); particles[p].draw(); } raf = requestAnimationFrame(animateParticles); } initParticles(); animateParticles(); var resizeTimer; window.addEventListener('resize', function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { resizeCanvas(); }, 200); }); /* Pause when hidden */ document.addEventListener('visibilitychange', function () { if (document.hidden) { cancelAnimationFrame(raf); } else { animateParticles(); } }); } /* ---------- Stat counter animation ---------- */ var statNumbers = document.querySelectorAll('.stat-number'); if ('IntersectionObserver' in window && statNumbers.length && !prefersReducedMotion) { var statObserver = new IntersectionObserver(function (entries) { for (var e = 0; e < entries.length; e++) { if (entries[e].isIntersecting) { animateStat(entries[e].target); statObserver.unobserve(entries[e].target); } } }, { threshold: 0.5 }); for (var s = 0; s < statNumbers.length; s++) { statObserver.observe(statNumbers[s]); } } function animateStat(el) { var text = el.textContent.trim(); var target = parseInt(text, 10); if (isNaN(target)) return; /* skip non-numeric like ∞ */ var duration = 1200; var start = performance.now(); function step(now) { var progress = Math.min((now - start) / duration, 1); var eased = 1 - Math.pow(1 - progress, 3); el.textContent = Math.round(eased * target); if (progress < 1) { requestAnimationFrame(step); } } el.textContent = '0'; requestAnimationFrame(step); } })();
Made on
Tilda