Landing page, Section pages and archived sets rendered per request from the store, with the approved design: hand-written CSS, self-hosted Archivo, a copper pour line in the Section rail, an admin-only Thumbnail and a text page index. Media is keyed by Page id so URLs survive publishing, and a Draft's media needs a session. Includes the scrypt admin auth and the login throttle the media rule needs.
121 lines
3.8 KiB
JavaScript
121 lines
3.8 KiB
JavaScript
/* Progressive enhancement only: the pour line, the page counter, and
|
|
tap-to-zoom. Without this file every menu is still readable and every
|
|
Page opens as a plain link. */
|
|
(function () {
|
|
'use strict';
|
|
|
|
var reducesMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
var pages = document.querySelector('[data-pages]');
|
|
var fill = document.querySelector('[data-pour]');
|
|
var counter = document.querySelector('[data-counter]');
|
|
var counterCurrent = counter && counter.querySelector('[data-counter-current]');
|
|
var figures = pages ? Array.prototype.slice.call(pages.querySelectorAll('[data-page]')) : [];
|
|
var currentPage = 1;
|
|
|
|
function updatePour() {
|
|
if (!pages || !fill) return;
|
|
var rect = pages.getBoundingClientRect();
|
|
var travel = rect.height - window.innerHeight;
|
|
if (travel <= 0) {
|
|
fill.style.width = '100%';
|
|
return;
|
|
}
|
|
var progress = Math.min(Math.max(-rect.top, 0), travel) / travel;
|
|
fill.style.width = (progress * 100).toFixed(1) + '%';
|
|
}
|
|
|
|
function updateCounter() {
|
|
if (!counter || figures.length === 0) return;
|
|
var middle = window.innerHeight / 2;
|
|
var best = 1;
|
|
var bestDistance = Infinity;
|
|
figures.forEach(function (figure, index) {
|
|
var rect = figure.getBoundingClientRect();
|
|
var distance = Math.abs(rect.top + rect.height / 2 - middle);
|
|
if (distance < bestDistance) {
|
|
bestDistance = distance;
|
|
best = index + 1;
|
|
}
|
|
});
|
|
if (best !== currentPage) {
|
|
currentPage = best;
|
|
if (counterCurrent) counterCurrent.textContent = String(best);
|
|
}
|
|
}
|
|
|
|
function onScroll() {
|
|
updatePour();
|
|
updateCounter();
|
|
}
|
|
|
|
if (counter) counter.hidden = false;
|
|
if (figures.length > 0) {
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
window.addEventListener('resize', onScroll);
|
|
onScroll();
|
|
}
|
|
|
|
if (counter) {
|
|
counter.addEventListener('click', function () {
|
|
var fold = document.querySelector('.fold--index');
|
|
if (!fold) return;
|
|
fold.open = true;
|
|
fold.scrollIntoView({ behavior: reducesMotion ? 'auto' : 'smooth', block: 'center' });
|
|
var first = fold.querySelector('a');
|
|
if (first) first.focus({ preventScroll: true });
|
|
});
|
|
}
|
|
|
|
var overlay = null;
|
|
|
|
function closeOverlay() {
|
|
if (!overlay) return;
|
|
overlay.remove();
|
|
overlay = null;
|
|
document.body.style.overflow = '';
|
|
document.removeEventListener('keydown', onKeydown);
|
|
}
|
|
|
|
function onKeydown(event) {
|
|
if (event.key === 'Escape') closeOverlay();
|
|
}
|
|
|
|
function openOverlay(href, alt) {
|
|
overlay = document.createElement('div');
|
|
overlay.className = 'zoom';
|
|
overlay.setAttribute('role', 'dialog');
|
|
overlay.setAttribute('aria-modal', 'true');
|
|
overlay.setAttribute('aria-label', alt);
|
|
|
|
var close = document.createElement('button');
|
|
close.type = 'button';
|
|
close.className = 'zoom__close';
|
|
close.textContent = 'Close';
|
|
|
|
var image = document.createElement('img');
|
|
image.src = href;
|
|
image.alt = alt;
|
|
|
|
overlay.appendChild(close);
|
|
overlay.appendChild(image);
|
|
overlay.addEventListener('click', function (event) {
|
|
if (event.target === overlay || event.target === close) closeOverlay();
|
|
});
|
|
document.addEventListener('keydown', onKeydown);
|
|
document.body.appendChild(overlay);
|
|
document.body.style.overflow = 'hidden';
|
|
close.focus();
|
|
}
|
|
|
|
Array.prototype.forEach.call(document.querySelectorAll('[data-zoom]'), function (link) {
|
|
link.addEventListener('click', function (event) {
|
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
|
|
return;
|
|
}
|
|
var image = link.querySelector('img');
|
|
event.preventDefault();
|
|
openOverlay(link.getAttribute('href'), (image && image.alt) || 'Menu page');
|
|
});
|
|
});
|
|
})();
|