diff --git a/unredact/static/app.js b/unredact/static/app.js
index fc2775f..dc81383 100644
--- a/unredact/static/app.js
+++ b/unredact/static/app.js
@@ -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 = '
';
+ html += '';
+
+ for (const m of assocMatches) {
+ const cls = tierBadgeClass(m.tier);
+ html += `
+
${tierLabel(m.tier)}
+
+
${escapeHtml(m.personName)}
+
${escapeHtml(tierDescription(m.tier))} · ${escapeHtml(m.category)} · matched on ${escapeHtml(m.matchType)}
+
+
`;
+ }
+
+ html += '
';
+ 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 = `${tierLabel(topMatch.tier)}`;
- }
-
div.innerHTML = `
- ${badgeHtml}
${escapeHtml(data.text)}
${data.error_px.toFixed(1)}px ${data.source || ""}
`;
+
+ 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;
diff --git a/unredact/static/style.css b/unredact/static/style.css
index a807e3d..0038e12 100644
--- a/unredact/static/style.css
+++ b/unredact/static/style.css
@@ -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;
+}