/* 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"; } // Mark JS as live BEFORE first paint. CSS only hides the pre-reveal animation // state under `html.js`, so with JS off every element stays visible (the // Motion layer in /_astro reveals them once it runs). Progressive enhancement. document.documentElement.classList.add("js"); 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 */ } }); } // --- Hero typing loop (fleet facts) ------------------------------------- // Facts arrive via a data attribute (no inline JS → script-src 'self' holds). // Reduced motion or JS-off: the first fact renders statically from markup. var factBox = document.getElementById("fleet-fact"); var factText = document.getElementById("fleet-fact-text"); var reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (factBox && factText && !reduced) { var facts; try { facts = JSON.parse(factBox.dataset.facts || "[]"); } catch (e) { facts = []; } if (facts.length > 1) { var fi = 0; // current fact var ci = facts[0].length; // start fully typed (matches SSR markup) var deleting = false; var tick = function () { var fact = facts[fi]; if (!deleting && ci === fact.length) { // hold at end of fact, then start the next deleting = true; setTimeout(tick, 3200); return; } if (deleting) { // swap instantly to the next fact (clean cut, like a terminal) deleting = false; fi = (fi + 1) % facts.length; ci = 0; factText.textContent = ""; setTimeout(tick, 260); return; } ci++; factText.textContent = facts[fi].slice(0, ci); setTimeout(tick, 45); }; setTimeout(tick, 3200); } } // Scroll reveals, hero entrance, backdrop parallax, scroll-progress bar, // nav underline and button springs all live in the Motion layer // (src/scripts/anim.ts → bundled /_astro/*.js). This file owns only the // pre-paint theme + the typing loop so it stays a tiny blocking script. }); })();