1(function() { 2 'use strict'; 3 4 // Tunable values for parallax behavior and safety caps. 5 var config = { 6 desktopMaxTranslate: 80, 7 mobileMaxTranslate: 45, 8 intensity: 1.2, 9 baseScale: 1, 10 kidsVideoScale: 1.12, 11 minDelta: 0.15, 12 maxOverlayBarHeight: 220, 13 overflowSafetyPadding: 1 14 }; 15 16 var items = Array.from(document.querySelectorAll('.homepageCategoryGrid .inner, .homepageCategoryGrid .videoParallaxMedia')); 17 // Sticky/fixed bars that can occlude visible viewport area. 18 var topBars = Array.from(document.querySelectorAll('#wpadminbar, #header-outer, header#top, #top, .main-navbar, .mainNavBar')); 19 var bottomBars = Array.from(document.querySelectorAll('#footer .footerMobile, .footerMobile, .cookieBanner')); 20 // Cache last translate to skip tiny paint updates. 21 var lastAppliedTranslate = new WeakMap(); 22 // requestAnimationFrame token for scroll/resize throttling. 23 var frame = null; 24 25 // Utility clamp used for normalizing progress. 26 function clamp(value, min, max) { 27 return Math.max(min, Math.min(max, value)); 28 } 29 30 // Match existing project mobile breakpoint. 31 function isMobileViewport() { 32 return window.innerWidth < 768; 33 } 34 35 // Use lower translation range on mobile. 36 function getMaxTranslate() { 37 return isMobileViewport() ? config.mobileMaxTranslate : config.desktopMaxTranslate; 38 } 39 40 // Prefer visualViewport height on mobile for better accuracy during browser chrome changes. 41 function getViewportHeight() { 42 if (isMobileViewport() && window.visualViewport) { 43 return Math.round(window.visualViewport.height); 44 } 45 return Math.round(window.innerHeight); 46 } 47 48 function getItemScale(item) { 49 // Kids video gets a slightly larger scale to preserve visible movement with safe overflow. 50 var isKidsVideo = item.classList.contains('videoParallaxMedia') && item.closest('.kidsThumbImg'); 51 return isKidsVideo ? config.kidsVideoScale : config.baseScale; 52 } 53 54 // For image layers, move relative to the card frame (.img) rather than the inner media node itself. 55 function getMotionSource(item) { 56 if (item.parentElement && item.parentElement.classList.contains('img')) { 57 return item.parentElement; 58 } 59 return item; 60 } 61 62 function getOverlayOffset(bars, viewportHeight, edge) { 63 // Computes occupied area from fixed/sticky bars so parallax visibility is based on true viewport. 64 var occupied = 0; 65 66 bars.forEach(function(bar) { 67 if (!bar || typeof bar.getBoundingClientRect !== 'function') { 68 return; 69 } 70 71 var rect = bar.getBoundingClientRect(); 72 var styles = window.getComputedStyle(bar); 73 var isFixed = styles.position === 'fixed' || styles.position === 'sticky' || bar.id === 'wpadminbar'; 74 75 if (!isFixed || rect.height <= 0 || rect.height > config.maxOverlayBarHeight) { 76 return; 77 } 78 79 if (edge === 'top') { 80 if (rect.bottom <= 0 || rect.top > viewportHeight * 0.25) { 81 return; 82 } 83 occupied = Math.max(occupied, rect.bottom); 84 return; 85 } 86 87 if (rect.top >= viewportHeight || rect.bottom <= 0) { 88 return; 89 } 90 91 var overlap = Math.max(0, viewportHeight - Math.max(rect.top, 0)); 92 occupied = Math.max(occupied, overlap); 93 }); 94 95 return Math.max(0, occupied); 96 } 97 98 function getEffectiveViewport() { 99 // Effective viewport excludes top/bottom overlays that can hide content. 100 var viewportHeight = getViewportHeight() || window.innerHeight; 101 var topOffset = getOverlayOffset(topBars, viewportHeight, 'top'); 102 var bottomOffset = getOverlayOffset(bottomBars, viewportHeight, 'bottom'); 103 var bottom = Math.max(topOffset, viewportHeight - bottomOffset); 104 var height = Math.max(1, bottom - topOffset); 105 106 return { 107 top: topOffset, 108 bottom: bottom, 109 height: height, 110 center: topOffset + (height / 2) 111 }; 112 } 113 114 function updateItem(item, viewport, maxTranslate) { 115 // Skip work when an item is fully outside effective viewport. 116 var motionRect = getMotionSource(item).getBoundingClientRect(); 117 if (!motionRect.height || motionRect.bottom <= viewport.top || motionRect.top >= viewport.bottom) { 118 return; 119 } 120 121 var itemScale = getItemScale(item); 122 var itemRect = item.getBoundingClientRect();
123 // Progress is based on item center traversing the full visible travel (enter -> exit). 124 var elementCenter = motionRect.top + (motionRect.height / 2); 125 var travelDistance = (viewport.height + motionRect.height) / 2; 126 var progress = clamp((elementCenter - viewport.center) / Math.max(1, travelDistance), -1, 1); 127 // Clamp movement by available overflow to avoid revealing blank edges. 128 var baseRange = maxTranslate * config.intensity; 129 var renderedOverflow = (itemRect.height - motionRect.height) / 2; 130 var scaleOverflow = (motionRect.height * Math.max(0, itemScale - 1)) / 2; 131 var safeRange = Math.min(baseRange, Math.max(0, renderedOverflow + scaleOverflow - config.overflowSafetyPadding)); 132 var translateY = progress * safeRange * -1; 133 var previousTranslate = lastAppliedTranslate.has(item) ? lastAppliedTranslate.get(item) : null; 134 135 // Skip sub-pixel-ish updates to reduce style churn. 136 if (previousTranslate !== null && Math.abs(previousTranslate - translateY) < config.minDelta) { 137 return; 138 } 139 140 lastAppliedTranslate.set(item, translateY); 141 // Final parallax transform. 142 item.style.transform = 'translate3d(0, ' + translateY + 'px, 0) scale(' + itemScale + ')'; 143 } 144 145 function renderParallax() { 146 // RAF callback: compute viewport once, then update all parallax items. 147 frame = null; 148 149 var viewport = getEffectiveViewport(); 150 var maxTranslate = getMaxTranslate(); 151 152 items.forEach(function(item) { 153 updateItem(item, viewport, maxTranslate); 154 }); 155 } 156 157 function scheduleParallax() { 158 // Throttle all scroll/resize triggers to one RAF render. 159 if (frame !== null) { 160 return; 161 } 162 163 frame = window.requestAnimationFrame(renderParallax); 164 } 165 166 function initParallax() { 167 // Maintain compatibility with template script that calls window.setTranslate(). 168 if (!items.length) { 169 window.setTranslate = function() {}; 170 return; 171 } 172 173 // Public hook used by homepage_template.php and any future manual refresh callers. 174 window.setTranslate = scheduleParallax; 175 // Keep parallax synced to scroll and viewport changes. 176 window.addEventListener('scroll', scheduleParallax, { passive: true }); 177 window.addEventListener('resize', scheduleParallax); 178 window.addEventListener('orientationchange', scheduleParallax); 179 180 if (window.visualViewport) { 181 window.visualViewport.addEventListener('resize', scheduleParallax); 182 } 183 184 scheduleParallax(); 185 } 186 187 initParallax(); 188})();
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.