1/* Same-product fallback and conservative display-only white-margin correction. */ 2(function () { 3 'use strict'; 4 5 function isBlank(data, offset) { 6 return data[offset + 3] <= 8 || (data[offset] >= 248 && data[offset + 1] >= 248 && data[offset + 2] >= 248); 7 } 8 9 // Keep every non-white pixel, including logos, labels and shadows. Never guess a product silhouette. 10 function contentBounds(data, width, height) { 11 var left = width, top = height, right = -1, bottom = -1; 12 for (var y = 0; y < height; y++) { 13 for (var x = 0; x < width; x++) { 14 if (!isBlank(data, (y * width + x) * 4)) { 15 left = Math.min(left, x); top = Math.min(top, y); 16 right = Math.max(right, x); bottom = Math.max(bottom, y); 17 } 18 } 19 } 20 if (right < 0) return null; 21 var pad = Math.max(3, Math.ceil(Math.max(width, height) * 0.035)); 22 left = Math.max(0, left - pad); top = Math.max(0, top - pad); 23 right = Math.min(width - 1, right + pad); bottom = Math.min(height - 1, bottom + pad); 24 if (left === 0 && top === 0 && right === width - 1 && bottom === height - 1) return null; 25 return { x: left / width, y: top / height, w: (right + 1 - left) / width, h: (bottom + 1 - top) / height }; 26 } 27 28 function fitBounds(bounds, naturalWidth, naturalHeight, frameWidth, frameHeight) {
29 if (!bounds || !naturalWidth || !naturalHeight || frameWidth <= 20 || frameHeight <= 20) return null; 30 var availableWidth = frameWidth - 20, availableHeight = frameHeight - 20; 31 var baseScale = Math.min(availableWidth / naturalWidth, availableHeight / naturalHeight); 32 var scale = Math.min(availableWidth / (naturalWidth * bounds.w), availableHeight / (naturalHeight * bounds.h), baseScale * 3); 33 var w = naturalWidth * scale, h = naturalHeight * scale; 34 return { width: w, height: h, left: (frameWidth - w * bounds.w) / 2 - w * bounds.x, top: (frameHeight - h * bounds.h) / 2 - h * bounds.y }; 35 } 36 37 // Pure helpers are exported for local algorithm checks, never added to browser globals. 38 if (typeof module !== 'undefined' && module.exports) module.exports = { contentBounds: contentBounds, fitBounds: fitBounds }; 39 if (typeof document === 'undefined') return; 40 41 var samples = new Map(); 42 function sampleImage(img) { 43 var key = img.currentSrc || img.src; 44 if (samples.has(key)) return samples.get(key); 45 var result = null; 46 try { 47 var ratio = Math.min(1, 420 / Math.max(img.naturalWidth, img.naturalHeight)); 48 var canvas = document.createElement('canvas'); 49 canvas.width = Math.max(1, Math.round(img.naturalWidth * ratio)); 50 canvas.height = Math.max(1, Math.round(img.naturalHeight * ratio)); 51 var ctx = canvas.getContext('2d', { willReadFrequently: true }); 52 if (ctx) { 53 ctx.drawImage(img, 0, 0, canvas.width, canvas.height); 54 result = contentBounds(ctx.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height); 55 } 56 } catch (error) { 57 // CORS/offloaded assets: leave the ordinary centered contain display intact. 58 result = null; 59 } 60 samples.set(key, result); 61 return result; 62 } 63 64 function initializeMedia(frame) { 65 var img = frame.querySelector('img'), status = frame.querySelector('.rn-product-image-status'); 66 if (!img) return; 67 var candidates = []; 68 try { candidates = JSON.parse(img.getAttribute('data-rn-image-candidates') || '[]'); } catch (error) { candidates = []; } 69 if (!Array.isArray(candidates)) candidates = []; 70 var index = 0, bounds = null; 71 function clearPosition() { 72 ['width','height','left','top','right','bottom','position','padding','transform','object-fit'].forEach(function (key) { img.style.removeProperty(key); }); 73 } 74 function position() { 75 if (img.hidden || !bounds) return; 76 var fit = fitBounds(bounds, img.naturalWidth, img.naturalHeight, frame.clientWidth, frame.clientHeight); 77 if (!fit) return; 78 img.style.setProperty('position', 'absolute', 'important'); 79 img.style.setProperty('right', 'auto', 'important'); 80 img.style.setProperty('bottom', 'auto', 'important'); 81 img.style.setProperty('padding', '0', 'important'); 82 img.style.setProperty('transform', 'none', 'important'); 83 img.style.setProperty('object-fit', 'contain', 'important'); 84 Object.keys(fit).forEach(function (key) { img.style.setProperty(key, fit[key] + 'px', 'important'); }); 85 } 86 function loaded() { 87 if (!img.naturalWidth) { failed(); return; } 88 img.hidden = false; 89 if (status) status.hidden = true; 90 frame.setAttribute('data-rn-image-state', 'loaded'); 91 if (frame.hasAttribute('data-rn-normalize-image')) { 92 bounds = sampleImage(img); 93 position(); 94 frame.setAttribute('data-rn-margin-state', bounds ? 'adjusted' : 'contain'); 95 } 96 } 97 function failed() { 98 clearPosition(); bounds = null; 99 index++; 100 if (index < candidates.length) { img.src = candidates[index]; return; } 101 img.hidden = true; 102 if (status) status.hidden = false; 103 frame.setAttribute('data-rn-image-state', 'missing'); 104 } 105 img.addEventListener('load', loaded); 106 img.addEventListener('error', failed); 107 if (frame.hasAttribute('data-rn-normalize-image')) { 108 if (typeof ResizeObserver !== 'undefined') new ResizeObserver(position).observe(frame); 109 else window.addEventListener('resize', position); 110 }
111 if (img.complete) { if (img.naturalWidth) loaded(); else failed(); } 112 } 113 114 function initializeFilter(details) { 115 var media = window.matchMedia('(max-width: 760px)'); 116 var mobileOpen = details.getAttribute('data-rn-active-filters') === '1'; 117 function apply() { details.open = media.matches ? mobileOpen : true; } 118 details.addEventListener('toggle', function () { if (media.matches) mobileOpen = details.open; }); 119 if (media.addEventListener) media.addEventListener('change', apply); 120 else media.addListener(apply); 121 apply(); 122 } 123 124 function start() { 125 document.querySelectorAll('[data-rn-product-media]').forEach(initializeMedia); 126 document.querySelectorAll('[data-rn-filter-disclosure]').forEach(initializeFilter); 127 } 128 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); 129 else start(); 130})();
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.