1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 LQIP PROGRESSIVE IMAGE (W-023) â widget behavior · closes G51 3 4 A tiny, dependency-free decode-fade adapter. The aspect-ratio box, the LQIP, 5 and the full <img> are ALL server-rendered and fully functional without this 6 file (progressive enhancement). This script adds exactly one layer, gated so a 7 load failure degrades to the plain image in a reserved box: 8 9 BLUR-UP â before decode, hide the full image (via the CSS armed leg, which 10 applies ONLY under the `html.hzo-img-ready` flag this script sets) so the 11 LQIP shows; on img.decode() (fallback `load`) fade the full image in and drop 12 the LQIP. On `error` â or a decode watchdog that never resolves â RECONCILE 13 the optimistic placeholder to a real fallback (drop the frozen blur, show the 14 alt as visible text) so a broken source is never a silent blurred box. 15 16 FAIL-VISIBLE BY CONSTRUCTION: the ready flag is NOT set under reduced-motion or 17 forced-colors (nothing to arm â the full image is already visible), and any 18 throw before it leaves content visible. Emits/consumes NO hzo:* events â decode 19 and failure are native DOM, not cross-component signals (§8.2). Reveal is NOT 20 handled here â [hzo_img] composes UNDER [hzo_reveal] without listening for it. 21 22 Idempotent (guarded by data-hzo-img-ready). Public API on the shared namespace 23 (never a new top-level global): hzo.img = { init, setup }. 24 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 25(function () { 26 'use strict'; 27 28 var mm = window.matchMedia; 29 var WATCHDOG = 10000; // ms â a never-decoding placeholder resolves to the plain image (G23-analogue) 30 31 function reduceMotion() { return !!(mm && mm('(prefers-reduced-motion: reduce)').matches); } 32 function forcedColors() { return !!(mm && mm('(forced-colors: active)').matches); } 33 34 function __(s) { 35 return (window.wp && wp.i18n && wp.i18n.__) ? wp.i18n.__(s, 'hozio-studio') : s; 36 } 37 38 /* Decoded / loaded: reveal the full image, fade out the LQIP. Idempotent. */ 39 function markLoaded(root) { 40 if (!root || root._hzoImgDone) { return; } 41 root._hzoImgDone = true; 42 if (root._hzoImgWatch) { clearTimeout(root._hzoImgWatch); root._hzoImgWatch = null; } 43 root.classList.remove('hzo-img--loading', 'hzo-img--error'); 44 root.classList.add('hzo-img--loaded'); 45 } 46 47 /* Failure branch: reconcile the placeholder to a real fallback on EVERY 48 failure (the G1-analogue â never leave the optimistic blur desynced from 49 truth). Drop the frozen blur (CSS), and surface the alt as visible text. */ 50 function markError(root, full) { 51 if (!root || root._hzoImgDone) { return; } 52 root._hzoImgDone = true; 53 if (root._hzoImgWatch) { clearTimeout(root._hzoImgWatch); root._hzoImgWatch = null; } 54 root.classList.remove('hzo-img--loading', 'hzo-img--loaded'); 55 root.classList.add('hzo-img--error'); 56 57 if (!root.querySelector('.hzo-img__fallback')) { 58 var label = (full && full.getAttribute('alt')) ? full.getAttribute('alt') : __('Image unavailable'); 59 var span = document.createElement('span'); 60 span.className = 'hzo-img__fallback'; 61 span.textContent = label; 62 // Decorative images (alt="") get an aria-hidden frame â no nameless noise. 63 if (!(full && full.getAttribute('alt'))) { span.setAttribute('aria-hidden', 'true'); } 64 root.appendChild(span); 65 } 66 } 67 68 function setup(root) { 69 if (!root || !root.getAttribute) { return; } 70 if (root.getAttribute('data-hzo-img-ready') === 'true') { return; } // idempotent 71 root.setAttribute('data-hzo-img-ready', 'true'); 72 73 var full = root.querySelector('[data-hzo-img-full]') || root.querySelector('.hzo-img__full'); 74 if (!full) { return; } // nothing to enhance â server markup stands 75 76 // Show-instant environments: never arm; the full image is already visible. 77 // Still settle so the LQIP is dropped from behind it. 78 if (reduceMotion() || forcedColors()) { markLoaded(root); return; } 79 80 // Already in cache / decoded before this ran â reveal now (no flash: this 81 // runs synchronously inside boot(), before the browser next paints).
82 if (full.complete && full.naturalWidth > 0) { markLoaded(root); return; } 83 if (full.complete && full.naturalWidth === 0) { markError(root, full); return; } // already errored 84 85 var onLoad = function () { markLoaded(root); }; 86 var onError = function () { markError(root, full); }; 87 88 // decode() gives a clean "ready to paint" signal; load/error are the 89 // fallback for browsers/paths where decode() rejects spuriously. 90 if (typeof full.decode === 'function') { 91 full.decode().then(onLoad, function () { 92 // decode rejected â defer to the load/error listeners rather than 93 // erroring outright (decode can reject even when the image loads). 94 if (full.complete && full.naturalWidth > 0) { markLoaded(root); } 95 }); 96 } 97 full.addEventListener('load', onLoad); 98 full.addEventListener('error', onError); 99 100 // Watchdog: a decode/load that never resolves must not blur forever. 101 root._hzoImgWatch = setTimeout(function () { markLoaded(root); }, WATCHDOG); 102 } 103 104 function init(scope) { 105 var imgs = (scope || document).querySelectorAll('.hzo-img:not([data-hzo-img-ready])'); 106 for (var i = 0; i < imgs.length; i++) { setup(imgs[i]); } 107 } 108 109 /* Print: force every still-loading instance to its full image (CSS covers the 110 static case; this flushes any instance mid-decode at print time). */ 111 function flushForPrint() { 112 var imgs = document.querySelectorAll('.hzo-img--loading'); 113 for (var i = 0; i < imgs.length; i++) { markLoaded(imgs[i]); } 114 } 115 window.addEventListener('beforeprint', flushForPrint); 116 if (mm) { 117 try { mm('print').addListener(function (m) { if (m.matches) { flushForPrint(); } }); } 118 catch (e) { /* addListener unsupported */ } 119 } 120 121 function boot() { 122 // Arm-gate: the CSS hidden leg applies ONLY under this flag, so any failure 123 // BEFORE this line leaves the full image visible. Not set under a 124 // show-instant environment (nothing to arm). 125 if (!(reduceMotion() || forcedColors())) { 126 document.documentElement.classList.add('hzo-img-ready'); 127 } 128 try { init(); } 129 catch (e) { /* leave content visible */ } 130 } 131 if (document.readyState === 'loading') { 132 document.addEventListener('DOMContentLoaded', boot); 133 } else { 134 boot(); 135 } 136 137 // Shared engine namespace (widget convention) â never a new top-level global. 138 window.hzo = window.hzo || {}; 139 window.hzo.img = { init: init, setup: setup }; 140})();
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.