/* Shared consultation/order dialog; deployed separately from the legacy energy.js. */ (function () { 'use strict'; var modal = document.getElementById('consultModal'); var form = document.getElementById('consultForm'); var menuToggle = document.querySelector('.menu-toggle'); var mobileMenu = document.getElementById('mobileMenu'); var backToTop = document.querySelector('.back-to-top'); var desktop = window.matchMedia('(min-width: 901px)'); var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); var focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'; var modalTrigger = null; var inertElements = []; var bodyWasLocked = false; var orderSnapshot = null; var status = document.getElementById('consultStatus'); function prepareRequest(trigger) { // An in-flight request keeps its original contents even if the dialog is reopened. if (!form || form.getAttribute('data-sending') === '1') return; var configurator = window.XiotEnergyConfigurator; orderSnapshot = trigger.hasAttribute('data-config-request') && configurator ? configurator.getSelection() : null; var selection = document.getElementById('consultSelection'); var topic = document.getElementById('consultTopic'); var option = document.createElement('option'); option.textContent = orderSnapshot ? 'Заказ решения XIOT-ENERGY' : 'Консультация по XIOT-ENERGY'; topic.replaceChildren(option); selection.hidden = !orderSnapshot; selection.textContent = orderSnapshot ? configurator.selectionText(orderSnapshot) : ''; document.getElementById('consultTitle').textContent = orderSnapshot ? 'Заказать решение' : 'Обсудить цифровой электрощит'; modal.querySelector('.modal-lead').textContent = orderSnapshot ? 'Оставьте контакты. Получим ваш комплект, подтвердим стоимость и согласуем поставку.' : 'Расскажите о задаче — свяжемся с вами и поможем определить состав решения.'; form.hidden = false; form.querySelector('.consult-submit').disabled = false; form.querySelector('.consult-submit span').textContent = orderSnapshot ? 'Отправить заказ в XIOT' : 'Отправить заявку'; status.textContent = ''; status.className = 'consult-status'; } function isVisible(element) { return element && element.isConnected && element.getClientRects().length && getComputedStyle(element).visibility !== 'hidden'; } function focusElement(element) { if (isVisible(element)) element.focus({ preventScroll: true }); } function navigationFocus() { return isVisible(menuToggle) ? menuToggle : document.querySelector('.header-inner nav a'); } function setMenu(open, restoreFocus) { if (!menuToggle || !mobileMenu) return; var wasOpen = menuToggle.getAttribute('aria-expanded') === 'true'; open = open && !desktop.matches; menuToggle.classList.toggle('is-open', open); mobileMenu.classList.toggle('is-open', open); menuToggle.setAttribute('aria-expanded', String(open)); menuToggle.setAttribute('aria-label', open ? 'Закрыть меню' : 'Открыть меню'); mobileMenu.setAttribute('aria-hidden', String(!open)); mobileMenu.inert = !open; if (!open && wasOpen && restoreFocus) focusElement(navigationFocus()); } function modalIsOpen() { return modal && modal.classList.contains('is-open'); } function modalFocusables() { return Array.prototype.filter.call(modal.querySelectorAll(focusableSelector), isVisible); } function openModal(event) { if (!modal || modalIsOpen()) return; modalTrigger = event.currentTarget; prepareRequest(modalTrigger); if (mobileMenu && mobileMenu.contains(modalTrigger)) modalTrigger = menuToggle; setMenu(false); bodyWasLocked = document.body.classList.contains('no-scroll'); modal.classList.add('is-open'); modal.setAttribute('aria-hidden', 'false'); document.body.classList.add('no-scroll'); inertElements = Array.prototype.filter.call(document.body.children, function (element) { return element !== modal && !element.inert && !/^(SCRIPT|STYLE|LINK)$/.test(element.tagName); }); inertElements.forEach(function (element) { element.inert = true; }); focusElement(document.getElementById('consultName') || modalFocusables()[0]); } function closeModal() { if (!modalIsOpen()) return; if (form && form.getAttribute('data-sending') === '1') { status.textContent = 'Отправляем заявку. Пожалуйста, дождитесь подтверждения — это займёт не более 30 секунд.'; return; } modal.classList.remove('is-open'); modal.setAttribute('aria-hidden', 'true'); if (!bodyWasLocked) document.body.classList.remove('no-scroll'); inertElements.forEach(function (element) { element.inert = false; }); inertElements = []; focusElement(isVisible(modalTrigger) ? modalTrigger : navigationFocus()); } document.querySelectorAll('[data-open-consult]').forEach(function (button) { button.addEventListener('click', openModal); }); document.querySelectorAll('[data-close-consult]').forEach(function (button) { button.addEventListener('click', closeModal); }); if (menuToggle && mobileMenu) { setMenu(false); menuToggle.addEventListener('click', function () { setMenu(menuToggle.getAttribute('aria-expanded') !== 'true'); }); mobileMenu.querySelectorAll('a, button').forEach(function (item) { item.addEventListener('click', function () { setMenu(false, true); }); }); document.addEventListener('click', function (event) { if (!mobileMenu.contains(event.target) && !menuToggle.contains(event.target)) { setMenu(false, mobileMenu.contains(document.activeElement)); } }); function onBreakpointChange() { if (desktop.matches) setMenu(false, true); } if (desktop.addEventListener) desktop.addEventListener('change', onBreakpointChange); else desktop.addListener(onBreakpointChange); } document.addEventListener('keydown', function (event) { if (event.key === 'Escape') { if (modalIsOpen()) closeModal(); else setMenu(false, true); } if (event.key !== 'Tab' || !modalIsOpen()) return; var items = modalFocusables(); var first = items[0]; var last = items[items.length - 1]; if (!items.length) { event.preventDefault(); return; } if (event.shiftKey && (document.activeElement === first || !modal.contains(document.activeElement))) { event.preventDefault(); focusElement(last); } else if (!event.shiftKey && (document.activeElement === last || !modal.contains(document.activeElement))) { event.preventDefault(); focusElement(first); } }); document.addEventListener('focusin', function (event) { if (modalIsOpen() && !modal.contains(event.target)) focusElement(modalFocusables()[0]); }); if (backToTop) { function updateBackToTop() { backToTop.classList.toggle('is-visible', window.scrollY > 560); } window.addEventListener('scroll', updateBackToTop, { passive: true }); backToTop.addEventListener('click', function (event) { event.preventDefault(); window.scrollTo({ top: 0, behavior: reducedMotion.matches ? 'auto' : 'smooth' }); focusElement(document.querySelector('.site-header .brand')); }); updateBackToTop(); } if (form) form.addEventListener('submit', function (event) { event.preventDefault(); if (!form.checkValidity()) { form.reportValidity(); return; } if (form.getAttribute('data-sending') === '1') return; var submitButton = form.querySelector('.consult-submit'); var isOrder = !!orderSnapshot; var topic = document.getElementById('consultTopic').value.trim(); if (orderSnapshot) topic += '\n' + window.XiotEnergyConfigurator.selectionText(orderSnapshot); var comment = document.getElementById('consultComment').value.trim(); // The existing mail handler renders HTML. Escape user text inside the plain-text topic. function escapeText(text) { return text.replace(/&/g, '&').replace(//g, '>'); } if (comment) topic += '\nКомментарий покупателя: ' + escapeText(comment); var payload = { func: 'xiotru_client', name: escapeText(document.getElementById('consultName').value.trim()), phone: escapeText(document.getElementById('consultPhone').value.trim()), email: document.getElementById('consultEmail').value.trim(), topic: topic }; if (!payload.name || !payload.phone || !payload.email) { status.textContent = 'Укажите имя, телефон и электронную почту.'; status.className = 'consult-status is-error'; return; } form.setAttribute('data-sending', '1'); form.setAttribute('aria-busy', 'true'); submitButton.disabled = true; var requestFields = Array.prototype.filter.call(form.querySelectorAll('input, select, textarea'), function (field) { return !field.disabled; }); requestFields.forEach(function (field) { field.disabled = true; }); var closeButton = modal.querySelector('.modal-close'); closeButton.disabled = true; status.textContent = 'Отправляем заявку…'; status.className = 'consult-status'; var controller = new AbortController(); var timeout = window.setTimeout(function () { controller.abort(); }, 30000); fetch('https://xiot.pro/system/ajax.php', { method: 'POST', signal: controller.signal, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: 'data=' + encodeURIComponent(JSON.stringify(payload)) }) .then(function (response) { if (!response.ok) throw new Error('HTTP ' + response.status); return response.json(); }) .then(function (msg) { // Empty/malformed responses are not proof of delivery. Preserve the order on failure. if (!msg || msg.error || !msg.ok) throw new Error('Request not confirmed'); status.textContent = isOrder ? 'Заявка на решение отправлена в XIOT. Свяжемся с вами для подтверждения стоимости и поставки.' : 'Заявка отправлена. Специалисты XIOT свяжутся с вами.'; status.className = 'consult-status is-success'; form.reset(); form.hidden = true; if (modalIsOpen()) focusElement(status); }) .catch(function () { status.textContent = 'Не удалось подтвердить отправку. Ваши данные сохранены в форме. Попробуйте ещё раз или напишите на info@xiot.ru.'; status.className = 'consult-status is-error'; }) .finally(function () { window.clearTimeout(timeout); form.removeAttribute('data-sending'); form.removeAttribute('aria-busy'); submitButton.disabled = false; requestFields.forEach(function (field) { field.disabled = false; }); closeButton.disabled = false; }); }); var revealElements = document.querySelectorAll('.reveal'); if ('IntersectionObserver' in window && !reducedMotion.matches) { var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.classList.add('is-visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.12 }); revealElements.forEach(function (element) { observer.observe(element); }); document.documentElement.classList.add('js-reveal'); } else { revealElements.forEach(function (element) { element.classList.add('is-visible'); }); } })();