/
icedev
/
venture
Обзор
Документация
Войти
/
icedev
/
venture
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/app.js
421 строка
15 KB
icedev
оы_ашч
24 июн 2026, 12:24
24 июн 2026, 12:24
c3381ff
Код
Авторство
О чём код?
(function () { var header = document.getElementById('site-header'); var burger = document.getElementById('burger-btn'); var nav = document.getElementById('nav'); if (!header || !burger || !nav) return; function openMenu() { header.classList.add('is-menu-open'); burger.classList.add('is-open'); burger.setAttribute('aria-expanded', 'true'); burger.setAttribute('aria-label', 'Закрыть меню'); document.body.classList.add('is-menu-open'); nav.setAttribute('aria-hidden', 'false'); } function closeMenu() { header.classList.remove('is-menu-open'); burger.classList.remove('is-open'); burger.setAttribute('aria-expanded', 'false'); burger.setAttribute('aria-label', 'Открыть меню'); document.body.classList.remove('is-menu-open'); if (window.innerWidth <= 768) { nav.setAttribute('aria-hidden', 'true'); } else { nav.removeAttribute('aria-hidden'); } } burger.addEventListener('click', function () { if (header.classList.contains('is-menu-open')) closeMenu(); else openMenu(); }); var navLinks = nav.querySelectorAll('a'); for (var i = 0; i < navLinks.length; i++) { navLinks[i].addEventListener('click', closeMenu); } if (window.innerWidth <= 768) { nav.setAttribute('aria-hidden', 'true'); } window.addEventListener('resize', function () { if (window.innerWidth > 768) { closeMenu(); } }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && header.classList.contains('is-menu-open')) closeMenu(); }); window.addEventListener('scroll', function () { header.classList.toggle('is-scrolled', window.scrollY > 8); }, { passive: true }); })(); (function () { var reveals = document.querySelectorAll('.reveal'); if (!reveals.length) return; if (!('IntersectionObserver' in window)) { for (var i = 0; i < reveals.length; i++) { reveals[i].classList.add('is-visible'); } return; } var observer = new IntersectionObserver(function (entries) { for (var j = 0; j < entries.length; j++) { if (entries[j].isIntersecting) { entries[j].target.classList.add('is-visible'); observer.unobserve(entries[j].target); } } }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); for (var k = 0; k < reveals.length; k++) { observer.observe(reveals[k]); } })(); (function () { var navLinks = document.querySelectorAll('[data-nav-link]'); if (!navLinks.length) return; var sections = []; for (var i = 0; i < navLinks.length; i++) { var link = navLinks[i]; var id = link.getAttribute('href'); if (id && id.charAt(0) === '#') { var section = document.querySelector(id); if (section) sections.push({ link: link, section: section }); } } if (!sections.length) return; function updateActiveNav() { var scrollPos = window.scrollY + 120; var current = sections[0]; for (var j = 0; j < sections.length; j++) { if (sections[j].section.offsetTop <= scrollPos) current = sections[j]; } for (var k = 0; k < navLinks.length; k++) { navLinks[k].classList.remove('is-active'); } if (current) current.link.classList.add('is-active'); } window.addEventListener('scroll', updateActiveNav, { passive: true }); updateActiveNav(); })(); (function () { function closestElement(el, selector) { if (!el) return null; if (el.closest) return el.closest(selector); while (el) { if (el.nodeType === 1 && el.matches && el.matches(selector)) return el; el = el.parentElement; } return null; } var form = document.getElementById('consultation-form'); var messageEl = document.getElementById('form-message'); var submitBtn = document.getElementById('form-submit-btn'); if (!form || !messageEl || !submitBtn) return; var FORMSPREE_URL = (form.getAttribute('action') || '').trim(); var nextUrlInput = document.getElementById('form-next-url'); function showMessage(text, isError) { messageEl.textContent = text; messageEl.classList.remove('form-message--hidden', 'form-message--error', 'form-message--success'); messageEl.classList.add(isError ? 'form-message--error' : 'form-message--success'); } function hideMessage() { messageEl.classList.add('form-message--hidden'); } if (nextUrlInput) { nextUrlInput.value = window.location.href.split('?')[0] + '?sent=1'; } if (window.location.search.indexOf('sent=1') !== -1) { showMessage('Заявка отправлена! Мы свяжемся с вами в ближайшее время.', false); if (window.history && window.history.replaceState) { var cleanUrl = window.location.pathname + window.location.hash; if (window.location.protocol !== 'file:') { window.history.replaceState({}, '', cleanUrl); } } } var fields = { name: { el: document.getElementById('field-name'), errorEl: document.getElementById('error-name'), validate: function (value) { var trimmed = value.trim(); if (!trimmed) return 'Введите ваше имя'; if (trimmed.length < 2) return 'Имя должно содержать минимум 2 символа'; if (!/^[a-zA-Z\u0400-\u04FF\u0451\u0401\s\-'.]+$/.test(trimmed)) return 'Имя может содержать только буквы'; return ''; } }, phone: { el: document.getElementById('field-phone'), errorEl: document.getElementById('error-phone'), validate: function (value) { var digits = value.replace(/\D/g, ''); if (!digits) return 'Введите номер телефона'; if (digits.length < 10) return 'Номер должен содержать не менее 10 цифр'; if (digits.length > 15) return 'Слишком длинный номер телефона'; return ''; } }, email: { el: document.getElementById('field-email'), errorEl: document.getElementById('error-email'), validate: function (value) { var trimmed = value.trim(); if (!trimmed) return ''; if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(trimmed)) return 'Введите корректный email'; return ''; } }, consent: { el: document.getElementById('field-consent'), errorEl: document.getElementById('error-consent'), validate: function (value, el) { if (!el || !el.checked) return 'Необходимо дать согласие на обработку персональных данных'; return ''; } } }; function setFieldError(key, errorText) { var field = fields[key]; if (!field || !field.el) return; var isCheckbox = field.el.type === 'checkbox'; if (isCheckbox) { var checkboxWrap = closestElement(field.el, '.form-checkbox'); if (checkboxWrap) checkboxWrap.classList.toggle('form-checkbox--error', !!errorText); } else { field.el.classList.toggle('form-input--error', !!errorText); } field.el.setAttribute('aria-invalid', errorText ? 'true' : 'false'); if (field.errorEl) field.errorEl.textContent = errorText; } function validateField(key) { var field = fields[key]; if (!field || !field.el) return true; var error = field.validate(field.el.value, field.el); setFieldError(key, error); return !error; } function validateForm() { var valid = true; var keys = Object.keys(fields); for (var i = 0; i < keys.length; i++) { if (!validateField(keys[i])) valid = false; } return valid; } function formatPhoneInput(input) { var digits = input.value.replace(/\D/g, ''); if (!digits) { input.value = ''; return; } if (digits.charAt(0) === '8') digits = '7' + digits.slice(1); if (digits.charAt(0) !== '7') digits = '7' + digits; digits = digits.slice(0, 11); var formatted = '+7'; if (digits.length > 1) formatted += ' (' + digits.slice(1, 4); if (digits.length >= 4) formatted += ') ' + digits.slice(4, 7); if (digits.length >= 7) formatted += '-' + digits.slice(7, 9); if (digits.length >= 9) formatted += '-' + digits.slice(9, 11); input.value = formatted; } function collectPayload() { var email = fields.email.el ? fields.email.el.value.trim() : ''; var data = { name: fields.name.el.value.trim(), phone: fields.phone.el.value.trim(), _subject: 'Новая заявка с сайта ВентПроект', consent: 'Дано согласие на обработку ПДн', consent_date: new Date().toISOString() }; if (email) { data.email = email; data._replyto = email; } return data; } function parseFormspreeError(data) { if (data.errors && data.errors.length) { return data.errors.map(function (e) { return e.message; }).join(' '); } return data.error || 'Ошибка отправки'; } function submitNative() { var consentInput = fields.consent.el; if (consentInput) consentInput.disabled = true; form.submit(); } var fieldKeys = Object.keys(fields); for (var f = 0; f < fieldKeys.length; f++) { (function (key) { var field = fields[key]; if (!field.el) return; var eventName = field.el.type === 'checkbox' ? 'change' : 'blur'; field.el.addEventListener(eventName, function () { validateField(key); }); if (field.el.type !== 'checkbox') { field.el.addEventListener('input', function () { if (field.el.classList.contains('form-input--error')) validateField(key); hideMessage(); }); } else { field.el.addEventListener('change', function () { hideMessage(); }); } })(fieldKeys[f]); } if (fields.phone.el) { fields.phone.el.addEventListener('input', function () { formatPhoneInput(fields.phone.el); }); } form.addEventListener('submit', function (e) { e.preventDefault(); hideMessage(); if (!FORMSPREE_URL || FORMSPREE_URL.indexOf('formspree.io') === -1) { showMessage('Некорректный адрес Formspree в атрибуте action формы.', true); return; } try { if (!validateForm()) { showMessage('Проверьте правильность заполнения полей.', true); var firstInvalid = form.querySelector('.form-input--error, .form-checkbox--error input'); if (firstInvalid && firstInvalid.focus) firstInvalid.focus(); return; } } catch (err) { showMessage('Ошибка валидации формы. Попробуйте обновить страницу.', true); return; } submitBtn.disabled = true; submitBtn.classList.add('is-loading'); var payload = collectPayload(); if (!window.fetch) { submitNative(); return; } fetch(FORMSPREE_URL, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(function (res) { return res.text().then(function (text) { var data = {}; try { data = text ? JSON.parse(text) : {}; } catch (parseErr) { if (res.ok) return { ok: true }; throw new Error('Сервер вернул неожиданный ответ. Попробуйте ещё раз.'); } if (!res.ok || data.ok === false) { throw new Error(parseFormspreeError(data)); } return data; }); }) .then(function () { showMessage('Заявка отправлена! Мы свяжемся с вами в ближайшее время.', false); form.reset(); for (var i = 0; i < fieldKeys.length; i++) { setFieldError(fieldKeys[i], ''); } }) .catch(function (err) { if (err.name === 'TypeError' || (err.message && err.message.indexOf('Failed to fetch') !== -1)) { showMessage('Не удалось отправить через AJAX. Пробуем альтернативный способ…', false); setTimeout(submitNative, 600); return; } showMessage(err.message || 'Не удалось отправить заявку. Попробуйте позже.', true); }) .finally(function () { submitBtn.disabled = false; submitBtn.classList.remove('is-loading'); }); }); })(); (function () { var COOKIE_KEY = 'ventpro_cookie_consent'; var banner = document.getElementById('cookie-banner'); var acceptBtn = document.getElementById('cookie-accept'); if (!banner || !acceptBtn) return; function storageGet(key) { try { return localStorage.getItem(key); } catch (e) { return null; } } function storageSet(key, value) { try { localStorage.setItem(key, value); return true; } catch (e) { return false; } } function showBanner() { banner.classList.remove('cookie-banner--hidden'); } function hideBanner() { banner.classList.add('cookie-banner--hidden'); } if (!storageGet(COOKIE_KEY)) { showBanner(); } acceptBtn.addEventListener('click', function () { storageSet(COOKIE_KEY, JSON.stringify({ accepted: true, date: new Date().toISOString() })); hideBanner(); }); })();