patx/voldservices
Add managed Voldservices galleries
Commit 57f00ea · patx · 2026-08-18T19:34:55-04:00
Comments
No comments yet.
Diff
diff --git a/docs/gallery/gallery.json b/docs/gallery/gallery.json
new file mode 100644
index 0000000..383a0c1
--- /dev/null
+++ b/docs/gallery/gallery.json
@@ -0,0 +1,86 @@
+{
+ "version": 1,
+ "categories": [],
+ "images": [
+ {
+ "id": "driveway-wash-seal",
+ "src": "img/after-driveway.jpg",
+ "thumb": "img/after-driveway.jpg",
+ "category": null,
+ "caption": "Driveway wash & seal",
+ "alt": "Travertine paver driveway cleaned and sealed at a two-storey South Florida home"
+ },
+ {
+ "id": "pool-deck-seal",
+ "src": "img/after-pool-deck.jpg",
+ "thumb": "img/after-pool-deck.jpg",
+ "category": null,
+ "caption": "Pool deck seal",
+ "alt": "Gold travertine pool deck sealed to a wet look beside a screened pool"
+ },
+ {
+ "id": "screened-lanai",
+ "src": "img/after-lanai.jpg",
+ "thumb": "img/after-lanai.jpg",
+ "category": null,
+ "caption": "Screened lanai",
+ "alt": "Screened lanai with travertine pavers cleaned back to a warm tone"
+ },
+ {
+ "id": "patio-walkway",
+ "src": "img/after-patio.jpg",
+ "thumb": "img/after-patio.jpg",
+ "category": null,
+ "caption": "Patio & walkway",
+ "alt": "Front paver patio and walkway cleaned, weeded, and re-sanded"
+ },
+ {
+ "id": "travertine-driveway",
+ "src": "img/work-driveway-travertine.jpg",
+ "thumb": "img/work-driveway-travertine.jpg",
+ "category": null,
+ "caption": "Travertine driveway",
+ "alt": "Finished travertine paver driveway taped off after sealing"
+ },
+ {
+ "id": "driveway-restoration",
+ "src": "img/work-driveway-install.jpg",
+ "thumb": "img/work-driveway-install.jpg",
+ "category": null,
+ "caption": "Driveway restoration",
+ "alt": "Paver driveway restoration in progress, with new pavers being laid over the old concrete"
+ }
+ ],
+ "comparisons": [
+ {
+ "id": "result-1",
+ "before": {
+ "id": "result-1-before",
+ "src": "img/before-driveway.jpg",
+ "thumb": "img/before-driveway.jpg",
+ "alt": "Travertine paver driveway dulled by dirt and organic growth before cleaning"
+ },
+ "after": {
+ "id": "result-1-after",
+ "src": "img/after-driveway.jpg",
+ "thumb": "img/after-driveway.jpg",
+ "alt": "The same driveway after pressure washing, re-sanding, and sealing"
+ }
+ },
+ {
+ "id": "result-2",
+ "before": {
+ "id": "result-2-before",
+ "src": "img/before-pool-deck.jpg",
+ "thumb": "img/before-pool-deck.jpg",
+ "alt": "Gold travertine pool deck greyed by weathering and organic staining before cleaning"
+ },
+ "after": {
+ "id": "result-2-after",
+ "src": "img/after-pool-deck.jpg",
+ "thumb": "img/after-pool-deck.jpg",
+ "alt": "The same pool deck after cleaning and sealing, with the stone color brought back"
+ }
+ }
+ ]
+}
diff --git a/docs/index.html b/docs/index.html
index d910624..904fcb6 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1604,15 +1604,15 @@
</div>
<div class="wand-comparisons">
- <article class="wand-project reveal">
+ <article class="wand-project reveal" data-comparison-id="result-1">
<div class="wand">
<div class="wand-layer">
- <img src="https://patx.gitman.io/voldservices/img/before-driveway.jpg"
+ <img data-comparison-side="before" src="https://patx.gitman.io/voldservices/img/before-driveway.jpg"
alt="Travertine paver driveway dulled by dirt and organic growth before cleaning"
loading="lazy" decoding="async" width="746" height="829" />
</div>
<div class="wand-layer wand-layer--after">
- <img src="https://patx.gitman.io/voldservices/img/after-driveway.jpg"
+ <img data-comparison-side="after" src="https://patx.gitman.io/voldservices/img/after-driveway.jpg"
alt="The same driveway after pressure washing, re-sanding, and sealing"
loading="lazy" decoding="async" width="746" height="829" />
</div>
@@ -1632,15 +1632,15 @@
</div>
</article>
- <article class="wand-project reveal">
+ <article class="wand-project reveal" data-comparison-id="result-2">
<div class="wand">
<div class="wand-layer">
- <img src="https://patx.gitman.io/voldservices/img/before-pool-deck.jpg"
+ <img data-comparison-side="before" src="https://patx.gitman.io/voldservices/img/before-pool-deck.jpg"
alt="Gold travertine pool deck greyed by weathering and organic staining before cleaning"
loading="lazy" decoding="async" width="746" height="829" />
</div>
<div class="wand-layer wand-layer--after">
- <img src="https://patx.gitman.io/voldservices/img/after-pool-deck.jpg"
+ <img data-comparison-side="after" src="https://patx.gitman.io/voldservices/img/after-pool-deck.jpg"
alt="The same pool deck after cleaning and sealing, with the stone color brought back"
loading="lazy" decoding="async" width="746" height="829" />
</div>
@@ -1680,7 +1680,7 @@
</a>
</div>
- <div class="work-grid reveal">
+ <div class="work-grid reveal" id="work-grid" data-gallery-manifest="gallery/gallery.json">
<button class="work-tile" type="button">
<img src="https://patx.gitman.io/voldservices/img/after-driveway.jpg" alt="Travertine paver driveway cleaned and sealed at a two-storey South Florida home" loading="lazy" decoding="async" width="746" height="829" />
<span class="work-cap"><b>Driveway wash & seal</b><span>Paver sealing</span></span>
@@ -2442,19 +2442,76 @@
var lightboxImg = document.getElementById('lightbox-img');
var lightboxClose = lightbox.querySelector('.lightbox-close');
var lastFocused = null;
+ var workGrid = document.getElementById('work-grid');
+
+ workGrid.addEventListener('click', function (event) {
+ var tile = event.target.closest('.work-tile');
+ if (!tile || !workGrid.contains(tile)) return;
+ var img = tile.querySelector('img');
+ if (!img) return;
+ lastFocused = tile;
+ lightboxImg.src = img.dataset.full || img.currentSrc || img.src;
+ lightboxImg.alt = img.alt;
+ lightbox.classList.add('is-open');
+ document.body.classList.add('is-locked');
+ lightboxClose.focus();
+ });
+
+ function renderManagedPhotos(manifest) {
+ if (!manifest || manifest.version !== 1 || !Array.isArray(manifest.images)) return;
+
+ if (Array.isArray(manifest.comparisons)) {
+ manifest.comparisons.forEach(function (comparison) {
+ var project = Array.prototype.find.call(
+ document.querySelectorAll('[data-comparison-id]'),
+ function (item) { return item.dataset.comparisonId === comparison.id; }
+ );
+ if (!project) return;
+ ['before', 'after'].forEach(function (side) {
+ var media = comparison[side];
+ var image = project.querySelector('[data-comparison-side="' + side + '"]');
+ if (!image || !media || !media.src || !media.alt) return;
+ image.src = media.src;
+ image.alt = media.alt;
+ });
+ });
+ }
- document.querySelectorAll('.work-tile').forEach(function (tile) {
- tile.addEventListener('click', function () {
- var img = tile.querySelector('img');
- if (!img) return;
- lastFocused = tile;
- lightboxImg.src = img.currentSrc || img.src;
- lightboxImg.alt = img.alt;
- lightbox.classList.add('is-open');
- document.body.classList.add('is-locked');
- lightboxClose.focus();
+ var fragment = document.createDocumentFragment();
+ manifest.images.forEach(function (item) {
+ if (!item || !item.src || !item.caption || !item.alt) return;
+ var tile = document.createElement('button');
+ tile.className = 'work-tile';
+ tile.type = 'button';
+
+ var image = document.createElement('img');
+ image.src = item.thumb || item.src;
+ image.alt = item.alt;
+ image.loading = 'lazy';
+ image.decoding = 'async';
+ image.dataset.full = item.src;
+
+ var cap = document.createElement('span');
+ cap.className = 'work-cap';
+ var title = document.createElement('b');
+ title.textContent = item.caption;
+ cap.appendChild(title);
+ tile.appendChild(image);
+ tile.appendChild(cap);
+ fragment.appendChild(tile);
+ });
+ workGrid.replaceChildren(fragment);
+ }
+
+ fetch(workGrid.dataset.galleryManifest, { credentials: 'same-origin' })
+ .then(function (response) {
+ if (!response.ok) throw new Error('Gallery request failed');
+ return response.json();
+ })
+ .then(renderManagedPhotos)
+ .catch(function () {
+ // Keep the server-rendered photos in place as the resilient fallback.
});
- });
function closeLightbox() {
lightbox.classList.remove('is-open');