Task Type: feature
Layer: Atlas
Priority: P88
Estimated effort: 2–3h
File: api.py — wiki page renderer (around line 36340)
Replace the current thin citation tooltip (plain title attribute showing authors (year)) with a rich hover popover that shows the full citation context: title, claim being supported, key excerpt, and figure reference.
In api.py the wiki citation tooltip is built at render time:
const tip = (r.authors || '') + ' (' + (r.year || '') + ')';
// stored as data-ref-text on the <a> elementThe mouseover handler (~line 36340) reads data-ref-text and shows a plain fixed <div> with just that string.
<div id="cite-popover" style="
position:fixed; background:#1a1a2e; color:#e0e0e0;
border:1px solid rgba(79,195,247,0.35); border-radius:8px;
padding:0.75rem 1rem; font-size:0.82rem; max-width:380px;
z-index:10000; pointer-events:none;
box-shadow:0 6px 20px rgba(0,0,0,0.5); line-height:1.5;
transition:opacity 0.12s ease;
">
<div style="font-weight:600;color:#4fc3f7;margin-bottom:0.3rem">
[N] Author et al. (Year) · Journal
</div>
<div style="color:#e0e0e0;font-style:italic;margin-bottom:0.4rem;font-size:0.8rem">
Title of the paper
</div>
<!-- claim line — shown only if refs[key].claim exists -->
<div style="background:rgba(129,199,132,0.1);border-left:2px solid #81c784;
padding:0.3rem 0.5rem;margin-bottom:0.4rem;font-size:0.78rem;color:#c8e6c9">
↳ Claim: {claim}
</div>
<!-- excerpt — shown only if refs[key].excerpt exists -->
<div style="color:#aaa;font-size:0.75rem;font-style:italic;margin-bottom:0.4rem">
"{excerpt}" {figure_ref}
</div>
<!-- footer with link -->
<div style="display:flex;justify-content:flex-end;margin-top:0.3rem">
<a href="{pubmed_url}" target="_blank" style="color:#4fc3f7;font-size:0.75rem">
PubMed →
</a>
</div>
</div>.ref-link (debounced — avoids flash on quick passes)pointer-events:auto when cursor is on the popover (so user can click the PubMed link)claim/excerpt are absent — shows just title + journalReplace the current ~20-line mouseover handler in api.py with:
(function() {
const popover = document.createElement('div');
popover.id = 'cite-popover';
popover.style.cssText = 'position:fixed;display:none;background:#1a1a2e;color:#e0e0e0;' +
'border:1px solid rgba(79,195,247,0.35);border-radius:8px;padding:0.75rem 1rem;' +
'font-size:0.82rem;max-width:380px;z-index:10000;pointer-events:none;' +
'box-shadow:0 6px 20px rgba(0,0,0,0.5);line-height:1.5;opacity:0;' +
'transition:opacity 0.12s ease;';
document.body.appendChild(popover);
let showTimer = null, hideTimer = null, currentKey = null;
function showPopover(a, key) {
if (key === currentKey) return;
currentKey = key;
const r = refs[key] || {};
const num = a.textContent.replace(/[\[\]]/g,'');
const url = r.pmid ? 'https://pubmed.ncbi.nlm.nih.gov/' + r.pmid + '/'
: r.doi ? 'https://doi.org/' + r.doi : '';
let html = '<div style="font-weight:600;color:#4fc3f7;margin-bottom:0.3rem">[' + num + '] ';
if (r.authors) html += r.authors.split(',')[0] + ' et al.';
if (r.year) html += ' (' + r.year + ')';
if (r.journal) html += ' · <span style="color:#888;font-weight:400">' + r.journal + '</span>';
html += '</div>';
if (r.title) {
const clean = r.title.replace(/^["']|["']$/g,'');
html += '<div style="color:#e0e0e0;font-style:italic;margin-bottom:0.4rem;font-size:0.8rem">' + clean + '</div>';
}
if (r.claim) {
html += '<div style="background:rgba(129,199,132,0.1);border-left:2px solid #81c784;' +
'padding:0.3rem 0.5rem;margin-bottom:0.4rem;font-size:0.78rem;color:#c8e6c9">' +
'↳ ' + r.claim + '</div>';
}
if (r.excerpt) {
html += '<div style="color:#aaa;font-size:0.75rem;font-style:italic;margin-bottom:0.4rem">“' +
r.excerpt + '”';
if (r.figure_ref) html += ' <span style="color:#ffd54f">— ' + r.figure_ref + '</span>';
html += '</div>';
}
if (url) {
html += '<div style="display:flex;justify-content:flex-end;margin-top:0.3rem">' +
'<a href="' + url + '" target="_blank" rel="noopener" ' +
'style="color:#4fc3f7;font-size:0.75rem;pointer-events:auto;text-decoration:none">' +
(r.pmid ? 'PubMed' : 'DOI') + ' →</a></div>';
}
popover.innerHTML = html;
popover.style.pointerEvents = url ? 'auto' : 'none';
const rect = a.getBoundingClientRect();
popover.style.display = 'block';
const pw = popover.offsetWidth;
const left = Math.max(8, Math.min(rect.left, window.innerWidth - pw - 8));
const top = rect.bottom + 6;
popover.style.left = left + 'px';
popover.style.top = (top + window.scrollY) + 'px';
popover.style.position = 'absolute';
popover.style.opacity = '1';
}
function hidePopover() {
currentKey = null;
popover.style.opacity = '0';
setTimeout(() => { popover.style.display = 'none'; }, 120);
}
contentEl.addEventListener('mouseover', function(e) {
const a = e.target.closest('a.ref-link');
if (!a) return;
const key = a.getAttribute('data-ref-key');
if (!key || !refs[key]) return;
clearTimeout(hideTimer);
showTimer = setTimeout(() => showPopover(a, key), 150);
});
contentEl.addEventListener('mouseout', function(e) {
const a = e.target.closest('a.ref-link');
if (!a) return;
clearTimeout(showTimer);
hideTimer = setTimeout(hidePopover, 300);
});
popover.addEventListener('mouseover', () => clearTimeout(hideTimer));
popover.addEventListener('mouseout', () => { hideTimer = setTimeout(hidePopover, 300); });
})();The citation superscript builder must also set data-ref-key on the <a> element (in addition to existing data-ref-text):
// In the [@key] replacement block:
return '<sup class="cite-sup" ...>'
+ '<a href="#ref-' + num + '" class="ref-link" '
+ 'data-ref-key="' + key + '" ' // <-- ADD THIS
+ 'data-ref-text="' + tip + '" '
+ 'title="' + tip + '">'
+ '[' + num + ']</a></sup>';After implementing:
/wiki/genes-foxp1 — hover over any [@bacon2020] citation → rich popover appearspointer-events was set conditionally (url ? 'auto' : 'none') which meant the popover body had pointer-events:none when there was no DOI/PMID, making the PubMed link inside unclickable even when presentpopover.style.pointerEvents = 'auto' so the popover is always interactive/api/status returns 200, service healthy3599f08f[@key] → [N] rendering — only change the hover behaviorrefs_json schema enrichment (claim, excerpt, figure_ref) is a separate task; the popover should degrade gracefully when those fields are absentpointer-events:auto on the popover allows clicking the PubMed link