1/** 2 * airo-logo-orientation.js â Runtime size correction for logo orientation mismatches. 3 * 4 * Fetches /airo-media.json, finds <img> elements pointing at /airo-assets/images/logo/*, 5 * and bumps their rendered height when the slot's `naturalOrientation` doesn't match 6 * the slot's layout (e.g. user uploads a square logo into the horizontal slot â without 7 * this correction the logo renders tiny because the template's `h-* w-auto` sizing was 8 * picked for a wider horizontal logo). 9 * 10 * Mirrors the pattern in airo-video-slots.js: loaded synchronously in <head>, uses a 11 * MutationObserver to catch React-rendered images, polls the manifest in dev mode. 12 */ 13;(function () { 14 var SLOT_PREFIX_IMAGES = '/airo-assets/images/' 15 // Must stay aligned with LOGO_NATURAL_SLOT_PATHS in MediaManifestService.ts. 16 var SLOT_TO_EXPECTED_LAYOUT = { 17 'logo/horizontal': 'horizontal', 18 'logo/primary': 'square', 19 'logo/vertical': 'vertical', 20 } 21 var naturalOrientations = {} 22 23 function extractSlotPath(url) { 24 if (!url) return null 25 var idx = url.indexOf(SLOT_PREFIX_IMAGES) 26 if (idx === -1) return null 27 return url.substring(idx + SLOT_PREFIX_IMAGES.length).split('?')[0] 28 } 29 30 var MISMATCH_SCALE = 1.4 31 32 function correctLogo(img) { 33 if (!img.src) return 34 if (img.getAttribute('data-airo-logo-corrected')) return 35 var slotPath = extractSlotPath(img.src) 36 if (!slotPath) return 37 var expectedLayout = SLOT_TO_EXPECTED_LAYOUT[slotPath] 38 if (!expectedLayout) return 39 var naturalOrientation = naturalOrientations[slotPath] 40 if (!naturalOrientation) return 41 42 // Uploads have no transparent layer the agent can rely on â a brightness-0/invert 43 // Tailwind filter would render the opaque upload as a flat blob. Strip it 44 // regardless of whether the URL carries `?variant=` (the agent doesn't always 45 // append one; the underlying invariant is "slot is upload-backed"). 46 img.style.filter = 'none' 47 48 if (naturalOrientation === expectedLayout) { 49 img.setAttribute('data-airo-logo-corrected', naturalOrientation) 50 return 51 } 52 53 function apply(retriesLeft) { 54 if (img.getAttribute('data-airo-logo-corrected')) return 55 var h = img.clientHeight 56 if (!h) { 57 if (retriesLeft > 0) { 58 requestAnimationFrame(function () { apply(retriesLeft - 1) }) 59 } 60 return 61 } 62 img.style.height = (h * MISMATCH_SCALE) + 'px' 63 img.style.width = 'auto' 64 img.setAttribute('data-airo-logo-corrected', naturalOrientation) 65 } 66 67 if (img.complete && img.clientHeight) { 68 apply(0) 69 } else { 70 img.addEventListener('load', function () { apply(20) }, { once: true }) 71 requestAnimationFrame(function () { apply(20) }) 72 } 73 } 74 75 function patchAll() { 76 document.querySelectorAll('img').forEach(correctLogo) 77 } 78 79 // Re-read the manifest, sync naturalOrientations, clear stale corrections, 80 // re-patch. Used on src changes (Vite HMR pushes new versions before our 3s poll). 81 function refreshFromManifest() { 82 fetch('/airo-media.json').then(function (r) { 83 return r.ok ? r.json() : {} 84 }).then(function (m) { 85 for (var k in SLOT_TO_EXPECTED_LAYOUT) { 86 var n = m[k] && m[k].naturalOrientation 87 if (n) naturalOrientations[k] = n 88 else delete naturalOrientations[k] 89 } 90 document.querySelectorAll('img[data-airo-logo-corrected]').forEach(function (img) { 91 img.removeAttribute('data-airo-logo-corrected') 92 img.style.height = '' 93 img.style.width = '' 94 img.style.filter = '' 95 }) 96 patchAll() 97 }).catch(function () {}) 98 } 99 100 fetch('/airo-media.json') 101 .then(function (res) { 102 if (!res.ok) return {} 103 return res.json() 104 }) 105 .then(function (manifest) { 106 for (var key in manifest) { 107 if (manifest[key] && manifest[key].naturalOrientation) { 108 naturalOrientations[key] = manifest[key].naturalOrientation 109 } 110 } 111 patchAll() 112 113 var observer = new MutationObserver(function (mutations) { 114 for (var i = 0; i < mutations.length; i++) { 115 var mutation = mutations[i] 116 if (mutation.type === 'childList') { 117 var added = mutation.addedNodes 118 for (var j = 0; j < added.length; j++) { 119 var node = added[j] 120 if (node instanceof HTMLImageElement) { 121 correctLogo(node) 122 } else if (node instanceof HTMLElement) { 123 node.querySelectorAll('img').forEach(correctLogo) 124 } 125 } 126 } else if (mutation.type === 'attributes' && mutation.attributeName === 'src') { 127 var target = mutation.target 128 if (target instanceof HTMLImageElement && extractSlotPath(target.src)) { 129 target.removeAttribute('data-airo-logo-corrected') 130 target.style.height = '' 131 target.style.width = '' 132 target.style.filter = '' 133 // Manifest may have changed alongside the src (Vite HMR pushes versions 134 // instantly; our 3s poll lags). Refresh before correcting. 135 refreshFromManifest() 136 } 137 } 138 } 139 }) 140 observer.observe(document.documentElement, { 141 childList: true, 142 subtree: true, 143 attributes: true, 144 attributeFilter: ['src'], 145 }) 146 147 var isDevMode = window.__AIRO_DEV_MODE__ === true || location.hostname === 'localhost' || location.hostname === '127.0.0.1' 148 if (isDevMode) { 149 var pollFailures = 0 150 var pollIntervalId = setInterval(function () { 151 fetch('/airo-media.json').then(function (r) { 152 if (!r.ok) return {} 153 return r.json() 154 }).then(function (m) { 155 pollFailures = 0 156 var changed = false 157 for (var k in m) { 158 var n = m[k] && m[k].naturalOrientation 159 if (n && naturalOrientations[k] !== n) { 160 naturalOrientations[k] = n 161 changed = true 162 } else if (!n && naturalOrientations[k]) { 163 delete naturalOrientations[k] 164 changed = true 165 } 166 } 167 if (changed) { 168 document.querySelectorAll('img[data-airo-logo-corrected]').forEach(function (img) { 169 img.removeAttribute('data-airo-logo-corrected') 170 img.style.height = '' 171 img.style.width = '' 172 img.style.filter = '' 173 })
174 patchAll() 175 } 176 }).catch(function (err) { 177 pollFailures++ 178 if (pollFailures === 1) { 179 // eslint-disable-next-line no-console 180 console.warn('[airo-logo-orientation] manifest poll failed:', err.message || err) 181 } 182 if (pollFailures >= 5) { 183 clearInterval(pollIntervalId) 184 } 185 }) 186 }, 3000) 187 } 188 }) 189 .catch(function () {}) 190})()
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.