1/* ============================================================ 2 iyush.dev â shared behavior 3 Theme engine (light/dark + dice themes), floating command shell, 4 live telemetry (npm + GitHub), scroll reveals, homepage boot. 5 Linked (deferred) by every page. 6 ============================================================ */ 7(function () { 8 "use strict"; 9 10 var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 11 12 /* ---------- Storage that never crashes ---------- */ 13 var mem = {}; 14 function storeGet(key) { 15 try { return window.localStorage.getItem(key); } catch (e) { return mem[key] || null; } 16 } 17 function storeSet(key, val) { 18 mem[key] = val; 19 try { window.localStorage.setItem(key, val); } catch (e) { /* memory only */ } 20 } 21 22 /* ---------- Analytics (Google Analytics 4 / gtag) ---------- */ 23 // ð Paste your GA4 Measurement ID below (format: "G-XXXXXXXXXX") to switch analytics ON. 24 // Empty string = disabled (safe no-op). Auto-skipped on localhost so your own 25 // dev traffic never pollutes the numbers. This is the ONLY place you set it. 26 var GA_ID = "G-GKC1N1X7KY"; 27 function initAnalytics() { 28 if (!GA_ID || GA_ID.indexOf("G-") !== 0) return; 29 var h = location.hostname; 30 if (!h || h === "localhost" || h === "127.0.0.1" || h.slice(-6) === ".local") return; 31 var s = document.createElement("script"); 32 s.async = true; 33 s.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(GA_ID); 34 document.head.appendChild(s); 35 window.dataLayer = window.dataLayer || []; 36 window.gtag = function () { window.dataLayer.push(arguments); }; 37 window.gtag("js", new Date()); 38 window.gtag("config", GA_ID); 39 } 40 // Fire a custom event (no-op until GA is configured). 41 function track(name, params) { 42 try { if (window.gtag) window.gtag("event", name, params || {}); } catch (e) { /* ignore */ } 43 } 44 45 /* ---------- Themes ---------- */ 46 // mode drives the light/dark toggle behavior + the OS-default logic. 47 var THEMES = { 48 dark: { label: "dark", mode: "dark", icon: "â¾" }, 49 light: { label: "light", mode: "light", icon: "â" }, 50 onepiece: { label: "one piece", mode: "dark", icon: "â" }, 51 matrix: { label: "matrix", mode: "dark", icon: "â" }, 52 synthwave: { label: "synthwave", mode: "dark", icon: "â" }, 53 dracula: { label: "dracula", mode: "dark", icon: "â" }, 54 solarized: { label: "solarized", mode: "light", icon: "â" } 55 }; 56 function currentTheme() { 57 var t = document.documentElement.getAttribute("data-theme"); 58 return THEMES[t] ? t : "dark"; 59 } 60 function updateToggle(theme) { 61 var btn = document.getElementById("themeToggle"); 62 if (!btn) return; 63 var t = THEMES[theme] || THEMES.dark; 64 btn.setAttribute("aria-pressed", t.mode === "light" ? "true" : "false"); 65 btn.setAttribute("aria-label", "Switch light/dark theme (current: " + t.label + ")"); 66 var icon = btn.querySelector(".tt-icon"); 67 var label = btn.querySelector(".tt-label"); 68 if (icon) icon.textContent = t.icon; 69 if (label) label.textContent = t.label; 70 } 71 function applyTheme(theme) { 72 if (!THEMES[theme]) theme = "dark"; 73 document.documentElement.setAttribute("data-theme", theme); 74 storeSet("theme", theme); 75 updateToggle(theme); 76 } 77 function toggleTheme() { 78 // Always resolve to one of the two base modes. 79 var mode = (THEMES[currentTheme()] || THEMES.dark).mode; 80 applyTheme(mode === "dark" ? "light" : "dark"); 81 } 82 function rollTheme() { 83 var cur = currentTheme(); 84 var keys = Object.keys(THEMES).filter(function (k) { return k !== cur; }); 85 var pick = keys[Math.floor(Math.random() * keys.length)]; 86 applyTheme(pick); 87 toast("ð² rolled: " + THEMES[pick].label); 88 track("theme_change", { theme: pick, via: "dice" }); 89 return pick; 90 } 91 92 /* ---------- Toast (theme roll announcements) ---------- */ 93 var toastEl = null, toastTimer = null; 94 function toast(msg) { 95 if (!toastEl) return; 96 toastEl.textContent = msg; 97 toastEl.hidden = false;
98 void toastEl.offsetWidth; // reflow so the transition runs 99 toastEl.classList.add("show"); 100 if (toastTimer) clearTimeout(toastTimer); 101 toastTimer = setTimeout(function () { 102 toastEl.classList.remove("show"); 103 setTimeout(function () { toastEl.hidden = true; }, reduce ? 0 : 260); 104 }, reduce ? 1200 : 1700); 105 } 106 107 function injectThemeControls() { 108 var toggle = document.getElementById("themeToggle"); 109 if (toggle && toggle.parentNode && !document.getElementById("diceRoll")) { 110 var d = document.createElement("button"); 111 d.id = "diceRoll"; 112 d.type = "button"; 113 d.className = "theme-toggle dice"; 114 d.setAttribute("aria-label", "Roll a random theme"); 115 d.title = "Roll a random theme"; 116 d.innerHTML = '<span class="tt-icon" aria-hidden="true">ð²</span>'; 117 d.addEventListener("click", rollTheme); 118 toggle.parentNode.insertBefore(d, toggle); 119 } 120 if (!document.getElementById("themeToast")) { 121 toastEl = document.createElement("div"); 122 toastEl.id = "themeToast"; 123 toastEl.className = "theme-toast"; 124 toastEl.setAttribute("role", "status"); 125 toastEl.setAttribute("aria-live", "polite"); 126 toastEl.hidden = true; 127 document.body.appendChild(toastEl); 128 } else { 129 toastEl = document.getElementById("themeToast"); 130 } 131 } 132 133 // Add the "games" nav link on every page (keeps the shell DRY). 134 function injectNav() { 135 var nav = document.querySelector(".sb-nav"); 136 if (!nav || nav.querySelector('a[href="games.html"]')) return; 137 var a = document.createElement("a"); 138 a.href = "games.html"; 139 a.textContent = "games"; 140 if (/(^|\/)games\.html$/.test(location.pathname)) a.setAttribute("aria-current", "page"); 141 var ext = nav.querySelector(".ext"); 142 nav.insertBefore(a, ext || null); 143 } 144 145 /* ---------- Live telemetry (graceful â silent on failure) ---------- */ 146 var npmWeekly = null; // owthorize weekly downloads, once fetched 147 148 function fmtNum(n) { 149 if (n == null) return "â"; 150 if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(/\.0$/, "") + "k"; 151 return String(n); 152 } 153 154 function renderNpm() { 155 if (npmWeekly == null) return; 156 var card = document.querySelector('a.service[href*="owthorize"]'); 157 if (!card) return; 158 var m = card.querySelector(".live-metric"); 159 if (!m) { 160 m = document.createElement("span"); 161 m.className = "live-metric"; 162 var link = card.querySelector(".service__link"); 163 if (link) card.insertBefore(m, link); else card.appendChild(m); 164 } 165 m.innerHTML = '<span class="ld" aria-hidden="true"></span><span><b>' + 166 fmtNum(npmWeekly) + "</b> installs / week</span>"; 167 } 168 169 function fetchNpm() { 170 try { 171 fetch("https://api.npmjs.org/downloads/point/last-week/owthorize") 172 .then(function (r) { return r.ok ? r.json() : null; }) 173 .then(function (d) { 174 if (d && typeof d.downloads === "number") { npmWeekly = d.downloads; renderNpm(); } 175 }) 176 .catch(function () {}); 177 } catch (e) { /* ignore */ } 178 } 179 180 var ghState = null; // { days: [counts...], lastPushMs } 181 182 function timeAgo(ms) { 183 if (!ms) return ""; 184 var s = Math.max(1, Math.floor((Date.now() - ms) / 1000)); 185 if (s < 3600) return Math.floor(s / 60) + "m ago"; 186 if (s < 86400) return Math.floor(s / 3600) + "h ago"; 187 return Math.floor(s / 86400) + "d ago"; 188 } 189 190 function renderGh() { 191 if (!ghState) return; 192 var inner = document.querySelector(".footer__inner"); 193 if (!inner) return; 194 var sig = document.getElementById("ghSignal"); 195 if (!sig) { 196 sig = document.createElement("span"); 197 sig.id = "ghSignal"; 198 sig.className = "gh-signal"; 199 sig.title = "GitHub push activity â last 14 days"; 200 inner.insertBefore(sig, inner.firstChild); 201 } 202 var max = Math.max(1, Math.max.apply(null, ghState.days)); 203 var bars = ghState.days.map(function (c) { 204 var h = c === 0 ? 2 : Math.round(3 + (c / max) * 13); 205 var op = c === 0 ? 0.25 : 0.85; 206 return '<i style="height:' + h + "px;opacity:" + op + '"></i>'; 207 }).join(""); 208 sig.innerHTML = '<span class="gh-label">gh activity</span>' + 209 '<span class="gh-bars" aria-hidden="true">' + bars + "</span>" + 210 (ghState.lastPushMs ? '<span class="gh-last">push ' + timeAgo(ghState.lastPushMs) + "</span>" : ""); 211 } 212 213 function fetchGitHub() { 214 try { 215 fetch("https://api.github.com/users/Spyyy004/events/public?per_page=100") 216 .then(function (r) { return r.ok ? r.json() : null; }) 217 .then(function (events) { 218 if (!Array.isArray(events)) return; 219 var days = [], i; 220 for (i = 0; i < 14; i++) days.push(0); 221 var now = Date.now(), lastPush = 0;
222 events.forEach(function (e) { 223 var t = Date.parse(e.created_at); 224 if (isNaN(t)) return; 225 if (e.type === "PushEvent") { 226 if (t > lastPush) lastPush = t; 227 var idx = 13 - Math.floor((now - t) / 86400000); 228 if (idx >= 0 && idx < 14) { 229 var commits = (e.payload && e.payload.commits && e.payload.commits.length) || 1; 230 days[idx] += commits; 231 } 232 } 233 }); 234 ghState = { days: days, lastPushMs: lastPush }; 235 renderGh(); 236 }) 237 .catch(function () {}); 238 } catch (e) { /* ignore */ } 239 } 240 241 /* ---------- Command bar / shell ---------- */ 242 var ROUTES = { home: "index.html", projects: "projects.html", blog: "blog.html", games: "games.html" }; 243 var OPEN = { 244 github: "https://github.com/Spyyy004", 245 linkedin: "https://linkedin.com/in/ayush-pawar004", 246 email: "mailto:[email protected]" 247 }; 248 var STACK = ["javascript", "typescript", "node", "react", "python", "postgres", "ml", "dns"]; 249 250 function esc(s) { 251 return String(s).replace(/[&<>"']/g, function (c) { 252 return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; 253 }); 254 } 255 function yearsShipping() { 256 try { return Math.max(1, new Date().getFullYear() - 2022); } catch (e) { return 3; } 257 } 258 259 function resolveRoute(s) { 260 var t = s.replace(/^cd\b\s*/, "").replace(/^~\/?/, "").replace(/^\//, "").replace(/\/+$/, "").trim(); 261 if (t === "" || t === "~" || t === "home" || t === "index") return { label: "home", url: ROUTES.home }; 262 if (t === "projects" || t === "project" || t === "services") return { label: "projects", url: ROUTES.projects }; 263 if (t === "blog" || t === "blogs" || t === "posts") return { label: "blog", url: ROUTES.blog }; 264 if (t === "games" || t === "game" || t === "arcade" || t === "play") return { label: "games", url: ROUTES.games }; 265 return null; 266 } 267 268 function initCommandBar() { 269 var bar = document.getElementById("cmdbar"); 270 var input = document.getElementById("cmdInput"); 271 var pop = document.getElementById("cmdPop"); 272 if (!bar || !input || !pop) return; 273 274 var history = [], hIndex = -1, popTimer = null; 275 276 function showPop(html) { 277 if (popTimer) { clearTimeout(popTimer); popTimer = null; } 278 pop.innerHTML = html; 279 pop.hidden = false; 280 } 281 function autoPop(html) { showPop(html); popTimer = setTimeout(hidePop, 2800); } 282 function hidePop() { pop.hidden = true; pop.innerHTML = ""; } 283 function flash(msg, isErr) { 284 autoPop('<span class="pop-line' + (isErr ? " pop-err" : "") + '">' + msg + "</span>"); 285 } 286 287 function showHelp() { 288 showPop( 289 '<div class="pop-h">available commands</div>' + 290 '<ul class="pop-list">' + 291 "<li><b>cd projects</b> · <b>cd blog</b> · <b>cd home</b><span>navigate</span></li>" + 292 "<li><b>ls</b><span>list routes</span></li>" + 293 "<li><b>neofetch</b><span>system card</span></li>" + 294 "<li><b>top</b><span>projects as processes</span></li>" + 295 "<li><b>stack</b><span>loaded modules</span></li>" + 296 "<li><b>theme</b> · light · dark · <name><span>switch appearance</span></li>" + 297 "<li><b>roll</b><span>random theme ð²</span></li>" + 298 "<li><b>open</b> github · linkedin · email<span>external links</span></li>" + 299 "<li><b>whoami</b> · <b>date</b> · <b>clear</b><span> </span></li>" + 300 "</ul>" + 301 '<div class="pop-tip">press <kbd>/</kbd> focus · <kbd>â</kbd><kbd>â</kbd> history · <kbd>esc</kbd> dismiss</div>' 302 ); 303 } 304 function showLs() { 305 showPop( 306 '<div class="pop-h">routes</div><ul class="pop-list">' + 307 "<li><b>~/</b><span>home</span></li>" + 308 "<li><b>~/projects</b><span>all services</span></li>" + 309 "<li><b>~/blog</b><span>writing</span></li>" + 310 "<li><b>~/games</b><span>the arcade</span></li>" + 311 "</ul>" 312 ); 313 } 314 function showNeofetch() { 315 var yrs = yearsShipping(); 316 var npm = npmWeekly != null ? (fmtNum(npmWeekly) + "/wk on npm") : "on npm"; 317 var L = function (k, v) { return '<span class="k">' + k + "</span> " + v; }; 318 showPop( 319 '<pre class="pop-pre">' + 320 '<span class="accent">[email protected]</span>\n' + 321 "---------------\n" + 322 L("host ", "full-stack engineer") + "\n" + 323 L("uptime ", yrs + " yrs (shipping since 2022)") + "\n" +
324 L("shipped ", "6 products · 1 acquired") + "\n" + 325 L("stack ", "js · ts · node · react · python") + "\n" + 326 L("live ", '<span class="live">owthorize</span> â ' + npm) + "\n" + 327 L("contact ", "[email protected]") + "\n" + 328 L("theme ", currentTheme() + ' <span class="k">(try: roll)</span>') + 329 "</pre>" 330 ); 331 } 332 function showTop() { 333 var pad = function (s, n) { s = String(s); while (s.length < n) s += " "; return s; }; 334 var row = function (pid, statusCls, status, name, stat) { 335 return pad(pid, 5) + '<span class="' + statusCls + '">' + pad(status, 10) + "</span>" + 336 pad(name, 18) + '<span class="k">' + stat + "</span>"; 337 }; 338 var owStat = npmWeekly != null ? (fmtNum(npmWeekly) + "/wk") : "live on npm"; 339 showPop( 340 '<pre class="pop-pre">' + 341 '<span class="k">tasks: 3 total · 1 live · 1 acquired · 1 shipped</span>\n\n' + 342 '<span class="k">' + pad("PID", 5) + pad("STATUS", 10) + pad("PROJECT", 18) + "STAT</span>\n" + 343 row("0", "live", "live", "owthorize", owStat) + "\n" + 344 row("1", "acquired", "acquired", "snoopsignal", "sold · $3k") + "\n" + 345 row("2", "archived", "shipped", "sqlpremierleague", "700+ users") + 346 "</pre>" 347 ); 348 } 349 function showStack() { 350 showPop( 351 '<div class="pop-h">loaded modules · lsmod</div>' + 352 '<pre class="pop-pre">' + STACK.join(" ") + "</pre>" 353 ); 354 } 355 356 function navigate(url) { 357 var go = function () { window.location.assign(url); }; 358 if (reduce) go(); else setTimeout(go, 220); 359 } 360 361 function run(raw) { 362 var cmd = (raw || "").trim().replace(/\s+/g, " "); 363 if (!cmd) return; 364 history.push(cmd); hIndex = history.length; 365 var lc = cmd.toLowerCase().replace(/^[>$]\s*/, ""); 366 track("command", { command: lc.split(" ")[0] || "unknown" }); 367 368 if (lc === "clear") { input.value = ""; updateIdle(); hidePop(); return; } 369 if (lc === "help" || lc === "?" || lc === "man") { showHelp(); return; } 370 if (lc === "ls" || lc === "dir") { showLs(); return; } 371 if (lc === "neofetch" || lc === "about") { showNeofetch(); return; } 372 if (lc === "top" || lc === "htop" || lc === "ps") { showTop(); return; } 373 if (lc === "stack" || lc === "lsmod" || lc === "skills") { showStack(); return; } 374 if (lc === "whoami") { flash("ayush pawar â full-stack engineer who ships fast."); return; } 375 if (lc === "date" || lc === "uptime") { 376 var now = new Date(); 377 flash("<b>" + esc(now.toString().replace(/\s\(.*\)$/, "")) + "</b> · uptime " + yearsShipping() + "y"); 378 return; 379 } 380 if (lc === "roll" || lc === "dice" || lc === "theme random" || lc === "shuffle") {
381 var picked = rollTheme(); flash("ð² rolled: <b>" + esc(THEMES[picked].label) + "</b>"); return; 382 } 383 if (lc === "theme") { toggleTheme(); flash("theme → <b>" + currentTheme() + "</b>"); return; } 384 if (lc.indexOf("theme ") === 0) { 385 var name = lc.slice(6).trim(); 386 if (THEMES[name]) { applyTheme(name); flash("theme → <b>" + esc(name) + "</b>"); } 387 else { flash("unknown theme: <b>" + esc(name) + "</b> â try 'roll' or theme light/dark", true); } 388 return; 389 } 390 if (lc === "sudo hire-me" || lc === "sudo hire me") { 391 flash("permission granted â â opening mailâ¦"); 392 window.location.href = OPEN.email; return; 393 } 394 if (lc.indexOf("sudo ") === 0) { flash("nice try â you're not in the sudoers file. ð"); return; } 395 396 if (lc.indexOf("open ") === 0) { 397 var key = lc.slice(5).trim().replace(/^~\//, ""); 398 if (key === "mail") key = "email"; 399 if (OPEN[key]) { 400 flash("opening <b>" + esc(key) + "</b>â¦"); 401 if (key === "email") window.location.href = OPEN[key]; 402 else window.open(OPEN[key], "_blank", "noopener"); 403 } else { flash("can't open '" + esc(key) + "' â try github, linkedin, or email", true); } 404 return; 405 } 406 407 var route = resolveRoute(lc); 408 if (route) { 409 input.value = ""; updateIdle(); 410 flash("cd → <b>~/" + (route.label === "home" ? "" : route.label) + "</b>"); 411 navigate(route.url); return; 412 } 413 414 flash("command not found: <b>" + esc(cmd) + "</b> â try 'help'", true); 415 } 416 417 function updateIdle() { 418 bar.classList.toggle("idle", document.activeElement !== input && !input.value); 419 } 420 421 input.addEventListener("focus", function () { updateIdle(); hidePop(); }); 422 input.addEventListener("blur", updateIdle); 423 input.addEventListener("input", updateIdle); 424 input.addEventListener("keydown", function (e) { 425 if (e.key === "Enter") { e.preventDefault(); run(input.value); } 426 else if (e.key === "Escape") { hidePop(); input.blur(); } 427 else if (e.key === "ArrowUp") { 428 if (history.length) { e.preventDefault(); hIndex = Math.max(0, hIndex - 1); input.value = history[hIndex] || ""; updateIdle(); } 429 } else if (e.key === "ArrowDown") { 430 if (history.length) { 431 e.preventDefault(); hIndex = Math.min(history.length, hIndex + 1); 432 input.value = history[hIndex] || ""; updateIdle(); 433 } 434 } 435 }); 436 437 document.addEventListener("keydown", function (e) { 438 var t = e.target; 439 var typing = t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable); 440 if (e.key === "/" && !typing) { e.preventDefault(); input.focus(); } 441 else if (e.key === "Escape" && document.activeElement === input) { hidePop(); input.blur(); } 442 }); 443 444 updateIdle(); 445 } 446 447 /* ---------- Scroll reveals ---------- */ 448 function initReveals() { 449 var els = document.querySelectorAll(".service, .reveal"); 450 if (!els.length) return; 451 if (reduce || !("IntersectionObserver" in window)) { 452 els.forEach(function (el) { el.classList.add("in"); }); 453 return; 454 } 455 var io = new IntersectionObserver(function (entries) { 456 entries.forEach(function (e) { 457 if (e.isIntersecting) { 458 var i = parseInt(e.target.style.getPropertyValue("--i"), 10) || 0; 459 e.target.style.animationDelay = (i * 0.08) + "s"; 460 e.target.classList.add("in"); 461 io.unobserve(e.target); 462 } 463 }); 464 }, { threshold: 0, rootMargin: "0px 0px -12% 0px" }); // threshold 0 so elements taller than the viewport still reveal 465 els.forEach(function (el) { io.observe(el); }); 466 } 467 468 /* ---------- Homepage boot sequence ---------- */ 469 function typeStatus() { 470 var el = document.getElementById("sbStatus"); 471 if (!el || reduce) return; 472 var rest = "online", i = 0; 473 el.textContent = "status: "; 474 (function tick() { 475 if (i <= rest.length) { el.textContent = "status: " + rest.slice(0, i); i++; setTimeout(tick, 55); } 476 })(); 477 } 478 function typeRole() { 479 var el = document.querySelector(".role"); 480 if (!el) return; 481 var text = el.getAttribute("data-type") || el.textContent.trim(); 482 if (reduce) { el.style.opacity = 1; return; } 483 el.textContent = ""; 484 var caret = document.createElement("span"); 485 caret.className = "typed-caret"; 486 el.appendChild(caret); 487 var i = 0; 488 (function tick() { 489 if (i < text.length) { 490 caret.insertAdjacentText("beforebegin", text.charAt(i)); 491 i++; 492 setTimeout(tick, 22 + Math.random() * 26); 493 } else { 494 setTimeout(function () { if (caret.parentNode) caret.parentNode.removeChild(caret); }, 1400); 495 } 496 })(); 497 } 498 function counts() { 499 document.querySelectorAll(".metric__num").forEach(function (el) { 500 var target = parseInt(el.getAttribute("data-count"), 10); 501 var vEl = el.querySelector(".v"); 502 if (reduce || isNaN(target)) { if (vEl) vEl.textContent = target; return; } 503 var start = null, dur = 900; 504 function step(ts) { 505 if (!start) start = ts; 506 var p = Math.min((ts - start) / dur, 1); 507 var eased = 1 - Math.pow(1 - p, 3); 508 vEl.textContent = Math.round(eased * target); 509 if (p < 1) requestAnimationFrame(step); 510 else vEl.textContent = target; 511 } 512 requestAnimationFrame(step); 513 }); 514 } 515 function initHomeBoot() { 516 var hero = document.getElementById("hero"); 517 if (!hero) return; 518 window.addEventListener("load", function () { 519 typeStatus(); 520 hero.classList.add("in"); 521 if (reduce) { typeRole(); counts(); return; } 522 setTimeout(typeRole, 950); 523 setTimeout(counts, 1150); 524 }); 525 } 526 527 /* ---------- DevTools easter egg ---------- */ 528 function consoleGreeting() { 529 try { 530 console.log("%c [email protected] ", 531 "background:#3FB950;color:#0A0C10;font-weight:700;padding:2px 6px;border-radius:4px"); 532 console.log("%cpsst â this site has a shell. press \"/\" then try: %chelp%c · %cneofetch%c · %ctop%c · %croll", 533 "color:#8B95A5", "color:#3FB950", "color:#8B95A5", 534 "color:#3FB950", "color:#8B95A5", "color:#3FB950", "color:#8B95A5", "color:#D29922"); 535 } catch (e) { /* ignore */ } 536 } 537 538 /* ---------- Wire up ---------- */
539 function init() { 540 initAnalytics(); 541 updateToggle(currentTheme()); 542 injectThemeControls(); 543 injectNav(); 544 var btn = document.getElementById("themeToggle"); 545 if (btn) btn.addEventListener("click", toggleTheme); 546 547 // Follow OS theme changes only while the user hasn't chosen explicitly. 548 if (window.matchMedia) { 549 try { 550 var mq = window.matchMedia("(prefers-color-scheme: light)"); 551 var onChange = function (e) { if (!storeGet("theme")) applyTheme(e.matches ? "light" : "dark"); }; 552 if (mq.addEventListener) mq.addEventListener("change", onChange); 553 else if (mq.addListener) mq.addListener(onChange); 554 } catch (e) { /* ignore */ } 555 } 556 557 initCommandBar(); 558 initReveals(); 559 initHomeBoot(); 560 fetchNpm(); 561 fetchGitHub(); 562 consoleGreeting(); 563 } 564 565 if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); 566 else init(); 567})();
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.