diff --git a/web/packages/ui/package.json b/web/packages/ui/package.json
index 59f1f0148e..c2887d3360 100644
--- a/web/packages/ui/package.json
+++ b/web/packages/ui/package.json
@@ -7,8 +7,10 @@
"exports": {
"./components/Header.astro": "./src/components/Header.astro",
"./components/Footer.astro": "./src/components/Footer.astro",
+ "./components/FontPreload.astro": "./src/components/FontPreload.astro",
"./components/Logo.astro": "./src/components/Logo.astro",
"./components/RssPill.astro": "./src/components/RssPill.astro",
+ "./components/starlight/Head.astro": "./src/components/starlight/Head.astro",
"./components/starlight/Header.astro": "./src/components/starlight/Header.astro",
"./components/starlight/Footer.astro": "./src/components/starlight/Footer.astro",
"./components/starlight/SocialIcons.astro": "./src/components/starlight/SocialIcons.astro",
diff --git a/web/packages/ui/src/assets/fonts/inter-latin-wght-italic.woff2 b/web/packages/ui/src/assets/fonts/inter-latin-wght-italic.woff2
new file mode 100644
index 0000000000..2f06c65192
Binary files /dev/null and b/web/packages/ui/src/assets/fonts/inter-latin-wght-italic.woff2 differ
diff --git a/web/packages/ui/src/components/FontPreload.astro b/web/packages/ui/src/components/FontPreload.astro
new file mode 100644
index 0000000000..8fbc911a8a
--- /dev/null
+++ b/web/packages/ui/src/components/FontPreload.astro
@@ -0,0 +1,11 @@
+---
+// Preload the two brand fonts every page renders text in, so the browser
+// fetches them with the HTML instead of after the stylesheet. `?url` gives the
+// same hashed, base-prefixed file the @font-face rules in fonts.css point at.
+// The italic face is left to load on demand.
+import interUrl from '../assets/fonts/inter-latin-wght-normal.woff2?url';
+import monoUrl from '../assets/fonts/jetbrains-mono-latin-wght-normal.woff2?url';
+---
+
+
+
diff --git a/web/packages/ui/src/components/starlight/Head.astro b/web/packages/ui/src/components/starlight/Head.astro
new file mode 100644
index 0000000000..97967a784f
--- /dev/null
+++ b/web/packages/ui/src/components/starlight/Head.astro
@@ -0,0 +1,14 @@
+---
+// Mirrors @astrojs/starlight 0.39.2 Head.astro; re-sync on every Starlight upgrade.
+//
+// Starlight's head tags (what its own Head component renders: route data from
+// Astro.locals.starlightRoute.head), plus the brand-font preloads. Rendered
+// directly rather than wrapping Starlight's Head, which this package can't
+// import without depending on @astrojs/starlight.
+import FontPreload from '../FontPreload.astro';
+
+const { head } = Astro.locals.starlightRoute;
+---
+
+{head.map(({ tag: Tag, attrs, content }) => )}
+
diff --git a/web/packages/ui/src/styles/fonts.css b/web/packages/ui/src/styles/fonts.css
index 43a4da6590..d9413f0e5d 100644
--- a/web/packages/ui/src/styles/fonts.css
+++ b/web/packages/ui/src/styles/fonts.css
@@ -1,6 +1,6 @@
/*
* Self-hosted brand fonts: Inter and JetBrains Mono, the faces --font-sans and
- * --font-mono in tokens.css name. Latin subset only, as variable fonts
+ * --font-mono in tokens.css name (Inter in normal and italic). Latin subset only, as variable fonts
* (weights 100-900), served from this package so no page requests a font from
* a third party. font-display: swap keeps text visible while they load.
* Files and their SIL Open Font License texts: ../assets/fonts/.
@@ -16,6 +16,17 @@
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
+@font-face {
+ font-family: 'Inter';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 100 900;
+ src: url('../assets/fonts/inter-latin-wght-italic.woff2') format('woff2');
+ unicode-range:
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
+ U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
diff --git a/web/sites/api/astro.config.mjs b/web/sites/api/astro.config.mjs
index f998b929f3..a2bb929b2f 100644
--- a/web/sites/api/astro.config.mjs
+++ b/web/sites/api/astro.config.mjs
@@ -43,6 +43,7 @@ export default defineConfig({
'@wheels-dev/ui/styles/starlight-theme.css',
],
components: {
+ Head: '@wheels-dev/ui/components/starlight/Head.astro',
Header: '@wheels-dev/ui/components/starlight/Header.astro',
Footer: '@wheels-dev/ui/components/starlight/Footer.astro',
SocialIcons: '@wheels-dev/ui/components/starlight/SocialIcons.astro',
diff --git a/web/sites/blog/src/layouts/BaseLayout.astro b/web/sites/blog/src/layouts/BaseLayout.astro
index fbdd2fed11..60ae0b7068 100644
--- a/web/sites/blog/src/layouts/BaseLayout.astro
+++ b/web/sites/blog/src/layouts/BaseLayout.astro
@@ -1,6 +1,7 @@
---
import Header from '@wheels-dev/ui/components/Header.astro';
import Footer from '@wheels-dev/ui/components/Footer.astro';
+import FontPreload from '@wheels-dev/ui/components/FontPreload.astro';
import NewsletterSignup from '../components/NewsletterSignup.astro';
import '@wheels-dev/ui/styles/tokens.css';
import '@wheels-dev/ui/styles/base.css';
@@ -18,6 +19,7 @@ const { title, description = 'News, tutorials, and release announcements for Whe