/**
* Homepage — renders different hero layouts based on theme config.
*
* Styles: split, fullbleed, minimal, stacked, typographic, mosaic.
*/
import Link from 'next/link';
import Image from 'next/image';
import { immich, type ImmichAsset } from '@/lib/immich';
import { getConfig } from '@/lib/config';
import { imageUrl, assetPlaceholder } from '@/lib/urls';
import { HeroCarousel } from '@/components/HeroCarousel';
import { FadeIn } from '@/components/FadeIn';
import { getServerDictionary } from '@/lib/i18n/server';
import type { Dictionary } from '@/lib/i18n';
// Render at request time — requires live Immich connection
export const dynamic = 'force-dynamic';
type HeroSubpage = { slug: string; name: string; albumCount: number };
type HeroAlbum = { id: string; slug: string; albumName: string; assetCount: number };
const pad2 = (n: number) => String(n).padStart(2, '0');
/**
* Flatten subpages + standalone albums into one indexed nav list.
* Every entry carries an index and a count; presets decide what to show.
*/
function heroNavEntries(subpages: HeroSubpage[], albums: HeroAlbum[], t: Dictionary) {
return [
...subpages.map((sp) => ({
key: `sp-${sp.slug}`,
href: `/${sp.slug}`,
label: sp.name,
count: t.common.albums(sp.albumCount),
})),
...albums.map((a) => ({
key: `al-${a.id}`,
href: `/${a.slug}`,
label: a.albumName,
count: t.common.photos(a.assetCount),
})),
];
}
/**
* Camera line for the hero chip: "Q3 · 28MM · ƒ/5.6 · 1/250 · ISO 200".
* Returns undefined when the asset carries no usable EXIF.
*/
function heroExifLine(asset: ImmichAsset): string | undefined {
const e = asset.exifInfo;
if (!e) return undefined;
const parts = [
e.city || e.model || undefined,
e.focalLength ? `${Math.round(e.focalLength)}mm` : undefined,
e.fNumber ? `ƒ/${e.fNumber}` : undefined,
e.exposureTime ? `${e.exposureTime}s` : undefined,
e.iso ? `ISO ${e.iso}` : undefined,
].filter(Boolean);
return parts.length > 0 ? parts.join(' · ') : undefined;
}
/** Shared nav links for hero sections. */
function HeroNavLinks({ subpages, albums }: { subpages: HeroSubpage[]; albums: HeroAlbum[] }) {
const t = getServerDictionary();
return (
<>
{heroNavEntries(subpages, albums, t).map((entry, i) => (
{entry.label}
))}
>
);
}
/** Title + optional subtitle + nav — shared by mosaic, minimal, fullbleed, split. */
function HeroTextContent({
title,
subtitle,
subpages,
albums,
}: {
title: string;
subtitle?: string;
subpages: HeroSubpage[];
albums: HeroAlbum[];
}) {
return (
<>
{subtitle}{title}
{config.siteSubtitle}
{config.siteSubtitle}
{config.siteSubtitle}