Skip to content

Repository files navigation

🖼️ Static Image Gallery — Vite + React Template

Fork it. Drop in your images. Ship it. A zero-backend gallery SPA that goes from empty folder to live GitHub Pages site in under five minutes.

Built with Vite + React + TypeScript. No server. No database. No fuss.


✨ What you get

  • 🗂️ Gallery — responsive grid of animated thumbnail cards
  • 📅 Timeline — chronological view grouped by year (optional age annotation)
  • 📺 Slideshow — fullscreen auto-advancing presentation, great for a TV display
  • 🔍 Lightbox — full-size overlay with keyboard navigation ( Esc)

Plus a small suite of Python CLI scripts to manage your images without ever touching metadata.json by hand:

process_images.py   →  resize + thumbnail + manifest in one go
rotate_image.py     →  fix sideways photos across all dirs at once
relabel_image.py    →  give an image a friendly name
delete_image.py     →  remove an image cleanly from everything

🏗️ Architecture

browser
  └── fetches metadata.json at runtime           (useManifest hook)
        └── renders one of three views            (useState in App.tsx)
              🗂️  Gallery   →  ThumbnailCard grid  (lazy-loaded <img>)
              📅  Timeline  →  year-grouped list
              📺  Slideshow →  fullscreen + auto-advance timer
              🔍  Lightbox  →  modal overlay, keyboard-navigable

Design choices worth knowing:

  • 🚫 No router — view switching is a single useState<ViewMode>. Simple and URL-change-free.
  • 🚫 No image bundling — Vite copies public/images/ verbatim into dist/. The JS bundle never imports an image. Add 500 images and the bundle stays the same size.
  • Runtime manifest — the app fetches metadata.json on load. To add/remove images, regenerate the manifest and redeploy. No JS rebuild needed.
  • 🎨 Pure CSS — design tokens in src/styles/theme.css as CSS custom properties. Co-located .css per component. Zero CSS frameworks.
  • 🐍 Python pipeline — one script does everything. Pillow is the only external dependency.

📁 Project structure

├── public/
│   ├── metadata.json            ← generated; fetched at runtime
│   └── images/
│       ├── full/                ← full-res images (max 1920 px longest edge)
│       └── thumbs/              ← thumbnails (400×300 bounding box)
├── raw_images/                  ← drop originals here (gitignored)
├── scripts/
│   ├── _image_utils.py          ← shared helpers
│   ├── process_images.py        ← resize + thumbnail + manifest
│   ├── rotate_image.py          ← rotate in all three dirs at once
│   ├── relabel_image.py         ← set a persistent display title
│   └── delete_image.py          ← remove from disk + manifest
├── custom_labels.json           ← your title overrides (tracked in git)
└── src/
    ├── App.tsx                  ← root: view state + lightbox state
    ├── components/
    │   ├── Gallery/
    │   ├── Timeline/
    │   ├── Slideshow/
    │   ├── Lightbox/
    │   └── Nav.tsx
    ├── hooks/
    │   ├── useManifest.ts       ← fetches and sorts metadata.json
    │   └── useSlideshow.ts      ← auto-advance interval + pause logic
    ├── styles/
    │   └── theme.css            ← CSS custom properties / design tokens
    └── types/
        └── gallery.types.ts

🚀 Getting started

Prerequisites

  • Node.js 20+ and pnpm
  • Python 3.10+ with Pillow — pip install Pillow

Step 1 — Install dependencies

pnpm install

Step 2 — Add your images

mkdir raw_images
# copy your JPG / PNG files in

Step 3 — Process images

python3 scripts/process_images.py

The script will:

  • Resize to max 1920 px (longest edge), JPEG quality 88
  • Generate 400×300 thumbnails
  • Extract dates from EXIF → filename pattern (YYYYMMDD_HHMMSS) → file mtime
  • Assign sequential labels: 000001, 000002, …
  • Write public/metadata.json

⚠️ If a date falls back to file mtime, the script prints a warning — mtime is unreliable after copying files.

On subsequent runs, skip already-processed images:

python3 scripts/process_images.py --skip-existing

Step 4 — Run the dev server

pnpm dev

Open http://localhost:5173 and enjoy. 🎉


🛠️ Image management scripts

Every script accepts these target formats interchangeably:

Format Example Resolves to
Short number 1 label 000001
Full label 000042 label 000042
Custom title Stars whatever you relabeled it to
Exact filename 20240215_143000.jpg that file

🔄 Rotate

python3 scripts/rotate_image.py 1 --rotate left    # 90° counter-clockwise
python3 scripts/rotate_image.py 1 --rotate right   # 90° clockwise
python3 scripts/rotate_image.py 1 --rotate 180     # upside-down

Updates raw_images/, public/images/full/, and public/images/thumbs/ in one shot.

✏️ Relabel

python3 scripts/relabel_image.py 1 "Unicorn Rainbow"

Saves the title to custom_labels.json (tracked in git) and patches metadata.json immediately. Survives re-runs of process_images.py.

🗑️ Delete

python3 scripts/delete_image.py 1          # asks for confirmation
python3 scripts/delete_image.py 1 --yes    # skip the prompt

Removes from all three image directories, from metadata.json, and from custom_labels.json.


📦 Deployment to GitHub Pages

Set your base path in vite.config.ts:

base: process.env.NODE_ENV === 'production' ? '/your-gallery/' : '/',

Then build and ship:

pnpm build
cp -r dist/* ../my-github-pages/public/your-gallery/
cd ../my-github-pages && git add . && git commit -m "Deploy gallery" && git push

That's it — your gallery is live. 🌍


📄 metadata.json format

{
  "generated": "2025-03-10T12:00:00Z",
  "images": [
    {
      "id": "20240215-143000",
      "filename": "20240215_143000.jpg",
      "thumbnail": "thumbs/20240215_143000.jpg",
      "title": "000001",
      "date": "2024-02-15",
      "order": 1,
      "width": 1200,
      "height": 900
    }
  ]
}

title defaults to the zero-padded order number; override it any time with relabel_image.py. date follows the chain: EXIF → filename pattern → file mtime.

About

Zero-backend image gallery SPA — gallery, timeline & fullscreen slideshow. Fork, drop in images, deploy to GitHub Pages in minutes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages