/* Public site behavior: hiding navigation + gallery lightbox. Vanilla JS. */ (function () { 'use strict'; /* ---- Navigation: hide on scroll-down, show on scroll-up ------------- */ var nav = document.getElementById('nav'); if (nav) { var lastY = window.scrollY; var onScroll = function () { var y = window.scrollY; if (y > lastY && y > 80) { nav.classList.add('nav-hidden'); } else if (y < lastY - 2 || y <= 80) { nav.classList.remove('nav-hidden'); } lastY = y; }; window.addEventListener('scroll', onScroll, { passive: true }); } /* ---- Topic sections: collapse / expand ------------------------------ A gallery may be split into topics, each a heading plus its own .grid. The button carries the state so the chevron and the panel can both be styled from it, and screen readers get it for free. Deliberately not remembered anywhere: every visit starts fully expanded. */ document.addEventListener('click', function (e) { var toggle = e.target.closest && e.target.closest('.topic-toggle'); if (!toggle) return; var panel = document.getElementById(toggle.getAttribute('aria-controls')); if (!panel) return; var open = toggle.getAttribute('aria-expanded') === 'true'; toggle.setAttribute('aria-expanded', open ? 'false' : 'true'); panel.hidden = open; }); /* ---- Lightbox for gallery pages ------------------------------------- Markup contract: every .grid contains . A gallery with topics has one .grid per section, so they are treated as a single sequence in document order — which is the order the client reads the page in. */ var grids = Array.prototype.slice.call(document.querySelectorAll('.grid')); if (!grids.length) return; var allLinks = []; grids.forEach(function (grid) { allLinks = allLinks.concat(Array.prototype.slice.call(grid.querySelectorAll('a'))); }); if (!allLinks.length) return; /* Only what the visitor can actually see right now. Recomputed on open, so arrowing through never lands on a photo inside a collapsed topic. */ var links = allLinks; function visibleLinks() { return allLinks.filter(function (a) { return a.offsetParent !== null; }); } var box = document.createElement('div'); box.className = 'lightbox'; box.innerHTML = '' + '' + '' + '' + ''; document.body.appendChild(box); var img = box.querySelector('img'); var count = box.querySelector('.lb-count'); var current = -1; function show(i) { current = (i + links.length) % links.length; img.src = links[current].href; count.textContent = (current + 1) + ' / ' + links.length; box.classList.add('open'); document.body.style.overflow = 'hidden'; } function close() { box.classList.remove('open'); img.src = ''; document.body.style.overflow = ''; current = -1; } allLinks.forEach(function (a) { a.addEventListener('click', function (e) { e.preventDefault(); links = visibleLinks(); var at = links.indexOf(a); if (at >= 0) show(at); }); }); box.querySelector('.lb-close').addEventListener('click', close); box.querySelector('.lb-prev').addEventListener('click', function () { show(current - 1); }); box.querySelector('.lb-next').addEventListener('click', function () { show(current + 1); }); box.addEventListener('click', function (e) { if (e.target === box) close(); }); document.addEventListener('keydown', function (e) { if (current < 0) return; if (e.key === 'Escape') close(); if (e.key === 'ArrowLeft') show(current - 1); if (e.key === 'ArrowRight') show(current + 1); }); /* Basic swipe support */ var touchX = null; box.addEventListener('touchstart', function (e) { touchX = e.touches[0].clientX; }, { passive: true }); box.addEventListener('touchend', function (e) { if (touchX === null || current < 0) return; var dx = e.changedTouches[0].clientX - touchX; if (dx > 50) show(current - 1); else if (dx < -50) show(current + 1); touchX = null; }, { passive: true }); })();