diff --git a/ui/coach.css b/ui/coach.css index 98ac8b3..535f096 100644 --- a/ui/coach.css +++ b/ui/coach.css @@ -1113,3 +1113,26 @@ flex: 1; } } + +/* ── Scryfall Card Hover Preview ──────────────────────────────────────── */ +#coach-card-preview { + position: fixed; + z-index: 9999; + pointer-events: none; + background: var(--bg-card, #1e1e2e); + border: 1px solid var(--border, #444); + border-radius: 12px; + padding: 4px; + box-shadow: 0 8px 32px rgba(0,0,0,.6); + transition: opacity .15s ease; +} +#coach-card-preview img { + display: block; + width: 240px; + height: auto; + border-radius: 10px; +} +td[data-card-name] .coach-card-name { + cursor: pointer; + border-bottom: 1px dashed var(--text-secondary, #888); +} diff --git a/ui/coach.js b/ui/coach.js index ee5ad96..b177878 100644 --- a/ui/coach.js +++ b/ui/coach.js @@ -18,6 +18,58 @@ let expandedSessions = {}; // sessionId → bool let dbDecks = []; // [{id, name}] from /api/decks + // ── Scryfall Card Hover Preview ───────────────────────── + const _scryfallImgCache = {}; + let _previewEl = null; + + function _ensurePreviewEl() { + if (_previewEl) return _previewEl; + _previewEl = document.createElement('div'); + _previewEl.id = 'coach-card-preview'; + _previewEl.style.display = 'none'; + _previewEl.innerHTML = ''; + document.body.appendChild(_previewEl); + return _previewEl; + } + + function _showCardPreview(cardName, evt) { + const el = _ensurePreviewEl(); + const img = el.querySelector('img'); + if (_scryfallImgCache[cardName]) { + img.src = _scryfallImgCache[cardName]; + } else { + img.src = ''; + fetch('https://api.scryfall.com/cards/named?fuzzy=' + encodeURIComponent(cardName)) + .then(r => r.ok ? r.json() : null) + .then(data => { + if (!data) return; + const url = (data.image_uris && data.image_uris.normal) || (data.card_faces && data.card_faces[0] && data.card_faces[0].image_uris && data.card_faces[0].image_uris.normal) || ''; + _scryfallImgCache[cardName] = url; + if (el.style.display !== 'none') img.src = url; + }).catch(() => {}); + } + el.style.display = 'block'; + _positionPreview(evt); + } + + function _positionPreview(evt) { + if (!_previewEl) return; + _previewEl.style.left = Math.max(0, Math.min(evt.clientX + 20, window.innerWidth - 260)) + 'px'; + _previewEl.style.top = Math.max(0, Math.min(evt.clientY - 150, window.innerHeight - 370)) + 'px'; + } + + function _hideCardPreview() { + if (_previewEl) _previewEl.style.display = 'none'; + } + + function _attachCardHover(container) { + container.querySelectorAll('td[data-card-name]').forEach(td => { + td.addEventListener('mouseenter', e => _showCardPreview(td.dataset.cardName, e)); + td.addEventListener('mousemove', e => _positionPreview(e)); + td.addEventListener('mouseleave', _hideCardPreview); + }); + } + // ── DOM References ────────────────────────────────────── const deckSelect = document.getElementById('coach-deck-select'); const powerSlider = document.getElementById('coach-power-level'); @@ -551,6 +603,7 @@ ).join('') + '' + '' + formatImpact(cut.currentImpactScore) + ''; + tr.querySelector('.coach-card-name').parentElement.dataset.cardName = cut.cardName; cutsBody.appendChild(tr); } @@ -572,6 +625,7 @@ '' + escHtml(s) + '' ).join('') + ''; + tr.querySelector('.coach-card-name').parentElement.dataset.cardName = add.cardName; addsBody.appendChild(tr); } @@ -600,6 +654,9 @@ // Raw text rawText.textContent = session.rawTextExplanation || session.summary || '(empty)'; + + _attachCardHover(cutsBody); + _attachCardHover(addsBody); updateApplyCount(); }