feat: photography features & image optimizations

- Hero carousel: gallery.yaml hero accepts single UUID or array, crossfade component
- Full-bleed single albums: subpages with 1 album skip grid, render photos directly
- EXIF on hover: camera/lens/focal length overlay on grid items (toggle via exifOnHover)
- Customizable grid: columns/gap/aspectRatio/layout (masonry|uniform) via gallery.yaml
- Per-subpage grid overrides: each subpage can override global grid config
- True masonry: per-image aspect ratios from EXIF dimensions
- next/image migration: homepage hero, album covers, grid thumbnails, about portrait
- ThumbHash blur placeholders and dominant color backgrounds
- Custom next/image loader via image proxy with width/quality params
- Lightbox fade-in transition with dominant color background
- Password-protected subpages, health check, rate limiting, OG images
This commit is contained in:
ralksta
2026-02-14 10:30:27 +01:00
parent 3c69860a64
commit b7217c4a76
31 changed files with 1998 additions and 162 deletions

View File

@@ -44,6 +44,7 @@ export function Lightbox({
const [showExif, setShowExif] = useState(false);
const [exifData, setExifData] = useState<ExifData | null>(null);
const [exifLoading, setExifLoading] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const overlayRef = useRef<HTMLDivElement>(null);
const touchStartX = useRef<number>(0);
const touchStartY = useRef<number>(0);
@@ -64,6 +65,7 @@ export function Lightbox({
// Reset EXIF data when switching images; refetch if panel is open
useEffect(() => {
setExifData(null);
setImageLoaded(false);
if (showExif && current) {
fetchExif(current.exifUrl);
}
@@ -149,12 +151,22 @@ export function Lightbox({
</button>
{/* Image */}
<div className="lightbox__image-container">
<div
className="lightbox__image-container"
style={{
backgroundColor: current.dominantColor || '#000',
backgroundImage: current.blurDataURL ? `url(${current.blurDataURL})` : undefined,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className="lightbox__image"
className={`lightbox__image${imageLoaded ? ' lightbox__image--loaded' : ''}`}
src={current.previewUrl}
alt=""
draggable={false}
onLoad={() => setImageLoaded(true)}
/>
</div>