Add Strong's tooltip on hover for word study links

Hovering over the Greek/Hebrew term in word study sidenotes now shows
a tooltip with the Strong's card: original word, transliteration,
Strong's number, and definition. Uses same positioning logic as verse
tooltips.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-05 07:54:29 -05:00
parent 990ef2e52d
commit 5a7a275b2b
3 changed files with 178 additions and 1 deletions
+1 -1
View File
@@ -160,7 +160,7 @@ def inject_word_markers(text, word_studies, verse_num):
# Link the original term to Strong's page if we have a Strong's number
strongs = study.get('strongs')
if strongs:
term_html = f'<a href="/strongs/{strongs}">{study["term"]}</a>'
term_html = f'<a href="/strongs/{strongs}" class="strongs-link" data-strongs="{strongs}">{study["term"]}</a>'
else:
term_html = study["term"]
# Wrap details (translit + note) in a span that's hidden by default
+107
View File
@@ -1619,6 +1619,113 @@ function showKeyboardHelp() {
});
})();
// Strong's tooltip functionality
(function() {
// Create tooltip element
var tooltip = document.createElement('div');
tooltip.className = 'strongs-tooltip';
document.body.appendChild(tooltip);
// Cache for fetched Strong's entries
var strongsCache = {};
// Fetch Strong's entry from API
async function fetchStrongsEntry(strongsNumber) {
if (strongsCache[strongsNumber]) {
return strongsCache[strongsNumber];
}
try {
var response = await fetch('/api/strongs/' + strongsNumber);
if (!response.ok) {
throw new Error('Not found');
}
var data = await response.json();
strongsCache[strongsNumber] = data;
return data;
} catch (e) {
return null;
}
}
// Show tooltip positioned relative to a link element
function showTooltip(data, linkElement) {
var isHebrew = data.strongs.startsWith('H');
var langClass = isHebrew ? 'hebrew' : 'greek';
tooltip.innerHTML =
'<div class="strongs-tooltip-word ' + langClass + '">' + data.word + '</div>' +
'<div class="strongs-tooltip-translit">' + data.transliteration + '</div>' +
'<div class="strongs-tooltip-number">' + data.strongs + '</div>' +
'<div class="strongs-tooltip-def">' + data.definition + '</div>';
// Position off-screen first to measure
tooltip.style.left = '-9999px';
tooltip.style.top = '-9999px';
tooltip.classList.add('show');
var tooltipWidth = tooltip.offsetWidth;
var tooltipHeight = tooltip.offsetHeight;
var linkRect = linkElement.getBoundingClientRect();
var padding = 10;
// Position below the link, centered
var x = linkRect.left + (linkRect.width / 2) - (tooltipWidth / 2);
var y = linkRect.bottom + 8 + window.scrollY;
// Keep within viewport
if (x + tooltipWidth > window.innerWidth - padding) {
x = window.innerWidth - tooltipWidth - padding;
}
if (x < padding) {
x = padding;
}
if (linkRect.bottom + 8 + tooltipHeight > window.innerHeight) {
y = linkRect.top - tooltipHeight - 8 + window.scrollY;
}
if (y < window.scrollY + padding) {
y = window.scrollY + padding;
}
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
}
function hideTooltip() {
tooltip.classList.remove('show');
}
// Event delegation for Strong's links
document.addEventListener('mouseover', function(e) {
var target = e.target;
if (!target.classList.contains('strongs-link')) return;
var strongsNumber = target.getAttribute('data-strongs');
if (!strongsNumber) return;
// Show loading state
tooltip.innerHTML = '<span style="color: var(--text-tertiary);">Loading...</span>';
var linkRect = target.getBoundingClientRect();
tooltip.style.left = linkRect.left + 'px';
tooltip.style.top = (linkRect.bottom + 8 + window.scrollY) + 'px';
tooltip.classList.add('show');
// Fetch and display
fetchStrongsEntry(strongsNumber).then(function(data) {
if (data && target.matches(':hover')) {
showTooltip(data, target);
} else if (!data) {
hideTooltip();
}
});
target.addEventListener('mouseleave', function() {
hideTooltip();
}, { once: true });
});
})();
// Site-wide verse linking
(function() {
function linkVerseReferences(element) {
+70
View File
@@ -301,6 +301,76 @@
}
}
/* Strong's tooltip styles */
.strongs-tooltip {
position: absolute;
background: var(--bg-color);
border: 1px solid var(--border-color-darker);
border-radius: 6px;
padding: 0.75rem 1rem;
min-width: 250px;
max-width: 350px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 9999;
font-size: 0.95rem;
line-height: 1.5;
color: var(--text-color);
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.strongs-tooltip.show {
opacity: 1;
}
.strongs-tooltip-word {
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.strongs-tooltip-word.hebrew {
font-family: 'SBL Hebrew', 'Ezra SIL', 'Times New Roman', serif;
direction: rtl;
}
.strongs-tooltip-word.greek {
font-family: 'SBL Greek', 'Gentium Plus', 'Times New Roman', serif;
}
.strongs-tooltip-translit {
font-style: italic;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.strongs-tooltip-number {
font-family: monospace;
font-size: 0.8rem;
color: var(--text-tertiary);
margin-bottom: 0.5rem;
}
.strongs-tooltip-def {
font-size: 0.9rem;
color: var(--text-color);
}
[data-theme="dark"] .strongs-tooltip {
background: #2a2a2a;
border-color: #444;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]) .strongs-tooltip {
background: #2a2a2a;
border-color: #444;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
}
/* Enhanced typography and spacing */
body {
counter-reset: sidenote-counter;