clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/184-task-notice-migration
lofi / website / docusaurus.config.ts
docusaurus.config.ts 11.4K
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 // Decision records are date-ordered with numeric prefixes; keep the187 // prefix in the document id so ids match filenames everywhere the188 // manifest is consumed (sidebar, llms corpus, links).189 numberPrefixParser: false,190 // `exclude` replaces the plugin defaults, so the `_`-prefix globs191 // must be restated alongside the repo-only content.192 exclude: [193 "spikes/**",194 "devx-contract.md",195 "seed.md",196 "assets/**",197 // Served under /node/docs by the "node" plugin instance instead.198 "node/**",199 "**/_*.{js,jsx,ts,tsx,md,mdx}",200 "**/_*/**",201 ],202 editUrl: "https://github.com/FelineStateMachine/lofi/edit/main/docs/",203 },204 blog: false,205 theme: {206 customCss: "./src/css/custom.css",207 },208 } satisfies Preset.Options,209 ],210 ],211212 themeConfig: {213 image: "img/lofi-social-card.png",214 mermaid: {215 // one custom brand palette for both color modes: filled shapes carry216 // their own text color (mint/lime with forest ink), and floating text217 // and lines use a mid sage that stays readable on light and dark pages218 theme: { light: "base", dark: "base" },219 options: {220 themeVariables: {221 fontFamily: '"Karla", system-ui, -apple-system, sans-serif',222 primaryColor: "#99e5bd",223 primaryTextColor: "#18231f",224 primaryBorderColor: "#39725c",225 secondaryColor: "#d5f26a",226 secondaryTextColor: "#18231f",227 secondaryBorderColor: "#39725c",228 tertiaryColor: "#c9eeda",229 tertiaryTextColor: "#18231f",230 tertiaryBorderColor: "#39725c",231 lineColor: "#6f9d88",232 // floating labels render on the mint edge-label chip, so they carry233 // forest ink; standalone titles use the mid sage via titleColor234 textColor: "#18231f",235 clusterBkg: "rgba(153, 229, 189, 0.16)",236 clusterBorder: "#39725c",237 titleColor: "#6f9d88",238 clusterTextColor: "#6f9d88",239 edgeLabelBackground: "#99e5bd",240 actorBkg: "#99e5bd",241 actorTextColor: "#18231f",242 actorBorder: "#39725c",243 actorLineColor: "#6f9d88",244 signalColor: "#6f9d88",245 signalTextColor: "#6f9d88",246 labelBoxBkgColor: "#d5f26a",247 labelBoxBorderColor: "#39725c",248 labelTextColor: "#18231f",249 loopTextColor: "#6f9d88",250 activationBkgColor: "#d5f26a",251 activationBorderColor: "#39725c",252 noteBkgColor: "#d5f26a",253 noteTextColor: "#18231f",254 noteBorderColor: "#39725c",255 },256 },257 },258 colorMode: {259 respectPrefersColorScheme: true,260 },261 navbar: {262 // Two trees, no wordmark: the site fronts two products, and each263 // product's own dropdown carries its links (so no top-level GitHub/JSR).264 // The demo stands beside them as a third top-level entry.265 logo: {266 alt: "lofi",267 src: "img/two-trees.svg",268 // square intrinsic dimensions at the navbar's 1.7rem height so the269 // image never contributes layout shift270 width: 30,271 height: 30,272 },273 items: [274 {275 type: "dropdown",276 label: "lofi",277 position: "left",278 to: "/",279 items: [280 { label: "docs", to: "/docs" },281 { label: "api", to: "/api" },282 {283 label: "github",284 href: "https://github.com/FelineStateMachine/lofi",285 },286 { label: "jsr", href: "https://jsr.io/@nzip/lofi" },287 ],288 },289 {290 type: "dropdown",291 label: "lofi-node",292 position: "left",293 to: "/node",294 items: [295 { label: "docs", to: "/node/docs" },296 { label: "api", to: "/node/api" },297 {298 label: "github",299 href: "https://github.com/FelineStateMachine/lofi-node",300 },301 { label: "jsr", href: "https://jsr.io/@nzip/lofi-node" },302 ],303 },304 {305 label: "demo",306 position: "left",307 href: "https://demo.lofi.host",308 // Wears the demo's magma styling (custom.css) instead of the309 // navbar link treatment.310 className: "navbar-demo-link",311 },312 ],313 },314 footer: {315 style: "dark",316 // One column per product, parallel structure, lowercase like the nav.317 links: [318 {319 title: "lofi",320 items: [321 { label: "docs", to: "/docs" },322 { label: "api", to: "/api" },323 { label: "demo", href: "https://demo.lofi.host" },324 {325 label: "github",326 href: "https://github.com/FelineStateMachine/lofi",327 },328 { label: "jsr", href: "https://jsr.io/@nzip/lofi" },329 { label: "llms.txt", to: "/llms.txt", target: "_blank" },330 ],331 },332 {333 title: "lofi-node",334 items: [335 { label: "docs", to: "/node/docs" },336 { label: "api", to: "/node/api" },337 {338 label: "github",339 href: "https://github.com/FelineStateMachine/lofi-node",340 },341 { label: "jsr", href: "https://jsr.io/@nzip/lofi-node" },342 { label: "llms.txt", to: "/node/llms.txt", target: "_blank" },343 ],344 },345 ],346 copyright: `MIT licensed. Built with Docusaurus.`,347 },348 prism: {349 // gruvbox material is the standard theme closest to the brand palette:350 // olive/lime greens and mint aquas on warm dark and cream backgrounds351 theme: prismThemes.gruvboxMaterialLight,352 darkTheme: prismThemes.gruvboxMaterialDark,353 additionalLanguages: ["bash", "json", "diff"],354 },355 } satisfies Preset.ThemeConfig,356};357358export default config;