Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9a30a38d7 | |||
| be24904b78 | |||
| 3b359d2b37 |
+12
-2
@@ -136,11 +136,11 @@ mark.hl { background: color-mix(in oklab, var(--roux) 22%, transparent); color:
|
||||
.lb { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto auto; grid-template-areas: "topbar" "stage" "meta" "thumbs"; }
|
||||
}
|
||||
.lb-topbar { grid-area: topbar; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 22px; border-bottom: 1px solid rgba(236,231,221,.08); }
|
||||
.lb-brand { display: flex; align-items: center; gap: 14px; font: 500 11px/1 var(--sans); letter-spacing: .2em; text-transform: uppercase; color: #c8c0b1; }
|
||||
.lb-brand { display: flex; align-items: center; gap: 14px; font: 500 11px/1 var(--sans); letter-spacing: .2em; text-transform: uppercase; color: #c8c0b1; min-width: 0; overflow: hidden; }
|
||||
.lb-brand svg { height: 18px; }
|
||||
.lb-index { font: 500 11px/1 var(--mono); letter-spacing: .14em; color: #c8c0b1; font-variant-numeric: tabular-nums; }
|
||||
.lb-index b { color: #ece7dd; }
|
||||
.lb-close { width: 36px; height: 36px; border: 1px solid rgba(236,231,221,.18); border-radius: 50%; display: grid; place-items: center; color: #ece7dd; }
|
||||
.lb-close { width: 36px; height: 36px; flex-shrink: 0; border: 1px solid rgba(236,231,221,.18); border-radius: 50%; display: grid; place-items: center; color: #ece7dd; }
|
||||
.lb-close:hover { background: rgba(236,231,221,.08); }
|
||||
.lb-stage { grid-area: stage; position: relative; overflow: hidden; display: grid; place-items: center; padding: 24px; min-height: 0; }
|
||||
.lb-track { position: absolute; inset: 0; display: flex; transition: transform .55s cubic-bezier(.4,.0,.2,1); will-change: transform; }
|
||||
@@ -198,6 +198,16 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ── full-image zoom overlay ── */
|
||||
.img-zoom {
|
||||
position: fixed; inset: 0; z-index: 400;
|
||||
background: rgba(8,6,4,.97);
|
||||
display: none; place-items: center;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
.img-zoom[data-open="true"] { display: grid; }
|
||||
.lb-img { cursor: zoom-in; }
|
||||
|
||||
/* ── scrollbar ── */
|
||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||
::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 0; }
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
{{ define "main" }}
|
||||
{{- $issue := index (.Params.issues | default (slice "01")) 0 }}
|
||||
{{- $issueURL := printf "/issues/%s/" $issue }}
|
||||
{{- $img := .Resources.GetMatch "*.png" }}
|
||||
{{- $cardSrc := "" }}
|
||||
{{- if $img }}{{- $c := $img.Resize "900x1350 webp" }}{{- $cardSrc = $c.RelPermalink }}{{- end }}
|
||||
|
||||
<section id="hero" class="px-[var(--pad)] pt-[clamp(36px,5vw,64px)] pb-[clamp(28px,3.5vw,48px)] border-b border-[var(--rule)]">
|
||||
<div class="font-sans font-medium text-[10px] leading-none tracking-[.22em] uppercase text-ink-soft mb-4">
|
||||
№ {{ $issue }} · {{ index (.Params.categories | default (slice "Plate")) 0 }}
|
||||
</div>
|
||||
<h1 class="font-display font-normal text-[clamp(48px,7vw,96px)] leading-[0.94] m-0 mb-4"><em>{{ .Title }}</em></h1>
|
||||
<p class="font-serif italic text-[clamp(13px,1vw,16px)] leading-[1.65] text-ink-2 max-w-[55ch] m-0">
|
||||
<p class="font-serif italic text-[clamp(13px,1vw,16px)] leading-[1.65] text-ink-2 max-w-[55ch] m-0 mb-5">
|
||||
{{ .Params.description }}
|
||||
<a href="{{ $issueURL }}" class="border-b border-current ml-1">Return to issue →</a>
|
||||
</p>
|
||||
<div class="flex items-center gap-5 flex-wrap">
|
||||
<a href="{{ $issueURL }}"
|
||||
class="font-sans font-medium text-[11px] leading-none tracking-[.16em] uppercase text-ink-soft
|
||||
border-b border-[var(--rule)] pb-px transition-colors duration-200 hover:text-ink hover:border-ink">
|
||||
← Return to issue
|
||||
</a>
|
||||
{{- if $cardSrc }}
|
||||
<button id="viewFull" data-src="{{ $cardSrc }}"
|
||||
class="inline-flex items-center gap-[7px]
|
||||
font-sans font-medium text-[11px] leading-none tracking-[.16em] uppercase text-ink-soft
|
||||
border-b border-[var(--rule)] pb-px transition-colors duration-200 hover:text-ink hover:border-ink">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>
|
||||
</svg>
|
||||
View full image
|
||||
</button>
|
||||
{{- end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="grid" data-density="default"
|
||||
|
||||
@@ -59,6 +59,23 @@
|
||||
{{- $posts | jsonify | safeJS }}
|
||||
</script>
|
||||
|
||||
{{/* Pure image zoom — opened by hero button or clicking the lightbox main image */}}
|
||||
<div id="imgZoom" class="img-zoom" role="dialog" aria-modal="true" aria-label="Full size image">
|
||||
<button id="imgZoomClose"
|
||||
class="absolute top-4 right-4 z-10
|
||||
w-10 h-10 grid place-items-center
|
||||
border border-white/20 rounded-full
|
||||
text-white/60 hover:text-white hover:bg-white/10
|
||||
transition-colors duration-200"
|
||||
aria-label="Close full image">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
<img id="imgZoomImg" src="" alt="" class="max-w-[96vw] max-h-[96vh] object-contain select-none" />
|
||||
</div>
|
||||
|
||||
<script src="/js/app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+66
-3
@@ -211,6 +211,8 @@
|
||||
let lbIdx = -1; // index into lbList
|
||||
let lbBuilt = false;
|
||||
let lbReferrer = null; // URL to return to on close
|
||||
let imgZoomOpen = false;
|
||||
let imgZoomIdx = -1;
|
||||
|
||||
function lbOpen(slug, scopedList) {
|
||||
lbList = scopedList || POSTS;
|
||||
@@ -348,9 +350,9 @@
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (lb.dataset.open !== 'true') return;
|
||||
if (e.key === 'Escape') { lbClose(); }
|
||||
if (e.key === 'ArrowLeft') { e.preventDefault(); goToSlide(lbIdx - 1); }
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); goToSlide(lbIdx + 1); }
|
||||
if (e.key === 'Escape') { if (imgZoomOpen) closeImgZoom(); else lbClose(); }
|
||||
if (e.key === 'ArrowLeft') { e.preventDefault(); imgZoomOpen ? goToZoomSlide(imgZoomIdx - 1) : goToSlide(lbIdx - 1); }
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); imgZoomOpen ? goToZoomSlide(imgZoomIdx + 1) : goToSlide(lbIdx + 1); }
|
||||
});
|
||||
|
||||
// Touch swipe
|
||||
@@ -469,4 +471,65 @@
|
||||
lbOpen(window.__ROUX_OPEN_SLUG, scoped.length ? scoped : POSTS);
|
||||
}
|
||||
|
||||
// ── Full-image zoom overlay
|
||||
const imgZoom = document.getElementById('imgZoom');
|
||||
const imgZoomImg = document.getElementById('imgZoomImg');
|
||||
|
||||
function openImgZoom(src) {
|
||||
if (!imgZoom || !imgZoomImg || !src) return;
|
||||
imgZoomImg.src = src;
|
||||
imgZoomIdx = lbIdx;
|
||||
imgZoom.dataset.open = 'true';
|
||||
imgZoomOpen = true;
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeImgZoom() {
|
||||
if (!imgZoom) return;
|
||||
imgZoom.dataset.open = 'false';
|
||||
imgZoomOpen = false;
|
||||
if (!lb || lb.dataset.open !== 'true') document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function goToZoomSlide(idx) {
|
||||
if (!lbList.length || !imgZoomImg) return;
|
||||
imgZoomIdx = Math.max(0, Math.min(idx, lbList.length - 1));
|
||||
const p = lbList[imgZoomIdx];
|
||||
if (p) imgZoomImg.src = p.card || p.thumb || '';
|
||||
goToSlide(imgZoomIdx);
|
||||
}
|
||||
|
||||
// Hero "View full image" button
|
||||
const viewFullBtn = document.getElementById('viewFull');
|
||||
if (viewFullBtn) {
|
||||
viewFullBtn.addEventListener('click', () => openImgZoom(viewFullBtn.dataset.src));
|
||||
}
|
||||
|
||||
// Click on the lightbox main image → zoom
|
||||
document.addEventListener('click', e => {
|
||||
const img = e.target.closest('.lb-img');
|
||||
if (img) openImgZoom(img.src);
|
||||
});
|
||||
|
||||
// Close on backdrop click or close button
|
||||
if (imgZoom) {
|
||||
imgZoom.addEventListener('click', e => { if (e.target === imgZoom) closeImgZoom(); });
|
||||
document.getElementById('imgZoomClose')?.addEventListener('click', closeImgZoom);
|
||||
|
||||
// Swipe to navigate
|
||||
let zoomTouchX = null;
|
||||
imgZoom.addEventListener('touchstart', e => { zoomTouchX = e.touches[0].clientX; }, { passive: true });
|
||||
imgZoom.addEventListener('touchend', e => {
|
||||
if (zoomTouchX === null) return;
|
||||
const dx = e.changedTouches[0].clientX - zoomTouchX;
|
||||
if (Math.abs(dx) > 50) dx < 0 ? goToZoomSlide(imgZoomIdx + 1) : goToZoomSlide(imgZoomIdx - 1);
|
||||
zoomTouchX = null;
|
||||
});
|
||||
}
|
||||
|
||||
// Escape when lightbox is NOT open (standalone zoom)
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && imgZoomOpen && (!lb || lb.dataset.open !== 'true')) closeImgZoom();
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user