patx/voldservices

Add managed Voldservices galleries

Commit 57f00ea · patx · 2026-08-18T19:34:55-04:00

Changeset
57f00eae6b633f2eaab2c83e5d9f84d41e54e226
Parents
f634c8d38bf619feb3a1a95e87d31229c6587be7

View source at this commit

Comments

No comments yet.

Log in to comment

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 &amp; 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');