UI: стиль главной страницы, новый лого и favicon

- Заменён логотип на новый (MONT Group of companies)
- Favicon взят с stend.4mont.ru, перенесён в static/
- Лого фиксировано в левом верхнем углу (position:fixed), уменьшено на 30%
- Добавлена кнопка «Инфраструктурный полигон МОНТ» → stend.4mont.ru
- Градиентный текст h1, анимация fadeUp при загрузке
- Акцент-полоски в заголовках карточек, hover-эффекты на row-card
- Статистика переведена на таблетки (stat-pill)
- Счётчики-бейджи рядом с «Вендоры» и «Категории»
- Кастомный скроллбар в панелях чипов
- Улучшены теги продуктов, кнопка сброса фильтров
This commit is contained in:
2026-05-12 12:20:38 +03:00
parent 3a5ce8a6c5
commit 1747c31ba3
6 changed files with 448 additions and 36 deletions
+91 -3
View File
@@ -17,6 +17,11 @@
clearBtn: document.getElementById("clearBtn"),
modeInfra: document.getElementById("modeInfra"),
modeIb: document.getElementById("modeIb"),
resultSection: document.querySelector(".result"),
filtersSection: document.querySelector(".board"),
activeFilters: document.getElementById("activeFilters"),
vendorBadge: document.getElementById("vendorBadge"),
categoryBadge: document.getElementById("categoryBadge"),
};
let clickAudioCtx = null;
@@ -130,9 +135,12 @@
else if (!allowedVendors.has(vendor.id)) node.classList.add("dim");
node.textContent = vendor.name;
node.addEventListener("click", () => {
if (state.selectedVendors.has(vendor.id)) state.selectedVendors.delete(vendor.id);
const wasSelected = state.selectedVendors.has(vendor.id);
if (wasSelected) state.selectedVendors.delete(vendor.id);
else state.selectedVendors.add(vendor.id);
render();
if (wasSelected) scrollAfterDeselect();
else scrollToResultsSmooth();
});
el.vendorList.appendChild(node);
}
@@ -148,14 +156,20 @@
else if (!allowedCategories.has(category.id)) node.classList.add("dim");
node.textContent = category.name;
node.addEventListener("click", () => {
if (state.selectedCategories.has(category.id)) state.selectedCategories.delete(category.id);
const wasSelected = state.selectedCategories.has(category.id);
if (wasSelected) state.selectedCategories.delete(category.id);
else state.selectedCategories.add(category.id);
render();
if (wasSelected) scrollAfterDeselect();
else scrollToResultsSmooth();
});
el.categoryList.appendChild(node);
}
el.stats.textContent = `Вендоров: ${allowedVendors.size}/${state.data.vendors.length} | Категорий: ${allowedCategories.size}/${state.data.categories.length} | Продуктов: ${visibleSets().allowedProducts.size}/${state.data.products.length}`;
const ps = visibleSets().allowedProducts.size;
el.stats.innerHTML = `<span class="stat-pill">Вендоры <b>${allowedVendors.size}</b>&thinsp;/&thinsp;${state.data.vendors.length}</span><span class="stat-pill">Категории <b>${allowedCategories.size}</b>&thinsp;/&thinsp;${state.data.categories.length}</span><span class="stat-pill">Продукты <b>${ps}</b>&thinsp;/&thinsp;${state.data.products.length}</span>`;
if (el.vendorBadge) el.vendorBadge.textContent = allowedVendors.size;
if (el.categoryBadge) el.categoryBadge.textContent = allowedCategories.size;
}
function renderResults() {
@@ -213,9 +227,82 @@
el.modeIb.classList.toggle("active", state.scope === "ib");
document.body.classList.toggle("scope-ib", state.scope === "ib");
renderChips();
renderActiveFilters();
renderResults();
}
function scrollToResultsSmooth() {
if (!el.resultSection) return;
const top = Math.max(el.resultSection.getBoundingClientRect().top + window.scrollY - 10, 0);
window.scrollTo({ top, behavior: "smooth" });
}
function scrollToFiltersSmooth() {
if (!el.filtersSection) return;
const top = Math.max(el.filtersSection.getBoundingClientRect().top + window.scrollY - 8, 0);
window.scrollTo({ top, behavior: "smooth" });
}
function scrollToTopSmooth() {
window.scrollTo({ top: 0, behavior: "smooth" });
}
function scrollAfterDeselect() {
if (window.innerWidth > 980) {
scrollToTopSmooth();
return;
}
scrollToFiltersSmooth();
}
function renderActiveFilters() {
if (!el.activeFilters) return;
el.activeFilters.innerHTML = "";
const vendorsById = new Map(state.data.vendors.map(v => [v.id, v.name]));
const categoriesById = new Map(state.data.categories.map(c => [c.id, c.name]));
const items = [];
for (const id of state.selectedVendors) {
const name = vendorsById.get(id);
if (name) items.push({ kind: "Вендор", name, id, type: "vendor" });
}
for (const id of state.selectedCategories) {
const name = categoriesById.get(id);
if (name) items.push({ kind: "Категория", name, id, type: "category" });
}
if (items.length === 0) {
el.activeFilters.style.display = "none";
return;
}
el.activeFilters.style.display = "flex";
for (const item of items) {
const node = document.createElement("div");
node.className = "active-filter";
const text = document.createElement("span");
text.innerHTML = `<span class="kind">${item.kind}:</span> ${item.name}`;
node.appendChild(text);
const remove = document.createElement("button");
remove.className = "remove";
remove.type = "button";
remove.setAttribute("aria-label", `Убрать фильтр ${item.name}`);
remove.textContent = "×";
remove.addEventListener("click", () => {
if (item.type === "vendor") state.selectedVendors.delete(item.id);
else state.selectedCategories.delete(item.id);
render();
scrollToFiltersSmooth();
});
node.appendChild(remove);
el.activeFilters.appendChild(node);
}
}
async function loadScopeData(scope) {
const res = await fetch(`/api/data?scope=${encodeURIComponent(scope)}`);
state.data = await res.json();
@@ -258,6 +345,7 @@
el.vendorSearch.value = "";
el.categorySearch.value = "";
render();
scrollAfterDeselect();
});
el.modeInfra.addEventListener("click", () => {