const HIDE_DELAY = 100; let _el = null; let _imgEl = null; let _hideTimer = null; let _visible = false; let _activeCard = null; let _hoverCard = null; let _enabled = true; let _showSeq = 0; export function init() { _el = document.createElement('div'); _el.className = 'hover-preview'; _imgEl = document.createElement('img'); _el.appendChild(_imgEl); document.body.appendChild(_el); } export function setEnabled(v) { _enabled = v; if (!v) { clearTimeout(_hideTimer); _hideTimer = null; _hideNow(); return; } if (_hoverCard?.isConnected) { _showForCard(_hoverCard); } } export function setupCard(card) { card.addEventListener('mouseenter', _onEnter); card.addEventListener('mouseleave', _onLeave); } function _onEnter() { const card = this; _hoverCard = card; if (!_enabled) return; _activeCard = card; if (_hideTimer) { clearTimeout(_hideTimer); _hideTimer = null; } if (_visible) { _switchTo(card); return; } _showForCard(card); } function _onLeave() { if (_hoverCard === this) _hoverCard = null; if (!_visible) { return; } _hideTimer = setTimeout(() => { _hideTimer = null; _hideNow(); }, HIDE_DELAY); } // ── 팝업 내용 전환 (이미 열린 상태에서 카드 변경) ── async function _showForCard(card) { const seq = ++_showSeq; _activeCard = card; const src = await _loadSrc(card); if (!src || !_enabled || _activeCard !== card || seq !== _showSeq) return; _imgEl.src = src; _visible = true; _show(card); } async function _switchTo(card) { const seq = ++_showSeq; const src = await _loadSrc(card); if (!src || !_enabled || _activeCard !== card || seq !== _showSeq) return; _imgEl.src = src; _show(card); } // ── 이미지 소스 반환 (카드 썸네일 캐시 우선) ── async function _loadSrc(card) { const thumb = card.querySelector('img.card-thumb'); let src = thumb?.src || ''; if (!src) { src = await window.api.readImage(card.dataset.filepath) || ''; if (src && thumb) thumb.src = src; } return src; } function _hideNow() { _showSeq++; _activeCard = null; _visible = false; _el.style.display = 'none'; _el.style.visibility = 'hidden'; } // ── 이미지 팝업 위치 계산 및 표시 ──────────────── function _show(card) { _el.style.display = 'block'; _el.style.visibility = 'hidden'; _el.style.left = '0px'; _el.style.top = '0px'; requestAnimationFrame(() => { if (!_visible) return; const popup = _el.getBoundingClientRect(); const cardRect = card.getBoundingClientRect(); const vw = window.innerWidth; const vh = window.innerHeight; const gap = 16; let left = cardRect.right + gap; if (left + popup.width > vw - 8) { left = cardRect.left - popup.width - gap; } left = Math.max(8, Math.min(left, vw - popup.width - 8)); let top = cardRect.top + (cardRect.height - popup.height) / 2; top = Math.max(8, Math.min(top, vh - popup.height - 8)); _el.style.left = left + 'px'; _el.style.top = top + 'px'; _el.style.visibility = 'visible'; }); }