feat: clickable tier badges with associate detail popup

Badge is now a button — clicking it opens a popup listing all
matching associates with their tier description (flight logs /
inner circle / named in files), category, and match type.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alex-GilbertandClaude Opus 4.6 committed 2026-02-16 09:33:27 -06:00
1 parent 921fafc828
commit 368df1a35b
2 files changed
+151 -11

No files matched your search

+63 -8
View File
@@ -909,6 +909,56 @@ function tierLabel(tier) {
return "T3";
}
function tierDescription(tier) {
if (tier === 1) return "Flight logs — traveled with Epstein";
if (tier === 2) return "Inner circle — staff, financial, or frequently named";
return "Named in Epstein case files";
}
function showAssocDetail(assocMatches, anchorEl) {
// Remove any existing popup
const old = document.getElementById("assoc-detail");
if (old) old.remove();
const popup = document.createElement("div");
popup.id = "assoc-detail";
let html = '<div class="assoc-detail-header">Possible associates<button class="assoc-detail-close">X</button></div>';
html += '<div class="assoc-detail-list">';
for (const m of assocMatches) {
const cls = tierBadgeClass(m.tier);
html += `<div class="assoc-detail-item">
<span class="assoc-badge ${cls}">${tierLabel(m.tier)}</span>
<div class="assoc-detail-info">
<div class="assoc-detail-name">${escapeHtml(m.personName)}</div>
<div class="assoc-detail-meta">${escapeHtml(tierDescription(m.tier))} · ${escapeHtml(m.category)} · matched on ${escapeHtml(m.matchType)}</div>
</div>
</div>`;
}
html += '</div>';
popup.innerHTML = html;
// Position near the badge
solvePanel.appendChild(popup);
// Close handlers
popup.querySelector(".assoc-detail-close").addEventListener("click", (e) => {
e.stopPropagation();
popup.remove();
});
// Close on outside click
const closeOnOutside = (e) => {
if (!popup.contains(e.target)) {
popup.remove();
document.removeEventListener("click", closeOnOutside, true);
}
};
setTimeout(() => document.addEventListener("click", closeOnOutside, true), 0);
}
solveStart.addEventListener("click", startSolve);
solveStop.addEventListener("click", stopSolve);
@@ -1020,18 +1070,23 @@ function handleSolveEvent(data, gapIdx) {
div.dataset.assocScore = topMatch.score;
}
let badgeHtml = "";
if (topMatch) {
const cls = tierBadgeClass(topMatch.tier);
const tooltip = `${topMatch.personName} (${topMatch.category}) — ${topMatch.matchType} match`;
badgeHtml = `<span class="assoc-badge ${cls}" title="${escapeHtml(tooltip)}">${tierLabel(topMatch.tier)}</span>`;
}
div.innerHTML = `
${badgeHtml}
<span class="result-text">${escapeHtml(data.text)}</span>
<span class="result-error">${data.error_px.toFixed(1)}px ${data.source || ""}</span>
`;
if (assocMatches.length > 0) {
const badge = document.createElement("button");
badge.className = `assoc-badge ${tierBadgeClass(topMatch.tier)}`;
badge.textContent = tierLabel(topMatch.tier);
badge.title = "Click for details";
badge.addEventListener("click", (e) => {
e.stopPropagation();
showAssocDetail(assocMatches, badge);
});
div.prepend(badge);
}
div.addEventListener("click", () => {
const override = ensureOverride();
override.gapPreviews[gapIdx] = data.text;
+88 -3
View File
@@ -487,16 +487,23 @@ main {
font-size: 0.75rem;
}
/* Associate badges */
/* Associate badges (clickable) */
.assoc-badge {
flex-shrink: 0;
padding: 1px 5px;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.6rem;
font-weight: 700;
letter-spacing: 0.05em;
cursor: default;
cursor: pointer;
margin-right: 4px;
border: 1px solid transparent;
transition: filter 0.15s, border-color 0.15s;
}
.assoc-badge:hover {
filter: brightness(1.3);
border-color: #fff;
}
.assoc-badge.tier-1 {
@@ -513,3 +520,81 @@ main {
background: #f9a825;
color: #000;
}
/* Associate detail popup */
#assoc-detail {
position: absolute;
top: 80px;
left: 12px;
right: 12px;
max-height: 60%;
background: rgba(16, 16, 32, 0.97);
border: 1px solid #444;
border-radius: 8px;
padding: 10px;
z-index: 40;
overflow-y: auto;
backdrop-filter: blur(10px);
}
.assoc-detail-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
color: #00d474;
margin-bottom: 8px;
font-size: 0.85rem;
}
.assoc-detail-close {
background: #0f3460;
color: #e0e0e0;
border: 1px solid #1a3a6e;
border-radius: 4px;
width: 1.5rem;
height: 1.5rem;
cursor: pointer;
font-size: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
}
.assoc-detail-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.assoc-detail-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 8px;
background: rgba(255, 255, 255, 0.04);
border-radius: 4px;
}
.assoc-detail-item .assoc-badge {
margin-top: 2px;
cursor: default;
pointer-events: none;
}
.assoc-detail-info {
flex: 1;
min-width: 0;
}
.assoc-detail-name {
color: #e0e0e0;
font-weight: 600;
font-size: 0.85rem;
}
.assoc-detail-meta {
color: #888;
font-size: 0.7rem;
margin-top: 2px;
}