(() => { // Justified rows: fill each row to the container width at roughly --row-height. const layout = (el) => { const cs = getComputedStyle(el); const gap = parseFloat(cs.columnGap) || 0; const target = parseFloat(cs.getPropertyValue('--row-height')) || 320; const width = el.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); let row = [], sum = 0; const flush = (last) => { let h = (width - gap * (row.length - 1)) / sum; if (last && h > target) h = target; for (const t of row) { t.style.width = Math.floor(h * t.dataset.ar) + 'px'; t.style.height = Math.floor(h) + 'px'; } row = []; sum = 0; }; for (const t of el.children) { row.push(t); sum += +t.dataset.ar; if ((width - gap * (row.length - 1)) / sum <= target) flush(false); } if (row.length) flush(true); }; const layoutAll = () => document.querySelectorAll('.rows').forEach(layout); let timer; addEventListener('resize', () => { clearTimeout(timer); timer = setTimeout(layoutAll, 100); }); layoutAll(); const follow = (sel) => { const a = document.querySelector(sel); if (a) location.href = a.href; }; addEventListener('keydown', (e) => { if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) return; if (e.key === 'ArrowLeft') follow('a[rel=prev]'); else if (e.key === 'ArrowRight') follow('a[rel=next]'); else if (e.key === 'Escape') follow('#up'); }); let x0 = null; addEventListener('touchstart', (e) => { x0 = e.touches[0].clientX; }, { passive: true }); addEventListener('touchend', (e) => { if (x0 === null) return; const dx = e.changedTouches[0].clientX - x0; x0 = null; if (Math.abs(dx) > 50) follow(dx > 0 ? 'a[rel=prev]' : 'a[rel=next]'); }, { passive: true }); })();