diff --git a/static/css/admin.css b/static/css/admin.css index e5caecc..71da89d 100644 --- a/static/css/admin.css +++ b/static/css/admin.css @@ -75,3 +75,88 @@ th:first-child { z-index: 3; } td input { transform: scale(1.05); } .matrix-tip { margin:0 0 6px; font-size:12px; color:#37507d; } + + @media (max-width: 980px) { + .wrap { + width: calc(100% - 16px); + margin: 8px auto 16px; + } + .top { + flex-direction: column; + align-items: stretch; + padding: 12px; + } + .top > div:last-child { + width: 100%; + display: grid !important; + grid-template-columns: 1fr 1fr; + } + .top > div:last-child a, + .top > div:last-child form, + .top > div:last-child button { + width: 100%; + } + .scope-switch { + flex-wrap: wrap; + } + .scope-chip { + flex: 1 1 auto; + text-align: center; + } + .grid { + grid-template-columns: 1fr; + } + .lists { + grid-template-columns: 1fr; + } + .list-box { + max-height: 300px; + } + .list-item { + align-items: flex-start; + flex-wrap: wrap; + } + .matrix-wrap { + padding: 8px; + } + .matrix-scroll { + max-height: 62vh; + } + th, td { + font-size: 11px; + padding: 5px; + } + th:first-child, + td:first-child { + min-width: 170px; + } + input[type="text"], + select, + button { + min-height: 40px; + } + } + + @media (max-width: 600px) { + .top > div:last-child { + grid-template-columns: 1fr; + } + .inline-product { + grid-template-columns: 1fr; + } + .matrix-h-scroll { + height: 24px; + } + .matrix-scroll::-webkit-scrollbar, + .matrix-h-scroll::-webkit-scrollbar { + height: 18px; + width: 12px; + } + th:first-child, + td:first-child { + min-width: 145px; + } + .matrix-tip { + font-size: 11px; + } + } diff --git a/static/css/index.css b/static/css/index.css index 491b091..5f2347a 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -283,6 +283,53 @@ font-size: 17px; color: #1f3f77; } + .active-filters { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 0 0 12px; + padding: 9px; + border-radius: 12px; + border: 1px solid #c9dafd; + background: linear-gradient(145deg, #e8f0ff, #dbe8ff); + box-shadow: inset 0 1px 0 rgba(255,255,255,.65); + } + .active-filter { + display: inline-flex; + align-items: center; + gap: 7px; + border-radius: 999px; + padding: 6px 8px 6px 10px; + border: 1px solid #cfe0ff; + background: linear-gradient(145deg, #f8fbff, #edf4ff); + color: #21457f; + font-size: 12px; + font-weight: 700; + } + .active-filter .kind { + opacity: .7; + font-weight: 600; + letter-spacing: .2px; + } + .active-filter .remove { + display: inline-grid; + place-items: center; + width: 20px; + height: 20px; + border: 0; + border-radius: 999px; + background: #dbe9ff; + color: #1f4a8f; + font-size: 14px; + line-height: 1; + cursor: pointer; + padding: 0; + transition: .16s ease; + } + .active-filter .remove:hover { + background: #c4dbff; + transform: scale(1.05); + } .rows { display: grid; @@ -347,3 +394,99 @@ .credit .name { font-size: 8px; } .credit a { font-size: 6px; } } + + @media (max-width: 768px) { + .wrap { + width: calc(100% - 16px); + margin: 10px auto 18px; + } + .brand-strip { + margin-bottom: 8px; + } + .brand-logo { + width: clamp(132px, 42vw, 190px); + } + .hero { + padding: 16px 14px 20px; + border-radius: 16px; + } + .hero h1 { + font-size: clamp(22px, 7vw, 32px); + line-height: 1.06; + } + .hero p { + font-size: 14px; + } + .mode-switch { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr; + } + .mode-btn { + width: 100%; + min-height: 42px; + } + .card { + min-height: 0; + padding: 12px; + } + .chip-grid { + max-height: 260px; + } + .chip { + font-size: 12px; + padding: 8px 10px; + } + .footer-bar { + flex-direction: column; + align-items: stretch; + gap: 8px; + background: rgba(255, 255, 255, .78); + border: 1px solid #dbe7ff; + border-radius: 12px; + padding: 10px; + } + button.action { + width: 100%; + min-height: 42px; + } + .result { + margin-top: 12px; + padding: 12px; + } + .active-filters { + margin-bottom: 10px; + } + .active-filter { + width: 100%; + justify-content: space-between; + } + .result-head h3 { + font-size: 15px; + } + .rows { + grid-template-columns: 1fr; + gap: 10px; + } + .credit { + right: 8px; + bottom: 8px; + background: rgba(255, 255, 255, .86); + border: 1px solid #dbe6ff; + border-radius: 10px; + padding: 4px 6px; + } + } + + @media (max-width: 420px) { + .hero::before, + .hero::after { + opacity: .72; + } + .search { + padding: 10px 11px; + } + .tag { + font-size: 11px; + } + } diff --git a/static/js/index.js b/static/js/index.js index c3118b8..f23a744 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -17,6 +17,9 @@ clearBtn: document.getElementById("clearBtn"), modeInfra: document.getElementById("modeInfra"), modeIb: document.getElementById("modeIb"), + resultSection: document.querySelector(".result"), + filtersSection: document.querySelector(".board"), + activeFilters: document.getElementById("activeFilters"), }; let clickAudioCtx = null; @@ -130,9 +133,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,9 +154,12 @@ 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); } @@ -213,9 +222,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 = `${item.kind}: ${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 +340,7 @@ el.vendorSearch.value = ""; el.categorySearch.value = ""; render(); + scrollAfterDeselect(); }); el.modeInfra.addEventListener("click", () => { diff --git a/templates/index.html b/templates/index.html index 3790483..c425d78 100644 --- a/templates/index.html +++ b/templates/index.html @@ -54,6 +54,7 @@

Вендоры и продукты (после фильтрации)

+