(() => { const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const selectors = [ ".section__head", ".info-card", ".feature-card", ".object-card", ".service-card", ".gallery__item", ".review-card", ".stats article", ".booking__form", ".contacts-card", ".hours-card", ]; const elements = Array.from(document.querySelectorAll(selectors.join(","))); if (!elements.length) return; const siblingCounters = new WeakMap(); elements.forEach((element) => { element.classList.add("scroll-reveal"); const parent = element.parentElement; let offsetIndex = 0; if (parent) { offsetIndex = siblingCounters.get(parent) ?? 0; siblingCounters.set(parent, offsetIndex + 1); } element.style.setProperty("--reveal-delay", `${(offsetIndex % 4) * 80}ms`); }); if (reduceMotion || !("IntersectionObserver" in window)) { elements.forEach((element) => element.classList.add("is-visible")); return; } const observer = new IntersectionObserver( (entries, currentObserver) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; entry.target.classList.add("is-visible"); currentObserver.unobserve(entry.target); }); }, { threshold: 0.18, rootMargin: "0px 0px -12% 0px" } ); elements.forEach((element) => observer.observe(element)); })(); (() => { const counters = Array.from(document.querySelectorAll(".js-counter")); if (!counters.length) return; const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const formatCounter = (counter, value) => { const suffix = counter.dataset.suffix || ""; counter.textContent = `${value}${suffix}`; }; const setFinalValue = (counter) => { const target = Number(counter.dataset.target || 0); formatCounter(counter, Number.isFinite(target) ? target : 0); }; const animateCounter = (counter) => { const target = Number(counter.dataset.target || 0); const duration = Number(counter.dataset.duration || 1300); if (!Number.isFinite(target) || target <= 0) { setFinalValue(counter); return; } let startTime = null; const tick = (timestamp) => { if (startTime === null) startTime = timestamp; const progress = Math.min((timestamp - startTime) / duration, 1); const currentValue = Math.floor(progress * target); formatCounter(counter, currentValue); if (progress < 1) { window.requestAnimationFrame(tick); return; } setFinalValue(counter); }; window.requestAnimationFrame(tick); }; if (reduceMotion || !("IntersectionObserver" in window)) { counters.forEach(setFinalValue); return; } const observer = new IntersectionObserver( (entries, currentObserver) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; animateCounter(entry.target); currentObserver.unobserve(entry.target); }); }, { threshold: 0.45 } ); counters.forEach((counter) => { formatCounter(counter, 0); observer.observe(counter); }); })(); (() => { const bookingForm = document.getElementById("booking-form"); const successModal = document.getElementById("booking-success-modal"); if (!bookingForm || !successModal) return; const closeTriggers = Array.from(successModal.querySelectorAll("[data-modal-close]")); const openModal = () => { successModal.classList.add("is-open"); successModal.setAttribute("aria-hidden", "false"); }; const closeModal = () => { successModal.classList.remove("is-open"); successModal.setAttribute("aria-hidden", "true"); }; const triggerAnalytics = () => { const eventName = "заявка"; const metrikaIdRaw = bookingForm.dataset.metrikaId || document.body.dataset.metrikaId || window.YANDEX_METRIKA_ID; const metrikaId = Number(metrikaIdRaw); if (typeof window.ym === "function" && Number.isFinite(metrikaId) && metrikaId > 0) { window.ym(metrikaId, "reachGoal", eventName); } const legacyCounterKey = Object.keys(window).find((key) => { return key.startsWith("yaCounter") && typeof window[key]?.reachGoal === "function"; }); if (legacyCounterKey) { window[legacyCounterKey].reachGoal(eventName); } if (typeof window.gtag === "function") { window.gtag("event", eventName, { event_category: "lead", event_label: "booking_form" }); } if (Array.isArray(window.dataLayer)) { window.dataLayer.push({ event: eventName, event_category: "lead", event_label: "booking_form" }); } }; const triggerAutoReply = async (email) => { if (!email) return; const autoReplyEndpoint = bookingForm.dataset.autoreplyEndpoint || window.BOOKING_AUTOREPLY_ENDPOINT; if (!autoReplyEndpoint) return; try { await fetch(autoReplyEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, source: "booking-form" }) }); } catch (error) { // Auto-reply is optional and should not block form success. console.error("Auto-reply request failed", error); } }; bookingForm.addEventListener("submit", async (event) => { event.preventDefault(); if (!bookingForm.checkValidity()) { bookingForm.reportValidity(); return; } const formData = new FormData(bookingForm); const email = String(formData.get("email") || "").trim(); triggerAnalytics(); await triggerAutoReply(email); bookingForm.reset(); openModal(); }); closeTriggers.forEach((trigger) => { trigger.addEventListener("click", closeModal); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && successModal.classList.contains("is-open")) { closeModal(); } }); })();