@commentray/render 0.0.4 → 0.0.6

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 (49) hide show
  1. package/dist/block-stretch-layout.d.ts.map +1 -1
  2. package/dist/block-stretch-layout.js +11 -23
  3. package/dist/block-stretch-layout.js.map +1 -1
  4. package/dist/browse-page-slug.d.ts +6 -0
  5. package/dist/browse-page-slug.d.ts.map +1 -0
  6. package/dist/browse-page-slug.js +9 -0
  7. package/dist/browse-page-slug.js.map +1 -0
  8. package/dist/build-commentray-nav-search.d.ts +22 -6
  9. package/dist/build-commentray-nav-search.d.ts.map +1 -1
  10. package/dist/build-commentray-nav-search.js +55 -36
  11. package/dist/build-commentray-nav-search.js.map +1 -1
  12. package/dist/build-stamp.d.ts +6 -0
  13. package/dist/build-stamp.d.ts.map +1 -0
  14. package/dist/build-stamp.js +23 -0
  15. package/dist/build-stamp.js.map +1 -0
  16. package/dist/code-browser-block-rays.d.ts +48 -0
  17. package/dist/code-browser-block-rays.d.ts.map +1 -0
  18. package/dist/code-browser-block-rays.js +95 -0
  19. package/dist/code-browser-block-rays.js.map +1 -0
  20. package/dist/code-browser-client.bundle.js +12 -7
  21. package/dist/code-browser-client.js +787 -100
  22. package/dist/code-browser-client.js.map +1 -1
  23. package/dist/code-browser-pair-nav.d.ts +23 -0
  24. package/dist/code-browser-pair-nav.d.ts.map +1 -0
  25. package/dist/code-browser-pair-nav.js +59 -0
  26. package/dist/code-browser-pair-nav.js.map +1 -0
  27. package/dist/code-browser-search.d.ts +50 -0
  28. package/dist/code-browser-search.d.ts.map +1 -1
  29. package/dist/code-browser-search.js +117 -0
  30. package/dist/code-browser-search.js.map +1 -1
  31. package/dist/code-browser.d.ts +51 -2
  32. package/dist/code-browser.d.ts.map +1 -1
  33. package/dist/code-browser.js +863 -196
  34. package/dist/code-browser.js.map +1 -1
  35. package/dist/highlighted-code-lines.d.ts +19 -0
  36. package/dist/highlighted-code-lines.d.ts.map +1 -0
  37. package/dist/highlighted-code-lines.js +61 -0
  38. package/dist/highlighted-code-lines.js.map +1 -0
  39. package/dist/index.d.ts +2 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/markdown-pipeline.d.ts.map +1 -1
  44. package/dist/markdown-pipeline.js +3 -2
  45. package/dist/markdown-pipeline.js.map +1 -1
  46. package/dist/mermaid-runtime-html.d.ts.map +1 -1
  47. package/dist/mermaid-runtime-html.js +3 -1
  48. package/dist/mermaid-runtime-html.js.map +1 -1
  49. package/package.json +2 -2
@@ -3,83 +3,137 @@ import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { MARKER_ID_BODY, buildBlockScrollLinks, } from "@commentray/core";
5
5
  import { tryBuildBlockStretchTableHtml } from "./block-stretch-layout.js";
6
+ import { formatCommentrayBuiltAtLocal } from "./build-stamp.js";
6
7
  import { escapeHtml } from "./html-utils.js";
8
+ import { renderHighlightedCodeLineRows } from "./highlighted-code-lines.js";
7
9
  import { mermaidRuntimeScriptHtml } from "./mermaid-runtime-html.js";
8
- import { renderFencedCode, renderMarkdownToHtml, } from "./markdown-pipeline.js";
10
+ import { renderMarkdownToHtml } from "./markdown-pipeline.js";
11
+ import { commentrayRenderVersion } from "./package-version.js";
9
12
  function renderGeneratorMetaHtml(label) {
10
13
  const t = label?.trim();
11
14
  if (!t)
12
15
  return "";
13
16
  return `<meta name="generator" content="${escapeHtml(t)}" />\n `;
14
17
  }
15
- function extractPreCodeInner(html) {
16
- const m = /<pre(?:\s[^>]*)?>\s*<code(?:\s[^>]*)?>([\s\S]*?)<\/code>\s*<\/pre>/i.exec(html.trim());
17
- return m ? m[1] : escapeHtml(html);
18
+ const META_DESCRIPTION_MAX_LEN = 320;
19
+ function codeBrowserMetaDescription(opts, title) {
20
+ const custom = opts.metaDescription?.trim();
21
+ if (custom)
22
+ return custom.slice(0, META_DESCRIPTION_MAX_LEN);
23
+ const fallback = `${title} — Side-by-side source and commentray documentation.`;
24
+ return fallback.slice(0, META_DESCRIPTION_MAX_LEN);
25
+ }
26
+ function renderMetaDescriptionHtml(opts, title) {
27
+ const content = codeBrowserMetaDescription(opts, title);
28
+ return `<meta name="description" content="${escapeHtml(content)}" />\n `;
18
29
  }
19
30
  /** Single capture: marker id (avoid a wrapping group around the whole comment — that shifted indices). */
20
31
  const BLOCK_MARKER_HTML_LINE = new RegExp(`^<!--\\s*commentray:block\\s+id=(${MARKER_ID_BODY})\\s*-->$`, "i");
21
- /** Inserts thin separator anchors after each `<!-- commentray:block … -->` line (optional index attrs for scroll sync). */
22
- function injectCommentrayBlockAnchors(markdown, links) {
23
- const byId = links ? new Map(links.map((l) => [l.id, l])) : undefined;
24
- return markdown
25
- .split("\n")
26
- .map((line) => {
27
- const m = BLOCK_MARKER_HTML_LINE.exec(line);
28
- if (!m?.[1])
29
- return line;
30
- const id = m[1];
31
- const link = byId?.get(id);
32
- const attrs = link !== undefined
33
- ? ` data-source-start="${String(link.sourceStart)}" data-commentray-line="${String(link.commentrayLine)}"`
34
- : "";
35
- return `${line}\n\n<div id="commentray-block-${escapeHtml(id)}" class="commentray-block-anchor" aria-hidden="true"${attrs}></div>`;
36
- })
37
- .join("\n");
32
+ function trimEndSpacesTabs(s) {
33
+ let end = s.length;
34
+ while (end > 0) {
35
+ const c = s[end - 1];
36
+ if (c !== " " && c !== "\t")
37
+ break;
38
+ end--;
39
+ }
40
+ return s.slice(0, end);
41
+ }
42
+ function isSetextUnderlineLine(line) {
43
+ const t = trimEndSpacesTabs(line);
44
+ return /^\s{0,3}=+\s*$/.test(t) || /^\s{0,3}-+\s*$/.test(t);
45
+ }
46
+ function isThematicBreakLine(line) {
47
+ const t = trimEndSpacesTabs(line);
48
+ return (/^\s{0,3}(?:\*[ \t]*){3,}\s*$/.test(t) ||
49
+ /^\s{0,3}(?:-[ \t]*){3,}\s*$/.test(t) ||
50
+ /^\s{0,3}(?:_[ \t]*){3,}\s*$/.test(t));
51
+ }
52
+ function parseFenceDelimiter(line) {
53
+ const t = trimEndSpacesTabs(line);
54
+ const m = /^(\s{0,3})(`{3,}|~{3,})(.*)$/.exec(t);
55
+ if (!m)
56
+ return null;
57
+ const run = m[2];
58
+ const head = run[0];
59
+ if (head !== "`" && head !== "~")
60
+ return null;
61
+ const ch = head === "`" ? "`" : "~";
62
+ return { ch, runLen: run.length, rest: m[3] ?? "" };
63
+ }
64
+ function isClosingFenceLine(info, open) {
65
+ if (info.ch !== open.ch || info.runLen < open.len)
66
+ return false;
67
+ return info.rest.trim() === "";
68
+ }
69
+ function lineAnchorHtml(mdLine0) {
70
+ const mdLine = String(mdLine0);
71
+ return `<span class="commentray-line-anchor" data-commentray-md-line="${mdLine}" id="commentray-md-line-${mdLine}" aria-hidden="true"></span>`;
72
+ }
73
+ function appendMdLineAnchorWhenAllowed(line, mdLine0) {
74
+ if (isSetextUnderlineLine(line) || isThematicBreakLine(line))
75
+ return line;
76
+ /** Blank lines must stay blank: a line that is only `<span …>` breaks CommonMark HTML / paragraph starts after block markers. */
77
+ if (line === "")
78
+ return "";
79
+ return `${line}${lineAnchorHtml(mdLine0)}`;
38
80
  }
39
- /** One highlighted row per source line so in-page search can scroll to a line. */
40
- async function renderCodeLineBlocks(code, language) {
41
- const lines = code.split("\n");
42
- const langAttr = escapeHtml(language);
43
- const parts = [];
81
+ /**
82
+ * Inserts per-line anchors for search / hash jumps and block separator anchors after each
83
+ * `<!-- commentray:block … -->` line (optional index attrs).
84
+ *
85
+ * Anchors are appended to the line when safe. A **leading** `<span>` breaks CommonMark block
86
+ * recognition (`#` headings, lists, thematic breaks, fences). Fenced code lines must not get a
87
+ * trailing anchor either (would corrupt fence delimiters or appear inside code).
88
+ */
89
+ function injectCommentrayDocAnchors(markdown, links) {
90
+ const byId = links ? new Map(links.map((l) => [l.id, l])) : undefined;
91
+ const lines = markdown.split("\n");
92
+ let fence = null;
93
+ const out = [];
44
94
  for (let i = 0; i < lines.length; i++) {
45
- const line = lines[i] === "" ? " " : lines[i];
46
- const fence = "```" + language + "\n" + line + "\n```\n";
47
- const block = await renderFencedCode(fence);
48
- const inner = extractPreCodeInner(block);
49
- const num = i + 1;
50
- parts.push(`<div class="code-line" id="code-line-${i}" data-line="${i}">` +
51
- `<span class="ln" aria-hidden="true">${num}</span>` +
52
- `<pre><code class="hljs language-${langAttr}">${inner}</code></pre>` +
53
- `</div>`);
54
- }
55
- return parts.join("\n");
56
- }
57
- /** Split a repo-relative path into its directory prefix (with trailing slash) and basename. */
58
- function splitFilePath(p) {
59
- const normalized = p.replaceAll("\\", "/").replace(/^\/+/, "");
60
- const idx = normalized.lastIndexOf("/");
61
- if (idx < 0)
62
- return { dir: "", base: normalized };
63
- return { dir: normalized.slice(0, idx + 1), base: normalized.slice(idx + 1) };
64
- }
65
- function renderFilePathLabel(filePath, fallbackTitle) {
66
- const shown = (filePath ?? "").trim();
67
- if (!shown) {
68
- return `<strong class="file-path file-path--title">${escapeHtml(fallbackTitle)}</strong>`;
95
+ const line = lines[i];
96
+ const delim = parseFenceDelimiter(line);
97
+ if (fence) {
98
+ if (delim && isClosingFenceLine(delim, fence)) {
99
+ fence = null;
100
+ out.push(line);
101
+ continue;
102
+ }
103
+ out.push(line);
104
+ continue;
105
+ }
106
+ if (delim) {
107
+ fence = { ch: delim.ch, len: delim.runLen };
108
+ out.push(line);
109
+ continue;
110
+ }
111
+ const m = BLOCK_MARKER_HTML_LINE.exec(line);
112
+ if (m?.[1]) {
113
+ const id = m[1];
114
+ const link = byId?.get(id);
115
+ const attrs = link !== undefined
116
+ ? ` data-source-start="${String(link.sourceStart)}" data-commentray-line="${String(link.commentrayLine)}"`
117
+ : "";
118
+ /** One `push` with embedded `\n\n` merged poorly with `join("\\n")`; keep real blank lines around raw `<div>`. */
119
+ out.push(`${line}${lineAnchorHtml(i)}`);
120
+ out.push("");
121
+ out.push(`<div id="commentray-block-${escapeHtml(id)}" class="commentray-block-anchor" aria-hidden="true"${attrs}></div>`);
122
+ out.push("");
123
+ continue;
124
+ }
125
+ out.push(appendMdLineAnchorWhenAllowed(line, i));
69
126
  }
70
- const { dir, base } = splitFilePath(shown);
71
- const dirHtml = dir
72
- ? `<span class="file-path__dir">${escapeHtml(dir)}</span>`
73
- : `<span class="file-path__dir file-path__dir--root" title="Repository root">/ </span>`;
74
- return (`<strong class="file-path" title="${escapeHtml(shown)}">` +
75
- dirHtml +
76
- `<span class="file-path__base">${escapeHtml(base)}</span>` +
77
- `</strong>`);
127
+ return out.join("\n");
78
128
  }
79
129
  /** GitHub “mark” glyph (Octicons-style path), MIT-licensed silhouette. */
80
130
  const GITHUB_MARK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20" fill="currentColor" aria-hidden="true">' +
81
131
  '<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>' +
82
132
  "</svg>";
133
+ /** Simple home glyph for same-site hub link (matches Octocat control size). */
134
+ const SITE_HOME_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true">' +
135
+ '<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>' +
136
+ "</svg>";
83
137
  function safeExternalHttpUrl(url) {
84
138
  const t = url?.trim();
85
139
  if (!t)
@@ -88,22 +142,47 @@ function safeExternalHttpUrl(url) {
88
142
  return null;
89
143
  return t;
90
144
  }
91
- function buildToolbarEndHtml(githubRepoUrl, toolHomeUrl) {
145
+ /** Allows relative static browse links (`./browse/…`) and `http(s):` URLs; rejects `javascript:` / `data:`. */
146
+ function safeToolbarNavigationHref(url) {
147
+ const t = url?.trim();
148
+ if (!t)
149
+ return null;
150
+ if (/^(javascript|data):/i.test(t))
151
+ return null;
152
+ return t;
153
+ }
154
+ function buildToolbarSiteHubHtml(siteHubUrl) {
155
+ const site = safeToolbarNavigationHref(siteHubUrl);
156
+ if (!site)
157
+ return "";
158
+ const se = escapeHtml(site);
159
+ return `<a class="toolbar-github" href="${se}" aria-label="Documentation home" title="Back to this site (hub)">${SITE_HOME_SVG}</a>`;
160
+ }
161
+ function buildToolbarEndHtml(githubRepoUrl, toolHomeUrl, commentrayRenderSemver, siteHubUrl) {
162
+ const site = safeToolbarNavigationHref(siteHubUrl);
92
163
  const gh = safeExternalHttpUrl(githubRepoUrl);
93
164
  const tool = safeExternalHttpUrl(toolHomeUrl);
94
165
  const bits = [];
95
- if (gh) {
166
+ if (!site && gh) {
96
167
  const he = escapeHtml(gh);
97
168
  bits.push(`<a class="toolbar-github" href="${he}" target="_blank" rel="noopener noreferrer" aria-label="View repository on GitHub" title="View repository on GitHub">${GITHUB_MARK_SVG}</a>`);
98
169
  }
99
170
  if (tool) {
100
171
  const te = escapeHtml(tool);
101
- bits.push(`<span class="toolbar-attribution" role="note">Rendered with <a href="${te}" target="_blank" rel="noopener noreferrer">Commentray</a></span>`);
172
+ const ver = escapeHtml(commentrayRenderSemver);
173
+ bits.push(`<span class="toolbar-attribution" role="note">Rendered with <a href="${te}" target="_blank" rel="noopener noreferrer">Commentray</a> <span class="toolbar-attribution__version" translate="no">v${ver}</span></span>`);
102
174
  }
103
175
  if (bits.length === 0)
104
176
  return "";
105
177
  return `<div class="toolbar__end">${bits.join("")}</div>`;
106
178
  }
179
+ function renderPageFooterHtml(builtAt) {
180
+ const iso = builtAt.toISOString();
181
+ const human = formatCommentrayBuiltAtLocal(builtAt);
182
+ return (`<footer class="app__footer" role="contentinfo">` +
183
+ `<p class="app__footer-line">HTML generated <time datetime="${escapeHtml(iso)}">${escapeHtml(human)}</time></p>` +
184
+ `</footer>`);
185
+ }
107
186
  function renderRelatedGithubNavHtml(links) {
108
187
  if (links.length === 0)
109
188
  return "";
@@ -114,34 +193,57 @@ function renderRelatedGithubNavHtml(links) {
114
193
  `</nav>`);
115
194
  }
116
195
  function renderToolbarDocHubHtml(opts) {
117
- const parts = [];
118
- const src = safeExternalHttpUrl(opts.sourceOnGithubUrl);
119
- const cr = safeExternalHttpUrl(opts.commentrayOnGithubUrl);
120
196
  const nav = opts.documentedNavJsonUrl?.trim();
121
197
  const hasEmbed = (opts.documentedPairsEmbeddedB64?.trim() ?? "").length > 0;
122
198
  const showDocumentedTree = Boolean(nav) || hasEmbed;
123
- if (src) {
124
- parts.push(`<a class="toolbar-blob-link" href="${escapeHtml(src)}" target="_blank" rel="noopener noreferrer">Source on GitHub</a>`);
125
- }
126
- if (cr) {
127
- parts.push(`<a class="toolbar-blob-link" href="${escapeHtml(cr)}" target="_blank" rel="noopener noreferrer">Commentray on GitHub</a>`);
128
- }
129
- if (showDocumentedTree) {
130
- const navAttr = nav ? escapeHtml(nav) : "";
131
- parts.push(`<button type="button" class="toolbar-tree-toggle" id="documented-files-toggle" aria-expanded="false" aria-controls="documented-files-panel" data-nav-json-url="${navAttr}">Documented files</button>`);
132
- }
133
- const toolbarDocHubHtml = parts.length > 0
134
- ? `<div class="toolbar-doc-hub">${parts.join('<span class="toolbar-doc-hub__sep" aria-hidden="true"> · </span>')}</div>`
135
- : "";
136
- const documentedPanelHtml = showDocumentedTree
137
- ? `<div id="documented-files-panel" class="documented-files-panel" hidden>
138
- <div class="documented-files-panel__inner">
139
- <p class="documented-files-panel__hint">Indexed source ↔ commentray pairs (embedded for offline when available). Links open on GitHub.</p>
199
+ const toolbarDocHubHtml = "";
200
+ const navAttr = escapeHtml(nav ?? "");
201
+ const navRailDocumentedHtml = showDocumentedTree
202
+ ? `<details class="nav-rail__doc-hub" id="documented-files-hub" data-nav-json-url="${navAttr}">
203
+ <summary class="nav-rail__doc-hub-summary">Comment-rayed files</summary>
204
+ <div class="nav-rail__doc-hub-inner">
205
+ <div class="nav-rail__doc-hub-filter-row">
206
+ <label class="nav-rail__doc-hub-filter-label" for="documented-files-filter">Filter</label>
207
+ <input type="search" id="documented-files-filter" class="nav-rail__doc-hub-filter" placeholder="Filter by path…" autocomplete="off" spellcheck="false" />
208
+ </div>
140
209
  <div id="documented-files-tree" class="documented-files-tree" role="tree"></div>
141
210
  </div>
142
- </div>`
211
+ </details>`
143
212
  : "";
144
- return { toolbarDocHubHtml, documentedPanelHtml };
213
+ return { toolbarDocHubHtml, navRailDocumentedHtml };
214
+ }
215
+ function renderNavRailContextHtml(filePath, commentrayPath, opts) {
216
+ const fpRaw = (filePath ?? "").trim();
217
+ const crRaw = (commentrayPath ?? "").trim();
218
+ const srcUrl = safeExternalHttpUrl(opts?.sourceOnGithubUrl);
219
+ const crUrl = safeExternalHttpUrl(opts?.commentrayOnGithubUrl);
220
+ const browseForCr = safeToolbarNavigationHref(opts?.commentrayStaticBrowseUrl);
221
+ const srcGh = srcUrl !== null
222
+ ? `<a class="nav-rail__pair-gh" id="toolbar-source-github" href="${escapeHtml(srcUrl)}" target="_blank" rel="noopener noreferrer" aria-label="Source file on GitHub" title="Open source on GitHub">${GITHUB_MARK_SVG}</a>`
223
+ : "";
224
+ const crGh = browseForCr !== null
225
+ ? `<a class="nav-rail__pair-gh" id="toolbar-commentray-github" href="${escapeHtml(browseForCr)}" rel="noopener" aria-label="Open companion pair in the site viewer" title="Open on site">${GITHUB_MARK_SVG}</a>`
226
+ : crUrl !== null
227
+ ? `<a class="nav-rail__pair-gh" id="toolbar-commentray-github" href="${escapeHtml(crUrl)}" target="_blank" rel="noopener noreferrer" aria-label="Companion commentray on GitHub" title="Open companion Markdown on GitHub">${GITHUB_MARK_SVG}</a>`
228
+ : "";
229
+ if (fpRaw.length === 0 && crRaw.length === 0 && srcGh === "" && crGh === "") {
230
+ return "";
231
+ }
232
+ const fp = escapeHtml(fpRaw);
233
+ const cr = escapeHtml(crRaw);
234
+ const fpDisp = fpRaw.length > 0 ? fp : "—";
235
+ const crDisp = crRaw.length > 0 ? cr : "—";
236
+ return `<div class="nav-rail__context nav-rail__context--compact" aria-label="Current documentation pair">
237
+ <span class="nav-rail__pair">
238
+ <span class="nav-rail__pair-lab">Src</span>
239
+ <span class="nav-rail__pair-path" title="${fp}">${fpDisp}</span>${srcGh}
240
+ </span>
241
+ <span class="nav-rail__pair-sep" aria-hidden="true">·</span>
242
+ <span class="nav-rail__pair">
243
+ <span class="nav-rail__pair-lab">Doc</span>
244
+ <span class="nav-rail__pair-path nav-rail__pair-path--secondary" id="nav-rail-doc-path" title="${cr}">${crDisp}</span>${crGh}
245
+ </span>
246
+ </div>`;
145
247
  }
146
248
  /** IIFE produced by `npm run build -w @commentray/render` (esbuild of `code-browser-client.ts`). */
147
249
  function loadCodeBrowserClientBundle() {
@@ -156,17 +258,328 @@ function loadCodeBrowserClientBundle() {
156
258
  throw new Error("Missing code-browser-client.bundle.js. Run `npm run build -w @commentray/render` to bundle the browser client.");
157
259
  }
158
260
  const CODE_BROWSER_STYLES = `
159
- :root { color-scheme: light dark; }
261
+ :root {
262
+ color-scheme: light dark;
263
+ --cr-control-h: 32px;
264
+ --cr-control-radius: 8px;
265
+ --cr-icon-inner: 18px;
266
+ --cr-label-caps-fs: 10px;
267
+ --cr-label-caps-track: 0.06em;
268
+ --cr-ui-fs: 12px;
269
+ }
160
270
  * { box-sizing: border-box; }
161
271
  body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
272
+ .skip-link {
273
+ position: absolute;
274
+ left: -9999px;
275
+ top: 0;
276
+ z-index: 10000;
277
+ padding: 8px 16px;
278
+ margin: 0;
279
+ font: inherit;
280
+ font-size: 14px;
281
+ text-decoration: none;
282
+ border-radius: 8px;
283
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas);
284
+ background: Canvas;
285
+ color: CanvasText;
286
+ }
287
+ .skip-link:focus {
288
+ left: 12px;
289
+ top: 8px;
290
+ outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas);
291
+ outline-offset: 2px;
292
+ }
293
+ .skip-link:focus:not(:focus-visible) {
294
+ left: -9999px;
295
+ top: 0;
296
+ outline: none;
297
+ }
298
+ .skip-link:focus-visible {
299
+ left: 12px;
300
+ top: 8px;
301
+ outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas);
302
+ outline-offset: 2px;
303
+ }
304
+ .sr-only {
305
+ position: absolute;
306
+ width: 1px;
307
+ height: 1px;
308
+ padding: 0;
309
+ margin: -1px;
310
+ overflow: hidden;
311
+ clip: rect(0, 0, 0, 0);
312
+ white-space: nowrap;
313
+ border: 0;
314
+ }
315
+ .app {
316
+ display: flex;
317
+ flex-direction: column;
318
+ align-items: stretch;
319
+ height: 100vh;
320
+ width: 100%;
321
+ overflow: hidden;
322
+ }
323
+ .app__chrome {
324
+ flex: 0 0 auto;
325
+ display: flex;
326
+ flex-direction: column;
327
+ gap: 8px;
328
+ padding: 8px 12px 10px;
329
+ border-bottom: 1px solid color-mix(in oklab, CanvasText 15%, Canvas);
330
+ background: color-mix(in oklab, CanvasText 4%, Canvas);
331
+ max-height: min(40vh, 420px);
332
+ min-height: 0;
333
+ overflow: auto;
334
+ }
335
+ .chrome__search-row {
336
+ display: flex;
337
+ flex-direction: row;
338
+ align-items: center;
339
+ gap: 10px;
340
+ flex-wrap: nowrap;
341
+ }
342
+ .chrome__search-row input[type="search"] {
343
+ flex: 1 1 auto;
344
+ min-width: 140px;
345
+ min-height: var(--cr-control-h);
346
+ padding: 0 12px;
347
+ font: inherit;
348
+ font-size: var(--cr-ui-fs);
349
+ line-height: 1.25;
350
+ border-radius: var(--cr-control-radius);
351
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas);
352
+ background: Canvas;
353
+ color: CanvasText;
354
+ }
355
+ .chrome__search-row #search-clear {
356
+ flex: 0 0 auto;
357
+ font: inherit;
358
+ font-size: var(--cr-ui-fs);
359
+ font-weight: 500;
360
+ min-height: var(--cr-control-h);
361
+ padding: 0 16px;
362
+ border-radius: var(--cr-control-radius);
363
+ cursor: pointer;
364
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas);
365
+ background: color-mix(in oklab, CanvasText 6%, Canvas);
366
+ color: CanvasText;
367
+ }
368
+ .chrome__search-row input[type="search"]:focus-visible,
369
+ .chrome__search-row #search-clear:focus-visible {
370
+ outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas);
371
+ outline-offset: 2px;
372
+ }
373
+ .chrome__search-label {
374
+ flex: 0 0 auto;
375
+ white-space: nowrap;
376
+ }
377
+ .nav-rail__context--compact {
378
+ display: flex;
379
+ flex-direction: row;
380
+ flex-wrap: wrap;
381
+ align-items: center;
382
+ gap: 6px 10px;
383
+ padding: 5px 10px;
384
+ border-radius: var(--cr-control-radius);
385
+ border: 1px solid color-mix(in oklab, CanvasText 14%, Canvas);
386
+ background: Canvas;
387
+ font-size: var(--cr-ui-fs);
388
+ line-height: 1.3;
389
+ }
390
+ .nav-rail__pair {
391
+ display: inline-flex;
392
+ flex-direction: row;
393
+ align-items: center;
394
+ gap: 6px;
395
+ min-width: 0;
396
+ flex: 1 1 140px;
397
+ max-width: min(48%, 100%);
398
+ }
399
+ .nav-rail__pair-lab {
400
+ flex: 0 0 auto;
401
+ font-size: var(--cr-label-caps-fs);
402
+ font-weight: 700;
403
+ letter-spacing: var(--cr-label-caps-track);
404
+ text-transform: uppercase;
405
+ opacity: 0.72;
406
+ }
407
+ .nav-rail__pair-path {
408
+ flex: 1 1 auto;
409
+ min-width: 0;
410
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
411
+ font-size: var(--cr-ui-fs);
412
+ color: CanvasText;
413
+ overflow: hidden;
414
+ text-overflow: ellipsis;
415
+ white-space: nowrap;
416
+ }
417
+ .nav-rail__pair-path--secondary { opacity: 0.88; }
418
+ .nav-rail__pair-sep {
419
+ flex: 0 0 auto;
420
+ opacity: 0.45;
421
+ user-select: none;
422
+ padding: 0 2px;
423
+ }
424
+ .nav-rail__pair-gh {
425
+ flex: 0 0 auto;
426
+ display: inline-flex;
427
+ align-items: center;
428
+ justify-content: center;
429
+ width: var(--cr-control-h);
430
+ height: var(--cr-control-h);
431
+ border-radius: var(--cr-control-radius);
432
+ border: 1px solid color-mix(in oklab, CanvasText 20%, Canvas);
433
+ background: color-mix(in oklab, CanvasText 5%, Canvas);
434
+ color: CanvasText;
435
+ }
436
+ .nav-rail__pair-gh:hover {
437
+ background: color-mix(in oklab, CanvasText 10%, Canvas);
438
+ }
439
+ .nav-rail__pair-gh:focus-visible {
440
+ outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas);
441
+ outline-offset: 2px;
442
+ }
443
+ .nav-rail__pair-gh svg {
444
+ width: var(--cr-icon-inner);
445
+ height: var(--cr-icon-inner);
446
+ display: block;
447
+ }
448
+ .toolbar .nav-rail__context--compact {
449
+ border: 0;
450
+ background: transparent;
451
+ padding: 0;
452
+ flex: 1 1 200px;
453
+ min-width: 0;
454
+ max-width: none;
455
+ gap: 6px 10px;
456
+ }
457
+ .toolbar .nav-rail__pair {
458
+ flex: 1 1 auto;
459
+ min-width: 0;
460
+ max-width: min(44vw, 420px);
461
+ }
462
+ .nav-rail__search-label {
463
+ font-size: var(--cr-label-caps-fs);
464
+ font-weight: 700;
465
+ letter-spacing: var(--cr-label-caps-track);
466
+ text-transform: uppercase;
467
+ opacity: 0.78;
468
+ }
469
+ .nav-rail__search-hint {
470
+ margin: 0;
471
+ font-size: 11px;
472
+ line-height: 1.35;
473
+ opacity: 0.78;
474
+ }
475
+ .nav-rail__code {
476
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
477
+ font-size: 10px;
478
+ }
479
+ .nav-rail__doc-hub {
480
+ position: relative;
481
+ flex: 0 0 auto;
482
+ align-self: center;
483
+ display: block;
484
+ border: 1px solid color-mix(in oklab, CanvasText 16%, Canvas);
485
+ border-radius: var(--cr-control-radius);
486
+ background: Canvas;
487
+ overflow: visible;
488
+ }
489
+ .nav-rail__doc-hub-summary {
490
+ cursor: pointer;
491
+ font-size: var(--cr-ui-fs);
492
+ font-weight: 600;
493
+ padding: 0 12px;
494
+ min-height: var(--cr-control-h);
495
+ display: inline-flex;
496
+ align-items: center;
497
+ box-sizing: border-box;
498
+ list-style: none;
499
+ user-select: none;
500
+ line-height: 1.25;
501
+ }
502
+ .nav-rail__doc-hub-summary::-webkit-details-marker { display: none; }
503
+ .nav-rail__doc-hub-inner {
504
+ position: absolute;
505
+ left: 0;
506
+ top: calc(100% + 4px);
507
+ z-index: 60;
508
+ min-width: min(280px, 78vw);
509
+ max-width: min(440px, 94vw);
510
+ max-height: min(52vh, 400px);
511
+ display: flex;
512
+ flex-direction: column;
513
+ gap: 8px;
514
+ overflow: hidden;
515
+ padding: 8px 10px;
516
+ font-size: 12px;
517
+ border: 1px solid color-mix(in oklab, CanvasText 16%, Canvas);
518
+ border-radius: 8px;
519
+ background: Canvas;
520
+ box-shadow: 0 8px 28px color-mix(in oklab, CanvasText 12%, transparent);
521
+ }
522
+ .nav-rail__doc-hub-filter-row {
523
+ flex: 0 0 auto;
524
+ }
525
+ .nav-rail__doc-hub-filter-label {
526
+ display: block;
527
+ font-size: var(--cr-label-caps-fs);
528
+ font-weight: 700;
529
+ letter-spacing: var(--cr-label-caps-track);
530
+ text-transform: uppercase;
531
+ opacity: 0.78;
532
+ margin-bottom: 4px;
533
+ }
534
+ .nav-rail__doc-hub-filter {
535
+ width: 100%;
536
+ box-sizing: border-box;
537
+ font: inherit;
538
+ font-size: 12px;
539
+ padding: 4px 8px;
540
+ border-radius: 6px;
541
+ border: 1px solid color-mix(in oklab, CanvasText 22%, Canvas);
542
+ background: color-mix(in oklab, CanvasText 4%, Canvas);
543
+ color: CanvasText;
544
+ }
545
+ .nav-rail__doc-hub-filter:focus {
546
+ outline: 2px solid color-mix(in oklab, CanvasText 40%, Canvas);
547
+ outline-offset: 1px;
548
+ }
549
+ .nav-rail__doc-hub-hint {
550
+ margin: 0 0 8px;
551
+ opacity: 0.78;
552
+ line-height: 1.4;
553
+ font-size: 12px;
554
+ }
555
+ .app__main {
556
+ flex: 1 1 auto;
557
+ min-width: 0;
558
+ min-height: 0;
559
+ display: flex;
560
+ flex-direction: column;
561
+ }
562
+ .app__footer {
563
+ flex: 0 0 auto;
564
+ padding: 6px 12px 10px;
565
+ border-top: 1px solid color-mix(in oklab, CanvasText 12%, Canvas);
566
+ background: color-mix(in oklab, CanvasText 3%, Canvas);
567
+ font-size: 11px;
568
+ line-height: 1.4;
569
+ color: color-mix(in oklab, CanvasText 72%, Canvas);
570
+ }
571
+ .app__footer-line { margin: 0; }
572
+ .app__footer time { font-variant-numeric: tabular-nums; }
162
573
  .toolbar {
574
+ position: relative;
163
575
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 8px 12px;
164
576
  border-bottom: 1px solid color-mix(in oklab, CanvasText 18%, Canvas);
165
- font-size: 13px; flex: 0 0 auto;
577
+ font-size: var(--cr-ui-fs);
578
+ flex: 0 0 auto;
166
579
  }
167
580
  .toolbar__main {
168
581
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
169
- flex: 1 1 280px;
582
+ flex: 0 1 auto;
170
583
  min-width: 0;
171
584
  }
172
585
  .toolbar__end {
@@ -176,22 +589,49 @@ const CODE_BROWSER_STYLES = `
176
589
  }
177
590
  .toolbar-github {
178
591
  display: inline-flex; align-items: center; justify-content: center;
179
- width: 34px; height: 34px; border-radius: 8px;
592
+ width: var(--cr-control-h);
593
+ height: var(--cr-control-h);
594
+ border-radius: var(--cr-control-radius);
180
595
  border: 1px solid color-mix(in oklab, CanvasText 22%, Canvas);
181
596
  background: color-mix(in oklab, CanvasText 6%, Canvas);
182
597
  color: CanvasText;
183
598
  }
599
+ .toolbar-github svg {
600
+ width: var(--cr-icon-inner);
601
+ height: var(--cr-icon-inner);
602
+ display: block;
603
+ }
184
604
  .toolbar-github:hover { background: color-mix(in oklab, CanvasText 11%, Canvas); }
185
605
  .toolbar-github:focus-visible { outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas); outline-offset: 2px; }
186
606
  .toolbar-attribution {
187
- font-size: 11px; line-height: 1.35; opacity: 0.82; max-width: min(360px, 42vw);
188
- text-align: right; color: color-mix(in oklab, CanvasText 88%, Canvas);
607
+ font-size: var(--cr-ui-fs);
608
+ line-height: 1.35;
609
+ opacity: 0.85;
610
+ max-width: min(360px, 42vw);
611
+ text-align: right;
612
+ color: color-mix(in oklab, CanvasText 88%, Canvas);
189
613
  }
190
614
  .toolbar-attribution a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
191
615
  .toolbar label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
616
+ .toolbar__main > label:has(#wrap-lines) {
617
+ margin: 0;
618
+ min-height: var(--cr-control-h);
619
+ padding: 0 12px 0 10px;
620
+ border-radius: var(--cr-control-radius);
621
+ border: 1px solid color-mix(in oklab, CanvasText 16%, Canvas);
622
+ background: color-mix(in oklab, CanvasText 4%, Canvas);
623
+ font-size: var(--cr-ui-fs);
624
+ font-weight: 500;
625
+ gap: 8px;
626
+ }
627
+ .toolbar label input:focus-visible {
628
+ outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas);
629
+ outline-offset: 2px;
630
+ }
192
631
  .toolbar .file-path {
193
632
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
194
- font-size: 13px; font-weight: 500;
633
+ font-size: var(--cr-ui-fs);
634
+ font-weight: 500;
195
635
  display: inline-flex; align-items: baseline; gap: 0; margin-right: 4px;
196
636
  max-width: 60vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
197
637
  }
@@ -205,7 +645,9 @@ const CODE_BROWSER_STYLES = `
205
645
  .toolbar .file-path--title { font-weight: 600; }
206
646
  .toolbar-related {
207
647
  display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px;
208
- max-width: min(520px, 90vw); font-size: 12px; line-height: 1.35;
648
+ max-width: min(520px, 90vw);
649
+ font-size: var(--cr-ui-fs);
650
+ line-height: 1.35;
209
651
  color: color-mix(in oklab, CanvasText 88%, Canvas);
210
652
  }
211
653
  .toolbar-related__prefix { font-weight: 600; opacity: 0.85; white-space: nowrap; }
@@ -215,69 +657,82 @@ const CODE_BROWSER_STYLES = `
215
657
  word-break: break-word;
216
658
  }
217
659
  .toolbar-related__sep { opacity: 0.55; user-select: none; }
218
- .toolbar-doc-hub {
219
- display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
220
- font-size: 12px; line-height: 1.35;
221
- color: color-mix(in oklab, CanvasText 88%, Canvas);
222
- }
223
- .toolbar-doc-hub__sep { opacity: 0.45; user-select: none; }
224
- .toolbar-blob-link {
225
- color: inherit; font-weight: 500; text-decoration: underline; text-underline-offset: 2px;
226
- white-space: nowrap;
660
+ #documented-files-tree {
661
+ flex: 1 1 auto;
662
+ min-height: 0;
663
+ overflow: auto;
227
664
  }
228
- .toolbar-tree-toggle {
229
- font: inherit; font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 6px; cursor: pointer;
230
- border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas);
231
- background: color-mix(in oklab, CanvasText 6%, Canvas); color: CanvasText;
665
+ .documented-files-tree ul { list-style: none; margin: 0; padding-left: 12px; }
666
+ .documented-files-tree > ul { padding-left: 0; }
667
+ .documented-files-tree li { margin: 2px 0; line-height: 1.35; }
668
+ .documented-files-tree .tree-dir { font-weight: 600; margin-top: 4px; font-size: 12px; }
669
+ .documented-files-tree .tree-file {
670
+ margin: 3px 0;
671
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
672
+ font-size: 11px;
232
673
  }
233
- .toolbar-tree-toggle:hover { background: color-mix(in oklab, CanvasText 11%, Canvas); }
234
- .documented-files-panel {
235
- flex: 0 0 auto; max-height: min(42vh, 360px); overflow: auto;
236
- border-bottom: 1px solid color-mix(in oklab, CanvasText 12%, Canvas);
237
- background: color-mix(in oklab, CanvasText 4%, Canvas);
674
+ .documented-files-tree .tree-file-link {
675
+ color: inherit;
676
+ font-weight: 500;
677
+ text-decoration: underline;
678
+ text-underline-offset: 2px;
679
+ word-break: break-word;
238
680
  }
239
- .documented-files-panel__inner { padding: 10px 14px 14px; font-size: 13px; }
240
- .documented-files-panel__hint { margin: 0 0 10px; opacity: 0.82; line-height: 1.4; }
241
- .documented-files-panel__code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace; font-size: 12px; }
242
- .documented-files-tree ul { list-style: none; margin: 0; padding-left: 14px; }
243
- .documented-files-tree > ul { padding-left: 0; }
244
- .documented-files-tree li { margin: 2px 0; line-height: 1.4; }
245
- .documented-files-tree .tree-dir { font-weight: 600; margin-top: 6px; }
246
- .documented-files-tree .tree-file { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; margin: 4px 0; }
247
- .documented-files-tree .tree-file-name { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace; font-size: 12px; }
248
- .documented-files-tree .tree-file-links { display: inline-flex; flex-wrap: wrap; gap: 6px 10px; font-size: 11px; }
249
- .documented-files-tree .tree-file-links a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
250
- .toolbar .search-field {
251
- display: inline-flex; align-items: center; gap: 6px; flex: 1 1 220px; min-width: 160px;
252
- }
253
- .toolbar .search-field input[type="search"] {
254
- flex: 1; min-width: 0; padding: 4px 8px; font: inherit; border-radius: 6px;
255
- border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas); background: Canvas;
256
- color: CanvasText;
681
+ .documented-files-tree .tree-file-link:hover {
682
+ opacity: 0.92;
257
683
  }
258
684
  .toolbar button {
259
- font: inherit; padding: 4px 10px; border-radius: 6px; cursor: pointer;
260
- border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas); background: color-mix(in oklab, CanvasText 6%, Canvas);
685
+ font: inherit;
686
+ font-size: var(--cr-ui-fs);
687
+ font-weight: 500;
688
+ min-height: var(--cr-control-h);
689
+ padding: 0 12px;
690
+ border-radius: var(--cr-control-radius);
691
+ cursor: pointer;
692
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas);
693
+ background: color-mix(in oklab, CanvasText 6%, Canvas);
261
694
  color: CanvasText;
262
695
  }
263
696
  .search-results {
264
- flex: 0 0 auto; max-height: 160px; overflow: auto; padding: 6px 12px 8px;
265
- border-bottom: 1px solid color-mix(in oklab, CanvasText 12%, Canvas);
266
- font-size: 12px;
697
+ flex: 0 1 auto;
698
+ min-height: 0;
699
+ max-height: min(320px, 38vh);
700
+ overflow: auto;
701
+ padding: 8px 8px 10px;
702
+ border-radius: 8px;
703
+ border: 1px solid color-mix(in oklab, CanvasText 12%, Canvas);
704
+ background: Canvas;
705
+ font-size: 13px;
267
706
  }
268
707
  .search-results[hidden] { display: none !important; }
269
- .search-results .hint { opacity: 0.75; margin-bottom: 6px; }
708
+ .search-results .hint { opacity: 0.75; margin-bottom: 8px; line-height: 1.45; }
270
709
  .search-results button.hit {
271
- display: block; width: 100%; text-align: left; margin: 2px 0; padding: 6px 8px;
710
+ display: block; width: 100%; text-align: left; margin: 4px 0; padding: 8px 10px;
272
711
  border-radius: 6px; border: 1px solid color-mix(in oklab, CanvasText 14%, Canvas);
273
712
  background: color-mix(in oklab, CanvasText 5%, Canvas); color: CanvasText; cursor: pointer;
274
713
  font: inherit;
275
714
  }
276
715
  .search-results button.hit:hover { background: color-mix(in oklab, CanvasText 10%, Canvas); }
277
- .search-results button.hit .meta { opacity: 0.8; font-size: 11px; }
278
- .search-results button.hit .src-tag { opacity: 0.75; font-weight: 500; font-size: 10px; }
279
- .search-results button.hit .snippet { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace; font-size: 11px; white-space: pre-wrap; word-break: break-word; margin-top: 2px; }
716
+ .search-results button.hit .meta { opacity: 0.8; font-size: 12px; }
717
+ .search-results button.hit .src-tag { opacity: 0.75; font-weight: 500; font-size: 11px; }
718
+ .search-results button.hit .snippet {
719
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace; font-size: 13px;
720
+ line-height: 1.45; white-space: pre-wrap; word-break: break-word; margin-top: 4px;
721
+ }
722
+ .search-results mark.search-hit {
723
+ padding: 0 2px; border-radius: 3px; font: inherit;
724
+ background: color-mix(in oklab, #f5a623 70%, Canvas);
725
+ color: CanvasText;
726
+ box-decoration-break: clone;
727
+ -webkit-box-decoration-break: clone;
728
+ }
729
+ @media (prefers-color-scheme: dark) {
730
+ .search-results mark.search-hit {
731
+ background: color-mix(in oklab, #c9a227 55%, Canvas);
732
+ }
733
+ }
280
734
  .shell { display: flex; flex-direction: row; flex: 1; min-height: 0; }
735
+ .app__main .shell { flex: 1 1 auto; }
281
736
  .pane--code {
282
737
  flex: 0 0 50%;
283
738
  min-width: 120px; overflow: auto; padding: 12px 16px;
@@ -285,12 +740,12 @@ const CODE_BROWSER_STYLES = `
285
740
  --code-line-font-size: 13px;
286
741
  --code-line-height: 1.5;
287
742
  }
743
+ .pane--code .code-line-stack { --code-ln-min-ch: 3; }
288
744
  .pane--code .code-line {
289
745
  display: grid;
290
- /* max-content: column wide enough for the longest line number (avoids 100+ bleeding into code). */
291
746
  grid-template-columns: max-content 1fr;
292
- column-gap: 12px;
293
- align-items: baseline;
747
+ column-gap: 10px;
748
+ align-items: start;
294
749
  }
295
750
  .pane--code .code-line pre {
296
751
  margin: 0;
@@ -316,6 +771,8 @@ const CODE_BROWSER_STYLES = `
316
771
  white-space: nowrap;
317
772
  font-size: var(--code-line-font-size);
318
773
  line-height: var(--code-line-height);
774
+ min-width: calc(var(--code-ln-min-ch, 3) * 1ch + 0.6ch);
775
+ box-sizing: content-box;
319
776
  }
320
777
  .pane--code .code-line:target .ln,
321
778
  .pane--code .code-line:hover .ln {
@@ -328,18 +785,78 @@ const CODE_BROWSER_STYLES = `
328
785
  white-space: pre;
329
786
  }
330
787
  .gutter {
331
- flex: 0 0 8px; cursor: col-resize; background: color-mix(in oklab, CanvasText 12%, Canvas);
788
+ flex: 0 0 14px; cursor: col-resize; background: color-mix(in oklab, CanvasText 12%, Canvas);
332
789
  position: relative;
790
+ --commentray-ray-accent: #3b7dd8;
791
+ }
792
+ @media (prefers-color-scheme: dark) {
793
+ .gutter { --commentray-ray-accent: #6eb0ff; }
794
+ }
795
+ .gutter__rays {
796
+ position: absolute; inset: 0; pointer-events: none; z-index: 1;
797
+ }
798
+ .gutter__rays svg { width: 100%; height: 100%; display: block; overflow: visible; }
799
+ .gutter__rays-path {
800
+ fill: none; stroke-linecap: round; vector-effect: non-scaling-stroke;
801
+ stroke: color-mix(in oklab, var(--commentray-ray-accent) 72%, CanvasText);
802
+ stroke-width: 1.35px; opacity: 0.26;
803
+ }
804
+ .gutter__rays-path--active {
805
+ stroke-width: 2.4px; opacity: 0.88;
806
+ }
807
+ .gutter__rays-path--trail {
808
+ stroke-dasharray: 3 4; opacity: 0.42;
809
+ }
810
+ .gutter__rays-path--active.gutter__rays-path--trail {
811
+ opacity: 0.72;
333
812
  }
334
813
  .gutter:hover { background: color-mix(in oklab, CanvasText 22%, Canvas); }
335
814
  .gutter::after {
336
815
  content: ""; position: absolute; top: 0; bottom: 0; left: -4px; right: -4px;
337
816
  }
338
817
  .pane--doc {
339
- flex: 1 1 auto; min-width: 120px; overflow: auto; padding: 12px 16px;
818
+ flex: 1 1 auto; min-width: 0; min-height: 0;
819
+ display: flex; flex-direction: column; overflow: hidden; padding: 12px 16px;
820
+ }
821
+ .doc-pane-body {
822
+ flex: 1 1 auto; min-height: 0; overflow: auto;
823
+ }
824
+ .toolbar-angle-picker {
825
+ display: inline-flex;
826
+ align-items: center;
827
+ gap: 8px;
828
+ flex: 0 0 auto;
829
+ color: color-mix(in oklab, CanvasText 88%, Canvas);
830
+ }
831
+ .toolbar-angle-picker > label {
832
+ font-size: var(--cr-label-caps-fs);
833
+ font-weight: 700;
834
+ letter-spacing: var(--cr-label-caps-track);
835
+ text-transform: uppercase;
836
+ opacity: 0.72;
837
+ }
838
+ .toolbar-angle-picker select {
839
+ font: inherit;
840
+ font-size: var(--cr-ui-fs);
841
+ min-height: var(--cr-control-h);
842
+ height: var(--cr-control-h);
843
+ padding: 0 10px;
844
+ border-radius: var(--cr-control-radius);
845
+ border: 1px solid color-mix(in oklab, CanvasText 25%, Canvas);
846
+ background: Canvas;
847
+ color: CanvasText;
848
+ }
849
+ .toolbar-angle-picker select:focus-visible {
850
+ outline: 2px solid color-mix(in oklab, CanvasText 45%, Canvas);
851
+ outline-offset: 2px;
340
852
  }
341
853
  .pane--doc { font-size: 15px; line-height: 1.45; }
342
854
  .pane--doc img { max-width: 100%; height: auto; }
855
+ .pane--doc .commentray-line-anchor {
856
+ display: inline;
857
+ vertical-align: baseline;
858
+ scroll-margin-top: 10px;
859
+ }
343
860
  .pane--doc .commentray-block-anchor {
344
861
  display: block;
345
862
  height: 0;
@@ -348,8 +865,6 @@ const CODE_BROWSER_STYLES = `
348
865
  border-top: 1px solid color-mix(in oklab, CanvasText 22%, Canvas);
349
866
  pointer-events: none;
350
867
  }
351
- .pane h2.pane-title { margin: 0 0 10px; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.75; }
352
- .app { display: flex; flex-direction: column; height: 100vh; }
353
868
  .shell--stretch-rows {
354
869
  flex: 1;
355
870
  min-height: 0;
@@ -413,6 +928,7 @@ const CODE_BROWSER_STYLES = `
413
928
  font-size: var(--code-line-font-size, 13px);
414
929
  line-height: var(--code-line-height, 1.5);
415
930
  }
931
+ .block-stretch .code-line-stack { --code-ln-min-ch: 3; }
416
932
  .block-stretch .code-line .ln {
417
933
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
418
934
  font-variant-numeric: tabular-nums;
@@ -425,20 +941,16 @@ const CODE_BROWSER_STYLES = `
425
941
  white-space: nowrap;
426
942
  font-size: var(--code-line-font-size, 13px);
427
943
  line-height: var(--code-line-height, 1.5);
944
+ min-width: calc(var(--code-ln-min-ch, 3) * 1ch + 0.6ch);
945
+ box-sizing: content-box;
428
946
  }
429
947
  .block-stretch.wrap .code-line pre,
430
948
  .block-stretch.wrap .code-line pre code { white-space: pre-wrap; word-break: break-word; }
431
949
  .block-stretch:not(.wrap) .code-line pre,
432
950
  .block-stretch:not(.wrap) .code-line pre code { white-space: pre; }
433
- .block-stretch-headings {
434
- display: grid;
435
- grid-template-columns: 1fr 1fr;
436
- gap: 0 16px;
437
- padding: 4px 12px 8px;
438
- border-bottom: 1px solid color-mix(in oklab, CanvasText 10%, Canvas);
439
- }
440
- .block-stretch-headings .pane-title { margin: 0; }
441
951
  `;
952
+ /** Native tooltip on #search-q (short hint is visible under the search row). */
953
+ const CODE_BROWSER_SEARCH_INPUT_TITLE = "Filename, path, or words. Matches this pair (source + commentray lines) first; merges commentray-nav-search.json when the export includes it (indexed paths + commentray lines).";
442
954
  function buildCodeBrowserPageHtml(p) {
443
955
  const shellClass = p.layout === "stretch" ? "shell shell--stretch-rows" : "shell";
444
956
  return `<!doctype html>
@@ -446,7 +958,7 @@ function buildCodeBrowserPageHtml(p) {
446
958
  <head>
447
959
  <meta charset="utf-8" />
448
960
  <meta name="viewport" content="width=device-width, initial-scale=1" />
449
- ${p.generatorMetaHtml}<title>${escapeHtml(p.title)}</title>
961
+ ${p.metaDescriptionHtml}${p.generatorMetaHtml}<title>${escapeHtml(p.title)}</title>
450
962
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/${escapeHtml(p.hljs)}.min.css" media="(prefers-color-scheme: light)" />
451
963
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/${escapeHtml(p.hljsDark)}.min.css" media="(prefers-color-scheme: dark)" />
452
964
  <style>
@@ -454,27 +966,38 @@ ${CODE_BROWSER_STYLES}
454
966
  </style>
455
967
  </head>
456
968
  <body>
969
+ <a class="skip-link" href="#main-content">Skip to main content</a>
457
970
  <div class="app">
458
- <header class="toolbar" aria-label="View options">
971
+ <header class="toolbar" role="banner" aria-label="View options">
972
+ <h1 class="sr-only">${escapeHtml(p.title)}</h1>
459
973
  <div class="toolbar__main">
460
- ${p.filePathHtml}
974
+ ${p.toolbarSiteHubHtml}
975
+ ${p.navRailContextHtml}
976
+ ${p.navRailDocumentedHtml}
977
+ ${p.angleSelectHtml}
461
978
  ${p.toolbarDocHubHtml}
462
- <span class="search-field">
463
- <label for="search-q">Search</label>
464
- <input type="search" id="search-q" placeholder="${escapeHtml(p.searchPlaceholder)}" autocomplete="off" spellcheck="false" />
465
- <button type="button" id="search-clear" title="Clear search">Clear</button>
466
- </span>
467
979
  ${p.relatedNavHtml}
468
980
  <label><input type="checkbox" id="wrap-lines" /> Wrap code lines</label>
469
981
  </div>
470
982
  ${p.toolbarEndHtml}
471
983
  </header>
472
- ${p.documentedPanelHtml}
473
- <div class="search-results" id="search-results" hidden aria-live="polite"></div>
474
- <div class="${shellClass}" id="shell" data-layout="${p.layout}" data-raw-code-b64="${escapeHtml(p.rawCodeB64)}" data-raw-md-b64="${escapeHtml(p.rawMdB64)}" data-scroll-block-links-b64="${escapeHtml(p.scrollBlockLinksB64)}"${p.shellDocumentedPairsAttr}${p.shellSearchAttrs}>
984
+ <header class="app__chrome" role="region" aria-label="Search">
985
+ <div class="chrome__search-row">
986
+ <label class="chrome__search-label nav-rail__search-label" for="search-q">Search</label>
987
+ <input type="search" id="search-q" placeholder="${escapeHtml(p.searchPlaceholder)}" title="${escapeHtml(CODE_BROWSER_SEARCH_INPUT_TITLE)}" autocomplete="off" spellcheck="false" />
988
+ <button type="button" id="search-clear" title="Clear search">Clear</button>
989
+ </div>
990
+ <div class="search-results" id="search-results" hidden aria-live="polite"></div>
991
+ <p class="nav-rail__search-hint chrome__search-hint">This pair + merged <code class="nav-rail__code">commentray-nav-search.json</code> when the export ships it.</p>
992
+ </header>
993
+ <main id="main-content" class="app__main" tabindex="-1">
994
+ <div class="${shellClass}" id="shell" data-layout="${p.layout}" data-raw-code-b64="${escapeHtml(p.rawCodeB64)}" data-raw-md-b64="${escapeHtml(p.rawMdB64)}" data-scroll-block-links-b64="${escapeHtml(p.scrollBlockLinksB64)}"${p.shellDocumentedPairsAttr}${p.shellSearchAttrs}>
475
995
  ${p.shellInner}
476
- </div>
996
+ </div>
997
+ </main>
998
+ ${p.pageFooterHtml}
477
999
  </div>
1000
+ <script type="text/plain" id="commentray-multi-angle-b64">${p.multiAngleScriptBlock}</script>
478
1001
  <script>
479
1002
  ${loadCodeBrowserClientBundle()}
480
1003
  </script>
@@ -482,10 +1005,108 @@ ${loadCodeBrowserClientBundle()}
482
1005
  </body>
483
1006
  </html>`;
484
1007
  }
1008
+ async function multiAngleJsonRowAndDocHtml(opts, spec) {
1009
+ const rows = spec.blockStretchRows;
1010
+ const links = rows !== undefined
1011
+ ? buildBlockScrollLinks(rows.index, rows.sourceRelative, rows.commentrayPathRel, spec.markdown, opts.code)
1012
+ : [];
1013
+ const mdForDoc = injectCommentrayDocAnchors(spec.markdown, links.length > 0 ? links : undefined);
1014
+ const scrollB64 = links.length > 0 ? Buffer.from(JSON.stringify(links), "utf8").toString("base64") : "";
1015
+ const commentrayHtml = await renderMarkdownToHtml(mdForDoc, {
1016
+ commentrayOutputUrls: opts.commentrayOutputUrls,
1017
+ });
1018
+ return {
1019
+ jsonRow: {
1020
+ id: spec.id,
1021
+ title: spec.title?.trim() || spec.id,
1022
+ docInnerHtmlB64: Buffer.from(commentrayHtml, "utf8").toString("base64"),
1023
+ rawMdB64: Buffer.from(spec.markdown, "utf8").toString("base64"),
1024
+ scrollBlockLinksB64: scrollB64,
1025
+ commentrayPathForSearch: spec.commentrayPathRel.trim(),
1026
+ commentrayOnGithubUrl: spec.commentrayOnGithubUrl,
1027
+ staticBrowseUrl: spec.staticBrowseUrl,
1028
+ },
1029
+ commentrayHtml,
1030
+ scrollB64,
1031
+ };
1032
+ }
1033
+ async function buildMultiAngleDualPaneShell(opts, multi) {
1034
+ const defaultId = multi.angles.some((a) => a.id === multi.defaultAngleId)
1035
+ ? multi.defaultAngleId
1036
+ : (multi.angles[0]?.id ?? "main");
1037
+ const jsonAngles = [];
1038
+ let defaultMarkdown = opts.commentrayMarkdown;
1039
+ let defaultScrollB64 = "";
1040
+ let defaultPathSearch = (opts.commentrayPathForSearch ?? "").trim();
1041
+ let defaultGh = opts.commentrayOnGithubUrl;
1042
+ let defaultStaticBrowse = (opts.commentrayStaticBrowseUrl ?? "").trim();
1043
+ let defaultPaneHtml = "";
1044
+ const codeHtml = await renderHighlightedCodeLineRows(opts.code, opts.language);
1045
+ for (const spec of multi.angles) {
1046
+ const { jsonRow, commentrayHtml, scrollB64 } = await multiAngleJsonRowAndDocHtml(opts, spec);
1047
+ if (spec.id === defaultId) {
1048
+ defaultMarkdown = spec.markdown;
1049
+ defaultScrollB64 = scrollB64;
1050
+ defaultPathSearch = spec.commentrayPathRel.trim();
1051
+ defaultGh = spec.commentrayOnGithubUrl;
1052
+ {
1053
+ const sb = (spec.staticBrowseUrl ?? "").trim();
1054
+ if (sb.length > 0)
1055
+ defaultStaticBrowse = sb;
1056
+ }
1057
+ defaultPaneHtml = commentrayHtml;
1058
+ }
1059
+ jsonAngles.push(jsonRow);
1060
+ }
1061
+ const selOpts = multi.angles
1062
+ .map((a) => {
1063
+ const lab = escapeHtml(a.title?.trim() || a.id);
1064
+ return `<option value="${escapeHtml(a.id)}"${a.id === defaultId ? " selected" : ""}>${lab}</option>`;
1065
+ })
1066
+ .join("");
1067
+ const angleSelectHtml = `<span class="toolbar-angle-picker"><label for="angle-select">Angle</label><select id="angle-select" aria-label="Commentray angle">${selOpts}</select></span>`;
1068
+ const shellInner = ` <section class="pane--code" id="code-pane" aria-label="Source code">` +
1069
+ ` ${codeHtml}\n` +
1070
+ ` </section>\n` +
1071
+ ` <div class="gutter" id="gutter" role="separator" aria-orientation="vertical" aria-label="Resize panes"></div>\n` +
1072
+ ` <section class="pane--doc commentray" id="doc-pane" aria-label="Commentray">\n` +
1073
+ ` <div id="doc-pane-body" class="doc-pane-body">\n` +
1074
+ ` ${defaultPaneHtml}\n` +
1075
+ ` </div>\n` +
1076
+ ` </section>\n`;
1077
+ const payloadObj = { defaultAngleId: defaultId, angles: jsonAngles };
1078
+ const multiAnglePayloadB64 = Buffer.from(JSON.stringify(payloadObj), "utf8").toString("base64");
1079
+ return {
1080
+ shellInner,
1081
+ multiShell: {
1082
+ rawMdB64: Buffer.from(defaultMarkdown, "utf8").toString("base64"),
1083
+ scrollBlockLinksB64: defaultScrollB64,
1084
+ commentrayPathForSearch: defaultPathSearch,
1085
+ commentrayOnGithubUrl: defaultGh,
1086
+ ...(defaultStaticBrowse.length > 0 ? { commentrayStaticBrowseUrl: defaultStaticBrowse } : {}),
1087
+ },
1088
+ angleSelectHtml,
1089
+ multiAnglePayloadB64,
1090
+ };
1091
+ }
485
1092
  async function buildCodeBrowserShell(opts, layoutPref) {
486
1093
  let layout = "dual";
487
1094
  let shellInner = "";
488
1095
  let scrollBlockLinksB64 = "";
1096
+ const multi = opts.multiAngleBrowsing;
1097
+ const multiActive = Boolean(multi && multi.angles.length >= 2);
1098
+ if (multiActive && multi) {
1099
+ const built = await buildMultiAngleDualPaneShell(opts, multi);
1100
+ const ms = built.multiShell;
1101
+ return {
1102
+ layout: "dual",
1103
+ shellInner: built.shellInner,
1104
+ scrollBlockLinksB64: ms.scrollBlockLinksB64,
1105
+ angleSelectHtml: built.angleSelectHtml,
1106
+ multiAnglePayloadB64: built.multiAnglePayloadB64,
1107
+ multiShell: ms,
1108
+ };
1109
+ }
489
1110
  if (opts.blockStretchRows && layoutPref !== "dual") {
490
1111
  const stretched = await tryBuildBlockStretchTableHtml({
491
1112
  code: opts.code,
@@ -498,51 +1119,52 @@ async function buildCodeBrowserShell(opts, layoutPref) {
498
1119
  });
499
1120
  if (stretched) {
500
1121
  layout = "stretch";
501
- shellInner =
502
- ` <div class="block-stretch-headings">` +
503
- `<h2 class="pane-title">Code</h2>` +
504
- `<h2 class="pane-title">Commentray</h2>` +
505
- `</div>\n` +
506
- ` ${stretched.preambleHtml}\n` +
507
- ` ${stretched.tableInnerHtml}\n`;
1122
+ shellInner = ` ${stretched.preambleHtml}\n` + ` ${stretched.tableInnerHtml}\n`;
508
1123
  }
509
1124
  }
510
1125
  if (layout === "dual") {
511
1126
  const links = opts.blockStretchRows !== undefined
512
1127
  ? buildBlockScrollLinks(opts.blockStretchRows.index, opts.blockStretchRows.sourceRelative, opts.blockStretchRows.commentrayPathRel, opts.commentrayMarkdown, opts.code)
513
1128
  : [];
514
- const mdForDoc = injectCommentrayBlockAnchors(opts.commentrayMarkdown, links.length > 0 ? links : undefined);
1129
+ const mdForDoc = injectCommentrayDocAnchors(opts.commentrayMarkdown, links.length > 0 ? links : undefined);
515
1130
  if (links.length > 0) {
516
1131
  scrollBlockLinksB64 = Buffer.from(JSON.stringify(links), "utf8").toString("base64");
517
1132
  }
518
1133
  const [codeHtml, commentrayHtml] = await Promise.all([
519
- renderCodeLineBlocks(opts.code, opts.language),
1134
+ renderHighlightedCodeLineRows(opts.code, opts.language),
520
1135
  renderMarkdownToHtml(mdForDoc, {
521
1136
  commentrayOutputUrls: opts.commentrayOutputUrls,
522
1137
  }),
523
1138
  ]);
524
1139
  shellInner =
525
1140
  ` <section class="pane--code" id="code-pane" aria-label="Source code">` +
526
- `<h2 class="pane-title">Code</h2>\n` +
527
1141
  ` ${codeHtml}\n` +
528
1142
  ` </section>\n` +
529
1143
  ` <div class="gutter" id="gutter" role="separator" aria-orientation="vertical" aria-label="Resize panes"></div>\n` +
530
1144
  ` <section class="pane--doc commentray" id="doc-pane" aria-label="Commentray">\n` +
531
- ` <h2 class="pane-title">Commentray</h2>\n` +
1145
+ ` <div id="doc-pane-body" class="doc-pane-body">\n` +
532
1146
  ` ${commentrayHtml}\n` +
1147
+ ` </div>\n` +
533
1148
  ` </section>\n`;
534
1149
  }
535
- return { layout, shellInner, scrollBlockLinksB64 };
1150
+ return {
1151
+ layout,
1152
+ shellInner,
1153
+ scrollBlockLinksB64,
1154
+ angleSelectHtml: "",
1155
+ multiAnglePayloadB64: "",
1156
+ };
536
1157
  }
537
- function searchChromeFromOptions(opts) {
1158
+ function searchChromeFromOptions(opts, commentrayPathOverride) {
1159
+ const crPath = (commentrayPathOverride ?? opts.commentrayPathForSearch ?? "").trim();
538
1160
  if (opts.staticSearchScope === "commentray-and-paths") {
539
1161
  return {
540
- searchPlaceholder: "Commentray + file paths (ordered tokens + fuzzy lines)…",
541
- shellSearchAttrs: ` data-search-scope="commentray-and-paths" data-search-file-path="${escapeHtml(opts.filePath ?? "")}" data-search-commentray-path="${escapeHtml((opts.commentrayPathForSearch ?? "").trim())}"`,
1162
+ searchPlaceholder: "Filename, path, or keywords…",
1163
+ shellSearchAttrs: ` data-search-scope="commentray-and-paths" data-search-file-path="${escapeHtml(opts.filePath ?? "")}" data-search-commentray-path="${escapeHtml(crPath)}"`,
542
1164
  };
543
1165
  }
544
1166
  return {
545
- searchPlaceholder: "Whole source (ordered tokens + fuzzy lines)…",
1167
+ searchPlaceholder: "Filename, path, or keywords…",
546
1168
  shellSearchAttrs: "",
547
1169
  };
548
1170
  }
@@ -552,6 +1174,38 @@ function shellDocumentedPairsAttrFromOptions(opts) {
552
1174
  return "";
553
1175
  return ` data-documented-pairs-b64="${escapeHtml(emb)}"`;
554
1176
  }
1177
+ function codeBrowserPageTitle(opts) {
1178
+ return opts.title ?? opts.filePath ?? "Commentray";
1179
+ }
1180
+ function codeBrowserHljsThemes(opts) {
1181
+ const hljs = opts.hljsTheme ?? "github";
1182
+ const hljsDark = opts.hljsTheme?.includes("dark") ? opts.hljsTheme : "github-dark";
1183
+ return { hljs, hljsDark };
1184
+ }
1185
+ function toolbarCommentrayGithubFromShell(shell, opts) {
1186
+ return shell.multiShell?.commentrayOnGithubUrl ?? opts.commentrayOnGithubUrl;
1187
+ }
1188
+ function toolbarCommentrayStaticBrowseFromShell(shell, opts) {
1189
+ const t = (shell.multiShell?.commentrayStaticBrowseUrl ??
1190
+ opts.commentrayStaticBrowseUrl ??
1191
+ "").trim();
1192
+ return t.length > 0 ? t : undefined;
1193
+ }
1194
+ function rawMdB64FromShell(shell, opts) {
1195
+ return (shell.multiShell?.rawMdB64 ?? Buffer.from(opts.commentrayMarkdown, "utf8").toString("base64"));
1196
+ }
1197
+ function navRailCommentrayPathFromShell(shell, opts) {
1198
+ const trimmed = (shell.multiShell?.commentrayPathForSearch ??
1199
+ opts.commentrayPathForSearch ??
1200
+ "").trim();
1201
+ return trimmed.length > 0 ? trimmed : undefined;
1202
+ }
1203
+ function shellSearchAttrsWithNavJson(shellSearchAttrsBase, documentedNavJsonUrl) {
1204
+ const navJson = documentedNavJsonUrl?.trim() ?? "";
1205
+ if (navJson.length === 0)
1206
+ return shellSearchAttrsBase;
1207
+ return `${shellSearchAttrsBase} data-nav-search-json-url="${escapeHtml(navJson)}"`;
1208
+ }
555
1209
  /**
556
1210
  * Static HTML shell for a minimal “code browser”: code + rendered commentray,
557
1211
  * draggable vertical splitter, togglable line wrap for the code pane, and
@@ -559,35 +1213,47 @@ function shellDocumentedPairsAttrFromOptions(opts) {
559
1213
  */
560
1214
  export async function renderCodeBrowserHtml(opts) {
561
1215
  const rawCodeB64 = Buffer.from(opts.code, "utf8").toString("base64");
562
- const rawMdB64 = Buffer.from(opts.commentrayMarkdown, "utf8").toString("base64");
563
- const title = opts.title ?? opts.filePath ?? "Commentray";
564
- const filePathHtml = renderFilePathLabel(opts.filePath, title);
565
- const toolbarEndHtml = buildToolbarEndHtml(opts.githubRepoUrl, opts.toolHomeUrl);
566
- const hljs = opts.hljsTheme ?? "github";
567
- const hljsDark = opts.hljsTheme?.includes("dark") ? opts.hljsTheme : "github-dark";
1216
+ const title = codeBrowserPageTitle(opts);
1217
+ const metaDescriptionHtml = renderMetaDescriptionHtml(opts, title);
1218
+ const builtAt = opts.builtAt ?? new Date();
1219
+ const renderSemver = commentrayRenderVersion();
1220
+ const toolbarSiteHubHtml = buildToolbarSiteHubHtml(opts.siteHubUrl);
1221
+ const toolbarEndHtml = buildToolbarEndHtml(opts.githubRepoUrl, opts.toolHomeUrl, renderSemver, opts.siteHubUrl);
1222
+ const pageFooterHtml = renderPageFooterHtml(builtAt);
1223
+ const { hljs, hljsDark } = codeBrowserHljsThemes(opts);
568
1224
  const mermaidScript = mermaidRuntimeScriptHtml(opts.includeMermaidRuntime);
569
1225
  const relatedNavHtml = renderRelatedGithubNavHtml(opts.relatedGithubNav ?? []);
570
1226
  const generatorMetaHtml = renderGeneratorMetaHtml(opts.generatorLabel);
571
- const { toolbarDocHubHtml, documentedPanelHtml } = renderToolbarDocHubHtml({
572
- sourceOnGithubUrl: opts.sourceOnGithubUrl,
573
- commentrayOnGithubUrl: opts.commentrayOnGithubUrl,
1227
+ const layoutPref = opts.codeBrowserLayout ?? "auto";
1228
+ const shell = await buildCodeBrowserShell(opts, layoutPref);
1229
+ const { toolbarDocHubHtml, navRailDocumentedHtml } = renderToolbarDocHubHtml({
574
1230
  documentedNavJsonUrl: opts.documentedNavJsonUrl,
575
1231
  documentedPairsEmbeddedB64: opts.documentedPairsEmbeddedB64,
576
1232
  });
577
- const layoutPref = opts.codeBrowserLayout ?? "auto";
578
- const { layout, shellInner, scrollBlockLinksB64 } = await buildCodeBrowserShell(opts, layoutPref);
579
- const { searchPlaceholder, shellSearchAttrs } = searchChromeFromOptions(opts);
1233
+ const rawMdB64 = rawMdB64FromShell(shell, opts);
1234
+ const scrollBlockLinksB64 = shell.scrollBlockLinksB64;
1235
+ const { searchPlaceholder, shellSearchAttrs: shellSearchAttrsBase } = searchChromeFromOptions(opts, shell.multiShell?.commentrayPathForSearch);
580
1236
  const shellDocumentedPairsAttr = shellDocumentedPairsAttrFromOptions(opts);
1237
+ const shellSearchAttrs = shellSearchAttrsWithNavJson(shellSearchAttrsBase, opts.documentedNavJsonUrl);
1238
+ const navRailContextHtml = renderNavRailContextHtml(opts.filePath, navRailCommentrayPathFromShell(shell, opts), {
1239
+ sourceOnGithubUrl: opts.sourceOnGithubUrl,
1240
+ commentrayOnGithubUrl: toolbarCommentrayGithubFromShell(shell, opts),
1241
+ commentrayStaticBrowseUrl: toolbarCommentrayStaticBrowseFromShell(shell, opts),
1242
+ });
581
1243
  return buildCodeBrowserPageHtml({
582
1244
  title,
1245
+ metaDescriptionHtml,
583
1246
  generatorMetaHtml,
584
- filePathHtml,
1247
+ toolbarSiteHubHtml,
1248
+ navRailContextHtml,
1249
+ angleSelectHtml: shell.angleSelectHtml,
585
1250
  toolbarDocHubHtml,
586
- documentedPanelHtml,
1251
+ navRailDocumentedHtml,
587
1252
  relatedNavHtml,
588
1253
  toolbarEndHtml,
589
- layout,
590
- shellInner,
1254
+ pageFooterHtml,
1255
+ layout: shell.layout,
1256
+ shellInner: shell.shellInner,
591
1257
  rawCodeB64,
592
1258
  rawMdB64,
593
1259
  scrollBlockLinksB64,
@@ -597,6 +1263,7 @@ export async function renderCodeBrowserHtml(opts) {
597
1263
  mermaidScript,
598
1264
  searchPlaceholder,
599
1265
  shellSearchAttrs,
1266
+ multiAngleScriptBlock: shell.multiAnglePayloadB64,
600
1267
  });
601
1268
  }
602
1269
  //# sourceMappingURL=code-browser.js.map