Skip to content

Commit 565809e

Browse files
committed
chore: initial website stopping point
1 parent b3a1312 commit 565809e

9 files changed

Lines changed: 463 additions & 68 deletions

File tree

.mcp.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
"mcpServers": {
33
"chrome-devtools": {
44
"command": "npx",
5-
"args": ["-y", "chrome-devtools-mcp@latest"]
5+
"args": [
6+
"-y",
7+
"chrome-devtools-mcp@latest",
8+
"--executablePath",
9+
"/usr/bin/chromium"
10+
]
611
}
712
}
813
}

src/website/astro.config.mjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// @ts-check
2+
import { readFileSync } from "node:fs";
3+
import { fileURLToPath } from "node:url";
24
import { defineConfig } from "astro/config";
35
import starlight from "@astrojs/starlight";
46

@@ -12,6 +14,17 @@ import starlight from "@astrojs/starlight";
1214
// lives nested at src/content/docs/docs/*. That subtree is populated at build
1315
// time by `scripts/ingest-docs.mjs`, which ingests the canonical `docs/`
1416
// directory at the repo root. See scripts/ingest-docs.mjs for the contract.
17+
18+
// Custom Shiki theme aligned to the Notebook tokenizer used by the marketing
19+
// homepage's CodeBlock component. Loaded as a JSON file so it can be edited
20+
// without touching this config.
21+
const notebookTheme = JSON.parse(
22+
readFileSync(
23+
fileURLToPath(new URL("./src/styles/shiki-notebook.json", import.meta.url)),
24+
"utf8",
25+
),
26+
);
27+
1528
export default defineConfig({
1629
site: "https://chaoticgoodcomputing.github.io",
1730
base: "/flowthru",
@@ -29,6 +42,34 @@ export default defineConfig({
2942
github: "https://github.com/chaoticgoodcomputing/flowthru",
3043
},
3144
customCss: ["./src/styles/flowthru.css"],
45+
// Starlight renders fenced code blocks via expressive-code (a Shiki
46+
// wrapper). Configure its theme here, not via markdown.shikiConfig —
47+
// expressive-code ignores the latter. Single theme = light-only,
48+
// matching the site's design direction.
49+
expressiveCode: {
50+
themes: [notebookTheme],
51+
useDarkModeMediaQuery: false,
52+
// The Notebook palette was hand-tuned against the paper background
53+
// already; let it through unmodified instead of having expressive-
54+
// code's contrast adjuster nudge our purples and ambers around.
55+
minSyntaxHighlightingColorContrast: 0,
56+
styleOverrides: {
57+
frames: {
58+
frameBoxShadowCssValue: "none",
59+
},
60+
},
61+
},
62+
// Site is light-only.
63+
// ThemeProvider — replaced with a version that hard-pins
64+
// `data-theme="light"` instead of consulting prefers-color-scheme.
65+
// Without this, users on dark-mode OSes see Starlight's dark theme.
66+
// ThemeSelect — replaced with a no-op so users aren't presented with
67+
// a control that does nothing useful.
68+
components: {
69+
ThemeProvider:
70+
"./src/components/starlight/LightThemeProvider.astro",
71+
ThemeSelect: "./src/components/starlight/EmptyThemeSelect.astro",
72+
},
3273
sidebar: [
3374
{
3475
label: "Tutorials",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
// Empty replacement for Starlight's <ThemeSelect />. Wired in astro.config.mjs
3+
// via the starlight() integration's `components` override, which renders this
4+
// in place of the built-in toggle. The site is light-only for now; a
5+
// coordinated dark variant is planned for a future pass that retheme both
6+
// the marketing surface and the docs together.
7+
---
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
// Replacement for Starlight's <ThemeProvider />.
3+
//
4+
// Starlight's default ThemeProvider runs a synchronous inline script that
5+
// sets `data-theme="dark"` when the user's OS prefers a dark color scheme.
6+
// The site is light-only for now (a coordinated dark variant is planned for
7+
// a future pass that retheme both the marketing surface and the docs
8+
// together), so we hard-pin `data-theme="light"` regardless of OS prefs.
9+
//
10+
// The script is inlined and `is:inline` to avoid a flash of mis-themed
11+
// content (FOUC) — same pattern Starlight uses.
12+
---
13+
14+
<script is:inline>
15+
document.documentElement.dataset.theme = "light";
16+
// Starlight's components reference window.StarlightThemeProvider; provide
17+
// a stub so anything calling .updatePickers() doesn't blow up. The actual
18+
// theme picker is replaced by EmptyThemeSelect.astro.
19+
window.StarlightThemeProvider = { updatePickers() {} };
20+
</script>

src/website/src/middleware.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Astro middleware — server-side response transform.
2+
//
3+
// Starlight's Page.astro hardcodes `<html data-theme="dark">` as the static
4+
// default attribute (it expects a client-side script to flip it to "light"
5+
// when the user prefers light mode). Since Flowthru's site is light-only
6+
// while we work out the base design language, we rewrite the attribute
7+
// before the response leaves the server.
8+
//
9+
// Doing this in middleware instead of via a client-side script means:
10+
// - the static HTML ships with `data-theme="light"` from the start,
11+
// so Starlight's `[data-theme="light"]` component rules apply
12+
// consistently from the first paint;
13+
// - there is no FOUC race between the inline theme script and the
14+
// cascade resolution;
15+
// - the `LightThemeProvider` stub (also in this project) is kept as a
16+
// belt-and-suspenders safety net for any client-side state mutation,
17+
// but is no longer load-bearing.
18+
//
19+
// When dark mode is eventually added (a coordinated v2 covering both the
20+
// marketing surface and the docs), this middleware comes out and the
21+
// runtime theme picker comes back.
22+
23+
import { defineMiddleware } from "astro:middleware";
24+
25+
const HTML_OPEN_TAG = /<html\b[^>]*>/i;
26+
const DARK_THEME_ATTR = /data-theme="dark"/g;
27+
28+
export const onRequest = defineMiddleware(async (_context, next) => {
29+
const response = await next();
30+
31+
const contentType = response.headers.get("content-type") ?? "";
32+
if (!contentType.includes("text/html")) {
33+
return response;
34+
}
35+
36+
const html = await response.text();
37+
const rewritten = html.replace(HTML_OPEN_TAG, (tag) =>
38+
tag.replace(DARK_THEME_ATTR, 'data-theme="light"'),
39+
);
40+
41+
// Preserve the original headers (status, content-type, etc.) but emit
42+
// the rewritten body. Drop content-length since the body length changes;
43+
// the server will recompute it.
44+
const headers = new Headers(response.headers);
45+
headers.delete("content-length");
46+
47+
return new Response(rewritten, {
48+
status: response.status,
49+
statusText: response.statusText,
50+
headers,
51+
});
52+
});

0 commit comments

Comments
 (0)