From eee4f1f0e9bef76a7930e0f23837b05373a63fe6 Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Mon, 2 Feb 2026 23:42:07 +0100 Subject: [PATCH] Improve card image hover: center small, pan large --- wwwroot/app.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ wwwroot/styles.css | 3 +++ 2 files changed, 48 insertions(+) diff --git a/wwwroot/app.js b/wwwroot/app.js index bdb9367..c779f44 100644 --- a/wwwroot/app.js +++ b/wwwroot/app.js @@ -543,6 +543,7 @@ function buildCard(s, { showAuthor = false, allowDelete = false, allowEdit = fal `; if (hasImage) { const btn = card.querySelector(".card-visual"); + setupCardVisualHover(btn, s.screenshotUrl); btn.addEventListener("click", () => openLightbox(s.screenshotUrl, s.name)); } if (allowEdit) { @@ -669,6 +670,50 @@ function openLightbox(url, title) { document.body.appendChild(overlay); } +function setupCardVisualHover(el, url) { + if (!el || !url) return; + const img = new Image(); + let naturalW = 0; + let naturalH = 0; + let loaded = false; + img.src = url; + img.onload = () => { + naturalW = img.naturalWidth; + naturalH = img.naturalHeight; + loaded = true; + }; + + const reset = () => { + el.classList.remove("hovering"); + el.style.backgroundSize = ""; + el.style.backgroundPosition = ""; + }; + + el.addEventListener("mouseenter", () => { + el.classList.add("hovering"); + el.style.backgroundSize = "auto"; + el.style.backgroundPosition = "center"; + }); + + el.addEventListener("mousemove", (e) => { + if (!loaded) return; + const rect = el.getBoundingClientRect(); + const overW = naturalW - rect.width; + const overH = naturalH - rect.height; + if (overW <= 0 && overH <= 0) { + el.style.backgroundPosition = "center"; + return; + } + const xRatio = (e.clientX - rect.left) / rect.width; + const yRatio = (e.clientY - rect.top) / rect.height; + const xPercent = overW > 0 ? xRatio * 100 : 50; + const yPercent = overH > 0 ? yRatio * 100 : 50; + el.style.backgroundPosition = `${xPercent}% ${yPercent}%`; + }); + + ["mouseleave", "blur"].forEach(evt => el.addEventListener(evt, reset)); +} + async function main() { setupHandlers(); try { diff --git a/wwwroot/styles.css b/wwwroot/styles.css index f8b02db..42c921d 100644 --- a/wwwroot/styles.css +++ b/wwwroot/styles.css @@ -161,12 +161,15 @@ button.ghost { background: transparent; border-color: #d5c7b5; color: #2c1c0d; } background: linear-gradient(135deg, #f0d9b5, #f6b24f); background-size: cover; background-position: center; + background-repeat: no-repeat; + background-color: #f6b24f; cursor: pointer; border: none; width: 100%; display: block; padding: 0; } +.card-visual.hovering { cursor: zoom-in; } .card-body { padding: 12px; display: flex; flex-direction: column; gap: 6px; flex: 1; }