1window.tnmInitRelatedCards = function (scope) { 2(scope || document).querySelectorAll('[data-related-cards]').forEach(function (root) { 3 if (root.dataset.relatedReady) return; 4 root.dataset.relatedReady = '1'; 5 var track = root.querySelector('.related-cards__track'); 6 var cards = Array.from(track.children); 7 var controls = root.querySelector('.related-cards__controls'); 8 var previous = root.querySelector('[data-related-prev]'); 9 var next = root.querySelector('[data-related-next]'); 10 var count = root.querySelector('.related-cards__count'); 11 var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); 12 var timer; 13 if (!cards.length) return; 14 15 function visibleCount() { 16 return parseInt(getComputedStyle(track).getPropertyValue('--related-visible'), 10) || 1; 17 } 18 function positions() { 19 var left = cards[0].getBoundingClientRect().left; 20 return cards.map(function (card) { return card.getBoundingClientRect().left - left; }); 21 } 22 function update() { 23 var max = track.scrollWidth - track.clientWidth; 24 controls.hidden = max < 2; 25 previous.disabled = track.scrollLeft <= 2; 26 next.disabled = track.scrollLeft >= max - 2; 27 var bounds = track.getBoundingClientRect(); 28 var visible = []; 29 cards.forEach(function (card, index) { 30 var rect = card.getBoundingClientRect(); 31 if (rect.left >= bounds.left - 2 && rect.right <= bounds.right + 2) visible.push(index + 1); 32 }); 33 if (visible.length) { 34 var first = visible[0], last = visible[visible.length - 1]; 35 count.textContent = (first === last ? first : first + 'â' + last) + ' of ' + cards.length; 36 } 37 } 38 function move(direction) { 39 var offsets = positions(); 40 var current = offsets.reduce(function (best, offset, index) { 41 return Math.abs(offset - track.scrollLeft) < Math.abs(offsets[best] - track.scrollLeft) ? index : best; 42 }, 0); 43 var target = Math.max(0, Math.min(cards.length - 1, current + direction * visibleCount())); 44 track.scrollTo({ left: offsets[target], behavior: reducedMotion.matches ? 'instant' : 'smooth' }); 45 } 46 previous.addEventListener('click', function () { move(-1); }); 47 next.addEventListener('click', function () { move(1); }); 48 root.addEventListener('keydown', function (event) { 49 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return; 50 if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; 51 event.preventDefault(); 52 // Keep the gallery and the admin catalogue shortcuts from also moving. 53 event.stopPropagation(); 54 move(event.key === 'ArrowLeft' ? -1 : 1); 55 }); 56 track.addEventListener('scroll', function () { 57 clearTimeout(timer); 58 timer = setTimeout(update, 120); 59 }, { passive: true }); 60 if (window.ResizeObserver) new ResizeObserver(update).observe(track); 61 else window.addEventListener('resize', update); 62 update(); 63}); 64}; 65window.tnmInitRelatedCards(document);
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.