@ox-content/vite-plugin 1.1.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.
package/dist/index.cjs CHANGED
@@ -6674,51 +6674,105 @@ const highlighterCache = /* @__PURE__ */ new Map();
6674
6674
  * Get or create the Shiki highlighter.
6675
6675
  */
6676
6676
  async function getHighlighter(theme, customLangs = []) {
6677
+ const { themeInput } = normalizeThemeInput(theme);
6677
6678
  const cacheKey = JSON.stringify({
6678
- theme,
6679
+ theme: themeInput,
6679
6680
  langs: customLangs
6680
6681
  });
6681
6682
  let highlighterPromise = highlighterCache.get(cacheKey);
6682
6683
  if (!highlighterPromise) {
6683
6684
  highlighterPromise = (0, shiki.createHighlighter)({
6684
- themes: [theme],
6685
+ themes: [themeInput],
6685
6686
  langs: [...BUILTIN_LANGS, ...customLangs]
6686
6687
  });
6687
6688
  highlighterCache.set(cacheKey, highlighterPromise);
6688
6689
  }
6689
6690
  return highlighterPromise;
6690
6691
  }
6692
+ function normalizeThemeInput(theme) {
6693
+ if (typeof theme === "string") return {
6694
+ themeInput: theme,
6695
+ themeName: theme
6696
+ };
6697
+ const themeName = theme.name || "ox-content-custom-theme";
6698
+ return {
6699
+ themeInput: theme.name ? theme : {
6700
+ ...theme,
6701
+ name: themeName
6702
+ },
6703
+ themeName
6704
+ };
6705
+ }
6691
6706
  /**
6692
6707
  * Rehype plugin for syntax highlighting with Shiki.
6693
6708
  */
6694
6709
  function rehypeShikiHighlight(options) {
6695
6710
  const { theme, langs } = options;
6696
6711
  return async (tree) => {
6712
+ const { themeName } = normalizeThemeInput(theme);
6697
6713
  const highlighter = await getHighlighter(theme, langs);
6714
+ const highlightBlockCode = (codeElement) => {
6715
+ let lang = "text";
6716
+ const langClass = normalizeClassName(codeElement.properties?.className).find((value) => value.startsWith("language-"));
6717
+ if (langClass) lang = langClass.replace("language-", "");
6718
+ const codeText = getTextContent(codeElement);
6719
+ try {
6720
+ const highlighted = highlighter.codeToHtml(codeText, {
6721
+ lang,
6722
+ theme: themeName
6723
+ });
6724
+ const parsed = (0, unified.unified)().use(rehype_parse.default, { fragment: true }).parse(highlighted);
6725
+ if (parsed.children[0]?.type === "element") {
6726
+ const highlightedPre = parsed.children[0];
6727
+ highlightedPre.properties ??= {};
6728
+ highlightedPre.properties["data-language"] = lang;
6729
+ return highlightedPre;
6730
+ }
6731
+ } catch {}
6732
+ return null;
6733
+ };
6734
+ const highlightInlineCode = (codeElement) => {
6735
+ let lang = "text";
6736
+ const originalCodeClasses = normalizeClassName(codeElement.properties?.className);
6737
+ const langClass = originalCodeClasses.find((value) => value.startsWith("language-"));
6738
+ if (!langClass) return null;
6739
+ lang = langClass.replace("language-", "");
6740
+ const codeText = getTextContent(codeElement);
6741
+ try {
6742
+ const highlighted = highlighter.codeToHtml(codeText, {
6743
+ lang,
6744
+ theme: themeName
6745
+ });
6746
+ const parsed = (0, unified.unified)().use(rehype_parse.default, { fragment: true }).parse(highlighted);
6747
+ if (parsed.children[0]?.type === "element") {
6748
+ const highlightedCode = parsed.children[0].children.find((child) => child.type === "element" && child.tagName === "code");
6749
+ if (highlightedCode) {
6750
+ highlightedCode.properties ??= {};
6751
+ const highlightedClasses = normalizeClassName(highlightedCode.properties.className);
6752
+ highlightedCode.properties.className = [...new Set([
6753
+ ...originalCodeClasses,
6754
+ ...highlightedClasses,
6755
+ "shiki-inline"
6756
+ ])];
6757
+ highlightedCode.properties["data-language"] = lang;
6758
+ return highlightedCode;
6759
+ }
6760
+ }
6761
+ } catch {}
6762
+ return null;
6763
+ };
6698
6764
  const visit = async (node) => {
6699
6765
  if ("children" in node) for (let i = 0; i < node.children.length; i++) {
6700
6766
  const child = node.children[i];
6701
6767
  if (child.type === "element" && child.tagName === "pre") {
6702
6768
  const codeElement = child.children.find((c) => c.type === "element" && c.tagName === "code");
6703
6769
  if (codeElement) {
6704
- let lang = "text";
6705
- const langClass = normalizeClassName(codeElement.properties?.className).find((value) => value.startsWith("language-"));
6706
- if (langClass) lang = langClass.replace("language-", "");
6707
- const codeText = getTextContent(codeElement);
6708
- try {
6709
- const highlighted = highlighter.codeToHtml(codeText, {
6710
- lang,
6711
- theme
6712
- });
6713
- const parsed = (0, unified.unified)().use(rehype_parse.default, { fragment: true }).parse(highlighted);
6714
- if (parsed.children[0]?.type === "element") {
6715
- const highlightedPre = parsed.children[0];
6716
- highlightedPre.properties ??= {};
6717
- highlightedPre.properties["data-language"] = lang;
6718
- node.children[i] = highlightedPre;
6719
- }
6720
- } catch {}
6770
+ const highlightedPre = highlightBlockCode(codeElement);
6771
+ if (highlightedPre) node.children[i] = highlightedPre;
6721
6772
  }
6773
+ } else if (child.type === "element" && child.tagName === "code") {
6774
+ const highlightedCode = highlightInlineCode(child);
6775
+ if (highlightedCode) node.children[i] = highlightedCode;
6722
6776
  } else if (child.type === "element") await visit(child);
6723
6777
  }
6724
6778
  };
@@ -7335,23 +7389,137 @@ function cleanSummaryText(text, maxLength = 120) {
7335
7389
  function renderInlineHtml(text) {
7336
7390
  let html = "";
7337
7391
  let lastIndex = 0;
7338
- const tokenPattern = /`([^`]+)`|\[([^\]]+)\]\(([^)]+)\)/g;
7392
+ const tokenPattern = /`([^`]+)`|\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*]+)\*|_([^_]+)_/g;
7339
7393
  let match;
7340
7394
  while ((match = tokenPattern.exec(text)) !== null) {
7341
7395
  html += escapeHtml$3(text.slice(lastIndex, match.index));
7342
7396
  if (match[1]) html += `<code>${escapeHtml$3(match[1])}</code>`;
7343
- else if (match[2] && match[3]) html += `<a href="${escapeHtml$3(match[3])}">${escapeHtml$3(match[2])}</a>`;
7397
+ else if (match[2] && match[3]) html += `<a href="${escapeHtml$3(match[3])}">${renderInlineHtml(match[2])}</a>`;
7398
+ else if (match[4] || match[5]) {
7399
+ const strongText = match[4] ?? match[5] ?? "";
7400
+ html += `<strong>${renderInlineHtml(strongText)}</strong>`;
7401
+ } else if (match[6] || match[7]) {
7402
+ const emphasisText = match[6] ?? match[7] ?? "";
7403
+ html += `<em>${renderInlineHtml(emphasisText)}</em>`;
7404
+ }
7344
7405
  lastIndex = match.index + match[0].length;
7345
7406
  }
7346
7407
  html += escapeHtml$3(text.slice(lastIndex));
7347
7408
  return html.replace(/\n/g, "<br>");
7348
7409
  }
7349
- function renderParagraphsHtml(text) {
7350
- return text.split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean).map((paragraph) => `<p>${renderInlineHtml(paragraph)}</p>`).join("\n");
7410
+ function isFenceStart(line) {
7411
+ return /^```([\w-]+)?\s*$/.exec(line.trim());
7412
+ }
7413
+ function isHeading(line) {
7414
+ return /^(#{1,6})\s+(.*)$/.exec(line.trim());
7415
+ }
7416
+ function isOrderedListItem(line) {
7417
+ return /^\d+\.\s+(.*)$/.exec(line.trim());
7418
+ }
7419
+ function isUnorderedListItem(line) {
7420
+ return /^[-*+]\s+(.*)$/.exec(line.trim());
7421
+ }
7422
+ function isMarkdownBlockStart(line) {
7423
+ return Boolean(isFenceStart(line) || isHeading(line) || isOrderedListItem(line) || isUnorderedListItem(line));
7424
+ }
7425
+ function renderMarkdownBlocksHtml(text) {
7426
+ const lines = text.split(/\r?\n/);
7427
+ const blocks = [];
7428
+ let index = 0;
7429
+ while (index < lines.length) {
7430
+ const line = lines[index];
7431
+ const trimmed = line.trim();
7432
+ if (!trimmed) {
7433
+ index++;
7434
+ continue;
7435
+ }
7436
+ const fenceMatch = isFenceStart(line);
7437
+ if (fenceMatch) {
7438
+ const language = fenceMatch[1] || "text";
7439
+ const codeLines = [];
7440
+ index++;
7441
+ while (index < lines.length && !lines[index].trim().startsWith("```")) {
7442
+ codeLines.push(lines[index]);
7443
+ index++;
7444
+ }
7445
+ if (index < lines.length) index++;
7446
+ blocks.push(renderCodeBlockHtml(codeLines.join("\n"), language));
7447
+ continue;
7448
+ }
7449
+ const headingMatch = isHeading(line);
7450
+ if (headingMatch) {
7451
+ const level = Math.min(headingMatch[1].length, 6);
7452
+ blocks.push(`<h${level}>${renderInlineHtml(headingMatch[2].trim())}</h${level}>`);
7453
+ index++;
7454
+ continue;
7455
+ }
7456
+ if (isOrderedListItem(line)) {
7457
+ const items = [];
7458
+ while (index < lines.length) {
7459
+ const currentLine = lines[index];
7460
+ const currentMatch = isOrderedListItem(currentLine);
7461
+ if (!currentMatch) break;
7462
+ const itemLines = [currentMatch[1].trim()];
7463
+ index++;
7464
+ while (index < lines.length) {
7465
+ const continuation = lines[index];
7466
+ const continuationTrimmed = continuation.trim();
7467
+ if (!continuationTrimmed || isMarkdownBlockStart(continuation) || /^ {0,1}\d+\.\s+/.test(continuationTrimmed)) break;
7468
+ itemLines.push(continuationTrimmed);
7469
+ index++;
7470
+ }
7471
+ items.push(`<li>${renderInlineHtml(itemLines.join(" "))}</li>`);
7472
+ if (index < lines.length && !lines[index].trim()) break;
7473
+ }
7474
+ blocks.push(`<ol>\n${items.join("\n")}\n</ol>`);
7475
+ continue;
7476
+ }
7477
+ if (isUnorderedListItem(line)) {
7478
+ const items = [];
7479
+ while (index < lines.length) {
7480
+ const currentLine = lines[index];
7481
+ const currentMatch = isUnorderedListItem(currentLine);
7482
+ if (!currentMatch) break;
7483
+ const itemLines = [currentMatch[1].trim()];
7484
+ index++;
7485
+ while (index < lines.length) {
7486
+ const continuation = lines[index];
7487
+ const continuationTrimmed = continuation.trim();
7488
+ if (!continuationTrimmed || isMarkdownBlockStart(continuation) || /^[-*+]\s+/.test(continuationTrimmed)) break;
7489
+ itemLines.push(continuationTrimmed);
7490
+ index++;
7491
+ }
7492
+ items.push(`<li>${renderInlineHtml(itemLines.join(" "))}</li>`);
7493
+ if (index < lines.length && !lines[index].trim()) break;
7494
+ }
7495
+ blocks.push(`<ul>\n${items.join("\n")}\n</ul>`);
7496
+ continue;
7497
+ }
7498
+ const paragraphLines = [trimmed];
7499
+ index++;
7500
+ while (index < lines.length) {
7501
+ const nextLine = lines[index];
7502
+ const nextTrimmed = nextLine.trim();
7503
+ if (!nextTrimmed || isMarkdownBlockStart(nextLine)) break;
7504
+ paragraphLines.push(nextTrimmed);
7505
+ index++;
7506
+ }
7507
+ blocks.push(`<p>${renderInlineHtml(paragraphLines.join(" "))}</p>`);
7508
+ }
7509
+ return `<div class="ox-api-entry__prose">\n${blocks.join("\n")}\n</div>`;
7351
7510
  }
7352
7511
  function renderCodeBlockHtml(code, language = "typescript") {
7353
7512
  return `<pre><code class="language-${language}">${escapeHtml$3(code)}</code></pre>`;
7354
7513
  }
7514
+ function renderHighlightedInlineCodeHtml(code, className, language = "typescript") {
7515
+ return `<code class="${escapeHtml$3(className)} language-${language}">${escapeHtml$3(code)}</code>`;
7516
+ }
7517
+ function renderDetailsControlsHtml(targetSelector) {
7518
+ return `<div class="ox-api-controls" data-ox-api-target="${targetSelector}" role="toolbar" aria-label="Reference display controls">
7519
+ <button type="button" class="ox-api-controls__button" data-ox-api-toggle="expand">Open all</button>
7520
+ <button type="button" class="ox-api-controls__button" data-ox-api-toggle="collapse">Close all</button>
7521
+ </div>`;
7522
+ }
7355
7523
  function buildDocsData(docs) {
7356
7524
  return {
7357
7525
  version: 1,
@@ -7360,16 +7528,6 @@ function buildDocsData(docs) {
7360
7528
  };
7361
7529
  }
7362
7530
  /**
7363
- * Regex pattern for matching JSDoc comment blocks.
7364
- *
7365
- * Matches block comments that start at the beginning of a line
7366
- * (with optional leading whitespace). This pattern avoids false matches
7367
- * with block comments inside strings like glob patterns.
7368
- *
7369
- * @internal
7370
- */
7371
- const JSDOC_BLOCK = /^[ \t]*\/\*\*\s*([\s\S]*?)\s*\*\//gm;
7372
- /**
7373
7531
  * Extracts JSDoc documentation from source files in specified directories.
7374
7532
  *
7375
7533
  * This function recursively searches directories for source files matching
@@ -7435,11 +7593,13 @@ const JSDOC_BLOCK = /^[ \t]*\/\*\*\s*([\s\S]*?)\s*\*\//gm;
7435
7593
  * ```
7436
7594
  */
7437
7595
  async function extractDocs(srcDirs, options) {
7596
+ const extractFileDocs = (await require_mermaid.importNapiModule()).extractFileDocs;
7597
+ if (!extractFileDocs) throw new Error("[ox-content] extractFileDocs is not available from @ox-content/napi.");
7438
7598
  const results = [];
7439
7599
  for (const srcDir of srcDirs) {
7440
7600
  const files = await findFiles(srcDir, options);
7441
7601
  for (const file of files) {
7442
- const entries = extractFromContent(await fs.promises.readFile(file, "utf-8"), file, options);
7602
+ const entries = extractFileDocs(file, options.private).map(parseNapiDocItem).filter((entry) => Boolean(entry));
7443
7603
  if (entries.length > 0) results.push({
7444
7604
  file,
7445
7605
  entries
@@ -7490,127 +7650,21 @@ function isExcluded(file, patterns) {
7490
7650
  return false;
7491
7651
  });
7492
7652
  }
7493
- /**
7494
- * Extracts documentation entries from file content.
7495
- */
7496
- function extractFromContent(content, file, options) {
7497
- const entries = [];
7498
- let match;
7499
- JSDOC_BLOCK.lastIndex = 0;
7500
- while ((match = JSDOC_BLOCK.exec(content)) !== null) {
7501
- const jsdocContent = match[1];
7502
- const jsdocEnd = match.index + match[0].length;
7503
- const afterJsdoc = content.slice(jsdocEnd).trim();
7504
- const lineNumber = content.slice(0, match.index).split("\n").length;
7505
- const entry = parseJsdocBlock(jsdocContent, afterJsdoc, file, lineNumber);
7506
- if (entry && (options.private || !entry.private)) entries.push(entry);
7507
- }
7508
- return entries;
7509
- }
7510
- /**
7511
- * Extracts the complete function signature for display.
7512
- *
7513
- * Captures the full function declaration from `export/async/function name(...): ReturnType`
7514
- * or `export const name = (...): ReturnType => {}`, handling multi-line signatures.
7515
- *
7516
- * @param signature - Multi-line function declaration text
7517
- * @returns Cleaned function signature or undefined if not found
7518
- *
7519
- * @internal
7520
- */
7521
- function extractFunctionSignature(signature) {
7522
- const match = signature.match(/(?:export\s+)?(?:async\s+)?(?:function\s+\w+|\w+\s*=\s*(?:async\s*)?\()\([^{]*?\)(?:\s*:\s*[^{;]+)?/s);
7523
- if (match) {
7524
- let sig = match[0].trim();
7525
- sig = sig.split("\n").map((line) => line.trim()).filter((line) => line).join("\n ");
7526
- return sig;
7527
- }
7528
- }
7529
- /**
7530
- * Extracts parameter and return types from a TypeScript function signature.
7531
- *
7532
- * Parses function signatures to extract:
7533
- * - Parameter names and their type annotations
7534
- * - Return type annotation
7535
- *
7536
- * Handles various function declaration styles:
7537
- * - `function name(param: type): ReturnType`
7538
- * - `const name = (param: type): ReturnType => {}`
7539
- * - `export async function name(param: type): Promise<ReturnType>`
7540
- *
7541
- * @param signature - Multi-line function signature text
7542
- * @param params - Array of parameter docs with names already extracted
7543
- * @returns Object with extracted parameter types and return type
7544
- *
7545
- * @internal
7546
- */
7547
- function extractTypesFromSignature(signature, _params) {
7548
- const paramTypes = [];
7549
- const paramListMatch = signature.match(/\(([^)]*)\)(?:\s*:\s*([^{=>]+))?/s);
7550
- if (paramListMatch && paramListMatch[1]) {
7551
- const paramListStr = paramListMatch[1];
7552
- const paramParts = splitParameters(paramListStr);
7553
- for (const part of paramParts) {
7554
- const trimmed = part.trim();
7555
- if (!trimmed) continue;
7556
- const typeMatch = /:\s*(.+?)(?:\s*=|$)/.exec(trimmed);
7557
- if (typeMatch) {
7558
- let typeStr = typeMatch[1].trim();
7559
- if (typeStr.includes("=")) typeStr = typeStr.split("=")[0].trim();
7560
- paramTypes.push(typeStr);
7561
- }
7562
- }
7563
- }
7564
- let returnType;
7565
- const returnTypeMatch = signature.match(/\)\s*:\s*(.+?)(?={|$)/);
7566
- if (returnTypeMatch) returnType = returnTypeMatch[1].trim();
7567
- return {
7568
- paramTypes,
7569
- returnType
7570
- };
7571
- }
7572
- /**
7573
- * Splits function parameters while respecting nested angle brackets (generics).
7574
- *
7575
- * Handles cases like:
7576
- * - `a: string, b: number` → `["a: string", "b: number"]`
7577
- * - `a: Promise<string>, b: Record<string, any>` → `["a: Promise<string>", "b: Record<string, any>"]`
7578
- *
7579
- * @param paramListStr - String containing all parameters
7580
- * @returns Array of individual parameter strings
7581
- *
7582
- * @internal
7583
- */
7584
- function splitParameters(paramListStr) {
7585
- const parts = [];
7586
- let current = "";
7587
- let depth = 0;
7588
- for (const char of paramListStr) if (char === "<") {
7589
- depth++;
7590
- current += char;
7591
- } else if (char === ">") {
7592
- depth--;
7593
- current += char;
7594
- } else if (char === "," && depth === 0) {
7595
- parts.push(current);
7596
- current = "";
7597
- } else current += char;
7598
- if (current) parts.push(current);
7599
- return parts;
7600
- }
7601
- /**
7602
- * Parses a JSDoc block and the following declaration.
7603
- * Only matches if the declaration is immediately after the JSDoc (with only whitespace/keywords between).
7604
- */
7605
- function parseJsdocBlock(jsdoc, declaration, file, line) {
7653
+ function parseNapiDocItem(item) {
7654
+ const kind = normalizeNapiKind(item.kind);
7655
+ if (!kind) return null;
7606
7656
  const params = [];
7607
7657
  const examples = [];
7608
7658
  const tags = {};
7609
7659
  let description = "";
7610
7660
  let returns;
7611
7661
  let isPrivate = false;
7612
- const rawLines = jsdoc.split("\n").map((l) => l.replace(/^\s*\*\s?/, ""));
7613
- const cleanedLines = rawLines.map((l) => l.trim()).filter((l) => l);
7662
+ const rawLines = (item.jsdoc ?? "").split("\n").map((line) => {
7663
+ const trimmedStart = line.trimStart();
7664
+ const withoutStar = trimmedStart.startsWith("*") ? trimmedStart.slice(1) : trimmedStart;
7665
+ return withoutStar.startsWith(" ") ? withoutStar.slice(1) : withoutStar;
7666
+ });
7667
+ const cleanedLines = rawLines.map((line) => line.trim()).filter(Boolean);
7614
7668
  let currentExample = "";
7615
7669
  let inExample = false;
7616
7670
  let rawLineIndex = 0;
@@ -7657,47 +7711,40 @@ function parseJsdocBlock(jsdoc, declaration, file, line) {
7657
7711
  else description += "\n" + lineText;
7658
7712
  }
7659
7713
  if (inExample && currentExample) examples.push(currentExample.trim());
7660
- const firstFewLines = declaration.split("\n").slice(0, 5).join("\n");
7661
- let name = "";
7662
- let kind = "function";
7663
- const ANCHORED_FUNCTION = /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
7664
- const ANCHORED_CONST_FUNC = /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\(/;
7665
- const ANCHORED_CLASS = /^(?:export\s+)?class\s+(\w+)/;
7666
- const ANCHORED_INTERFACE = /^(?:export\s+)?interface\s+(\w+)/;
7667
- const ANCHORED_TYPE = /^(?:export\s+)?type\s+(\w+)/;
7668
- let declMatch;
7669
- if (declMatch = ANCHORED_FUNCTION.exec(firstFewLines)) {
7670
- name = declMatch[1];
7671
- kind = "function";
7672
- } else if (declMatch = ANCHORED_CONST_FUNC.exec(firstFewLines)) {
7673
- name = declMatch[1];
7674
- kind = "function";
7675
- } else if (declMatch = ANCHORED_CLASS.exec(firstFewLines)) {
7676
- name = declMatch[1];
7677
- kind = "class";
7678
- } else if (declMatch = ANCHORED_INTERFACE.exec(firstFewLines)) {
7679
- name = declMatch[1];
7680
- kind = "interface";
7681
- } else if (declMatch = ANCHORED_TYPE.exec(firstFewLines)) {
7682
- name = declMatch[1];
7683
- kind = "type";
7684
- }
7685
- if (!name) return null;
7686
- let signature;
7687
- if (kind === "function") {
7688
- const signatureTypes = extractTypesFromSignature(firstFewLines, params);
7689
- if (signatureTypes.paramTypes.length > 0) {
7690
- for (let i = 0; i < params.length && i < signatureTypes.paramTypes.length; i++) if (params[i].type === "unknown") params[i].type = signatureTypes.paramTypes[i];
7691
- }
7692
- if (signatureTypes.returnType && (!returns || returns.type === "unknown")) if (returns) returns.type = signatureTypes.returnType;
7693
- else returns = {
7694
- type: signatureTypes.returnType,
7695
- description: ""
7696
- };
7697
- signature = extractFunctionSignature(firstFewLines);
7714
+ if (params.length === 0 && item.params.length > 0) params.push(...item.params.map((param) => ({
7715
+ name: param.name,
7716
+ type: param.typeAnnotation ?? "unknown",
7717
+ description: param.description ?? "",
7718
+ optional: param.optional || void 0,
7719
+ default: param.defaultValue
7720
+ })));
7721
+ else if (item.params.length > 0) {
7722
+ const paramMap = new Map(item.params.map((param) => [param.name, param]));
7723
+ for (const param of params) {
7724
+ const rustParam = paramMap.get(param.name);
7725
+ if (!rustParam) continue;
7726
+ if (param.type === "unknown" && rustParam.typeAnnotation) param.type = rustParam.typeAnnotation;
7727
+ if (!param.description && rustParam.description) param.description = rustParam.description;
7728
+ if (param.optional === void 0 && rustParam.optional) param.optional = true;
7729
+ if (!param.default && rustParam.defaultValue) param.default = rustParam.defaultValue;
7730
+ }
7731
+ }
7732
+ if (!returns && item.returnType) returns = {
7733
+ type: item.returnType,
7734
+ description: ""
7735
+ };
7736
+ else if (returns && returns.type === "unknown" && item.returnType) returns.type = item.returnType;
7737
+ if (!description) description = item.doc ?? "";
7738
+ for (const tag of item.tags) {
7739
+ if (tag.tag === "param" || tag.tag === "returns" || tag.tag === "return" || tag.tag === "example") continue;
7740
+ if (tag.tag === "private") {
7741
+ isPrivate = true;
7742
+ continue;
7743
+ }
7744
+ if (!tags[tag.tag]) tags[tag.tag] = tag.value;
7698
7745
  }
7699
7746
  return {
7700
- name,
7747
+ name: item.name,
7701
7748
  kind,
7702
7749
  description,
7703
7750
  params: params.length > 0 ? params : void 0,
@@ -7705,39 +7752,69 @@ function parseJsdocBlock(jsdoc, declaration, file, line) {
7705
7752
  examples: examples.length > 0 ? examples : void 0,
7706
7753
  tags: Object.keys(tags).length > 0 ? tags : void 0,
7707
7754
  private: isPrivate,
7708
- file,
7709
- line,
7710
- signature
7755
+ file: item.sourcePath,
7756
+ line: item.line,
7757
+ endLine: item.endLine,
7758
+ signature: item.signature
7711
7759
  };
7712
7760
  }
7761
+ function normalizeNapiKind(kind) {
7762
+ switch (kind) {
7763
+ case "function":
7764
+ case "class":
7765
+ case "interface":
7766
+ case "type":
7767
+ case "variable":
7768
+ case "module": return kind;
7769
+ case "enum": return "type";
7770
+ default: return null;
7771
+ }
7772
+ }
7713
7773
  /**
7714
7774
  * Generates Markdown documentation from extracted docs.
7715
7775
  */
7716
7776
  function generateMarkdown(docs, options) {
7717
7777
  const result = {};
7718
- const symbolMap = buildSymbolMap(docs);
7778
+ const sortedDocs = sortExtractedDocs(docs);
7779
+ const symbolMap = buildSymbolMap(sortedDocs);
7719
7780
  if (options.groupBy === "file") {
7720
7781
  const docToFile = /* @__PURE__ */ new Map();
7721
- for (const doc of docs) {
7782
+ for (const doc of sortedDocs) {
7722
7783
  let fileName = path.basename(doc.file, path.extname(doc.file));
7723
7784
  if (fileName === "index") fileName = "index-module";
7724
7785
  docToFile.set(doc, fileName);
7725
7786
  const markdown = generateFileMarkdown(doc, options, fileName, symbolMap);
7726
7787
  result[`${fileName}.md`] = markdown;
7727
7788
  }
7728
- result["index.md"] = generateIndex(docs, docToFile);
7789
+ result["index.md"] = generateIndex(sortedDocs, docToFile);
7729
7790
  } else {
7730
7791
  const byKind = /* @__PURE__ */ new Map();
7731
- for (const doc of docs) for (const entry of doc.entries) {
7792
+ for (const doc of sortedDocs) for (const entry of doc.entries) {
7732
7793
  const existing = byKind.get(entry.kind) || [];
7733
7794
  existing.push(entry);
7734
7795
  byKind.set(entry.kind, existing);
7735
7796
  }
7736
- for (const [kind, entries] of byKind) result[`${kind}s.md`] = generateCategoryMarkdown(kind, entries, options, symbolMap);
7797
+ for (const entries of byKind.values()) entries.sort(compareEntriesByName);
7798
+ for (const [kind, entries] of [...byKind.entries()].sort(([a], [b]) => compareStrings(a, b))) result[`${kind}s.md`] = generateCategoryMarkdown(kind, entries, options, symbolMap);
7737
7799
  result["index.md"] = generateCategoryIndex(byKind);
7738
7800
  }
7739
7801
  return result;
7740
7802
  }
7803
+ function compareStrings(a, b) {
7804
+ return a.localeCompare(b, "en", {
7805
+ numeric: true,
7806
+ sensitivity: "base"
7807
+ });
7808
+ }
7809
+ function compareEntriesByName(a, b) {
7810
+ return compareStrings(a.name, b.name);
7811
+ }
7812
+ function sortExtractedDocs(docs) {
7813
+ return [...docs].map((doc) => ({
7814
+ ...doc,
7815
+ entries: [...doc.entries].sort(compareEntriesByName)
7816
+ })).sort((a, b) => compareStrings(path.basename(a.file), path.basename(b.file)));
7817
+ }
7741
7818
  function generateFileMarkdown(doc, options, currentFileName, symbolMap) {
7742
7819
  let md = `# ${path.basename(doc.file)}\n\n`;
7743
7820
  if (options.githubUrl) {
@@ -7745,16 +7822,25 @@ function generateFileMarkdown(doc, options, currentFileName, symbolMap) {
7745
7822
  if (sourceLink) md += sourceLink + "\n\n";
7746
7823
  }
7747
7824
  md += `> ${doc.entries.length} documented symbol${doc.entries.length === 1 ? "" : "s"}. `;
7748
- md += "Skim the one-line surface first, then expand the accordions for details.\n\n";
7749
- md += "## Overview\n\n";
7750
- for (const entry of doc.entries) md += renderOverviewLine(entry, `#${entryAnchor(entry.name)}`);
7751
- md += "\n## Reference\n\n";
7825
+ md += "Read the signatures first, then expand each item for parameters, return types, and examples.\n\n";
7826
+ md += "## Reference\n\n";
7827
+ if (doc.entries.length > 1) md += renderDetailsControlsHtml(".ox-api-entry") + "\n\n";
7752
7828
  for (const entry of doc.entries) md += generateEntryMarkdown(entry, options, currentFileName, symbolMap);
7753
7829
  return md;
7754
7830
  }
7755
7831
  function normalizeSignature(signature) {
7756
7832
  if (!signature) return;
7757
- return signature.replace(/\s+/g, " ").replace(/^export\s+/, "").replace(/^async\s+function\s+/, "").replace(/^function\s+/, "").replace(/^class\s+/, "").trim();
7833
+ return signature.replace(/\s+/g, " ").replace(/^export\s+/, "").replace(/^declare\s+/, "").replace(/^abstract\s+/, "").replace(/^async\s+function\s+/, "").replace(/^function\s+/, "").replace(/^class\s+/, "").replace(/^interface\s+/, "").replace(/^type\s+/, "").trim();
7834
+ }
7835
+ function formatKindLabel(kind) {
7836
+ switch (kind) {
7837
+ case "function": return "fn";
7838
+ case "interface": return "interface";
7839
+ case "class": return "class";
7840
+ case "type": return "type";
7841
+ case "const": return "const";
7842
+ default: return kind;
7843
+ }
7758
7844
  }
7759
7845
  function renderOverviewLine(entry, href) {
7760
7846
  const signature = normalizeSignature(entry.signature);
@@ -7767,60 +7853,56 @@ function renderOverviewLine(entry, href) {
7767
7853
  function renderOverviewHtmlItem(entry, href) {
7768
7854
  const signature = normalizeSignature(entry.signature);
7769
7855
  const summary = cleanSummaryText(entry.description, 88);
7770
- const fragments = [`<a href="${escapeHtml$3(href)}"><code>${escapeHtml$3(entry.name)}</code></a>`, `<span class="ox-api-module__kind">${escapeHtml$3(entry.kind)}</span>`];
7771
- if (signature) fragments.push(`<code>${escapeHtml$3(signature)}</code>`);
7772
- if (summary) fragments.push(`<span>${renderInlineHtml(summary)}</span>`);
7773
- return `<li>${fragments.join("")}</li>`;
7856
+ const heading = signature ? `<a href="${escapeHtml$3(href)}" class="ox-api-module__link">${renderHighlightedInlineCodeHtml(signature, "ox-api-module__signature ox-api-module__signature--highlighted")}</a>` : `<a href="${escapeHtml$3(href)}" class="ox-api-module__link"><code class="ox-api-module__name">${escapeHtml$3(entry.name)}</code></a>`;
7857
+ return `<li><span class="ox-api-module__kind">${escapeHtml$3(formatKindLabel(entry.kind))}</span><div class="ox-api-module__item">${heading}${summary ? `<span class="ox-api-module__summary">${renderInlineHtml(summary)}</span>` : ""}</div></li>`;
7774
7858
  }
7775
- function renderParamsTableHtml(params) {
7776
- return `<div class="ox-api-entry__section">
7859
+ function renderParamsListHtml(params) {
7860
+ return `<div class="ox-api-entry__section ox-api-entry__section--params">
7777
7861
  <h4>Parameters</h4>
7778
- <table>
7779
- <thead>
7780
- <tr><th>Name</th><th>Type</th><th>Description</th></tr>
7781
- </thead>
7782
- <tbody>
7862
+ <ul class="ox-api-entry__params">
7783
7863
  ${params.map((param) => {
7784
7864
  const flags = [param.optional ? "optional" : "", param.default ? `default: ${param.default}` : ""].filter(Boolean);
7785
7865
  const description = [param.description, flags.join(" · ")].filter(Boolean).join(" — ");
7786
- return `<tr>
7787
- <td><code>${escapeHtml$3(param.name)}</code></td>
7788
- <td><code>${escapeHtml$3(param.type)}</code></td>
7789
- <td>${renderInlineHtml(description)}</td>
7790
- </tr>`;
7866
+ return `<li class="ox-api-entry__param">
7867
+ <div class="ox-api-entry__param-heading">
7868
+ <code class="ox-api-entry__param-name">${escapeHtml$3(param.name)}</code>
7869
+ <code class="ox-api-entry__param-type">${escapeHtml$3(param.type)}</code>
7870
+ </div>
7871
+ ${description ? `<p class="ox-api-entry__param-description">${renderInlineHtml(description)}</p>` : ""}
7872
+ </li>`;
7791
7873
  }).join("\n")}
7792
- </tbody>
7793
- </table>
7874
+ </ul>
7794
7875
  </div>`;
7795
7876
  }
7796
7877
  function renderTagListHtml(tags) {
7797
- return `<div class="ox-api-entry__section">
7878
+ return `<div class="ox-api-entry__section ox-api-entry__section--tags">
7798
7879
  <h4>Tags</h4>
7799
- <ul class="ox-api-entry__tags">${Object.entries(tags).map(([tag, value]) => `<li><span class="ox-api-entry__tag-name">@${escapeHtml$3(tag)}</span><span>${renderInlineHtml(value)}</span></li>`).join("")}</ul>
7880
+ <ul class="ox-api-entry__tags">${Object.entries(tags).map(([tag, value]) => `<li><span class="ox-api-entry__tag-name">@${escapeHtml$3(tag)}</span><span class="ox-api-entry__tag-value">${renderInlineHtml(value)}</span></li>`).join("")}</ul>
7800
7881
  </div>`;
7801
7882
  }
7802
7883
  function generateEntryMarkdown(entry, options, currentFileName, symbolMap) {
7803
7884
  const processedDescription = entry.description && currentFileName && symbolMap ? convertSymbolLinks(entry.description, currentFileName, symbolMap) : entry.description;
7804
7885
  const summarySignature = normalizeSignature(entry.signature);
7805
- const sourceHref = options?.githubUrl ? generateSourceHref(entry.file, options.githubUrl, entry.line) : void 0;
7886
+ const sourceHref = options?.githubUrl ? generateSourceHref(entry.file, options.githubUrl, entry.line, entry.endLine) : void 0;
7806
7887
  let body = "";
7807
- if (processedDescription) body += renderParagraphsHtml(processedDescription) + "\n";
7888
+ if (processedDescription) body += renderMarkdownBlocksHtml(processedDescription) + "\n";
7808
7889
  if (sourceHref) body += `<p class="ox-api-entry__source"><a href="${escapeHtml$3(sourceHref)}">View source</a></p>\n`;
7809
- if (entry.signature) body += `<div class="ox-api-entry__section">\n<h4>Signature</h4>\n${renderCodeBlockHtml(entry.signature)}\n</div>\n`;
7810
- if (entry.params && entry.params.length > 0) body += renderParamsTableHtml(entry.params) + "\n";
7811
- if (entry.returns) body += `<div class="ox-api-entry__section">
7890
+ if (entry.params && entry.params.length > 0) body += renderParamsListHtml(entry.params) + "\n";
7891
+ if (entry.returns) body += `<div class="ox-api-entry__section ox-api-entry__section--returns">
7812
7892
  <h4>Returns</h4>
7813
- <p><code>${escapeHtml$3(entry.returns.type)}</code>${entry.returns.description ? ` — ${renderInlineHtml(entry.returns.description)}` : ""}</p>
7893
+ <div class="ox-api-entry__return">
7894
+ <code class="ox-api-entry__return-type">${escapeHtml$3(entry.returns.type)}</code>
7895
+ ${entry.returns.description ? `<p class="ox-api-entry__return-description">${renderInlineHtml(entry.returns.description)}</p>` : ""}
7896
+ </div>
7814
7897
  </div>\n`;
7815
7898
  if (entry.examples && entry.examples.length > 0) {
7816
7899
  const examplesHtml = entry.examples.map((example) => example.replace(/^```\w*\n?/, "").replace(/\n?```$/, "")).map((example) => renderCodeBlockHtml(example, "ts")).join("\n");
7817
- body += `<div class="ox-api-entry__section">\n<h4>Examples</h4>\n${examplesHtml}\n</div>\n`;
7900
+ body += `<div class="ox-api-entry__section ox-api-entry__section--examples">\n<h4>Examples</h4>\n${examplesHtml}\n</div>\n`;
7818
7901
  }
7819
7902
  if (entry.tags && Object.keys(entry.tags).length > 0) body += renderTagListHtml(entry.tags) + "\n";
7820
7903
  const summaryDescription = cleanSummaryText(processedDescription, summarySignature ? 80 : 120);
7821
- const summaryParts = [`<span class="ox-api-entry__kind">${escapeHtml$3(entry.kind)}</span>`, `<code class="ox-api-entry__name">${escapeHtml$3(entry.name)}</code>`];
7822
- if (summarySignature) summaryParts.push(`<code class="ox-api-entry__signature">${escapeHtml$3(summarySignature)}</code>`);
7823
- if (summaryDescription) summaryParts.push(`<span class="ox-api-entry__description">${renderInlineHtml(summaryDescription)}</span>`);
7904
+ const summaryHeading = summarySignature ? renderHighlightedInlineCodeHtml(summarySignature, "ox-api-entry__signature ox-api-entry__signature--highlighted") : `<code class="ox-api-entry__name">${escapeHtml$3(entry.name)}</code>`;
7905
+ const summaryParts = [`<span class="ox-api-entry__kind">${escapeHtml$3(formatKindLabel(entry.kind))}</span>`, `<span class="ox-api-entry__summary-main">${summaryHeading}${summaryDescription ? `<span class="ox-api-entry__description">${renderInlineHtml(summaryDescription)}</span>` : ""}</span>`];
7824
7906
  return `<details id="${entryAnchor(entry.name)}" class="ox-api-entry">
7825
7907
  <summary>${summaryParts.join("")}</summary>
7826
7908
  <div class="ox-api-entry__body">
@@ -7835,6 +7917,7 @@ function generateIndex(docs, docToFile) {
7835
7917
  md += "Generated by [Ox Content](https://github.com/ubugeeei/ox-content)\n\n";
7836
7918
  md += "> Use search scopes like `@api transform` to limit results to the generated API reference.\n\n";
7837
7919
  md += "## Modules\n\n";
7920
+ if (docs.length > 1) md += renderDetailsControlsHtml(".ox-api-module") + "\n\n";
7838
7921
  for (const doc of docs) {
7839
7922
  const displayName = path.basename(doc.file, path.extname(doc.file));
7840
7923
  let fileName = displayName;
@@ -7865,13 +7948,14 @@ function generateCategoryMarkdown(kind, entries, options, symbolMap) {
7865
7948
  md += "## Overview\n\n";
7866
7949
  for (const entry of entries) md += renderOverviewLine(entry, `#${entryAnchor(entry.name)}`);
7867
7950
  md += "\n## Reference\n\n";
7951
+ if (entries.length > 1) md += renderDetailsControlsHtml(".ox-api-entry") + "\n\n";
7868
7952
  for (const entry of entries) md += generateEntryMarkdown(entry, options, categoryFileName, symbolMap);
7869
7953
  return md;
7870
7954
  }
7871
7955
  function generateCategoryIndex(byKind) {
7872
7956
  let md = "# API Documentation\n\n";
7873
7957
  md += "Generated by [Ox Content](https://github.com/ubugeeei/ox-content)\n\n";
7874
- for (const [kind, entries] of byKind) {
7958
+ for (const [kind, entries] of [...byKind.entries()].sort(([a], [b]) => compareStrings(a, b))) {
7875
7959
  const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
7876
7960
  md += `## [${kindTitle}](./${kind}s.md)\n\n`;
7877
7961
  md += `> ${entries.length} item${entries.length === 1 ? "" : "s"}.\n\n`;
@@ -7961,18 +8045,19 @@ async function writeDocs(docs, outDir, extractedDocs, options) {
7961
8045
  * Resolves docs options with defaults.
7962
8046
  */
7963
8047
  /**
7964
- * Generates a GitHub source link for a file and optional line number.
8048
+ * Generates a GitHub source link for a file and optional line range.
7965
8049
  *
7966
8050
  * @param filePath - Full path to the source file
7967
8051
  * @param githubUrl - Base GitHub repository URL
7968
- * @param lineNumber - Optional line number to link to
8052
+ * @param lineNumber - Optional start line number to link to
8053
+ * @param endLineNumber - Optional end line number to link to
7969
8054
  * @returns Absolute GitHub URL to source code
7970
8055
  */
7971
- function generateSourceHref(filePath, githubUrl, lineNumber) {
7972
- return `${githubUrl}/blob/main/${filePath.replace(/^.*?\/(npm|packages|crates|src)\//, "$1/")}${lineNumber ? `#L${lineNumber}` : ""}`;
8056
+ function generateSourceHref(filePath, githubUrl, lineNumber, endLineNumber) {
8057
+ return `${githubUrl}/blob/main/${filePath.replace(/^.*?\/(npm|packages|crates|src)\//, "$1/")}${lineNumber ? endLineNumber && endLineNumber > lineNumber ? `#L${lineNumber}-L${endLineNumber}` : `#L${lineNumber}` : ""}`;
7973
8058
  }
7974
- function generateSourceLink(filePath, githubUrl, lineNumber) {
7975
- return `**[Source](${generateSourceHref(filePath, githubUrl, lineNumber)})**`;
8059
+ function generateSourceLink(filePath, githubUrl, lineNumber, endLineNumber) {
8060
+ return `**[Source](${generateSourceHref(filePath, githubUrl, lineNumber, endLineNumber)})**`;
7976
8061
  }
7977
8062
  function resolveDocsOptions(options) {
7978
8063
  if (options === false) return false;
@@ -8130,23 +8215,74 @@ async function openBrowser() {
8130
8215
  function escapeHtml$2(str) {
8131
8216
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8132
8217
  }
8218
+ function normalizeBrandValue(str) {
8219
+ return str.replace(/\s+/g, "").toLowerCase();
8220
+ }
8221
+ function renderWordmarkSvg() {
8222
+ return `<svg width="430" height="102" viewBox="0 0 270 64" fill="none" xmlns="http://www.w3.org/2000/svg">
8223
+ <defs>
8224
+ <linearGradient id="ogWordmarkGradient" x1="286" y1="10" x2="320" y2="54" gradientUnits="userSpaceOnUse">
8225
+ <stop offset="0%" stop-color="#355cff"/>
8226
+ <stop offset="100%" stop-color="#74c7ff"/>
8227
+ </linearGradient>
8228
+ </defs>
8229
+ <text
8230
+ x="2"
8231
+ y="43"
8232
+ fill="#eff6ff"
8233
+ font-family="IBM Plex Sans, IBM Plex Mono, Avenir Next, Segoe UI, sans-serif"
8234
+ font-size="34"
8235
+ font-weight="700"
8236
+ letter-spacing="-1.4"
8237
+ >
8238
+ OXCONTENT
8239
+ </text>
8240
+ <text
8241
+ x="213"
8242
+ y="43.5"
8243
+ fill="#eff6ff"
8244
+ font-family="IBM Plex Sans, IBM Plex Mono, Avenir Next, Segoe UI, sans-serif"
8245
+ font-size="40"
8246
+ font-weight="400"
8247
+ >
8248
+ (
8249
+ </text>
8250
+ <g transform="translate(216 9) scale(0.089) rotate(-7 256 256)">
8251
+ <path
8252
+ d="M161 96H286C298 96 309 101 318 110L352 144C361 153 366 164 366 176V386C366 399 355 410 342 410H161C148 410 138 399 138 386V120C138 107 148 96 161 96Z"
8253
+ fill="url(#ogWordmarkGradient)"
8254
+ />
8255
+ </g>
8256
+ <text
8257
+ x="252"
8258
+ y="43.5"
8259
+ fill="#eff6ff"
8260
+ font-family="IBM Plex Sans, IBM Plex Mono, Avenir Next, Segoe UI, sans-serif"
8261
+ font-size="40"
8262
+ font-weight="400"
8263
+ >
8264
+ )
8265
+ </text>
8266
+ </svg>`;
8267
+ }
8133
8268
  /**
8134
8269
  * Returns the built-in default template function.
8135
8270
  */
8136
8271
  function getDefaultTemplate() {
8137
8272
  return function defaultTemplate(props) {
8138
- const { title, description, siteName, tags } = props;
8139
- const tagsHtml = tags?.length ? `<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:auto;">
8140
- ${tags.map((tag) => `<span style="background:rgba(255,255,255,0.15);color:#e2e8f0;padding:4px 12px;border-radius:16px;font-size:14px;">${escapeHtml$2(tag)}</span>`).join("")}
8141
- </div>` : "";
8142
- return `<div style="width:100%;height:100%;display:flex;flex-direction:column;justify-content:center;padding:60px 80px;background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%);font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
8143
- <div style="display:flex;flex-direction:column;gap:16px;flex:1;justify-content:center;">
8144
- <h1 style="font-size:56px;font-weight:700;color:#ffffff;line-height:1.2;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;">${escapeHtml$2(title)}</h1>
8145
- ${description ? `<p style="font-size:24px;color:#94a3b8;line-height:1.5;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${escapeHtml$2(description)}</p>` : ""}
8146
- </div>
8147
- <div style="display:flex;align-items:flex-end;justify-content:space-between;margin-top:auto;">
8148
- ${siteName ? `<span style="font-size:20px;color:#64748b;font-weight:500;">${escapeHtml$2(siteName)}</span>` : ""}
8149
- ${tagsHtml}
8273
+ const { title, description, siteName } = props;
8274
+ const rawBrand = siteName?.trim() ? siteName : "Ox Content";
8275
+ const isBrandCard = normalizeBrandValue(title) === normalizeBrandValue(rawBrand);
8276
+ const heroTitle = isBrandCard ? "cargo doc for JavaScript" : title;
8277
+ const heroDescription = isBrandCard ? "Rust-powered docs and high-performance Markdown tooling." : description && description.trim().length > 0 ? description : "Rust-powered docs and Markdown tooling.";
8278
+ const descriptionHtml = heroDescription.trim().length > 0 ? `<p style="max-width:760px;font-size:28px;color:#93a4c3;line-height:1.45;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${escapeHtml$2(heroDescription)}</p>` : "";
8279
+ return `<div style="width:100%;height:100%;position:relative;overflow:hidden;box-sizing:border-box;padding:56px 64px 52px;background:#0b1220;font-family:'IBM Plex Sans','Avenir Next','Segoe UI',system-ui,sans-serif;color:#eff6ff;border:1px solid #223252;border-top:4px solid #4f6fae;">
8280
+ <div style="position:relative;z-index:1;display:flex;flex-direction:column;height:100%;">
8281
+ <div style="display:flex;align-items:flex-start;">${renderWordmarkSvg()}</div>
8282
+ <div style="display:flex;flex-direction:column;justify-content:center;gap:24px;max-width:860px;flex:1;padding:22px 0 0;">
8283
+ <h1 style="font-size:78px;font-weight:700;color:#eff6ff;line-height:1.02;letter-spacing:-0.055em;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${escapeHtml$2(heroTitle)}</h1>
8284
+ ${descriptionHtml}
8285
+ </div>
8150
8286
  </div>
8151
8287
  </div>`;
8152
8288
  };
@@ -8860,31 +8996,32 @@ initIslands((el, props) => {
8860
8996
  const defaultTheme = {
8861
8997
  name: "default",
8862
8998
  colors: {
8863
- primary: "#e04d0a",
8864
- primaryHover: "#f5602a",
8999
+ primary: "#4f6fae",
9000
+ primaryHover: "#425f96",
8865
9001
  background: "#ffffff",
8866
- backgroundAlt: "#f8f9fa",
8867
- text: "#1a1a1a",
8868
- textMuted: "#666666",
8869
- border: "#e5e7eb",
8870
- codeBackground: "#1e293b",
8871
- codeText: "#e2e8f0"
9002
+ backgroundAlt: "#f5f7fb",
9003
+ text: "#131a30",
9004
+ textMuted: "#4f607b",
9005
+ border: "#d2dbea",
9006
+ codeBackground: "#101a31",
9007
+ codeText: "#edf3ff"
8872
9008
  },
8873
9009
  darkColors: {
8874
- primary: "#f5714a",
8875
- primaryHover: "#ff8a66",
8876
- background: "#141414",
8877
- backgroundAlt: "#141414",
8878
- text: "#e5e5e5",
8879
- textMuted: "#a3a3a3",
8880
- border: "#2a2a2a",
8881
- codeBackground: "#1a1a1a",
8882
- codeText: "#e5e5e5"
9010
+ primary: "#86a4da",
9011
+ primaryHover: "#a3bbe8",
9012
+ background: "#060816",
9013
+ backgroundAlt: "#0d1528",
9014
+ text: "#ebf2ff",
9015
+ textMuted: "#8ea0bf",
9016
+ border: "#223252",
9017
+ codeBackground: "#0a1020",
9018
+ codeText: "#e7f0ff"
8883
9019
  },
8884
9020
  fonts: {
8885
- sans: "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif",
8886
- mono: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace"
9021
+ sans: "\"IBM Plex Sans\", \"Avenir Next\", \"Segoe UI Variable\", \"Segoe UI\", sans-serif",
9022
+ mono: "\"IBM Plex Mono\", \"SFMono-Regular\", Consolas, monospace"
8887
9023
  },
9024
+ entryPage: { mode: "default" },
8888
9025
  layout: {
8889
9026
  sidebarWidth: "260px",
8890
9027
  headerHeight: "60px",
@@ -8892,6 +9029,9 @@ const defaultTheme = {
8892
9029
  },
8893
9030
  header: {
8894
9031
  logo: void 0,
9032
+ logoLight: void 0,
9033
+ logoDark: void 0,
9034
+ showSiteNameText: true,
8895
9035
  logoWidth: 28,
8896
9036
  logoHeight: 28
8897
9037
  },
@@ -8969,6 +9109,7 @@ function resolveTheme(config) {
8969
9109
  colors: merged.colors ?? defaultTheme.colors,
8970
9110
  darkColors: merged.darkColors ?? defaultTheme.darkColors,
8971
9111
  fonts: merged.fonts ?? defaultTheme.fonts,
9112
+ entryPage: merged.entryPage ?? defaultTheme.entryPage,
8972
9113
  layout: merged.layout ?? defaultTheme.layout,
8973
9114
  header: merged.header ?? defaultTheme.header,
8974
9115
  footer: merged.footer ?? defaultTheme.footer,
@@ -9009,13 +9150,17 @@ function themeToNapi(theme) {
9009
9150
  sans: theme.fonts.sans,
9010
9151
  mono: theme.fonts.mono
9011
9152
  } : void 0,
9153
+ entryPage: theme.entryPage.mode ? { mode: theme.entryPage.mode } : void 0,
9012
9154
  layout: theme.layout.sidebarWidth ? {
9013
9155
  sidebarWidth: theme.layout.sidebarWidth,
9014
9156
  headerHeight: theme.layout.headerHeight,
9015
9157
  maxContentWidth: theme.layout.maxContentWidth
9016
9158
  } : void 0,
9017
- header: theme.header.logo ? {
9159
+ header: theme.header.logo || theme.header.logoLight || theme.header.logoDark ? {
9018
9160
  logo: theme.header.logo,
9161
+ logoLight: theme.header.logoLight,
9162
+ logoDark: theme.header.logoDark,
9163
+ showSiteNameText: theme.header.showSiteNameText,
9019
9164
  logoWidth: theme.header.logoWidth,
9020
9165
  logoHeight: theme.header.logoHeight
9021
9166
  } : void 0,
@@ -9064,55 +9209,65 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9064
9209
  --sidebar-width: 260px;
9065
9210
  --header-height: 60px;
9066
9211
  --max-content-width: 960px;
9067
- --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
9068
- --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
9212
+ --font-sans: 'IBM Plex Sans', 'Avenir Next', 'Segoe UI Variable', 'Segoe UI', sans-serif;
9213
+ --font-mono: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;
9069
9214
  --color-bg: #ffffff;
9070
- --color-bg-alt: #f8f9fa;
9071
- --color-text: #1a1a1a;
9072
- --color-text-muted: #666666;
9073
- --color-border: #e5e7eb;
9074
- --color-primary: #b7410e;
9075
- --color-primary-hover: #ce5937;
9076
- --color-code-bg: #1e293b;
9077
- --color-code-text: #e2e8f0;
9215
+ --color-bg-alt: #f5f7fb;
9216
+ --color-text: #131a30;
9217
+ --color-text-muted: #4f607b;
9218
+ --color-border: #d2dbea;
9219
+ --color-primary: #4f6fae;
9220
+ --color-primary-hover: #425f96;
9221
+ --color-code-bg: #101a31;
9222
+ --color-code-bg-top: #18264a;
9223
+ --color-code-text: #edf3ff;
9078
9224
  --color-code-line-highlight: rgba(56, 189, 248, 0.16);
9079
9225
  --color-code-line-warning: rgba(245, 158, 11, 0.18);
9080
9226
  --color-code-line-warning-border: #f59e0b;
9081
9227
  --color-code-line-error: rgba(239, 68, 68, 0.18);
9082
9228
  --color-code-line-error-border: #ef4444;
9229
+ --color-code-frame-border: rgba(147, 166, 200, 0.46);
9230
+ --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");
9231
+ --surface-noise-size: 164px 164px;
9083
9232
  }
9084
9233
  [data-theme="dark"] {
9085
- --color-bg: #141414;
9086
- --color-bg-alt: #141414;
9087
- --color-text: #e5e5e5;
9088
- --color-text-muted: #a3a3a3;
9089
- --color-border: #2a2a2a;
9090
- --color-primary: #c9714a;
9091
- --color-primary-hover: #d4845f;
9092
- --color-code-bg: #1a1a1a;
9093
- --color-code-text: #e5e5e5;
9234
+ --color-bg: #060816;
9235
+ --color-bg-alt: #0d1528;
9236
+ --color-text: #ebf2ff;
9237
+ --color-text-muted: #8ea0bf;
9238
+ --color-border: #223252;
9239
+ --color-primary: #86a4da;
9240
+ --color-primary-hover: #a3bbe8;
9241
+ --color-code-bg: #0a1020;
9242
+ --color-code-bg-top: #0a1020;
9243
+ --color-code-text: #e7f0ff;
9094
9244
  --color-code-line-highlight: rgba(14, 165, 233, 0.2);
9095
9245
  --color-code-line-warning: rgba(245, 158, 11, 0.2);
9096
9246
  --color-code-line-warning-border: #f59e0b;
9097
9247
  --color-code-line-error: rgba(239, 68, 68, 0.22);
9098
9248
  --color-code-line-error-border: #f87171;
9249
+ --color-code-frame-border: rgba(34, 50, 82, 0.92);
9250
+ --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");
9099
9251
  }
9100
9252
  @media (prefers-color-scheme: dark) {
9101
9253
  :root:not([data-theme="light"]) {
9102
- --color-bg: #141414;
9103
- --color-bg-alt: #141414;
9104
- --color-text: #e5e5e5;
9105
- --color-text-muted: #a3a3a3;
9106
- --color-border: #2a2a2a;
9107
- --color-primary: #c9714a;
9108
- --color-primary-hover: #d4845f;
9109
- --color-code-bg: #1a1a1a;
9110
- --color-code-text: #e5e5e5;
9254
+ --color-bg: #060816;
9255
+ --color-bg-alt: #0d1528;
9256
+ --color-text: #ebf2ff;
9257
+ --color-text-muted: #8ea0bf;
9258
+ --color-border: #223252;
9259
+ --color-primary: #86a4da;
9260
+ --color-primary-hover: #a3bbe8;
9261
+ --color-code-bg: #0a1020;
9262
+ --color-code-bg-top: #0a1020;
9263
+ --color-code-text: #e7f0ff;
9111
9264
  --color-code-line-highlight: rgba(14, 165, 233, 0.2);
9112
9265
  --color-code-line-warning: rgba(245, 158, 11, 0.2);
9113
9266
  --color-code-line-warning-border: #f59e0b;
9114
9267
  --color-code-line-error: rgba(239, 68, 68, 0.22);
9115
9268
  --color-code-line-error-border: #f87171;
9269
+ --color-code-frame-border: rgba(34, 50, 82, 0.92);
9270
+ --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");
9116
9271
  }
9117
9272
  }
9118
9273
  * { box-sizing: border-box; margin: 0; padding: 0; }
@@ -9122,6 +9277,10 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9122
9277
  line-height: 1.7;
9123
9278
  color: var(--color-text);
9124
9279
  background: var(--color-bg);
9280
+ background-image: var(--surface-noise-image);
9281
+ background-size: var(--surface-noise-size);
9282
+ background-repeat: repeat;
9283
+ background-blend-mode: soft-light;
9125
9284
  }
9126
9285
  a { color: var(--color-primary); text-decoration: none; }
9127
9286
  a:hover { color: var(--color-primary-hover); text-decoration: underline; }
@@ -9140,6 +9299,18 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9140
9299
  padding: 0 1.5rem;
9141
9300
  z-index: 100;
9142
9301
  }
9302
+ .header,
9303
+ .sidebar,
9304
+ .search-modal,
9305
+ .mobile-footer,
9306
+ .content .ox-api-entry,
9307
+ .content .ox-api-module,
9308
+ .content blockquote.ox-callout {
9309
+ background-image: var(--surface-noise-image);
9310
+ background-size: var(--surface-noise-size);
9311
+ background-repeat: repeat;
9312
+ background-blend-mode: soft-light;
9313
+ }
9143
9314
  .header-title {
9144
9315
  font-size: 1.25rem;
9145
9316
  font-weight: 600;
@@ -9164,7 +9335,7 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9164
9335
  padding: 0.5rem 0.75rem;
9165
9336
  background: var(--color-bg-alt);
9166
9337
  border: 1px solid var(--color-border);
9167
- border-radius: 6px;
9338
+ border-radius: 4px;
9168
9339
  color: var(--color-text-muted);
9169
9340
  cursor: pointer;
9170
9341
  font-size: 0.875rem;
@@ -9190,7 +9361,6 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9190
9361
  inset: 0;
9191
9362
  z-index: 200;
9192
9363
  background: rgba(0,0,0,0.6);
9193
- backdrop-filter: blur(4px);
9194
9364
  justify-content: center;
9195
9365
  padding-top: 10vh;
9196
9366
  }
@@ -9201,9 +9371,8 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9201
9371
  margin: 0 1rem;
9202
9372
  background: var(--color-bg);
9203
9373
  border: 1px solid var(--color-border);
9204
- border-radius: 12px;
9374
+ border-radius: 4px;
9205
9375
  overflow: hidden;
9206
- box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4);
9207
9376
  max-height: 70vh;
9208
9377
  display: flex;
9209
9378
  flex-direction: column;
@@ -9243,7 +9412,7 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9243
9412
  .search-result {
9244
9413
  display: block;
9245
9414
  padding: 0.75rem 1rem;
9246
- border-radius: 8px;
9415
+ border-radius: 4px;
9247
9416
  color: var(--color-text);
9248
9417
  text-decoration: none;
9249
9418
  }
@@ -9274,7 +9443,7 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9274
9443
  border: none;
9275
9444
  cursor: pointer;
9276
9445
  padding: 0.5rem;
9277
- border-radius: 6px;
9446
+ border-radius: 4px;
9278
9447
  color: var(--color-text-muted);
9279
9448
  transition: background 0.15s, color 0.15s;
9280
9449
  }
@@ -9303,38 +9472,50 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9303
9472
  left: 0;
9304
9473
  bottom: 0;
9305
9474
  width: var(--sidebar-width);
9306
- background: var(--color-bg-alt);
9307
- border-right: 1px solid var(--color-border);
9475
+ background: color-mix(in srgb, var(--color-bg-alt) 16%, var(--color-bg));
9476
+ border-right: 1px solid color-mix(in srgb, var(--color-border) 48%, transparent);
9308
9477
  overflow-y: auto;
9309
- padding: 1.5rem 1rem;
9478
+ padding: 1rem 0.875rem 1.5rem;
9479
+ }
9480
+ .sidebar--entry { display: none; }
9481
+ .sidebar nav {
9482
+ display: flex;
9483
+ flex-direction: column;
9484
+ gap: 1rem;
9310
9485
  }
9311
- .nav-section { margin-bottom: 1.5rem; }
9486
+ .nav-section { margin-bottom: 0; }
9312
9487
  .nav-title {
9313
- font-size: 0.75rem;
9488
+ font-size: 0.6875rem;
9314
9489
  font-weight: 600;
9315
9490
  text-transform: uppercase;
9316
- letter-spacing: 0.05em;
9491
+ letter-spacing: 0.08em;
9317
9492
  color: var(--color-text-muted);
9318
- margin-bottom: 0.5rem;
9319
- padding: 0 0.75rem;
9493
+ margin-bottom: 0.4rem;
9494
+ padding: 0 0.625rem;
9495
+ }
9496
+ .nav-list {
9497
+ list-style: none;
9498
+ display: flex;
9499
+ flex-direction: column;
9500
+ gap: 0.125rem;
9320
9501
  }
9321
- .nav-list { list-style: none; }
9322
- .nav-item { margin: 0.125rem 0; }
9502
+ .nav-item { margin: 0; }
9323
9503
  .nav-link {
9324
9504
  display: block;
9325
- padding: 0.5rem 0.75rem;
9326
- border-radius: 6px;
9327
- color: var(--color-text);
9505
+ padding: 0.45rem 0.625rem;
9506
+ border-radius: 0;
9507
+ color: color-mix(in srgb, var(--color-text) 92%, var(--color-text-muted));
9328
9508
  font-size: 0.875rem;
9329
- transition: background 0.15s;
9330
9509
  }
9331
9510
  .nav-link:hover {
9332
- background: var(--color-border);
9511
+ background: color-mix(in srgb, var(--color-bg-alt) 58%, transparent);
9512
+ color: var(--color-text);
9333
9513
  text-decoration: none;
9334
9514
  }
9335
9515
  .nav-link.active {
9336
- background: var(--color-primary);
9337
- color: white;
9516
+ background: color-mix(in srgb, var(--color-bg-alt) 72%, transparent);
9517
+ color: var(--color-text);
9518
+ font-weight: 600;
9338
9519
  }
9339
9520
 
9340
9521
  /* Main content */
@@ -9389,8 +9570,31 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9389
9570
  padding: 0.5rem 1rem;
9390
9571
  margin: 1rem 0;
9391
9572
  background: var(--color-bg-alt);
9392
- border-radius: 0 6px 6px 0;
9573
+ border-radius: 0 4px 4px 0;
9574
+ }
9575
+ .content blockquote.ox-callout {
9576
+ --callout-accent: var(--color-primary);
9577
+ border-left-width: 3px;
9578
+ border-left-color: var(--callout-accent);
9579
+ padding: 0.9rem 1rem;
9580
+ border-radius: 4px;
9581
+ background: color-mix(in srgb, var(--color-bg-alt) 92%, var(--callout-accent) 8%);
9393
9582
  }
9583
+ .content blockquote.ox-callout.ox-callout--note,
9584
+ .content blockquote.ox-callout.ox-callout--important { --callout-accent: var(--color-primary); }
9585
+ .content blockquote.ox-callout.ox-callout--tip { --callout-accent: #0891b2; }
9586
+ .content blockquote.ox-callout.ox-callout--warning { --callout-accent: #d97706; }
9587
+ .content blockquote.ox-callout.ox-callout--caution { --callout-accent: #dc2626; }
9588
+ .content .ox-callout-title {
9589
+ margin: 0 0 0.5rem;
9590
+ font-size: 0.75rem;
9591
+ font-weight: 700;
9592
+ letter-spacing: 0.08em;
9593
+ text-transform: uppercase;
9594
+ color: var(--callout-accent, var(--color-primary));
9595
+ }
9596
+ .content blockquote.ox-callout > :last-child { margin-bottom: 0; }
9597
+ .content blockquote.ox-callout > :not(.ox-callout-title):first-of-type { margin-top: 0; }
9394
9598
  .content code {
9395
9599
  font-family: var(--font-mono);
9396
9600
  font-size: 0.875em;
@@ -9400,18 +9604,26 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9400
9604
  word-break: break-all;
9401
9605
  }
9402
9606
  .content pre {
9403
- background: var(--color-code-bg);
9607
+ background: linear-gradient(
9608
+ 180deg,
9609
+ var(--color-code-bg-top) 0,
9610
+ var(--color-code-bg) 3.5rem
9611
+ ) !important;
9404
9612
  color: var(--color-code-text);
9405
9613
  padding: 1rem 1.25rem;
9406
- border-radius: 8px;
9614
+ border-radius: 4px;
9615
+ border: 1px solid var(--color-code-frame-border);
9407
9616
  overflow-x: auto;
9408
9617
  margin: 1.5rem 0;
9409
9618
  line-height: 1.5;
9410
9619
  }
9411
9620
  .content pre code {
9412
9621
  background: transparent;
9622
+ border: 0;
9413
9623
  padding: 0;
9624
+ border-radius: 0;
9414
9625
  font-size: 0.8125rem;
9626
+ word-break: normal;
9415
9627
  }
9416
9628
  .content pre.ox-code-block code {
9417
9629
  display: block;
@@ -9426,11 +9638,11 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9426
9638
  }
9427
9639
  .content pre.ox-code-block .ox-code-line--warning {
9428
9640
  background: var(--color-code-line-warning);
9429
- box-shadow: inset 3px 0 0 var(--color-code-line-warning-border);
9641
+ border-left: 3px solid var(--color-code-line-warning-border);
9430
9642
  }
9431
9643
  .content pre.ox-code-block .ox-code-line--error {
9432
9644
  background: var(--color-code-line-error);
9433
- box-shadow: inset 3px 0 0 var(--color-code-line-error-border);
9645
+ border-left: 3px solid var(--color-code-line-error-border);
9434
9646
  }
9435
9647
  .content table {
9436
9648
  width: 100%;
@@ -9444,21 +9656,355 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9444
9656
  text-align: left;
9445
9657
  }
9446
9658
  .content th { background: var(--color-bg-alt); font-weight: 600; }
9447
- .content img { max-width: 100%; height: auto; border-radius: 8px; display: block; }
9659
+ .content img { max-width: 100%; height: auto; border-radius: 4px; display: block; }
9448
9660
  .content img[alt*="Logo"] { max-width: 200px; display: block; margin: 1rem 0; }
9449
9661
  .content img[alt*="Architecture"] { max-width: 600px; }
9450
9662
  .content img[alt*="Benchmark"] { max-width: 680px; }
9451
9663
  .content hr { border: none; border-top: 1px solid var(--color-border); margin: 2rem 0; }
9664
+ .content .ox-api-controls {
9665
+ display: flex;
9666
+ justify-content: flex-end;
9667
+ align-items: center;
9668
+ gap: 0.5rem;
9669
+ margin: 0 0 1rem;
9670
+ }
9671
+ .content .ox-api-controls__button {
9672
+ appearance: none;
9673
+ border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);
9674
+ background: color-mix(in srgb, var(--color-bg-alt) 82%, var(--color-bg));
9675
+ padding: 0.4rem 0.7rem;
9676
+ border-radius: 4px;
9677
+ color: color-mix(in srgb, var(--color-text) 82%, var(--color-text-muted));
9678
+ font-family: var(--font-mono);
9679
+ font-size: 0.78rem;
9680
+ font-weight: 600;
9681
+ line-height: 1.4;
9682
+ cursor: pointer;
9683
+ }
9684
+ .content .ox-api-controls__button:hover {
9685
+ color: var(--color-primary);
9686
+ border-color: color-mix(in srgb, var(--color-primary) 38%, var(--color-border));
9687
+ background: color-mix(in srgb, var(--color-bg-alt) 68%, var(--color-primary) 6%);
9688
+ }
9689
+ .content .ox-api-entry,
9690
+ .content .ox-api-module {
9691
+ margin: 0;
9692
+ border: 0;
9693
+ border-top: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
9694
+ border-radius: 0;
9695
+ background: transparent;
9696
+ overflow: visible;
9697
+ }
9698
+ .content .ox-api-entry:last-child,
9699
+ .content .ox-api-module:last-child {
9700
+ border-bottom: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
9701
+ }
9702
+ .content .ox-api-entry summary,
9703
+ .content .ox-api-module summary {
9704
+ list-style: none;
9705
+ cursor: pointer;
9706
+ padding: 1rem 0;
9707
+ position: relative;
9708
+ }
9709
+ .content .ox-api-entry summary::-webkit-details-marker,
9710
+ .content .ox-api-module summary::-webkit-details-marker { display: none; }
9711
+ .content .ox-api-entry summary {
9712
+ display: grid;
9713
+ grid-template-columns: var(--octc-api-kind-width, 6.5rem) minmax(0, 1fr) auto;
9714
+ align-items: start;
9715
+ gap: 0.95rem;
9716
+ }
9717
+ .content .ox-api-entry summary::after,
9718
+ .content .ox-api-module summary::after {
9719
+ content: "+";
9720
+ align-self: center;
9721
+ color: var(--color-text-muted);
9722
+ font-family: var(--font-mono);
9723
+ font-size: 0.95rem;
9724
+ font-weight: 600;
9725
+ line-height: 1;
9726
+ }
9727
+ .content .ox-api-entry[open] summary::after,
9728
+ .content .ox-api-module[open] summary::after {
9729
+ content: "−";
9730
+ color: var(--color-primary);
9731
+ }
9732
+ .content .ox-api-entry[open] summary,
9733
+ .content .ox-api-module[open] summary {
9734
+ border-bottom: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
9735
+ }
9736
+ .content .ox-api-entry__kind,
9737
+ .content .ox-api-module__kind {
9738
+ display: block;
9739
+ width: var(--octc-api-kind-width, 6.5rem);
9740
+ padding: 0.3rem 0 0;
9741
+ background: transparent;
9742
+ border: 0;
9743
+ font-family: var(--font-mono);
9744
+ font-size: 0.76rem;
9745
+ font-weight: 600;
9746
+ letter-spacing: 0.01em;
9747
+ text-align: left;
9748
+ white-space: nowrap;
9749
+ color: var(--color-text-muted);
9750
+ }
9751
+ .content .ox-api-module__count {
9752
+ display: inline-flex;
9753
+ align-items: center;
9754
+ padding: 0.2rem 0.48rem;
9755
+ border-radius: 4px;
9756
+ background: color-mix(in srgb, var(--color-bg-alt) 84%, var(--color-primary) 8%);
9757
+ border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);
9758
+ color: var(--color-text-muted);
9759
+ font-family: var(--font-mono);
9760
+ font-size: 0.72rem;
9761
+ font-weight: 600;
9762
+ letter-spacing: 0.03em;
9763
+ white-space: nowrap;
9764
+ }
9765
+ .content .ox-api-entry__name {
9766
+ display: block;
9767
+ font-family: var(--font-mono);
9768
+ font-size: 0.95rem;
9769
+ font-weight: 600;
9770
+ line-height: 1.55;
9771
+ }
9772
+ .content .ox-api-entry__signature,
9773
+ .content .ox-api-module__signature {
9774
+ display: block;
9775
+ width: 100%;
9776
+ min-width: 0;
9777
+ font-family: var(--font-mono);
9778
+ font-size: 0.95rem;
9779
+ line-height: 1.55;
9780
+ white-space: nowrap;
9781
+ overflow-x: auto;
9782
+ overflow-y: hidden;
9783
+ -webkit-overflow-scrolling: touch;
9784
+ }
9785
+ .content .ox-api-entry__description {
9786
+ display: block;
9787
+ color: color-mix(in srgb, var(--color-text) 78%, var(--color-text-muted));
9788
+ font-size: 0.9rem;
9789
+ line-height: 1.6;
9790
+ max-width: 72ch;
9791
+ }
9792
+ .content .ox-api-entry__summary-main {
9793
+ min-width: 0;
9794
+ display: flex;
9795
+ flex-direction: column;
9796
+ gap: 0.55rem;
9797
+ }
9798
+ .content .ox-api-entry__name,
9799
+ .content .ox-api-entry__signature,
9800
+ .content .ox-api-module__name,
9801
+ .content .ox-api-module__signature {
9802
+ background: transparent;
9803
+ padding: 0;
9804
+ border-radius: 0;
9805
+ word-break: normal;
9806
+ }
9807
+ .content code.shiki-inline.ox-api-entry__signature--highlighted,
9808
+ .content code.shiki-inline.ox-api-module__signature--highlighted {
9809
+ display: block;
9810
+ width: 100%;
9811
+ max-width: 100%;
9812
+ background: linear-gradient(
9813
+ 180deg,
9814
+ var(--color-code-bg-top) 0,
9815
+ var(--color-code-bg) 2.75rem
9816
+ ) !important;
9817
+ border: 1px solid var(--color-code-frame-border) !important;
9818
+ padding: 0.55rem 0.7rem !important;
9819
+ border-radius: 4px !important;
9820
+ white-space: nowrap;
9821
+ overflow-x: auto;
9822
+ overflow-y: hidden;
9823
+ -webkit-overflow-scrolling: touch;
9824
+ }
9825
+ .content code.shiki-inline.ox-api-entry__signature--highlighted .line,
9826
+ .content code.shiki-inline.ox-api-module__signature--highlighted .line {
9827
+ display: block;
9828
+ width: max-content;
9829
+ min-width: 100%;
9830
+ }
9831
+ .content .ox-api-entry__body,
9832
+ .content .ox-api-module__body { padding: 0.7rem 0 1.9rem; }
9833
+ .content .ox-api-entry__body {
9834
+ margin-left: calc(var(--octc-api-kind-width, 6.5rem) + 0.95rem);
9835
+ margin-top: 0.7rem;
9836
+ padding: 1.45rem 1rem 2.1rem 1.1rem;
9837
+ border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
9838
+ border-radius: 4px;
9839
+ background: color-mix(in srgb, var(--color-bg-alt) 68%, transparent);
9840
+ }
9841
+ .content .ox-api-entry__body > :first-child { margin-top: 0; }
9842
+ .content .ox-api-entry__body > :last-child { margin-bottom: 0; }
9843
+ .content .ox-api-entry[open] summary {
9844
+ padding-bottom: 0.35rem;
9845
+ }
9846
+ .content .ox-api-entry__section {
9847
+ display: grid;
9848
+ grid-template-columns: 6.5rem minmax(0, 1fr);
9849
+ gap: 0.2rem 1.25rem;
9850
+ align-items: start;
9851
+ margin-top: 1rem;
9852
+ padding-top: 1rem;
9853
+ border-top: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
9854
+ }
9855
+ .content .ox-api-entry__section h4 {
9856
+ margin-top: 0;
9857
+ margin-bottom: 0;
9858
+ padding-top: 0.25rem;
9859
+ font-family: var(--font-mono);
9860
+ font-size: 0.78rem;
9861
+ font-weight: 700;
9862
+ letter-spacing: 0.04em;
9863
+ text-transform: uppercase;
9864
+ color: var(--color-text-muted);
9865
+ }
9866
+ .content .ox-api-entry__section > :not(h4) { min-width: 0; }
9867
+ .content .ox-api-entry__source {
9868
+ margin: 0 0 0.15rem;
9869
+ font-family: var(--font-mono);
9870
+ font-size: 0.78rem;
9871
+ color: var(--color-text-muted);
9872
+ }
9873
+ .content .ox-api-entry__source a {
9874
+ color: inherit;
9875
+ text-decoration-color: color-mix(in srgb, var(--color-text-muted) 38%, transparent);
9876
+ }
9877
+ .content .ox-api-entry__tags,
9878
+ .content .ox-api-module__list {
9879
+ list-style: none;
9880
+ padding-left: 0;
9881
+ margin: 0;
9882
+ }
9883
+ .content .ox-api-entry__tags {
9884
+ display: flex;
9885
+ flex-wrap: wrap;
9886
+ gap: 0.6rem;
9887
+ }
9888
+ .content .ox-api-entry__tags li {
9889
+ display: inline-flex;
9890
+ align-items: center;
9891
+ gap: 0.45rem;
9892
+ padding: 0.4rem 0.55rem;
9893
+ border: 1px solid color-mix(in srgb, var(--color-border) 80%, transparent);
9894
+ border-radius: 4px;
9895
+ background: color-mix(in srgb, var(--color-bg-alt) 72%, transparent);
9896
+ }
9897
+ .content .ox-api-module__list li {
9898
+ display: grid;
9899
+ grid-template-columns: var(--octc-api-kind-width, 6.5rem) minmax(0, 1fr);
9900
+ align-items: start;
9901
+ gap: 1rem;
9902
+ padding: 0.85rem 0;
9903
+ border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
9904
+ }
9905
+ .content .ox-api-module__list li:first-child { border-top: none; }
9906
+ .content .ox-api-entry__tag-name,
9907
+ .content .ox-api-module__title { font-weight: 700; }
9908
+ .content .ox-api-entry__tag-name {
9909
+ color: var(--color-primary);
9910
+ font-family: var(--font-mono);
9911
+ font-size: 0.74rem;
9912
+ }
9913
+ .content .ox-api-entry__tag-value {
9914
+ color: var(--color-text);
9915
+ font-size: 0.84rem;
9916
+ line-height: 1.45;
9917
+ }
9918
+ .content .ox-api-module summary {
9919
+ display: grid;
9920
+ grid-template-columns: minmax(0, 1fr) auto auto;
9921
+ align-items: center;
9922
+ gap: 0.9rem;
9923
+ }
9924
+ .content .ox-api-module__body { padding-top: 0.15rem; }
9925
+ .content .ox-api-module__item { min-width: 0; }
9926
+ .content .ox-api-module__link {
9927
+ display: block;
9928
+ text-decoration: none;
9929
+ }
9930
+ .content .ox-api-module__link:hover { text-decoration: none; }
9931
+ .content .ox-api-module__name,
9932
+ .content .ox-api-module__signature {
9933
+ display: block;
9934
+ font-family: var(--font-mono);
9935
+ font-size: 0.91rem;
9936
+ line-height: 1.55;
9937
+ color: var(--color-text);
9938
+ }
9939
+ .content .ox-api-module__summary {
9940
+ display: block;
9941
+ margin-top: 0.45rem;
9942
+ color: color-mix(in srgb, var(--color-text) 76%, var(--color-text-muted));
9943
+ font-size: 0.88rem;
9944
+ line-height: 1.55;
9945
+ }
9946
+ .content .ox-api-entry__section--examples pre {
9947
+ margin: 0;
9948
+ border: 1px solid var(--color-code-frame-border);
9949
+ border-radius: 4px;
9950
+ }
9951
+ .content .ox-api-entry__params {
9952
+ list-style: none;
9953
+ padding: 0;
9954
+ margin: 0;
9955
+ }
9956
+ .content .ox-api-entry__param {
9957
+ padding: 0.8rem 0;
9958
+ border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
9959
+ }
9960
+ .content .ox-api-entry__param:first-child {
9961
+ padding-top: 0;
9962
+ border-top: 0;
9963
+ }
9964
+ .content .ox-api-entry__param-heading {
9965
+ display: flex;
9966
+ flex-wrap: wrap;
9967
+ align-items: center;
9968
+ gap: 0.5rem;
9969
+ }
9970
+ .content .ox-api-entry__param-name,
9971
+ .content .ox-api-entry__return-type {
9972
+ font-family: var(--font-mono);
9973
+ font-size: 0.84rem;
9974
+ font-weight: 600;
9975
+ color: var(--color-text);
9976
+ background: color-mix(in srgb, var(--color-bg-alt) 84%, transparent);
9977
+ border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);
9978
+ padding: 0.28rem 0.42rem;
9979
+ border-radius: 4px;
9980
+ }
9981
+ .content .ox-api-entry__param-type {
9982
+ font-family: var(--font-mono);
9983
+ font-size: 0.78rem;
9984
+ color: var(--color-code-text);
9985
+ background: color-mix(in srgb, var(--color-code-bg) 96%, #243556);
9986
+ border: 1px solid color-mix(in srgb, var(--color-code-bg) 82%, var(--color-border));
9987
+ padding: 0.26rem 0.42rem;
9988
+ border-radius: 4px;
9989
+ }
9990
+ .content .ox-api-entry__param-description,
9991
+ .content .ox-api-entry__return-description {
9992
+ margin: 0.55rem 0 0;
9993
+ font-size: 0.88rem;
9994
+ line-height: 1.6;
9995
+ color: color-mix(in srgb, var(--color-text) 78%, var(--color-text-muted));
9996
+ }
9997
+ .content .ox-api-entry__return { margin: 0; }
9452
9998
 
9453
9999
  /* Responsive */
9454
10000
  @media (max-width: 768px) {
9455
10001
  .menu-toggle { display: block; }
9456
10002
  .sidebar {
9457
10003
  transform: translateX(-100%);
9458
- transition: transform 0.3s ease;
9459
10004
  z-index: 99;
9460
10005
  width: 280px;
9461
10006
  }
10007
+ .sidebar--entry { display: block; }
9462
10008
  .sidebar.open { transform: translateX(0); }
9463
10009
  .main { margin-left: 0; padding: 1rem 0.75rem; }
9464
10010
  .content { padding: 0 0.25rem; }
@@ -9475,6 +10021,30 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9475
10021
  overflow-x: auto;
9476
10022
  -webkit-overflow-scrolling: touch;
9477
10023
  }
10024
+ .content .ox-api-controls {
10025
+ justify-content: flex-start;
10026
+ gap: 0.75rem;
10027
+ }
10028
+ .content .ox-api-entry__body {
10029
+ margin-left: 0;
10030
+ margin-top: 0.55rem;
10031
+ padding: 1.2rem 0 1.7rem;
10032
+ border-left: 0;
10033
+ border-right: 0;
10034
+ border-bottom: 0;
10035
+ border-radius: 0;
10036
+ background: transparent;
10037
+ }
10038
+ .content .ox-api-entry__section {
10039
+ grid-template-columns: 1fr;
10040
+ gap: 0.5rem;
10041
+ }
10042
+ .content .ox-api-entry__tags li,
10043
+ .content .ox-api-module__list li,
10044
+ .content .ox-api-module summary {
10045
+ grid-template-columns: 1fr;
10046
+ }
10047
+ .content .ox-api-entry__signature { width: 100%; }
9478
10048
  .content pre.ox-code-block .line {
9479
10049
  margin: 0 -0.75rem;
9480
10050
  padding: 0 0.75rem;
@@ -9482,13 +10052,17 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9482
10052
  .content code { font-size: 0.8125em; }
9483
10053
  .content table {
9484
10054
  display: block;
10055
+ width: max-content;
10056
+ min-width: 100%;
10057
+ max-width: calc(100vw - 1.5rem);
9485
10058
  overflow-x: auto;
9486
10059
  -webkit-overflow-scrolling: touch;
9487
10060
  font-size: 0.8125rem;
9488
- margin: 1rem -0.75rem;
9489
- width: calc(100% + 1.5rem);
10061
+ margin: 1rem 0;
10062
+ border-collapse: separate;
10063
+ border-spacing: 0;
9490
10064
  }
9491
- .content th, .content td { padding: 0.5rem 0.75rem; white-space: nowrap; }
10065
+ .content th, .content td { padding: 0.5rem 0.75rem; white-space: nowrap; vertical-align: top; }
9492
10066
  .content img { margin: 1rem 0; }
9493
10067
  .content img[alt*="Logo"] { max-width: 150px; }
9494
10068
  .content img[alt*="Architecture"] { max-width: 100%; }
@@ -9496,12 +10070,13 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9496
10070
  .content blockquote { padding: 0.5rem 0.75rem; margin: 1rem 0; font-size: 0.9375rem; }
9497
10071
  .header { padding: 0 1rem; }
9498
10072
  .header-title { font-size: 1rem; }
9499
- .header-title img { width: 24px; height: 24px; }
10073
+ .header-title:not(.header-title--logo-only) img { width: 24px; height: 24px; }
10074
+ .header-title--logo-only .header-logo { width: 152px; height: auto; }
9500
10075
  .overlay {
9501
10076
  display: none;
9502
10077
  position: fixed;
9503
10078
  inset: 0;
9504
- background: rgba(0,0,0,0.5);
10079
+ background: transparent;
9505
10080
  z-index: 98;
9506
10081
  }
9507
10082
  .overlay.open { display: block; }
@@ -9512,7 +10087,7 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9512
10087
  .main { padding: 0.75rem 0.5rem; }
9513
10088
  .content h1 { font-size: 1.35rem; }
9514
10089
  .content pre { font-size: 0.6875rem; padding: 0.625rem; }
9515
- .content table { font-size: 0.75rem; }
10090
+ .content table { max-width: calc(100vw - 1rem); font-size: 0.75rem; }
9516
10091
  .content th, .content td { padding: 0.375rem 0.5rem; }
9517
10092
  }
9518
10093
  </style>
@@ -9565,7 +10140,7 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9565
10140
  </div>
9566
10141
  <div class="overlay"></div>
9567
10142
  <div class="layout">
9568
- <aside class="sidebar">
10143
+ <aside class="sidebar{{#entryPage}} sidebar--entry{{/entryPage}}">
9569
10144
  <nav>
9570
10145
  {{navigation}}
9571
10146
  </nav>
@@ -9619,6 +10194,22 @@ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
9619
10194
  });
9620
10195
  }
9621
10196
 
10197
+ document.querySelectorAll('.ox-api-controls').forEach((controls) => {
10198
+ const targetSelector = controls.getAttribute('data-ox-api-target');
10199
+ if (!targetSelector) return;
10200
+
10201
+ controls.querySelectorAll('[data-ox-api-toggle]').forEach((button) => {
10202
+ button.addEventListener('click', () => {
10203
+ const shouldOpen = button.getAttribute('data-ox-api-toggle') === 'expand';
10204
+ document.querySelectorAll(targetSelector).forEach((entry) => {
10205
+ if (entry instanceof HTMLDetailsElement) {
10206
+ entry.open = shouldOpen;
10207
+ }
10208
+ });
10209
+ });
10210
+ });
10211
+ });
10212
+
9622
10213
  // Search functionality
9623
10214
  const searchButton = document.querySelector('.search-button');
9624
10215
  const searchOverlay = document.querySelector('.search-modal-overlay');
@@ -9951,8 +10542,14 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
9951
10542
  name: pageData.entryPage.hero.name,
9952
10543
  text: pageData.entryPage.hero.text,
9953
10544
  tagline: pageData.entryPage.hero.tagline,
10545
+ notice: pageData.entryPage.hero.notice ? {
10546
+ title: pageData.entryPage.hero.notice.title,
10547
+ body: pageData.entryPage.hero.notice.body
10548
+ } : void 0,
9954
10549
  image: pageData.entryPage.hero.image ? {
9955
10550
  src: pageData.entryPage.hero.image.src,
10551
+ lightSrc: pageData.entryPage.hero.image.lightSrc,
10552
+ darkSrc: pageData.entryPage.hero.image.darkSrc,
9956
10553
  alt: pageData.entryPage.hero.image.alt,
9957
10554
  width: pageData.entryPage.hero.image.width,
9958
10555
  height: pageData.entryPage.hero.image.height
@@ -11067,43 +11664,41 @@ function renderViewerHtml(pages, options) {
11067
11664
  <style>
11068
11665
  :root {
11069
11666
  --bg: #ffffff;
11070
- --bg-card: #f8f9fa;
11667
+ --bg-card: #f5f7fb;
11071
11668
  --bg-preview: #ffffff;
11072
- --text: #1a1a2e;
11073
- --text-muted: #6b7280;
11074
- --border: #e5e7eb;
11075
- --accent: #e8590c;
11076
- --accent-light: #fff4e6;
11669
+ --text: #131a30;
11670
+ --text-muted: #4f607b;
11671
+ --border: #d2dbea;
11672
+ --accent: #4f6fae;
11673
+ --accent-light: #eef2fa;
11077
11674
  --error: #dc2626;
11078
11675
  --error-bg: #fef2f2;
11079
11676
  --warning: #d97706;
11080
11677
  --warning-bg: #fffbeb;
11081
11678
  --success: #16a34a;
11082
- --tag-bg: #f0f0f0;
11083
- --shadow: 0 1px 3px rgba(0,0,0,0.08);
11084
- --radius: 8px;
11679
+ --tag-bg: #ecf3ff;
11680
+ --radius: 16px;
11085
11681
  }
11086
11682
  @media (prefers-color-scheme: dark) {
11087
11683
  :root {
11088
- --bg: #0f172a;
11089
- --bg-card: #1e293b;
11090
- --bg-preview: #334155;
11091
- --text: #e2e8f0;
11092
- --text-muted: #94a3b8;
11093
- --border: #334155;
11094
- --accent: #fb923c;
11095
- --accent-light: #431407;
11684
+ --bg: #060816;
11685
+ --bg-card: #0d1528;
11686
+ --bg-preview: #10172d;
11687
+ --text: #ebf2ff;
11688
+ --text-muted: #8ea0bf;
11689
+ --border: #223252;
11690
+ --accent: #86a4da;
11691
+ --accent-light: #151730;
11096
11692
  --error: #f87171;
11097
11693
  --error-bg: #450a0a;
11098
11694
  --warning: #fbbf24;
11099
11695
  --warning-bg: #451a03;
11100
11696
  --success: #4ade80;
11101
- --tag-bg: #334155;
11102
- --shadow: 0 1px 3px rgba(0,0,0,0.3);
11697
+ --tag-bg: #131b33;
11103
11698
  }
11104
11699
  }
11105
11700
  * { margin: 0; padding: 0; box-sizing: border-box; }
11106
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); }
11701
+ body { font-family: 'IBM Plex Sans', 'Avenir Next', 'Segoe UI', system-ui, sans-serif; background: radial-gradient(circle at top left, rgba(79,111,174,0.10), transparent 24%), radial-gradient(circle at 85% 14%, rgba(145,237,233,0.08), transparent 22%), var(--bg); color: var(--text); }
11107
11702
  .header { padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; }
11108
11703
  .header svg { width: 28px; height: 28px; color: var(--accent); }
11109
11704
  .header h1 { font-size: 18px; font-weight: 600; }
@@ -11124,7 +11719,7 @@ function renderViewerHtml(pages, options) {
11124
11719
  .search-input { padding: 6px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: 13px; flex: 1; min-width: 200px; }
11125
11720
  .search-input::placeholder { color: var(--text-muted); }
11126
11721
  .container { padding: 24px; display: flex; flex-direction: column; gap: 20px; max-width: 1200px; margin: 0 auto; }
11127
- .card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-card); box-shadow: var(--shadow); overflow: hidden; }
11722
+ .card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-card); overflow: hidden; }
11128
11723
  .card-header { padding: 16px; border-bottom: 1px solid var(--border); }
11129
11724
  .card-path { font-size: 12px; color: var(--text-muted); font-family: monospace; margin-bottom: 4px; }
11130
11725
  .card-title { font-size: 16px; font-weight: 600; }
@@ -12019,13 +12614,16 @@ function DefaultTheme({ children }) {
12019
12614
  ${page.description ? `<meta name="description" content="${escapeHtml(page.description)}">` : ""}
12020
12615
  <style>
12021
12616
  :root {
12022
- --octc-color-primary: #e04d0a;
12023
- --octc-color-text: #1a1a1a;
12617
+ --octc-color-primary: #4f6fae;
12618
+ --octc-color-text: #131a30;
12024
12619
  --octc-color-bg: #ffffff;
12620
+ --octc-color-bg-alt: #f5f7fb;
12621
+ --octc-color-text-muted: #4f607b;
12622
+ --octc-color-border: #d2dbea;
12025
12623
  }
12026
12624
  body {
12027
- font-family: system-ui, sans-serif;
12028
- line-height: 1.6;
12625
+ font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI Variable", "Segoe UI", sans-serif;
12626
+ line-height: 1.7;
12029
12627
  color: var(--octc-color-text);
12030
12628
  background: var(--octc-color-bg);
12031
12629
  max-width: 800px;