Simple Gal
A minimal static site generator for fine art photography portfolios. Generates fast-loading, responsive photo albums from a filesystem-based data source.
How It Works
Simple Gal uses your filesystem as the data source. Directories become albums, images are ordered by numeric prefix, and markdown files become pages.
content/ # Root directory
├── config.toml # Site configuration (optional)
├── 040-about.md # Page (numbered = shown in nav)
├── 050-github.md # External link (URL-only .md file)
├── 010-Landscapes/ # Album (numbered = shown in nav)
│ ├── info.txt # Optional description
│ ├── 001-dawn.jpg # Preview image (lowest number)
│ ├── 002-sunset.jpg
│ └── 010-mountains.jpg # Non-contiguous numbering OK
├── 020-Travel/ # Container directory (has subdirs)
│ ├── 010-Japan/ # Nested album
│ │ ├── info.txt
│ │ └── 001-tokyo.jpg
│ └── 020-Italy/
│ └── 001-rome.jpg
├── 030-Minimal/ # Another album
│ └── 001-solo.jpg
└── wip-experiments/ # No number prefix = not in nav (still accessible)
└── 001-draft.jpg
Naming Convention
All entities (albums, images, pages, container directories) follow the same NNN-name convention:
NNN-numeric prefix determines sort order (e.g.,001-,020-,100-)- Dashes in the name portion become spaces in display titles (
010-My-Best-Photos→ "My Best Photos") - Only numbered entries appear in the navigation menu
- Unnumbered entries still exist and are accessible by URL, but are hidden from nav
Albums
- A directory containing images (
.jpg,.jpeg,.png,.webp) - Optional
info.txtfor a description shown on the album page - Preview image: Image numbered
001-*is used as the album thumbnail. Falls back to the first image by sort order if no001exists. - Directories cannot mix images and subdirectories
Image Metadata
Image titles and descriptions can come from multiple sources. The first available value wins:
- Title: IPTC title (from Lightroom/Capture One) → filename stem → none
- Description: sidecar
.txtfile → IPTC caption → none
A sidecar file uses the same stem as the image: 001-photo.txt for 001-photo.jpg. IPTC titles sourced from metadata are sanitized for use in URLs (truncated to 80 chars, special characters replaced).
Container Directories
- A directory containing other directories (not images)
- Numbered containers appear in nav as groups with their children nested underneath
- Unnumbered containers are transparent: their children are promoted to the parent level in nav
Pages
Markdown files (.md) in the content root become site pages:
- Content pages: Regular markdown rendered as HTML pages (e.g.,
040-about.md) - Link pages: If the
.mdfile contains only a URL, it renders as an external link in the nav (e.g.,050-github.mdcontaininghttps://github.com/user/repo) - Pages appear in the navigation after albums, separated by a divider
- An
# H1heading overrides the filename-derived title
Navigation Order
Navigation items are sorted by their numeric prefix. Albums and containers appear first, then a separator, then pages. Within each group, items are sorted by number.
Build Pipeline
1. Scan → manifest.json (filesystem → structured data)
2. Process → processed/ (responsive sizes + thumbnails)
3. Generate → dist/ (final HTML site)
Each stage is independent and produces a manifest file that the next stage consumes. Image processing is cached — unchanged sources are skipped on subsequent builds.
Image Processing
Optimized for fine art photography:
- Formats: AVIF (primary) + WebP (fallback)
- Responsive sizes: 800px, 1400px, 2080px (configurable)
- Thumbnails: Single size, cropped to configured aspect ratio (default 4:5)
- Quality: 90% compression, EXIF preserved
Frontend
Pure HTML/CSS with minimal JS (89 lines):
- No frameworks, no build tools, no npm
- CSS custom properties for theming
- Dark/light/auto color schemes (respects
prefers-color-scheme) - Stories-style navigation (click/swipe left/right edges)
- Keyboard navigation (arrow keys)
- Responsive frames that preserve aspect ratio
- View transitions (where supported)
Installation
Dependencies
- Rust compiler (for building the CLI)
- ImageMagick (
convertandidentifycommands) with AVIF and WebP support
See DEPENDENCIES.md for platform-specific installation instructions, or run:
Build
Usage
# Full build (defaults: --source content --output dist)
# Override paths
# Run stages individually
CLI Options
Options:
--source <DIR> Content directory [default: content]
--output <DIR> Output directory [default: dist]
--temp-dir <DIR> Intermediate files directory [default: .simple-gal-temp]
All options are global and shared across subcommands. Intermediate files (manifests, processed images) are stored in --temp-dir and preserved between builds for caching and debugging.
Build Script
# Full build using the same script as CI
Configuration
Place config.toml in your content root (e.g., content/config.toml). All options are optional — defaults are used for any missing values.
# Path to content directory (resolved relative to CWD)
= "content"
[]
= [4, 5] # width:height
[]
= [800, 1400, 2080] # Responsive sizes to generate
= 90 # AVIF/WebP quality (0-100)
[]
= "3vw" # Preferred horizontal frame size
= "1rem" # Minimum horizontal frame size
= "2.5rem" # Maximum horizontal frame size
[]
= "6vw" # Preferred vertical frame size
= "2rem" # Minimum vertical frame size
= "5rem" # Maximum vertical frame size
[]
= "#ffffff"
= "#111111"
= "#666666" # Nav, breadcrumbs, captions
= "#e0e0e0"
= "#333333"
= "#000000"
[]
= "#0a0a0a"
= "#eeeeee"
= "#999999"
= "#333333"
= "#cccccc"
= "#ffffff"
[]
= 4 # Max parallel workers (omit for auto = CPU cores)
Partial configuration is supported — override only the values you want to change:
[]
= "#fafafa"
UI Behavior
Album View
- Title + optional description
- Grid of thumbnails (no pagination, controlled album sizes)
Image View
- Breadcrumb navigation: Home > Album
- Image in responsive frame with configurable padding
- Navigation: click/tap right edge → next, left edge → previous
- Keyboard: left/right arrow keys
- First image ← goes to album, last image → goes to album
Color Schemes
- Respects
prefers-color-scheme - Light: white background, dark text
- Dark: near-black background, light text
Development
# Run tests
# Build and preview locally
Project Structure
├── src/
│ ├── main.rs # CLI entry point
│ ├── naming.rs # Filename parsing (NNN-name convention)
│ ├── types.rs # Shared types (Page, NavItem)
│ ├── config.rs # Site configuration (config.toml)
│ ├── scan.rs # Stage 1: filesystem → manifest
│ ├── process.rs # Stage 2: image processing
│ ├── generate.rs # Stage 3: HTML generation
│ └── imaging/ # ImageMagick backend
├── static/
│ ├── style.css # Base styles (inlined at build)
│ └── nav.js # Keyboard/touch navigation (inlined at build)
├── scripts/
│ ├── build.sh # Full build (used by CI)
│ └── install-deps.sh # System dependency installer
└── fixtures/ # Test data
Deployment
GitHub Actions workflow:
- Triggers on push to main
- Builds the CLI and runs the full pipeline
- Publishes to GitHub Pages