@ox-content/vite-plugin 1.0.0-alpha.0 → 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/dist/chunk.cjs +39 -49
  2. package/dist/github.cjs +323 -3
  3. package/dist/github.cjs.map +1 -0
  4. package/dist/github.mjs +2 -0
  5. package/dist/{github2.js → github2.mjs} +2 -3
  6. package/dist/github2.mjs.map +1 -0
  7. package/dist/index.cjs +1371 -598
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +114 -69
  10. package/dist/index.d.cts.map +1 -1
  11. package/dist/index.d.mts +1960 -0
  12. package/dist/index.d.mts.map +1 -0
  13. package/dist/{index.js → index.mjs} +1383 -623
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/mermaid.cjs +115 -3
  16. package/dist/mermaid.cjs.map +1 -0
  17. package/dist/{mermaid2.js → mermaid.mjs} +14 -6
  18. package/dist/mermaid.mjs.map +1 -0
  19. package/dist/mermaid2.mjs +2 -0
  20. package/dist/ogp.cjs +316 -3
  21. package/dist/ogp.cjs.map +1 -0
  22. package/dist/ogp.mjs +2 -0
  23. package/dist/{ogp2.js → ogp2.mjs} +2 -3
  24. package/dist/ogp2.mjs.map +1 -0
  25. package/dist/tabs.cjs +212 -3
  26. package/dist/tabs.cjs.map +1 -0
  27. package/dist/tabs.mjs +2 -0
  28. package/dist/{tabs2.js → tabs2.mjs} +2 -3
  29. package/dist/tabs2.mjs.map +1 -0
  30. package/dist/youtube.cjs +135 -3
  31. package/dist/youtube.cjs.map +1 -0
  32. package/dist/youtube.mjs +2 -0
  33. package/dist/{youtube2.js → youtube2.mjs} +2 -3
  34. package/dist/youtube2.mjs.map +1 -0
  35. package/package.json +61 -56
  36. package/dist/github.js +0 -3
  37. package/dist/github2.cjs +0 -313
  38. package/dist/github2.cjs.map +0 -1
  39. package/dist/github2.js.map +0 -1
  40. package/dist/index.d.ts +0 -1915
  41. package/dist/index.d.ts.map +0 -1
  42. package/dist/index.js.map +0 -1
  43. package/dist/mermaid.js +0 -3
  44. package/dist/mermaid2.cjs +0 -92
  45. package/dist/mermaid2.cjs.map +0 -1
  46. package/dist/mermaid2.js.map +0 -1
  47. package/dist/ogp.js +0 -3
  48. package/dist/ogp2.cjs +0 -306
  49. package/dist/ogp2.cjs.map +0 -1
  50. package/dist/ogp2.js.map +0 -1
  51. package/dist/tabs.js +0 -3
  52. package/dist/tabs2.cjs +0 -203
  53. package/dist/tabs2.cjs.map +0 -1
  54. package/dist/tabs2.js.map +0 -1
  55. package/dist/youtube.js +0 -3
  56. package/dist/youtube2.cjs +0 -127
  57. package/dist/youtube2.cjs.map +0 -1
  58. package/dist/youtube2.js.map +0 -1
@@ -0,0 +1,1960 @@
1
+ import { LanguageRegistration, LanguageRegistration as LanguageRegistration$1, ThemeRegistration, ThemeRegistration as ThemeRegistration$1 } from "shiki";
2
+ import { EnvironmentOptions, Plugin } from "vite";
3
+
4
+ //#region src/theme.d.ts
5
+ /**
6
+ * Theme API for ox-content SSG
7
+ *
8
+ * Provides VitePress-like theming with default theme + customization.
9
+ */
10
+ /**
11
+ * Theme color configuration.
12
+ */
13
+ interface ThemeColors {
14
+ /** Primary accent color */
15
+ primary?: string;
16
+ /** Primary color on hover */
17
+ primaryHover?: string;
18
+ /** Background color */
19
+ background?: string;
20
+ /** Alternative background color (sidebar, code blocks) */
21
+ backgroundAlt?: string;
22
+ /** Main text color */
23
+ text?: string;
24
+ /** Muted/secondary text color */
25
+ textMuted?: string;
26
+ /** Border color */
27
+ border?: string;
28
+ /** Code block background color */
29
+ codeBackground?: string;
30
+ /** Code block text color */
31
+ codeText?: string;
32
+ }
33
+ /**
34
+ * Theme layout configuration.
35
+ */
36
+ interface ThemeLayout {
37
+ /** Sidebar width (CSS value, e.g., "260px") */
38
+ sidebarWidth?: string;
39
+ /** Header height (CSS value, e.g., "60px") */
40
+ headerHeight?: string;
41
+ /** Maximum content width (CSS value, e.g., "960px") */
42
+ maxContentWidth?: string;
43
+ }
44
+ /**
45
+ * Theme font configuration.
46
+ */
47
+ interface ThemeFonts {
48
+ /** Sans-serif font stack */
49
+ sans?: string;
50
+ /** Monospace font stack */
51
+ mono?: string;
52
+ }
53
+ /**
54
+ * Entry page theme configuration.
55
+ */
56
+ interface ThemeEntryPage {
57
+ /** Landing page presentation mode */
58
+ mode?: "default" | "subtle";
59
+ }
60
+ /**
61
+ * Theme header configuration.
62
+ */
63
+ interface ThemeHeader {
64
+ /** Logo image URL */
65
+ logo?: string;
66
+ /** Light mode logo image URL */
67
+ logoLight?: string;
68
+ /** Dark mode logo image URL */
69
+ logoDark?: string;
70
+ /** Whether to render the site name text next to the logo */
71
+ showSiteNameText?: boolean;
72
+ /** Logo width in pixels */
73
+ logoWidth?: number;
74
+ /** Logo height in pixels */
75
+ logoHeight?: number;
76
+ }
77
+ /**
78
+ * Theme footer configuration.
79
+ */
80
+ interface ThemeFooter {
81
+ /** Footer message (supports HTML) */
82
+ message?: string;
83
+ /** Copyright text (supports HTML) */
84
+ copyright?: string;
85
+ }
86
+ /**
87
+ * Social links configuration.
88
+ */
89
+ interface SocialLinks {
90
+ /** GitHub URL */
91
+ github?: string;
92
+ /** Twitter/X URL */
93
+ twitter?: string;
94
+ /** Discord URL */
95
+ discord?: string;
96
+ }
97
+ /**
98
+ * Embedded HTML content for specific positions in the page layout.
99
+ */
100
+ interface ThemeEmbed {
101
+ /** Content to embed into <head> */
102
+ head?: string;
103
+ /** Content before header */
104
+ headerBefore?: string;
105
+ /** Content after header */
106
+ headerAfter?: string;
107
+ /** Content before sidebar navigation */
108
+ sidebarBefore?: string;
109
+ /** Content after sidebar navigation */
110
+ sidebarAfter?: string;
111
+ /** Content before main content */
112
+ contentBefore?: string;
113
+ /** Content after main content */
114
+ contentAfter?: string;
115
+ /** Content before footer */
116
+ footerBefore?: string;
117
+ /** Custom footer content (replaces default footer) */
118
+ footer?: string;
119
+ }
120
+ /**
121
+ * Complete theme configuration.
122
+ */
123
+ interface ThemeConfig {
124
+ /** Theme name for identification */
125
+ name?: string;
126
+ /** Base theme to extend */
127
+ extends?: ThemeConfig;
128
+ /** Light mode colors (maps to CSS variables) */
129
+ colors?: ThemeColors;
130
+ /** Dark mode colors (maps to CSS variables) */
131
+ darkColors?: ThemeColors;
132
+ /** Font configuration (maps to CSS variables) */
133
+ fonts?: ThemeFonts;
134
+ /** Entry page configuration */
135
+ entryPage?: ThemeEntryPage;
136
+ /** Layout configuration (maps to CSS variables) */
137
+ layout?: ThemeLayout;
138
+ /** Header configuration */
139
+ header?: ThemeHeader;
140
+ /** Footer configuration */
141
+ footer?: ThemeFooter;
142
+ /** Social links configuration */
143
+ socialLinks?: SocialLinks;
144
+ /** Embedded HTML content at specific positions */
145
+ embed?: ThemeEmbed;
146
+ /** Additional custom CSS */
147
+ css?: string;
148
+ /** Additional custom JavaScript */
149
+ js?: string;
150
+ }
151
+ /**
152
+ * Resolved theme configuration (after merging with defaults).
153
+ */
154
+ interface ResolvedThemeConfig {
155
+ name: string;
156
+ colors: ThemeColors;
157
+ darkColors: ThemeColors;
158
+ fonts: ThemeFonts;
159
+ entryPage: ThemeEntryPage;
160
+ layout: ThemeLayout;
161
+ header: ThemeHeader;
162
+ footer: ThemeFooter;
163
+ socialLinks: SocialLinks;
164
+ embed: ThemeEmbed;
165
+ css: string;
166
+ js: string;
167
+ }
168
+ /**
169
+ * Default theme configuration.
170
+ * Based on the current ox-content SSG styles.
171
+ */
172
+ declare const defaultTheme: ThemeConfig;
173
+ /**
174
+ * Defines a theme configuration with type checking.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * const myTheme = defineTheme({
179
+ * extends: defaultTheme,
180
+ * colors: {
181
+ * primary: '#3498db',
182
+ * },
183
+ * footer: {
184
+ * copyright: '2025 My Company',
185
+ * },
186
+ * });
187
+ * ```
188
+ */
189
+ declare function defineTheme(config: ThemeConfig): ThemeConfig;
190
+ /**
191
+ * Merges multiple theme configurations.
192
+ * Later themes override earlier ones.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const merged = mergeThemes(defaultTheme, customTheme, overrides);
197
+ * ```
198
+ */
199
+ declare function mergeThemes(...themes: ThemeConfig[]): ThemeConfig;
200
+ /**
201
+ * Resolves a theme configuration by merging with its extends chain and defaults.
202
+ */
203
+ declare function resolveTheme(config?: ThemeConfig): ResolvedThemeConfig;
204
+ //#endregion
205
+ //#region src/types.d.ts
206
+ /**
207
+ * Hero section action button.
208
+ */
209
+ interface HeroAction {
210
+ /** Button theme: 'brand' (primary) or 'alt' (secondary) */
211
+ theme?: "brand" | "alt";
212
+ /** Button text */
213
+ text: string;
214
+ /** Link URL */
215
+ link: string;
216
+ }
217
+ /**
218
+ * Hero section image configuration.
219
+ */
220
+ interface HeroImage {
221
+ /** Image source URL */
222
+ src: string;
223
+ /** Light mode image source URL */
224
+ lightSrc?: string;
225
+ /** Dark mode image source URL */
226
+ darkSrc?: string;
227
+ /** Alt text */
228
+ alt?: string;
229
+ /** Image width */
230
+ width?: number;
231
+ /** Image height */
232
+ height?: number;
233
+ }
234
+ /**
235
+ * Hero notice configuration.
236
+ */
237
+ interface HeroNotice {
238
+ /** Notice title */
239
+ title?: string;
240
+ /** Notice paragraphs */
241
+ body?: string[];
242
+ }
243
+ /**
244
+ * Hero section configuration for entry page.
245
+ */
246
+ interface HeroConfig {
247
+ /** Main title (large, gradient text) */
248
+ name?: string;
249
+ /** Secondary text (medium size) */
250
+ text?: string;
251
+ /** Tagline (smaller, muted) */
252
+ tagline?: string;
253
+ /** Notice shown near the top of the hero */
254
+ notice?: HeroNotice;
255
+ /** Hero image */
256
+ image?: HeroImage;
257
+ /** Action buttons */
258
+ actions?: HeroAction[];
259
+ }
260
+ /**
261
+ * Feature card for entry page.
262
+ */
263
+ interface FeatureConfig {
264
+ /** Icon - supports: "mdi:icon-name" (Iconify), image URL, or emoji */
265
+ icon?: string;
266
+ /** Feature title */
267
+ title: string;
268
+ /** Feature description */
269
+ details?: string;
270
+ /** Optional link */
271
+ link?: string;
272
+ /** Link text */
273
+ linkText?: string;
274
+ }
275
+ /**
276
+ * Entry page frontmatter configuration.
277
+ */
278
+ interface EntryPageConfig {
279
+ /** Layout type - set to 'entry' for entry page */
280
+ layout: "entry";
281
+ /** Hero section */
282
+ hero?: HeroConfig;
283
+ /** Feature cards */
284
+ features?: FeatureConfig[];
285
+ }
286
+ /**
287
+ * SSG (Static Site Generation) options.
288
+ */
289
+ interface SsgOptions {
290
+ /**
291
+ * Enable SSG mode.
292
+ * @default true
293
+ */
294
+ enabled?: boolean;
295
+ /**
296
+ * Output file extension.
297
+ * @default '.html'
298
+ */
299
+ extension?: string;
300
+ /**
301
+ * Clean output directory before build.
302
+ * @default false
303
+ */
304
+ clean?: boolean;
305
+ /**
306
+ * Bare HTML output (no navigation, no styles).
307
+ * Useful for benchmarking or when using custom layouts.
308
+ * @default false
309
+ */
310
+ bare?: boolean;
311
+ /**
312
+ * Site name for header and title suffix.
313
+ */
314
+ siteName?: string;
315
+ /**
316
+ * OG image URL for social sharing (static URL).
317
+ * If generateOgImage is enabled, this serves as the fallback.
318
+ */
319
+ ogImage?: string;
320
+ /**
321
+ * Generate OG images per page using Rust-based generator.
322
+ * When enabled, each page will have a unique OG image.
323
+ * @default false
324
+ */
325
+ generateOgImage?: boolean;
326
+ /**
327
+ * Site URL for generating absolute OG image URLs.
328
+ * Required for proper SNS sharing.
329
+ * Example: 'https://example.com'
330
+ */
331
+ siteUrl?: string;
332
+ /**
333
+ * Theme configuration for customizing the SSG output.
334
+ * Use defineTheme() to create a theme configuration.
335
+ */
336
+ theme?: ThemeConfig;
337
+ }
338
+ /**
339
+ * Resolved SSG options.
340
+ */
341
+ interface ResolvedSsgOptions {
342
+ enabled: boolean;
343
+ extension: string;
344
+ clean: boolean;
345
+ bare: boolean;
346
+ siteName?: string;
347
+ ogImage?: string;
348
+ generateOgImage: boolean;
349
+ siteUrl?: string;
350
+ theme?: ResolvedThemeConfig;
351
+ }
352
+ /**
353
+ * Plugin options.
354
+ */
355
+ interface OxContentOptions {
356
+ /**
357
+ * Source directory for Markdown files.
358
+ * @default 'content'
359
+ */
360
+ srcDir?: string;
361
+ /**
362
+ * Output directory for built files.
363
+ * @default 'dist'
364
+ */
365
+ outDir?: string;
366
+ /**
367
+ * Base path for the site.
368
+ * @default '/'
369
+ */
370
+ base?: string;
371
+ /**
372
+ * SSG (Static Site Generation) options.
373
+ * Set to false to disable SSG completely.
374
+ * @default { enabled: true }
375
+ */
376
+ ssg?: SsgOptions | boolean;
377
+ /**
378
+ * Enable GitHub Flavored Markdown extensions.
379
+ * @default true
380
+ */
381
+ gfm?: boolean;
382
+ /**
383
+ * Enable footnotes.
384
+ * @default true
385
+ */
386
+ footnotes?: boolean;
387
+ /**
388
+ * Enable tables.
389
+ * @default true
390
+ */
391
+ tables?: boolean;
392
+ /**
393
+ * Enable task lists.
394
+ * @default true
395
+ */
396
+ taskLists?: boolean;
397
+ /**
398
+ * Enable strikethrough.
399
+ * @default true
400
+ */
401
+ strikethrough?: boolean;
402
+ /**
403
+ * Enable syntax highlighting for code blocks.
404
+ * @default false
405
+ */
406
+ highlight?: boolean;
407
+ /**
408
+ * Syntax highlighting theme.
409
+ * @default 'github-dark'
410
+ */
411
+ highlightTheme?: string | ThemeRegistration$1;
412
+ /**
413
+ * Additional languages for syntax highlighting.
414
+ * Accepts Shiki LanguageRegistration objects (e.g., TextMate grammars).
415
+ * These are loaded alongside the built-in languages.
416
+ */
417
+ highlightLangs?: LanguageRegistration$1[];
418
+ /**
419
+ * Opt-in code block annotations for fenced code blocks.
420
+ *
421
+ * Supports the configurable attribute syntax by default, and can also opt
422
+ * into VitePress-compatible fence metadata and inline notation.
423
+ *
424
+ * Example:
425
+ * ` ```ts annotate="highlight:1,3-4;warning:6;error:7" `
426
+ *
427
+ * @default false
428
+ */
429
+ codeAnnotations?: boolean | CodeAnnotationsOptions;
430
+ /**
431
+ * Enable mermaid diagram rendering.
432
+ * @default false
433
+ */
434
+ mermaid?: boolean;
435
+ /**
436
+ * Parse YAML frontmatter.
437
+ * @default true
438
+ */
439
+ frontmatter?: boolean;
440
+ /**
441
+ * Generate table of contents.
442
+ * @default true
443
+ */
444
+ toc?: boolean;
445
+ /**
446
+ * Maximum heading depth for TOC.
447
+ * @default 3
448
+ */
449
+ tocMaxDepth?: number;
450
+ /**
451
+ * Enable OG image generation.
452
+ * @default false
453
+ */
454
+ ogImage?: boolean;
455
+ /**
456
+ * OG image generation options.
457
+ */
458
+ ogImageOptions?: OgImageOptions;
459
+ /**
460
+ * Custom AST transformers.
461
+ */
462
+ transformers?: MarkdownTransformer[];
463
+ /**
464
+ * Source documentation generation options.
465
+ * Set to false to disable (opt-out).
466
+ * @default { enabled: true }
467
+ */
468
+ docs?: DocsOptions | false;
469
+ /**
470
+ * Full-text search options.
471
+ * Set to false to disable search.
472
+ * @default { enabled: true }
473
+ */
474
+ search?: SearchOptions | boolean;
475
+ /**
476
+ * Enable OG Viewer dev tool.
477
+ * Accessible at /__og-viewer during development.
478
+ * @default true
479
+ */
480
+ ogViewer?: boolean;
481
+ /**
482
+ * i18n (internationalization) options.
483
+ * Set to false to disable i18n.
484
+ * @default false
485
+ */
486
+ i18n?: I18nOptions | false;
487
+ }
488
+ /**
489
+ * Resolved options with all defaults applied.
490
+ */
491
+ interface ResolvedOptions {
492
+ srcDir: string;
493
+ outDir: string;
494
+ base: string;
495
+ ssg: ResolvedSsgOptions;
496
+ gfm: boolean;
497
+ footnotes: boolean;
498
+ tables: boolean;
499
+ taskLists: boolean;
500
+ strikethrough: boolean;
501
+ highlight: boolean;
502
+ highlightTheme: string | ThemeRegistration$1;
503
+ highlightLangs: LanguageRegistration$1[];
504
+ codeAnnotations: ResolvedCodeAnnotationsOptions;
505
+ mermaid: boolean;
506
+ frontmatter: boolean;
507
+ toc: boolean;
508
+ tocMaxDepth: number;
509
+ ogImage: boolean;
510
+ ogImageOptions: ResolvedOgImageOptions$1;
511
+ transformers: MarkdownTransformer[];
512
+ docs: ResolvedDocsOptions | false;
513
+ search: ResolvedSearchOptions;
514
+ ogViewer: boolean;
515
+ i18n: ResolvedI18nOptions | false;
516
+ }
517
+ /**
518
+ * Supported line annotation kinds for code blocks.
519
+ */
520
+ type CodeAnnotationKind = "highlight" | "warning" | "error";
521
+ /**
522
+ * Supported code annotation syntaxes.
523
+ */
524
+ type CodeAnnotationSyntax = "attribute" | "vitepress" | "both";
525
+ /**
526
+ * Opt-in code annotation configuration.
527
+ */
528
+ interface CodeAnnotationsOptions {
529
+ /**
530
+ * Annotation syntax to enable.
531
+ *
532
+ * - `attribute`: custom attribute syntax like `annotate="highlight:1,3-4"`
533
+ * - `vitepress`: VitePress-compatible syntax like `{1,3-4}` and `[!code warning]`
534
+ * - `both`: enables both syntaxes
535
+ *
536
+ * @default "attribute"
537
+ */
538
+ notation?: CodeAnnotationSyntax;
539
+ /**
540
+ * Attribute name read from the code fence meta string.
541
+ *
542
+ * Example: `annotate="highlight:1,3-4;warning:6"`
543
+ *
544
+ * @default "annotate"
545
+ */
546
+ metaKey?: string;
547
+ /**
548
+ * Enable line numbers for all code blocks by default.
549
+ *
550
+ * In `vitepress` or `both` mode, fenced code blocks can override this with
551
+ * `:line-numbers`, `:line-numbers=<start>`, or `:no-line-numbers`.
552
+ *
553
+ * @default false
554
+ */
555
+ defaultLineNumbers?: boolean;
556
+ }
557
+ /**
558
+ * Resolved code annotation configuration.
559
+ */
560
+ interface ResolvedCodeAnnotationsOptions {
561
+ enabled: boolean;
562
+ notation: CodeAnnotationSyntax;
563
+ metaKey: string;
564
+ defaultLineNumbers: boolean;
565
+ }
566
+ /**
567
+ * OG image generation options.
568
+ * Uses Chromium-based rendering with customizable templates.
569
+ */
570
+ interface OgImageOptions {
571
+ /**
572
+ * Path to a custom template file (.ts, .vue, .svelte, .tsx/.jsx).
573
+ * - `.ts`: default-export a function `(props) => string`
574
+ * - `.vue`: Vue SFC, rendered via SSR
575
+ * - `.svelte`: Svelte SFC, rendered via SSR
576
+ * - `.tsx`/`.jsx`: React Server Component, rendered via SSR
577
+ * If not specified, the built-in default template is used.
578
+ */
579
+ template?: string;
580
+ /**
581
+ * Vue plugin to use for compiling `.vue` templates.
582
+ * - `'vitejs'`: Use `@vue/compiler-sfc` (official, default)
583
+ * - `'vizejs'`: Use `@vizejs/vite-plugin` (Rust-based)
584
+ * @default 'vitejs'
585
+ */
586
+ vuePlugin?: "vitejs" | "vizejs";
587
+ /**
588
+ * Image width in pixels.
589
+ * @default 1200
590
+ */
591
+ width?: number;
592
+ /**
593
+ * Image height in pixels.
594
+ * @default 630
595
+ */
596
+ height?: number;
597
+ /**
598
+ * Enable content-hash based caching.
599
+ * Skips rendering when content hasn't changed.
600
+ * @default true
601
+ */
602
+ cache?: boolean;
603
+ /**
604
+ * Number of concurrent page instances for parallel rendering.
605
+ * @default 1
606
+ */
607
+ concurrency?: number;
608
+ }
609
+ /**
610
+ * Resolved OG image options with all defaults applied.
611
+ */
612
+ interface ResolvedOgImageOptions$1 {
613
+ template?: string;
614
+ vuePlugin: "vitejs" | "vizejs";
615
+ width: number;
616
+ height: number;
617
+ cache: boolean;
618
+ concurrency: number;
619
+ }
620
+ /**
621
+ * Custom AST transformer.
622
+ */
623
+ interface MarkdownTransformer {
624
+ /**
625
+ * Transformer name.
626
+ */
627
+ name: string;
628
+ /**
629
+ * Transform function.
630
+ */
631
+ transform: (ast: MarkdownNode, context: TransformContext) => MarkdownNode | Promise<MarkdownNode>;
632
+ }
633
+ /**
634
+ * Transform context passed to transformers.
635
+ */
636
+ interface TransformContext {
637
+ /**
638
+ * File path being processed.
639
+ */
640
+ filePath: string;
641
+ /**
642
+ * Frontmatter data.
643
+ */
644
+ frontmatter: Record<string, unknown>;
645
+ /**
646
+ * Resolved plugin options.
647
+ */
648
+ options: ResolvedOptions;
649
+ }
650
+ /**
651
+ * Markdown AST node (simplified for TypeScript).
652
+ */
653
+ interface MarkdownNode {
654
+ type: string;
655
+ children?: MarkdownNode[];
656
+ value?: string;
657
+ [key: string]: unknown;
658
+ }
659
+ /**
660
+ * Transform result.
661
+ */
662
+ interface TransformResult {
663
+ /**
664
+ * Generated JavaScript code.
665
+ */
666
+ code: string;
667
+ /**
668
+ * Source map (null means no source map).
669
+ */
670
+ map?: null;
671
+ /**
672
+ * Rendered HTML.
673
+ */
674
+ html: string;
675
+ /**
676
+ * Parsed frontmatter.
677
+ */
678
+ frontmatter: Record<string, unknown>;
679
+ /**
680
+ * Table of contents.
681
+ */
682
+ toc: TocEntry[];
683
+ }
684
+ /**
685
+ * Table of contents entry.
686
+ */
687
+ interface TocEntry {
688
+ /**
689
+ * Heading depth (1-6).
690
+ */
691
+ depth: number;
692
+ /**
693
+ * Heading text.
694
+ */
695
+ text: string;
696
+ /**
697
+ * Slug/ID for linking.
698
+ */
699
+ slug: string;
700
+ /**
701
+ * Child entries.
702
+ */
703
+ children: TocEntry[];
704
+ }
705
+ /**
706
+ * Options for source documentation generation.
707
+ */
708
+ interface DocsOptions {
709
+ /**
710
+ * Enable/disable docs generation.
711
+ * @default true (opt-out)
712
+ */
713
+ enabled?: boolean;
714
+ /**
715
+ * Source directories to scan for documentation.
716
+ * @default ['./src']
717
+ */
718
+ src?: string[];
719
+ /**
720
+ * Output directory for generated documentation.
721
+ * @default 'docs/api'
722
+ */
723
+ out?: string;
724
+ /**
725
+ * Glob patterns for files to include.
726
+ * @default ['**\/*.ts', '**\/*.tsx']
727
+ */
728
+ include?: string[];
729
+ /**
730
+ * Glob patterns for files to exclude.
731
+ * @default ['**\/*.test.*', '**\/*.spec.*', 'node_modules']
732
+ */
733
+ exclude?: string[];
734
+ /**
735
+ * Output format.
736
+ * @default 'markdown'
737
+ */
738
+ format?: "markdown" | "json" | "html";
739
+ /**
740
+ * Include private members in documentation.
741
+ * @default false
742
+ */
743
+ private?: boolean;
744
+ /**
745
+ * Generate table of contents for each file.
746
+ * @default true
747
+ */
748
+ toc?: boolean;
749
+ /**
750
+ * Group documentation by file or category.
751
+ * @default 'file'
752
+ */
753
+ groupBy?: "file" | "category";
754
+ /**
755
+ * GitHub repository URL for source code links.
756
+ * When provided, generated documentation will include links to source code.
757
+ * Example: 'https://github.com/ubugeeei/ox-content'
758
+ */
759
+ githubUrl?: string;
760
+ /**
761
+ * Generate navigation metadata file.
762
+ * @default true
763
+ */
764
+ generateNav?: boolean;
765
+ }
766
+ /**
767
+ * Resolved docs options with all defaults applied.
768
+ */
769
+ interface ResolvedDocsOptions {
770
+ enabled: boolean;
771
+ src: string[];
772
+ out: string;
773
+ include: string[];
774
+ exclude: string[];
775
+ format: "markdown" | "json" | "html";
776
+ private: boolean;
777
+ toc: boolean;
778
+ groupBy: "file" | "category";
779
+ githubUrl?: string;
780
+ generateNav: boolean;
781
+ }
782
+ /**
783
+ * A single documentation entry extracted from source.
784
+ */
785
+ interface DocEntry {
786
+ name: string;
787
+ kind: "function" | "class" | "interface" | "type" | "variable" | "module";
788
+ description: string;
789
+ params?: ParamDoc[];
790
+ returns?: ReturnDoc;
791
+ examples?: string[];
792
+ tags?: Record<string, string>;
793
+ private?: boolean;
794
+ file: string;
795
+ line: number;
796
+ endLine: number;
797
+ signature?: string;
798
+ }
799
+ /**
800
+ * Parameter documentation.
801
+ */
802
+ interface ParamDoc {
803
+ name: string;
804
+ type: string;
805
+ description: string;
806
+ optional?: boolean;
807
+ default?: string;
808
+ }
809
+ /**
810
+ * Return type documentation.
811
+ */
812
+ interface ReturnDoc {
813
+ type: string;
814
+ description: string;
815
+ }
816
+ /**
817
+ * Extracted documentation for a single file.
818
+ */
819
+ interface ExtractedDocs {
820
+ file: string;
821
+ entries: DocEntry[];
822
+ }
823
+ /**
824
+ * Machine-readable payload emitted alongside generated docs.
825
+ */
826
+ interface GeneratedDocsData {
827
+ version: 1;
828
+ generatedAt: string;
829
+ modules: ExtractedDocs[];
830
+ }
831
+ /**
832
+ * Options for full-text search.
833
+ */
834
+ interface SearchOptions {
835
+ /**
836
+ * Enable search functionality.
837
+ * @default true
838
+ */
839
+ enabled?: boolean;
840
+ /**
841
+ * Maximum number of search results.
842
+ * @default 10
843
+ */
844
+ limit?: number;
845
+ /**
846
+ * Enable prefix matching for autocomplete.
847
+ * @default true
848
+ */
849
+ prefix?: boolean;
850
+ /**
851
+ * Placeholder text for the search input.
852
+ * @default 'Search documentation...'
853
+ */
854
+ placeholder?: string;
855
+ /**
856
+ * Keyboard shortcut to focus search (without modifier).
857
+ * @default '/'
858
+ */
859
+ hotkey?: string;
860
+ }
861
+ /**
862
+ * Resolved search options.
863
+ */
864
+ interface ResolvedSearchOptions {
865
+ enabled: boolean;
866
+ limit: number;
867
+ prefix: boolean;
868
+ placeholder: string;
869
+ hotkey: string;
870
+ }
871
+ /**
872
+ * Search document structure.
873
+ */
874
+ interface SearchDocument {
875
+ id: string;
876
+ title: string;
877
+ url: string;
878
+ body: string;
879
+ headings: string[];
880
+ code: string[];
881
+ }
882
+ /**
883
+ * Search result structure.
884
+ */
885
+ interface SearchResult {
886
+ id: string;
887
+ title: string;
888
+ url: string;
889
+ score: number;
890
+ matches: string[];
891
+ snippet: string;
892
+ scopes?: string[];
893
+ }
894
+ /**
895
+ * Parsed search query with optional scope prefixes.
896
+ */
897
+ interface ScopedSearchQuery {
898
+ text: string;
899
+ scopes: string[];
900
+ }
901
+ /**
902
+ * Locale configuration.
903
+ */
904
+ interface LocaleConfig {
905
+ /** BCP 47 locale tag (e.g., 'en', 'ja', 'zh-Hans'). */
906
+ code: string;
907
+ /** Display name for this locale (e.g., 'English', '日本語'). */
908
+ name: string;
909
+ /** Text direction. @default 'ltr' */
910
+ dir?: "ltr" | "rtl";
911
+ }
912
+ /**
913
+ * i18n (internationalization) options.
914
+ */
915
+ interface I18nOptions {
916
+ /**
917
+ * Enable i18n.
918
+ * @default false
919
+ */
920
+ enabled?: boolean;
921
+ /**
922
+ * Path to i18n dictionary directory (relative to project root).
923
+ * @default 'content/i18n'
924
+ */
925
+ dir?: string;
926
+ /**
927
+ * Default locale tag.
928
+ * @default 'en'
929
+ */
930
+ defaultLocale?: string;
931
+ /**
932
+ * Available locales.
933
+ */
934
+ locales?: LocaleConfig[];
935
+ /**
936
+ * Hide default locale prefix in URLs.
937
+ * When true, `/page` serves the default locale and `/ja/page` serves Japanese.
938
+ * When false, all locales get prefixed: `/en/page`, `/ja/page`.
939
+ * @default true
940
+ */
941
+ hideDefaultLocale?: boolean;
942
+ /**
943
+ * Run i18n checks during build.
944
+ * @default true
945
+ */
946
+ check?: boolean;
947
+ /**
948
+ * Translation function names to detect in source code.
949
+ * @default ['t', '$t']
950
+ */
951
+ functionNames?: string[];
952
+ }
953
+ /**
954
+ * Resolved i18n options with all defaults applied.
955
+ */
956
+ interface ResolvedI18nOptions {
957
+ enabled: boolean;
958
+ dir: string;
959
+ defaultLocale: string;
960
+ locales: LocaleConfig[];
961
+ hideDefaultLocale: boolean;
962
+ check: boolean;
963
+ functionNames: string[];
964
+ }
965
+ //#endregion
966
+ //#region src/environment.d.ts
967
+ /**
968
+ * Creates the Markdown processing environment configuration.
969
+ *
970
+ * This environment is used for:
971
+ * - Server-side rendering of Markdown files
972
+ * - Static site generation
973
+ * - Pre-rendering at build time
974
+ *
975
+ * @example
976
+ * ```ts
977
+ * // In your vite.config.ts
978
+ * export default defineConfig({
979
+ * environments: {
980
+ * markdown: createMarkdownEnvironment({
981
+ * srcDir: 'content',
982
+ * gfm: true,
983
+ * }),
984
+ * },
985
+ * });
986
+ * ```
987
+ */
988
+ declare function createMarkdownEnvironment(options: ResolvedOptions): EnvironmentOptions;
989
+ //#endregion
990
+ //#region src/transform.d.ts
991
+ /**
992
+ * Transforms Markdown content into a JavaScript module.
993
+ *
994
+ * This is the primary entry point for transforming Markdown files. It handles
995
+ * the complete transformation pipeline including parsing, rendering, syntax
996
+ * highlighting, and code generation.
997
+ *
998
+ * ## Pipeline Steps
999
+ *
1000
+ * 1. **Parse & Render**: Uses Rust-based parser via NAPI for high performance
1001
+ * 2. **Extract Metadata**: Parses YAML frontmatter and generates table of contents
1002
+ * 3. **Enhance HTML**: Applies syntax highlighting and Mermaid diagram rendering
1003
+ * 4. **Generate Code**: Creates importable JavaScript module
1004
+ *
1005
+ * ## Generated Module Exports
1006
+ *
1007
+ * - `html` (string): Rendered HTML content with all enhancements applied
1008
+ * - `frontmatter` (object): Parsed YAML frontmatter as JavaScript object
1009
+ * - `toc` (array): Hierarchical table of contents entries
1010
+ * - `render` (function): Client-side render function for dynamic updates
1011
+ *
1012
+ * ## Markdown Features Supported
1013
+ *
1014
+ * The supported features depend on parser options:
1015
+ * - **Commonmark**: Headings, paragraphs, lists, code blocks, links, images
1016
+ * - **GFM Extensions**: Tables, task lists, strikethrough, autolinks
1017
+ * - **Enhancements**: Syntax highlighting, Mermaid diagrams, TOC generation
1018
+ * - **Metadata**: YAML frontmatter parsing
1019
+ *
1020
+ * ## Performance
1021
+ *
1022
+ * Uses Rust-based parsing via NAPI bindings for optimal performance. Falls back
1023
+ * gracefully if Rust bindings are unavailable.
1024
+ *
1025
+ * @param source - Raw Markdown source code (may include YAML frontmatter)
1026
+ * @param filePath - File path for source attribution and relative link resolution
1027
+ * @param options - Resolved plugin options controlling transformation behavior
1028
+ *
1029
+ * @returns Promise resolving to transformation result with HTML and metadata
1030
+ *
1031
+ * @throws Error if NAPI bindings are unavailable (can be handled gracefully)
1032
+ *
1033
+ * @example
1034
+ * ```typescript
1035
+ * import { transformMarkdown } from './transform';
1036
+ * import { resolveOptions } from './index';
1037
+ *
1038
+ * // Transform a Markdown file with YAML frontmatter
1039
+ * const markdown = `---
1040
+ * title: Getting Started
1041
+ * author: john
1042
+ * ---
1043
+ *
1044
+ * # Getting Started
1045
+ *
1046
+ * Welcome! This guide explains [transformMarkdown] function.
1047
+ *
1048
+ * ## Installation
1049
+ *
1050
+ * \`\`\`bash
1051
+ * npm install @ox-content/vite-plugin
1052
+ * \`\`\`
1053
+ * `;
1054
+ *
1055
+ * const options = resolveOptions({
1056
+ * highlight: true,
1057
+ * highlightTheme: 'github-dark',
1058
+ * toc: true,
1059
+ * gfm: true,
1060
+ * mermaid: true,
1061
+ * });
1062
+ *
1063
+ * const result = await transformMarkdown(markdown, 'docs/getting-started.md', options);
1064
+ *
1065
+ * // Generated module exports
1066
+ * console.log(result.html); // Rendered HTML with syntax highlighting
1067
+ * console.log(result.frontmatter); // { title: 'Getting Started', author: 'john' }
1068
+ * console.log(result.toc); // [{ depth: 1, text: 'Getting Started', ... }]
1069
+ * console.log(result.code); // ES module export statement
1070
+ * ```
1071
+ */
1072
+ /**
1073
+ * SSG-specific transform options.
1074
+ */
1075
+ interface SsgTransformOptions {
1076
+ /** Convert `.md` links to `.html` links */
1077
+ convertMdLinks?: boolean;
1078
+ /** Base URL for absolute link conversion */
1079
+ baseUrl?: string;
1080
+ /** Source file path for relative link resolution */
1081
+ sourcePath?: string;
1082
+ }
1083
+ declare function transformMarkdown(source: string, filePath: string, options: ResolvedOptions, ssgOptions?: SsgTransformOptions): Promise<TransformResult>;
1084
+ //#endregion
1085
+ //#region src/docs.d.ts
1086
+ /**
1087
+ * Extracts JSDoc documentation from source files in specified directories.
1088
+ *
1089
+ * This function recursively searches directories for source files matching
1090
+ * the include/exclude patterns, then extracts all documented items (functions,
1091
+ * classes, interfaces, types) from those files.
1092
+ *
1093
+ * ## Process
1094
+ *
1095
+ * 1. **File Discovery**: Recursively walks directories, applying filters
1096
+ * 2. **File Reading**: Loads each matching file's content
1097
+ * 3. **JSDoc Extraction**: Parses JSDoc comments using regex patterns
1098
+ * 4. **Declaration Matching**: Pairs JSDoc comments with source declarations
1099
+ * 5. **Result Collection**: Aggregates extracted documentation by file
1100
+ *
1101
+ * ## Include/Exclude Patterns
1102
+ *
1103
+ * Patterns support:
1104
+ * - `**` - Match any directory structure
1105
+ * - `*` - Match any filename
1106
+ * - Standard glob patterns (e.g., `**\/*.test.ts`)
1107
+ *
1108
+ * ## Performance Considerations
1109
+ *
1110
+ * - Uses filesystem I/O which can be slow for large codebases
1111
+ * - Consider using more specific include patterns to reduce file scanning
1112
+ * - Results are not cached; call once per build/dev session
1113
+ *
1114
+ * @param srcDirs - Array of source directory paths to scan
1115
+ * @param options - Documentation extraction options (filters, grouping, etc.)
1116
+ *
1117
+ * @returns Promise resolving to array of extracted documentation by file.
1118
+ * Each ExtractedDocs object contains file path and array of DocEntry items.
1119
+ *
1120
+ * @example
1121
+ * ```typescript
1122
+ * const docs = await extractDocs(
1123
+ * ['./packages/vite-plugin/src'],
1124
+ * {
1125
+ * enabled: true,
1126
+ * src: [],
1127
+ * out: 'docs',
1128
+ * include: ['**\/*.ts'],
1129
+ * exclude: ['**\/*.test.ts', '**\/*.spec.ts'],
1130
+ * format: 'markdown',
1131
+ * private: false,
1132
+ * toc: true,
1133
+ * groupBy: 'file',
1134
+ * generateNav: true,
1135
+ * }
1136
+ * );
1137
+ *
1138
+ * // Returns:
1139
+ * // [
1140
+ * // {
1141
+ * // file: '/path/to/transform.ts',
1142
+ * // entries: [
1143
+ * // { name: 'transformMarkdown', kind: 'function', ... },
1144
+ * // { name: 'loadNapiBindings', kind: 'function', ... },
1145
+ * // ]
1146
+ * // },
1147
+ * // ...
1148
+ * // ]
1149
+ * ```
1150
+ */
1151
+ declare function extractDocs(srcDirs: string[], options: ResolvedDocsOptions): Promise<ExtractedDocs[]>;
1152
+ /**
1153
+ * Generates Markdown documentation from extracted docs.
1154
+ */
1155
+ declare function generateMarkdown(docs: ExtractedDocs[], options: ResolvedDocsOptions): Record<string, string>;
1156
+ /**
1157
+ * Writes generated documentation to the output directory.
1158
+ */
1159
+ declare function writeDocs(docs: Record<string, string>, outDir: string, extractedDocs?: ExtractedDocs[], options?: ResolvedDocsOptions): Promise<void>;
1160
+ declare function resolveDocsOptions(options: DocsOptions | false | undefined): ResolvedDocsOptions | false;
1161
+ //#endregion
1162
+ //#region src/ssg.d.ts
1163
+ /**
1164
+ * Default HTML template for SSG pages with navigation.
1165
+ */
1166
+ declare const DEFAULT_HTML_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>{{title}}{{#siteName}} - {{siteName}}{{/siteName}}</title>\n {{#description}}<meta name=\"description\" content=\"{{description}}\">{{/description}}\n <!-- Open Graph -->\n <meta property=\"og:type\" content=\"website\">\n <meta property=\"og:title\" content=\"{{title}}{{#siteName}} - {{siteName}}{{/siteName}}\">\n {{#description}}<meta property=\"og:description\" content=\"{{description}}\">{{/description}}\n {{#ogImage}}<meta property=\"og:image\" content=\"{{ogImage}}\">{{/ogImage}}\n <!-- Twitter Card -->\n {{#ogImage}}<meta name=\"twitter:card\" content=\"summary_large_image\">{{/ogImage}}\n {{^ogImage}}<meta name=\"twitter:card\" content=\"summary\">{{/ogImage}}\n <meta name=\"twitter:title\" content=\"{{title}}{{#siteName}} - {{siteName}}{{/siteName}}\">\n {{#description}}<meta name=\"twitter:description\" content=\"{{description}}\">{{/description}}\n {{#ogImage}}<meta name=\"twitter:image\" content=\"{{ogImage}}\">{{/ogImage}}\n <style>\n :root {\n --sidebar-width: 260px;\n --header-height: 60px;\n --max-content-width: 960px;\n --font-sans: 'IBM Plex Sans', 'Avenir Next', 'Segoe UI Variable', 'Segoe UI', sans-serif;\n --font-mono: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;\n --color-bg: #ffffff;\n --color-bg-alt: #f5f7fb;\n --color-text: #131a30;\n --color-text-muted: #4f607b;\n --color-border: #d2dbea;\n --color-primary: #4f6fae;\n --color-primary-hover: #425f96;\n --color-code-bg: #101a31;\n --color-code-bg-top: #18264a;\n --color-code-text: #edf3ff;\n --color-code-line-highlight: rgba(56, 189, 248, 0.16);\n --color-code-line-warning: rgba(245, 158, 11, 0.18);\n --color-code-line-warning-border: #f59e0b;\n --color-code-line-error: rgba(239, 68, 68, 0.18);\n --color-code-line-error-border: #ef4444;\n --color-code-frame-border: rgba(147, 166, 200, 0.46);\n --surface-noise-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 180'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23noise)' opacity='0.062'/%3E%3C/svg%3E\");\n --surface-noise-size: 164px 164px;\n }\n [data-theme=\"dark\"] {\n --color-bg: #060816;\n --color-bg-alt: #0d1528;\n --color-text: #ebf2ff;\n --color-text-muted: #8ea0bf;\n --color-border: #223252;\n --color-primary: #86a4da;\n --color-primary-hover: #a3bbe8;\n --color-code-bg: #0a1020;\n --color-code-bg-top: #0a1020;\n --color-code-text: #e7f0ff;\n --color-code-line-highlight: rgba(14, 165, 233, 0.2);\n --color-code-line-warning: rgba(245, 158, 11, 0.2);\n --color-code-line-warning-border: #f59e0b;\n --color-code-line-error: rgba(239, 68, 68, 0.22);\n --color-code-line-error-border: #f87171;\n --color-code-frame-border: rgba(34, 50, 82, 0.92);\n --surface-noise-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 180'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.25' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23noise)' opacity='0.098'/%3E%3C/svg%3E\");\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n --color-bg: #060816;\n --color-bg-alt: #0d1528;\n --color-text: #ebf2ff;\n --color-text-muted: #8ea0bf;\n --color-border: #223252;\n --color-primary: #86a4da;\n --color-primary-hover: #a3bbe8;\n --color-code-bg: #0a1020;\n --color-code-bg-top: #0a1020;\n --color-code-text: #e7f0ff;\n --color-code-line-highlight: rgba(14, 165, 233, 0.2);\n --color-code-line-warning: rgba(245, 158, 11, 0.2);\n --color-code-line-warning-border: #f59e0b;\n --color-code-line-error: rgba(239, 68, 68, 0.22);\n --color-code-line-error-border: #f87171;\n --color-code-frame-border: rgba(34, 50, 82, 0.92);\n --surface-noise-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 180'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.25' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23noise)' opacity='0.098'/%3E%3C/svg%3E\");\n }\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n html { scroll-behavior: smooth; }\n body {\n font-family: var(--font-sans);\n line-height: 1.7;\n color: var(--color-text);\n background: var(--color-bg);\n background-image: var(--surface-noise-image);\n background-size: var(--surface-noise-size);\n background-repeat: repeat;\n background-blend-mode: soft-light;\n }\n a { color: var(--color-primary); text-decoration: none; }\n a:hover { color: var(--color-primary-hover); text-decoration: underline; }\n\n /* Header */\n .header {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: var(--header-height);\n background: var(--color-bg);\n border-bottom: 1px solid var(--color-border);\n display: flex;\n align-items: center;\n padding: 0 1.5rem;\n z-index: 100;\n }\n .header,\n .sidebar,\n .search-modal,\n .mobile-footer,\n .content .ox-api-entry,\n .content .ox-api-module,\n .content blockquote.ox-callout {\n background-image: var(--surface-noise-image);\n background-size: var(--surface-noise-size);\n background-repeat: repeat;\n background-blend-mode: soft-light;\n }\n .header-title {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--color-text);\n }\n .header-title:hover { text-decoration: none; }\n .menu-toggle {\n display: none;\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n margin-right: 0.75rem;\n }\n .menu-toggle svg { display: block; }\n .menu-toggle path { stroke: var(--color-text); }\n .header-actions { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }\n .search-button {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem 0.75rem;\n background: var(--color-bg-alt);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n color: var(--color-text-muted);\n cursor: pointer;\n font-size: 0.875rem;\n transition: border-color 0.15s, color 0.15s;\n }\n .search-button:hover { border-color: var(--color-primary); color: var(--color-text); }\n .search-button svg { width: 16px; height: 16px; }\n .search-button kbd {\n padding: 0.125rem 0.375rem;\n background: var(--color-bg);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n font-family: var(--font-mono);\n font-size: 0.75rem;\n }\n @media (max-width: 640px) {\n .search-button span, .search-button kbd { display: none; }\n .search-button { padding: 0.5rem; }\n }\n .search-modal-overlay {\n display: none;\n position: fixed;\n inset: 0;\n z-index: 200;\n background: rgba(0,0,0,0.6);\n justify-content: center;\n padding-top: 10vh;\n }\n .search-modal-overlay.open { display: flex; }\n .search-modal {\n width: 100%;\n max-width: 560px;\n margin: 0 1rem;\n background: var(--color-bg);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n overflow: hidden;\n max-height: 70vh;\n display: flex;\n flex-direction: column;\n }\n .search-header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 1rem;\n border-bottom: 1px solid var(--color-border);\n }\n .search-header svg { flex-shrink: 0; color: var(--color-text-muted); }\n .search-input {\n flex: 1;\n background: none;\n border: none;\n outline: none;\n font-size: 1rem;\n color: var(--color-text);\n }\n .search-input::placeholder { color: var(--color-text-muted); }\n .search-close {\n padding: 0.25rem 0.5rem;\n background: var(--color-bg-alt);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n color: var(--color-text-muted);\n font-family: var(--font-mono);\n font-size: 0.75rem;\n cursor: pointer;\n }\n .search-results {\n flex: 1;\n overflow-y: auto;\n padding: 0.5rem;\n }\n .search-result {\n display: block;\n padding: 0.75rem 1rem;\n border-radius: 4px;\n color: var(--color-text);\n text-decoration: none;\n }\n .search-result:hover, .search-result.selected { background: var(--color-bg-alt); text-decoration: none; }\n .search-result-title { font-weight: 600; font-size: 0.875rem; margin-bottom: 0.25rem; }\n .search-result-snippet { font-size: 0.8125rem; color: var(--color-text-muted); }\n .search-empty { padding: 2rem 1rem; text-align: center; color: var(--color-text-muted); }\n .search-footer {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 1rem;\n padding: 0.75rem 1rem;\n border-top: 1px solid var(--color-border);\n background: var(--color-bg-alt);\n font-size: 0.75rem;\n color: var(--color-text-muted);\n }\n .search-footer kbd {\n padding: 0.125rem 0.375rem;\n background: var(--color-bg);\n border: 1px solid var(--color-border);\n border-radius: 4px;\n font-family: var(--font-mono);\n }\n .theme-toggle {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.5rem;\n border-radius: 4px;\n color: var(--color-text-muted);\n transition: background 0.15s, color 0.15s;\n }\n .theme-toggle:hover { background: var(--color-bg-alt); color: var(--color-text); }\n .theme-toggle svg { display: block; width: 20px; height: 20px; }\n .theme-toggle .icon-sun { display: none; }\n .theme-toggle .icon-moon { display: block; }\n [data-theme=\"dark\"] .theme-toggle .icon-sun { display: block; }\n [data-theme=\"dark\"] .theme-toggle .icon-moon { display: none; }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) .theme-toggle .icon-sun { display: block; }\n :root:not([data-theme=\"light\"]) .theme-toggle .icon-moon { display: none; }\n }\n\n /* Layout */\n .layout {\n display: flex;\n padding-top: var(--header-height);\n min-height: 100vh;\n }\n\n /* Sidebar */\n .sidebar {\n position: fixed;\n top: var(--header-height);\n left: 0;\n bottom: 0;\n width: var(--sidebar-width);\n background: color-mix(in srgb, var(--color-bg-alt) 16%, var(--color-bg));\n border-right: 1px solid color-mix(in srgb, var(--color-border) 48%, transparent);\n overflow-y: auto;\n padding: 1rem 0.875rem 1.5rem;\n }\n .sidebar--entry { display: none; }\n .sidebar nav {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n .nav-section { margin-bottom: 0; }\n .nav-title {\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--color-text-muted);\n margin-bottom: 0.4rem;\n padding: 0 0.625rem;\n }\n .nav-list {\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n }\n .nav-item { margin: 0; }\n .nav-link {\n display: block;\n padding: 0.45rem 0.625rem;\n border-radius: 0;\n color: color-mix(in srgb, var(--color-text) 92%, var(--color-text-muted));\n font-size: 0.875rem;\n }\n .nav-link:hover {\n background: color-mix(in srgb, var(--color-bg-alt) 58%, transparent);\n color: var(--color-text);\n text-decoration: none;\n }\n .nav-link.active {\n background: color-mix(in srgb, var(--color-bg-alt) 72%, transparent);\n color: var(--color-text);\n font-weight: 600;\n }\n\n /* Main content */\n .main {\n flex: 1;\n margin-left: var(--sidebar-width);\n padding: 2rem;\n min-width: 0;\n overflow-x: hidden;\n }\n .content {\n max-width: var(--max-content-width);\n margin: 0 auto;\n overflow-wrap: break-word;\n word-wrap: break-word;\n word-break: break-word;\n }\n\n /* TOC (right sidebar) */\n .toc {\n position: fixed;\n top: calc(var(--header-height) + 2rem);\n right: 2rem;\n width: 200px;\n font-size: 0.8125rem;\n }\n .toc-title {\n font-weight: 600;\n margin-bottom: 0.75rem;\n color: var(--color-text-muted);\n }\n .toc-list { list-style: none; }\n .toc-item { margin: 0.375rem 0; }\n .toc-link {\n color: var(--color-text-muted);\n display: block;\n padding-left: calc((var(--depth, 1) - 1) * 0.75rem);\n }\n .toc-link:hover { color: var(--color-primary); }\n @media (max-width: 1200px) { .toc { display: none; } }\n\n /* Typography */\n .content h1 { font-size: 2.25rem; margin-bottom: 1rem; line-height: 1.2; }\n .content h2 { font-size: 1.5rem; margin-top: 2.5rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-border); }\n .content h3 { font-size: 1.25rem; margin-top: 2rem; margin-bottom: 0.75rem; }\n .content h4 { font-size: 1rem; margin-top: 1.5rem; margin-bottom: 0.5rem; }\n .content p { margin-bottom: 1rem; }\n .content ul, .content ol { margin: 1rem 0; padding-left: 1.5rem; }\n .content li { margin: 0.375rem 0; }\n .content blockquote {\n border-left: 4px solid var(--color-primary);\n padding: 0.5rem 1rem;\n margin: 1rem 0;\n background: var(--color-bg-alt);\n border-radius: 0 4px 4px 0;\n }\n .content blockquote.ox-callout {\n --callout-accent: var(--color-primary);\n border-left-width: 3px;\n border-left-color: var(--callout-accent);\n padding: 0.9rem 1rem;\n border-radius: 4px;\n background: color-mix(in srgb, var(--color-bg-alt) 92%, var(--callout-accent) 8%);\n }\n .content blockquote.ox-callout.ox-callout--note,\n .content blockquote.ox-callout.ox-callout--important { --callout-accent: var(--color-primary); }\n .content blockquote.ox-callout.ox-callout--tip { --callout-accent: #0891b2; }\n .content blockquote.ox-callout.ox-callout--warning { --callout-accent: #d97706; }\n .content blockquote.ox-callout.ox-callout--caution { --callout-accent: #dc2626; }\n .content .ox-callout-title {\n margin: 0 0 0.5rem;\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--callout-accent, var(--color-primary));\n }\n .content blockquote.ox-callout > :last-child { margin-bottom: 0; }\n .content blockquote.ox-callout > :not(.ox-callout-title):first-of-type { margin-top: 0; }\n .content code {\n font-family: var(--font-mono);\n font-size: 0.875em;\n background: var(--color-bg-alt);\n padding: 0.2em 0.4em;\n border-radius: 4px;\n word-break: break-all;\n }\n .content pre {\n background: linear-gradient(\n 180deg,\n var(--color-code-bg-top) 0,\n var(--color-code-bg) 3.5rem\n ) !important;\n color: var(--color-code-text);\n padding: 1rem 1.25rem;\n border-radius: 4px;\n border: 1px solid var(--color-code-frame-border);\n overflow-x: auto;\n margin: 1.5rem 0;\n line-height: 1.5;\n }\n .content pre code {\n background: transparent;\n border: 0;\n padding: 0;\n border-radius: 0;\n font-size: 0.8125rem;\n word-break: normal;\n }\n .content pre.ox-code-block code {\n display: block;\n }\n .content pre.ox-code-block .line {\n display: block;\n margin: 0 -1.25rem;\n padding: 0 1.25rem;\n }\n .content pre.ox-code-block .ox-code-line--highlight {\n background: var(--color-code-line-highlight);\n }\n .content pre.ox-code-block .ox-code-line--warning {\n background: var(--color-code-line-warning);\n border-left: 3px solid var(--color-code-line-warning-border);\n }\n .content pre.ox-code-block .ox-code-line--error {\n background: var(--color-code-line-error);\n border-left: 3px solid var(--color-code-line-error-border);\n }\n .content table {\n width: 100%;\n border-collapse: collapse;\n margin: 1.5rem 0;\n font-size: 0.875rem;\n }\n .content th, .content td {\n border: 1px solid var(--color-border);\n padding: 0.75rem 1rem;\n text-align: left;\n }\n .content th { background: var(--color-bg-alt); font-weight: 600; }\n .content img { max-width: 100%; height: auto; border-radius: 4px; display: block; }\n .content img[alt*=\"Logo\"] { max-width: 200px; display: block; margin: 1rem 0; }\n .content img[alt*=\"Architecture\"] { max-width: 600px; }\n .content img[alt*=\"Benchmark\"] { max-width: 680px; }\n .content hr { border: none; border-top: 1px solid var(--color-border); margin: 2rem 0; }\n .content .ox-api-controls {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 0.5rem;\n margin: 0 0 1rem;\n }\n .content .ox-api-controls__button {\n appearance: none;\n border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);\n background: color-mix(in srgb, var(--color-bg-alt) 82%, var(--color-bg));\n padding: 0.4rem 0.7rem;\n border-radius: 4px;\n color: color-mix(in srgb, var(--color-text) 82%, var(--color-text-muted));\n font-family: var(--font-mono);\n font-size: 0.78rem;\n font-weight: 600;\n line-height: 1.4;\n cursor: pointer;\n }\n .content .ox-api-controls__button:hover {\n color: var(--color-primary);\n border-color: color-mix(in srgb, var(--color-primary) 38%, var(--color-border));\n background: color-mix(in srgb, var(--color-bg-alt) 68%, var(--color-primary) 6%);\n }\n .content .ox-api-entry,\n .content .ox-api-module {\n margin: 0;\n border: 0;\n border-top: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);\n border-radius: 0;\n background: transparent;\n overflow: visible;\n }\n .content .ox-api-entry:last-child,\n .content .ox-api-module:last-child {\n border-bottom: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);\n }\n .content .ox-api-entry summary,\n .content .ox-api-module summary {\n list-style: none;\n cursor: pointer;\n padding: 1rem 0;\n position: relative;\n }\n .content .ox-api-entry summary::-webkit-details-marker,\n .content .ox-api-module summary::-webkit-details-marker { display: none; }\n .content .ox-api-entry summary {\n display: grid;\n grid-template-columns: var(--octc-api-kind-width, 6.5rem) minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.95rem;\n }\n .content .ox-api-entry summary::after,\n .content .ox-api-module summary::after {\n content: \"+\";\n align-self: center;\n color: var(--color-text-muted);\n font-family: var(--font-mono);\n font-size: 0.95rem;\n font-weight: 600;\n line-height: 1;\n }\n .content .ox-api-entry[open] summary::after,\n .content .ox-api-module[open] summary::after {\n content: \"\u2212\";\n color: var(--color-primary);\n }\n .content .ox-api-entry[open] summary,\n .content .ox-api-module[open] summary {\n border-bottom: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);\n }\n .content .ox-api-entry__kind,\n .content .ox-api-module__kind {\n display: block;\n width: var(--octc-api-kind-width, 6.5rem);\n padding: 0.3rem 0 0;\n background: transparent;\n border: 0;\n font-family: var(--font-mono);\n font-size: 0.76rem;\n font-weight: 600;\n letter-spacing: 0.01em;\n text-align: left;\n white-space: nowrap;\n color: var(--color-text-muted);\n }\n .content .ox-api-module__count {\n display: inline-flex;\n align-items: center;\n padding: 0.2rem 0.48rem;\n border-radius: 4px;\n background: color-mix(in srgb, var(--color-bg-alt) 84%, var(--color-primary) 8%);\n border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);\n color: var(--color-text-muted);\n font-family: var(--font-mono);\n font-size: 0.72rem;\n font-weight: 600;\n letter-spacing: 0.03em;\n white-space: nowrap;\n }\n .content .ox-api-entry__name {\n display: block;\n font-family: var(--font-mono);\n font-size: 0.95rem;\n font-weight: 600;\n line-height: 1.55;\n }\n .content .ox-api-entry__signature,\n .content .ox-api-module__signature {\n display: block;\n width: 100%;\n min-width: 0;\n font-family: var(--font-mono);\n font-size: 0.95rem;\n line-height: 1.55;\n white-space: nowrap;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n }\n .content .ox-api-entry__description {\n display: block;\n color: color-mix(in srgb, var(--color-text) 78%, var(--color-text-muted));\n font-size: 0.9rem;\n line-height: 1.6;\n max-width: 72ch;\n }\n .content .ox-api-entry__summary-main {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 0.55rem;\n }\n .content .ox-api-entry__name,\n .content .ox-api-entry__signature,\n .content .ox-api-module__name,\n .content .ox-api-module__signature {\n background: transparent;\n padding: 0;\n border-radius: 0;\n word-break: normal;\n }\n .content code.shiki-inline.ox-api-entry__signature--highlighted,\n .content code.shiki-inline.ox-api-module__signature--highlighted {\n display: block;\n width: 100%;\n max-width: 100%;\n background: linear-gradient(\n 180deg,\n var(--color-code-bg-top) 0,\n var(--color-code-bg) 2.75rem\n ) !important;\n border: 1px solid var(--color-code-frame-border) !important;\n padding: 0.55rem 0.7rem !important;\n border-radius: 4px !important;\n white-space: nowrap;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n }\n .content code.shiki-inline.ox-api-entry__signature--highlighted .line,\n .content code.shiki-inline.ox-api-module__signature--highlighted .line {\n display: block;\n width: max-content;\n min-width: 100%;\n }\n .content .ox-api-entry__body,\n .content .ox-api-module__body { padding: 0.7rem 0 1.9rem; }\n .content .ox-api-entry__body {\n margin-left: calc(var(--octc-api-kind-width, 6.5rem) + 0.95rem);\n margin-top: 0.7rem;\n padding: 1.45rem 1rem 2.1rem 1.1rem;\n border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);\n border-radius: 4px;\n background: color-mix(in srgb, var(--color-bg-alt) 68%, transparent);\n }\n .content .ox-api-entry__body > :first-child { margin-top: 0; }\n .content .ox-api-entry__body > :last-child { margin-bottom: 0; }\n .content .ox-api-entry[open] summary {\n padding-bottom: 0.35rem;\n }\n .content .ox-api-entry__section {\n display: grid;\n grid-template-columns: 6.5rem minmax(0, 1fr);\n gap: 0.2rem 1.25rem;\n align-items: start;\n margin-top: 1rem;\n padding-top: 1rem;\n border-top: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);\n }\n .content .ox-api-entry__section h4 {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 0.25rem;\n font-family: var(--font-mono);\n font-size: 0.78rem;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--color-text-muted);\n }\n .content .ox-api-entry__section > :not(h4) { min-width: 0; }\n .content .ox-api-entry__source {\n margin: 0 0 0.15rem;\n font-family: var(--font-mono);\n font-size: 0.78rem;\n color: var(--color-text-muted);\n }\n .content .ox-api-entry__source a {\n color: inherit;\n text-decoration-color: color-mix(in srgb, var(--color-text-muted) 38%, transparent);\n }\n .content .ox-api-entry__tags,\n .content .ox-api-module__list {\n list-style: none;\n padding-left: 0;\n margin: 0;\n }\n .content .ox-api-entry__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 0.6rem;\n }\n .content .ox-api-entry__tags li {\n display: inline-flex;\n align-items: center;\n gap: 0.45rem;\n padding: 0.4rem 0.55rem;\n border: 1px solid color-mix(in srgb, var(--color-border) 80%, transparent);\n border-radius: 4px;\n background: color-mix(in srgb, var(--color-bg-alt) 72%, transparent);\n }\n .content .ox-api-module__list li {\n display: grid;\n grid-template-columns: var(--octc-api-kind-width, 6.5rem) minmax(0, 1fr);\n align-items: start;\n gap: 1rem;\n padding: 0.85rem 0;\n border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);\n }\n .content .ox-api-module__list li:first-child { border-top: none; }\n .content .ox-api-entry__tag-name,\n .content .ox-api-module__title { font-weight: 700; }\n .content .ox-api-entry__tag-name {\n color: var(--color-primary);\n font-family: var(--font-mono);\n font-size: 0.74rem;\n }\n .content .ox-api-entry__tag-value {\n color: var(--color-text);\n font-size: 0.84rem;\n line-height: 1.45;\n }\n .content .ox-api-module summary {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 0.9rem;\n }\n .content .ox-api-module__body { padding-top: 0.15rem; }\n .content .ox-api-module__item { min-width: 0; }\n .content .ox-api-module__link {\n display: block;\n text-decoration: none;\n }\n .content .ox-api-module__link:hover { text-decoration: none; }\n .content .ox-api-module__name,\n .content .ox-api-module__signature {\n display: block;\n font-family: var(--font-mono);\n font-size: 0.91rem;\n line-height: 1.55;\n color: var(--color-text);\n }\n .content .ox-api-module__summary {\n display: block;\n margin-top: 0.45rem;\n color: color-mix(in srgb, var(--color-text) 76%, var(--color-text-muted));\n font-size: 0.88rem;\n line-height: 1.55;\n }\n .content .ox-api-entry__section--examples pre {\n margin: 0;\n border: 1px solid var(--color-code-frame-border);\n border-radius: 4px;\n }\n .content .ox-api-entry__params {\n list-style: none;\n padding: 0;\n margin: 0;\n }\n .content .ox-api-entry__param {\n padding: 0.8rem 0;\n border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);\n }\n .content .ox-api-entry__param:first-child {\n padding-top: 0;\n border-top: 0;\n }\n .content .ox-api-entry__param-heading {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.5rem;\n }\n .content .ox-api-entry__param-name,\n .content .ox-api-entry__return-type {\n font-family: var(--font-mono);\n font-size: 0.84rem;\n font-weight: 600;\n color: var(--color-text);\n background: color-mix(in srgb, var(--color-bg-alt) 84%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);\n padding: 0.28rem 0.42rem;\n border-radius: 4px;\n }\n .content .ox-api-entry__param-type {\n font-family: var(--font-mono);\n font-size: 0.78rem;\n color: var(--color-code-text);\n background: color-mix(in srgb, var(--color-code-bg) 96%, #243556);\n border: 1px solid color-mix(in srgb, var(--color-code-bg) 82%, var(--color-border));\n padding: 0.26rem 0.42rem;\n border-radius: 4px;\n }\n .content .ox-api-entry__param-description,\n .content .ox-api-entry__return-description {\n margin: 0.55rem 0 0;\n font-size: 0.88rem;\n line-height: 1.6;\n color: color-mix(in srgb, var(--color-text) 78%, var(--color-text-muted));\n }\n .content .ox-api-entry__return { margin: 0; }\n\n /* Responsive */\n @media (max-width: 768px) {\n .menu-toggle { display: block; }\n .sidebar {\n transform: translateX(-100%);\n z-index: 99;\n width: 280px;\n }\n .sidebar--entry { display: block; }\n .sidebar.open { transform: translateX(0); }\n .main { margin-left: 0; padding: 1rem 0.75rem; }\n .content { padding: 0 0.25rem; }\n .content h1 { font-size: 1.5rem; line-height: 1.3; margin-bottom: 0.75rem; }\n .content h2 { font-size: 1.2rem; margin-top: 2rem; }\n .content h3 { font-size: 1.1rem; }\n .content p { font-size: 0.9375rem; margin-bottom: 0.875rem; }\n .content ul, .content ol { padding-left: 1.25rem; font-size: 0.9375rem; }\n .content pre {\n padding: 0.75rem;\n font-size: 0.75rem;\n margin: 1rem -0.75rem;\n border-radius: 0;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .content .ox-api-controls {\n justify-content: flex-start;\n gap: 0.75rem;\n }\n .content .ox-api-entry__body {\n margin-left: 0;\n margin-top: 0.55rem;\n padding: 1.2rem 0 1.7rem;\n border-left: 0;\n border-right: 0;\n border-bottom: 0;\n border-radius: 0;\n background: transparent;\n }\n .content .ox-api-entry__section {\n grid-template-columns: 1fr;\n gap: 0.5rem;\n }\n .content .ox-api-entry__tags li,\n .content .ox-api-module__list li,\n .content .ox-api-module summary {\n grid-template-columns: 1fr;\n }\n .content .ox-api-entry__signature { width: 100%; }\n .content pre.ox-code-block .line {\n margin: 0 -0.75rem;\n padding: 0 0.75rem;\n }\n .content code { font-size: 0.8125em; }\n .content table {\n display: block;\n width: max-content;\n min-width: 100%;\n max-width: calc(100vw - 1.5rem);\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n font-size: 0.8125rem;\n margin: 1rem 0;\n border-collapse: separate;\n border-spacing: 0;\n }\n .content th, .content td { padding: 0.5rem 0.75rem; white-space: nowrap; vertical-align: top; }\n .content img { margin: 1rem 0; }\n .content img[alt*=\"Logo\"] { max-width: 150px; }\n .content img[alt*=\"Architecture\"] { max-width: 100%; }\n .content img[alt*=\"Benchmark\"] { max-width: 100%; }\n .content blockquote { padding: 0.5rem 0.75rem; margin: 1rem 0; font-size: 0.9375rem; }\n .header { padding: 0 1rem; }\n .header-title { font-size: 1rem; }\n .header-title:not(.header-title--logo-only) img { width: 24px; height: 24px; }\n .header-title--logo-only .header-logo { width: 152px; height: auto; }\n .overlay {\n display: none;\n position: fixed;\n inset: 0;\n background: transparent;\n z-index: 98;\n }\n .overlay.open { display: block; }\n }\n\n /* Extra small devices */\n @media (max-width: 480px) {\n .main { padding: 0.75rem 0.5rem; }\n .content h1 { font-size: 1.35rem; }\n .content pre { font-size: 0.6875rem; padding: 0.625rem; }\n .content table { max-width: calc(100vw - 1rem); font-size: 0.75rem; }\n .content th, .content td { padding: 0.375rem 0.5rem; }\n }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <button class=\"menu-toggle\" aria-label=\"Toggle menu\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\">\n <path d=\"M3 12h18M3 6h18M3 18h18\"/>\n </svg>\n </button>\n <a href=\"{{base}}index.html\" class=\"header-title\">\n <img src=\"{{base}}logo.svg\" alt=\"\" width=\"28\" height=\"28\" style=\"margin-right: 8px; vertical-align: middle;\" />\n {{siteName}}\n </a>\n <div class=\"header-actions\">\n <button class=\"search-button\" aria-label=\"Search\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>\n </svg>\n <span>Search</span>\n <kbd>/</kbd>\n </button>\n <button class=\"theme-toggle\" aria-label=\"Toggle theme\">\n <svg class=\"icon-sun\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/>\n </svg>\n <svg class=\"icon-moon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>\n </svg>\n </button>\n </div>\n </header>\n <div class=\"search-modal-overlay\">\n <div class=\"search-modal\">\n <div class=\"search-header\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>\n </svg>\n <input type=\"text\" class=\"search-input\" placeholder=\"Search documentation...\" />\n <button class=\"search-close\">Esc</button>\n </div>\n <div class=\"search-results\"></div>\n <div class=\"search-footer\">\n <span><kbd>\u2191</kbd><kbd>\u2193</kbd> to navigate</span>\n <span><kbd>Enter</kbd> to select</span>\n <span><kbd>Esc</kbd> to close</span>\n </div>\n </div>\n </div>\n <div class=\"overlay\"></div>\n <div class=\"layout\">\n <aside class=\"sidebar{{#entryPage}} sidebar--entry{{/entryPage}}\">\n <nav>\n{{navigation}}\n </nav>\n </aside>\n <main class=\"main\">\n <article class=\"content\">\n{{content}}\n </article>\n </main>\n{{#hasToc}}\n <aside class=\"toc\">\n <div class=\"toc-title\">On this page</div>\n <ul class=\"toc-list\">\n{{toc}}\n </ul>\n </aside>\n{{/hasToc}}\n </div>\n <script>\n // Menu toggle\n const toggle = document.querySelector('.menu-toggle');\n const sidebar = document.querySelector('.sidebar');\n const overlay = document.querySelector('.overlay');\n if (toggle && sidebar && overlay) {\n const close = () => { sidebar.classList.remove('open'); overlay.classList.remove('open'); };\n toggle.addEventListener('click', () => {\n sidebar.classList.toggle('open');\n overlay.classList.toggle('open');\n });\n overlay.addEventListener('click', close);\n sidebar.querySelectorAll('a').forEach(a => a.addEventListener('click', close));\n }\n\n // Theme toggle\n const themeToggle = document.querySelector('.theme-toggle');\n const getPreferredTheme = () => {\n const stored = localStorage.getItem('theme');\n if (stored) return stored;\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n };\n const setTheme = (theme) => {\n document.documentElement.setAttribute('data-theme', theme);\n localStorage.setItem('theme', theme);\n };\n // Initialize theme\n setTheme(getPreferredTheme());\n if (themeToggle) {\n themeToggle.addEventListener('click', () => {\n const current = document.documentElement.getAttribute('data-theme') || getPreferredTheme();\n setTheme(current === 'dark' ? 'light' : 'dark');\n });\n }\n\n document.querySelectorAll('.ox-api-controls').forEach((controls) => {\n const targetSelector = controls.getAttribute('data-ox-api-target');\n if (!targetSelector) return;\n\n controls.querySelectorAll('[data-ox-api-toggle]').forEach((button) => {\n button.addEventListener('click', () => {\n const shouldOpen = button.getAttribute('data-ox-api-toggle') === 'expand';\n document.querySelectorAll(targetSelector).forEach((entry) => {\n if (entry instanceof HTMLDetailsElement) {\n entry.open = shouldOpen;\n }\n });\n });\n });\n });\n\n // Search functionality\n const searchButton = document.querySelector('.search-button');\n const searchOverlay = document.querySelector('.search-modal-overlay');\n const searchInput = document.querySelector('.search-input');\n const searchResults = document.querySelector('.search-results');\n const searchClose = document.querySelector('.search-close');\n let searchIndex = null;\n let selectedIndex = 0;\n let results = [];\n\n const openSearch = () => {\n searchOverlay.classList.add('open');\n searchInput.focus();\n };\n const closeSearch = () => {\n searchOverlay.classList.remove('open');\n searchInput.value = '';\n searchResults.innerHTML = '';\n selectedIndex = 0;\n results = [];\n };\n\n // Load search index\n const loadSearchIndex = async () => {\n if (searchIndex) return;\n try {\n const res = await fetch('{{base}}search-index.json');\n searchIndex = await res.json();\n } catch (e) {\n console.warn('Failed to load search index:', e);\n }\n };\n\n const parseScopedQuery = (query) => {\n const scopes = [];\n const terms = [];\n for (const part of query.trim().split(/\\s+/).filter(Boolean)) {\n if (part.startsWith('@') && part.length > 1) {\n scopes.push(part.slice(1).toLowerCase());\n } else {\n terms.push(part);\n }\n }\n return { text: terms.join(' ').trim(), scopes: [...new Set(scopes)] };\n };\n\n const getScopesForDoc = (doc) => {\n const source = (doc.id || doc.url || '').replace(/^\\/+/, '').toLowerCase();\n const segments = source.split('/').filter(Boolean);\n if (segments.length <= 1) return [];\n\n const scopes = [];\n let current = '';\n for (const segment of segments.slice(0, -1)) {\n current = current ? current + '/' + segment : segment;\n scopes.push(current);\n }\n return scopes;\n };\n\n const matchesScopes = (doc, scopes) => {\n if (!scopes.length) return true;\n const docScopes = new Set(getScopesForDoc(doc));\n return scopes.some((scope) => docScopes.has(scope));\n };\n\n // Tokenize query\n const tokenize = (text) => {\n const tokens = [];\n let current = '';\n for (const char of text) {\n const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);\n if (isCjk) {\n if (current) { tokens.push(current.toLowerCase()); current = ''; }\n tokens.push(char);\n } else if (/[a-zA-Z0-9_]/.test(char)) {\n current += char;\n } else if (current) {\n tokens.push(current.toLowerCase());\n current = '';\n }\n }\n if (current) tokens.push(current.toLowerCase());\n return tokens;\n };\n\n // Perform search\n const performSearch = async (query) => {\n await loadSearchIndex();\n if (!searchIndex) {\n searchResults.innerHTML = '<div class=\"search-empty\">Search index not available</div>';\n return;\n }\n\n const parsedQuery = parseScopedQuery(query);\n if (!parsedQuery.text && parsedQuery.scopes.length === 0) {\n searchResults.innerHTML = '';\n results = [];\n return;\n }\n\n const tokens = tokenize(parsedQuery.text);\n const k1 = 1.2, b = 0.75;\n const docScores = new Map();\n\n if (!tokens.length) {\n searchIndex.documents.forEach((doc, docIdx) => {\n if (matchesScopes(doc, parsedQuery.scopes)) {\n docScores.set(docIdx, { score: 0, matches: new Set() });\n }\n });\n }\n\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n const isLast = i === tokens.length - 1;\n let matchingTerms = [];\n if (isLast && token.length >= 2) {\n matchingTerms = Object.keys(searchIndex.index).filter(t => t.startsWith(token));\n } else if (searchIndex.index[token]) {\n matchingTerms = [token];\n }\n\n for (const term of matchingTerms) {\n const postings = searchIndex.index[term] || [];\n const df = searchIndex.df[term] || 1;\n const idf = Math.log((searchIndex.doc_count - df + 0.5) / (df + 0.5) + 1.0);\n\n for (const posting of postings) {\n const doc = searchIndex.documents[posting.doc_idx];\n if (!doc) continue;\n if (!matchesScopes(doc, parsedQuery.scopes)) continue;\n const boost = posting.field === 'Title' ? 10 : posting.field === 'Heading' ? 5 : 1;\n const tf = posting.tf;\n const docLen = doc.body.length;\n const score = idf * ((tf * (k1 + 1)) / (tf + k1 * (1 - b + b * docLen / searchIndex.avg_dl))) * boost;\n\n if (!docScores.has(posting.doc_idx)) {\n docScores.set(posting.doc_idx, { score: 0, matches: new Set() });\n }\n const entry = docScores.get(posting.doc_idx);\n entry.score += score;\n entry.matches.add(term);\n }\n }\n }\n\n results = Array.from(docScores.entries())\n .map(([docIdx, data]) => {\n const doc = searchIndex.documents[docIdx];\n const scopes = getScopesForDoc(doc);\n let snippet = '';\n if (doc.body) {\n const bodyLower = doc.body.toLowerCase();\n let firstPos = -1;\n for (const match of data.matches) {\n const pos = bodyLower.indexOf(match);\n if (pos !== -1 && (firstPos === -1 || pos < firstPos)) firstPos = pos;\n }\n const start = firstPos === -1 ? 0 : Math.max(0, firstPos - 50);\n const end = Math.min(doc.body.length, start + 150);\n snippet = doc.body.slice(start, end);\n if (start > 0) snippet = '...' + snippet;\n if (end < doc.body.length) snippet += '...';\n }\n return { ...doc, score: data.score, scopes, snippet };\n })\n .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))\n .slice(0, 10);\n\n selectedIndex = 0;\n renderResults();\n };\n\n const renderResults = () => {\n if (!results.length) {\n searchResults.innerHTML = '<div class=\"search-empty\">No results found</div>';\n return;\n }\n searchResults.innerHTML = results.map((r, i) =>\n '<a href=\"' + r.url + '\" class=\"search-result' + (i === selectedIndex ? ' selected' : '') + '\">' +\n '<div class=\"search-result-title\">' + r.title + (r.scopes?.length ? '<span class=\"search-result-scope\">@' + r.scopes[0] + '</span>' : '') + '</div>' +\n (r.snippet ? '<div class=\"search-result-snippet\">' + r.snippet + '</div>' : '') +\n '</a>'\n ).join('');\n };\n\n // Event listeners\n if (searchButton) searchButton.addEventListener('click', openSearch);\n if (searchClose) searchClose.addEventListener('click', closeSearch);\n if (searchOverlay) searchOverlay.addEventListener('click', (e) => { if (e.target === searchOverlay) closeSearch(); });\n\n let searchTimeout = null;\n if (searchInput) {\n searchInput.addEventListener('input', () => {\n if (searchTimeout) clearTimeout(searchTimeout);\n searchTimeout = setTimeout(() => performSearch(searchInput.value), 150);\n });\n searchInput.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') closeSearch();\n else if (e.key === 'ArrowDown') {\n e.preventDefault();\n if (selectedIndex < results.length - 1) { selectedIndex++; renderResults(); }\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n if (selectedIndex > 0) { selectedIndex--; renderResults(); }\n } else if (e.key === 'Enter' && results[selectedIndex]) {\n e.preventDefault();\n window.location.href = results[selectedIndex].url;\n }\n });\n }\n\n // Global keyboard shortcut (/ or Cmd+K)\n document.addEventListener('keydown', (e) => {\n if ((e.key === '/' && !(e.target instanceof HTMLInputElement)) ||\n ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k')) {\n e.preventDefault();\n openSearch();\n }\n });\n </script>\n</body>\n</html>";
1167
+ /**
1168
+ * Resolves SSG options with defaults.
1169
+ */
1170
+ declare function resolveSsgOptions(ssg: SsgOptions | boolean | undefined): ResolvedSsgOptions;
1171
+ /**
1172
+ * Builds all markdown files to static HTML.
1173
+ */
1174
+ declare function buildSsg(options: ResolvedOptions, root: string): Promise<{
1175
+ files: string[];
1176
+ errors: string[];
1177
+ }>;
1178
+ //#endregion
1179
+ //#region src/search.d.ts
1180
+ /**
1181
+ * Resolves search options with defaults.
1182
+ */
1183
+ declare function resolveSearchOptions(options: SearchOptions | boolean | undefined): ResolvedSearchOptions;
1184
+ /**
1185
+ * Builds the search index from Markdown files.
1186
+ */
1187
+ declare function buildSearchIndex(srcDir: string, base: string): Promise<string>;
1188
+ /**
1189
+ * Writes the search index to a file.
1190
+ */
1191
+ declare function writeSearchIndex(indexJson: string, outDir: string): Promise<void>;
1192
+ //#endregion
1193
+ //#region src/jsx-runtime.d.ts
1194
+ /**
1195
+ * Custom JSX Runtime for Static HTML Generation
1196
+ *
1197
+ * This module provides a JSX runtime that outputs static HTML strings.
1198
+ * No React, no hydration, no client-side JavaScript - just pure HTML.
1199
+ *
1200
+ * @example
1201
+ * ```tsx
1202
+ * // tsconfig.json or vite.config.ts
1203
+ * {
1204
+ * "compilerOptions": {
1205
+ * "jsx": "react-jsx",
1206
+ * "jsxImportSource": "@ox-content/vite-plugin"
1207
+ * }
1208
+ * }
1209
+ *
1210
+ * // MyComponent.tsx
1211
+ * export function Hero({ title }: { title: string }) {
1212
+ * return (
1213
+ * <section class="hero">
1214
+ * <h1>{title}</h1>
1215
+ * </section>
1216
+ * );
1217
+ * }
1218
+ * ```
1219
+ */
1220
+ /**
1221
+ * JSX element type - either a string (intrinsic) or a function component.
1222
+ */
1223
+ type JSXElementType = string | ((props: Record<string, unknown>) => JSXNode);
1224
+ /**
1225
+ * Valid JSX child types.
1226
+ */
1227
+ type JSXChild = string | number | boolean | null | undefined | JSXNode | JSXChild[];
1228
+ /**
1229
+ * JSX node - the result of JSX expressions.
1230
+ */
1231
+ interface JSXNode {
1232
+ __html: string;
1233
+ }
1234
+ /**
1235
+ * Props with children.
1236
+ */
1237
+ interface JSXProps {
1238
+ children?: JSXChild;
1239
+ [key: string]: unknown;
1240
+ }
1241
+ /**
1242
+ * Creates a JSX element.
1243
+ * This is the core function called by the JSX transform.
1244
+ */
1245
+ declare function jsx(type: JSXElementType, props: JSXProps, _key?: string): JSXNode;
1246
+ /**
1247
+ * Creates a JSX element with static children.
1248
+ * Called by the JSX transform for elements with multiple children.
1249
+ */
1250
+ declare function jsxs(type: JSXElementType, props: JSXProps, key?: string): JSXNode;
1251
+ /**
1252
+ * Fragment component - renders children without a wrapper element.
1253
+ */
1254
+ declare function Fragment({
1255
+ children
1256
+ }: {
1257
+ children?: JSXChild;
1258
+ }): JSXNode;
1259
+ /**
1260
+ * Renders a JSX node to an HTML string.
1261
+ */
1262
+ declare function renderToString(node: JSXNode): string;
1263
+ /**
1264
+ * Creates raw HTML without escaping.
1265
+ * Use with caution - only for trusted content.
1266
+ *
1267
+ * @example
1268
+ * ```tsx
1269
+ * <div>{raw('<strong>Bold</strong>')}</div>
1270
+ * ```
1271
+ */
1272
+ declare function raw(html: string): JSXNode;
1273
+ /**
1274
+ * Conditionally renders content.
1275
+ *
1276
+ * @example
1277
+ * ```tsx
1278
+ * {when(isLoggedIn, <UserMenu />)}
1279
+ * ```
1280
+ */
1281
+ declare function when(condition: boolean, content: JSXNode): JSXNode;
1282
+ /**
1283
+ * Maps over an array and renders each item.
1284
+ *
1285
+ * @example
1286
+ * ```tsx
1287
+ * {each(items, (item) => <li>{item.name}</li>)}
1288
+ * ```
1289
+ */
1290
+ declare function each<T>(items: T[], render: (item: T, index: number) => JSXNode): JSXNode;
1291
+ //#endregion
1292
+ //#region src/page-context.d.ts
1293
+ /**
1294
+ * Base page props available for all pages.
1295
+ */
1296
+ interface BasePageProps {
1297
+ /** Page title from frontmatter or first heading */
1298
+ title: string;
1299
+ /** Page description from frontmatter */
1300
+ description?: string;
1301
+ /** Rendered HTML content */
1302
+ html: string;
1303
+ /** Table of contents entries */
1304
+ toc: TocEntry[];
1305
+ /** Source file path (relative to docs root) */
1306
+ path: string;
1307
+ /** Output URL path */
1308
+ url: string;
1309
+ /** Raw frontmatter object */
1310
+ frontmatter: Record<string, unknown>;
1311
+ /** Layout name from frontmatter */
1312
+ layout?: string;
1313
+ }
1314
+ /**
1315
+ * Extended page props with custom frontmatter.
1316
+ */
1317
+ type PageProps<T extends Record<string, unknown> = Record<string, unknown>> = BasePageProps & {
1318
+ /** Custom frontmatter fields */frontmatter: T & Record<string, unknown>;
1319
+ };
1320
+ /**
1321
+ * Site-wide configuration available in context.
1322
+ */
1323
+ interface SiteConfig {
1324
+ /** Site name */
1325
+ name: string;
1326
+ /** Base URL path */
1327
+ base: string;
1328
+ /** All pages in the site */
1329
+ pages: BasePageProps[];
1330
+ /** Navigation groups */
1331
+ nav: NavGroup[];
1332
+ }
1333
+ /**
1334
+ * Navigation group.
1335
+ */
1336
+ interface NavGroup {
1337
+ title: string;
1338
+ items: NavItem[];
1339
+ }
1340
+ /**
1341
+ * Navigation item.
1342
+ */
1343
+ interface NavItem {
1344
+ title: string;
1345
+ path: string;
1346
+ href: string;
1347
+ }
1348
+ /**
1349
+ * Complete render context.
1350
+ */
1351
+ interface RenderContext<T extends Record<string, unknown> = Record<string, unknown>> {
1352
+ /** Current page props */
1353
+ page: PageProps<T>;
1354
+ /** Site configuration */
1355
+ site: SiteConfig;
1356
+ }
1357
+ /**
1358
+ * Sets the current render context.
1359
+ * Called internally during page rendering.
1360
+ * @internal
1361
+ */
1362
+ declare function setRenderContext(ctx: RenderContext): void;
1363
+ /**
1364
+ * Clears the current render context.
1365
+ * Called internally after page rendering.
1366
+ * @internal
1367
+ */
1368
+ declare function clearRenderContext(): void;
1369
+ /**
1370
+ * Gets the current page props.
1371
+ *
1372
+ * @returns The current page props
1373
+ * @throws Error if called outside of a render context
1374
+ *
1375
+ * @example
1376
+ * ```tsx
1377
+ * function PageTitle() {
1378
+ * const page = usePageProps();
1379
+ * return <h1>{page.title}</h1>;
1380
+ * }
1381
+ * ```
1382
+ */
1383
+ declare function usePageProps<T extends Record<string, unknown> = Record<string, unknown>>(): PageProps<T>;
1384
+ /**
1385
+ * Gets the site configuration.
1386
+ *
1387
+ * @returns The site configuration
1388
+ * @throws Error if called outside of a render context
1389
+ *
1390
+ * @example
1391
+ * ```tsx
1392
+ * function SiteHeader() {
1393
+ * const site = useSiteConfig();
1394
+ * return <header>{site.name}</header>;
1395
+ * }
1396
+ * ```
1397
+ */
1398
+ declare function useSiteConfig(): SiteConfig;
1399
+ /**
1400
+ * Gets the full render context.
1401
+ *
1402
+ * @returns The complete render context
1403
+ * @throws Error if called outside of a render context
1404
+ *
1405
+ * @example
1406
+ * ```tsx
1407
+ * function Layout({ children }) {
1408
+ * const ctx = useRenderContext();
1409
+ * return (
1410
+ * <html>
1411
+ * <head><title>{ctx.page.title} - {ctx.site.name}</title></head>
1412
+ * <body>{children}</body>
1413
+ * </html>
1414
+ * );
1415
+ * }
1416
+ * ```
1417
+ */
1418
+ declare function useRenderContext<T extends Record<string, unknown> = Record<string, unknown>>(): RenderContext<T>;
1419
+ /**
1420
+ * Gets the navigation groups.
1421
+ *
1422
+ * @example
1423
+ * ```tsx
1424
+ * function Sidebar() {
1425
+ * const nav = useNav();
1426
+ * return (
1427
+ * <nav>
1428
+ * {each(nav, (group) => (
1429
+ * <div>
1430
+ * <h3>{group.title}</h3>
1431
+ * <ul>
1432
+ * {each(group.items, (item) => (
1433
+ * <li><a href={item.href}>{item.title}</a></li>
1434
+ * ))}
1435
+ * </ul>
1436
+ * </div>
1437
+ * ))}
1438
+ * </nav>
1439
+ * );
1440
+ * }
1441
+ * ```
1442
+ */
1443
+ declare function useNav(): NavGroup[];
1444
+ /**
1445
+ * Checks if the given path is the current page.
1446
+ *
1447
+ * @example
1448
+ * ```tsx
1449
+ * function NavLink({ href, children }) {
1450
+ * const isActive = useIsActive(href);
1451
+ * return <a href={href} class={isActive ? 'active' : ''}>{children}</a>;
1452
+ * }
1453
+ * ```
1454
+ */
1455
+ declare function useIsActive(path: string): boolean;
1456
+ /**
1457
+ * Schema for frontmatter type generation.
1458
+ */
1459
+ interface FrontmatterSchema {
1460
+ /** Field name */
1461
+ name: string;
1462
+ /** TypeScript type */
1463
+ type: string;
1464
+ /** Whether the field is optional */
1465
+ optional: boolean;
1466
+ /** JSDoc description */
1467
+ description?: string;
1468
+ }
1469
+ /**
1470
+ * Infers TypeScript types from frontmatter values.
1471
+ */
1472
+ declare function inferType(value: unknown): string;
1473
+ /**
1474
+ * Generates TypeScript interface from frontmatter samples.
1475
+ */
1476
+ declare function generateFrontmatterTypes(samples: Record<string, unknown>[], interfaceName?: string): string;
1477
+ //#endregion
1478
+ //#region src/theme-renderer.d.ts
1479
+ /**
1480
+ * Theme component type.
1481
+ */
1482
+ type ThemeComponent = (props: ThemeProps) => JSXNode;
1483
+ /**
1484
+ * Props passed to the theme component.
1485
+ */
1486
+ interface ThemeProps {
1487
+ /** Rendered page content as JSX */
1488
+ children: JSXNode;
1489
+ }
1490
+ /**
1491
+ * Page data for rendering.
1492
+ */
1493
+ interface PageData {
1494
+ /** Page title */
1495
+ title: string;
1496
+ /** Page description */
1497
+ description?: string;
1498
+ /** Rendered HTML content */
1499
+ html: string;
1500
+ /** Table of contents */
1501
+ toc: TocEntry[];
1502
+ /** Source file path */
1503
+ path: string;
1504
+ /** Output URL path */
1505
+ url: string;
1506
+ /** Frontmatter */
1507
+ frontmatter: Record<string, unknown>;
1508
+ /** Layout name */
1509
+ layout?: string;
1510
+ }
1511
+ /**
1512
+ * Theme render options.
1513
+ */
1514
+ interface ThemeRenderOptions {
1515
+ /** Theme component to use */
1516
+ theme: ThemeComponent;
1517
+ /** Site name */
1518
+ siteName: string;
1519
+ /** Base URL path */
1520
+ base: string;
1521
+ /** Navigation groups */
1522
+ nav: NavGroup[];
1523
+ /** All pages (for site context) */
1524
+ pages: PageData[];
1525
+ /** Output directory for type definitions */
1526
+ typesOutDir?: string;
1527
+ }
1528
+ /**
1529
+ * Renders a page using the theme component.
1530
+ *
1531
+ * @param page - Page data to render
1532
+ * @param options - Theme render options
1533
+ * @returns Rendered HTML string
1534
+ */
1535
+ declare function renderPage(page: PageData, options: ThemeRenderOptions): string;
1536
+ /**
1537
+ * Renders all pages and generates type definitions.
1538
+ *
1539
+ * @param pages - All pages to render
1540
+ * @param options - Theme render options
1541
+ * @returns Map of output paths to rendered HTML
1542
+ */
1543
+ declare function renderAllPages(pages: PageData[], options: ThemeRenderOptions): Promise<Map<string, string>>;
1544
+ /**
1545
+ * Generates TypeScript type definitions from page frontmatter.
1546
+ *
1547
+ * @param pages - All pages
1548
+ * @param outDir - Output directory for types
1549
+ */
1550
+ declare function generateTypes(pages: PageData[], outDir: string): Promise<void>;
1551
+ /**
1552
+ * Default theme component.
1553
+ * A minimal theme that renders page content with basic styling.
1554
+ */
1555
+ declare function DefaultTheme({
1556
+ children
1557
+ }: ThemeProps): JSXNode;
1558
+ /**
1559
+ * Creates a theme with layout switching support.
1560
+ *
1561
+ * @example
1562
+ * ```tsx
1563
+ * import { createTheme } from '@ox-content/vite-plugin';
1564
+ * import { DefaultLayout } from './layouts/Default';
1565
+ * import { EntryLayout } from './layouts/Entry';
1566
+ *
1567
+ * export default createTheme({
1568
+ * layouts: {
1569
+ * default: DefaultLayout,
1570
+ * entry: EntryLayout,
1571
+ * },
1572
+ * });
1573
+ * ```
1574
+ */
1575
+ declare function createTheme(config: {
1576
+ layouts: Record<string, ThemeComponent>;
1577
+ defaultLayout?: string;
1578
+ }): ThemeComponent;
1579
+ //#endregion
1580
+ //#region src/plugins/tabs.d.ts
1581
+ /**
1582
+ * Transform Tabs components in HTML.
1583
+ */
1584
+ declare function transformTabs(html: string): Promise<string>;
1585
+ /**
1586
+ * Generate dynamic CSS for :has() based tab switching.
1587
+ * This is needed because :has() selectors need unique IDs.
1588
+ */
1589
+ declare function generateTabsCSS(groupCount: number): string;
1590
+ //#endregion
1591
+ //#region src/plugins/youtube.d.ts
1592
+ /**
1593
+ * YouTube Plugin - Privacy-enhanced iframe embedding
1594
+ *
1595
+ * Transforms <YouTube> components into responsive iframe embeds
1596
+ * using youtube-nocookie.com for enhanced privacy.
1597
+ */
1598
+ interface YouTubeOptions {
1599
+ /** Use privacy-enhanced mode (youtube-nocookie.com). Default: true */
1600
+ privacyEnhanced?: boolean;
1601
+ /** Default aspect ratio. Default: "16/9" */
1602
+ aspectRatio?: string;
1603
+ /** Allow fullscreen. Default: true */
1604
+ allowFullscreen?: boolean;
1605
+ /** Lazy load iframe. Default: true */
1606
+ lazyLoad?: boolean;
1607
+ }
1608
+ /**
1609
+ * Extract YouTube video ID from various URL formats.
1610
+ */
1611
+ declare function extractVideoId(input: string): string | null;
1612
+ /**
1613
+ * Transform YouTube components in HTML.
1614
+ */
1615
+ declare function transformYouTube(html: string, options?: YouTubeOptions): Promise<string>;
1616
+ //#endregion
1617
+ //#region src/plugins/github.d.ts
1618
+ /**
1619
+ * GitHub Plugin - Repository card embedding
1620
+ *
1621
+ * Transforms <GitHub> components into static repository cards
1622
+ * by fetching data from GitHub API at build time.
1623
+ */
1624
+ interface GitHubRepoData {
1625
+ name: string;
1626
+ full_name: string;
1627
+ description: string | null;
1628
+ html_url: string;
1629
+ stargazers_count: number;
1630
+ forks_count: number;
1631
+ language: string | null;
1632
+ owner: {
1633
+ login: string;
1634
+ avatar_url: string;
1635
+ };
1636
+ }
1637
+ interface GitHubOptions {
1638
+ /** GitHub API token for higher rate limits. */
1639
+ token?: string;
1640
+ /** Cache fetched data. Default: true */
1641
+ cache?: boolean;
1642
+ /** Cache TTL in milliseconds. Default: 3600000 (1 hour) */
1643
+ cacheTTL?: number;
1644
+ }
1645
+ /**
1646
+ * Fetch repository data from GitHub API.
1647
+ */
1648
+ declare function fetchRepoData(repo: string, options: Required<GitHubOptions>): Promise<GitHubRepoData | null>;
1649
+ /**
1650
+ * Collect all GitHub repos from HTML for pre-fetching.
1651
+ */
1652
+ declare function collectGitHubRepos(html: string): Promise<string[]>;
1653
+ /**
1654
+ * Pre-fetch all GitHub repos data.
1655
+ */
1656
+ declare function prefetchGitHubRepos(repos: string[], options?: GitHubOptions): Promise<Map<string, GitHubRepoData | null>>;
1657
+ /**
1658
+ * Transform GitHub components in HTML.
1659
+ */
1660
+ declare function transformGitHub(html: string, repoDataMap?: Map<string, GitHubRepoData | null>, options?: GitHubOptions): Promise<string>;
1661
+ //#endregion
1662
+ //#region src/plugins/ogp.d.ts
1663
+ /**
1664
+ * OGP Card Plugin - Link card embedding
1665
+ *
1666
+ * Transforms <OgCard> components into static link preview cards
1667
+ * by fetching OGP metadata at build time.
1668
+ */
1669
+ interface OgpData {
1670
+ url: string;
1671
+ title: string;
1672
+ description?: string;
1673
+ image?: string;
1674
+ siteName?: string;
1675
+ favicon?: string;
1676
+ }
1677
+ interface OgpOptions {
1678
+ /** Request timeout in milliseconds. Default: 10000 */
1679
+ timeout?: number;
1680
+ /** Cache fetched data. Default: true */
1681
+ cache?: boolean;
1682
+ /** Cache TTL in milliseconds. Default: 3600000 (1 hour) */
1683
+ cacheTTL?: number;
1684
+ /** User agent for requests */
1685
+ userAgent?: string;
1686
+ }
1687
+ /**
1688
+ * Fetch OGP data for a URL.
1689
+ */
1690
+ declare function fetchOgpData(url: string, options: Required<OgpOptions>): Promise<OgpData | null>;
1691
+ /**
1692
+ * Collect all OGP URLs from HTML for pre-fetching.
1693
+ */
1694
+ declare function collectOgpUrls(html: string): Promise<string[]>;
1695
+ /**
1696
+ * Pre-fetch all OGP data.
1697
+ */
1698
+ declare function prefetchOgpData(urls: string[], options?: OgpOptions): Promise<Map<string, OgpData | null>>;
1699
+ /**
1700
+ * Transform OgCard components in HTML.
1701
+ */
1702
+ declare function transformOgp(html: string, ogpDataMap?: Map<string, OgpData | null>, options?: OgpOptions): Promise<string>;
1703
+ //#endregion
1704
+ //#region src/plugins/mermaid.d.ts
1705
+ /**
1706
+ * Mermaid Plugin - Native Rust renderer via NAPI
1707
+ *
1708
+ * Renders mermaid code blocks to SVG using the native Rust renderer
1709
+ * via NAPI. Delegates to the NAPI `transformMermaid` function which
1710
+ * extracts mermaid code blocks from HTML and renders them using mmdc.
1711
+ */
1712
+ interface MermaidOptions {
1713
+ /** Mermaid theme. Default: "neutral" */
1714
+ theme?: "default" | "dark" | "forest" | "neutral" | "base";
1715
+ }
1716
+ /**
1717
+ * Transforms mermaid code blocks in HTML to rendered SVG diagrams.
1718
+ * Uses the native Rust NAPI transformMermaid function.
1719
+ */
1720
+ declare function transformMermaidStatic(html: string, _options?: MermaidOptions): Promise<string>;
1721
+ /**
1722
+ * @deprecated No longer used. Mermaid rendering is now done at build time via NAPI.
1723
+ */
1724
+ declare const mermaidClientScript = "";
1725
+ //#endregion
1726
+ //#region src/plugins/index.d.ts
1727
+ /**
1728
+ * Transform all plugin components in HTML.
1729
+ * Call this during SSG build to process all plugins at once.
1730
+ */
1731
+ interface TransformAllOptions {
1732
+ tabs?: boolean;
1733
+ youtube?: boolean;
1734
+ github?: boolean;
1735
+ ogp?: boolean;
1736
+ mermaid?: boolean;
1737
+ githubToken?: string;
1738
+ }
1739
+ /**
1740
+ * Transform all enabled plugins in HTML content.
1741
+ */
1742
+ declare function transformAllPlugins(html: string, options?: TransformAllOptions): Promise<string>;
1743
+ //#endregion
1744
+ //#region src/island/parse.d.ts
1745
+ /**
1746
+ * Island Parser
1747
+ *
1748
+ * Detects <Island> components in HTML and transforms them
1749
+ * into hydration-ready elements with data attributes.
1750
+ */
1751
+ type LoadStrategy = "eager" | "idle" | "visible" | "media";
1752
+ interface IslandInfo {
1753
+ component: string;
1754
+ load: LoadStrategy;
1755
+ mediaQuery?: string;
1756
+ props: Record<string, unknown>;
1757
+ }
1758
+ interface ParseIslandsResult {
1759
+ html: string;
1760
+ islands: IslandInfo[];
1761
+ }
1762
+ /**
1763
+ * Transform Island components in HTML.
1764
+ *
1765
+ * Converts:
1766
+ * ```html
1767
+ * <Island load="visible">
1768
+ * <Counter initial={0} />
1769
+ * </Island>
1770
+ * ```
1771
+ *
1772
+ * To:
1773
+ * ```html
1774
+ * <div id="ox-island-0"
1775
+ * data-ox-island="Counter"
1776
+ * data-ox-load="visible"
1777
+ * data-ox-props='{"initial":0}'
1778
+ * class="ox-island">
1779
+ * <!-- fallback content -->
1780
+ * </div>
1781
+ * ```
1782
+ */
1783
+ declare function transformIslands(html: string): Promise<ParseIslandsResult>;
1784
+ /**
1785
+ * Check if HTML contains any Island components.
1786
+ */
1787
+ declare function hasIslands(html: string): boolean;
1788
+ /**
1789
+ * Extract island info without transforming HTML.
1790
+ * Useful for analysis/bundling purposes.
1791
+ */
1792
+ declare function extractIslandInfo(html: string): Promise<IslandInfo[]>;
1793
+ /**
1794
+ * Generate client-side hydration script.
1795
+ * This is a minimal script that imports and initializes islands.
1796
+ */
1797
+ declare function generateHydrationScript(components: string[]): string;
1798
+ //#endregion
1799
+ //#region src/og-image/types.d.ts
1800
+ /**
1801
+ * Type definitions for Chromium-based OG image generation.
1802
+ */
1803
+ /**
1804
+ * Props passed to OG image template functions.
1805
+ */
1806
+ interface OgImageTemplateProps {
1807
+ /** Page title */
1808
+ title: string;
1809
+ /** Page description */
1810
+ description?: string;
1811
+ /** Site name */
1812
+ siteName?: string;
1813
+ /** Author name */
1814
+ author?: string;
1815
+ /** Tags/categories */
1816
+ tags?: string[];
1817
+ /** Custom data from frontmatter (arbitrary key-value pairs) */
1818
+ [key: string]: unknown;
1819
+ }
1820
+ /**
1821
+ * Template function that receives page metadata and returns an HTML string.
1822
+ */
1823
+ type OgImageTemplateFn = (props: OgImageTemplateProps) => string | Promise<string>;
1824
+ /**
1825
+ * OG image generation options (user-facing).
1826
+ */
1827
+ interface OgImageOptions$1 {
1828
+ /**
1829
+ * Path to a custom template file (.ts, .vue, .svelte, .tsx/.jsx).
1830
+ * - `.ts`: default-export a function `(props) => string`
1831
+ * - `.vue`: Vue SFC, rendered via SSR
1832
+ * - `.svelte`: Svelte SFC, rendered via SSR
1833
+ * - `.tsx`/`.jsx`: React Server Component, rendered via SSR
1834
+ * If not specified, the built-in default template is used.
1835
+ */
1836
+ template?: string;
1837
+ /**
1838
+ * Vue plugin to use for compiling `.vue` templates.
1839
+ * - `'vitejs'`: Use `@vue/compiler-sfc` (official, default)
1840
+ * - `'vizejs'`: Use `@vizejs/vite-plugin` (Rust-based)
1841
+ * @default 'vitejs'
1842
+ */
1843
+ vuePlugin?: "vitejs" | "vizejs";
1844
+ /**
1845
+ * Image width in pixels.
1846
+ * @default 1200
1847
+ */
1848
+ width?: number;
1849
+ /**
1850
+ * Image height in pixels.
1851
+ * @default 630
1852
+ */
1853
+ height?: number;
1854
+ /**
1855
+ * Enable content-hash based caching.
1856
+ * Skips rendering when content hasn't changed.
1857
+ * @default true
1858
+ */
1859
+ cache?: boolean;
1860
+ /**
1861
+ * Number of concurrent page instances for parallel rendering.
1862
+ * @default 1
1863
+ */
1864
+ concurrency?: number;
1865
+ }
1866
+ /**
1867
+ * Resolved OG image options with all defaults applied.
1868
+ */
1869
+ interface ResolvedOgImageOptions {
1870
+ template?: string;
1871
+ vuePlugin: "vitejs" | "vizejs";
1872
+ width: number;
1873
+ height: number;
1874
+ cache: boolean;
1875
+ concurrency: number;
1876
+ }
1877
+ //#endregion
1878
+ //#region src/og-image/browser.d.ts
1879
+ /**
1880
+ * Chromium browser session with automatic cleanup via Explicit Resource Management.
1881
+ *
1882
+ * Usage:
1883
+ * await using session = await openBrowser();
1884
+ * const png = await session.renderPage(html, 1200, 630);
1885
+ * // browser.close() is called automatically when session goes out of scope
1886
+ */
1887
+ /**
1888
+ * A browser session that can render HTML pages to PNG.
1889
+ * Implements AsyncDisposable for automatic cleanup via `await using`.
1890
+ */
1891
+ interface OgBrowserSession extends AsyncDisposable {
1892
+ renderPage(html: string, width: number, height: number, publicDir?: string): Promise<Buffer>;
1893
+ }
1894
+ //#endregion
1895
+ //#region src/og-image/index.d.ts
1896
+ /**
1897
+ * Resolves user-provided OG image options with defaults.
1898
+ */
1899
+ declare function resolveOgImageOptions(options: OgImageOptions$1 | undefined): ResolvedOgImageOptions;
1900
+ /**
1901
+ * A single page entry for batch OG image generation.
1902
+ */
1903
+ interface OgImagePageEntry {
1904
+ /** Props to pass to the template */
1905
+ props: OgImageTemplateProps;
1906
+ /** Absolute path to write the output PNG */
1907
+ outputPath: string;
1908
+ }
1909
+ /**
1910
+ * Result of OG image generation for a single page.
1911
+ */
1912
+ interface OgImageResult {
1913
+ outputPath: string;
1914
+ cached: boolean;
1915
+ error?: string;
1916
+ }
1917
+ /**
1918
+ * Generates OG images for a batch of pages.
1919
+ *
1920
+ * Manages the full lifecycle: resolve template → launch browser (with `using`) →
1921
+ * render each page (with caching and concurrency).
1922
+ *
1923
+ * All errors are non-fatal: failures are reported in results but never throw.
1924
+ */
1925
+ declare function generateOgImages(pages: OgImagePageEntry[], options: ResolvedOgImageOptions, root: string): Promise<OgImageResult[]>;
1926
+ //#endregion
1927
+ //#region src/i18n.d.ts
1928
+ /**
1929
+ * Resolves i18n options with defaults.
1930
+ */
1931
+ declare function resolveI18nOptions(options: I18nOptions | false | undefined): ResolvedI18nOptions | false;
1932
+ /**
1933
+ * Creates the i18n sub-plugin for the Vite plugin array.
1934
+ */
1935
+ declare function createI18nPlugin(resolvedOptions: ResolvedOptions): Plugin;
1936
+ //#endregion
1937
+ //#region src/index.d.ts
1938
+ /**
1939
+ * Creates the Ox Content Vite plugin.
1940
+ *
1941
+ * @example
1942
+ * ```ts
1943
+ * // vite.config.ts
1944
+ * import { defineConfig } from 'vite';
1945
+ * import { oxContent } from '@ox-content/vite-plugin';
1946
+ *
1947
+ * export default defineConfig({
1948
+ * plugins: [
1949
+ * oxContent({
1950
+ * srcDir: 'content',
1951
+ * gfm: true,
1952
+ * }),
1953
+ * ],
1954
+ * });
1955
+ * ```
1956
+ */
1957
+ declare function oxContent(options?: OxContentOptions): Plugin[];
1958
+ //#endregion
1959
+ export { type BasePageProps, CodeAnnotationKind, type CodeAnnotationSyntax, type CodeAnnotationsOptions, DEFAULT_HTML_TEMPLATE, DefaultTheme, type DocEntry, type DocsOptions, type EntryPageConfig, type ExtractedDocs, type FeatureConfig, Fragment, type FrontmatterSchema, GeneratedDocsData, type GitHubOptions, type GitHubRepoData, type HeroAction, type HeroConfig, type HeroImage, HeroNotice, type I18nOptions, type IslandInfo, type JSXChild, type JSXElementType, type JSXNode, type JSXProps, type LanguageRegistration, type LoadStrategy, type LocaleConfig, MarkdownNode, MarkdownTransformer, type MermaidOptions, type NavGroup, type NavItem, type OgBrowserSession, type OgImageOptions, type OgImagePageEntry, type OgImageOptions$1 as OgImagePluginOptions, type OgImageResult, type OgImageTemplateFn, type OgImageTemplateProps, type OgpData, type OgpOptions, type OxContentOptions, type PageData, type PageProps, type ParamDoc, type ParseIslandsResult, type RenderContext, type ResolvedCodeAnnotationsOptions, type ResolvedDocsOptions, type ResolvedI18nOptions, type ResolvedOgImageOptions, ResolvedOptions, type ResolvedSearchOptions, type ResolvedSsgOptions, type ResolvedThemeConfig, type ReturnDoc, ScopedSearchQuery, type SearchDocument, type SearchOptions, type SearchResult, type SiteConfig, type SocialLinks, type SsgOptions, type ThemeColors, type ThemeComponent, type ThemeConfig, type ThemeEmbed, type ThemeEntryPage, type ThemeFonts, type ThemeFooter, type ThemeHeader, type ThemeLayout, type ThemeProps, type ThemeRegistration, type ThemeRenderOptions, TocEntry, type TransformAllOptions, TransformContext, TransformResult, type YouTubeOptions, buildSearchIndex, buildSsg, clearRenderContext, collectGitHubRepos, collectOgpUrls, createI18nPlugin, createMarkdownEnvironment, createTheme, defaultTheme, defineTheme, each, extractDocs, extractIslandInfo, extractVideoId, fetchOgpData, fetchRepoData, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, hasIslands, inferType, jsx, jsxs, mergeThemes, mermaidClientScript, oxContent, prefetchGitHubRepos, prefetchOgpData, raw, renderAllPages, renderPage, renderToString, resolveDocsOptions, resolveI18nOptions, resolveOgImageOptions, resolveSearchOptions, resolveSsgOptions, resolveTheme, setRenderContext, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeSearchIndex };
1960
+ //# sourceMappingURL=index.d.mts.map