feat: add 41 categories, interactive tooltip, IB links to vendor page, vendor info bar

This commit is contained in:
2026-05-12 16:04:11 +03:00
parent 800965598c
commit 1bf9aeb749
3 changed files with 92 additions and 8 deletions
+44
View File
@@ -687,6 +687,7 @@
.vendor-tooltip.visible {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.vtt-logo {
width: 100%;
@@ -754,3 +755,46 @@
border: 1px solid #d0e0ff;
}
.vtt-link:hover { opacity: .85; }
/* Vendor info bar in results */
.vendor-info-bar {
display: flex;
align-items: flex-start;
gap: 18px;
background: linear-gradient(135deg, #f0f5ff 0%, #e8f0ff 100%);
border: 1px solid #c8d8f5;
border-radius: 16px;
padding: 16px 20px;
margin-bottom: 18px;
}
.vendor-info-bar + .vendor-info-bar { margin-top: -8px; }
.vib-logo {
flex-shrink: 0;
width: 100px;
height: 64px;
background: #fff;
border-radius: 10px;
border: 1px solid #dae6ff;
display: flex;
align-items: center;
justify-content: center;
padding: 8px 10px;
}
.vib-logo img { max-width: 100%; max-height: 48px; object-fit: contain; }
.vib-logo-text { font-size: 18px; font-weight: 800; color: #3978e0; }
.vib-info { flex: 1; min-width: 0; }
.vib-name { font-size: 15px; font-weight: 800; color: #1a3e79; margin: 0 0 5px; }
.vib-desc { font-size: 13px; color: #4a5d7a; line-height: 1.55; margin: 0 0 10px; }
.vib-links { display: flex; gap: 8px; flex-wrap: wrap; }
.vib-link {
display: inline-flex;
align-items: center;
font-size: 12px;
font-weight: 700;
padding: 4px 12px;
border-radius: 999px;
text-decoration: none;
transition: .15s ease;
}
.vib-link.mont { background: linear-gradient(135deg, #1f4ea3, #3978e0); color: #fff; }
.vib-link.site { background: #fff; color: #2a5aaa; border: 1px solid #c8d8f5; }
.vib-link:hover { opacity: .82; transform: translateY(-1px); }
+48 -8
View File
@@ -30,8 +30,12 @@
const tooltip = document.createElement('div');
tooltip.className = 'vendor-tooltip';
document.body.appendChild(tooltip);
tooltip.addEventListener('mouseenter', () => { overTooltip = true; clearTimeout(hideTimer); });
tooltip.addEventListener('mouseleave', () => { overTooltip = false; hideTooltip(); });
let tooltipTimer = null;
let hideTimer = null;
let overTooltip = false;
let vendorMap = {};
function buildVendorMap() {
@@ -82,8 +86,12 @@
tooltip.classList.add('visible');
}
function hideTooltip() {
tooltip.classList.remove('visible');
function hideTooltip(immediate) {
clearTimeout(hideTimer);
if (immediate) { tooltip.classList.remove('visible'); return; }
hideTimer = setTimeout(() => {
if (!overTooltip) tooltip.classList.remove('visible');
}, 120);
}
@@ -244,6 +252,8 @@
function renderResults() {
const { allowedCategories, allowedVendors, allowedProducts, productsByVendor, categoriesByProduct } = visibleSets();
const productsById = new Map(state.data.products.map(p => [p.id, p]));
const vendorsById = new Map(state.data.vendors.map(v => [v.id, v]));
const isIb = state.scope === 'ib';
const rows = [];
for (const vendor of state.data.vendors) {
@@ -257,7 +267,7 @@
});
const products = productIds.map(pId => productsById.get(pId)).filter(Boolean);
if (products.length === 0) continue;
rows.push({ vendor: vendor.name, products });
rows.push({ vendor, products });
}
el.resultRows.innerHTML = "";
@@ -266,21 +276,51 @@
return;
}
// Vendor info bars for selected vendors
if (state.selectedVendors.size > 0) {
for (const vid of state.selectedVendors) {
const v = vendorsById.get(vid);
if (!v) continue;
const bar = document.createElement('div');
bar.className = 'vendor-info-bar';
const logoInner = v.logo
? `<img src="/static/${v.logo}" alt="${v.name}" onerror="this.parentElement.innerHTML='<span class=vib-logo-text>${v.name.slice(0,2).toUpperCase()}</span>'">`
: `<span class="vib-logo-text">${v.name.slice(0,2).toUpperCase()}</span>`;
let linksHtml = '';
if (v.mont_page) linksHtml += `<a class="vib-link mont" href="${v.mont_page}" target="_blank" rel="noopener">MONT ↗</a>`;
if (v.website) linksHtml += `<a class="vib-link site" href="${v.website}" target="_blank" rel="noopener">Сайт ↗</a>`;
bar.innerHTML = `
<div class="vib-logo">${logoInner}</div>
<div class="vib-info">
<div class="vib-name">${v.name}</div>
${v.description ? `<div class="vib-desc">${v.description}</div>` : ''}
${linksHtml ? `<div class="vib-links">${linksHtml}</div>` : ''}
</div>`;
el.resultRows.appendChild(bar);
}
}
for (const row of rows) {
const card = document.createElement("article");
card.className = "row-card";
const title = document.createElement("strong");
title.textContent = row.vendor;
title.textContent = row.vendor.name;
card.appendChild(title);
const tags = document.createElement("div");
tags.className = "tags";
for (const product of row.products) {
const hasUrl = product.url && String(product.url).trim().length > 0;
const tag = document.createElement(hasUrl ? "a" : "span");
// IB scope: link to vendor's mont page; infra: link to product url
let url = '';
if (isIb) {
url = row.vendor.mont_page || '';
} else {
url = (product.url && String(product.url).trim()) || '';
}
const tag = document.createElement(url ? "a" : "span");
tag.className = "tag";
tag.textContent = product.name;
if (hasUrl) {
tag.href = product.url;
if (url) {
tag.href = url;
tag.target = "_blank";
tag.rel = "noopener noreferrer";
}