diff --git a/assets/css/custom.css b/assets/css/custom.css index 7383b0b..ce41372 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -251,6 +251,40 @@ body:not(.home) .menu { display: none; } } } +/* small peek column — mirrors .proj-info's spot and width on the OTHER side + of the file list, showing whichever image is under the mouse while + something is locked. sits inside the space .proj-layout already reserves + to keep the file list anchored (see above), so it needs that reserved + padding to be at least 18rem + the gap wide to have room; 1300px keeps a + safety margin above the ~1168px point where that stops being true. plain + absolute positioning against .filebrowser — no scroll-tracking needed, + it just scrolls together with the file list it's attached to. */ +.proj-peek { display: none; } +@media (min-width: 1300px) { + .filebrowser { position: relative; } + .proj-peek { + display: block; + position: absolute; + top: 0; + right: 100%; + margin-right: 1.5rem; + width: 18rem; + opacity: 0; + visibility: hidden; + transition: opacity 0.15s ease; + pointer-events: none; + } + .proj-peek.is-visible { opacity: 1; visibility: visible; } + .proj-peek img { + width: 100%; + height: auto; + max-height: 70vh; + object-fit: contain; + display: block; + margin: 0; + } +} + /* prev/next as shell "cd" commands — same vocabulary as the rest of the site */ .proj-nav { font-family: var(--font-family-mono); diff --git a/assets/js/filebrowser.js b/assets/js/filebrowser.js index 2369aaa..1669b82 100644 --- a/assets/js/filebrowser.js +++ b/assets/js/filebrowser.js @@ -30,9 +30,10 @@ if (img.complete) img.style.opacity = "1"; // already cached → no fade } - // renders row i into the preview pane. this runs on hover AND on lock — - // hovering always drives what the pane shows, lock only decides what it - // falls back to once the mouse leaves the list again. + // renders row i into the MAIN preview pane. driven by lock (click) and + // keyboard nav only — hovering never touches this, it drives the small + // peek column instead (see bind()), so a locked file stays put while you + // browse the others. function render(i, silent, scroll) { if (!rows.length) return; if (i < 0) i = 0; @@ -79,6 +80,27 @@ cur = 0; locked = -1; if (!rows.length) return; + // small side-by-side peek — sits to the left of the file list (mirroring + // the info text's spot on the right, same 18rem width) and shows + // whichever OTHER image is under the mouse while something is locked, + // without disturbing the main preview. + var peek = null, peekImg = null; + if (container) { + peek = document.createElement("div"); + peek.className = "proj-peek"; + peekImg = document.createElement("img"); + peekImg.alt = ""; + peek.appendChild(peekImg); + container.insertBefore(peek, container.firstChild); + } + function showPeek(a) { + var src = a.getAttribute("data-img"); + if (!peek || !src) { if (peek) peek.classList.remove("is-visible"); return; } + peekImg.src = src; + peek.classList.add("is-visible"); + } + function hidePeek() { if (peek) peek.classList.remove("is-visible"); } + rows.forEach(function (a, i) { // portfolio-index rows are real links (data-img + href together) — a // single click there used to jump straight into the project, which @@ -87,29 +109,27 @@ // click on the already-locked row, or the preview image itself, // actually navigates. var isProjectLink = a.hasAttribute("href") && a.hasAttribute("data-img"); - a.addEventListener("mouseenter", function () { render(i); }); + a.addEventListener("mouseenter", function () { + if (locked >= 0 && i !== locked) showPeek(a); else hidePeek(); + }); a.addEventListener("click", function (e) { if (isProjectLink) { if (locked === i) return; // already locked — let the link through e.preventDefault(); lock(i); render(i, true); + hidePeek(); return; } if (a.getAttribute("href")) return; // ".." navigates normally e.preventDefault(); if (locked === i) { unlock(); ghostClear(); } else { lock(i); render(i, true); } + hidePeek(); }); }); - // leaving the list reverts the preview back to whatever is locked (or - // to the initial file, if nothing is) — hover only ever peeks - if (list) { - list.addEventListener("mouseleave", function () { - render(locked >= 0 ? locked : 0, true); - }); - } + if (list) list.addEventListener("mouseleave", hidePeek); var start = rows.findIndex(function (a) { return a.classList.contains("is-selected"); }); start = start < 0 ? 0 : start;