diff --git a/kjvstudy_org/jinja_filters.py b/kjvstudy_org/jinja_filters.py index f779963..7f871ec 100644 --- a/kjvstudy_org/jinja_filters.py +++ b/kjvstudy_org/jinja_filters.py @@ -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'{study["term"]}' + term_html = f'{study["term"]}' else: term_html = study["term"] # Wrap details (translit + note) in a span that's hidden by default diff --git a/kjvstudy_org/static/base.js b/kjvstudy_org/static/base.js index 8640503..9a26cca 100644 --- a/kjvstudy_org/static/base.js +++ b/kjvstudy_org/static/base.js @@ -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 = + '
' + data.word + '
' + + '
' + data.transliteration + '
' + + '
' + data.strongs + '
' + + '
' + data.definition + '
'; + + // 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 = 'Loading...'; + 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) { diff --git a/kjvstudy_org/templates/base.html b/kjvstudy_org/templates/base.html index 5136fdc..b7bd6de 100644 --- a/kjvstudy_org/templates/base.html +++ b/kjvstudy_org/templates/base.html @@ -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;