v2.1 closes the loop on everything started in v2.0. The gallery now delivers all images from a CDN: faster loads, cleaner display in both grid and fullscreen, with no compromise on visual quality. The content model has been rebuilt from the ground up: a better pipeline from source image to live gallery, and a new architecture for albums, series, and curation. Most of it isn’t exposed yet; v2.2 will be a UI pass across the board.
### Media & Delivery
**Three-tier derivatives — one file per need**
- Every album/series image is now produced in three variants, each encoded once from a lossless master (single lossy step): `thumbs/` (AVIF q50, 760px short side — the grid), `full/` (AVIF q80 — full-screen), `downloads/` (JPEG q95 4:4:4 — the downloadable keepsake). Display moved to AVIF (much lighter than the old JPEG full-size); downloads stay JPEG for universal portability.
- Masters are lossless **PNG/TIFF** dropped in staging. A master already in JPEG is copied byte-for-byte to `downloads/` — never re-encoded JPEG→JPEG.
**Media served from Vercel Blob**
- Album & series media moved **out of the repo to Vercel Blob**, behind a single switch (`VFE_MEDIA_BASE` in `js/config.js`: empty = local `/assets`, else the Blob base). The local `assets/` tree stays as a gitignored mirror and is the source of truth; paths are identical on both sides, so swapping provider (or reverting to local) is a one-line change. Cross-origin download CORS verified.
---
### Architecture & Curation Model
- Restructured into a single **monorepo** with a clean git history; `prod/` → `site/` (Vercel Root Directory = `site/`).
- New curation tiers: **Albums** (one game = one album), **Series** (a cross-cutting thematic selection with its own media), **Collections** (groupings of albums, by reference only — no media of their own), **Starred** (the homepage: configurable mixes + pinned items). **Sets are gone** — a former multi-set album becomes several distinct albums.
- Data split in two: machine-managed **`data.js`** (albums/series metadata) and hand-curated **`curation.js`** (collections + starred, never touched by the script).
- Albums gain an optional **`subtitle`** so a former set reads as e.g. *Halo Infinite · Campaign* (name and subtitle kept in separate spans).
- New **`featured`** spotlight: a per-album list of 1-based image indices flagged for special treatment; the tag follows the image into every view (album, mix, collection) and onto the fullscreen image. It lives in **`data.js`** (index-coupled to the album numbering, so the pipeline realigns it through a renumber).
---
### Routing — single page, query params
- Removed the per-album static pages. Everything renders from `index.html` via query params: `?album=`, `?series=`, `?collection=`, `?mix=` (no param = first mix). Nav links and the lightbox "Album" button point to these.
---
### Gallery — reflow-free justified grid
- New **`dims.js`** ships precomputed image ratios with the code, so the justified grid is laid out **before** any pixel loads — no reflow/jump. A `ResizeObserver` re-justifies on real width changes; the loading overlay lifts only once the width is stable and an image is ready.
- Fixed a long-standing footgun: a thumbnail `transition: all` was animating geometry → restricted to `opacity`/`filter` so sizes snap instantly.
- Missing-media guard: a thumbnail that fails to load is hidden and dropped from the grid and the lightbox.
---
### Lightbox & UX
- Adjacent full-screen images (**±2**) are preloaded → instant next / previous navigation.
---
### Pipeline — local-first `sync-and-push.sh`
- Rewritten and **local-first**: sync, repairs and the staging push only mutate `assets/` + `data.js` + `dims.js` on disk. Vercel Blob is touched at exactly one place — a single **mirror** at the very end (full local↔Blob diff: upload missing/changed by path+size, delete extras) that you confirm. The local drive is the source of truth; the Blob just follows.
- Robustness: preflight checks (python / node / Pillow-AVIF / trash), **atomic `data.js` write** (temp + `os.replace`), per-item confirmation before a vanished-item removal, slug-collision menu (append / replace / different — never a silent merge), per-folder skip prompt, EXIF orientation honored, staged sources sent to the **Trash** (recoverable).
- Repair & integrity tooling: `fix-renames.py` (filesystem-driven slug rename — rename the folder, the tooling re-derives files + `data.js` + Blob), `rebuild-media.py` (renumber to close gaps, auto-remapping `featured`; or regenerate `full/`+`thumbs/` from `downloads/`), `gen-dims.py` (ratios), `check-collections.mjs` (validate every `curation.js` reference against `data.js`).
---
### Versioning
- Introduced a milestone version scheme (`v{major}.{minor}`): a single `VFE_VERSION` source of truth mirrored from the annotated Git tag, surfaced in the site footer, with a `tools/tag-release.sh` helper that bumps the tag and the constant together.
There is now a public repo for those that are interest https://github.com/trgnn/vfe.trognon.space