diff --git a/matrix.db b/matrix.db index 898c929..e5e5217 100644 Binary files a/matrix.db and b/matrix.db differ diff --git a/static/css/index.css b/static/css/index.css index 8a8d766..12825f7 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -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); } diff --git a/static/js/index.js b/static/js/index.js index 5d9ec59..30c2f64 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -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 + ? `${v.name}` + : `${v.name.slice(0,2).toUpperCase()}`; + let linksHtml = ''; + if (v.mont_page) linksHtml += `MONT ↗`; + if (v.website) linksHtml += `Сайт ↗`; + bar.innerHTML = ` + +
+
${v.name}
+ ${v.description ? `
${v.description}
` : ''} + ${linksHtml ? `` : ''} +
`; + 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"; }