// ── Global tooltip system ── // Replaces native title="" with styled tooltips. Supports: // data-tip="short text" — single-line tooltip (replaces title) // data-help="line1\nline2" — multi-line inline help (shown on hover) const SHOW_DELAY = 400; const HIDE_DELAY = 100; const OFFSET = 8; let tipEl = null; let showTimer = null; let hideTimer = null; let currentTarget = null; function create() { if (tipEl) return; tipEl = document.createElement('div'); tipEl.id = 'tooltip'; tipEl.setAttribute('role', 'tooltip'); document.body.appendChild(tipEl); } function show(target) { clearTimeout(hideTimer); if (currentTarget === target) return; currentTarget = target; create(); const tip = target.dataset.tip || ''; const help = target.dataset.help || ''; if (!tip && !help) { hide(); return; } if (help) { tipEl.classList.add('tooltip-help'); tipEl.innerHTML = help.replace(/\\n/g, '
'); } else { tipEl.classList.remove('tooltip-help'); tipEl.textContent = tip; } tipEl.style.visibility = 'hidden'; tipEl.style.display = 'block'; const rect = target.getBoundingClientRect(); const tipRect = tipEl.getBoundingClientRect(); let top, left; const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; if (spaceBelow >= tipRect.height + OFFSET || spaceBelow >= spaceAbove) { top = rect.bottom + OFFSET; tipEl.classList.remove('tooltip-above'); } else { top = rect.top - tipRect.height - OFFSET; tipEl.classList.add('tooltip-above'); } left = rect.left + rect.width / 2 - tipRect.width / 2; left = Math.max(8, Math.min(left, window.innerWidth - tipRect.width - 8)); top = Math.max(4, top); tipEl.style.top = top + 'px'; tipEl.style.left = left + 'px'; tipEl.style.visibility = ''; } function hide() { currentTarget = null; if (tipEl) tipEl.style.display = 'none'; } function onEnter(e) { const target = e.target.closest('[data-tip],[data-help]'); if (!target) return; clearTimeout(hideTimer); showTimer = setTimeout(() => show(target), SHOW_DELAY); } function onLeave(e) { const target = e.target.closest('[data-tip],[data-help]'); if (!target) return; clearTimeout(showTimer); hideTimer = setTimeout(hide, HIDE_DELAY); } document.addEventListener('pointerenter', onEnter, true); document.addEventListener('pointerleave', onLeave, true); document.addEventListener('pointerdown', () => { clearTimeout(showTimer); hide(); }, true); document.addEventListener('keydown', () => { clearTimeout(showTimer); hide(); }, true); document.addEventListener('scroll', () => { clearTimeout(showTimer); hide(); }, true);