Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 82 additions & 38 deletions src/layouts/PostLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,89 @@ const year = date.getFullYear();
<span class="lightbox-title">Full-size image</span>
<span class="lightbox-hint">Click outside or press Esc to close</span>
</div>
<button class="lightbox-nav lightbox-prev" aria-label="Previous image">&#8249;</button>
<div class="lightbox-container">
<img src="" alt="" />
</div>
<button class="lightbox-nav lightbox-next" aria-label="Next image">&#8250;</button>
<button class="lightbox-close" aria-label="Close">&times;</button>
<div class="lightbox-footer">
<span>Click anywhere outside the image to close</span>
<span class="lightbox-counter"></span>
</div>
`;
document.body.appendChild(lightbox);

const lightboxImg = lightbox.querySelector('.lightbox-container img');
const closeBtn = lightbox.querySelector('.lightbox-close');
const container = lightbox.querySelector('.lightbox-container');
const prevBtn = lightbox.querySelector('.lightbox-prev');
const nextBtn = lightbox.querySelector('.lightbox-next');
const counter = lightbox.querySelector('.lightbox-counter');

// Get year and slug from data attributes
const article = document.querySelector('article[data-year][data-slug]');
const year = article?.dataset.year;
const slug = article?.dataset.slug;

// Collect all prose images (excluding those wrapped in links)
const proseImages = Array.from(document.querySelectorAll('.prose img')).filter(
(img) => img.parentElement?.tagName !== 'A'
);
let currentIndex = 0;

// Function to get original image path
function getOriginalPath(src, baseName) {
if (year && slug && baseName) {
return `/originals/${year}/${slug}/${baseName}.png`;
}
return src;
}

// Function to load image with fallbacks
function loadImage(src, alt, baseName) {
lightboxImg.src = getOriginalPath(src, baseName);
lightboxImg.alt = alt;

if (baseName && year && slug) {
lightboxImg.onerror = () => {
lightboxImg.src = `/originals/${year}/${slug}/${baseName}.jpg`;
lightboxImg.onerror = () => {
lightboxImg.src = src;
};
};
}
}

// Function to update navigation state
function updateNavigation() {
prevBtn.classList.toggle('disabled', currentIndex === 0);
nextBtn.classList.toggle('disabled', currentIndex === proseImages.length - 1);
counter.textContent = `${currentIndex + 1} / ${proseImages.length}`;
}

// Function to show image at index
function showImage(index) {
if (index < 0 || index >= proseImages.length) return;
currentIndex = index;
const img = proseImages[index];
const src = img.getAttribute('src') || '';
const alt = img.getAttribute('alt') || '';
const match = src.match(/\/_astro\/([^.]+)\./);
const baseName = match ? match[1] : null;
loadImage(src, alt, baseName);
updateNavigation();
}

// Navigation handlers
prevBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (currentIndex > 0) showImage(currentIndex - 1);
});

nextBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (currentIndex < proseImages.length - 1) showImage(currentIndex + 1);
});

// Close lightbox on overlay click (but not container), close button, or Escape key
lightbox.addEventListener('click', (e) => {
Expand All @@ -122,52 +192,26 @@ const year = date.getFullYear();
});
// Prevent closing when clicking the container
container.addEventListener('click', (e) => e.stopPropagation());

// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (!lightbox.classList.contains('active')) return;
if (e.key === 'Escape') lightbox.classList.remove('active');
if (e.key === 'ArrowLeft' && currentIndex > 0) showImage(currentIndex - 1);
if (e.key === 'ArrowRight' && currentIndex < proseImages.length - 1) showImage(currentIndex + 1);
});

// Get year and slug from data attributes
const article = document.querySelector('article[data-year][data-slug]');
const year = article?.dataset.year;
const slug = article?.dataset.slug;

// Make prose images clickable to open lightbox with original
document.querySelectorAll('.prose img').forEach((img) => {
// Skip if already wrapped in a link
if (img.parentElement?.tagName === 'A') return;

proseImages.forEach((img, index) => {
img.addEventListener('click', () => {
// Extract the filename from the optimized src
currentIndex = index;
const src = img.getAttribute('src') || '';
const alt = img.getAttribute('alt') || '';

// Try to find the original image
// The optimized path is like /_astro/filename.hash.webp
// We need to map it back to /originals/YEAR/SLUG/filename.png
const match = src.match(/\/_astro\/([^.]+)\./);
if (match && year && slug) {
const baseName = match[1];
// Try to load the original
const originalPath = `/originals/${year}/${slug}/${baseName}.png`;
lightboxImg.src = originalPath;
lightboxImg.alt = alt;
lightbox.classList.add('active');

// Fallback to optimized if original fails
lightboxImg.onerror = () => {
// Try jpg
lightboxImg.src = `/originals/${year}/${slug}/${baseName}.jpg`;
lightboxImg.onerror = () => {
// Fall back to optimized version
lightboxImg.src = src;
};
};
} else {
// Use the src as-is if we can't parse it
lightboxImg.src = src;
lightboxImg.alt = alt;
lightbox.classList.add('active');
}
const baseName = match ? match[1] : null;
loadImage(src, alt, baseName);
updateNavigation();
lightbox.classList.add('active');
});
});
</script>
Expand Down
43 changes: 43 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,49 @@ code {
font-size: 0.75rem;
}

/* Lightbox navigation arrows */
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: var(--color-primary);
border: none;
color: white;
font-size: 2rem;
width: 3rem;
height: 3rem;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease, transform 0.2s ease, opacity 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
z-index: 10;
}

.lightbox-nav:hover:not(.disabled) {
background: var(--color-primary-hover);
transform: translateY(-50%) scale(1.1);
}

.lightbox-nav.disabled {
opacity: 0.3;
cursor: not-allowed;
}

.lightbox-prev {
left: 1rem;
}

.lightbox-next {
right: 4.5rem;
}

.lightbox-counter {
font-variant-numeric: tabular-nums;
}

.prose h2 {
margin-top: 2em;
margin-bottom: 1em;
Expand Down