1import { readFileSync } from "node:fs";2import { fileURLToPath } from "node:url";3import { themes as prismThemes } from "prism-react-renderer";4import type { Config } from "@docusaurus/types";5import type * as Preset from "@docusaurus/preset-classic";67// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)89const denoJson = JSON.parse(10 readFileSync(new URL("../deno.json", import.meta.url), "utf8"),11) as { version: string; description: string };1213// The /node landing shows the lofi-node version from the same pinned checkout14// the docs render from (LOFI_NODE_DIR, default ../lofi-node); "alpha" when the15// checkout is absent so a docs-only environment still builds.16let lofiNodeVersion = "alpha";17try {18 const nodeDir = process.env.LOFI_NODE_DIR ?? "../lofi-node";19 lofiNodeVersion = (JSON.parse(20 readFileSync(new URL(`../${nodeDir}/deno.json`, import.meta.url), "utf8"),21 ) as { version: string }).version;22} catch {23 // Keep the fallback label.24}2526// Fonts must ship as files, not base64 in the render-blocking stylesheet —27// webpack's default inline threshold would bloat styles.css by hundreds of KB.28// Rewrites Docusaurus's own font rule in place: appending a second rule makes29// webpack run both, and the asset/resource pass emits the url-loader's JS30// module source as the .woff2 the CSS points at — a 127-byte stub, no font.31function noInlineFontsPlugin() {32 return {33 name: "lofi-no-inline-fonts",34 configureWebpack: (config: { module?: { rules?: unknown[] } }) => {35 for (const rule of config.module?.rules ?? []) {36 if (37 rule && typeof rule === "object" && "test" in rule &&38 rule.test instanceof RegExp && rule.test.test("x.woff2")39 ) {40 const fontRule = rule as Record<string, unknown>;41 delete fontRule.use;42 fontRule.type = "asset/resource";43 fontRule.generator = { filename: "assets/fonts/[name]-[hash][ext]" };44 }45 }46 return {};47 },48 };49}5051const config: Config = {52 title: "lofi",53 tagline: "Put down roots. Skip the spinner.",54 favicon: "img/favicon.svg",5556 future: {57 v4: true,58 },5960 url: "https://lofi.host",61 baseUrl: "/",62 trailingSlash: false,6364 onBrokenLinks: "throw",65 onBrokenAnchors: "warn",6667 customFields: {68 lofiVersion: denoJson.version,69 lofiDescription: denoJson.description,70 lofiNodeVersion,71 },7273 markdown: {74 // Parse .md files as CommonMark so existing docs need no MDX escaping;75 // only .mdx files opt into MDX.76 format: "detect",77 mermaid: true,78 hooks: {79 onBrokenMarkdownLinks: "throw",80 },81 },8283 themes: [84 "@docusaurus/theme-mermaid",85 [86 // fully client-side search over an index built at build time — no87 // external search service, which suits a local-first framework88 "@easyops-cn/docusaurus-search-local",89 {90 hashed: true,91 indexBlog: false,92 docsRouteBasePath: ["docs", "api", "node/docs", "node/api"],93 docsDir: ["../docs", "api-gen", "node-docs-gen", "node-api-gen"],94 },95 ],96 ],9798 i18n: {99 defaultLocale: "en",100 locales: ["en"],101 },102103 // Fonts are self-hosted via Fontsource imports in src/css/custom.css, so104 // the docs work offline with their real faces and no third-party requests.105106 plugins: [107 noInlineFontsPlugin,108 [109 // The docs for the offline-first framework install and read offline.110 // Precaching is opt-in (installed PWA / standalone / ?offlineMode) so111 // drive-by visitors are not handed the whole corpus.112 "@docusaurus/plugin-pwa",113 {114 // trailingSlash:false emits routes as <path>.html, which the default115 // worker never checks; see src/sw-custom.js.116 swCustom: fileURLToPath(new URL("./src/sw-custom.js", import.meta.url)),117 offlineModeActivationStrategies: [118 "appInstalled",119 "standalone",120 "queryString",121 ],122 pwaHead: [123 { tagName: "link", rel: "manifest", href: "/manifest.json" },124 { tagName: "meta", name: "theme-color", content: "#18231f" },125 {126 tagName: "meta",127 name: "apple-mobile-web-app-capable",128 content: "yes",129 },130 {131 tagName: "meta",132 name: "apple-mobile-web-app-status-bar-style",133 content: "black-translucent",134 },135 {136 tagName: "link",137 rel: "apple-touch-icon",138 href: "/img/pwa/icon-192.png",139 },140 ],141 },142 ],143 [144 "@docusaurus/plugin-content-docs",145 {146 id: "api",147 path: "api-gen",148 routeBasePath: "api",149 sidebarPath: "./sidebars-api.ts",150 editUrl: undefined,151 },152 ],153 [154 "@docusaurus/plugin-content-docs",155 {156 // Assembled by tools/node_docs.ts from the pinned lofi-node checkout157 // rendered out of the pinned lofi-node checkout; no edit URL because158 // pages have two possible homes — each page names its source.159 id: "node",160 path: "node-docs-gen",161 routeBasePath: "node/docs",162 sidebarPath: "./sidebars-node.ts",163 editUrl: undefined,164 },165 ],166 [167 "@docusaurus/plugin-content-docs",168 {169 id: "node-api",170 path: "node-api-gen",171 routeBasePath: "node/api",172 sidebarPath: "./sidebars-node-api.ts",173 editUrl: undefined,174 },175 ],176 ],177178 presets: [179 [180 "classic",181 {182 docs: {183 path: "../docs",184 routeBasePath: "docs",185 sidebarPath: "./sidebars.ts",186 // `exclude` replaces the plugin defaults, so the `_`-prefix globs187 // must be restated alongside the repo-only content.188 exclude: [189 "spikes/**",190 "devx-contract.md",191 "seed.md",192 "assets/**",193 // Served under /node/docs by the "node" plugin instance instead.194 "node/**",195 "**/_*.{js,jsx,ts,tsx,md,mdx}",196 "**/_*/**",197 ],198 editUrl: "https://github.com/FelineStateMachine/lofi/edit/main/docs/",199 },200 blog: false,201 theme: {202 customCss: "./src/css/custom.css",203 },204 } satisfies Preset.Options,205 ],206 ],207208 themeConfig: {209 image: "img/lofi-social-card.png",210 mermaid: {211 // one custom brand palette for both color modes: filled shapes carry212 // their own text color (mint/lime with forest ink), and floating text213 // and lines use a mid sage that stays readable on light and dark pages214 theme: { light: "base", dark: "base" },215 options: {216 themeVariables: {217 fontFamily: '"Karla", system-ui, -apple-system, sans-serif',218 primaryColor: "#99e5bd",219 primaryTextColor: "#18231f",220 primaryBorderColor: "#39725c",221 secondaryColor: "#d5f26a",222 secondaryTextColor: "#18231f",223 secondaryBorderColor: "#39725c",224 tertiaryColor: "#c9eeda",225 tertiaryTextColor: "#18231f",226 tertiaryBorderColor: "#39725c",227 lineColor: "#6f9d88",228 // floating labels render on the mint edge-label chip, so they carry229 // forest ink; standalone titles use the mid sage via titleColor230 textColor: "#18231f",231 clusterBkg: "rgba(153, 229, 189, 0.16)",232 clusterBorder: "#39725c",233 titleColor: "#6f9d88",234 clusterTextColor: "#6f9d88",235 edgeLabelBackground: "#99e5bd",236 actorBkg: "#99e5bd",237 actorTextColor: "#18231f",238 actorBorder: "#39725c",239 actorLineColor: "#6f9d88",240 signalColor: "#6f9d88",241 signalTextColor: "#6f9d88",242 labelBoxBkgColor: "#d5f26a",243 labelBoxBorderColor: "#39725c",244 labelTextColor: "#18231f",245 loopTextColor: "#6f9d88",246 activationBkgColor: "#d5f26a",247 activationBorderColor: "#39725c",248 noteBkgColor: "#d5f26a",249 noteTextColor: "#18231f",250 noteBorderColor: "#39725c",251 },252 },253 },254 colorMode: {255 respectPrefersColorScheme: true,256 },257 navbar: {258 // Two trees, no wordmark: the site now fronts two products, and each259 // product's own dropdown carries its links (so no top-level GitHub/JSR).260 logo: {261 alt: "lofi",262 src: "img/two-trees.svg",263 // square intrinsic dimensions at the navbar's 1.7rem height so the264 // image never contributes layout shift265 width: 30,266 height: 30,267 },268 items: [269 {270 type: "dropdown",271 label: "lofi",272 position: "left",273 to: "/",274 items: [275 { label: "docs", to: "/docs" },276 { label: "api", to: "/api" },277 { label: "github", href: "https://github.com/FelineStateMachine/lofi" },278 { label: "jsr", href: "https://jsr.io/@nzip/lofi" },279 ],280 },281 {282 type: "dropdown",283 label: "lofi-node",284 position: "left",285 to: "/node",286 items: [287 { label: "docs", to: "/node/docs" },288 { label: "api", to: "/node/api" },289 { label: "github", href: "https://github.com/FelineStateMachine/lofi-node" },290 { label: "jsr", href: "https://jsr.io/@nzip/lofi-node" },291 ],292 },293 ],294 },295 footer: {296 style: "dark",297 // One column per product, parallel structure, lowercase like the nav.298 links: [299 {300 title: "lofi",301 items: [302 { label: "docs", to: "/docs" },303 { label: "api", to: "/api" },304 { label: "github", href: "https://github.com/FelineStateMachine/lofi" },305 { label: "jsr", href: "https://jsr.io/@nzip/lofi" },306 { label: "llms.txt", to: "/llms.txt", target: "_blank" },307 ],308 },309 {310 title: "lofi-node",311 items: [312 { label: "docs", to: "/node/docs" },313 { label: "api", to: "/node/api" },314 { label: "github", href: "https://github.com/FelineStateMachine/lofi-node" },315 { label: "jsr", href: "https://jsr.io/@nzip/lofi-node" },316 { label: "llms.txt", to: "/node/llms.txt", target: "_blank" },317 ],318 },319 ],320 copyright: `MIT licensed. Built with Docusaurus.`,321 },322 prism: {323 // gruvbox material is the standard theme closest to the brand palette:324 // olive/lime greens and mint aquas on warm dark and cream backgrounds325 theme: prismThemes.gruvboxMaterialLight,326 darkTheme: prismThemes.gruvboxMaterialDark,327 additionalLanguages: ["bash", "json", "diff"],328 },329 } satisfies Preset.ThemeConfig,330};331332export default config;