- 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
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
/**
|
|
* Homepage — Split hero layout.
|
|
* Left panel: site title, subtitle, navigation links.
|
|
* Right panel: hero image carousel from Immich.
|
|
*/
|
|
|
|
import Link from 'next/link';
|
|
import { immich } from '@/lib/immich';
|
|
import { getConfig } from '@/lib/config';
|
|
import { imageUrl, assetPlaceholder } from '@/lib/urls';
|
|
import { HeroCarousel } from '@/components/HeroCarousel';
|
|
|
|
// Render at request time — requires live Immich connection
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export default async function HomePage() {
|
|
const config = getConfig();
|
|
const [subpages, albums] = await Promise.all([
|
|
immich.getSubpages(),
|
|
immich.getStandaloneAlbums(),
|
|
]);
|
|
|
|
// Fetch ThumbHash for all hero images
|
|
const heroData = await Promise.all(
|
|
config.heroImages.map(async (id) => {
|
|
const asset = await immich.getAssetInfo(id);
|
|
const ph = asset ? assetPlaceholder(asset) : null;
|
|
return {
|
|
src: imageUrl(id, 'preview'),
|
|
...(ph ? { blurDataURL: ph.blurDataURL } : {}),
|
|
};
|
|
}),
|
|
);
|
|
|
|
return (
|
|
<div className="hero">
|
|
{/* ── Left Panel ──────────────────────────────── */}
|
|
<div className="hero__left">
|
|
<div className="hero__content">
|
|
<h1 className="hero__title">{config.siteTitle}</h1>
|
|
{config.siteSubtitle && (
|
|
<p className="hero__subtitle">{config.siteSubtitle}</p>
|
|
)}
|
|
|
|
<nav className="hero__nav">
|
|
{subpages.map((sp) => (
|
|
<Link
|
|
key={sp.slug}
|
|
href={`/${sp.slug}`}
|
|
className="hero__nav-link"
|
|
>
|
|
{sp.name}
|
|
</Link>
|
|
))}
|
|
{albums.map((album) => (
|
|
<Link
|
|
key={album.id}
|
|
href={`/${album.slug}`}
|
|
className="hero__nav-link"
|
|
>
|
|
{album.albumName}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── Right Panel (Hero Carousel) ─────────────── */}
|
|
<div className="hero__right">
|
|
<HeroCarousel images={heroData} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|