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:
30
README.md
30
README.md
@@ -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
|
||||||
|
|||||||
588
app/globals.css
588
app/globals.css
@@ -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(
|
||||||
|
to top,
|
||||||
rgba(0, 0, 0, 0.8) 0%,
|
rgba(0, 0, 0, 0.8) 0%,
|
||||||
rgba(0, 0, 0, 0.3) 40%,
|
rgba(0, 0, 0, 0.3) 40%,
|
||||||
transparent 100%);
|
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(
|
||||||
|
90deg,
|
||||||
var(--bg-card) 0%,
|
var(--bg-card) 0%,
|
||||||
var(--bg-card-hover) 50%,
|
var(--bg-card-hover) 50%,
|
||||||
var(--bg-card) 100%);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -14,15 +14,25 @@ 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:
|
||||||
@@ -30,11 +40,11 @@ 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
|
||||||
@@ -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'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user