@docubook/flame 2.0.0-beta.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.docu/components/Theme.tsx +42 -34
  2. package/.docu/components/home/BackgroundBlobs.tsx +31 -0
  3. package/.docu/components/home/index.tsx +1 -0
  4. package/.docu/lib/build.deno.js +1 -1
  5. package/.docu/lib/{build.impl-BBlR3si3.js → build.impl-CAQr352X.js} +60 -29
  6. package/.docu/lib/build.impl-CAQr352X.js.map +1 -0
  7. package/.docu/lib/build.impl-NLhjTqTa.js +2 -0
  8. package/.docu/lib/build.node.js +1 -1
  9. package/.docu/lib/clean.js +1 -1
  10. package/.docu/lib/deploy.deno.js +1 -1
  11. package/.docu/lib/deploy.node.js +1 -1
  12. package/.docu/lib/{deploy.shared-Dxv3lsOb.js → deploy.shared-CvgObu6_.js} +45 -17
  13. package/.docu/lib/deploy.shared-CvgObu6_.js.map +1 -0
  14. package/.docu/lib/{html.shared-DBMwv_Z6.js → html.shared-CsEFmy_Y.js} +181 -158
  15. package/.docu/lib/html.shared-CsEFmy_Y.js.map +1 -0
  16. package/.docu/lib/{logger-DYmvFF1d.js → logger-DwxYdOL7.js} +4 -3
  17. package/.docu/lib/logger-DwxYdOL7.js.map +1 -0
  18. package/.docu/lib/{paths-Bl2cdp9E.js → paths-Cbz-dj4s.js} +5 -9
  19. package/.docu/lib/{paths-Bl2cdp9E.js.map → paths-Cbz-dj4s.js.map} +1 -1
  20. package/.docu/lib/preview.deno.js +1 -1
  21. package/.docu/lib/{preview.impl-anVcZv2Z.js → preview.impl-BwrVLNIQ.js} +3 -3
  22. package/.docu/lib/{preview.impl-anVcZv2Z.js.map → preview.impl-BwrVLNIQ.js.map} +1 -1
  23. package/.docu/lib/preview.node.js +1 -1
  24. package/.docu/lib/server.deno.js +1 -1
  25. package/.docu/lib/{server.impl-Cvoa6Diq.js → server.impl-Cs6DEy4V.js} +43 -52
  26. package/.docu/lib/server.impl-Cs6DEy4V.js.map +1 -0
  27. package/.docu/lib/server.node.js +1 -1
  28. package/.docu/node/build.impl.ts +71 -34
  29. package/.docu/node/build.ts +68 -34
  30. package/.docu/node/cache-key.ts +8 -4
  31. package/.docu/node/client.ts +36 -27
  32. package/.docu/node/deploy.shared.ts +44 -18
  33. package/.docu/node/deploy.ts +5 -20
  34. package/.docu/node/home-client.ts +6 -0
  35. package/.docu/node/html.shared.ts +5 -4
  36. package/.docu/node/html.ts +4 -3
  37. package/.docu/node/hydrate.node.ts +87 -115
  38. package/.docu/node/hydrate.ts +74 -80
  39. package/.docu/node/mdx-manifest.d.ts +2 -2
  40. package/.docu/node/paths.ts +2 -9
  41. package/.docu/node/plugin-builder.ts +25 -13
  42. package/.docu/node/plugin.ts +33 -9
  43. package/.docu/node/server-routes.ts +59 -50
  44. package/.docu/node/server.impl.ts +4 -2
  45. package/.docu/node/server.ts +12 -16
  46. package/.docu/node/types.ts +11 -0
  47. package/.docu/pages/404.tsx +12 -9
  48. package/.docu/pages/index.tsx +8 -29
  49. package/.docu/styles/base.css +128 -0
  50. package/.docu/styles/globals.css +5 -137
  51. package/.docu/styles/site.css +7 -0
  52. package/package.json +6 -5
  53. package/.docu/lib/build.impl-BBlR3si3.js.map +0 -1
  54. package/.docu/lib/build.impl-DpoDUmei.js +0 -2
  55. package/.docu/lib/deploy.shared-Dxv3lsOb.js.map +0 -1
  56. package/.docu/lib/html.shared-DBMwv_Z6.js.map +0 -1
  57. package/.docu/lib/logger-DYmvFF1d.js.map +0 -1
  58. package/.docu/lib/server.impl-Cvoa6Diq.js.map +0 -1
@@ -1,5 +1,12 @@
1
1
  import type { Pluggable } from "unified";
2
- import type { DocuConfig, PageContext, PageMeta, DevServerContext, PluginBuilder } from "./plugin";
2
+ import type {
3
+ BuildEndContext,
4
+ DevServerContext,
5
+ DocuConfig,
6
+ PageContext,
7
+ PageMeta,
8
+ PluginBuilder,
9
+ } from "./plugin";
3
10
 
4
11
  type Awaitable<T> = T | Promise<T>;
5
12
 
@@ -20,7 +27,9 @@ export class BuildPluginBuilder implements PluginBuilder {
20
27
  > = [];
21
28
  private _injectBody: Array<(context: PageContext) => string | string[]> = [];
22
29
  private _injectHead: Array<(context: PageContext) => string | string[]> = [];
23
- private _onEnd: Array<(config: DocuConfig, pages: PageMeta[]) => Awaitable<void>> = [];
30
+ private _onEnd: Array<
31
+ (config: DocuConfig, pages: PageMeta[], context: BuildEndContext) => Awaitable<void>
32
+ > = [];
24
33
  private _onLoad: OnLoadHandler[] = [];
25
34
  private _onStart: Array<(config: DocuConfig) => Awaitable<void>> = [];
26
35
  private _rehypePlugins: Array<() => Pluggable[]> = [];
@@ -185,22 +194,24 @@ export class BuildPluginBuilder implements PluginBuilder {
185
194
 
186
195
  /**
187
196
  * Register a callback to run once after all pages are built.
188
- * Receives the resolved config and aggregated page metadata.
189
- * Errors thrown by the callback propagate to the caller via `runOnEnd()`.
197
+ * Receives the resolved config, aggregated docs metadata, and final build metadata.
198
+ * Callback errors are logged by `runOnEnd()` and do not stop later callbacks.
190
199
  *
191
- * @param callback - Receives config and page metadata array. May return a Promise.
200
+ * @param callback - Receives config, page metadata, and emitted assets. May return a Promise.
192
201
  *
193
202
  * @example
194
- * build.onEnd(async (config, pages) => {
203
+ * build.onEnd(async (config, pages, { outDir }) => {
195
204
  * const xml = generateSitemap(pages, config.meta.baseURL);
196
- * const out = ".docu/dist/sitemap.xml";
205
+ * const out = join(outDir, "sitemap.xml");
197
206
  * // Bun.write on Bun for speed, writeFile on Node/Deno
198
207
  * await (typeof Bun !== "undefined"
199
208
  * ? Bun.write(out, xml)
200
209
  * : writeFile(out, xml));
201
210
  * });
202
211
  */
203
- onEnd(callback: (config: DocuConfig, pages: PageMeta[]) => Awaitable<void>): void {
212
+ onEnd(
213
+ callback: (config: DocuConfig, pages: PageMeta[], context: BuildEndContext) => Awaitable<void>
214
+ ): void {
204
215
  this._onEnd.push(callback);
205
216
  }
206
217
 
@@ -208,7 +219,7 @@ export class BuildPluginBuilder implements PluginBuilder {
208
219
  * Register a callback to transform raw file content before MDX compilation.
209
220
  * Filtered by regex against the file's relative path — only the **first** matching
210
221
  * handler's result is used.
211
- * Errors thrown by the callback propagate to the caller via `runOnLoad()`.
222
+ * Callback errors are logged by `runOnLoad()` before trying later matching handlers.
212
223
  *
213
224
  * @param args.filter - RegExp matched against the file's relative path.
214
225
  * @param args.namespace - Optional namespace prefix (reserved for future use).
@@ -232,7 +243,7 @@ export class BuildPluginBuilder implements PluginBuilder {
232
243
  /**
233
244
  * Register a callback to run once before the build starts.
234
245
  * Receives the resolved DocuConfig for validation or resource initialization.
235
- * Errors thrown by the callback propagate to the caller via `runOnStart()`.
246
+ * Callback errors are logged by `runOnStart()` and do not stop later callbacks.
236
247
  *
237
248
  * @param callback - Receives the resolved config. May return a Promise.
238
249
  *
@@ -303,12 +314,13 @@ export class BuildPluginBuilder implements PluginBuilder {
303
314
  * Errors inside individual callbacks are caught and logged — execution
304
315
  * continues to the next callback without throwing.
305
316
  *
306
- * @param pages - Array of metadata for every built page.
317
+ * @param pages - Array of metadata for every built docs page.
318
+ * @param context - Final output directory and page-specific asset manifest.
307
319
  */
308
- async runOnEnd(pages: PageMeta[]): Promise<void> {
320
+ async runOnEnd(pages: PageMeta[], context: BuildEndContext): Promise<void> {
309
321
  for (let i = 0; i < this._onEnd.length; i++) {
310
322
  try {
311
- await this._onEnd[i](this.config, pages);
323
+ await this._onEnd[i](this.config, pages, context);
312
324
  } catch (err) {
313
325
  console.error(
314
326
  `[plugin] onEnd callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`
@@ -1,5 +1,5 @@
1
1
  import type { Pluggable } from "unified";
2
- import type { DocuConfig } from "./types";
2
+ import type { AssetEntry, AssetManifest, DocuConfig } from "./types";
3
3
 
4
4
  // ─── Config Types ───────────────────────────────────────
5
5
 
@@ -18,14 +18,20 @@ export type PluginEntry = string | [string, Record<string, unknown>];
18
18
  /**
19
19
  * Context passed to content-transform hooks for a single page.
20
20
  */
21
+ export type PageType = keyof AssetManifest;
22
+
21
23
  export interface PageContext {
24
+ /** Page family used to select the page-specific asset bundle. */
25
+ pageType: PageType;
26
+ /** Hashed CSS and optional JavaScript entry used by this page. */
27
+ assets: Readonly<AssetEntry>;
22
28
  /** Relative slug path: "getting-started/introduction" */
23
29
  slug: string;
24
- /** Absolute file path on disk */
30
+ /** Absolute or project-relative source file path on disk. */
25
31
  filePath: string;
26
- /** Parsed frontmatter metadata */
32
+ /** Parsed frontmatter metadata. Generated pages receive site metadata or an empty object. */
27
33
  frontmatter: Record<string, unknown>;
28
- /** Raw MDX/MD content (only available in `transformFrontmatter` when enabled) */
34
+ /** Raw MDX/MD content. Only docs pages provide content. */
29
35
  content?: string;
30
36
  /** Resolved site configuration */
31
37
  config: DocuConfig;
@@ -53,9 +59,21 @@ export interface DevServerContext {
53
59
  port: number;
54
60
  /** Dev server hostname */
55
61
  hostname: string;
62
+ /** Current page-specific asset manifest, refreshed after HMR rebuilds. */
63
+ assetManifest: Readonly<AssetManifest>;
56
64
  }
57
65
 
58
- export type { DocuConfig };
66
+ /**
67
+ * Final build metadata passed to `onEnd` after all HTML files are written.
68
+ */
69
+ export interface BuildEndContext {
70
+ /** Page-specific hashed assets emitted under `outDir/assets`. */
71
+ assetManifest: Readonly<AssetManifest>;
72
+ /** Absolute static output directory. */
73
+ outDir: string;
74
+ }
75
+
76
+ export type { AssetEntry, AssetManifest, DocuConfig };
59
77
 
60
78
  // ─── PluginBuilder Interface ────────────────────────────
61
79
 
@@ -95,19 +113,25 @@ export interface PluginBuilder {
95
113
  * Register a callback to run once after all pages are built.
96
114
  * Use for: generating sitemaps, RSS feeds, manifests, post-build reports.
97
115
  *
98
- * @param callback - Receives config and aggregated page metadata. May return a Promise.
116
+ * @param callback - Receives config, aggregated docs metadata, and final build metadata.
99
117
  *
100
118
  * @example
101
- * build.onEnd(async (config, pages) => {
119
+ * build.onEnd(async (config, pages, { outDir }) => {
102
120
  * const xml = generateSitemap(pages, config.meta.baseURL);
103
- * const out = join(DIST_DIR, "sitemap.xml");
121
+ * const out = join(outDir, "sitemap.xml");
104
122
  * // Bun.write on Bun for speed, writeFile on Node/Deno
105
123
  * await (typeof Bun !== "undefined"
106
124
  * ? Bun.write(out, xml)
107
125
  * : writeFile(out, xml));
108
126
  * });
109
127
  */
110
- onEnd(callback: (config: DocuConfig, pages: PageMeta[]) => void | Promise<void>): void;
128
+ onEnd(
129
+ callback: (
130
+ config: DocuConfig,
131
+ pages: PageMeta[],
132
+ context: BuildEndContext
133
+ ) => void | Promise<void>
134
+ ): void;
111
135
 
112
136
  // ─── Content Transform ─────────────────────────────────
113
137
 
@@ -5,10 +5,10 @@ import React, { type ReactNode } from "react";
5
5
  import { renderToString } from "react-dom/server";
6
6
  import { compileMdx, frontmatterField } from "./mdx";
7
7
  import { DEFAULT_FAVICON, getContentType } from "./utils";
8
- import { DOCS_DIR, DIST_DIR, PROJECT_ROOT } from "./paths";
8
+ import { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT } from "./paths";
9
9
  import { BuildPluginBuilder } from "./plugin-builder";
10
- import type { PageContext } from "./plugin";
11
- import type { DocuConfig, TocItem } from "./types";
10
+ import type { PageContext, PageType } from "./plugin";
11
+ import type { AssetEntry, AssetManifest, DocuConfig, TocItem } from "./types";
12
12
  import DocsPage from "../pages/docs/[[...slug]]";
13
13
  import NotFoundPage from "../pages/404";
14
14
  import IndexPage from "../pages/index";
@@ -18,37 +18,49 @@ import { htmlShell as createHtmlShell, hmrScript, errorHtml } from "./html.share
18
18
 
19
19
  export interface ServerState {
20
20
  docuConfig: DocuConfig;
21
- assetManifest: { css: string; js: string };
21
+ assetManifest: AssetManifest;
22
22
  inlineThemeCss?: string;
23
23
  builder: BuildPluginBuilder | null;
24
24
  }
25
25
 
26
- function createHtmlResponse(
26
+ async function createHtmlResponse(
27
27
  title: string,
28
28
  description: string,
29
29
  body: string,
30
30
  status: number,
31
31
  state: ServerState,
32
+ pageType: PageType,
33
+ page: Omit<PageContext, "pageType" | "assets" | "config">,
32
34
  depth = 0
33
- ): Response {
35
+ ): Promise<Response> {
34
36
  const nonce = generateNonce();
35
37
  const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;
36
- const html = createHtmlShell({
38
+ const assets: AssetEntry = state.assetManifest[pageType];
39
+ const context: PageContext = {
40
+ ...page,
41
+ pageType,
42
+ assets,
43
+ config: state.docuConfig,
44
+ };
45
+ let html = createHtmlShell({
37
46
  title,
38
47
  description,
39
48
  body,
40
49
  favicon,
41
- css: state.assetManifest.css,
42
- js: state.assetManifest.js,
50
+ css: assets.css,
51
+ js: assets.js,
43
52
  nonce,
44
53
  extraScripts: hmrScript(nonce),
45
54
  themeCss: state.inlineThemeCss,
55
+ headExtra: state.builder?.collectHead(context),
56
+ bodyExtra: state.builder?.collectBody(context),
46
57
  depth,
47
58
  // 404 pages can be requested at arbitrary depths (e.g. a noLink section
48
59
  // path typed in the address bar) — relative asset paths would resolve
49
60
  // against the wrong directory and break CSS/JS.
50
61
  absoluteAssets: status === 404,
51
62
  });
63
+ if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);
52
64
  /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */
53
65
  return htmlResponse(html, nonce, status, true);
54
66
  }
@@ -127,7 +139,7 @@ async function getDocsForSlug(
127
139
  let frontmatter = result.frontmatter as Record<string, unknown>;
128
140
  if (state.builder) {
129
141
  frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {
130
- slug: slug || "/",
142
+ slug,
131
143
  filePath: relPath,
132
144
  content,
133
145
  });
@@ -176,41 +188,24 @@ async function renderDocsServerPage(
176
188
  // Match build.ts depth calculation: slug.split("/").length, fallback to 1 for empty
177
189
  const depth = slug.length || 1;
178
190
 
179
- if (state.builder) {
180
- const ctx: PageContext = {
181
- slug: slug.join("/") || "/",
191
+ return createHtmlResponse(
192
+ title,
193
+ description,
194
+ body,
195
+ 200,
196
+ state,
197
+ "docs",
198
+ {
199
+ slug: slug.join("/"),
182
200
  filePath: doc.filePath,
183
201
  frontmatter: doc.frontmatter,
184
202
  content: doc.resolvedContent,
185
- config: state.docuConfig,
186
- };
187
- const headExtra = state.builder.collectHead(ctx);
188
- const bodyExtra = state.builder.collectBody(ctx);
189
- const nonce = generateNonce();
190
- const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;
191
- let html = createHtmlShell({
192
- title,
193
- description,
194
- body,
195
- favicon,
196
- css: state.assetManifest.css,
197
- js: state.assetManifest.js,
198
- nonce,
199
- extraScripts: hmrScript(nonce),
200
- themeCss: state.inlineThemeCss,
201
- headExtra,
202
- bodyExtra,
203
- depth,
204
- });
205
- html = await state.builder.runTransformHtmlChain(html, ctx);
206
- /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */
207
- return htmlResponse(html, nonce, 200, true);
208
- }
209
-
210
- return createHtmlResponse(title, description, body, 200, state, depth);
203
+ },
204
+ depth
205
+ );
211
206
  }
212
207
 
213
- function renderPage(
208
+ async function renderPage(
214
209
  Component: React.ComponentType<Record<string, unknown>>,
215
210
  title: string,
216
211
  description: string,
@@ -218,14 +213,22 @@ function renderPage(
218
213
  state: ServerState,
219
214
  props: Record<string, unknown> = {},
220
215
  depth = 0
221
- ): Response {
222
- const page = React.createElement(
223
- DocsLayout,
224
- { repoUrl: state.docuConfig.repo?.url, pathname: "/docs" },
225
- React.createElement(Component, props)
216
+ ): Promise<Response> {
217
+ const body = renderToString(React.createElement(Component, props));
218
+ return createHtmlResponse(
219
+ title,
220
+ description,
221
+ body,
222
+ status,
223
+ state,
224
+ "notFound",
225
+ {
226
+ slug: "404",
227
+ filePath: resolve(PAGES_DIR, "404.tsx"),
228
+ frontmatter: {},
229
+ },
230
+ depth
226
231
  );
227
- const body = renderToString(page);
228
- return createHtmlResponse(title, description, body, status, state, depth);
229
232
  }
230
233
 
231
234
  export async function handleDocsIndex(state: ServerState): Promise<Response> {
@@ -242,7 +245,7 @@ export async function handleDocsRoute(slug: string[], state: ServerState): Promi
242
245
  return renderDocsServerPage(doc, slug, `/docs/${path}`, state);
243
246
  }
244
247
 
245
- export function handleIndex(state: ServerState): Response {
248
+ export async function handleIndex(state: ServerState): Promise<Response> {
246
249
  const page = React.createElement(IndexPage);
247
250
  const body = renderToString(page);
248
251
  return createHtmlResponse(
@@ -250,11 +253,17 @@ export function handleIndex(state: ServerState): Response {
250
253
  state.docuConfig.meta?.description || "",
251
254
  body,
252
255
  200,
253
- state
256
+ state,
257
+ "home",
258
+ {
259
+ slug: "",
260
+ filePath: resolve(PAGES_DIR, "index.tsx"),
261
+ frontmatter: { ...state.docuConfig.meta } as Record<string, unknown>,
262
+ }
254
263
  );
255
264
  }
256
265
 
257
- export function handleNotFound(state: ServerState, depth = 0): Response {
266
+ export async function handleNotFound(state: ServerState, depth = 0): Promise<Response> {
258
267
  return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, depth);
259
268
  }
260
269
 
@@ -63,6 +63,7 @@ export async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle>
63
63
  for (const plugin of plugins) {
64
64
  await plugin.setup(builder);
65
65
  }
66
+ await builder.runOnStart();
66
67
  }
67
68
 
68
69
  const state: ServerState = {
@@ -155,6 +156,7 @@ export async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle>
155
156
  const pluginResponse = await builder.runHandleRequest(req, {
156
157
  port: handle?.port ?? PORT,
157
158
  hostname: handle?.hostname ?? "localhost",
159
+ assetManifest: state.assetManifest,
158
160
  });
159
161
  if (pluginResponse) {
160
162
  const securedResponse = wrapPluginResponse(pluginResponse, true);
@@ -198,7 +200,7 @@ export async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle>
198
200
  // Manual route matching — same routes Bun.FileSystemRouter derives
199
201
  // from `.docu/pages/` ("/", "/docs/[[...slug]]", "/404").
200
202
  if (pathname === "/") {
201
- response = handleIndex(state);
203
+ response = await handleIndex(state);
202
204
  } else if (pathname === "/docs" || pathname === "/docs/") {
203
205
  response = await handleDocsIndex(state);
204
206
  } else if (pathname.startsWith("/docs/")) {
@@ -206,7 +208,7 @@ export async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle>
206
208
  response =
207
209
  slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);
208
210
  } else {
209
- response = handleNotFound(state);
211
+ response = await handleNotFound(state);
210
212
  }
211
213
 
212
214
  logger.request(
@@ -54,6 +54,7 @@ if (builder) {
54
54
  for (const plugin of plugins) {
55
55
  await plugin.setup(builder);
56
56
  }
57
+ await builder.runOnStart();
57
58
  }
58
59
 
59
60
  const state: ServerState = {
@@ -159,6 +160,7 @@ const server = Bun.serve({
159
160
  const pluginResponse = await builder.runHandleRequest(req, {
160
161
  port: serverPort,
161
162
  hostname: serverHostname,
163
+ assetManifest: state.assetManifest,
162
164
  });
163
165
  if (pluginResponse) {
164
166
  const securedResponse = wrapPluginResponse(pluginResponse, true);
@@ -200,27 +202,21 @@ const server = Bun.serve({
200
202
  const match = router?.match(pathname);
201
203
  let response: Response;
202
204
 
203
- if (match) {
204
- const routeName = match.name;
205
+ const routeName = match?.name;
205
206
 
206
- if (routeName === "/docs/[[...slug]]") {
207
- const slugParam = match.params?.slug;
208
- const slug = slugParam ? slugParam.split("/") : [];
207
+ if (routeName === "/docs/[[...slug]]") {
208
+ const slugParam = match?.params?.slug;
209
+ const slug = slugParam ? slugParam.split("/") : [];
209
210
 
210
- if (slug.length === 0) {
211
- response = await handleDocsIndex(state);
212
- } else {
213
- response = await handleDocsRoute(slug, state);
214
- }
215
- } else if (routeName === "/404") {
216
- response = handleNotFound(state);
217
- } else if (routeName === "/") {
218
- response = handleIndex(state);
211
+ if (slug.length === 0) {
212
+ response = await handleDocsIndex(state);
219
213
  } else {
220
- response = handleNotFound(state);
214
+ response = await handleDocsRoute(slug, state);
221
215
  }
216
+ } else if (routeName === "/") {
217
+ response = await handleIndex(state);
222
218
  } else {
223
- response = handleNotFound(state);
219
+ response = await handleNotFound(state);
224
220
  }
225
221
 
226
222
  logger.request(
@@ -93,6 +93,17 @@ export interface DocuConfig {
93
93
  plugins?: PluginEntry[];
94
94
  }
95
95
 
96
+ export interface AssetEntry {
97
+ css: string;
98
+ js?: string;
99
+ }
100
+
101
+ export interface AssetManifest {
102
+ docs: AssetEntry;
103
+ home: AssetEntry;
104
+ notFound: AssetEntry;
105
+ }
106
+
96
107
  export interface BuildCacheEntry {
97
108
  hash: string;
98
109
  mtime: number;
@@ -1,13 +1,16 @@
1
+ import { BackgroundBlobs, Hero } from "../components/home";
2
+
1
3
  export default function NotFoundPage() {
2
4
  return (
3
- <div className="flex w-full flex-1 px-4 py-8 lg:h-[calc(100vh-4rem)] lg:px-8 lg:py-4">
4
- <div className="bg-base-100 border-base-300 flex min-h-[50vh] w-full flex-col items-center justify-center rounded-xl border shadow-md lg:min-h-0 lg:flex-1">
5
- <h1 className="text-6xl font-bold">404</h1>
6
- <p className="text-base-content/60 py-4 text-xl">Page not found</p>
7
- <a href="/docs/" className="btn btn-primary mt-2">
8
- Go to Docs
9
- </a>
10
- </div>
11
- </div>
5
+ <main className="bg-base-100 relative isolate flex min-h-screen w-full items-center justify-center overflow-hidden">
6
+ <BackgroundBlobs />
7
+ <Hero
8
+ hero={{
9
+ headline: "404",
10
+ description: "Page not found",
11
+ actions: [{ text: "Go to Docs", link: "/docs/" }],
12
+ }}
13
+ />
14
+ </main>
12
15
  );
13
16
  }
@@ -1,6 +1,7 @@
1
1
  import { loadDocuConfig } from "../node/paths";
2
2
  import { docsHtmlHref, isExternalUrl } from "../node/utils";
3
- import { Hero, Features } from "../components/home";
3
+ import { Hero, Features, BackgroundBlobs } from "../components/home";
4
+ import { ThemeToggle } from "../components/Theme";
4
5
  import type { HomeFeature } from "../node/types";
5
6
 
6
7
  const docuConfig = loadDocuConfig();
@@ -61,41 +62,19 @@ export default function IndexPage() {
61
62
 
62
63
  return (
63
64
  <div className="bg-base-100 relative isolate min-h-screen overflow-hidden">
64
- <div className="absolute top-4 right-4 z-10" id="theme-island" />
65
-
66
- {/* Background gradient blobs */}
67
- <div
68
- aria-hidden="true"
69
- className="pointer-events-none absolute -top-40 left-1/2 -z-10 -translate-x-1/2 blur-3xl sm:-top-80"
70
- >
71
- <div
72
- style={{
73
- clipPath:
74
- "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)",
75
- }}
76
- className="from-primary to-accent h-[40rem] w-[80rem] bg-gradient-to-tr opacity-20"
77
- />
65
+ <div className="absolute top-4 right-4 z-10">
66
+ <div id="theme-toggle-island">
67
+ <ThemeToggle />
68
+ </div>
78
69
  </div>
79
70
 
71
+ <BackgroundBlobs />
72
+
80
73
  {/* Hero Section */}
81
74
  <Hero hero={hero} />
82
75
 
83
76
  {/* Features Section */}
84
77
  <Features features={features} />
85
-
86
- {/* Bottom gradient blob */}
87
- <div
88
- aria-hidden="true"
89
- className="pointer-events-none absolute bottom-0 left-1/2 -z-10 translate-x-1/4 blur-3xl"
90
- >
91
- <div
92
- style={{
93
- clipPath:
94
- "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)",
95
- }}
96
- className="from-accent to-primary h-[30rem] w-[70rem] bg-gradient-to-tr opacity-20"
97
- />
98
- </div>
99
78
  </div>
100
79
  );
101
80
  }