160 lines
5.2 KiB
Markdown
160 lines
5.2 KiB
Markdown
# Gallery
|
|
|
|
Static photo gallery for logging meals and food memories.
|
|
|
|
The site is based on the HTML5 UP Lens template. The front-end remains a mostly static HTML/CSS/JavaScript site, and the local Node server now also exposes a tiny rankings sync API for shared Elo persistence.
|
|
|
|
## Repo Layout
|
|
|
|
- `templates/index.html`: source template for the main gallery page
|
|
- `templates/rankings.html`: source template for the rankings page
|
|
- `index.html`: generated static gallery page
|
|
- `rankings.html`: generated static rankings page
|
|
- `assets/`: site CSS, JavaScript, fonts, and audio
|
|
- `images/fulls/`: full-size gallery images
|
|
- `images/thumbs/`: gallery thumbnails
|
|
- `data/meals.json`: source of truth for gallery entries
|
|
- `data/elo.json`: Elo ratings, record totals, and ranking settings
|
|
- `scripts/build.js`: renders static pages from templates and data
|
|
- `scripts/check.js`: validates data, image assets, and generated pages
|
|
- `scripts/generate-thumbnails.js`: regenerates thumbnails from the full-size images
|
|
- `scripts/ingest-meal.js`: ingests a new meal image and metadata in one command
|
|
- `scripts/serve.js`: serves the generated site and the rankings sync API
|
|
- `scripts/lib/elo.js`: validates and syncs Elo data against the meal list
|
|
- `scripts/lib/rankings-state.js`: normalizes and persists the shared rankings state
|
|
- `package.json`: minimal Node build entrypoint
|
|
|
|
## Run Locally
|
|
|
|
Install dependencies:
|
|
|
|
```sh
|
|
npm install
|
|
```
|
|
|
|
Build the site and validate the generated output:
|
|
|
|
```sh
|
|
npm run build
|
|
```
|
|
|
|
Serve it locally:
|
|
|
|
```sh
|
|
npm run serve
|
|
```
|
|
|
|
Then open `http://127.0.0.1:4321`.
|
|
|
|
By default, rankings sync state is written to `.runtime/rankings-state.json`.
|
|
Override that path with `RANKINGS_STATE_PATH=/absolute/path/to/rankings-state.json`.
|
|
|
|
If you want a single command that builds and serves, run:
|
|
|
|
```sh
|
|
npm start
|
|
```
|
|
|
|
To validate the repo state without rebuilding thumbnails or pages, run:
|
|
|
|
```sh
|
|
npm run check
|
|
```
|
|
|
|
## Content Workflow
|
|
|
|
Gallery entries live in `data/meals.json`, and the build generates both `index.html` and `rankings.html` from the template and data files.
|
|
|
|
After editing content or templates, rebuild the site with:
|
|
|
|
```sh
|
|
npm run build
|
|
```
|
|
|
|
The gallery build keeps the existing Lens thumbnail markup intact, so the current client-side viewer code continues to work.
|
|
|
|
To ingest a new meal image and update the site in one command, run:
|
|
|
|
```sh
|
|
npm run ingest -- --image /path/to/photo.jpg --title "meal title" --description "notes"
|
|
```
|
|
|
|
Optional ingestion flags:
|
|
|
|
- `--position "left center"` sets the viewer image alignment
|
|
- `--focus-x 0.35 --focus-y 0.45` sets the thumbnail crop focal point
|
|
|
|
If you only need to regenerate thumbnails, run:
|
|
|
|
```sh
|
|
npm run build:thumbs
|
|
```
|
|
|
|
To force a full thumbnail rebuild, run:
|
|
|
|
```sh
|
|
npm run build:thumbs:force
|
|
```
|
|
|
|
## Rankings Data
|
|
|
|
`data/elo.json` stores the seed rating, Elo `kFactor`, and a win-loss record for each meal.
|
|
The page build keeps this file aligned with `data/meals.json`, so new meals automatically appear in `rankings.html` with the default seed rating.
|
|
|
|
The interactive voting flow on `rankings.html` now prefers the same-origin API exposed by `scripts/serve.js`:
|
|
|
|
- `GET /api/rankings`: load the shared rankings state
|
|
- `POST /api/rankings/vote`: apply one head-to-head result on the server
|
|
- `POST /api/rankings/reset`: reset the shared board back to the seeded state
|
|
|
|
The server persists the shared board to `.runtime/rankings-state.json` by default, or to `RANKINGS_STATE_PATH` if you set it.
|
|
That makes rankings persist across reloads, sessions, browsers, and devices as long as they are hitting the same deployed site.
|
|
|
|
If the API is unavailable, the page falls back to browser `localStorage`.
|
|
In that fallback mode, votes still persist across reloads in the same browser profile, but they do not sync across browsers or devices.
|
|
Use the reset button on the rankings page if you want to clear the current saved board and go back to the seeded state.
|
|
|
|
## Deployment Notes
|
|
|
|
For Docker/VPS deployment, mount a persistent volume and point `RANKINGS_STATE_PATH` at it so rankings survive container rebuilds and restarts.
|
|
|
|
Example:
|
|
|
|
```sh
|
|
RANKINGS_STATE_PATH=/data/rankings-state.json npm start
|
|
```
|
|
|
|
In a containerized setup, mount `/data` as a named volume or bind mount.
|
|
If you reverse-proxy the app through Caddy on the same domain, the rankings page will use the shared API automatically with no extra CORS setup.
|
|
|
|
## Image Conventions
|
|
|
|
- Full-size images and thumbnails share the same numeric ID
|
|
- Full-size images live at `images/fulls/<id>.jpg`
|
|
- Thumbnails live at `images/thumbs/<id>.jpg`
|
|
- `position` controls the full-screen viewer image alignment
|
|
- `thumbnail.focus` optionally overrides the default center crop for generated thumbnails
|
|
|
|
## Thumbnail Focus
|
|
|
|
Thumbnails are generated from `images/fulls` with `sharp` at `240x320`.
|
|
The generator auto-rotates images using EXIF orientation, skips unchanged files by default, and removes stale thumbnail `.jpg` files that no longer map to a meal entry.
|
|
|
|
For images that should crop away from the center, add optional thumbnail focus metadata to the meal entry:
|
|
|
|
```json
|
|
{
|
|
"id": "34",
|
|
"title": "example",
|
|
"description": "example",
|
|
"thumbnail": {
|
|
"focus": {
|
|
"x": 0.35,
|
|
"y": 0.45
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The `x` and `y` values are normalized from `0` to `1`, where `0.5, 0.5` is the center of the image.
|