1/* Auto-scrolls every .phone-screen iframe through the real report. 2 Same-origin (misc-reports.pages.dev now, moldscanner.ai later), so the parent can drive scrollTop. 3 Scroll speed is px per second in the iframe's own coordinate space. */ 4(function () { 5 var SPEED = 140; // px/s while scrolling 6 var HOLD_TOP = 1800; // ms to hold on the verdict before scrolling 7 var HOLD_END = 1400; // ms to hold at the bottom before snapping back 8 var MAX_PX = 12000; // scroll this far into the report, then loop (the whole thing is 38,000 px) 9 10 function drive(iframe) { 11 var win = iframe.contentWindow, doc; 12 try { doc = win.document; } catch (e) { return; } 13 var el = doc.scrollingElement || doc.documentElement; 14 // The report sets html{scroll-behavior:smooth}. Under smooth, every scrollTop write starts a new 15 // animation from the current spot, so a per-frame driver moves about 1px total. Force instant. 16 doc.documentElement.style.scrollBehavior = 'auto'; 17 if (doc.body) doc.body.style.scrollBehavior = 'auto'; 18 var y = 0, last = null, phase = 'top', holdUntil = performance.now() + HOLD_TOP; 19 function maxY() { return Math.min(MAX_PX, el.scrollHeight - win.innerHeight); } 20 function tick(now) { 21 if (last === null) last = now; 22 var dt = (now - last) / 1000; last = now; 23 if (phase === 'top') { if (now >= holdUntil) phase = 'scroll'; } 24 else if (phase === 'scroll') { 25 y += SPEED * dt; 26 if (y >= maxY()) { y = maxY(); phase = 'end'; holdUntil = now + HOLD_END; } 27 } else if (phase === 'end') { 28 if (now >= holdUntil) { y = 0; phase = 'top'; holdUntil = now + HOLD_TOP; } 29 } 30 el.scrollTop = y; 31 requestAnimationFrame(tick); 32 } 33 requestAnimationFrame(tick); 34 } 35 36 function arm(iframe) { 37 // Do not wait for the iframe's load event: the report pulls dozens of images from moldscanner.ai 38 // and load can take 6+ seconds. Start as soon as the document has real height. 39 var started = false; 40 function ready() { 41 try { var d = iframe.contentDocument; return d && d.body && (d.scrollingElement || d.documentElement).scrollHeight > 3000; } catch (e) { return false; } 42 } 43 function go() { if (started) return; started = true; drive(iframe); } 44 var poll = setInterval(function () { if (ready()) { clearInterval(poll); go(); } }, 150); 45 iframe.addEventListener('load', function () { clearInterval(poll); go(); }); 46 } 47 48 document.querySelectorAll('.phone-screen iframe:not(.chapters):not(.static)').forEach(arm); 49 50 /* Fade each report iframe in over its poster once it has real content. 51 Until then the poster (top of the same report) is what the visitor sees, so the phone is 52 never a blank white rectangle. If this never fires, the poster just stays: the failure mode 53 is a correct still, not an empty screen. */ 54 function reveal(iframe) { 55 if (iframe.classList.contains('ready')) return; 56 iframe.classList.add('ready'); 57 } 58 document.querySelectorAll('.phone-screen iframe').forEach(function (iframe) { 59 function painted() { 60 try { 61 var d = iframe.contentDocument; 62 if (!d || !d.body) return false; 63 // Wait for the report's own hero photo, not just the document, or we fade in on white. 64 var hero = d.querySelector('img'); 65 return (d.scrollingElement || d.documentElement).scrollHeight > 3000 && 66 (!hero || (hero.complete && hero.naturalWidth > 0)); 67 } catch (e) { return false; } 68 } 69 var poll = setInterval(function () { 70 if (painted()) { clearInterval(poll); reveal(iframe); } 71 }, 150); 72 iframe.addEventListener('load', function () { 73 // load fires only after every image lands; by then it is definitely safe to show. 74 clearInterval(poll); reveal(iframe); 75 }); 76 // Never poll forever. 77 setTimeout(function () { clearInterval(poll); }, 30000); 78 }); 79})();
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.