diff --git a/docs/components.md b/docs/components.md
index c84829b..4e118ef 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -76,6 +76,6 @@ import PageShell from "@gk/ui/components/PageShell.astro";
```
-The site is dark by default and turns light when the device is set to light (`prefers-color-scheme`). Colors come from the tokens in `tokens.css`, which has the light values too, so a component that uses `var(--gk-…)` needs nothing more. For a translucent color use a channel token, as in `rgba(var(--gk-gold-rgb), 0.35)`, never a hardcoded rgba. In SVG markup use `style="fill:var(--gk-gold)"`.
+The site follows the device's light or dark setting until the visitor presses the sun and moon toggle in the header (`ThemeToggle`), which saves their choice in the browser. A script in PageShell's head sets `` or `"dark"` before the page paints; without script the site stays dark. Style light mode with `:root[data-theme="light"]` (in a component, `:global(:root[data-theme="light"]) .x`), not a `prefers-color-scheme` media query, so the toggle can override it. Colors come from the tokens in `tokens.css`, which has the light values too, so a component that uses `var(--gk-…)` needs nothing more. For a translucent color use a channel token, as in `rgba(var(--gk-gold-rgb), 0.35)`, never a hardcoded rgba. In SVG markup use `style="fill:var(--gk-gold)"`.
-Motif images come in a light recolor and, for seals, a gold foil version for each mode. Show a motif with `Motif` (or `Seal`), which picks the right file for the device; a plain `` stays dark. Pass `foil` only to a seal standing alone at 120px or more and half opacity or more. After changing a motif SVG, run `node packages/ui/scripts/light-motifs.mjs` to rewrite the `light/`, `foil/` and `foil-light/` versions.
+Motif images come in a light recolor and, for seals, a gold foil version for each mode. Show a motif with `Motif` (or `Seal`), which renders both files and shows the one for the current mode; a plain `` stays dark. Pass `foil` only to a seal standing alone at 120px or more and half opacity or more. After changing a motif SVG, run `node packages/ui/scripts/light-motifs.mjs` to rewrite the `light/`, `foil/` and `foil-light/` versions.
diff --git a/packages/ui/src/assets/icons/moon.svg b/packages/ui/src/assets/icons/moon.svg
new file mode 100644
index 0000000..09a915e
--- /dev/null
+++ b/packages/ui/src/assets/icons/moon.svg
@@ -0,0 +1 @@
+
diff --git a/packages/ui/src/assets/icons/sun.svg b/packages/ui/src/assets/icons/sun.svg
new file mode 100644
index 0000000..a62d8a5
--- /dev/null
+++ b/packages/ui/src/assets/icons/sun.svg
@@ -0,0 +1 @@
+
diff --git a/packages/ui/src/components/Motif.astro b/packages/ui/src/components/Motif.astro
index 470933c..c9ccba0 100644
--- a/packages/ui/src/components/Motif.astro
+++ b/packages/ui/src/components/Motif.astro
@@ -1,7 +1,9 @@
---
/**
- * A motif image that follows the device's light or dark setting: the dark
- * original, or its light recolor. Pass `foil` for a large standalone seal.
+ * A motif image that follows the site's light or dark mode: the dark
+ * original, or its light recolor. Both are in the page and CSS shows one
+ * (base.css); the hidden one is lazy, so it never loads. Pass `foil` for a
+ * large standalone seal.
*/
import type { HTMLAttributes } from "astro/types";
import { motifSources } from "./seals.ts";
@@ -10,7 +12,16 @@ interface Props extends Omit, "src"> {
src: string;
foil?: boolean;
}
-const { src, foil, ...rest } = Astro.props;
+const { src, foil, class: className, loading = "lazy", ...rest } = Astro.props;
const v = motifSources(src, { foil });
---
-
+{
+ v.light === v.dark ? (
+
+ ) : (
+ <>
+
+
+ >
+ )
+}
diff --git a/packages/ui/src/components/PageShell.astro b/packages/ui/src/components/PageShell.astro
index 692ccb4..b443f13 100644
--- a/packages/ui/src/components/PageShell.astro
+++ b/packages/ui/src/components/PageShell.astro
@@ -36,8 +36,31 @@ const fullTitle = title === "Greg Kilmer" ? title : `${title} · Greg Kilmer`;
-
-
+
+
+
diff --git a/packages/ui/src/components/SiteHeader.astro b/packages/ui/src/components/SiteHeader.astro
index b68df70..dd62fdf 100644
--- a/packages/ui/src/components/SiteHeader.astro
+++ b/packages/ui/src/components/SiteHeader.astro
@@ -10,6 +10,7 @@ import monogram from "../assets/brand/gk-monogram.svg?url";
import Divider from "./Divider.astro";
import Motif from "./Motif.astro";
import Star from "./Star.astro";
+import ThemeToggle from "./ThemeToggle.astro";
import { main, tail, world, type NavKey, type StoreNav } from "./nav.ts";
import { filamentBanner } from "./seals.ts";
@@ -43,10 +44,11 @@ const decs = [60, 30, 0, -30].map((d, i) => ({ at: (i / 4) * 100, label: `${d >
a Patiun LLC workshop
-