1// Read the already-loaded local images; no requests or page screenshots are needed. 2const imageSamples = new WeakMap(); 3let activeBrand, frame; 4 5function imagePixel(image, x, y) { 6 if (!image.complete || !image.naturalWidth) return null; 7 let sample = imageSamples.get(image); 8 if (!sample || sample.src !== image.currentSrc) { 9 const canvas = document.createElement('canvas'); 10 const scale = 96 / Math.max(image.naturalWidth, image.naturalHeight); 11 canvas.width = Math.max(1, Math.round(image.naturalWidth * scale)); 12 canvas.height = Math.max(1, Math.round(image.naturalHeight * scale)); 13 try { 14 const context = canvas.getContext('2d', {willReadFrequently: true}); 15 context.drawImage(image, 0, 0, canvas.width, canvas.height); 16 sample = {src: image.currentSrc, pixels: context.getImageData(0, 0, canvas.width, canvas.height).data, width: canvas.width, height: canvas.height}; 17 } catch { 18 // An image without canvas permission leaves the normal surface-color fallback. 19 sample = {src: image.currentSrc}; 20 } 21 imageSamples.set(image, sample); 22 } 23 if (!sample.pixels) return null; 24 const rect = image.getBoundingClientRect(), style = getComputedStyle(image); 25 let width = rect.width, height = rect.height; 26 const scale = style.objectFit === 'cover' 27 ? Math.max(width / image.naturalWidth, height / image.naturalHeight) 28 : Math.min(width / image.naturalWidth, height / image.naturalHeight); 29 if (['cover', 'contain', 'scale-down', 'none'].includes(style.objectFit)) { 30 const fit = style.objectFit === 'none' ? 1 : style.objectFit === 'scale-down' ? Math.min(1, scale) : scale; 31 width = image.naturalWidth * fit; 32 height = image.naturalHeight * fit; 33 } 34 const position = style.objectPosition.split(' ');
35 const offset = (value, space) => value?.endsWith('%') ? space * parseFloat(value) / 100 : parseFloat(value) || 0; 36 const u = (x - rect.left - offset(position[0], rect.width - width)) / width; 37 const v = (y - rect.top - offset(position[1], rect.height - height)) / height; 38 if (u < 0 || u >= 1 || v < 0 || v >= 1) return null; 39 const index = (Math.floor(v * sample.height) * sample.width + Math.floor(u * sample.width)) * 4; 40 return [...sample.pixels.slice(index, index + 3), sample.pixels[index + 3] / 255]; 41} 42 43function color(value) { 44 const parts = value.match(/[\d.]+/g)?.map(Number); 45 return parts?.length >= 3 ? [parts[0], parts[1], parts[2], parts[3] ?? 1] : [0, 0, 0, 0]; 46} 47 48function over(front, back) { 49 return front.slice(0, 3).map((channel, i) => channel * front[3] + back[i] * (1 - front[3])); 50} 51 52function surfacePixel(element, x, y, cache) { 53 if (!element) return [250, 248, 239]; 54 let surface = cache.get(element); 55 if (!surface) { 56 const style = getComputedStyle(element); 57 surface = color(style.backgroundColor); 58 // The site's gradient surfaces have a solid or light paper base. 59 if (surface[3] < 1 && style.backgroundImage.includes('gradient(')) { 60 const stops = style.backgroundImage.match(/rgba?\([^)]*\)/g)?.map(color) || []; 61 const opaque = stops.find(stop => stop[3] === 1); 62 if (opaque) surface = opaque; 63 } 64 cache.set(element, surface); 65 } 66 const base = surface[3] === 1 ? surface.slice(0, 3) : over(surface, surfacePixel(element.parentElement, x, y, cache)); 67 const pixel = element instanceof HTMLImageElement ? imagePixel(element, x, y) : null; 68 return pixel ? over(pixel, base) : base; 69} 70 71function luminance(rgb) { 72 const linear = rgb.map(channel => { 73 const value = channel / 255; 74 return value <= .04045 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4; 75 }); 76 return linear[0] * .2126 + linear[1] * .7152 + linear[2] * .0722; 77} 78 79function update() { 80 frame = null; 81 if (!activeBrand?.isConnected) return; 82 const header = activeBrand.closest('.site-header'), cache = new Map(); 83 for (const part of activeBrand.querySelectorAll('.vector-logo,.brand-subheading')) { 84 const rect = part.getBoundingClientRect(); 85 if (!rect.width || !rect.height) continue; 86 let sum = 0, count = 0; 87 for (let row = 0; row < 3; row++) { 88 for (let column = 0; column < 7; column++) { 89 const x = rect.left + rect.width * (column + .5) / 7; 90 const y = rect.top + rect.height * (row + .5) / 3; 91 if (x < 0 || y < 0 || x >= innerWidth || y >= innerHeight) continue; 92 const element = document.elementsFromPoint(x, y).find(item => !header.contains(item)); 93 sum += luminance(surfacePixel(element, x, y, cache)); 94 count++; 95 } 96 } 97 // Hysteresis prevents flicker around the light/dark crossover while scrolling. 98 const threshold = part.classList.contains('is-on-dark') ? .26 : .22; 99 part.classList.toggle('is-on-dark', count > 0 && sum / count < threshold); 100 } 101} 102 103function schedule() { 104 if (!frame) frame = requestAnimationFrame(update); 105} 106 107window.addEventListener('scroll', schedule, {passive: true}); 108window.addEventListener('resize', schedule, {passive: true}); 109document.addEventListener('load', event => { 110 if (event.target instanceof HTMLImageElement) schedule(); 111}, true); 112document.fonts?.ready.then(schedule); 113 114export function bindLogoContrast(brand) { 115 activeBrand = brand; 116 schedule(); 117}
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.