1(function () { 2 "use strict"; 3 4 const DISMISS_KEY = "skillscout.mdviewerBannerDismissed"; 5 const DISMISSED_CLASS = "mdviewer-banner-dismissed"; 6 // Pageviews a returning visitor gets banner-free before the promo comes back. 7 const PAGEVIEWS_BEFORE_RESET = 10; 8 const scriptUrl = document.currentScript?.src; 9 const assetBaseUrl = scriptUrl ? new URL(".", scriptUrl) : new URL("/assets/", window.location.href); 10 const assetUrl = (fileName) => new URL(fileName, assetBaseUrl).href; 11 12 function readDismissal() { 13 let raw; 14 try { 15 raw = localStorage.getItem(DISMISS_KEY); 16 } catch { 17 return null; 18 } 19 if (!raw) return null; 20 // Legacy value: dismissal was permanent, so start its pageview count now. 21 if (raw === "1") return { views: 0 }; 22 try { 23 const parsed = JSON.parse(raw); 24 if (parsed && typeof parsed === "object") { 25 return { views: Number(parsed.views) || 0 }; 26 } 27 } catch { 28 // Unreadable value behaves like no dismissal at all. 29 } 30 return null; 31 } 32 33 function writeDismissal(views) { 34 try { 35 localStorage.setItem(DISMISS_KEY, JSON.stringify({ views })); 36 } catch { 37 // The banner still closes for this page when storage is unavailable. 38 } 39 } 40 41 function clearDismissal() { 42 try { 43 localStorage.removeItem(DISMISS_KEY); 44 } catch { 45 // Nothing to clean up when storage is unavailable. 46 } 47 } 48 49 function saveBannerDismissal() { 50 writeDismissal(0); 51 } 52 53 // Counts this pageview against the dismissal and reports whether the banner 54 // stays hidden. Runs once per page load, before the banner is rendered. 55 function isBannerDismissed() { 56 const dismissal = readDismissal(); 57 if (!dismissal) return false; 58 59 const views = dismissal.views + 1; 60 if (views >= PAGEVIEWS_BEFORE_RESET) { 61 clearDismissal(); 62 return false; 63 } 64 65 writeDismissal(views); 66 return true; 67 } 68 69 if (isBannerDismissed()) { 70 document.documentElement.classList.add(DISMISSED_CLASS); 71 } 72 73 function createMdviewerBanner() { 74 const banner = document.createElement("aside"); 75 banner.className = "partner-banner"; 76 banner.id = "mdviewerBanner"; 77 banner.setAttribute("aria-label", "MDViewer promotion"); 78 banner.innerHTML = ` 79 <a 80 class="partner-banner-link" 81 href="https://getmd.ma/?utm_source=skillscout&utm_medium=referral&utm_campaign=mdviewer_banner&utm_content=banner" 82 target="_blank" 83 rel="noopener noreferrer" 84 aria-label="View any SKILL.md instantly with MDViewer" 85 data-telemetry-event="Partner.mdViewerOpened" 86 data-telemetry-placement="sitewide-partner-banner" 87 ></a> 88 <div class="partner-banner-inner"> 89 <div class="partner-banner-brand" aria-hidden="true"> 90 <img class="partner-banner-logo" src="${assetUrl("mdviewer-icon.webp")}" alt="" width="512" height="512" /> 91 <span class="partner-banner-name">MDViewer</span> 92 </div> 93 <p class="partner-banner-copy" aria-hidden="true"> 94 <svg class="partner-banner-copy-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 95 <path d="M2.5 12s3.5-5.5 9.5-5.5 9.5 5.5 9.5 5.5-3.5 5.5-9.5 5.5S2.5 12 2.5 12Z"></path> 96 <circle cx="12" cy="12" r="2.5"></circle> 97 </svg> 98 <span>View any <code>SKILL.md</code> instantly <span class="partner-banner-copy-suffix">with MDViewer</span></span> 99 </p> 100 <div class="partner-banner-actions"> 101 <span class="partner-banner-cta" aria-hidden="true">Get MDViewer</span> 102 <button class="partner-banner-dismiss" id="dismissMdviewerBanner" type="button" aria-label="Dismiss MDViewer banner"> 103 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"> 104 <path d="M6 6l12 12M18 6 6 18"></path> 105 </svg> 106 </button> 107 </div> 108 </div> 109 `; 110 return banner; 111 } 112 113 function ensureMdviewerBanner() { 114 let banner = document.getElementById("mdviewerBanner"); 115 if (!banner) { 116 banner = createMdviewerBanner(); 117 const header = document.querySelector(".site-nav-wrap, #navWrap, body > header"); 118 if (header) { 119 header.insertAdjacentElement("afterend", banner); 120 } else { 121 document.body.prepend(banner); 122 } 123 } 124 125 const dismissButton = banner.querySelector("#dismissMdviewerBanner"); 126 if (dismissButton && !dismissButton.dataset.siteShellBound) {
127 dismissButton.dataset.siteShellBound = "true"; 128 dismissButton.addEventListener("click", (event) => { 129 event.preventDefault(); 130 event.stopPropagation(); 131 saveBannerDismissal(); 132 document.documentElement.classList.add(DISMISSED_CLASS); 133 banner.hidden = true; 134 }); 135 } 136 } 137 138 function bindArticleHeader() { 139 const burgerButton = document.getElementById("articleBurgerBtn"); 140 const mobileMenu = document.getElementById("articleMobileMenu"); 141 const navWrap = document.getElementById("articleNavWrap"); 142 143 if (burgerButton && mobileMenu) { 144 burgerButton.addEventListener("click", () => { 145 const open = mobileMenu.classList.toggle("open"); 146 burgerButton.classList.toggle("open", open); 147 burgerButton.setAttribute("aria-expanded", String(open)); 148 }); 149 150 mobileMenu.querySelectorAll(".site-mobile-link").forEach((link) => { 151 link.addEventListener("click", () => { 152 mobileMenu.classList.remove("open"); 153 burgerButton.classList.remove("open"); 154 burgerButton.setAttribute("aria-expanded", "false"); 155 }); 156 }); 157 } 158 159 if (navWrap) { 160 const syncNavBorder = () => navWrap.classList.toggle("scrolled", window.scrollY > 8); 161 syncNavBorder(); 162 window.addEventListener("scroll", syncNavBorder, { passive: true }); 163 } 164 } 165 166 function ensureCoffeeButton() { 167 if (document.getElementById("buyMeACoffeeButton")) return; 168 169 const button = document.createElement("a"); 170 button.className = "bmc-support-button"; 171 button.id = "buyMeACoffeeButton"; 172 button.href = "https://buymeacoffee.com/ulyanas"; 173 button.target = "_blank"; 174 button.rel = "noopener noreferrer"; 175 button.setAttribute("aria-label", "Buy me a coffee"); 176 button.setAttribute("data-telemetry-event", "Support.buyMeACoffeeOpened"); 177 button.setAttribute("data-telemetry-placement", "sitewide-floating-button"); 178 button.innerHTML = ` 179 <img 180 class="bmc-support-logo" 181 src="${assetUrl("bmc-brand-logo.svg")}" 182 alt="" 183 width="195" 184 height="40" 185 aria-hidden="true" 186 /> 187 `; 188 document.body.append(button); 189 190 const footer = document.querySelector("body > footer"); 191 if (!footer) return; 192 193 let updateScheduled = false; 194 const updateFooterOffset = () => { 195 updateScheduled = false; 196 const footerRect = footer.getBoundingClientRect(); 197 const visibleFooterHeight = Math.max(0, window.innerHeight - footerRect.top); 198 const footerOffset = Math.min(visibleFooterHeight, footerRect.height); 199 button.style.setProperty("--bmc-footer-offset", `${Math.ceil(footerOffset)}px`); 200 }; 201 const scheduleFooterOffsetUpdate = () => { 202 if (updateScheduled) return; 203 updateScheduled = true; 204 window.requestAnimationFrame(updateFooterOffset); 205 }; 206 207 updateFooterOffset(); 208 window.addEventListener("scroll", scheduleFooterOffsetUpdate, { passive: true }); 209 window.addEventListener("resize", scheduleFooterOffsetUpdate); 210 } 211 212 document.addEventListener("DOMContentLoaded", () => { 213 ensureMdviewerBanner(); 214 bindArticleHeader(); 215 ensureCoffeeButton(); 216 }); 217})();
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.