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