Game screenshots composed and framed as photographs. A fast, dark, self-hosted Jekyll portfolio with a fullscreen viewer.
Preview | Adding photographs | Tests | Analytics | Deployment | Licence
- 16 albums and 179 screenshots, hosted in this repository as WebP at four sizes plus the full resolution file. No external image host.
- Justified gallery layout, hover focus effects and a fullscreen viewer with keyboard, touch, zoom, pan and deep links.
- Opt-in Google Analytics 4 behind a consent banner. Nothing loads from Google until a visitor accepts.
- Self-hosted Poppins, plain CSS, no JavaScript framework, deployed by GitHub Actions to GitHub Pages.
- Ruby 3.3 or newer (CI uses 3.4) and Bundler 2.5 or newer
- Python 3.11 or newer with Pillow 13, for the gallery generator only
On Windows, winget install RubyInstallerTeam.Ruby.3.4 installs Ruby. Open a new terminal afterwards so ruby is on the PATH.
bundle install
bundle exec jekyll serve --livereloadThe site is served at http://localhost:4000. Production build, as CI runs it:
$env:JEKYLL_ENV = "production"; bundle exec jekyll build --traceGemfile.lock is committed and lists the Windows and Linux platforms, so installs are reproducible on both.
All text uses self-hosted Poppins (400, 500 and 600, latin subset, SIL Open Font License in assets/fonts/Poppins-OFL.txt). Icons use a subset of Material Symbols. Nothing is loaded from a font service.
Browser tests use Playwright against a production build in _site. Install playwright once, build with JEKYLL_ENV=production, then run:
node tests/pages.mjs # layout, sticky footer and privacy controls at 8 viewports
node tests/viewer.mjs # fullscreen viewer
node tests/analytics.mjs # consent and events, Google is mockedSet PLAYWRIGHT_MODULE to the path of playwright/index.mjs if it is not installed in this folder.
Google Analytics 4 is opt in and loads only in production builds. Events, GA4 setup, reports and the privacy decisions still open are in docs/analytics.md. Browser tests (Google is mocked, nothing is sent): build with JEKYLL_ENV=production, then node tests/analytics.mjs (needs playwright).
Originals live outside this repository, in a folder with one sub folder per album (currently D:\Grimtech\tools).
pip install -r tools/requirements.txt
python tools/gallery.py build- Drop new images into
<source>\<Album-Name>\. A new folder creates a new album and a stub post in_posts/; open that post and set the real title and developer. - Run
python tools/gallery.py build. Only new or changed images are processed. - Preview, then commit
assets/gallery/,_data/virtual-photography/andtools/gallery-manifest.json.
Use --source <path> or the GALLERY_SOURCE environment variable for a different source folder.
What the generator does:
- Writes
assets/gallery/<Album>/<name>-<width>.webpat 480, 960, 1600 and 2560 px wide, plus<name>-full.webpat the original resolution. It never upscales. - Applies EXIF orientation, keeps the colour profile, and does no sharpening or denoising.
- Detects duplicates by SHA-256, and detects renamed files so captions and order survive.
- Keeps existing order,
title,captionandaltvalues in_data/virtual-photography/<Album>.json. New photographs are appended. The album page shows the newest first. - Writes social preview images (
og.jpg) for each album and the homepage.
Useful options:
| Command | Purpose |
|---|---|
build --dry-run |
Show what would change, write nothing |
build --album Control |
Only one album |
build --force |
Regenerate everything, for example after changing quality |
build --prune |
Remove entries and files whose source image was deleted |
validate |
Check JSON, files, decodability, alt text and legacy host references. CI runs this. |
Edit alt and caption directly in the album JSON. They are kept on later runs.
Settings are in tools/gallery.config.json: widths, quality (resized files, default 90), full_quality (full size, default 92) and method (WebP effort, default 6). Changing them regenerates everything on the next build. On representative dark, cinematic frames q92 measured roughly 39 to 48 dB PSNR against the originals at 5 to 20 times smaller than the PNGs.
- The cover for an album is
card-image(an index into the album JSON) in its post front matter. - The homepage selected photographs are listed in
_data/featured.yml, in display order. The first one also becomes the default social preview. - Optional
card-posin a post (for examplecard-pos: 50% 30%) moves the cover crop on album tiles.
Direct gems: jekyll, jekyll-feed, jekyll-seo-tag, jekyll-sitemap, plus webrick and wdm for local serving and tzinfo and tzinfo-data on Windows (Jekyll fails without them there). Sass is used only through Jekyll itself; the stylesheet is plain CSS in assets/css/site.css.
Pushing to main runs .github/workflows/jekyll.yml: validate the gallery, build with Bundler and Jekyll 4.4, deploy to GitHub Pages. This needs Pages configured with the "GitHub Actions" source. The CNAME file keeps the custom domain.
GitHub Pages limits: published site under 1 GB, repository ideally under 1 GB (soft), 100 MB per file, 10 minute deploys, soft bandwidth limit of 100 GB per month. The gallery is about 128 MB.
cannot load such file -- tzinfoon Windows: runbundle install.rubyis not recognised after installing: open a new terminal.jekyll servefails with webrick errors:bundle installand usebundle exec.validatereports a missing file: runpython tools/gallery.py buildwith the original images available.- Gallery images do not update in the browser: GitHub Pages caches for 10 minutes, hard refresh.
tools/migration/ holds the pre-migration JSON backup and lensdump-migration.json, which maps every old Lensdump URL to its new local file. The Lensdump originals were left untouched.
Code: MIT. Photographs: CC BY-NC 4.0, by PatrickJr.
