1/** 2 * Highlight Element JavaScript 3 * 4 * Handles responsive layout adjustments for highlight sections: 5 * - Synchronizes text width with content limits on desktop 6 * - Matches image height to text content height 7 * - Repositions author credits for contain-fit images 8 */ 9 10(function(){ 11 /** Maximum viewport width considered "mobile" in pixels */ 12 const MOBILE_MAX = 768; 13 14 /** 15 * Checks if the current viewport is mobile size 16 * @returns {boolean} True if window width is <= 768px 17 */ 18 const inMobile = () => window.innerWidth <= MOBILE_MAX; 19 20 /** 21 * Gets all highlight wrapper elements from the DOM 22 * Note: Called each time to handle dynamic WordPress content changes 23 * @returns {NodeList} All elements with class 'highlight-wrapper' 24 */ 25 function getWrappers(){ return document.querySelectorAll('.highlight-wrapper'); } 26 27 /** 28 * Creates a debounced function that uses requestAnimationFrame for smooth performance 29 * Combines setTimeout debouncing with RAF to prevent layout thrashing during rapid events 30 * 31 * @param {Function} fn - The function to debounce 32 * @param {number} delay - Debounce delay in milliseconds 33 * @returns {Function} Debounced function that runs on next animation frame 34 */ 35 function rafDebounce(fn, delay){ 36 let raf = null, t = null, lastArgs = null; 37 const run = () => { raf = null; fn.apply(null, lastArgs || []); }; 38 return function(...args){ 39 lastArgs = args; 40 if(t){ clearTimeout(t); } 41 t = setTimeout(()=>{ t=null; if(!raf){ raf = requestAnimationFrame(run); } }, delay); 42 }; 43 } 44 45 /** 46 * Synchronizes text width with content limit and matches image height to text 47 * 48 * Desktop behavior: 49 * - Calculates how wide the text should be based on the content limit boundary 50 * - Accounts for whether image is on left or right side 51 * - Sets image height to match the calculated text height 52 * 53 * Mobile behavior: 54 * - Resets all custom widths/heights to allow natural stacking 55 * 56 * Uses read-then-write batching to prevent layout thrashing: 57 * - First phase: reads all DOM measurements without writes 58 * - Second phase: applies all DOM changes at once 59 */ 60 function syncTextAndImage(){ 61 const wMobile = inMobile(); 62 const work = []; // batch writes 63 64 getWrappers().forEach(function(wrapper){ 65 const textWrap = wrapper.querySelector('.textcontent-wrapper'); 66 if (textWrap?.classList.contains('no-images')) return; // skip wrappers without images 67 68 const text = textWrap?.querySelector('.textcontent'); 69 const limit = wrapper.querySelector('.highlightWidth .contentlimit'); 70 const image = wrapper.querySelector('.image'); 71 const content = wrapper.querySelector('.highlight-content'); 72 const imageRight = content?.classList.contains('image-right'); 73 74 // Missing required elements - reset to defaults 75 if (!text || !limit){ 76 work.push(()=>{ 77 if(text) text.style.width = ''; 78 if(image) image.style.height = 'auto'; 79 }); 80 return; 81 } 82 // Mobile viewport - use natural layout 83 if (wMobile){ 84 work.push(()=>{ 85 text.style.width = ''; 86 image.style.height = 'auto'; 87 }); 88 return; 89 } 90 91 // READS (no writes here) 92 const limitRect = limit.getBoundingClientRect(); 93 const limitLeft = limitRect.left; 94 const limitW = limitRect.width; 95 const textWrapRect = textWrap.getBoundingClientRect(); 96 const textLeft = textWrapRect.left; 97 const textWrapOuterW = textWrapRect.width; 98 99 let newW; 100 if (imageRight){ 101 // Image on right: calculate text width from right edge 102 const rightLimit = limitLeft + limitW; 103 const rightText = textLeft + textWrapOuterW; 104 newW = limitW - Math.abs(rightLimit - rightText); 105 } else { 106 // Image on left: calculate text width from left edge 107 newW = limitW - (limitLeft - textLeft) * -1; 108 } 109 110 // WRITES (batched) 111 work.push(()=>{ 112 text.style.width = newW + 'px'; 113 if(image) image.style.height = text.offsetHeight + 'px'; 114 }); 115 }); 116
117 work.forEach(fn => fn()); 118 } 119 120 /** 121 * Repositions author credit overlay for images with object-fit: contain 122 * 123 * Problem: When an image uses object-fit: contain, it may have empty space (letterboxing/pillarboxing) 124 * around it. The author credit should be positioned relative to the actual visible image, 125 * not the container edges. 126 * 127 * Solution: Calculates the scale and gaps around the contained image, then offsets the author 128 * credit accordingly so it appears in the corner of the actual image. 129 * 130 * Desktop only - on mobile, author credit uses default positioning. 131 */ 132 function positionContainAuthors(){ 133 const desktop = !inMobile(); 134 const work = []; 135 136 getWrappers().forEach(function(wrapper){ 137 const auth = wrapper.querySelector('.image-author'); 138 if(!auth) return; 139 140 const contain = wrapper.querySelector('.previewimage .contain-image'); 141 const img = contain?.querySelector('img'); 142 // Reset if missing elements or on mobile 143 if(!contain || !img || !desktop){ 144 work.push(()=>{ 145 auth.style.right = ''; 146 auth.style.bottom = ''; 147 }); 148 return; 149 } 150 151 /** 152 * Calculates and applies the author credit position 153 * Waits for image load to get accurate natural dimensions 154 */ 155 const apply = () => { 156 const rect = contain.getBoundingClientRect(); 157 const cw = rect.width, ch = rect.height; // container width/height 158 const nw = img.naturalWidth || cw; // natural image width 159 const nh = img.naturalHeight || ch; // natural image height 160 const scale = Math.min(cw/nw, ch/nh); // how much the image is scaled 161 const gapX = (cw - nw*scale)/2; // horizontal gap (pillarboxing) 162 const gapY = (ch - nh*scale)/2; // vertical gap (letterboxing) 163 const inset = 6; // small offset from image edge 164 work.push(()=>{ 165 auth.style.right = (gapX+inset) + 'px'; 166 auth.style.bottom = (gapY+inset) + 'px'; 167 }); 168 work.forEach(fn => fn()); 169 }; 170 171 // Apply immediately if loaded, otherwise wait for load event 172 if(img.complete) apply(); 173 else img.addEventListener('load', apply, { once: true }); 174 }); 175 } 176 177 /** 178 * Executes all layout adjustment functions 179 * Called on initial page load and on window resize 180 */ 181 function runAll(){ 182 syncTextAndImage(); 183 positionContainAuthors(); 184 } 185 186 /** 187 * Initialize on DOM ready 188 * Sets up initial layout and registers resize listener 189 */ 190 if (document.readyState === 'loading') { 191 document.addEventListener('DOMContentLoaded', function(){ 192 // Lightbox gallery presence is handled globally; just log once if present 193 getWrappers().forEach(function(wrapper){ 194 const gallery = wrapper.querySelector('.lightbox-gallery'); 195 if(gallery) { /* console.log('Highlight lightbox gallery initialized'); */ } 196 }); 197 runAll(); 198 }); 199 } else { 200 // Document already loaded - run immediately 201 runAll(); 202 } 203 204 /** 205 * Handle window resize with optimized debouncing 206 * Uses 80ms delay with RAF to balance responsiveness and performance 207 */ 208 const onResize = rafDebounce(runAll, 80); 209 window.addEventListener('resize', onResize); 210 211 /** 212 * Expose layout functions globally for external use (e.g., contact-flag font sizing) 213 */ 214 window.syncHighlightTextAndImage = syncTextAndImage; 215 window.positionHighlightContainAuthors = positionContainAuthors; 216 217})();
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.