752 lines
24 KiB
JavaScript
752 lines
24 KiB
JavaScript
function getCurrentPageName() {
|
||
const normalizedPath = window.location.pathname.replace(/\/+$/, "");
|
||
const raw = (normalizedPath.split("/").pop() || "").toLowerCase();
|
||
const normalized = raw.replace(/\.html$/, "");
|
||
return normalized || "index";
|
||
}
|
||
|
||
const isLocalDevHost =
|
||
window.location.hostname === "localhost" ||
|
||
window.location.hostname === "127.0.0.1";
|
||
|
||
function routeHref(page) {
|
||
if (page === "index") {
|
||
return isLocalDevHost ? "index.html" : "/";
|
||
}
|
||
return isLocalDevHost ? `${page}.html` : `/${page}`;
|
||
}
|
||
|
||
function renderSharedLayout() {
|
||
const currentPage = getCurrentPageName();
|
||
const headerMount = document.querySelector("[data-site-header]");
|
||
const footerMount = document.querySelector("[data-site-footer]");
|
||
|
||
if (headerMount) {
|
||
const navItems = [
|
||
{ page: "prices", href: routeHref("prices"), label: "Цены" },
|
||
{ page: "portfolio", href: routeHref("portfolio"), label: "Портфолио" },
|
||
{ page: "reviews", href: routeHref("reviews"), label: "Отзывы" },
|
||
];
|
||
|
||
const navHtml = navItems
|
||
.map((item) => {
|
||
const isCurrent = currentPage === item.page;
|
||
return `<a class="header-link${isCurrent ? " header-link--current" : ""}" href="${item.href}"${isCurrent ? ' aria-current="page"' : ""}>${item.label}</a>`;
|
||
})
|
||
.join("");
|
||
|
||
headerMount.outerHTML = `
|
||
<header class="header">
|
||
<div class="container header__inner">
|
||
<div class="header__left">
|
||
<a href="${routeHref("index")}" class="logo">
|
||
<img src="image/logo-test.jpg" alt="Фриз">
|
||
</a>
|
||
<nav class="header__nav" aria-label="Основная навигация">
|
||
${navHtml}
|
||
</nav>
|
||
</div>
|
||
<div class="header__actions">
|
||
<div class="header__contact">
|
||
<a class="phone-link" href="tel:+79217412646">+7 (921) 741-26-46</a>
|
||
<span class="header__meta">Ежедневно 10:00-18:00</span>
|
||
<span class="header__geo">Санкт-Петербург</span>
|
||
</div>
|
||
<a class="btn btn--outline" href="https://vk.com/ya_lo_ya" target="_blank" rel="noopener noreferrer">VK</a>
|
||
</div>
|
||
</div>
|
||
</header>`;
|
||
}
|
||
|
||
if (footerMount) {
|
||
const isPrivacyPage = currentPage === "privacy";
|
||
const isRequisitesPage = currentPage === "requisites";
|
||
|
||
footerMount.outerHTML = `
|
||
<a class="floating-call" href="tel:+79217412646" aria-label="Позвонить">Позвонить</a>
|
||
<footer class="footer">
|
||
<div class="container footer__inner">
|
||
<div class="footer__brand">
|
||
<p class="footer__copy">© <span id="year"></span> Фриз. Все права защищены.</p>
|
||
<p class="footer__ip">ИП Погребнякова Ольга Юрьевна</p>
|
||
<p class="footer__geo">Работаем в Санкт-Петербурге и Ленинградской области</p>
|
||
</div>
|
||
<div class="footer__legal">
|
||
<a class="footer__link" href="${routeHref("requisites")}"${isRequisitesPage ? ' aria-current="page"' : ""}>Реквизиты ИП</a>
|
||
<a class="footer__link" href="${routeHref("privacy")}"${isPrivacyPage ? ' aria-current="page"' : ""}>Политика обработки персональных данных</a>
|
||
</div>
|
||
<p class="footer__disclaimer">Информация на сайте не является публичной офертой и носит информационный характер</p>
|
||
</div>
|
||
</footer>`;
|
||
}
|
||
}
|
||
|
||
renderSharedLayout();
|
||
|
||
const currentYear = document.getElementById("year");
|
||
if (currentYear) {
|
||
currentYear.textContent = new Date().getFullYear();
|
||
}
|
||
|
||
const reduceMotionMql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||
|
||
function easeInOutCubic(t) {
|
||
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
||
}
|
||
|
||
function smoothScrollTo(targetY) {
|
||
const startY = window.scrollY || document.documentElement.scrollTop;
|
||
const change = targetY - startY;
|
||
if (reduceMotionMql.matches) {
|
||
window.scrollTo(0, targetY);
|
||
return;
|
||
}
|
||
if (Math.abs(change) < 2) {
|
||
return;
|
||
}
|
||
const duration = Math.min(980, Math.max(480, Math.abs(change) * 0.62));
|
||
const start = performance.now();
|
||
|
||
const step = (now) => {
|
||
const t = Math.min(1, (now - start) / duration);
|
||
window.scrollTo(0, startY + change * easeInOutCubic(t));
|
||
if (t < 1) {
|
||
requestAnimationFrame(step);
|
||
}
|
||
};
|
||
requestAnimationFrame(step);
|
||
}
|
||
|
||
document.addEventListener(
|
||
"click",
|
||
(e) => {
|
||
if (document.documentElement.classList.contains("modal-open")) {
|
||
return;
|
||
}
|
||
const a = e.target.closest("a");
|
||
if (!a) {
|
||
return;
|
||
}
|
||
const href = a.getAttribute("href");
|
||
if (!href || href === "#" || href.charAt(0) !== "#") {
|
||
return;
|
||
}
|
||
if (a.hasAttribute("download")) {
|
||
return;
|
||
}
|
||
const el = document.querySelector(href);
|
||
if (!el) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
const header = document.querySelector(".header");
|
||
const headerH = header ? header.getBoundingClientRect().height : 0;
|
||
const gap = 16;
|
||
const y = el.getBoundingClientRect().top + window.scrollY - headerH - gap;
|
||
smoothScrollTo(Math.max(0, y));
|
||
history.pushState(null, "", href);
|
||
},
|
||
true
|
||
);
|
||
|
||
const isHomePage = () => {
|
||
return getCurrentPageName() === "index";
|
||
};
|
||
|
||
document.querySelectorAll("a.logo").forEach((link) => {
|
||
const href = (link.getAttribute("href") || "").trim();
|
||
const goesHome =
|
||
href === "index.html" ||
|
||
href === "./index.html" ||
|
||
href === "/" ||
|
||
href === "/index" ||
|
||
href.endsWith("/index.html");
|
||
if (!goesHome) {
|
||
return;
|
||
}
|
||
link.addEventListener("click", (event) => {
|
||
if (!isHomePage()) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
if (window.scrollY > 0) {
|
||
smoothScrollTo(0);
|
||
}
|
||
if (window.location.hash) {
|
||
history.replaceState(null, "", window.location.pathname + window.location.search);
|
||
}
|
||
});
|
||
});
|
||
|
||
const form = document.getElementById("contactForm");
|
||
if (form) {
|
||
form.setAttribute("novalidate", "novalidate");
|
||
const nameInput = form.querySelector('input[name="name"]');
|
||
const phoneInput = form.querySelector('input[name="phone"]');
|
||
const honeypotInput = form.querySelector('input[name="contact_fax"]');
|
||
const startedAtInput = form.querySelector('input[name="started_at"]');
|
||
const submitButton = form.querySelector('button[type="submit"]');
|
||
|
||
if (startedAtInput) {
|
||
startedAtInput.value = String(Date.now());
|
||
}
|
||
|
||
if (phoneInput) {
|
||
const formatPhone = (rawValue) => {
|
||
const digits = rawValue.replace(/\D/g, "");
|
||
const localDigits = digits.startsWith("7") ? digits.slice(1) : digits;
|
||
const trimmed = localDigits.slice(0, 10);
|
||
|
||
if (!trimmed.length) {
|
||
return "+7";
|
||
}
|
||
|
||
let result = "+7";
|
||
if (trimmed.length > 0) {
|
||
result += ` (${trimmed.slice(0, 3)}`;
|
||
}
|
||
if (trimmed.length >= 3) {
|
||
result += ")";
|
||
}
|
||
if (trimmed.length > 3) {
|
||
result += ` ${trimmed.slice(3, 6)}`;
|
||
}
|
||
if (trimmed.length > 6) {
|
||
result += `-${trimmed.slice(6, 8)}`;
|
||
}
|
||
if (trimmed.length > 8) {
|
||
result += `-${trimmed.slice(8, 10)}`;
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
phoneInput.addEventListener("focus", () => {
|
||
if (!phoneInput.value.trim()) {
|
||
phoneInput.value = "+7";
|
||
} else {
|
||
phoneInput.value = formatPhone(phoneInput.value);
|
||
}
|
||
});
|
||
|
||
phoneInput.addEventListener("input", () => {
|
||
phoneInput.value = formatPhone(phoneInput.value);
|
||
phoneInput.setSelectionRange(phoneInput.value.length, phoneInput.value.length);
|
||
});
|
||
|
||
phoneInput.addEventListener("keydown", (event) => {
|
||
const selectionStart = phoneInput.selectionStart;
|
||
const selectionEnd = phoneInput.selectionEnd;
|
||
const hasSelection =
|
||
selectionStart !== null &&
|
||
selectionEnd !== null &&
|
||
selectionStart !== selectionEnd;
|
||
const fullSelection =
|
||
selectionStart === 0 && selectionEnd === phoneInput.value.length;
|
||
|
||
if (
|
||
fullSelection &&
|
||
(event.key === "Backspace" || event.key === "Delete")
|
||
) {
|
||
event.preventDefault();
|
||
phoneInput.value = "";
|
||
return;
|
||
}
|
||
|
||
if (
|
||
(event.key === "Backspace" || event.key === "Delete") &&
|
||
selectionStart !== null &&
|
||
selectionStart <= 2 &&
|
||
!hasSelection
|
||
) {
|
||
event.preventDefault();
|
||
}
|
||
});
|
||
|
||
phoneInput.addEventListener("blur", () => {
|
||
if (phoneInput.value === "+7" || phoneInput.value === "+7 (") {
|
||
phoneInput.value = "";
|
||
}
|
||
});
|
||
}
|
||
|
||
const consentInput = form.querySelector('input[name="consent"]');
|
||
const consentLabel = form.querySelector(".contact-form__consent");
|
||
const nameValidationRegex = /^[A-Za-zА-Яа-яЁё\s-]{2,80}$/;
|
||
|
||
const showFormToast = (message, variant = "warning") => {
|
||
let toast = document.getElementById("formToast");
|
||
if (!toast) {
|
||
toast = document.createElement("div");
|
||
toast.id = "formToast";
|
||
toast.className = "form-toast";
|
||
document.body.appendChild(toast);
|
||
}
|
||
toast.textContent = message;
|
||
toast.classList.remove("form-toast--warning", "form-toast--success");
|
||
toast.classList.add(`form-toast--${variant}`);
|
||
toast.setAttribute("role", variant === "success" ? "status" : "alert");
|
||
toast.setAttribute("aria-live", "polite");
|
||
toast.classList.add("is-visible");
|
||
clearTimeout(toast._hideTimer);
|
||
const hideMs = variant === "success" ? 6000 : 5000;
|
||
toast._hideTimer = window.setTimeout(() => {
|
||
toast.classList.remove("is-visible");
|
||
}, hideMs);
|
||
toast.onclick = () => {
|
||
clearTimeout(toast._hideTimer);
|
||
toast.classList.remove("is-visible");
|
||
};
|
||
};
|
||
|
||
form.addEventListener("submit", (event) => {
|
||
event.preventDefault();
|
||
|
||
if (nameInput) {
|
||
const normalizedName = nameInput.value.trim().replace(/\s+/g, " ");
|
||
nameInput.value = normalizedName;
|
||
if (!normalizedName) {
|
||
showFormToast("Пожалуйста, укажите ваше имя", "warning");
|
||
nameInput.focus();
|
||
return;
|
||
}
|
||
if (!nameValidationRegex.test(normalizedName)) {
|
||
showFormToast(
|
||
"Имя должно содержать только буквы, пробелы и дефис (2-80 символов)",
|
||
"warning"
|
||
);
|
||
nameInput.focus();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (phoneInput) {
|
||
const phoneDigits = phoneInput.value.replace(/\D/g, "");
|
||
const isPhoneValid = phoneDigits.length === 11 && phoneDigits.startsWith("7");
|
||
if (!isPhoneValid) {
|
||
showFormToast("Пожалуйста, укажите корректный номер телефона", "warning");
|
||
phoneInput.focus();
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (consentInput && !consentInput.checked) {
|
||
consentLabel?.classList.add("contact-form__consent--error");
|
||
consentLabel?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||
showFormToast(
|
||
"Чтобы отправить заявку, отметьте согласие на обработку персональных данных",
|
||
"warning"
|
||
);
|
||
consentInput.focus();
|
||
return;
|
||
}
|
||
consentLabel?.classList.remove("contact-form__consent--error");
|
||
|
||
if (startedAtInput && !startedAtInput.value) {
|
||
startedAtInput.value = String(Date.now());
|
||
}
|
||
|
||
const formData = new FormData(form);
|
||
// Явно чистим honeypot: защищает от автозаполнения браузером.
|
||
if (honeypotInput) {
|
||
honeypotInput.value = "";
|
||
}
|
||
formData.delete("website");
|
||
formData.set("contact_fax", "");
|
||
if (!formData.get("consent")) {
|
||
formData.set("consent", "1");
|
||
}
|
||
|
||
if (submitButton) {
|
||
submitButton.disabled = true;
|
||
submitButton.setAttribute("aria-busy", "true");
|
||
}
|
||
|
||
fetch("submit.php", {
|
||
method: "POST",
|
||
body: formData,
|
||
headers: {
|
||
Accept: "application/json",
|
||
},
|
||
})
|
||
.then(async (response) => {
|
||
let payload = null;
|
||
try {
|
||
payload = await response.json();
|
||
} catch (error) {
|
||
payload = null;
|
||
}
|
||
|
||
if (!response.ok || !payload?.ok) {
|
||
const errorMessage =
|
||
payload?.message ||
|
||
"Не удалось отправить заявку. Позвоните нам, пожалуйста, по телефону +7 (921) 741-26-46";
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
showFormToast("Спасибо! Заявка принята — скоро мы с вами свяжемся", "success");
|
||
form.reset();
|
||
if (startedAtInput) {
|
||
startedAtInput.value = String(Date.now());
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
showFormToast(error.message, "warning");
|
||
})
|
||
.finally(() => {
|
||
if (submitButton) {
|
||
submitButton.disabled = false;
|
||
submitButton.removeAttribute("aria-busy");
|
||
}
|
||
});
|
||
});
|
||
|
||
if (consentInput) {
|
||
consentInput.addEventListener("change", () => {
|
||
if (consentInput.checked) {
|
||
consentLabel?.classList.remove("contact-form__consent--error");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
const revealItems = document.querySelectorAll(".reveal");
|
||
const revealObserver = new IntersectionObserver(
|
||
(entries, observer) => {
|
||
entries.forEach((entry) => {
|
||
if (!entry.isIntersecting) {
|
||
return;
|
||
}
|
||
entry.target.classList.add("is-visible");
|
||
observer.unobserve(entry.target);
|
||
});
|
||
},
|
||
{
|
||
threshold: 0.15,
|
||
}
|
||
);
|
||
|
||
revealItems.forEach((item) => revealObserver.observe(item));
|
||
|
||
const priceAccordion = document.getElementById("priceAccordion");
|
||
|
||
const fallbackPriceData = [
|
||
{
|
||
title: "Электромонтажные работы",
|
||
items: [
|
||
{ name: "Установка розетки", price: "от 650 ₽/шт" },
|
||
{ name: "Установка выключателя", price: "от 550 ₽/шт" },
|
||
{ name: "Монтаж подрозетника", price: "от 450 ₽/шт" },
|
||
{ name: "Монтаж точки освещения", price: "от 1 200 ₽/шт" },
|
||
{ name: "Монтаж потолочного светильника", price: "от 900 ₽/шт" },
|
||
],
|
||
},
|
||
{
|
||
title: "Плиточные работы",
|
||
items: [
|
||
{ name: "Укладка плитки 20x30", price: "от 350 ₽/шт" },
|
||
{ name: "Укладка керамогранита 60x60", price: "от 900 ₽/шт" },
|
||
{ name: "Запил плитки под 45 градусов", price: "от 500 ₽/пог.м" },
|
||
{ name: "Затирка эпоксидная", price: "от 300 ₽/м²" },
|
||
],
|
||
},
|
||
{
|
||
title: "Сантехнические работы",
|
||
items: [
|
||
{ name: "Установка смесителя", price: "от 1 500 ₽/шт" },
|
||
{ name: "Монтаж унитаза", price: "от 2 500 ₽/шт" },
|
||
{ name: "Установка раковины с тумбой", price: "от 3 000 ₽/шт" },
|
||
{ name: "Подключение полотенцесушителя", price: "от 2 800 ₽/шт" },
|
||
],
|
||
},
|
||
];
|
||
|
||
function escapeHtml(value) {
|
||
const text = String(value ?? "");
|
||
return text
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
function normalizePriceData(data) {
|
||
if (!Array.isArray(data)) {
|
||
return JSON.parse(JSON.stringify(fallbackPriceData));
|
||
}
|
||
|
||
return data.map((category) => ({
|
||
title: typeof category?.title === "string" ? category.title : "",
|
||
items: Array.isArray(category?.items)
|
||
? category.items.map((item) => ({
|
||
name: typeof item?.name === "string" ? item.name : "",
|
||
price: typeof item?.price === "string" ? item.price : "",
|
||
amount: typeof item?.amount === "string" ? item.amount : "",
|
||
unit: typeof item?.unit === "string" ? item.unit : "",
|
||
total: typeof item?.total === "string" ? item.total : "",
|
||
}))
|
||
: [],
|
||
}));
|
||
}
|
||
|
||
async function loadPriceData() {
|
||
const externalData =
|
||
typeof window.PRICES_DATA !== "undefined" ? window.PRICES_DATA : null;
|
||
if (externalData) {
|
||
return normalizePriceData(externalData);
|
||
}
|
||
return JSON.parse(JSON.stringify(fallbackPriceData));
|
||
}
|
||
|
||
function buildPriceList(items) {
|
||
return items
|
||
.map(
|
||
(item) => {
|
||
const rawPrice = String(item.price || "").trim();
|
||
const desktopPrice = rawPrice
|
||
.replace(/\s*\/\s*[^\s]+$/u, "")
|
||
.trim();
|
||
const mobilePrice = rawPrice
|
||
.replace(/^от\s+/iu, "")
|
||
.replace(/\s*\/\s*[^\s]+$/u, "")
|
||
.trim();
|
||
const derivedUnitMatch = rawPrice.match(/\/\s*([^\s]+)$/);
|
||
const derivedUnit = derivedUnitMatch ? derivedUnitMatch[1] : "";
|
||
const amountValue = item.amount || "1";
|
||
const unitValue = item.unit || derivedUnit || "";
|
||
const normalizedName = String(item.name || "").trim().replace(/\s+/g, " ");
|
||
const fullName = escapeHtml(normalizedName);
|
||
return `
|
||
<li>
|
||
<span class="price-work-name">${fullName}</span>
|
||
<span class="price-value">
|
||
<span class="price-value__full">${escapeHtml(desktopPrice || rawPrice)}</span>
|
||
<span class="price-value__mobile">${escapeHtml(mobilePrice || rawPrice)}</span>
|
||
</span>
|
||
<span>${escapeHtml(amountValue)}</span>
|
||
<span>${escapeHtml(unitValue || "—")}</span>
|
||
</li>
|
||
`;
|
||
}
|
||
)
|
||
.join("");
|
||
}
|
||
|
||
function renderPriceAccordion(priceData) {
|
||
if (!priceAccordion) {
|
||
return;
|
||
}
|
||
|
||
priceAccordion.innerHTML = priceData
|
||
.map(
|
||
(category) => `
|
||
<article class="price-category">
|
||
<button class="price-category__toggle" type="button" aria-expanded="false">
|
||
${escapeHtml(category.title || "Категория")}
|
||
</button>
|
||
<div class="price-list-wrap" hidden>
|
||
<ul class="price-list">
|
||
<li class="price-list__head" aria-hidden="true">
|
||
<span>Виды работ</span>
|
||
<span>Цена</span>
|
||
<span>Кол-во</span>
|
||
<span>Ед.изм</span>
|
||
</li>
|
||
${buildPriceList(Array.isArray(category.items) ? category.items : [])}
|
||
</ul>
|
||
</div>
|
||
</article>
|
||
`
|
||
)
|
||
.join("");
|
||
}
|
||
|
||
if (priceAccordion) {
|
||
loadPriceData().then(renderPriceAccordion);
|
||
|
||
priceAccordion.addEventListener("click", (event) => {
|
||
const target = event.target;
|
||
if (!(target instanceof HTMLElement)) {
|
||
return;
|
||
}
|
||
|
||
if (target.classList.contains("price-category__toggle")) {
|
||
const category = target.closest(".price-category");
|
||
const listWrap = category ? category.querySelector(".price-list-wrap") : null;
|
||
if (!listWrap) {
|
||
return;
|
||
}
|
||
|
||
const expanded = target.getAttribute("aria-expanded") === "true";
|
||
target.setAttribute("aria-expanded", String(!expanded));
|
||
listWrap.hidden = expanded;
|
||
}
|
||
});
|
||
}
|
||
|
||
const LOGO_KEY = "remont-spb-logo";
|
||
const DEFAULT_LOGO = "logo-interior-1.png";
|
||
const logoOptions = [
|
||
{ id: "logo-option-1.png", label: "1" },
|
||
{ id: "logo-option-2.png", label: "2" },
|
||
{ id: "logo-option-3.png", label: "3" },
|
||
{ id: "logo-option-4.png", label: "4" },
|
||
{ id: "logo-option-6.png", label: "5" },
|
||
{ id: "logo-right-2.png", label: "6" },
|
||
{ id: "logo-right-4.png", label: "7" },
|
||
{ id: "logo-right-5.png", label: "8" },
|
||
{ id: "logo-simple-1.png", label: "9" },
|
||
{ id: "logo-simple-3.png", label: "10" },
|
||
{ id: "logo-interior-1.png", label: "11" },
|
||
{ id: "logo-interior-6.png", label: "12" },
|
||
{ id: "logo-interior-8.png", label: "13" },
|
||
{ id: "logo-interior-9.png", label: "14" },
|
||
{ id: "logo-flat-2.png", label: "15" },
|
||
{ id: "logo-flat-3.png", label: "16" },
|
||
{ id: "logo-flat-5.png", label: "17" },
|
||
];
|
||
|
||
const BG_KEY = "remont-spb-bg";
|
||
const DEFAULT_BG = "bg-4.png";
|
||
const bgOptions = [
|
||
{ id: "none", label: "Без фона" },
|
||
{ id: "bg-1.png", label: "Фон 1" },
|
||
{ id: "bg-2.png", label: "Фон 2" },
|
||
{ id: "bg-3.png", label: "Фон 3" },
|
||
{ id: "bg-4.png", label: "Фон 4" },
|
||
{ id: "bg-5.png", label: "Фон 5" },
|
||
];
|
||
|
||
function applyLogo(logoId) {
|
||
localStorage.setItem(LOGO_KEY, logoId);
|
||
const logoPath = `image/${logoId}`;
|
||
|
||
const logoImgs = document.querySelectorAll(".logo img");
|
||
logoImgs.forEach((img) => {
|
||
img.src = logoPath;
|
||
});
|
||
}
|
||
|
||
function applyBg(bgId) {
|
||
localStorage.setItem(BG_KEY, bgId);
|
||
const heroBg = document.getElementById("heroBg");
|
||
const heroSection = document.getElementById("hero");
|
||
|
||
if (heroBg && heroSection) {
|
||
if (bgId === "none") {
|
||
heroBg.style.display = "none";
|
||
heroBg.src = "";
|
||
} else {
|
||
heroBg.style.display = "block";
|
||
heroBg.src = `image/${bgId}`;
|
||
}
|
||
}
|
||
}
|
||
|
||
function applyBrandingDefaults() {
|
||
// Применяем зафиксированные значения брендинга без UI-переключателя.
|
||
applyLogo(DEFAULT_LOGO);
|
||
applyBg(DEFAULT_BG);
|
||
}
|
||
|
||
applyBrandingDefaults();
|
||
|
||
/* Portfolio Gallery Logic */
|
||
const workCards = document.querySelectorAll('.work-card[data-images]');
|
||
const portfolioModal = document.getElementById('portfolioModal');
|
||
const portfolioTrack = document.getElementById('portfolioTrack');
|
||
|
||
if (portfolioModal && portfolioTrack) {
|
||
const closeButtons = portfolioModal.querySelectorAll('[data-close]');
|
||
|
||
const openModal = (images) => {
|
||
portfolioTrack.innerHTML = images
|
||
.map(
|
||
(src) =>
|
||
`<div class="portfolio-modal__slide"><img src="${src}" alt="Фото объекта"></div>`
|
||
)
|
||
.join('');
|
||
portfolioModal.classList.add('is-open');
|
||
document.documentElement.classList.add('modal-open');
|
||
document.body.classList.add('modal-open');
|
||
};
|
||
|
||
const closeModal = () => {
|
||
portfolioModal.classList.remove('is-open');
|
||
document.documentElement.classList.remove('modal-open');
|
||
document.body.classList.remove('modal-open');
|
||
};
|
||
|
||
workCards.forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
try {
|
||
const images = JSON.parse(card.dataset.images);
|
||
if (images && images.length) {
|
||
openModal(images);
|
||
}
|
||
} catch (e) {
|
||
console.error('Ошибка при чтении картинок галереи', e);
|
||
}
|
||
});
|
||
});
|
||
|
||
closeButtons.forEach(btn => btn.addEventListener('click', closeModal));
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && portfolioModal.classList.contains('is-open')) {
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
// Логика перетаскивания (Drag-to-scroll)
|
||
let isDown = false;
|
||
let startX;
|
||
let scrollLeft;
|
||
|
||
portfolioTrack.addEventListener('mousedown', (e) => {
|
||
isDown = true;
|
||
portfolioTrack.classList.add('is-dragging');
|
||
startX = e.pageX - portfolioTrack.offsetLeft;
|
||
scrollLeft = portfolioTrack.scrollLeft;
|
||
});
|
||
|
||
portfolioTrack.addEventListener('mouseleave', () => {
|
||
isDown = false;
|
||
portfolioTrack.classList.remove('is-dragging');
|
||
});
|
||
|
||
portfolioTrack.addEventListener('mouseup', () => {
|
||
isDown = false;
|
||
portfolioTrack.classList.remove('is-dragging');
|
||
});
|
||
|
||
portfolioTrack.addEventListener('mousemove', (e) => {
|
||
if (!isDown) return;
|
||
e.preventDefault();
|
||
const x = e.pageX - portfolioTrack.offsetLeft;
|
||
const walk = (x - startX) * 2; // Скорость прокрутки при перетаскивании
|
||
portfolioTrack.scrollLeft = scrollLeft - walk;
|
||
});
|
||
|
||
// Прокрутка колесиком мыши (Wheel scroll)
|
||
portfolioTrack.addEventListener('wheel', (e) => {
|
||
if (e.deltaY !== 0) {
|
||
e.preventDefault();
|
||
portfolioTrack.scrollLeft += e.deltaY;
|
||
}
|
||
});
|
||
|
||
// Блокируем прокрутку страницы, пока открыта модалка
|
||
portfolioModal.addEventListener(
|
||
'wheel',
|
||
(e) => {
|
||
if (!portfolioModal.classList.contains('is-open')) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
},
|
||
{ passive: false }
|
||
);
|
||
}
|