/* Single external script (no inline JS anywhere → strict script-src 'self'). Loaded blocking in so the theme is set before first paint (no flash). */ (function () { // --- Pre-paint theme ----------------------------------------------------- try { var stored = localStorage.getItem("theme"); var theme = stored || (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"); document.documentElement.dataset.theme = theme; } catch (e) { document.documentElement.dataset.theme = "dark"; } function onReady(fn) { if (document.readyState !== "loading") fn(); else document.addEventListener("DOMContentLoaded", fn); } onReady(function () { // --- Theme toggle ------------------------------------------------------ var btn = document.getElementById("theme-toggle"); if (btn) { btn.addEventListener("click", function () { var root = document.documentElement; var next = root.dataset.theme === "light" ? "dark" : "light"; root.dataset.theme = next; try { localStorage.setItem("theme", next); } catch (e) { /* ignore */ } }); } // --- Reveal on scroll (progressive enhancement, motion-aware) ---------- var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; var targets = document.querySelectorAll("[data-reveal]"); if (!reduce && "IntersectionObserver" in window) { targets.forEach(function (el) { el.classList.add("reveal"); }); var io = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); io.unobserve(entry.target); } }); }, { rootMargin: "0px 0px -10% 0px", threshold: 0.1 }, ); targets.forEach(function (el) { io.observe(el); }); } }); })();