feat: visible close button, product name above image, SKU + descriptive name in list

- Panel close button: high-contrast rgba background, 38px, bold ×, shadow
- Detail view: full product name (SKU + description) shown above image
- List view: NAME column shows part_number on line 1, descriptive name line 2
- txDescName() helper builds name from description field or constructs from specs
This commit is contained in:
Rene Fichtmueller 2026-04-01 20:02:52 +02:00
parent 2b683dadfb
commit fe81c2d19d

View File

@ -413,14 +413,14 @@
.panel::-webkit-scrollbar { width: 4px; } .panel::-webkit-scrollbar { width: 4px; }
.panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } .panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.panel-close { .panel-close {
position: absolute; top: 1rem; right: 1rem; position: absolute; top: 0.75rem; right: 0.75rem;
background: var(--surface2); border: 1px solid var(--border); background: rgba(255,255,255,0.14); border: 1.5px solid rgba(255,255,255,0.28);
color: var(--text-dim); width: 32px; height: 32px; border-radius: var(--radius-md); color: rgba(255,255,255,0.92); width: 38px; height: 38px; border-radius: 8px;
cursor: pointer; font-size: 1.1rem; cursor: pointer; font-size: 1.3rem; font-weight: 700;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
transition: all 0.2s; transition: all 0.2s; box-shadow: 0 2px 10px rgba(0,0,0,0.35); z-index: 10;
} }
.panel-close:hover { background: var(--accent); color: #fff; border-color: var(--accent); } .panel-close:hover { background: var(--accent); color: #fff; border-color: var(--accent); box-shadow: 0 2px 12px rgba(255,102,0,0.4); }
.panel-title { .panel-title {
font-family: var(--font-heading); font-family: var(--font-heading);
font-size: 1.3rem; font-weight: 400; color: var(--text-bright); font-size: 1.3rem; font-weight: 400; color: var(--text-bright);
@ -959,6 +959,24 @@ function buildDOM(parent, html) {
parent.appendChild(t.content.cloneNode(true)); parent.appendChild(t.content.cloneNode(true));
} }
// Build a human-readable descriptive product name from available fields
function txDescName(t) {
// Use description field if populated and meaningful (not just the SKU)
if (t.description && t.description.length > 10 && t.description !== t.standard_name && t.description !== t.slug) {
return t.description;
}
// Construct from specs
var parts = [];
if (t.speed) parts.push(t.speed);
if (t.form_factor) parts.push(t.form_factor);
if (t.reach_label) parts.push(t.reach_label);
else if (t.reach_meters) parts.push(t.reach_meters + ' km');
if (t.wavelengths) parts.push('\u03bb' + t.wavelengths); // λ
if (t.connector) parts.push(t.connector);
if (t.fiber_type) parts.push(t.fiber_type);
return parts.join(', ') || '';
}
function openPanel(html) { function openPanel(html) {
var p = el('detail-panel'); var p = el('detail-panel');
buildDOM(el('panel-content'), html); buildDOM(el('panel-content'), html);
@ -1750,7 +1768,9 @@ function searchTransceivers() {
buildDOM(el('tx-table'), lastTxData.map(function(t) { buildDOM(el('tx-table'), lastTxData.map(function(t) {
return '<tr class="clickable" data-txid="' + esc(t.id) + '">' return '<tr class="clickable" data-txid="' + esc(t.id) + '">'
+ '<td onclick="event.stopPropagation()"><input type="checkbox" class="compare-cb" data-id="' + esc(t.id) + '"></td>' + '<td onclick="event.stopPropagation()"><input type="checkbox" class="compare-cb" data-id="' + esc(t.id) + '"></td>'
+ '<td style="font-weight:600;color:var(--text-bright)">' + esc(t.standard_name || t.slug) + '</td>' + '<td><div style="font-weight:600;color:var(--text-bright);line-height:1.2">' + esc(t.part_number || t.standard_name || t.slug) + '</div>'
+ (txDescName(t) && txDescName(t) !== (t.part_number || t.standard_name || t.slug) ? '<div style="font-size:0.72rem;color:var(--text-dim);margin-top:0.15rem;line-height:1.3;max-width:40ch;white-space:normal">' + esc(txDescName(t)) + '</div>' : '')
+ '</td>'
+ '<td>' + esc(t.vendor_name || '—') + '</td>' + '<td>' + esc(t.vendor_name || '—') + '</td>'
+ '<td><span class="b b-blue">' + esc(t.form_factor) + '</span></td>' + '<td><span class="b b-blue">' + esc(t.form_factor) + '</span></td>'
+ '<td class="mono">' + esc(t.speed) + '</td>' + '<td class="mono">' + esc(t.speed) + '</td>'
@ -1775,6 +1795,16 @@ async function openTxDetail(id) {
var t = data.data || data.transceiver || data; var t = data.data || data.transceiver || data;
var h = ''; var h = '';
// Product name header — above the image
var descName = txDescName(t);
var sku = t.part_number || t.standard_name || t.slug;
h += '<div style="margin-bottom:0.9rem">';
h += '<div style="font-size:1.1rem;font-weight:700;color:var(--text-bright);letter-spacing:-0.01em;line-height:1.2">' + esc(sku) + '</div>';
if (descName && descName !== sku) {
h += '<div style="font-size:0.82rem;color:var(--text-dim);margin-top:0.3rem;line-height:1.4">' + esc(descName) + '</div>';
}
h += '</div>';
// Image section // Image section
var hasRealImage = t.image_url || (t.vendor_name === 'FLEXOPTIX' && FF_REFERENCE_IMAGES[t.form_factor]); var hasRealImage = t.image_url || (t.vendor_name === 'FLEXOPTIX' && FF_REFERENCE_IMAGES[t.form_factor]);
h += '<div class="tx-image-box ' + (hasRealImage ? 'has-photo' : 'has-svg') + '">'; h += '<div class="tx-image-box ' + (hasRealImage ? 'has-photo' : 'has-svg') + '">';