/** * Pre-paint inline scripts shared by the document layouts (`RootLayout`, * `PageLayout`, `ReferenceLayout`). They run synchronously — in ``, or * immediately after the markup they act on — before that content paints, so the * page never flashes the wrong theme, a since-dismissed banner, or a sidebar * scrolled away from the current page. Kept in one place so the layouts can't * drift on this timing-critical logic. * * Under the client router (``), each script's element reappears * on every navigated-to page but is executed only once per real page load — * Astro skips scripts whose content it has already run. Anything that must hold * per navigation therefore also registers an `astro:after-swap` listener on the * first (and only) execution: the swap replaces the `` attributes and the * body wholesale, wiping `data-theme`/`data-blume-banner-hidden` and rebuilding * the sidebar, and `after-swap` fires before the new page paints — the same * no-flash timing the initial inline run has. * * All are constants, never built by interpolating config into source text: any * values they need ride in as `data-*` attributes on the script tag and are read * back through `document.currentScript`. Baking a config string into JS — even * via `JSON.stringify` — is code construction, and JSON escaping does not cover * a script context (``, U+2028/U+2029 all survive it). Attributes are * HTML-escaped by Astro, so the value can never be parsed as code. */ /** * Set `data-theme` from the stored preference (or the configured default, or the * OS setting for `"system"`) before the body paints, avoiding a theme flash — * and again after every client-router swap, which resets `` attributes to * the incoming page's server-rendered (theme-less) set. * * Reads `data-mode` — `"system" | "light" | "dark"`. */ export const THEME_INIT_SCRIPT = `(()=>{const m=document.currentScript?.dataset.mode??"system";const apply=()=>{const s=localStorage.getItem("blume-theme");const sys=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.dataset.theme=s??(m==="system"?sys:m);};apply();document.addEventListener("astro:after-swap",apply);})();`; /** * Hide a previously-dismissed banner before it can flash in — and again after * every client-router swap, which wipes the `` marker attribute. * * Reads `data-key` — the banner's dismissal key. */ export const BANNER_INIT_SCRIPT = `(()=>{const k=document.currentScript?.dataset.key;if(!k){return;}const apply=()=>{if(localStorage.getItem("blume-banner:"+k)){document.documentElement.setAttribute("data-blume-banner-hidden","");}};apply();document.addEventListener("astro:after-swap",apply);})();`; /** * Keep the page styled across client-router swaps. Astro hoists the CSS of a * component rendered after the head has streamed (the page's MDX content, the * WebMcp island) into the **body** as `` tags — and the * client router only preloads and persists stylesheets it finds in the head. * A swapped-in body `` applies asynchronously, so every navigation to a * page with body CSS painted one or two completely unstyled frames (giant raw * SVG logo, default link colors) before the sheet kicked in — even when the * same sheet was already loaded on the outgoing page, because the swap throws * the old body (and its link element) away. * * Two listeners close the gap. `astro:before-preparation` wraps the router's * loader: after the next document is fetched, any of its body stylesheets not * already in the live head are appended there and awaited, so their rules * apply before the swap. `astro:before-swap` then moves the incoming * document's body stylesheets into its head, where the router's head diff * keeps the already-loaded copy (matched by `href`) instead of re-inserting a * fresh, not-yet-applied link — and drops it again on a later navigation to a * page that doesn't use it. A sheet that fails to load resolves rather than * wedging the navigation; the page renders as it would have without this. */ export const SWAP_STYLESHEET_INIT_SCRIPT = `(()=>{const sel='body link[rel="stylesheet"]';document.addEventListener("astro:before-preparation",(e)=>{const load=e.loader;e.loader=async()=>{await load();const links=[...e.newDocument.querySelectorAll(sel)].filter((l)=>!document.head.querySelector('link[rel="stylesheet"][href="'+l.getAttribute("href")+'"]'));await Promise.all(links.map((l)=>new Promise((done)=>{const c=document.createElement("link");for(const a of l.attributes){c.setAttribute(a.name,a.value);}c.onload=done;c.onerror=done;document.head.append(c);})));};});document.addEventListener("astro:before-swap",(e)=>{for(const l of e.newDocument.querySelectorAll(sel)){e.newDocument.head.append(l);}});})();`; /** * Keep the sidebar's scroll useful across page changes. The sidebar is its own * scroll container, reborn scrolled to the top whenever its markup is rebuilt — * on a long sidebar the viewport would visibly jump away from the link you just * clicked. * * On the initial load it centers the current page's link before the sidebar * paints (it runs inline immediately after the sidebar `