/* ========================================================= MPS Engineering 2026 - minimal vanilla JS ========================================================= */ (function(){ const $ = (s, root=document) => root.querySelector(s); const $$ = (s, root=document) => Array.from(root.querySelectorAll(s)); const STORAGE_KEY = "mps_catalog_products"; const TELEGRAM = "https://t.me/yourcompany"; const VIBER = "viber://chat?number=%2B380975058913"; const menuToggle = $("#menuToggle"); const mainNav = $("#mainNav"); menuToggle?.addEventListener("click", () => { const open = mainNav.classList.toggle("open"); menuToggle.setAttribute("aria-expanded", String(open)); }); $$("#mainNav a").forEach(a => a.addEventListener("click", () => mainNav?.classList.remove("open"))); const baseProducts = []; function getAdminProducts(){ return []; } function catName(cat){ return {canopy:"Навіси", frame:"Каркаси", truss:"Ферми / балки", stairs:"Сходи", fence:"Ворота / огорожі", other:"Інше"}[cat] || "Інше"; } function iconSvg(cat){ const stroke = "currentColor"; if(cat === "stairs") return ``; if(cat === "fence") return ``; if(cat === "truss") return ``; return ``; } function productCard(p){ const img = p.image ? `${p.name}` : `
${iconSvg(p.cat || p.category || "frame")}
`; const cat = p.cat || p.category || "other"; const price = p.price ? `від $${Number(p.price).toLocaleString("en-US")}` : "від за запитом"; const text = `${p.name} ${p.desc || p.description || ""} ${catName(cat)} ${(p.specs || []).join(" ")}`.toLowerCase(); return `
${img}${p.status || "Під замовлення"}
${catName(cat)}

${p.name}

${p.desc || p.description || "Виготовлення під ваші розміри та умови монтажу."}

${(p.specs || ["під розмір", "монтаж", "гарантія"]).map(s=>`${s}`).join("")}
`; } function allProducts(){ return []; } function renderProducts(){ const grid = $("#productGrid"); if(!grid) return; grid.innerHTML = allProducts().map(productCard).join(""); filterProducts(); } function filterProducts(){ const grid = $("#productGrid"); if(!grid) return; const q = ($("#catalogSearch")?.value || "").trim().toLowerCase(); const cat = $("#catalogFilter")?.value || "all"; let visible = 0; $$(".product-card", grid).forEach(card => { const ok = (!q || (card.dataset.text || "").includes(q)) && (cat === "all" || card.dataset.cat === cat); card.style.display = ok ? "" : "none"; if(ok) visible++; }); const empty = $("#catalogEmpty"); if(empty) empty.hidden = true; } $("#catalogSearch")?.addEventListener("input", filterProducts); $("#catalogFilter")?.addEventListener("change", filterProducts); renderProducts(); const rates = { canopy: 2100, frame: 4200, truss: 2600, stairs: 5200, fence: 1800 }; const typeNames = { canopy:"Металевий навіс", frame:"Каркас ангара / складу", truss:"Ферми / балки", stairs:"Сходи / площадка", fence:"Ворота / огорожа" }; const material = { paint:1, zinc:1.25, premium:1.4, painted:1, galvanized:1.25, heavy:1.4 }; const materialNames = { paint:"сталь + фарбування", zinc:"оцинкована сталь", premium:"посилена конструкція", painted:"сталь + фарбування", galvanized:"оцинкована сталь", heavy:"посилена конструкція" }; function calc(){ const priceEl = $("#calcPrice"); const summaryEl = $("#calcSummary"); if (!priceEl || !summaryEl) { return {summary:"", price:""}; } const type = $("#calcType")?.value || "canopy"; const mat = $("#calcMaterial")?.value || "painted"; const width = Math.max(1, Number($("#calcWidth")?.value || 1)); const length = Math.max(1, Number($("#calcLength")?.value || 1)); const complexity = Number($("#calcComplexity")?.value || 1); const area = width * length; const base = area * rates[type] * material[mat] * complexity; const low = Math.round(base * 0.9 / 1000) * 1000; const high = Math.round(base * 1.25 / 1000) * 1000; const fmt = n => n.toLocaleString("uk-UA"); const summary = `${typeNames[type]}, ${materialNames[mat]}, ${width} x ${length} м, площа ${area.toFixed(1)} м².`; priceEl.textContent = `від ${fmt(low)} до ${fmt(high)} грн`; summaryEl.textContent = summary; const minField = $("#estimatedMin"); const maxField = $("#estimatedMax"); const areaField = $("#areaM2"); if (minField) minField.value = String(low); if (maxField) maxField.value = String(high); if (areaField) areaField.value = String(area.toFixed(2)); return {summary, price:`${fmt(low)}-${fmt(high)} грн`}; } ["#calcType","#calcMaterial","#calcWidth","#calcLength","#calcComplexity"].forEach(s => $(s)?.addEventListener("input", calc)); ["#calcType","#calcMaterial","#calcComplexity"].forEach(s => $(s)?.addEventListener("change", calc)); calc(); $("#useCalcRequest")?.addEventListener("click", () => { const result = calc(); const msg = $("#requestMessage"); if(msg) msg.value = `Параметри з калькулятора: ${result.summary} Орієнтовна вартість: ${result.price}`; $("#contacts")?.scrollIntoView({behavior:"smooth", block:"start"}); setTimeout(() => $("#requestName")?.focus(), 500); }); $$("[data-project-filter]").forEach(btn => btn.addEventListener("click", () => { const type = btn.dataset.projectFilter; $$("[data-project-filter]").forEach(b=>b.classList.toggle("active", b===btn)); $$(".project-card").forEach(card => { card.style.display = (type === "all" || card.dataset.type === type) ? "" : "none"; }); })); })(); /* ========================================================= MySQL API integration for MPS Engineering ========================================================= */ (() => { const API_PRODUCTS = "api/products.php"; const API_REQUESTS = "api/requests.php"; const secureFetch = (...args) => window.MPSSecurity ? window.MPSSecurity.fetch(...args) : fetch(...args); const categoryLabels = { canopy: "Навіси", frame: "Каркаси", beam: "Ферми / балки", stairs: "Сходи", fence: "Огорожі / ворота", other: "Інше" }; const statusLabels = { available: "В наявності", order: "Під замовлення" }; function qs(selector, root = document) { return root.querySelector(selector); } function qsa(selector, root = document) { return Array.from(root.querySelectorAll(selector)); } function money(value) { if (!value && value !== 0) return "За запитом"; return "від " + Number(value).toLocaleString("uk-UA") + " грн"; } function productGallery(product) { return [ product.image_path, ...((product.gallery_images || []).filter(Boolean)) ].filter(Boolean); } function productImage(product) { if (product.image_path) { return `${escapeHtml(product.title)}`; } return ` `; } function escapeHtml(text = "") { return String(text) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function specsHtml(product) { const specs = String(product.specs || "") .split(";") .map(s => s.trim()) .filter(Boolean) .slice(0, 4); if (product.material) specs.unshift(product.material); if (product.size_info) specs.unshift(product.size_info); return specs.slice(0, 4).map(s => `${escapeHtml(s)}`).join(""); } function specsList(product) { const specs = String(product.specs || "") .split(";") .map(s => s.trim()) .filter(Boolean); if (product.material) specs.unshift("Матеріал: " + product.material); if (product.size_info) specs.unshift("Розмір: " + product.size_info); if (product.price_from) specs.push("Ціна: " + money(product.price_from)); return specs; } function materialKey(product) { const text = String(product.material || product.specs || "").toLowerCase(); if (text.includes("цинк") || text.includes("оцинк")) return "zinc"; if (text.includes("посилен") || text.includes("антикор")) return "premium"; if (text.includes("фарб") || text.includes("сталь")) return "paint"; return "other"; } function numericArea(product) { const source = String(product.size_info || product.specs || ""); const nums = source.match(/\d+(?:[.,]\d+)?/g)?.map(n => Number(n.replace(",", "."))) || []; if (nums.length >= 2) return nums[0] * nums[1]; if (nums.length === 1) return nums[0]; return 0; } function sizeBucket(product) { const area = numericArea(product); if (!area) return "unknown"; if (area < 20) return "small"; if (area <= 80) return "medium"; return "large"; } function productCard(product) { const status = product.status || "order"; const pdf = product.pdf_path ? `PDF / креслення` : `PDF / характеристики`; const gallery = productGallery(product); const galleryCount = gallery.length || (product.image_path ? 1 : 0); const specItems = specsHtml(product) || `розміри за запитомпокриття за запитомвага за запитом`; const title = escapeHtml(product.title || "Товар"); const priceText = money(product.price_from); return `
${statusLabels[status] || "Під замовлення"} ${galleryCount ? galleryCount + " фото" : "фото за запитом"} ${productImage(product)}
${categoryLabels[product.category] || "Інше"} ${statusLabels[status] || "Під замовлення"}

${title}

${escapeHtml(product.description || "Виготовлення під ваші розміри та умови монтажу.")}

${specItems}
Ціна${priceText}
${pdf}
`; } async function fetchProducts(params = {}) { const query = new URLSearchParams(params); const res = await fetch(API_PRODUCTS + (query.toString() ? "?" + query.toString() : ""), { cache: "no-store" }); const data = await res.json(); if (!data.ok) throw new Error(data.error || "Помилка завантаження товарів"); return data.products || []; } async function renderPublicProducts() { const grids = [ qs("#productGrid"), qs("#productsGrid"), qs("#catalogGrid"), qs("#homeCatalogGrid") ].filter(Boolean); if (!grids.length) return; const searchInput = qs("#catalogSearch") || qs("#search") || qs("#homeCatalogSearch"); const categorySelect = qs("#catalogFilter") || qs("#catalogCategory") || qs("#category") || qs("#homeCatalogCategory"); const materialSelect = qs("#materialFilter") || qs("#homeMaterialFilter"); const priceSelect = qs("#priceFilter") || qs("#homePriceFilter"); const sizeSelect = qs("#sizeFilter") || qs("#homeSizeFilter"); let currentProducts = []; const selectedCompare = new Set((() => { try { return JSON.parse(localStorage.getItem("mps_compare_ids") || "[]"); } catch { return []; } })()); function filteredProducts(products) { const material = materialSelect ? materialSelect.value : "all"; const price = priceSelect ? priceSelect.value : "all"; const size = sizeSelect ? sizeSelect.value : "all"; return products.filter(product => { const okMaterial = material === "all" || materialKey(product) === material; const okPrice = price === "all" || (Number(product.price_from || 0) > 0 && Number(product.price_from) <= Number(price)); const okSize = size === "all" || sizeBucket(product) === size; return okMaterial && okPrice && okSize; }); } function syncCompareInputs() { qsa(".compare-input").forEach(input => { input.checked = selectedCompare.has(String(input.value)); }); } function renderCompareBar() { const bar = qs("#compareBar"); const count = qs("#compareCount"); if (!bar || !count) return; count.textContent = String(selectedCompare.size); const headerCount = qs("#headerCompareCount"); if (headerCount) headerCount.textContent = String(selectedCompare.size); bar.hidden = selectedCompare.size === 0; } function productById(id) { return currentProducts.find(product => String(product.id) === String(id)); } function showProductDetails(product) { if (!product) return; const gallery = productGallery(product); const firstImage = gallery[0]; const specs = specsList(product); const modal = document.createElement("div"); modal.className = "product-modal"; modal.innerHTML = ` `; document.body.appendChild(modal); window.MPSSecurity?.prepareForm(modal.querySelector("form")).catch(() => {}); modal.querySelector(".product-modal__close").addEventListener("click", () => modal.remove()); modal.addEventListener("click", event => { if (event.target === modal) modal.remove(); }); modal.querySelectorAll(".product-modal__thumbs button").forEach(button => { button.addEventListener("click", () => { const img = button.querySelector("img"); const main = modal.querySelector(".product-modal__main"); if (img && main) main.src = img.src; }); }); } function showCompare() { const products = Array.from(selectedCompare).map(productById).filter(Boolean); if (products.length < 2) { alert("Оберіть мінімум два товари для порівняння."); return; } const rows = [ ["Тип", p => categoryLabels[p.category] || "Інше"], ["Розмір", p => p.size_info || "за запитом"], ["Матеріал", p => p.material || "за запитом"], ["Ціна", p => money(p.price_from)], ["Покриття / вага / товщина", p => String(p.specs || "за запитом")] ]; const modal = document.createElement("div"); modal.className = "product-modal compare-modal"; modal.innerHTML = ` `; document.body.appendChild(modal); window.MPSSecurity?.prepareForm(modal.querySelector("form")).catch(() => {}); modal.querySelector(".product-modal__close").addEventListener("click", () => modal.remove()); modal.addEventListener("click", event => { if (event.target === modal) modal.remove(); }); } async function load() { try { const products = await fetchProducts({ search: searchInput ? searchInput.value.trim() : "", category: categorySelect ? categorySelect.value : "all" }); currentProducts = products; const shownProducts = filteredProducts(products); grids.forEach(grid => { grid.innerHTML = shownProducts.length ? shownProducts.map(productCard).join("") : ""; }); syncCompareInputs(); renderCompareBar(); const counter = qs("#catalogCount") || qs("#productCount") || qs("#homeCatalogCount"); if (counter) counter.textContent = String(shownProducts.length); const emptyBox = qs("#catalogEmptyBox"); if (emptyBox) emptyBox.hidden = shownProducts.length > 0; } catch (err) { grids.forEach(grid => { grid.innerHTML = ""; }); const emptyBox = qs("#catalogEmptyBox"); if (emptyBox) { emptyBox.hidden = false; emptyBox.innerHTML = `

Каталог поки недоступний

Перевірте запуск Apache/MySQL або налаштування бази даних.

`; } } } searchInput?.addEventListener("input", load); categorySelect?.addEventListener("change", load); materialSelect?.addEventListener("change", load); priceSelect?.addEventListener("change", load); sizeSelect?.addEventListener("change", load); document.addEventListener("change", event => { const input = event.target.closest(".compare-input"); if (!input) return; if (input.checked) { selectedCompare.add(String(input.value)); } else { selectedCompare.delete(String(input.value)); } localStorage.setItem("mps_compare_ids", JSON.stringify(Array.from(selectedCompare))); renderCompareBar(); }); document.addEventListener("click", event => { const details = event.target.closest("[data-details]"); if (details) { showProductDetails(productById(details.dataset.details)); } }); qs("#compareBtn")?.addEventListener("click", showCompare); qs("#clearCompareBtn")?.addEventListener("click", () => { selectedCompare.clear(); localStorage.setItem("mps_compare_ids", "[]"); syncCompareInputs(); renderCompareBar(); }); await load(); } function fillAdminForm(product) { qs("#productId").value = product.id || ""; qs("#formAction").value = "update"; qs("#formTitle").textContent = "Редагування товару"; qs("#title").value = product.title || ""; qs("#category").value = product.category || "other"; qs("#status").value = product.status || "order"; qs("#price_from").value = product.price_from || ""; qs("#size_info").value = product.size_info || ""; qs("#material").value = product.material || ""; qs("#description").value = product.description || ""; qs("#specs").value = product.specs || ""; qs("#is_active").checked = Number(product.is_active) === 1; window.scrollTo({ top: 0, behavior: "smooth" }); } function resetAdminForm() { const form = qs("#dbProductForm"); if (!form) return; form.reset(); qs("#productId").value = ""; qs("#formAction").value = "create"; qs("#formTitle").textContent = "Новий товар"; qs("#is_active").checked = true; } async function renderAdminProducts() { const list = qs("#adminProducts"); if (!list) return; try { const products = await fetchProducts({ admin: "1" }); qs("#adminCount").textContent = "Усього товарів: " + products.length; if (!products.length) { list.innerHTML = `
Товарів ще немає. Заповніть форму зліва.
`; return; } list.innerHTML = products.map(product => `
${product.image_path ? `` : productImage(product)}
${escapeHtml(product.title)} ${categoryLabels[product.category] || "Інше"} · ${money(product.price_from)} · ${Number(product.is_active) ? "показується" : "приховано"}

${escapeHtml(product.description || "")}

`).join(""); list._products = products; } catch (err) { list.innerHTML = `
Помилка API: ${escapeHtml(err.message)}. Перевірте базу metalconstruct_db.
`; qs("#adminCount").textContent = "Помилка"; } } function setupAdmin() { const form = qs("#dbProductForm"); if (!form) return; form.addEventListener("submit", async (event) => { event.preventDefault(); const fd = new FormData(form); fd.set("is_active", qs("#is_active").checked ? "1" : "0"); const status = qs("#adminStatus"); status.textContent = "Збереження..."; try { const res = await fetch(API_PRODUCTS, { method: "POST", body: fd }); const data = await res.json(); if (!data.ok) throw new Error(data.error || "Не вдалося зберегти"); status.textContent = "Товар збережено в базу."; resetAdminForm(); await renderAdminProducts(); } catch (err) { status.textContent = "Помилка: " + err.message; } }); qs("#resetProductForm")?.addEventListener("click", resetAdminForm); qs("#reloadProducts")?.addEventListener("click", renderAdminProducts); qs("#adminProducts")?.addEventListener("click", async (event) => { const edit = event.target.closest("[data-edit]"); const del = event.target.closest("[data-delete]"); if (edit) { const products = qs("#adminProducts")._products || []; const product = products.find(p => String(p.id) === String(edit.dataset.edit)); if (product) fillAdminForm(product); } if (del) { if (!confirm("Видалити цей товар з бази?")) return; const fd = new FormData(); fd.set("action", "delete"); fd.set("id", del.dataset.delete); await fetch(API_PRODUCTS, { method: "POST", body: fd }); await renderAdminProducts(); } }); renderAdminProducts(); } function setupRequestForms() { qsa("form").forEach(form => { if (form.id === "dbProductForm") return; if (!form.matches("#requestForm, #estimateForm")) return; window.MPSSecurity?.addHoneypot(form); form.addEventListener("submit", async (event) => { event.preventDefault(); const fd = new FormData(form); const area = qs("#areaM2"); const status = qs("#requestStatus", form) || qs("#requestStatus") || form.querySelector(".form-status"); if (status) { status.textContent = "Надсилання заявки..."; status.style.color = ""; } if (area && !fd.get("area_m2")) fd.set("area_m2", area.value || ""); try { const response = await secureFetch(API_REQUESTS, { method: "POST", body: fd }); const data = await response.json(); if (!response.ok || !data.ok) throw new Error(data.error || "Не вдалося надіслати заявку"); if (status) status.textContent = "Заявку надіслано. Менеджер зв’яжеться з вами."; form.reset(); const fileName = form.querySelector(".file-name"); if (fileName) fileName.textContent = "Файл не вибрано"; } catch (err) { if (status) { status.textContent = err.message || "Не вдалося надіслати заявку."; status.style.color = "#b42318"; } } }); }); } function setupCallbackModal() { function openModal() { const modal = document.createElement("div"); modal.className = "product-modal callback-modal"; modal.innerHTML = `
Зворотний дзвінок

Замовити дзвінок

Залиште телефон, менеджер зв’яжеться з вами для уточнення задачі.

`; document.body.appendChild(modal); window.MPSSecurity?.prepareForm(modal.querySelector("form")).catch(() => {}); modal.querySelector(".product-modal__close").addEventListener("click", () => modal.remove()); modal.addEventListener("click", event => { if (event.target === modal) modal.remove(); }); modal.querySelector("form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; const status = form.querySelector(".form-status"); const payload = Object.fromEntries(new FormData(form).entries()); status.textContent = "Надсилання..."; try { const response = await secureFetch(API_REQUESTS, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); const data = await response.json(); if (!response.ok || !data.ok) throw new Error(data.error || "Не вдалося надіслати заявку"); status.textContent = "Заявку на дзвінок надіслано."; form.reset(); } catch (err) { status.textContent = "Не вдалося надіслати. Спробуйте телефоном."; status.style.color = "#b42318"; } }); } document.addEventListener("click", event => { const trigger = event.target.closest("[data-callback-open]"); if (!trigger) return; event.preventDefault(); openModal(); }); } document.addEventListener("DOMContentLoaded", () => { renderPublicProducts(); setupAdmin(); setupRequestForms(); setupCallbackModal(); }); })(); /* Improved online-store interface */ (function(){ const $ = (s, r=document) => r.querySelector(s); const $$ = (s, r=document) => Array.from(r.querySelectorAll(s)); const FAVORITES_KEY = "mps_store_favorites"; const REQUEST_KEY = "mps_store_request"; const read = (key) => { try { return JSON.parse(localStorage.getItem(key) || "[]"); } catch { return []; } }; const write = (key, value) => localStorage.setItem(key, JSON.stringify(value)); function cardData(card) { return { id: String(card?.dataset.productId || card?.dataset.id || ""), title: card?.dataset.title || card?.querySelector("h3")?.textContent?.trim() || "Товар", price: card?.querySelector(".store-price-block b,.price-row b")?.textContent?.trim() || "За запитом" }; } function updateHeaderCounts() { const fav = read(FAVORITES_KEY).length; const req = read(REQUEST_KEY).length; const cmp = read("mps_compare_ids").length; const setCount = (selector, value) => { $$(selector).forEach(el => { el.textContent = String(value); el.hidden = value === 0; }); }; setCount("#headerFavoriteCount", fav); setCount("#headerRequestCount", req); setCount("#headerCompareCount", cmp); } function syncCards() { const favorites = new Set(read(FAVORITES_KEY).map(String)); const requests = new Set(read(REQUEST_KEY).map(item => String(item.id))); $$(".store-product-card").forEach(card => { const id = String(card.dataset.productId || ""); const favBtn = card.querySelector("[data-favorite]"); const reqBtn = card.querySelector("[data-request]"); const isFav = favorites.has(id); const isReq = requests.has(id); favBtn?.classList.toggle("active", isFav); if (favBtn) { favBtn.textContent = isFav ? "♥" : "♡"; favBtn.setAttribute("aria-label", isFav ? "Видалити з обраного" : "Додати в обране"); } reqBtn?.classList.toggle("active", isReq); if (reqBtn) reqBtn.textContent = isReq ? "У заявці" : "До заявки"; }); } function renderDrawer() { const body = $("#requestDrawerItems"); if (!body) return; const items = read(REQUEST_KEY); body.innerHTML = items.length ? items.map(item => `
${escapeStore(item.title)}${escapeStore(item.price)}
`).join("") : `
Список порожній

Додайте товари з каталогу для прорахунку.

`; } function escapeStore(value) { return String(value ?? "") .replaceAll("&","&") .replaceAll("<","<") .replaceAll(">",">") .replaceAll('"',""") .replaceAll("'","'"); } function openDrawer() { const drawer = $("#requestDrawer"); const overlay = $("#requestDrawerOverlay"); if (!drawer || !overlay) return; renderDrawer(); drawer.classList.add("open"); drawer.setAttribute("aria-hidden", "false"); overlay.hidden = false; document.body.style.overflow = "hidden"; } function closeDrawer() { const drawer = $("#requestDrawer"); const overlay = $("#requestDrawerOverlay"); drawer?.classList.remove("open"); drawer?.setAttribute("aria-hidden", "true"); if (overlay) overlay.hidden = true; document.body.style.overflow = ""; } function applyFavoritesFilter() { const params = new URLSearchParams(location.search); if (params.get("favorites") !== "1") return; const favorites = new Set(read(FAVORITES_KEY).map(String)); const note = $("#favoritesNote"); if (note) note.hidden = false; $$(".store-product-card").forEach(card => { card.hidden = !favorites.has(String(card.dataset.productId || "")); }); const count = $$(".store-product-card").filter(card => !card.hidden).length; const counter = $("#catalogCount"); if (counter) counter.textContent = String(count); const empty = $("#catalogEmptyBox"); if (empty) empty.hidden = count > 0; } function prefillEstimateRequest() { if (!location.pathname.endsWith("estimate.html")) return; const items = read(REQUEST_KEY); if (!items.length) return; const textarea = $('textarea[name="comment"]'); if (!textarea || textarea.value.trim()) return; textarea.value = "Товари для прорахунку:\n" + items.map((item, i) => `${i + 1}. ${item.title} — ${item.price}`).join("\n"); } document.addEventListener("DOMContentLoaded", () => { updateHeaderCounts(); renderDrawer(); syncCards(); prefillEstimateRequest(); const grids = ["#catalogGrid", "#homeCatalogGrid"].map($).filter(Boolean); grids.forEach(grid => { new MutationObserver(() => { syncCards(); setTimeout(applyFavoritesFilter, 0); }).observe(grid, {childList:true}); }); setTimeout(applyFavoritesFilter, 250); $$(".store-request-open").forEach(btn => btn.addEventListener("click", openDrawer)); $("#requestDrawerClose")?.addEventListener("click", closeDrawer); $("#requestDrawerOverlay")?.addEventListener("click", closeDrawer); $("#clearRequestList")?.addEventListener("click", () => { write(REQUEST_KEY, []); updateHeaderCounts(); syncCards(); renderDrawer(); }); $("#requestCheckout")?.addEventListener("click", () => { sessionStorage.setItem("mps_request_checkout", "1"); }); document.addEventListener("click", event => { const favorite = event.target.closest("[data-favorite]"); if (favorite) { const card = favorite.closest(".store-product-card"); const id = String(card?.dataset.productId || favorite.dataset.favorite || ""); const current = read(FAVORITES_KEY).map(String); const next = current.includes(id) ? current.filter(item => item !== id) : [id, ...current]; write(FAVORITES_KEY, next); updateHeaderCounts(); syncCards(); applyFavoritesFilter(); return; } const request = event.target.closest("[data-request]"); if (request) { const card = request.closest(".store-product-card"); const product = cardData(card); const current = read(REQUEST_KEY); const exists = current.some(item => String(item.id) === product.id); const next = exists ? current.filter(item => String(item.id) !== product.id) : [product, ...current]; write(REQUEST_KEY, next); updateHeaderCounts(); syncCards(); renderDrawer(); if (!exists) openDrawer(); return; } const remove = event.target.closest("[data-request-remove]"); if (remove) { const id = String(remove.dataset.requestRemove); write(REQUEST_KEY, read(REQUEST_KEY).filter(item => String(item.id) !== id)); updateHeaderCounts(); syncCards(); renderDrawer(); } }); const filters = $("#catalogFilters"); $("#filterToggle")?.addEventListener("click", () => filters?.classList.add("open")); $("#filterClose")?.addEventListener("click", () => filters?.classList.remove("open")); $("#applyCatalogFilters")?.addEventListener("click", () => filters?.classList.remove("open")); $("#resetCatalogFilters")?.addEventListener("click", () => { ["search","category","materialFilter","sizeFilter","priceFilter"].forEach(id => { const el = $("#" + id); if (!el) return; if (el.tagName === "SELECT") el.selectedIndex = 0; else el.value = ""; el.dispatchEvent(new Event(el.tagName === "SELECT" ? "change" : "input", {bubbles:true})); }); filters?.classList.remove("open"); }); const params = new URLSearchParams(location.search); const category = params.get("category"); const query = params.get("q"); if (category && $("#category")) { $("#category").value = category; $("#category").dispatchEvent(new Event("change", {bubbles:true})); } if (query && $("#search")) { $("#search").value = query; $("#search").dispatchEvent(new Event("input", {bubbles:true})); } const sort = $("#catalogSort"); const grid = $("#catalogGrid"); function sortCards() { if (!sort || !grid) return; const cards = $$(".store-product-card", grid); cards.sort((a,b) => { const pa = Number(a.dataset.price || 0); const pb = Number(b.dataset.price || 0); if (sort.value === "price-asc") return pa - pb; if (sort.value === "price-desc") return pb - pa; return 0; }).forEach(card => grid.appendChild(card)); } sort?.addEventListener("change", sortCards); }); })(); /* Catalog breadcrumbs */ (function(){ const labels = { canopy: "Навіси", frame: "Каркаси", beam: "Ферми та балки", stairs: "Сходи та перила", fence: "Ворота й огорожі", other: "Інше" }; document.addEventListener("DOMContentLoaded", () => { const params = new URLSearchParams(location.search); const category = params.get("category"); const wrap = document.querySelector("#catalogBreadcrumbCategoryWrap"); const label = document.querySelector("#catalogBreadcrumbCategory"); if (category && labels[category] && wrap && label) { label.textContent = labels[category]; wrap.hidden = false; } }); })(); /* FAQ accordion */ (function(){ document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll(".store-faq article > button").forEach(button => { button.addEventListener("click", () => { const article = button.closest("article"); const content = article?.querySelector(":scope > div"); if (!content) return; const open = button.getAttribute("aria-expanded") === "true"; button.setAttribute("aria-expanded", open ? "false" : "true"); content.hidden = open; const icon = button.querySelector("b"); if (icon) icon.textContent = open ? "+" : "−"; }); }); }); })(); /* User account status in the public header */ (function(){ document.addEventListener("DOMContentLoaded", async () => { const desktopLink = document.querySelector("#userAccountLink"); const navLink = document.querySelector("#userAccountNavLink"); if (!desktopLink && !navLink) return; try { const response = await fetch("api/user-status.php", { credentials: "same-origin", headers: {"Accept":"application/json"} }); if (!response.ok) return; const data = await response.json(); const loggedIn = Boolean(data.logged_in); const url = loggedIn ? (data.dashboard_url || "dashboard.php") : (data.login_url || "login.php"); const label = loggedIn ? "Кабінет" : "Увійти"; if (desktopLink) { desktopLink.href = url; const text = desktopLink.querySelector(".store-account-label"); if (text) text.textContent = label; desktopLink.title = loggedIn && data.name ? `Кабінет: ${data.name}` : label; } if (navLink) { navLink.href = url; navLink.textContent = label; } } catch (_) { // The public site remains usable even if the auth status endpoint is unavailable. } }); })();