1(function() { 2 'use strict'; 3 4 var SPEED_PX_PER_SEC = { slow: 20, medium: 40, fast: 80 }; 5 var RESUME_DELAY_AFTER_POINTER = 1200; // ms â covers mobile momentum settle 6 var RESUME_DELAY_AFTER_HOVER_OR_FOCUS = 250; 7 8 function initLogoRow(section) { 9 var track = section.querySelector('.logo-row-section__track'); 10 var logos = section.querySelector('.logo-row-section__logos'); 11 var fadeLeft = section.querySelector('.logo-row-section__fade--left'); 12 var fadeRight = section.querySelector('.logo-row-section__fade--right'); 13 14 if (!track || !logos) return; 15 16 var autoplay = section.getAttribute('data-autoplay') === 'true'; 17 var speedKey = section.getAttribute('data-autoplay-speed') || 'medium'; 18 var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 19 20 if (!SPEED_PX_PER_SEC[speedKey]) speedKey = 'medium'; 21 22 var originalContentWidth = 0; 23 24 // ---- Scroll state check ---- 25 26 function checkScrollState() { 27 var trackWidth = track.offsetWidth; 28 var logosWidth = logos.scrollWidth; 29 var scrollLeft = track.scrollLeft; 30 var needsScroll = logosWidth > trackWidth + 2; 31 32 section.classList.toggle('has-scroll', needsScroll); 33 34 // When autoplaying, fade visibility is handled by CSS (.is-autoplaying). 35 if (section.classList.contains('is-autoplaying')) return; 36 37 if (needsScroll) { 38 var maxScroll = logosWidth - trackWidth; 39 fadeLeft.classList.toggle('is-visible', scrollLeft > 5); 40 fadeRight.classList.toggle('is-visible', scrollLeft < maxScroll - 5); 41 } else { 42 fadeLeft.classList.remove('is-visible'); 43 fadeRight.classList.remove('is-visible'); 44 } 45 } 46 47 // ---- rAF autoplay loop ---- 48 49 var rafId = null; 50 var lastTs = 0; 51 var isPaused = false; 52 var resumeTimer = null; 53 54 function autoplayStep(ts) { 55 if (!lastTs) lastTs = ts; 56 var deltaMs = ts - lastTs; 57 lastTs = ts; 58 59 if (!isPaused) { 60 var pxPerMs = SPEED_PX_PER_SEC[speedKey] / 1000; 61 track.scrollLeft += pxPerMs * deltaMs; 62 63 // Seamless loop: once we've scrolled past the original content width, 64 // rewind by that width. The cloned second half makes this invisible. 65 if (track.scrollLeft >= originalContentWidth) { 66 track.scrollLeft -= originalContentWidth; 67 } 68 } 69 70 rafId = requestAnimationFrame(autoplayStep); 71 } 72 73 function startAutoplay() { 74 if (rafId !== null) return; 75 lastTs = 0; 76 rafId = requestAnimationFrame(autoplayStep); 77 } 78 79 function stopAutoplay() { 80 if (rafId !== null) cancelAnimationFrame(rafId); 81 rafId = null; 82 lastTs = 0; 83 } 84 85 function pauseAutoplay() { 86 isPaused = true; 87 clearTimeout(resumeTimer); 88 } 89 90 function resumeAutoplay(delay) { 91 clearTimeout(resumeTimer); 92 resumeTimer = setTimeout(function() { 93 isPaused = false; 94 lastTs = 0; // reset timestamp so delta on next frame isn't a huge jump 95 }, delay); 96 } 97 98 // ---- Drag wiring ---- 99 100 var isDragging = false; 101 var didDrag = false; // true if pointer moved beyond click threshold 102 var dragStartX = 0; 103 var dragStartScrollLeft = 0; 104 var capturedPointerId = null; 105 var DRAG_THRESHOLD = 5; // px â movement below this counts as a click, not a drag 106 107 track.addEventListener('pointerdown', function(e) { 108 if (!section.classList.contains('has-scroll')) return; 109 // Custom drag only for mouse/pen; touch keeps native momentum scroll 110 if (e.pointerType !== 'mouse' && e.pointerType !== 'pen') return; 111 112 // Prevent native image/link drag so our custom drag works over logos 113 e.preventDefault(); 114 115 isDragging = true; 116 didDrag = false;
117 dragStartX = e.clientX; 118 dragStartScrollLeft = track.scrollLeft; 119 capturedPointerId = e.pointerId; 120 try { track.setPointerCapture(e.pointerId); } catch (_) {} 121 section.classList.add('is-dragging'); 122 }); 123 124 track.addEventListener('pointermove', function(e) { 125 if (!isDragging) return; 126 if (!didDrag && Math.abs(e.clientX - dragStartX) > DRAG_THRESHOLD) { 127 didDrag = true; 128 } 129 track.scrollLeft = dragStartScrollLeft - (e.clientX - dragStartX); 130 }); 131 132 function endDrag() { 133 if (!isDragging) return; 134 isDragging = false; 135 if (capturedPointerId !== null) { 136 try { track.releasePointerCapture(capturedPointerId); } catch (_) {} 137 capturedPointerId = null; 138 } 139 section.classList.remove('is-dragging'); 140 } 141 142 track.addEventListener('pointerup', endDrag); 143 track.addEventListener('pointercancel', endDrag); 144 145 // Block link navigation when the pointer was dragged, allow on simple clicks. 146 // Also: since preventDefault on pointerdown suppresses the native click event, 147 // synthesize a click for simple taps (no drag) so logo links still work. 148 track.addEventListener('pointerup', function(e) { 149 if (e.pointerType !== 'mouse' && e.pointerType !== 'pen') return; 150 if (didDrag) return; // real drag â do nothing, link stays blocked 151 152 // Simple click â find the nearest <a> under the pointer and navigate 153 var target = document.elementFromPoint(e.clientX, e.clientY); 154 if (!target) return; 155 var link = target.closest('.logo-row-section__logo-link'); 156 if (link) { 157 link.click(); 158 } 159 }); 160 161 // ---- Scroll & resize listeners ---- 162 163 track.addEventListener('scroll', checkScrollState, { passive: true }); 164 165 var resizeTimer; 166 window.addEventListener('resize', function() { 167 clearTimeout(resizeTimer); 168 resizeTimer = setTimeout(function() { 169 checkScrollState(); 170 // If layout now fits without scroll, halt autoplay. 171 // NOTE: We do NOT remove existing clones on resize â accepted limitation for MVP. 172 if (!section.classList.contains('has-scroll')) { 173 stopAutoplay(); 174 section.classList.remove('is-autoplaying'); 175 } 176 }, 150); 177 }, { passive: true }); 178 179 // ---- Init after images load ---- 180 181 function init() { 182 checkScrollState(); 183 184 var hasScroll = section.classList.contains('has-scroll'); 185 186 if (hasScroll && autoplay && !reducedMotion) { 187 // Clone every <li> for seamless loop 188 var items = logos.querySelectorAll('.logo-row-section__logo-item'); 189 items.forEach(function(item) { 190 var clone = item.cloneNode(true); 191 clone.setAttribute('data-logo-clone', 'true'); 192 clone.setAttribute('aria-hidden', 'true'); 193 var links = clone.querySelectorAll('a'); 194 links.forEach(function(a) { a.setAttribute('tabindex', '-1'); }); 195 logos.appendChild(clone); 196 }); 197 198 originalContentWidth = logos.scrollWidth / 2; 199 section.classList.add('is-autoplaying'); 200 startAutoplay(); 201 202 // ---- Pause/resume wiring (only when autoplay is active) ---- 203 204 track.addEventListener('pointerdown', function() { 205 pauseAutoplay(); 206 }); 207 208 track.addEventListener('pointerenter', function(e) { 209 if (e.pointerType === 'mouse') { 210 pauseAutoplay(); 211 } 212 }); 213 214 section.addEventListener('focusin', function() { 215 pauseAutoplay(); 216 }); 217 218 track.addEventListener('pointerup', function() { 219 resumeAutoplay(RESUME_DELAY_AFTER_POINTER); 220 }); 221 222 track.addEventListener('pointercancel', function() { 223 resumeAutoplay(RESUME_DELAY_AFTER_POINTER); 224 }); 225 226 track.addEventListener('pointerleave', function(e) { 227 if (e.pointerType === 'mouse') { 228 resumeAutoplay(RESUME_DELAY_AFTER_HOVER_OR_FOCUS); 229 } 230 }); 231 232 section.addEventListener('focusout', function() { 233 resumeAutoplay(RESUME_DELAY_AFTER_HOVER_OR_FOCUS); 234 }); 235 } 236 } 237 238 var images = logos.querySelectorAll('img'); 239 var loaded = 0; 240 if (images.length === 0) { 241 init(); 242 } else {
243 images.forEach(function(img) { 244 if (img.complete) { 245 loaded++; 246 if (loaded === images.length) init(); 247 } else { 248 img.addEventListener('load', function() { 249 loaded++; 250 if (loaded === images.length) init(); 251 }); 252 } 253 }); 254 } 255 } 256 257 document.addEventListener('DOMContentLoaded', function() { 258 document.querySelectorAll('.logo-row-section').forEach(initLogoRow); 259 }); 260})();
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.