1/** 2 * plan-gallery.js â Vanilla JS for the static-SSR plan detail photo gallery. 3 * Public pages are static SSR (no SignalR), so all gallery behavior lives here, 4 * driven by data-attributes rendered by Components/PlanDetail/PlanGallery.razor. 5 * 6 * Behavior: 7 * - Prev/Next buttons cycle the main image through the gallery set (wraps). 8 * - Swipe left/right on the main image does the same (touch devices). 9 * - Clicking a thumbnail sets the main image to that offset. 10 * - Thumbnail strip Prev/Next scroll the strip horizontally. 11 * - The active thumbnail is highlighted and scrolled into view. 12 * 13 * Why document-level delegation with NO per-gallery init: public pages navigate 14 * via Blazor ENHANCED navigation, which patches the DOM in place and does not 15 * execute <script> tags in swapped-in content â a per-load init wired to 16 * DOMContentLoaded simply never runs when the user *clicks into* a detail page. 17 * These handlers bind once (this file is referenced in App.razor) and derive all 18 * state from the DOM on every interaction (current index = the .active thumb), 19 * so they keep working across every enhanced navigation. 20 */ 21(function () { 22 'use strict'; 23 if (window.__hpmPlanGalleryInit) return; 24 window.__hpmPlanGalleryInit = true; 25 26 function galleryOf(el) { 27 return el && el.closest ? el.closest('[data-plan-gallery]') : null; 28 } 29 30 function slides(root) { 31 var thumbs = root.querySelectorAll('.pg-thumb'); 32 if (thumbs.length) { 33 return Array.prototype.map.call(thumbs, function (t) { 34 return { full: t.dataset.full || t.src, alt: t.alt }; 35 }); 36 } 37 var main = root.querySelector('[data-pg-main]'); 38 return main ? [{ full: main.getAttribute('src'), alt: main.getAttribute('alt') || '' }] : []; 39 } 40 41 function currentIndex(root) { 42 var thumbs = root.querySelectorAll('.pg-thumb'); 43 for (var i = 0; i < thumbs.length; i++) { 44 if (thumbs[i].classList.contains('active')) return i; 45 } 46 return 0; 47 } 48 49 function show(root, i) { 50 var set = slides(root); 51 if (!set.length) return; 52 var main = root.querySelector('[data-pg-main]'); 53 if (!main) return; 54 var idx = ((i % set.length) + set.length) % set.length; 55 main.setAttribute('src', set[idx].full); 56 main.setAttribute('alt', set[idx].alt); 57 var thumbs = root.querySelectorAll('.pg-thumb'); 58 for (var t = 0; t < thumbs.length; t++) { 59 thumbs[t].classList.toggle('active', t === idx); 60 } 61 var active = thumbs[idx]; 62 if (active && active.scrollIntoView) { 63 active.scrollIntoView({ block: 'nearest', inline: 'nearest' }); 64 } 65 } 66 67 function scrollThumbs(root, dir) { 68 var wrap = root.querySelector('[data-pg-thumbs]'); 69 if (!wrap) return; 70 // When every thumbnail already fits (no horizontal overflow) there is 71 // nothing to scroll â step the main image instead so the arrows are 72 // never dead controls. 73 if (wrap.scrollWidth <= wrap.clientWidth + 1) { 74 show(root, currentIndex(root) + dir); 75 return; 76 } 77 var amount = Math.max(wrap.clientWidth * 0.8, 160); 78 wrap.scrollBy({ left: dir * amount, behavior: 'smooth' }); 79 } 80 81 document.addEventListener('click', function (e) { 82 var t = e.target; 83 if (!t || !t.closest) return; 84 var root = galleryOf(t); 85 if (!root) return; 86 if (t.closest('[data-pg-prev]')) { show(root, currentIndex(root) - 1); return; } 87 if (t.closest('[data-pg-next]')) { show(root, currentIndex(root) + 1); return; } 88 if (t.closest('[data-pg-thumb-prev]')) { scrollThumbs(root, -1); return; } 89 if (t.closest('[data-pg-thumb-next]')) { scrollThumbs(root, 1); return; } 90 var thumb = t.closest('.pg-thumb'); 91 if (thumb) { 92 var i = parseInt(thumb.dataset.pgIndex, 10); 93 show(root, isNaN(i) ? 0 : i); 94 } 95 });
96 97 // Swipe support on the main image (delegated; start point remembered between 98 // touchstart and touchend). 99 var touchStart = null; 100 document.addEventListener('touchstart', function (e) { 101 var main = e.target && e.target.closest ? e.target.closest('[data-pg-main]') : null; 102 if (!main) { touchStart = null; return; } 103 var t = e.changedTouches[0]; 104 touchStart = { x: t.clientX, y: t.clientY, root: galleryOf(main) }; 105 }, { passive: true }); 106 document.addEventListener('touchend', function (e) { 107 if (!touchStart || !touchStart.root) return; 108 var t = e.changedTouches[0]; 109 var dx = t.clientX - touchStart.x, dy = t.clientY - touchStart.y; 110 // Horizontal swipe that isn't mostly vertical. 111 if (Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy)) { 112 show(touchStart.root, currentIndex(touchStart.root) + (dx < 0 ? 1 : -1)); 113 } 114 touchStart = null; 115 }, { passive: true }); 116})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.