diff --git a/README.md b/README.md
index 054eb33..0cbaa4f 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,22 @@ theme: studio # or: minimal, editorial, classic, noir, monograph, botanica
 |
 |
+
+ Noir Darkroom analog, film noir |
+ Monograph Typographic, book-like |
+
+
+  |
+  |
+
+
+ Botanica Organic, nature-inspired |
+ |
+
+
+  |
+ |
+
ā **[Theming Guide](docs/theming.md)**
diff --git a/docs/screenshots/hero-fullbleed-home.png b/docs/screenshots/hero-fullbleed-home.png
new file mode 100644
index 0000000..2b9f277
Binary files /dev/null and b/docs/screenshots/hero-fullbleed-home.png differ
diff --git a/docs/screenshots/hero-minimal-home.png b/docs/screenshots/hero-minimal-home.png
new file mode 100644
index 0000000..2c5aeb2
Binary files /dev/null and b/docs/screenshots/hero-minimal-home.png differ
diff --git a/docs/screenshots/hero-mosaic-home.png b/docs/screenshots/hero-mosaic-home.png
new file mode 100644
index 0000000..a6f355c
Binary files /dev/null and b/docs/screenshots/hero-mosaic-home.png differ
diff --git a/docs/screenshots/hero-split-home.png b/docs/screenshots/hero-split-home.png
new file mode 100644
index 0000000..cf5435d
Binary files /dev/null and b/docs/screenshots/hero-split-home.png differ
diff --git a/docs/screenshots/hero-stacked-home.png b/docs/screenshots/hero-stacked-home.png
new file mode 100644
index 0000000..da7355e
Binary files /dev/null and b/docs/screenshots/hero-stacked-home.png differ
diff --git a/docs/screenshots/hero-typographic-home.png b/docs/screenshots/hero-typographic-home.png
new file mode 100644
index 0000000..2a8e146
Binary files /dev/null and b/docs/screenshots/hero-typographic-home.png differ
diff --git a/docs/screenshots/theme-botanica-grid.png b/docs/screenshots/theme-botanica-grid.png
new file mode 100644
index 0000000..239bb08
Binary files /dev/null and b/docs/screenshots/theme-botanica-grid.png differ
diff --git a/docs/screenshots/theme-botanica-home.png b/docs/screenshots/theme-botanica-home.png
new file mode 100644
index 0000000..885f732
Binary files /dev/null and b/docs/screenshots/theme-botanica-home.png differ
diff --git a/docs/screenshots/theme-classic-grid.png b/docs/screenshots/theme-classic-grid.png
index a08c5ad..e6c31d3 100644
Binary files a/docs/screenshots/theme-classic-grid.png and b/docs/screenshots/theme-classic-grid.png differ
diff --git a/docs/screenshots/theme-classic-home.png b/docs/screenshots/theme-classic-home.png
index 91cd245..b136808 100644
Binary files a/docs/screenshots/theme-classic-home.png and b/docs/screenshots/theme-classic-home.png differ
diff --git a/docs/screenshots/theme-editorial-grid.png b/docs/screenshots/theme-editorial-grid.png
index 6ff2a08..6fb0baf 100644
Binary files a/docs/screenshots/theme-editorial-grid.png and b/docs/screenshots/theme-editorial-grid.png differ
diff --git a/docs/screenshots/theme-editorial-home.png b/docs/screenshots/theme-editorial-home.png
index bc36e49..ce90ca4 100644
Binary files a/docs/screenshots/theme-editorial-home.png and b/docs/screenshots/theme-editorial-home.png differ
diff --git a/docs/screenshots/theme-minimal-grid.png b/docs/screenshots/theme-minimal-grid.png
index 4c5e207..87cb2e1 100644
Binary files a/docs/screenshots/theme-minimal-grid.png and b/docs/screenshots/theme-minimal-grid.png differ
diff --git a/docs/screenshots/theme-minimal-home.png b/docs/screenshots/theme-minimal-home.png
index 6f87b00..f243ccc 100644
Binary files a/docs/screenshots/theme-minimal-home.png and b/docs/screenshots/theme-minimal-home.png differ
diff --git a/docs/screenshots/theme-monograph-grid.png b/docs/screenshots/theme-monograph-grid.png
new file mode 100644
index 0000000..d17ae4d
Binary files /dev/null and b/docs/screenshots/theme-monograph-grid.png differ
diff --git a/docs/screenshots/theme-monograph-home.png b/docs/screenshots/theme-monograph-home.png
new file mode 100644
index 0000000..3c15ce5
Binary files /dev/null and b/docs/screenshots/theme-monograph-home.png differ
diff --git a/docs/screenshots/theme-noir-grid.png b/docs/screenshots/theme-noir-grid.png
new file mode 100644
index 0000000..f4db044
Binary files /dev/null and b/docs/screenshots/theme-noir-grid.png differ
diff --git a/docs/screenshots/theme-noir-home.png b/docs/screenshots/theme-noir-home.png
new file mode 100644
index 0000000..03b4028
Binary files /dev/null and b/docs/screenshots/theme-noir-home.png differ
diff --git a/docs/screenshots/theme-studio-grid.png b/docs/screenshots/theme-studio-grid.png
index 2ffc4d5..141e1bf 100644
Binary files a/docs/screenshots/theme-studio-grid.png and b/docs/screenshots/theme-studio-grid.png differ
diff --git a/docs/screenshots/theme-studio-home.png b/docs/screenshots/theme-studio-home.png
index 6f77833..2b9f277 100644
Binary files a/docs/screenshots/theme-studio-home.png and b/docs/screenshots/theme-studio-home.png differ
diff --git a/docs/theming.md b/docs/theming.md
index a5cac08..5a1f258 100644
--- a/docs/theming.md
+++ b/docs/theming.md
@@ -26,6 +26,41 @@ That's it. Your entire gallery switches to the minimal theme.
Default is `studio` if no theme is specified.
+
+
+ Studio Leica-inspired, red dot |
+ Minimal Swiss brutalist, fullbleed hero |
+
+
+  |
+  |
+
+
+ Editorial Cinematic magazine, warm charcoal |
+ Classic Gilded gallery, gold accents |
+
+
+  |
+  |
+
+
+ Noir Darkroom analog, film noir |
+ Monograph Typographic, book-like |
+
+
+  |
+  |
+
+
+ Botanica Organic, nature-inspired |
+ |
+
+
+  |
+ |
+
+
+
### What makes each theme unique
**Studio** (default) ā Leica-inspired with a red nav dot, passepartout photo frames, film grain, and a split hero layout. Clean editorial feel with Playfair Display headings.
@@ -112,6 +147,29 @@ Controls the homepage hero layout:
- **`typographic`** ā no hero image; massive centered title with numbered album navigation list (used by Monograph)
- **`mosaic`** ā asymmetric multi-image grid with frosted-glass title overlay centered on top
+
+
+ | Split |
+ Fullbleed |
+ Minimal |
+
+
+  |
+  |
+  |
+
+
+ | Stacked |
+ Typographic |
+ Mosaic |
+
+
+  |
+  |
+  |
+
+
+
### `grid.layout`
Controls the photo grid layout on album pages:
diff --git a/scripts/screenshots.ts b/scripts/screenshots.ts
index 50b00de..1cfaac4 100644
--- a/scripts/screenshots.ts
+++ b/scripts/screenshots.ts
@@ -19,11 +19,11 @@ import { spawn, type ChildProcess } from 'child_process';
import fs from 'fs';
import path from 'path';
-const THEMES = ['studio', 'minimal', 'editorial', 'classic'];
+const THEMES = ['studio', 'minimal', 'editorial', 'classic', 'noir', 'monograph', 'botanica'];
const BASE_URL = 'http://localhost:3000';
-const GRID_PATH = '/deutschland/kloster-chorin-2024'; // public subpage with photos
+const GRID_PATH = '/deutschland/kloster-chorin'; // public subpage with photos
const OUTPUT_DIR = path.join(process.cwd(), 'docs', 'screenshots');
-const GALLERY_YAML = path.join(process.cwd(), 'content', 'gallery.yaml');
+const SETTINGS_YAML = path.join(process.cwd(), 'content', 'settings.yaml');
const VIEWPORT = { width: 1440, height: 900 };
const IMAGE_LOAD_TIMEOUT = 8000; // ms to wait for images after networkidle
@@ -31,8 +31,8 @@ async function main() {
// Ensure output directory exists
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
- // Read original gallery.yaml to restore later
- const originalYaml = fs.readFileSync(GALLERY_YAML, 'utf8');
+ // Read original settings.yaml to restore later
+ const originalYaml = fs.readFileSync(SETTINGS_YAML, 'utf8');
// Dynamic import for Playwright (may not be installed globally)
const { chromium } = await import('playwright');
@@ -43,9 +43,9 @@ async function main() {
console.log(`\nšØ Theme: ${theme}`);
console.log('ā'.repeat(50));
- // Update gallery.yaml with the theme
- const yamlWithTheme = setThemeInYaml(originalYaml, theme);
- fs.writeFileSync(GALLERY_YAML, yamlWithTheme);
+ // Update settings.yaml with the theme
+ const yamlWithTheme = setThemeInSettingsYaml(originalYaml, theme);
+ fs.writeFileSync(SETTINGS_YAML, yamlWithTheme);
// Start dev server
console.log(' ā³ Starting dev server...');
@@ -89,13 +89,62 @@ async function main() {
await context.close();
// Stop dev server
- server.kill('SIGTERM');
- await new Promise((resolve) => server.on('close', resolve));
+ if (server.pid) {
+ process.kill(-server.pid, 'SIGTERM');
+ }
+ await new Promise((resolve) => {
+ const timeout = setTimeout(resolve, 3000);
+ server.on('close', () => {
+ clearTimeout(timeout);
+ resolve(null);
+ });
+ });
+ console.log(' š Server stopped');
+ }
+
+ const HERO_STYLES = ['split', 'fullbleed', 'minimal', 'stacked', 'typographic', 'mosaic'];
+ for (const hero of HERO_STYLES) {
+ console.log(`\nš¼ Hero Style: ${hero} (Theme: studio)`);
+ console.log('ā'.repeat(50));
+
+ const yamlWithTheme = setThemeInSettingsYaml(originalYaml, 'studio');
+ const yamlWithHero = setHeroStyleInSettingsYaml(yamlWithTheme, hero);
+ fs.writeFileSync(SETTINGS_YAML, yamlWithHero);
+
+ console.log(' ā³ Starting dev server...');
+ const server = startDevServer();
+ await waitForServer(BASE_URL, 15000);
+ console.log(' ā
Server ready');
+
+ const context = await browser.newContext({ viewport: VIEWPORT });
+ const page = await context.newPage();
+
+ console.log(' šø Capturing homepage hero...');
+ await page.goto(BASE_URL, { waitUntil: 'networkidle' });
+ await waitForImages(page);
+ await page.screenshot({
+ path: path.join(OUTPUT_DIR, `hero-${hero}-home.png`),
+ fullPage: false,
+ });
+ console.log(` ā
hero-${hero}-home.png`);
+
+ await context.close();
+
+ if (server.pid) {
+ process.kill(-server.pid, 'SIGTERM');
+ }
+ await new Promise((resolve) => {
+ const timeout = setTimeout(resolve, 3000);
+ server.on('close', () => {
+ clearTimeout(timeout);
+ resolve(null);
+ });
+ });
console.log(' š Server stopped');
}
} finally {
- // Always restore original gallery.yaml
- fs.writeFileSync(GALLERY_YAML, originalYaml);
+ // Always restore original settings.yaml
+ fs.writeFileSync(SETTINGS_YAML, originalYaml);
console.log('\nā
Restored original gallery.yaml');
await browser.close();
}
@@ -111,14 +160,35 @@ async function main() {
// āā Helpers āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
-function setThemeInYaml(yaml: string, theme: string): string {
- // Replace existing theme line or insert after the header comment
- if (/^theme:\s*.+$/m.test(yaml)) {
- return yaml.replace(/^theme:\s*.+$/m, `theme: ${theme}`);
+/**
+ * Replace the theme preset in settings.yaml.
+ * Handles both `theme: preset` shorthand and `theme:\n preset: "x"` block.
+ */
+function setThemeInSettingsYaml(yaml: string, theme: string): string {
+ // Handle ` preset: "studio"` or ` preset: studio` (block format)
+ if (/^ preset:\s*/m.test(yaml)) {
+ return yaml.replace(/^ preset:\s*.+$/m, ` preset: "${theme}"`);
}
- // Insert theme after the header comments
- const marker = '# Secrets (API key, server URL) stay in .env.local.';
- return yaml.replace(marker, `${marker}\n\ntheme: ${theme}`);
+ // Handle `theme: studio` shorthand
+ if (/^theme:\s*\w+\s*$/m.test(yaml)) {
+ return yaml.replace(/^theme:\s*\w+\s*$/m, `theme: ${theme}`);
+ }
+ // Fallback: insert block after the theme comment
+ const marker = '# Presets: studio, minimal, editorial, classic, noir, monograph, botanica';
+ if (yaml.includes(marker)) {
+ return yaml.replace(marker, `${marker}\ntheme: ${theme}`);
+ }
+ return yaml + `\ntheme: ${theme}\n`;
+}
+
+/**
+ * Replace the heroStyle in settings.yaml.
+ */
+function setHeroStyleInSettingsYaml(yaml: string, style: string): string {
+ if (/^ heroStyle:\s*/m.test(yaml)) {
+ return yaml.replace(/^ heroStyle:\s*.+$/m, ` heroStyle: "${style}"`);
+ }
+ return yaml;
}
function startDevServer(): ChildProcess {
@@ -126,6 +196,7 @@ function startDevServer(): ChildProcess {
cwd: process.cwd(),
stdio: 'pipe',
env: { ...process.env },
+ detached: true,
});
// Suppress server output unless DEBUG is set
@@ -196,8 +267,8 @@ main().catch((err) => {
console.error('ā Screenshot generation failed:', err);
// Try to restore gallery.yaml on error
try {
- const original = fs.readFileSync(GALLERY_YAML + '.bak', 'utf8');
- fs.writeFileSync(GALLERY_YAML, original);
+ const original = fs.readFileSync(SETTINGS_YAML + '.bak', 'utf8');
+ fs.writeFileSync(SETTINGS_YAML, original);
} catch {
// Backup may not exist
}