Files
frieze/script.js
T

898 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function normalizePriceItems(items) {
if (!Array.isArray(items)) {
return [];
}
return 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 : "",
}));
}
function normalizePriceData(data) {
if (!Array.isArray(data)) {
return JSON.parse(JSON.stringify(fallbackPriceData));
}
return data.map((category) => {
const children = Array.isArray(category?.children)
? category.children.map((child) => ({
title: typeof child?.title === "string" ? child.title : "",
items: normalizePriceItems(child?.items),
}))
: [];
return {
title: typeof category?.title === "string" ? category.title : "",
items: normalizePriceItems(category?.items),
children,
};
});
}
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 buildPriceTable(items) {
return `
<ul class="price-list">
<li class="price-list__head" aria-hidden="true">
<span>Виды работ</span>
<span>Цена</span>
<span>Кол-во</span>
<span>Ед.изм</span>
</li>
${buildPriceList(Array.isArray(items) ? items : [])}
</ul>
`;
}
function slugifyCategory(title) {
const map = {
а: "a",
б: "b",
в: "v",
г: "g",
д: "d",
е: "e",
ё: "e",
ж: "zh",
з: "z",
и: "i",
й: "y",
к: "k",
л: "l",
м: "m",
н: "n",
о: "o",
п: "p",
р: "r",
с: "s",
т: "t",
у: "u",
ф: "f",
х: "h",
ц: "ts",
ч: "ch",
ш: "sh",
щ: "sch",
ъ: "",
ы: "y",
ь: "",
э: "e",
ю: "yu",
я: "ya",
};
return String(title || "")
.toLowerCase()
.split("")
.map((ch) => map[ch] ?? ch)
.join("")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
function openPriceCategoryFromHash() {
if (!priceAccordion) {
return;
}
const hash = decodeURIComponent(location.hash.replace(/^#/, "")).trim();
if (!hash) {
return;
}
const category = priceAccordion.querySelector(
`.price-category#${CSS.escape(hash)}`
);
if (!(category instanceof HTMLElement)) {
return;
}
const toggle = category.querySelector(".price-category__toggle");
const listWrap = toggle?.nextElementSibling;
if (
toggle instanceof HTMLElement &&
listWrap instanceof HTMLElement &&
listWrap.classList.contains("price-list-wrap")
) {
toggle.setAttribute("aria-expanded", "true");
listWrap.hidden = false;
}
requestAnimationFrame(() => {
const header = document.querySelector(".header");
const offset =
header instanceof HTMLElement ? header.getBoundingClientRect().height + 12 : 12;
const top = category.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
});
}
function renderPriceAccordion(priceData) {
if (!priceAccordion) {
return;
}
priceAccordion.innerHTML = priceData
.map((category) => {
const hasChildren =
Array.isArray(category.children) && category.children.length > 0;
const categoryId = slugifyCategory(category.title || "kategoriya");
if (hasChildren) {
const childrenHtml = category.children
.map(
(child) => `
<article class="price-subcategory">
<button class="price-subcategory__toggle" type="button" aria-expanded="false">
${escapeHtml(child.title || "Подкатегория")}
</button>
<div class="price-list-wrap" hidden>
${buildPriceTable(child.items)}
</div>
</article>
`
)
.join("");
return `
<article class="price-category" id="${escapeHtml(categoryId)}">
<button class="price-category__toggle" type="button" aria-expanded="false">
${escapeHtml(category.title || "Категория")}
</button>
<div class="price-list-wrap price-list-wrap--nested" hidden>
<div class="price-subcategories">
${childrenHtml}
</div>
</div>
</article>
`;
}
return `
<article class="price-category" id="${escapeHtml(categoryId)}">
<button class="price-category__toggle" type="button" aria-expanded="false">
${escapeHtml(category.title || "Категория")}
</button>
<div class="price-list-wrap" hidden>
${buildPriceTable(category.items)}
</div>
</article>
`;
})
.join("");
openPriceCategoryFromHash();
}
if (priceAccordion) {
loadPriceData().then(renderPriceAccordion);
window.addEventListener("hashchange", openPriceCategoryFromHash);
priceAccordion.addEventListener("click", (event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) {
return;
}
const toggle = target.closest(
".price-category__toggle, .price-subcategory__toggle"
);
if (!toggle || !(toggle instanceof HTMLElement)) {
return;
}
const listWrap = toggle.nextElementSibling;
if (!(listWrap instanceof HTMLElement) || !listWrap.classList.contains("price-list-wrap")) {
return;
}
const expanded = toggle.getAttribute("aria-expanded") === "true";
toggle.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 }
);
}