feat: restyle themes to be visually distinct

- Minimal: Swiss Brutalist — fullbleed hero, true B&W, zero-gap grid, Geist font
- Editorial: Cinematic Magazine — warm charcoal, Bodoni Moda serif, slow transitions
- Classic: Gilded Gallery — gold accents, Cinzel headings, passepartout, ornamental dividers

Updated config presets, per-theme color palettes, ~550 lines of scoped CSS overrides,
data-preset attribute on html element, responsive breakpoints, screenshots, and docs.
This commit is contained in:
ralksta
2026-02-15 17:17:25 +01:00
parent a9737ad8e7
commit 570cabc972
7 changed files with 778 additions and 56 deletions

View File

@@ -4,11 +4,16 @@ A self-hosted photography portfolio powered by [Immich](https://immich.app). Tur
Immich Folio acts as a **secure reverse proxy** between your visitors and your private Immich instance. Your Immich server stays on your local network, completely invisible to the outside world. Immich Folio acts as a **secure reverse proxy** between your visitors and your private Immich instance. Your Immich server stays on your local network, completely invisible to the outside world.
<p align="center">
<img src="docs/screenshots/theme-editorial-home.png" width="49%" alt="Homepage — Editorial theme" />
<img src="docs/screenshots/theme-editorial-grid.png" width="49%" alt="Photo grid — Editorial theme" />
</p>
## Features ## Features
### Gallery & Layout ### Gallery & Layout
- **Split hero homepage** — full-height hero with site title, subtitle, and navigation links on the left; hero image on the right - **Configurable hero layouts** — split (title + image side-by-side), fullbleed (edge-to-edge hero image), or minimal (centered title card)
- **Hero image carousel** — single image or crossfade carousel of multiple Immich assets - **Hero image carousel** — single image or crossfade carousel of multiple Immich assets
- **Masonry photo grid** — responsive layout with natural aspect ratios and configurable columns, gap, and aspect ratio - **Masonry photo grid** — responsive layout with natural aspect ratios and configurable columns, gap, and aspect ratio
- **Uniform grid mode** — switch to a fixed-aspect uniform grid per-subpage or globally - **Uniform grid mode** — switch to a fixed-aspect uniform grid per-subpage or globally
@@ -86,12 +91,31 @@ All gallery structure — hero images, albums, subpages, grid layout, footer —
### Theming ### Theming
Customize the visual identity of your gallery with built-in presets or fine-grained control over colors, fonts, corners, photo frames, and more. Four built-in presets with distinct visual identities — or mix and match with fine-grained control over colors, fonts, corners, photo frames, and hero layout.
```yaml ```yaml
theme: minimal # or: studio, editorial, classic theme: studio # or: minimal, editorial, classic
``` ```
<table>
<tr>
<td align="center"><strong>Studio</strong><br><em>Leica-inspired, red dot</em></td>
<td align="center"><strong>Minimal</strong><br><em>Swiss brutalist, fullbleed hero</em></td>
</tr>
<tr>
<td><img src="docs/screenshots/theme-studio-home.png" width="400" alt="Studio theme" /></td>
<td><img src="docs/screenshots/theme-minimal-home.png" width="400" alt="Minimal theme" /></td>
</tr>
<tr>
<td align="center"><strong>Editorial</strong><br><em>Cinematic magazine, warm charcoal</em></td>
<td align="center"><strong>Classic</strong><br><em>Gilded gallery, gold accents</em></td>
</tr>
<tr>
<td><img src="docs/screenshots/theme-editorial-home.png" width="400" alt="Editorial theme" /></td>
<td><img src="docs/screenshots/theme-classic-home.png" width="400" alt="Classic theme" /></td>
</tr>
</table>
→ **[Theming Guide](docs/theming.md)** → **[Theming Guide](docs/theming.md)**
## Docker ## Docker

View File

@@ -279,10 +279,12 @@
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
padding: 24px; padding: 24px;
background: linear-gradient(to top, background: linear-gradient(
rgba(0, 0, 0, 0.8) 0%, to top,
rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.8) 0%,
transparent 100%); rgba(0, 0, 0, 0.3) 40%,
transparent 100%
);
opacity: 0; opacity: 0;
transition: opacity var(--transition-normal); transition: opacity var(--transition-normal);
} }
@@ -412,10 +414,12 @@
/* ── Loading States ───────────────────────────────── */ /* ── Loading States ───────────────────────────────── */
.skeleton { .skeleton {
background: linear-gradient(90deg, background: linear-gradient(
var(--bg-card) 0%, 90deg,
var(--bg-card-hover) 50%, var(--bg-card) 0%,
var(--bg-card) 100%); var(--bg-card-hover) 50%,
var(--bg-card) 100%
);
background-size: 200% 100%; background-size: 200% 100%;
animation: skeleton-shimmer 1.5s infinite; animation: skeleton-shimmer 1.5s infinite;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
@@ -792,7 +796,557 @@
color: var(--accent); color: var(--accent);
} }
/* ── Responsive ───────────────────────────────────── */ /* ══════════════════════════════════════════════════════
THEME: Minimal — Swiss Brutalist
══════════════════════════════════════════════════════ */
/* Header: ultra-stripped, no border, just floating text */
[data-preset='minimal'] .header {
border-bottom: none;
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
[data-preset='minimal'] .header__nav-link {
font-size: 0.6rem;
font-weight: 400;
letter-spacing: 0.18em;
color: var(--text-muted);
border-bottom: none;
}
[data-preset='minimal'] .header__nav-link:hover,
[data-preset='minimal'] .header__nav-link.active {
color: var(--text-primary);
border-bottom-color: transparent;
}
/* Hero: fullbleed image, title overlaid bottom-left */
[data-preset='minimal'] .hero {
grid-template-columns: 1fr;
position: relative;
}
[data-preset='minimal'] .hero__left {
position: absolute;
bottom: 0;
left: 0;
z-index: 2;
padding: 32px 40px;
background: none;
align-items: flex-start;
justify-content: flex-end;
}
[data-preset='minimal'] .hero__content {
text-align: left;
}
[data-preset='minimal'] .hero__title {
font-family: var(--font-sans);
font-size: 0.7rem;
font-weight: 400;
letter-spacing: 0.2em;
text-transform: uppercase;
opacity: 0.6;
}
[data-preset='minimal'] .hero__subtitle {
font-size: 0.6rem;
letter-spacing: 0.15em;
margin-bottom: 16px;
opacity: 0.4;
}
[data-preset='minimal'] .hero__nav {
flex-direction: row;
gap: 16px;
}
[data-preset='minimal'] .hero__nav-link {
font-size: 0.6rem;
letter-spacing: 0.15em;
text-transform: uppercase;
border-bottom: none;
opacity: 0.5;
transition: opacity var(--transition-fast);
}
[data-preset='minimal'] .hero__nav-link:hover {
opacity: 1;
border-bottom-color: transparent;
color: var(--text-primary);
}
[data-preset='minimal'] .hero__right {
grid-column: 1;
grid-row: 1;
height: 100vh;
height: 100svh;
}
/* Photo grid: zero hover effects, clinical stillness */
[data-preset='minimal'] .photo-grid {
column-gap: 2px;
}
[data-preset='minimal'] .photo-grid__item {
margin-bottom: 2px;
border-radius: 0;
cursor: pointer;
}
[data-preset='minimal'] .photo-grid__item:hover img {
filter: none;
transform: none;
}
[data-preset='minimal'] .photo-grid__item:active img {
filter: brightness(0.8);
transform: none;
}
/* Album cards: no overlay, text below */
[data-preset='minimal'] .album-card {
border: none;
border-radius: 0;
}
[data-preset='minimal'] .album-card:hover {
border-color: transparent;
transform: none;
}
[data-preset='minimal'] .album-card:hover .album-card__image {
transform: none;
filter: none;
}
[data-preset='minimal'] .album-card__overlay {
background: linear-gradient(to top, rgba(0, 0, 0, 0.4) 0%, transparent 30%);
opacity: 1;
}
[data-preset='minimal'] .album-card__title {
font-family: var(--font-sans);
font-size: 0.7rem;
font-weight: 400;
letter-spacing: 0.15em;
text-transform: uppercase;
}
[data-preset='minimal'] .album-card__count {
font-size: 0.6rem;
font-weight: 400;
letter-spacing: 0.1em;
}
/* Section labels: numbered indices */
[data-preset='minimal'] .section-label {
font-size: 0.6rem;
letter-spacing: 0.2em;
color: var(--text-muted);
}
/* Footer: single line, ultra-minimal */
[data-preset='minimal'] .footer {
border-top: none;
}
[data-preset='minimal'] .footer__name {
font-size: 0.6rem;
letter-spacing: 0.15em;
}
/* Album header */
[data-preset='minimal'] .album-header__title {
font-family: var(--font-sans);
font-size: 0.85rem;
letter-spacing: 0.2em;
text-transform: uppercase;
font-weight: 400;
}
[data-preset='minimal'] .album-header__meta {
font-size: 0.7rem;
letter-spacing: 0.1em;
}
/* Subpage grid */
[data-preset='minimal'] .subpage-grid {
gap: 2px;
}
[data-preset='minimal'] .subpage-grid__item {
border: none;
}
[data-preset='minimal'] .subpage-grid__item-title {
font-family: var(--font-sans);
font-size: 0.75rem;
letter-spacing: 0.12em;
text-transform: uppercase;
}
/* ══════════════════════════════════════════════════════
THEME: Editorial — Cinematic Magazine
══════════════════════════════════════════════════════ */
/* Header: transparent, magazine masthead feel */
[data-preset='editorial'] .header {
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border-bottom: 1px solid var(--border-subtle);
}
[data-preset='editorial'] .header__nav-link {
font-family: var(--font-sans);
font-size: 0.65rem;
font-weight: 400;
letter-spacing: 0.18em;
}
/* Hero: dramatic oversized serif title */
[data-preset='editorial'] .hero__title {
font-size: 4.5rem;
font-weight: 300;
letter-spacing: 0.02em;
text-transform: none;
line-height: 1;
}
[data-preset='editorial'] .hero__subtitle {
font-family: var(--font-caption);
font-size: 0.9rem;
font-weight: 400;
letter-spacing: 0.08em;
text-transform: none;
font-style: italic;
margin-bottom: 48px;
opacity: 0.7;
}
[data-preset='editorial'] .hero__nav-link {
font-family: var(--font-caption);
font-size: 0.8rem;
font-style: italic;
font-weight: 400;
letter-spacing: 0.06em;
text-transform: none;
border-bottom: none;
}
[data-preset='editorial'] .hero__nav-link:hover {
color: var(--accent);
border-bottom-color: transparent;
}
/* Photo grid: generous spacing, cinematic transitions */
[data-preset='editorial'] .photo-grid {
column-gap: 32px;
}
[data-preset='editorial'] .photo-grid__item {
margin-bottom: 32px;
}
[data-preset='editorial'] .photo-grid__item img {
transition:
filter 1.5s ease,
transform 1.5s ease;
filter: saturate(0.7) brightness(0.95);
}
[data-preset='editorial'] .photo-grid__item:hover img {
filter: saturate(1) brightness(1);
transform: scale(1.01);
}
/* Album cards: text below, editorial layout */
[data-preset='editorial'] .album-card {
border: none;
border-radius: 0;
aspect-ratio: 16 / 9;
}
[data-preset='editorial'] .album-card:hover {
transform: none;
border-color: transparent;
}
[data-preset='editorial'] .album-card__overlay {
justify-content: flex-start;
padding: 28px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, transparent 40%);
}
[data-preset='editorial'] .album-card__title {
font-size: 2rem;
font-weight: 300;
letter-spacing: 0.02em;
text-transform: none;
}
[data-preset='editorial'] .album-card__count {
font-family: var(--font-caption);
font-style: italic;
font-size: 0.8rem;
}
[data-preset='editorial'] .album-card:hover .album-card__image {
transform: scale(1.015);
filter: brightness(0.8);
transition:
transform 1.5s ease,
filter 1.5s ease;
}
/* Section labels: pull-quote style with left border */
[data-preset='editorial'] .section-label {
font-family: var(--font-caption);
font-size: 0.8rem;
font-style: italic;
font-weight: 400;
letter-spacing: 0.08em;
text-transform: none;
color: var(--text-secondary);
padding-left: 16px;
border-left: 2px solid var(--accent);
}
/* Album header: large cinematic title */
[data-preset='editorial'] .album-header__title {
font-size: 3rem;
font-weight: 300;
letter-spacing: 0.02em;
text-transform: none;
}
[data-preset='editorial'] .album-header__meta {
font-family: var(--font-caption);
font-style: italic;
}
/* Footer: centered italic attribution */
[data-preset='editorial'] .footer__name {
font-family: var(--font-caption);
font-style: italic;
text-transform: none;
letter-spacing: 0.06em;
font-size: 0.8rem;
}
/* Subpage grid */
[data-preset='editorial'] .subpage-grid {
gap: 2px;
}
[data-preset='editorial'] .subpage-grid__item {
border: none;
aspect-ratio: 16 / 9;
}
[data-preset='editorial'] .subpage-grid__item-title {
font-size: 1.4rem;
letter-spacing: 0.02em;
font-weight: 300;
text-transform: none;
}
/* ══════════════════════════════════════════════════════
THEME: Classic — Gilded Gallery
══════════════════════════════════════════════════════ */
/* Header: centered nav with gold accent */
[data-preset='classic'] .header {
background: rgba(18, 18, 16, 0.92);
border-bottom: 1px solid rgba(196, 154, 60, 0.15);
}
[data-preset='classic'][data-theme='light'] .header {
background: rgba(253, 251, 245, 0.92);
}
[data-preset='classic'] .header__nav {
justify-content: center;
}
[data-preset='classic'] .header__nav-link {
font-size: 0.7rem;
font-weight: 400;
letter-spacing: 0.14em;
padding: 20px 18px;
}
/* Hero: elegant centered card above banner */
[data-preset='classic'] .hero__left {
padding: 80px 48px;
}
[data-preset='classic'] .hero__title {
font-size: 2.8rem;
font-weight: 400;
letter-spacing: 0.12em;
text-transform: uppercase;
line-height: 1.2;
}
[data-preset='classic'] .hero__subtitle {
font-family: var(--font-caption);
font-size: 0.85rem;
font-weight: 400;
letter-spacing: 0.15em;
font-style: italic;
margin-bottom: 12px;
}
/* Decorative ornament beneath subtitle */
[data-preset='classic'] .hero__subtitle::after {
content: '◆';
display: block;
margin-top: 20px;
font-size: 0.5rem;
color: var(--accent);
letter-spacing: 0;
}
[data-preset='classic'] .hero__nav-link {
font-family: var(--font-caption);
font-size: 0.8rem;
font-weight: 400;
letter-spacing: 0.1em;
font-style: italic;
text-transform: none;
border-bottom: none;
}
[data-preset='classic'] .hero__nav-link:hover {
color: var(--accent);
border-bottom-color: transparent;
}
/* Photo grid: warm passepartout, elegant hover */
[data-preset='classic'] .photo-grid {
column-gap: var(--grid-gap, 20px);
}
[data-preset='classic'] .photo-grid__item {
margin-bottom: var(--grid-gap, 20px);
}
[data-preset='classic'] .photo-grid__item:hover img {
filter: brightness(0.9);
transform: scale(1.01);
}
/* Warm passepartout override */
[data-preset='classic'][data-photo-frame='passepartout'] .photo-grid__item {
background: #1a1816;
border-color: rgba(196, 154, 60, 0.12);
}
[data-preset='classic'][data-photo-frame='passepartout'][data-theme='light'] .photo-grid__item {
background: #fffdf5;
border-color: rgba(196, 154, 60, 0.2);
}
/* Album cards: warm rounded, gold hover */
[data-preset='classic'] .album-card {
border-radius: 12px;
border: 1px solid rgba(196, 154, 60, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
[data-preset='classic'][data-theme='light'] .album-card {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border-color: rgba(196, 154, 60, 0.15);
}
[data-preset='classic'] .album-card:hover {
border-color: var(--accent);
transform: translateY(-3px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
[data-preset='classic'] .album-card__title {
font-weight: 400;
letter-spacing: 0.06em;
}
[data-preset='classic'] .album-card__count {
font-family: var(--font-caption);
font-style: italic;
}
/* Section labels: decorative centered dividers */
[data-preset='classic'] .section-label {
text-align: center;
font-family: var(--font-caption);
font-weight: 400;
letter-spacing: 0.25em;
font-size: 0.7rem;
color: var(--text-muted);
}
[data-preset='classic'] .section-label::before {
content: '— ';
color: var(--accent);
}
[data-preset='classic'] .section-label::after {
content: ' —';
color: var(--accent);
}
/* Album header: elegant serif */
[data-preset='classic'] .album-header__title {
font-weight: 400;
letter-spacing: 0.1em;
font-size: 2.2rem;
}
[data-preset='classic'] .album-header__meta {
font-family: var(--font-caption);
font-style: italic;
}
/* Footer: ornamental */
[data-preset='classic'] .footer {
border-top: 1px solid rgba(196, 154, 60, 0.15);
}
[data-preset='classic'] .footer__name {
font-family: var(--font-caption);
font-style: italic;
text-transform: none;
letter-spacing: 0.08em;
font-size: 0.8rem;
}
/* Scroll-to-top: gold border */
[data-preset='classic'] .scroll-to-top {
border-radius: 12px;
}
[data-preset='classic'] .scroll-to-top:hover {
border-color: var(--accent);
color: var(--accent);
}
/* Subpage grid: warm borders */
[data-preset='classic'] .subpage-grid__item {
border: 1px solid rgba(196, 154, 60, 0.1);
border-radius: 8px;
overflow: hidden;
}
[data-preset='classic'] .subpage-grid__item-title {
font-weight: 400;
letter-spacing: 0.06em;
}
@media (max-width: 1024px) { @media (max-width: 1024px) {
.photo-grid { .photo-grid {
--grid-columns: 2; --grid-columns: 2;
@@ -859,4 +1413,28 @@
[data-photo-frame='passepartout'] .photo-grid__item { [data-photo-frame='passepartout'] .photo-grid__item {
padding: 6px 6px 18px; padding: 6px 6px 18px;
} }
}
/* Minimal: adjust fullbleed hero for mobile */
[data-preset='minimal'] .hero__left {
padding: 20px 24px;
}
[data-preset='minimal'] .hero__right {
height: 70vh;
height: 70svh;
}
/* Editorial: scale down oversized title */
[data-preset='editorial'] .hero__title {
font-size: 2.5rem;
}
[data-preset='editorial'] .album-header__title {
font-size: 2rem;
}
/* Classic: scale title */
[data-preset='classic'] .hero__title {
font-size: 2rem;
}
}

View File

@@ -57,6 +57,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
lang="en" lang="en"
suppressHydrationWarning suppressHydrationWarning
style={themeVars as React.CSSProperties} style={themeVars as React.CSSProperties}
data-preset={theme.preset}
data-grain={theme.grain} data-grain={theme.grain}
data-header-dot={theme.headerDot} data-header-dot={theme.headerDot}
data-photo-frame={theme.photoFrame} data-photo-frame={theme.photoFrame}

View File

@@ -54,6 +54,99 @@
--accent-dim: rgba(200, 0, 15, 0.08); --accent-dim: rgba(200, 0, 15, 0.08);
} }
/* ── Minimal: Swiss Brutalist Palette ────────────── */
[data-preset='minimal'] {
--bg-primary: #000000;
--bg-secondary: #0a0a0a;
--bg-card: #111111;
--bg-card-hover: #1a1a1a;
--bg-overlay: rgba(0, 0, 0, 0.98);
--text-primary: #ffffff;
--text-secondary: #888888;
--text-muted: #555555;
--border-subtle: #1a1a1a;
--border-hover: #333333;
}
[data-preset='minimal'][data-theme='light'] {
--bg-primary: #ffffff;
--bg-secondary: #f7f7f7;
--bg-card: #ffffff;
--bg-card-hover: #fafafa;
--bg-overlay: rgba(255, 255, 255, 0.98);
--text-primary: #000000;
--text-secondary: #666666;
--text-muted: #999999;
--border-subtle: #eeeeee;
--border-hover: #cccccc;
}
/* ── Editorial: Cinematic Magazine Palette ────────── */
[data-preset='editorial'] {
--bg-primary: #1a1714;
--bg-secondary: #141210;
--bg-card: #1e1b17;
--bg-card-hover: #252119;
--bg-overlay: rgba(20, 18, 16, 0.98);
--text-primary: #ede8e0;
--text-secondary: #9a9086;
--text-muted: #6b6359;
--border-subtle: #2a2620;
--border-hover: #3a3530;
}
[data-preset='editorial'][data-theme='light'] {
--bg-primary: #faf8f4;
--bg-secondary: #f0ece4;
--bg-card: #ffffff;
--bg-card-hover: #f5f2ec;
--bg-overlay: rgba(250, 248, 244, 0.96);
--text-primary: #1a1714;
--text-secondary: #5a5550;
--text-muted: #8a857f;
--border-subtle: #e5e0d8;
--border-hover: #ccc7c0;
}
/* ── Classic: Gilded Gallery Palette ─────────────── */
[data-preset='classic'] {
--bg-primary: #121210;
--bg-secondary: #0e0e0c;
--bg-card: #181816;
--bg-card-hover: #1e1e1a;
--bg-overlay: rgba(14, 14, 12, 0.98);
--text-primary: #f0ece4;
--text-secondary: #a09888;
--text-muted: #706858;
--border-subtle: #2a2822;
--border-hover: #3a3830;
}
[data-preset='classic'][data-theme='light'] {
--bg-primary: #fdfbf5;
--bg-secondary: #f5f0e6;
--bg-card: #ffffff;
--bg-card-hover: #faf6ee;
--bg-overlay: rgba(253, 251, 245, 0.96);
--text-primary: #1a1810;
--text-secondary: #5a5545;
--text-muted: #8a8575;
--border-subtle: #e5dfcf;
--border-hover: #d0c8b5;
}
/* ── Reset ────────────────────────────────────────── */ /* ── Reset ────────────────────────────────────────── */
*, *,
*::before, *::before,
@@ -84,4 +177,4 @@ a {
img { img {
display: block; display: block;
max-width: 100%; max-width: 100%;
} }

View File

@@ -52,10 +52,10 @@ subpages:
# ── Theme ───────────────────────────────────────────────────── # ── Theme ─────────────────────────────────────────────────────
# Control the visual identity of your gallery. Use a preset name: # Control the visual identity of your gallery. Use a preset name:
# theme: studio # Leica-inspired, red accent, passepartout frames, film grain # theme: studio # Leica-inspired, red dot, passepartout frames, film grain
# theme: minimal # Clean, no distractions, grey accent # theme: minimal # Swiss brutalist, fullbleed hero, true B&W, zero-gap grid
# theme: editorial # Magazine feel, shadow frames # theme: editorial # Cinematic magazine, warm charcoal, Bodoni serif, shadow frames
# theme: classic # Warm, golden accent, rounded corners # theme: classic # Gilded gallery, gold accents, Cinzel headings, passepartout
# #
# Or customize individual properties (defaults from the preset): # Or customize individual properties (defaults from the preset):
# theme: # theme:

View File

@@ -14,32 +14,42 @@ That's it. Your entire gallery switches to the minimal theme.
## Built-in Presets ## Built-in Presets
| Preset | Look | Accent | Fonts | Corners | Frame | Grain | | Preset | Style | Accent | Fonts | Hero | Frame | Grain |
|--------|------|--------|-------|---------|-------|-------| | ------------- | ------------------------------ | ------------ | -------------------------- | --------- | ------------ | ----- |
| **studio** | Leica-inspired, editorial | 🔴 `#e60012` | Playfair Display + DM Sans | Sharp | Passepartout | ✓ | | **studio** | Leica-inspired, editorial | 🔴 `#e60012` | Playfair Display + DM Sans | Split | Passepartout | ✓ |
| **minimal** | Clean, no distractions | ⚪ `#666666` | Inter | Sharp | None | ✗ | | **minimal** | Swiss brutalist, high contrast | ⚫ `#000000` | Geist + IBM Plex Mono | Fullbleed | None | ✗ |
| **editorial** | Magazine feel | ⚫ `#1a1a1a` | Libre Baskerville + Source Sans | Slight | Shadow | ✗ | | **editorial** | Cinematic magazine, warm tones | 🟤 `#8B2500` | Bodoni Moda + Newsreader | Split | Shadow | ✗ |
| **classic** | Warm, inviting | 🟡 `#8b6914` | Cormorant Garamond + Lato | Rounded | None | ✗ | | **classic** | Gilded gallery, warm luxury | 🟡 `#c49a3c` | Cinzel + Crimson Pro | Minimal | Passepartout | ✗ |
Default is `studio` if no theme is specified. Default is `studio` if no theme is specified.
### 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.
**Minimal** — Swiss Brutalist aesthetic inspired by Müller-Brockmann and Dieter Rams. Fullbleed hero image, true black/white palette, zero-gap photo grid with no hover effects, ultra-tiny navigation text. Every pixel earns its place.
**Editorial** — Cinematic magazine feel inspired by Aperture and Magnum Photos. Oversized Bodoni Moda serif titles, warm charcoal backgrounds, generous grid spacing, slow cinematic transitions with desaturation-on-idle photos, and pull-quote section labels.
**Classic** — Fine art gallery aesthetic with warm gold accents, Cinzel Roman capitals for headings, decorative ornamental dividers, warm passepartout frames, rounded corners, and an elegant gold header dot. Feels like a luxury exhibition catalog.
## Custom Theme ## Custom Theme
Start from any preset and override individual properties: Start from any preset and override individual properties:
```yaml ```yaml
theme: theme:
preset: studio # base preset to extend (default: studio) preset: studio # base preset to extend (default: studio)
accent: "#2563eb" # brand/accent color (any hex) accent: '#2563eb' # brand/accent color (any hex)
fonts: fonts:
heading: "Inter" # Google Fonts name for headings heading: 'Inter' # Google Fonts name for headings
body: "Inter" # body text body: 'Inter' # body text
caption: "JetBrains Mono" # EXIF captions caption: 'JetBrains Mono' # EXIF captions
radius: 8 # border-radius in px (0 = sharp corners) radius: 8 # border-radius in px (0 = sharp corners)
photoFrame: none # "none" | "passepartout" | "shadow" photoFrame: none # "none" | "passepartout" | "shadow"
grain: false # film grain overlay on photos grain: false # film grain overlay on photos
headerDot: false # accent-colored dot in the nav bar headerDot: false # accent-colored dot in the nav bar
heroStyle: split # "split" | "fullbleed" | "minimal" heroStyle: split # "split" | "fullbleed" | "minimal"
``` ```
All properties are optional — omitted values fall back to the preset defaults. All properties are optional — omitted values fall back to the preset defaults.
@@ -61,6 +71,7 @@ Google Fonts names. Fonts are loaded automatically — just use the name as it a
### `radius` ### `radius`
Border radius in pixels applied to photo grid items and album cards: Border radius in pixels applied to photo grid items and album cards:
- `0` — sharp, editorial corners - `0` — sharp, editorial corners
- `4-8` — subtle rounding - `4-8` — subtle rounding
- `12+` — soft, modern feel - `12+` — soft, modern feel
@@ -84,38 +95,53 @@ When `true`, shows a small accent-colored dot in the navigation bar (inspired by
### `heroStyle` ### `heroStyle`
Controls the homepage hero layout: Controls the homepage hero layout:
- **`split`** — title/nav on the left, hero image on the right (default)
- **`fullbleed`** — hero image fills the entire viewport - **`split`** — title/nav on the left, hero image on the right (used by Studio and Editorial)
- **`minimal`** — text-only, no hero image - **`fullbleed`** — hero image fills the entire viewport, title overlaid in the corner (used by Minimal)
- **`minimal`** — centered title card with decorative ornament, hero image as a banner below (used by Classic)
## Examples ## Examples
### Minimal portfolio ### Minimal portfolio
```yaml ```yaml
theme: theme:
preset: minimal preset: minimal
accent: "#0066cc" accent: '#0066cc'
fonts: fonts:
heading: "Outfit" heading: 'Outfit'
body: "Outfit" body: 'Outfit'
``` ```
### Film photography blog ### Film photography blog
```yaml ```yaml
theme: theme:
preset: studio preset: studio
accent: "#d4a017" accent: '#d4a017'
grain: true grain: true
photoFrame: passepartout photoFrame: passepartout
``` ```
### Modern magazine ### Modern magazine
```yaml ```yaml
theme: theme:
preset: editorial preset: editorial
radius: 12 radius: 12
fonts: fonts:
heading: "Fraunces" heading: 'Fraunces'
body: "Inter" body: 'Inter'
caption: "IBM Plex Mono" caption: 'IBM Plex Mono'
```
### Luxury gallery
```yaml
theme:
preset: classic
accent: '#b8860b'
fonts:
heading: 'Playfair Display'
body: 'Lora'
``` ```

View File

@@ -58,19 +58,19 @@ const THEME_PRESETS: Record<string, ThemeConfig> = {
}, },
minimal: { minimal: {
preset: 'minimal', preset: 'minimal',
accent: '#666666', accent: '#000000',
fonts: { heading: 'Inter', body: 'Inter', caption: 'JetBrains Mono' }, fonts: { heading: 'Geist', body: 'Geist', caption: 'IBM Plex Mono' },
radius: 0, radius: 0,
photoFrame: 'none', photoFrame: 'none',
grain: false, grain: false,
headerDot: false, headerDot: false,
heroStyle: 'split', heroStyle: 'fullbleed',
}, },
editorial: { editorial: {
preset: 'editorial', preset: 'editorial',
accent: '#1a1a1a', accent: '#8B2500',
fonts: { heading: 'Libre Baskerville', body: 'Source Sans 3', caption: 'Source Sans 3' }, fonts: { heading: 'Bodoni Moda', body: 'Newsreader', caption: 'Spectral' },
radius: 4, radius: 0,
photoFrame: 'shadow', photoFrame: 'shadow',
grain: false, grain: false,
headerDot: false, headerDot: false,
@@ -78,13 +78,13 @@ const THEME_PRESETS: Record<string, ThemeConfig> = {
}, },
classic: { classic: {
preset: 'classic', preset: 'classic',
accent: '#8b6914', accent: '#c49a3c',
fonts: { heading: 'Cormorant Garamond', body: 'Lato', caption: 'Lato' }, fonts: { heading: 'Cinzel', body: 'Crimson Pro', caption: 'Crimson Pro' },
radius: 8, radius: 12,
photoFrame: 'none', photoFrame: 'passepartout',
grain: false, grain: false,
headerDot: false, headerDot: true,
heroStyle: 'split', heroStyle: 'minimal',
}, },
}; };