/** * Root layout — site header with portfolio-style navigation. * Shows all subpages + standalone albums as nav links. * Injects theme CSS custom properties from gallery.yaml config. */ import type { Metadata } from 'next'; import { headers } from 'next/headers'; import Link from 'next/link'; import './globals.css'; import { SubpageNav } from '@/components/SubpageNav'; import { ThemeToggle } from '@/components/ThemeToggle'; import { ScrollToTop } from '@/components/ScrollToTop'; import { Footer } from '@/components/Footer'; import { SetupScreen } from '@/components/SetupScreen'; import { getConfig, getGoogleFontsUrl, AppConfig } from '@/lib/config'; import { isAdminPath } from '@/lib/admin/paths'; // DevToolbarLoader is a Client Component (ssr: false is only allowed there) import { DevToolbarLoader } from '@/components/DevToolbarLoader'; export async function generateMetadata(): Promise { const config = getConfig(); const siteTitle = config.seo.title; const siteDescription = config.seo.description; const robots = { index: !config.seo.noIndex, follow: !config.seo.noFollow, }; return { title: siteTitle, description: siteDescription, robots, icons: { apple: '/apple-touch-icon.png', }, openGraph: { title: siteTitle, description: siteDescription, type: 'website', images: [`/api/og?title=${encodeURIComponent(siteTitle)}`], }, twitter: { card: 'summary_large_image', title: siteTitle, description: siteDescription, images: [`/api/og?title=${encodeURIComponent(siteTitle)}`], }, }; } export default async function RootLayout({ children }: { children: React.ReactNode }) { const config = getConfig(); const { theme } = config; const fontsUrl = getGoogleFontsUrl(theme); const themeVars: Record = { '--accent': theme.accent, '--accent-dim': `${theme.accent}1f`, '--font-serif': `'${theme.fonts.heading}', Georgia, 'Times New Roman', serif`, '--font-sans': `'${theme.fonts.body}', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`, '--font-caption': `'${theme.fonts.caption}', Georgia, serif`, '--radius-sm': `${theme.radius}px`, '--radius-md': `${Math.round(theme.radius * 1.5)}px`, '--radius-lg': `${theme.radius * 2}px`, }; // /admin stays reachable during setup — it is the tool that completes the // setup, so the setup screen must not lock it out (#326). const pathname = (await headers()).get('x-pathname'); if ((config as AppConfig & { needsSetup?: boolean }).needsSetup && !isAdminPath(pathname)) { return ( ); } return ( Skip to content
{children}