feat: separate settings and gallery config, add impressum page
This commit is contained in:
@@ -877,11 +877,17 @@
|
|||||||
.footer__inner {
|
.footer__inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.footer__name {
|
.footer__name {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
@@ -891,6 +897,19 @@
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer__legal-link {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__legal-link:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.footer__links {
|
.footer__links {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
121
app/impressum/page.tsx
Normal file
121
app/impressum/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Impressum (Legal Notice) page.
|
||||||
|
* Required for German law compliance. Displays information from settings.yaml.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { getConfig } from '@/lib/config';
|
||||||
|
import { BackLink } from '@/components/BackLink';
|
||||||
|
import { FadeIn } from '@/components/FadeIn';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Impressum',
|
||||||
|
robots: { index: false, follow: true }, // Usually no need to index legal pages
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ImpressumPage() {
|
||||||
|
const { legal } = getConfig();
|
||||||
|
|
||||||
|
if (!legal.enabled) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="subpage-container">
|
||||||
|
<header className="subpage-header">
|
||||||
|
<BackLink href="/" label="Home" />
|
||||||
|
<FadeIn>
|
||||||
|
<h1 className="subpage-title">Impressum</h1>
|
||||||
|
<p className="subpage-subtitle">Angaben gemäß § 5 TMG</p>
|
||||||
|
</FadeIn>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="subpage-content" style={{ maxWidth: '600px', margin: '0 auto' }}>
|
||||||
|
<FadeIn delay={0.1}>
|
||||||
|
<section style={{ marginBottom: '2rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.2rem', marginBottom: '0.5rem', opacity: 0.8 }}>Anschrift</h2>
|
||||||
|
<p style={{ lineHeight: 1.6 }}>
|
||||||
|
{legal.name}
|
||||||
|
<br />
|
||||||
|
{legal.address}
|
||||||
|
<br />
|
||||||
|
{legal.zipCity}
|
||||||
|
<br />
|
||||||
|
{legal.country}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{(legal.email || legal.phone) && (
|
||||||
|
<section style={{ marginBottom: '2rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.2rem', marginBottom: '0.5rem', opacity: 0.8 }}>Kontakt</h2>
|
||||||
|
<p style={{ lineHeight: 1.6 }}>
|
||||||
|
{legal.email && (
|
||||||
|
<>
|
||||||
|
E-Mail: {legal.email}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{legal.phone && <>Telefon: {legal.phone}</>}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(legal.taxId || legal.vatId) && (
|
||||||
|
<section style={{ marginBottom: '2rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.2rem', marginBottom: '0.5rem', opacity: 0.8 }}>
|
||||||
|
Steuernummer
|
||||||
|
</h2>
|
||||||
|
<p style={{ lineHeight: 1.6 }}>
|
||||||
|
{legal.taxId && (
|
||||||
|
<>
|
||||||
|
Steuernummer: {legal.taxId}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{legal.vatId && <>Umsatzsteuer-ID: {legal.vatId}</>}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{legal.extraInfo && (
|
||||||
|
<section style={{ marginBottom: '2rem' }}>
|
||||||
|
<h2 style={{ fontSize: '1.2rem', marginBottom: '0.5rem', opacity: 0.8 }}>
|
||||||
|
Weitere Informationen
|
||||||
|
</h2>
|
||||||
|
<p style={{ lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{legal.extraInfo}</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section style={{ marginTop: '4rem', fontSize: '0.85rem', opacity: 0.6 }}>
|
||||||
|
<p>Quelle: Erstellt mit dem Impressum-Generator von eRecht24.</p>
|
||||||
|
</section>
|
||||||
|
</FadeIn>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style jsx>{`
|
||||||
|
.subpage-container {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
min-height: 80vh;
|
||||||
|
}
|
||||||
|
.subpage-header {
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.subpage-title {
|
||||||
|
font-family: var(--font-serif);
|
||||||
|
font-size: clamp(2.5rem, 8vw, 4rem);
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.subpage-subtitle {
|
||||||
|
font-family: var(--font-caption);
|
||||||
|
font-size: 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,21 +3,32 @@
|
|||||||
* Reads configuration from gallery.yaml via getConfig().
|
* Reads configuration from gallery.yaml via getConfig().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
import { getConfig } from '@/lib/config';
|
import { getConfig } from '@/lib/config';
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const { footer } = config;
|
const { footer, legal } = config;
|
||||||
|
|
||||||
// Don't render if no footer config
|
// Don't render if no footer config AND no legal config
|
||||||
if (!footer || (!footer.name && !footer.instagram && !footer.email && !footer.website)) {
|
if (
|
||||||
|
!legal.enabled &&
|
||||||
|
(!footer || (!footer.name && !footer.instagram && !footer.email && !footer.website))
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
<div className="footer__inner">
|
<div className="footer__inner">
|
||||||
{footer.name && <span className="footer__name">{footer.name}</span>}
|
<div className="footer__left">
|
||||||
|
{footer?.name && <span className="footer__name">{footer.name}</span>}
|
||||||
|
{legal.enabled && (
|
||||||
|
<Link href="/impressum" className="footer__legal-link">
|
||||||
|
Impressum
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="footer__links">
|
<div className="footer__links">
|
||||||
{footer.instagram && (
|
{footer.instagram && (
|
||||||
|
|||||||
@@ -1,72 +1,24 @@
|
|||||||
# ── Gallery Configuration ──────────────────────────────────────
|
# ── Gallery Structure ──────────────────────────────────────────────
|
||||||
# Define which Immich albums to publish and how to organize them.
|
# This file defines WHICH albums and subpages are shown.
|
||||||
#
|
# Global settings (theme, map, legal, title) now live in content/settings.yaml.
|
||||||
# Find album UUIDs in Immich: Albums → click album → UUID in the URL bar.
|
|
||||||
# Find asset UUIDs: click a photo → UUID in the URL bar.
|
|
||||||
|
|
||||||
# Hero image(s) shown on the homepage (Immich asset UUIDs).
|
# Hero images for the homepage carousel (Immich asset UUIDs)
|
||||||
# Use a single UUID for a static image, or a list for a crossfade carousel.
|
hero:
|
||||||
hero: 00000000-0000-0000-0000-000000000000
|
- "your-asset-uuid-1"
|
||||||
# hero:
|
- "your-asset-uuid-2"
|
||||||
# - 00000000-0000-0000-0000-000000000000
|
|
||||||
# - 11111111-1111-1111-1111-111111111111
|
|
||||||
# - 22222222-2222-2222-2222-222222222222
|
|
||||||
|
|
||||||
# Standalone albums — shown directly on the homepage as cards
|
# Standalone albums (shown on the home page)
|
||||||
# Use a string for the UUID, or a map for an inline name override.
|
|
||||||
albums:
|
albums:
|
||||||
- 11111111-1111-1111-1111-111111111111: "Meine Favoriten" # inline override
|
- "your-album-uuid-1"
|
||||||
- 22222222-2222-2222-2222-222222222222
|
- "your-album-uuid-2"
|
||||||
|
|
||||||
# Show camera/lens EXIF on photo hover (default: true)
|
# Subpages: group albums under custom URL paths
|
||||||
# exifOnHover: false
|
|
||||||
|
|
||||||
# Enable the /map page — plots photos on a world map by city/country (default: false)
|
|
||||||
# map: true
|
|
||||||
|
|
||||||
# Photo grid layout — customize columns, gap, aspect ratio, and mode.
|
|
||||||
# grid:
|
|
||||||
# columns: 3 # default: 3
|
|
||||||
# gap: 12 # pixels, default: 12
|
|
||||||
# aspectRatio: "1" # "1", "3/2", "auto", etc. default: "1"
|
|
||||||
# layout: masonry # "masonry" or "uniform", default: "masonry"
|
|
||||||
|
|
||||||
# Subpages — group albums into collections at /subpage-slug/album-slug.
|
|
||||||
# You can use a simple map for concise definitions, or a full object for passwords/grids.
|
|
||||||
subpages:
|
subpages:
|
||||||
Japan: # Simple map format
|
"Travel":
|
||||||
- 33333333-3333-3333-3333-333333333333: "Tokyo Nights"
|
- "album-uuid-3"
|
||||||
- 44444444-4444-4444-4444-444444444444
|
- "album-uuid-4"
|
||||||
|
"Projects":
|
||||||
Wedding: # Advanced object format (needed for passwords/grid overrides)
|
password: "your-password" # Optional protection
|
||||||
password: clientpass123
|
|
||||||
albums:
|
albums:
|
||||||
- 55555555-5555-5555-5555-555555555555: "The Big Day"
|
- "album-uuid-5"
|
||||||
|
- "album-uuid-6"
|
||||||
# Footer — optional minimal footer with social links
|
|
||||||
# footer:
|
|
||||||
# name: John Doe
|
|
||||||
# instagram: johndoe
|
|
||||||
# email: hello@example.com
|
|
||||||
# website: https://example.com
|
|
||||||
|
|
||||||
# ── Theme ─────────────────────────────────────────────────────
|
|
||||||
# Control the visual identity of your gallery. Use a preset name:
|
|
||||||
# theme: studio # Leica-inspired, red dot, passepartout frames, film grain
|
|
||||||
# theme: minimal # Swiss brutalist, fullbleed hero, true B&W, zero-gap grid
|
|
||||||
# theme: editorial # Cinematic magazine, warm charcoal, Bodoni serif, shadow frames
|
|
||||||
# theme: classic # Gilded gallery, gold accents, Cinzel headings, passepartout
|
|
||||||
#
|
|
||||||
# Or customize individual properties (defaults from the preset):
|
|
||||||
# theme:
|
|
||||||
# preset: studio # base preset to extend
|
|
||||||
# accent: "#2563eb" # brand/accent color
|
|
||||||
# fonts:
|
|
||||||
# heading: "Inter" # Google Fonts name for headings
|
|
||||||
# body: "Inter" # body text
|
|
||||||
# caption: "JetBrains Mono" # EXIF captions
|
|
||||||
# radius: 8 # border-radius in pixels (0 = sharp)
|
|
||||||
# photoFrame: none # "none" | "passepartout" | "shadow"
|
|
||||||
# grain: false # film grain overlay on photos
|
|
||||||
# headerDot: false # accent dot in the nav bar
|
|
||||||
# heroStyle: split # "split" | "fullbleed" | "minimal"
|
|
||||||
|
|||||||
52
content/settings.yaml.example
Normal file
52
content/settings.yaml.example
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# ── Global Site Settings ──────────────────────────────────────────
|
||||||
|
# This file controls the visual identity, global behavior, and legal info.
|
||||||
|
# Structure is separated from gallery.yaml (album IDs) for clarity.
|
||||||
|
|
||||||
|
# Public site title and subtitle (overrides .env if set)
|
||||||
|
title: "My Portfolio"
|
||||||
|
subtitle: "A visual journal"
|
||||||
|
|
||||||
|
# Show EXIF metadata (camera, lens, ISO) on photo grid hover
|
||||||
|
exifOnHover: true
|
||||||
|
|
||||||
|
# Enable the interactive world map view (/map)
|
||||||
|
map: true
|
||||||
|
|
||||||
|
# Enable smooth page transitions between routes
|
||||||
|
transitions: true
|
||||||
|
|
||||||
|
# ── Theme configuration ──────────────────────────────────────────
|
||||||
|
# Presets: studio, minimal, editorial, classic, noir, monograph, botanica
|
||||||
|
theme:
|
||||||
|
preset: "studio"
|
||||||
|
# accent: "#e60012"
|
||||||
|
# photoFrame: "passepartout" # none, passepartout, shadow
|
||||||
|
# grain: true
|
||||||
|
|
||||||
|
# ── Grid configuration ───────────────────────────────────────────
|
||||||
|
grid:
|
||||||
|
columns: 3
|
||||||
|
gap: 12
|
||||||
|
aspectRatio: "1" # "1" (square), "3/2", "2/3", "16/9", "auto"
|
||||||
|
layout: "masonry" # masonry, uniform, showcase, filmstrip, editorial-flow
|
||||||
|
|
||||||
|
# ── Footer & Social ──────────────────────────────────────────────
|
||||||
|
footer:
|
||||||
|
name: "My Photography"
|
||||||
|
instagram: "https://instagram.com/your-handle"
|
||||||
|
email: "hello@example.com"
|
||||||
|
website: "https://example.com"
|
||||||
|
|
||||||
|
# ── Legal / Impressum (German Law compliance) ────────────────────
|
||||||
|
# If enabled: true, a link to /impressum will appear in the footer.
|
||||||
|
legal:
|
||||||
|
enabled: false
|
||||||
|
name: "Your Full Name"
|
||||||
|
address: "Street and House Number"
|
||||||
|
zipCity: "ZIP Code and City"
|
||||||
|
country: "Germany"
|
||||||
|
email: "legal@example.com"
|
||||||
|
phone: "+49 123 456789"
|
||||||
|
# taxId: "DE123456789"
|
||||||
|
# vatId: "12/345/67890"
|
||||||
|
extraInfo: "Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV: Max Mustermann"
|
||||||
134
lib/config.ts
134
lib/config.ts
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Typed configuration loader.
|
* Typed configuration loader.
|
||||||
* Secrets come from env vars; gallery structure comes from content/gallery.yaml.
|
* Secrets come from env vars; gallery structure comes from content/gallery.yaml.
|
||||||
|
* Global settings come from content/settings.yaml.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
@@ -8,8 +9,6 @@ import path from 'path';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { env } from './env';
|
import { env } from './env';
|
||||||
|
|
||||||
// ── Env helpers (now handled by lib/env.ts via Zod) ───────────
|
|
||||||
|
|
||||||
// ── Types ──────────────────────────────────────────────────────
|
// ── Types ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** A subpage groups multiple Immich albums under one URL path. */
|
/** A subpage groups multiple Immich albums under one URL path. */
|
||||||
@@ -31,6 +30,20 @@ export interface FooterConfig {
|
|||||||
website?: string;
|
website?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Legal / Impressum configuration. */
|
||||||
|
export interface LegalConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
zipCity: string;
|
||||||
|
country: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
taxId?: string;
|
||||||
|
vatId?: string;
|
||||||
|
extraInfo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/** Theme configuration — controls the visual identity of the gallery. */
|
/** Theme configuration — controls the visual identity of the gallery. */
|
||||||
export interface ThemeConfig {
|
export interface ThemeConfig {
|
||||||
preset: string;
|
preset: string;
|
||||||
@@ -118,10 +131,29 @@ const THEME_PRESETS: Record<string, ThemeConfig> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Raw YAML structure (before validation). */
|
/** Raw YAML structure for gallery structure. */
|
||||||
interface GalleryYaml {
|
interface GalleryYaml {
|
||||||
hero?: string | string[];
|
hero?: string | string[];
|
||||||
albums?: string[];
|
albums?: string[];
|
||||||
|
subpages?:
|
||||||
|
| Record<string, string[] | Array<string | Record<string, string>>>
|
||||||
|
| Array<{
|
||||||
|
name: string;
|
||||||
|
albums: Array<string | Record<string, string>>;
|
||||||
|
password?: string;
|
||||||
|
grid?: {
|
||||||
|
columns?: number;
|
||||||
|
gap?: number;
|
||||||
|
aspectRatio?: string;
|
||||||
|
layout?: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Raw YAML structure for global settings. */
|
||||||
|
interface SettingsYaml {
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
exifOnHover?: boolean;
|
exifOnHover?: boolean;
|
||||||
map?: boolean;
|
map?: boolean;
|
||||||
transitions?: boolean;
|
transitions?: boolean;
|
||||||
@@ -143,47 +175,28 @@ interface GalleryYaml {
|
|||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
layout?: string;
|
layout?: string;
|
||||||
};
|
};
|
||||||
footer?: {
|
footer?: FooterConfig;
|
||||||
name?: string;
|
legal?: Partial<LegalConfig>;
|
||||||
instagram?: string;
|
|
||||||
email?: string;
|
|
||||||
website?: string;
|
|
||||||
};
|
|
||||||
subpages?:
|
|
||||||
| Record<string, string[] | Array<string | Record<string, string>>>
|
|
||||||
| Array<{
|
|
||||||
name: string;
|
|
||||||
albums: Array<string | Record<string, string>>;
|
|
||||||
password?: string;
|
|
||||||
grid?: {
|
|
||||||
columns?: number;
|
|
||||||
gap?: number;
|
|
||||||
aspectRatio?: string;
|
|
||||||
layout?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── YAML loading ───────────────────────────────────────────────
|
// ── YAML loading ───────────────────────────────────────────────
|
||||||
|
|
||||||
function loadGalleryYaml(): GalleryYaml {
|
function loadYaml<T>(filename: string): T {
|
||||||
const yamlPath = path.join(process.cwd(), 'content', 'gallery.yaml');
|
const yamlPath = path.join(process.cwd(), 'content', filename);
|
||||||
|
|
||||||
if (!fs.existsSync(yamlPath)) {
|
if (!fs.existsSync(yamlPath)) {
|
||||||
throw new Error(
|
// gallery.yaml is required, settings.yaml can fallback to defaults
|
||||||
`Gallery config not found: ${yamlPath}\n` +
|
if (filename === 'gallery.yaml') {
|
||||||
`Copy content/gallery.yaml.example to content/gallery.yaml and add your album IDs.`,
|
throw new Error(
|
||||||
);
|
`Required config not found: ${yamlPath}\n` +
|
||||||
|
`Copy content/gallery.yaml.example to content/gallery.yaml and add your album IDs.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {} as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = fs.readFileSync(yamlPath, 'utf8');
|
const raw = fs.readFileSync(yamlPath, 'utf8');
|
||||||
const data = yaml.load(raw) as GalleryYaml;
|
return (yaml.load(raw) || {}) as T;
|
||||||
|
|
||||||
if (!data || typeof data !== 'object') {
|
|
||||||
throw new Error('gallery.yaml is empty or invalid');
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── UUID validation ────────────────────────────────────────────
|
// ── UUID validation ────────────────────────────────────────────
|
||||||
@@ -242,6 +255,8 @@ export interface AppConfig {
|
|||||||
theme: ThemeConfig;
|
theme: ThemeConfig;
|
||||||
/** Footer social links config. */
|
/** Footer social links config. */
|
||||||
footer: FooterConfig | null;
|
footer: FooterConfig | null;
|
||||||
|
/** Legal / Impressum configuration. */
|
||||||
|
legal: LegalConfig;
|
||||||
/** Show the /map page. Default: false. */
|
/** Show the /map page. Default: false. */
|
||||||
map: boolean;
|
map: boolean;
|
||||||
/** Enable page transitions between routes. Default: true. */
|
/** Enable page transitions between routes. Default: true. */
|
||||||
@@ -258,7 +273,7 @@ const VALID_PHOTO_FRAMES = ['none', 'passepartout', 'shadow'];
|
|||||||
const VALID_HERO_STYLES = ['split', 'fullbleed', 'minimal', 'stacked', 'typographic', 'mosaic'];
|
const VALID_HERO_STYLES = ['split', 'fullbleed', 'minimal', 'stacked', 'typographic', 'mosaic'];
|
||||||
const VALID_LAYOUTS = ['masonry', 'uniform', 'showcase', 'filmstrip', 'editorial-flow'];
|
const VALID_LAYOUTS = ['masonry', 'uniform', 'showcase', 'filmstrip', 'editorial-flow'];
|
||||||
|
|
||||||
function resolveTheme(raw?: GalleryYaml['theme']): ThemeConfig {
|
function resolveTheme(raw?: SettingsYaml['theme']): ThemeConfig {
|
||||||
// No theme key → default preset
|
// No theme key → default preset
|
||||||
if (!raw) return { ...THEME_PRESETS.studio };
|
if (!raw) return { ...THEME_PRESETS.studio };
|
||||||
|
|
||||||
@@ -331,10 +346,11 @@ export function getConfig(): AppConfig {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gallery = loadGalleryYaml();
|
const gallery = loadYaml<GalleryYaml>('gallery.yaml');
|
||||||
|
const settings = loadYaml<SettingsYaml>('settings.yaml');
|
||||||
|
|
||||||
// Resolve theme: string shorthand → preset, object → merged with preset base
|
// Resolve theme: string shorthand → preset, object → merged with preset base
|
||||||
const theme = resolveTheme(gallery.theme);
|
const theme = resolveTheme(settings.theme);
|
||||||
|
|
||||||
const albumOverrides: Record<string, string> = {};
|
const albumOverrides: Record<string, string> = {};
|
||||||
|
|
||||||
@@ -451,33 +467,45 @@ export function getConfig(): AppConfig {
|
|||||||
albums: allAlbumIds,
|
albums: allAlbumIds,
|
||||||
standaloneAlbums: standaloneAlbumIds.filter((id) => !subpageAlbumIds.has(id)),
|
standaloneAlbums: standaloneAlbumIds.filter((id) => !subpageAlbumIds.has(id)),
|
||||||
subpages,
|
subpages,
|
||||||
siteTitle: env.SITE_TITLE,
|
siteTitle: settings.title ?? env.SITE_TITLE,
|
||||||
siteSubtitle: env.SITE_SUBTITLE,
|
siteSubtitle: settings.subtitle ?? env.SITE_SUBTITLE,
|
||||||
heroImages: gallery.hero
|
heroImages: gallery.hero
|
||||||
? (Array.isArray(gallery.hero) ? gallery.hero : [gallery.hero]).map((id) =>
|
? (Array.isArray(gallery.hero) ? gallery.hero : [gallery.hero]).map((id) =>
|
||||||
validateUuid(id, 'gallery.yaml hero'),
|
validateUuid(id, 'gallery.yaml hero'),
|
||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
exifOnHover: gallery.exifOnHover !== false,
|
exifOnHover: settings.exifOnHover !== false,
|
||||||
grid: {
|
grid: {
|
||||||
columns: gallery.grid?.columns ?? 3,
|
columns: settings.grid?.columns ?? 3,
|
||||||
gap: gallery.grid?.gap ?? 12,
|
gap: settings.grid?.gap ?? 12,
|
||||||
aspectRatio: gallery.grid?.aspectRatio ?? '1',
|
aspectRatio: settings.grid?.aspectRatio ?? '1',
|
||||||
layout: (VALID_LAYOUTS.includes(gallery.grid?.layout ?? '')
|
layout: (VALID_LAYOUTS.includes(settings.grid?.layout ?? '')
|
||||||
? gallery.grid!.layout
|
? settings.grid!.layout
|
||||||
: 'masonry') as GridConfig['layout'],
|
: 'masonry') as GridConfig['layout'],
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
footer: gallery.footer
|
footer: settings.footer
|
||||||
? {
|
? {
|
||||||
name: gallery.footer.name,
|
name: settings.footer.name,
|
||||||
instagram: gallery.footer.instagram,
|
instagram: settings.footer.instagram,
|
||||||
email: gallery.footer.email,
|
email: settings.footer.email,
|
||||||
website: gallery.footer.website,
|
website: settings.footer.website,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
map: gallery.map === true,
|
legal: {
|
||||||
transitions: gallery.transitions !== false,
|
enabled: settings.legal?.enabled === true,
|
||||||
|
name: settings.legal?.name || '',
|
||||||
|
address: settings.legal?.address || '',
|
||||||
|
zipCity: settings.legal?.zipCity || '',
|
||||||
|
country: settings.legal?.country || '',
|
||||||
|
email: settings.legal?.email,
|
||||||
|
phone: settings.legal?.phone,
|
||||||
|
taxId: settings.legal?.taxId,
|
||||||
|
vatId: settings.legal?.vatId,
|
||||||
|
extraInfo: settings.legal?.extraInfo,
|
||||||
|
},
|
||||||
|
map: settings.map === true,
|
||||||
|
transitions: settings.transitions !== false,
|
||||||
albumOverrides,
|
albumOverrides,
|
||||||
cacheTtl: env.CACHE_TTL * 1000,
|
cacheTtl: env.CACHE_TTL * 1000,
|
||||||
rateLimitRpm: env.RATE_LIMIT_RPM,
|
rateLimitRpm: env.RATE_LIMIT_RPM,
|
||||||
|
|||||||
Reference in New Issue
Block a user