feat: add 41 categories, interactive tooltip, IB links to vendor page, vendor info bar
This commit is contained in:
@@ -687,6 +687,7 @@
|
|||||||
.vendor-tooltip.visible {
|
.vendor-tooltip.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0) scale(1);
|
transform: translateY(0) scale(1);
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.vtt-logo {
|
.vtt-logo {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -754,3 +755,46 @@
|
|||||||
border: 1px solid #d0e0ff;
|
border: 1px solid #d0e0ff;
|
||||||
}
|
}
|
||||||
.vtt-link:hover { opacity: .85; }
|
.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
@@ -30,8 +30,12 @@
|
|||||||
const tooltip = document.createElement('div');
|
const tooltip = document.createElement('div');
|
||||||
tooltip.className = 'vendor-tooltip';
|
tooltip.className = 'vendor-tooltip';
|
||||||
document.body.appendChild(tooltip);
|
document.body.appendChild(tooltip);
|
||||||
|
tooltip.addEventListener('mouseenter', () => { overTooltip = true; clearTimeout(hideTimer); });
|
||||||
|
tooltip.addEventListener('mouseleave', () => { overTooltip = false; hideTooltip(); });
|
||||||
|
|
||||||
let tooltipTimer = null;
|
let tooltipTimer = null;
|
||||||
|
let hideTimer = null;
|
||||||
|
let overTooltip = false;
|
||||||
let vendorMap = {};
|
let vendorMap = {};
|
||||||
|
|
||||||
function buildVendorMap() {
|
function buildVendorMap() {
|
||||||
@@ -82,8 +86,12 @@
|
|||||||
tooltip.classList.add('visible');
|
tooltip.classList.add('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideTooltip() {
|
function hideTooltip(immediate) {
|
||||||
tooltip.classList.remove('visible');
|
clearTimeout(hideTimer);
|
||||||
|
if (immediate) { tooltip.classList.remove('visible'); return; }
|
||||||
|
hideTimer = setTimeout(() => {
|
||||||
|
if (!overTooltip) tooltip.classList.remove('visible');
|
||||||
|
}, 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -244,6 +252,8 @@
|
|||||||
function renderResults() {
|
function renderResults() {
|
||||||
const { allowedCategories, allowedVendors, allowedProducts, productsByVendor, categoriesByProduct } = visibleSets();
|
const { allowedCategories, allowedVendors, allowedProducts, productsByVendor, categoriesByProduct } = visibleSets();
|
||||||
const productsById = new Map(state.data.products.map(p => [p.id, p]));
|
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 = [];
|
const rows = [];
|
||||||
for (const vendor of state.data.vendors) {
|
for (const vendor of state.data.vendors) {
|
||||||
@@ -257,7 +267,7 @@
|
|||||||
});
|
});
|
||||||
const products = productIds.map(pId => productsById.get(pId)).filter(Boolean);
|
const products = productIds.map(pId => productsById.get(pId)).filter(Boolean);
|
||||||
if (products.length === 0) continue;
|
if (products.length === 0) continue;
|
||||||
rows.push({ vendor: vendor.name, products });
|
rows.push({ vendor, products });
|
||||||
}
|
}
|
||||||
|
|
||||||
el.resultRows.innerHTML = "";
|
el.resultRows.innerHTML = "";
|
||||||
@@ -266,21 +276,51 @@
|
|||||||
return;
|
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) {
|
for (const row of rows) {
|
||||||
const card = document.createElement("article");
|
const card = document.createElement("article");
|
||||||
card.className = "row-card";
|
card.className = "row-card";
|
||||||
const title = document.createElement("strong");
|
const title = document.createElement("strong");
|
||||||
title.textContent = row.vendor;
|
title.textContent = row.vendor.name;
|
||||||
card.appendChild(title);
|
card.appendChild(title);
|
||||||
const tags = document.createElement("div");
|
const tags = document.createElement("div");
|
||||||
tags.className = "tags";
|
tags.className = "tags";
|
||||||
for (const product of row.products) {
|
for (const product of row.products) {
|
||||||
const hasUrl = product.url && String(product.url).trim().length > 0;
|
// IB scope: link to vendor's mont page; infra: link to product url
|
||||||
const tag = document.createElement(hasUrl ? "a" : "span");
|
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.className = "tag";
|
||||||
tag.textContent = product.name;
|
tag.textContent = product.name;
|
||||||
if (hasUrl) {
|
if (url) {
|
||||||
tag.href = product.url;
|
tag.href = url;
|
||||||
tag.target = "_blank";
|
tag.target = "_blank";
|
||||||
tag.rel = "noopener noreferrer";
|
tag.rel = "noopener noreferrer";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user