Spaces:
Running
Running
topic layout: drop right rail — Contents + Open questions as collapsibles at top, Open questions + Sources-cited references at bottom; add figure/SVG rendering support
Browse files- app.js +153 -192
- styles.css +98 -45
app.js
CHANGED
|
@@ -26,8 +26,10 @@ const state = {
|
|
| 26 |
collapsed: new Set(JSON.parse(localStorage.getItem('viz-collapsed') || '[]')),
|
| 27 |
treeCollapsed: new Set(JSON.parse(localStorage.getItem('viz-tree-collapsed') || '[]')),
|
| 28 |
srcNs: null, // selected source-namespace filter (null = all)
|
|
|
|
|
|
|
|
|
|
| 29 |
pageContent: new Map(), // path -> article markdown (cached)
|
| 30 |
-
searchText: new Map(), // topic path -> lowercased body, for nav content search
|
| 31 |
citedBy: new Map(), // sourceId -> Set(topic path) that cite it
|
| 32 |
citeCount: new Map(), // sourceId -> # distinct articles citing it
|
| 33 |
wordCount: new Map(), // topic path -> prose word count
|
|
@@ -190,29 +192,6 @@ function setupMarked() {
|
|
| 190 |
renderer(tok) { return citationHTML(tok.id); },
|
| 191 |
}],
|
| 192 |
});
|
| 193 |
-
// Cross-references between topics are authored as bare `category/node` code
|
| 194 |
-
// spans (e.g. `preference-data/ai-feedback-data`) rather than markdown links.
|
| 195 |
-
// Turn each into a live hop to that topic — but only when the page exists, so
|
| 196 |
-
// genuine code spans (`preference/DPO`, planned-but-unwritten nodes) stay plain.
|
| 197 |
-
// (Markdown-link cross-refs are handled separately by rewriteInternalLinks;
|
| 198 |
-
// this href is already a `#/` route, so that pass leaves it untouched.)
|
| 199 |
-
marked.use({
|
| 200 |
-
renderer: {
|
| 201 |
-
// marked@13 hands codespan the already-HTML-escaped code text as a plain
|
| 202 |
-
// string (the {text} token arg is a marked-v16 API) — so read it directly
|
| 203 |
-
// and don't re-escape, or every cross-ref silently falls through to plain.
|
| 204 |
-
codespan(code) {
|
| 205 |
-
const m = /^([a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*)$/.exec(code);
|
| 206 |
-
if (m) {
|
| 207 |
-
const path = `topics/${m[1]}.md`;
|
| 208 |
-
if (state.pages.some(p => p.path === path)) {
|
| 209 |
-
return `<a class="xref" href="#/topic/${encodeURIComponent(path)}" title="Topic: ${m[1]}"><code>${code}</code></a>`;
|
| 210 |
-
}
|
| 211 |
-
}
|
| 212 |
-
return false; // fall back to marked's default code-span rendering
|
| 213 |
-
},
|
| 214 |
-
},
|
| 215 |
-
});
|
| 216 |
marked.setOptions({ gfm: true, breaks: false });
|
| 217 |
}
|
| 218 |
|
|
@@ -290,56 +269,36 @@ function enhanceProse(root, currentPath) {
|
|
| 290 |
if (h.tagName !== 'H4') toc.push({ slug, text: txt, level: h.tagName === 'H2' ? 2 : 3 });
|
| 291 |
});
|
| 292 |
if (window.hljs) $$('pre code', root).forEach(c => { try { hljs.highlightElement(c); } catch (e) {} });
|
|
|
|
| 293 |
rewriteInternalLinks(root, currentPath); // markdown links to topics/sources → in-app routes
|
| 294 |
linkifySectionRefs(root, secMap); // §N / §N.M → jump to that section
|
| 295 |
acronymHovers(root); // known acronyms get a hover definition
|
| 296 |
// remaining external links open in a new tab
|
| 297 |
$$('a[href^="http"]', root).forEach(a => { a.target = '_blank'; a.rel = 'noopener'; });
|
| 298 |
-
highlightDocMatches(root, ($('#navSearch') || {}).value); // mark active search terms in the article
|
| 299 |
return toc;
|
| 300 |
}
|
| 301 |
-
|
| 302 |
-
//
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
mark.textContent = text.slice(idx, idx + q.length);
|
| 319 |
-
frag.append(mark);
|
| 320 |
-
last = idx + q.length;
|
| 321 |
-
idx = lc.indexOf(q, last);
|
| 322 |
-
}
|
| 323 |
-
if (last < text.length) frag.append(text.slice(last));
|
| 324 |
-
node.replaceWith(frag);
|
| 325 |
-
}, { SCRIPT: 1, STYLE: 1, PRE: 1 }); // search highlights headings/inline-code/links too; only fenced code, scripts & KaTeX (via classList) are skipped
|
| 326 |
-
}
|
| 327 |
-
function clearDocHighlights(root) {
|
| 328 |
-
root = root || $('#prose');
|
| 329 |
-
if (!root) return;
|
| 330 |
-
const marks = root.querySelectorAll('mark.search-hl');
|
| 331 |
-
marks.forEach(m => m.replaceWith(document.createTextNode(m.textContent)));
|
| 332 |
-
if (marks.length) root.normalize(); // merge split text nodes so re-highlighting stays clean
|
| 333 |
-
}
|
| 334 |
-
// (Re)highlight both the article body and the aside (open questions, toc) for query q.
|
| 335 |
-
function refreshDocHighlights(q) {
|
| 336 |
-
highlightDocMatches($('#prose'), q);
|
| 337 |
-
highlightDocMatches($('#aside'), q);
|
| 338 |
}
|
| 339 |
|
| 340 |
// Walk visible text nodes under root, skipping code/math/links/headings/abbr.
|
| 341 |
-
function walkTextNodes(root, fn
|
| 342 |
-
const SKIP =
|
| 343 |
const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
| 344 |
acceptNode(n) {
|
| 345 |
if (!n.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
|
|
@@ -500,7 +459,7 @@ async function softRefresh() {
|
|
| 500 |
============================================================ */
|
| 501 |
function layoutAside(on) { $('#layout').classList.toggle('has-aside', !!on); }
|
| 502 |
function setView(html) { $('#view').innerHTML = html; }
|
| 503 |
-
function setAside(html) { $('#aside').innerHTML = html || '';
|
| 504 |
function showError(msg) {
|
| 505 |
layoutAside(false);
|
| 506 |
setView(`<div class="error-box">⚠ ${esc(msg)}<br><br><a class="btn" href="#/">← back to home</a></div>`);
|
|
@@ -751,43 +710,68 @@ async function renderTopic(path) {
|
|
| 751 |
const title = fm.title || meta.title || path.split('/').pop().replace(/\.md$/, '');
|
| 752 |
const cat = meta.parent || path.split('/').slice(-2, -1)[0] || '';
|
| 753 |
|
| 754 |
-
layoutAside(
|
| 755 |
setView(`
|
| 756 |
-
<div class="
|
| 757 |
-
<
|
| 758 |
-
|
| 759 |
-
<div class="doc-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
${maturityBadge(fm.maturity)}
|
| 763 |
-
${fm.sources ? `<span class="doc-sub">${fm.sources.length} source${fm.sources.length === 1 ? '' : 's'} cited</span>` : ''}
|
| 764 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
</div>
|
| 766 |
-
<div class="prose" id="prose">${renderBody(body)}</div>
|
| 767 |
-
${footerHTML()}
|
| 768 |
`);
|
| 769 |
wireShare();
|
| 770 |
const toc = enhanceProse($('#prose'), path);
|
| 771 |
bindInternalLinks($('#prose'));
|
| 772 |
|
| 773 |
-
//
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 791 |
}
|
| 792 |
|
| 793 |
/* ── SOURCE RECORD ─────────────────────────────────────── */
|
|
@@ -994,27 +978,8 @@ function renderTopicsNav(list, q) {
|
|
| 994 |
matches.forEach(n => {
|
| 995 |
const it = el('div', 'nav-item');
|
| 996 |
it.dataset.path = n.page.path;
|
| 997 |
-
|
| 998 |
-
|
| 999 |
-
const { infos, total } = q && (state.searchText.get(n.page.path) || '').includes(q)
|
| 1000 |
-
? bodyMatchInfos(n.page.path, q) : { infos: [], total: 0 };
|
| 1001 |
-
const titleTarget = base + (infos[0] && infos[0].section ? `~${infos[0].section}` : '');
|
| 1002 |
-
|
| 1003 |
-
const title = el('span', 'nav-item-title', highlightMatch(n.page.title || n.slug, q));
|
| 1004 |
-
if (total > 1) title.append(el('span', 'nav-hit-count', String(total)));
|
| 1005 |
-
it.append(title);
|
| 1006 |
-
it.addEventListener('click', () => go(titleTarget)); // clicking the row → first hit / top
|
| 1007 |
-
|
| 1008 |
-
if (infos.length) {
|
| 1009 |
-
const snips = el('div', 'nav-snippets');
|
| 1010 |
-
infos.forEach(h => {
|
| 1011 |
-
const s = el('div', 'nav-snippet', h.snippet);
|
| 1012 |
-
s.addEventListener('click', (e) => { e.stopPropagation(); go(base + (h.section ? `~${h.section}` : '')); });
|
| 1013 |
-
snips.append(s);
|
| 1014 |
-
});
|
| 1015 |
-
if (total > infos.length) snips.append(el('div', 'nav-snippet more', `+${total - infos.length} more match${total - infos.length === 1 ? '' : 'es'}`));
|
| 1016 |
-
it.append(snips);
|
| 1017 |
-
}
|
| 1018 |
body.append(it);
|
| 1019 |
});
|
| 1020 |
wrap.append(body);
|
|
@@ -1105,60 +1070,7 @@ function sortSources(items, mode) {
|
|
| 1105 |
else arr.sort((a, b) => (state.citeCount.get(b.id) || 0) - (state.citeCount.get(a.id) || 0) || (a.title || a.id).localeCompare(b.title || b.id));
|
| 1106 |
return arr;
|
| 1107 |
}
|
| 1108 |
-
|
| 1109 |
-
// body. `q` is already lowercased by callers.
|
| 1110 |
-
const matchPage = (p, q) =>
|
| 1111 |
-
(p.title || '').toLowerCase().includes(q) ||
|
| 1112 |
-
p.path.toLowerCase().includes(q) ||
|
| 1113 |
-
(state.searchText.get(p.path) || '').includes(q);
|
| 1114 |
-
// Every body occurrence of q in a page: a highlighted excerpt + its nearest heading,
|
| 1115 |
-
// so the sidebar can list all in-document hits and each can jump to its own section.
|
| 1116 |
-
// Returns { infos: [...], total } — total is the true count even when infos is capped.
|
| 1117 |
-
function bodyMatchInfos(path, q, cap = 20) {
|
| 1118 |
-
const raw = state.pageContent.get(path), lc = state.searchText.get(path);
|
| 1119 |
-
if (raw == null || lc == null) return { infos: [], total: 0 };
|
| 1120 |
-
const text = parseFrontmatter(raw).body; // same body the index was built from → indices align with lc and hits map to visible prose
|
| 1121 |
-
const infos = []; let total = 0, i = lc.indexOf(q);
|
| 1122 |
-
while (i >= 0) {
|
| 1123 |
-
total++;
|
| 1124 |
-
if (infos.length < cap) {
|
| 1125 |
-
const start = Math.max(0, i - 30);
|
| 1126 |
-
let clip = text.slice(start, i + q.length + 40)
|
| 1127 |
-
.replace(/\[source:[^\]]*\]/g, ' ')
|
| 1128 |
-
.replace(/[#>*_`~|]+/g, ' ')
|
| 1129 |
-
.replace(/\s+/g, ' ').trim();
|
| 1130 |
-
clip = (start > 0 ? '…' : '') + clip + '…';
|
| 1131 |
-
infos.push({ snippet: highlightMatch(clip, q), section: headingSlugBefore(text, i) });
|
| 1132 |
-
}
|
| 1133 |
-
i = lc.indexOf(q, i + q.length);
|
| 1134 |
-
}
|
| 1135 |
-
return { infos, total };
|
| 1136 |
-
}
|
| 1137 |
-
// Slug of the last ##/###/#### heading before `pos`, replicating enhanceProse's
|
| 1138 |
-
// slugify + de-dup (and skipping fenced code) so the id matches the rendered anchor.
|
| 1139 |
-
function headingSlugBefore(md, pos) {
|
| 1140 |
-
const seen = {};
|
| 1141 |
-
let found = '', inFence = false, offset = 0;
|
| 1142 |
-
for (const line of md.split('\n')) {
|
| 1143 |
-
if (/^\s*(```|~~~)/.test(line)) inFence = !inFence;
|
| 1144 |
-
else if (!inFence) {
|
| 1145 |
-
const h = /^(#{2,4})\s+(.+?)\s*#*$/.exec(line);
|
| 1146 |
-
if (h) {
|
| 1147 |
-
let slug = h[2].trim().toLowerCase().replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-').slice(0, 60) || 'sec';
|
| 1148 |
-
if (seen[slug] != null) slug = `${slug}-${++seen[slug]}`; else seen[slug] = 0;
|
| 1149 |
-
if (offset <= pos) found = slug; // last heading at/before the match wins
|
| 1150 |
-
}
|
| 1151 |
-
}
|
| 1152 |
-
offset += line.length + 1; // +1 for the consumed '\n'
|
| 1153 |
-
}
|
| 1154 |
-
return found;
|
| 1155 |
-
}
|
| 1156 |
-
// Throttled nav re-render, used while background indexing fills in during a search.
|
| 1157 |
-
let _navRefreshT = null;
|
| 1158 |
-
function scheduleNavRefresh() {
|
| 1159 |
-
if (_navRefreshT) return;
|
| 1160 |
-
_navRefreshT = setTimeout(() => { _navRefreshT = null; if (($('#navSearch').value || '').trim()) renderNav(); }, 200);
|
| 1161 |
-
}
|
| 1162 |
const matchSource = (s, q) => (s.title || '').toLowerCase().includes(q) || s.id.toLowerCase().includes(q);
|
| 1163 |
function toggleCat(cat) {
|
| 1164 |
if (state.collapsed.has(cat)) state.collapsed.delete(cat); else state.collapsed.add(cat);
|
|
@@ -1180,6 +1092,30 @@ function highlightActive() {
|
|
| 1180 |
Filters an in-memory index, so it stays instant at thousands of items.
|
| 1181 |
============================================================ */
|
| 1182 |
// `text` and `q` must already be lowercased (callers pre-lower for speed).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1183 |
function highlightMatch(text, raw) {
|
| 1184 |
const q = raw.trim();
|
| 1185 |
if (!q) return esc(text);
|
|
@@ -1187,6 +1123,37 @@ function highlightMatch(text, raw) {
|
|
| 1187 |
if (i < 0) return esc(text);
|
| 1188 |
return esc(text.slice(0, i)) + '<b>' + esc(text.slice(i, i + q.length)) + '</b>' + esc(text.slice(i + q.length));
|
| 1189 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1190 |
|
| 1191 |
/* ── mobile nav ───────────���────────────────────────────── */
|
| 1192 |
function closeNav() { document.body.classList.remove('nav-open'); }
|
|
@@ -1235,6 +1202,7 @@ async function loadCore() {
|
|
| 1235 |
}
|
| 1236 |
try { state.taxonomy = taxT && window.jsyaml ? jsyaml.load(taxT) : null; } catch (e) { state.taxonomy = null; }
|
| 1237 |
buildTaxonomy();
|
|
|
|
| 1238 |
}
|
| 1239 |
const SOURCES_CACHE_KEY = 'viz-sources-v1';
|
| 1240 |
function setSources(items) {
|
|
@@ -1245,7 +1213,7 @@ function setSources(items) {
|
|
| 1245 |
if (items.some(s => s.title)) state.sourceTitlesLoaded = true; // tree stage has none yet
|
| 1246 |
}
|
| 1247 |
function refreshSourceViews() {
|
| 1248 |
-
updateCounts();
|
| 1249 |
softRefresh(); // re-renders the current view (incl. the sources browser), upgrading titles/resolution
|
| 1250 |
}
|
| 1251 |
// Reverse the filename sanitization (`:`/`/` → `-`): `arxiv-1707.06347.md` → `arxiv:1707.06347`.
|
|
@@ -1326,6 +1294,13 @@ function buildTaxonomy() {
|
|
| 1326 |
state.tax = cats;
|
| 1327 |
}
|
| 1328 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1329 |
const catLabel = (c) => prettyTitle((c || '').replace(/-/g, ' '));
|
| 1330 |
// Sentence-cased category for headings, e.g. "reward-modeling" → "Reward modeling".
|
| 1331 |
const catDisplay = (c) => { const s = catLabel(c); return s.charAt(0).toUpperCase() + s.slice(1); };
|
|
@@ -1373,8 +1348,6 @@ async function ensureCitemap() {
|
|
| 1373 |
ids.forEach(id => { if (!state.citedBy.has(id)) state.citedBy.set(id, new Set()); state.citedBy.get(id).add(path); });
|
| 1374 |
state.refCount.set(path, refs);
|
| 1375 |
state.wordCount.set(path, countWords(content));
|
| 1376 |
-
state.searchText.set(path, parseFrontmatter(content).body.toLowerCase()); // body only (frontmatter/open-questions excluded) so every hit maps to visible prose
|
| 1377 |
-
if (($('#navSearch').value || '').trim()) scheduleNavRefresh(); // live-fill an in-progress search
|
| 1378 |
} catch (e) { /* skip unreadable article */ }
|
| 1379 |
}
|
| 1380 |
};
|
|
@@ -1427,30 +1400,18 @@ async function boot() {
|
|
| 1427 |
$('#navScrim').addEventListener('click', closeNav);
|
| 1428 |
$('#brandHome').addEventListener('click', () => go('#/'));
|
| 1429 |
let st;
|
| 1430 |
-
|
| 1431 |
-
const focusNavSearch = () => { const s = $('#navSearch'); s.focus(); s.select(); };
|
| 1432 |
-
const clearNavSearch = () => { const s = $('#navSearch'); s.value = ''; syncSearchBadge(); renderNav(); refreshDocHighlights(''); s.focus(); };
|
| 1433 |
-
// The badge is a ⌘K focus hint while empty, and a ✕ clear button once you've typed.
|
| 1434 |
-
const syncSearchBadge = () => {
|
| 1435 |
-
const has = !!$('#navSearch').value;
|
| 1436 |
-
searchBadge.textContent = has ? '✕' : '⌘K';
|
| 1437 |
-
searchBadge.classList.toggle('is-clear', has);
|
| 1438 |
-
searchBadge.title = has ? 'Clear search' : 'Focus search (⌘K)';
|
| 1439 |
-
};
|
| 1440 |
-
$('#navSearch').addEventListener('input', () => {
|
| 1441 |
-
ensureCitemap(); // kick off body indexing on first keystroke so content matches fill in fast
|
| 1442 |
-
syncSearchBadge();
|
| 1443 |
-
clearTimeout(st); st = setTimeout(() => { renderNav(); refreshDocHighlights($('#navSearch').value); }, 120);
|
| 1444 |
-
});
|
| 1445 |
window.addEventListener('hashchange', route);
|
| 1446 |
|
| 1447 |
-
//
|
| 1448 |
-
|
|
|
|
|
|
|
|
|
|
| 1449 |
window.addEventListener('keydown', (e) => {
|
| 1450 |
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault();
|
| 1451 |
-
if (e.key === 'Escape' && document.activeElement === $('#navSearch') && $('#navSearch').value) { clearNavSearch(); return; }
|
| 1452 |
const typing = /^(input|textarea)$/i.test(document.activeElement?.tagName || '');
|
| 1453 |
-
if (e.key === '/' && !typing) { e.preventDefault();
|
| 1454 |
});
|
| 1455 |
initHovercards();
|
| 1456 |
|
|
|
|
| 26 |
collapsed: new Set(JSON.parse(localStorage.getItem('viz-collapsed') || '[]')),
|
| 27 |
treeCollapsed: new Set(JSON.parse(localStorage.getItem('viz-tree-collapsed') || '[]')),
|
| 28 |
srcNs: null, // selected source-namespace filter (null = all)
|
| 29 |
+
palItems: [], // command-palette index
|
| 30 |
+
palFiltered: [],
|
| 31 |
+
palSel: 0,
|
| 32 |
pageContent: new Map(), // path -> article markdown (cached)
|
|
|
|
| 33 |
citedBy: new Map(), // sourceId -> Set(topic path) that cite it
|
| 34 |
citeCount: new Map(), // sourceId -> # distinct articles citing it
|
| 35 |
wordCount: new Map(), // topic path -> prose word count
|
|
|
|
| 192 |
renderer(tok) { return citationHTML(tok.id); },
|
| 193 |
}],
|
| 194 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
marked.setOptions({ gfm: true, breaks: false });
|
| 196 |
}
|
| 197 |
|
|
|
|
| 269 |
if (h.tagName !== 'H4') toc.push({ slug, text: txt, level: h.tagName === 'H2' ? 2 : 3 });
|
| 270 |
});
|
| 271 |
if (window.hljs) $$('pre code', root).forEach(c => { try { hljs.highlightElement(c); } catch (e) {} });
|
| 272 |
+
wrapFigures(root); // images / inline SVG → captioned <figure>
|
| 273 |
rewriteInternalLinks(root, currentPath); // markdown links to topics/sources → in-app routes
|
| 274 |
linkifySectionRefs(root, secMap); // §N / §N.M → jump to that section
|
| 275 |
acronymHovers(root); // known acronyms get a hover definition
|
| 276 |
// remaining external links open in a new tab
|
| 277 |
$$('a[href^="http"]', root).forEach(a => { a.target = '_blank'; a.rel = 'noopener'; });
|
|
|
|
| 278 |
return toc;
|
| 279 |
}
|
| 280 |
+
// Present visuals as proper figures: a markdown image (alt → caption) or a
|
| 281 |
+
// block-level inline <svg> becomes a centered <figure> with an optional caption.
|
| 282 |
+
function wrapFigures(root) {
|
| 283 |
+
$$('img', root).forEach(img => {
|
| 284 |
+
if (img.closest('figure')) return;
|
| 285 |
+
const fig = el('figure', 'fig');
|
| 286 |
+
img.replaceWith(fig); fig.append(img);
|
| 287 |
+
const cap = img.getAttribute('alt');
|
| 288 |
+
if (cap) fig.append(el('figcaption', null, esc(cap)));
|
| 289 |
+
img.loading = 'lazy';
|
| 290 |
+
});
|
| 291 |
+
$$(':scope > svg, :scope > p > svg', root).forEach(svg => {
|
| 292 |
+
const host = svg.parentElement.tagName === 'P' ? svg.parentElement : svg;
|
| 293 |
+
if (host.closest && host.closest('figure')) return;
|
| 294 |
+
const fig = el('figure', 'fig');
|
| 295 |
+
host.replaceWith(fig); fig.append(svg);
|
| 296 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
}
|
| 298 |
|
| 299 |
// Walk visible text nodes under root, skipping code/math/links/headings/abbr.
|
| 300 |
+
function walkTextNodes(root, fn) {
|
| 301 |
+
const SKIP = { CODE: 1, PRE: 1, A: 1, ABBR: 1, SCRIPT: 1, STYLE: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1 };
|
| 302 |
const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
| 303 |
acceptNode(n) {
|
| 304 |
if (!n.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
|
|
|
|
| 459 |
============================================================ */
|
| 460 |
function layoutAside(on) { $('#layout').classList.toggle('has-aside', !!on); }
|
| 461 |
function setView(html) { $('#view').innerHTML = html; }
|
| 462 |
+
function setAside(html) { $('#aside').innerHTML = html || ''; }
|
| 463 |
function showError(msg) {
|
| 464 |
layoutAside(false);
|
| 465 |
setView(`<div class="error-box">⚠ ${esc(msg)}<br><br><a class="btn" href="#/">← back to home</a></div>`);
|
|
|
|
| 710 |
const title = fm.title || meta.title || path.split('/').pop().replace(/\.md$/, '');
|
| 711 |
const cat = meta.parent || path.split('/').slice(-2, -1)[0] || '';
|
| 712 |
|
| 713 |
+
layoutAside(false); // no right rail — single reading column
|
| 714 |
setView(`
|
| 715 |
+
<div class="doc">
|
| 716 |
+
<div class="crumbs"><a href="#/">Wiki</a><span class="sep">/</span>
|
| 717 |
+
<span>${esc(catDisplay(cat))}</span></div>
|
| 718 |
+
<div class="doc-head">
|
| 719 |
+
<div class="doc-kicker">Topic Article<button class="share-btn" data-share title="Copy a link to this page">🔗 copy link</button></div>
|
| 720 |
+
<h1 class="doc-title">${esc(title)}</h1>
|
| 721 |
+
<div class="doc-meta-row">${maturityBadge(fm.maturity)}</div>
|
|
|
|
| 722 |
</div>
|
| 723 |
+
<div id="docTop"></div>
|
| 724 |
+
<div class="prose" id="prose">${renderBody(body)}</div>
|
| 725 |
+
<div id="docBottom"></div>
|
| 726 |
+
${footerHTML()}
|
| 727 |
</div>
|
|
|
|
|
|
|
| 728 |
`);
|
| 729 |
wireShare();
|
| 730 |
const toc = enhanceProse($('#prose'), path);
|
| 731 |
bindInternalLinks($('#prose'));
|
| 732 |
|
| 733 |
+
// cited sources, in the order they first appear in the article
|
| 734 |
+
const citedIds = []; const seenCite = new Set();
|
| 735 |
+
$$('#prose .cite[data-src]').forEach(a => { const id = a.dataset.src; if (!seenCite.has(id)) { seenCite.add(id); citedIds.push(id); } });
|
| 736 |
+
const base = location.hash.split('~')[0];
|
| 737 |
+
|
| 738 |
+
// TOP: contents + open questions (both collapsed, unobtrusive)
|
| 739 |
+
let top = '';
|
| 740 |
+
if (toc.length) top += contentsDisclosure(toc, base);
|
| 741 |
+
if (fm.open_questions?.length) top += openQuestionsDisclosure(fm.open_questions, false);
|
| 742 |
+
$('#docTop').innerHTML = top;
|
| 743 |
+
|
| 744 |
+
// BOTTOM: open questions (expanded) + the references + this-page links
|
| 745 |
+
let bot = '';
|
| 746 |
+
if (fm.open_questions?.length) bot += openQuestionsDisclosure(fm.open_questions, true);
|
| 747 |
+
bot += referencesSection(citedIds.length ? citedIds : (fm.sources || []));
|
| 748 |
+
bot += `<div class="page-links"><a href="${esc(RESOLVE)}/${esc(path)}" target="_blank" rel="noopener">raw markdown</a><a href="${esc(DATASET_BLOB)}/${esc(path)}" target="_blank" rel="noopener">source on HF ↗</a></div>`;
|
| 749 |
+
$('#docBottom').innerHTML = bot;
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
// collapsible <details> blocks used in the article flow
|
| 753 |
+
function openQuestionsDisclosure(list, open) {
|
| 754 |
+
return `<details class="disclosure oq-d"${open ? ' open' : ''}>
|
| 755 |
+
<summary><span class="disc-ic">?</span> Open questions <span class="disc-n">${list.length}</span></summary>
|
| 756 |
+
<ul class="oq-list">${list.map(q => `<li>${renderInline(q)}</li>`).join('')}</ul>
|
| 757 |
+
</details>`;
|
| 758 |
+
}
|
| 759 |
+
function contentsDisclosure(toc, base) {
|
| 760 |
+
return `<details class="disclosure toc-d">
|
| 761 |
+
<summary><span class="disc-ic">≡</span> Contents <span class="disc-n">${toc.length}</span></summary>
|
| 762 |
+
<nav class="toc">${toc.map(t => `<a href="${base}~${t.slug}" class="${t.level === 3 ? 'h3' : ''}">${esc(t.text)}</a>`).join('')}</nav>
|
| 763 |
+
</details>`;
|
| 764 |
+
}
|
| 765 |
+
// references (sources cited), listed at the end of the article
|
| 766 |
+
function referencesSection(ids) {
|
| 767 |
+
if (!ids.length) return '';
|
| 768 |
+
return `<section class="refs-section"><h2 class="refs-h">Sources cited<span class="refs-n">${ids.length}</span></h2>
|
| 769 |
+
<ol class="refs-list">${ids.map(id => {
|
| 770 |
+
const s = state.srcById.get(id); const ext = externalUrl(id);
|
| 771 |
+
return `<li><a class="ref-id" href="#/source/${encodeURIComponent(id)}" data-src="${esc(id)}">${esc(id)}</a>` +
|
| 772 |
+
(s && s.title ? `<span class="ref-title">${esc(s.title)}</span>` : '') +
|
| 773 |
+
(ext ? ` <a class="ref-ext" href="${esc(ext)}" target="_blank" rel="noopener" title="open the original">↗</a>` : '') + `</li>`;
|
| 774 |
+
}).join('')}</ol></section>`;
|
| 775 |
}
|
| 776 |
|
| 777 |
/* ── SOURCE RECORD ─────────────────────────────────────── */
|
|
|
|
| 978 |
matches.forEach(n => {
|
| 979 |
const it = el('div', 'nav-item');
|
| 980 |
it.dataset.path = n.page.path;
|
| 981 |
+
it.innerHTML = `${esc(n.page.title || n.slug)}`;
|
| 982 |
+
it.addEventListener('click', () => go(`#/topic/${encodeURIComponent(n.page.path)}`));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 983 |
body.append(it);
|
| 984 |
});
|
| 985 |
wrap.append(body);
|
|
|
|
| 1070 |
else arr.sort((a, b) => (state.citeCount.get(b.id) || 0) - (state.citeCount.get(a.id) || 0) || (a.title || a.id).localeCompare(b.title || b.id));
|
| 1071 |
return arr;
|
| 1072 |
}
|
| 1073 |
+
const matchPage = (p, q) => (p.title || '').toLowerCase().includes(q) || p.path.toLowerCase().includes(q);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1074 |
const matchSource = (s, q) => (s.title || '').toLowerCase().includes(q) || s.id.toLowerCase().includes(q);
|
| 1075 |
function toggleCat(cat) {
|
| 1076 |
if (state.collapsed.has(cat)) state.collapsed.delete(cat); else state.collapsed.add(cat);
|
|
|
|
| 1092 |
Filters an in-memory index, so it stays instant at thousands of items.
|
| 1093 |
============================================================ */
|
| 1094 |
// `text` and `q` must already be lowercased (callers pre-lower for speed).
|
| 1095 |
+
function fuzzyScore(q, text) {
|
| 1096 |
+
const idx = text.indexOf(q);
|
| 1097 |
+
if (idx === 0) return 1000; // prefix match — best
|
| 1098 |
+
if (idx > 0) return 700 - Math.min(idx, 300);
|
| 1099 |
+
// subsequence fallback — start at the first occurrence of q[0]; bail cheaply if absent.
|
| 1100 |
+
let start = text.indexOf(q[0]);
|
| 1101 |
+
if (start < 0) return -1;
|
| 1102 |
+
let qi = 1, gaps = 0, last = start;
|
| 1103 |
+
for (let ti = start + 1; ti < text.length && qi < q.length; ti++) {
|
| 1104 |
+
if (text[ti] === q[qi]) { gaps += ti - last - 1; last = ti; qi++; }
|
| 1105 |
+
}
|
| 1106 |
+
return qi === q.length ? 300 - Math.min(gaps, 200) - Math.min(start, 50) : -1;
|
| 1107 |
+
}
|
| 1108 |
+
function paletteResults(raw) {
|
| 1109 |
+
const q = raw.toLowerCase().trim();
|
| 1110 |
+
if (!q) return state.palItems.slice(0, 40);
|
| 1111 |
+
const scored = [];
|
| 1112 |
+
for (const it of state.palItems) {
|
| 1113 |
+
const s = Math.max(fuzzyScore(q, it._l), fuzzyScore(q, it._s) - 60);
|
| 1114 |
+
if (s > -1) scored.push({ it, s });
|
| 1115 |
+
}
|
| 1116 |
+
scored.sort((a, b) => b.s - a.s || a.it.label.localeCompare(b.it.label));
|
| 1117 |
+
return scored.slice(0, 40).map(x => x.it);
|
| 1118 |
+
}
|
| 1119 |
function highlightMatch(text, raw) {
|
| 1120 |
const q = raw.trim();
|
| 1121 |
if (!q) return esc(text);
|
|
|
|
| 1123 |
if (i < 0) return esc(text);
|
| 1124 |
return esc(text.slice(0, i)) + '<b>' + esc(text.slice(i, i + q.length)) + '</b>' + esc(text.slice(i + q.length));
|
| 1125 |
}
|
| 1126 |
+
function renderPalette() {
|
| 1127 |
+
const q = $('#palInput').value;
|
| 1128 |
+
state.palFiltered = paletteResults(q);
|
| 1129 |
+
if (state.palSel >= state.palFiltered.length) state.palSel = 0;
|
| 1130 |
+
const box = $('#palResults');
|
| 1131 |
+
if (!state.palFiltered.length) { box.innerHTML = `<div class="pal-empty">No matches for “${esc(q)}”.</div>`; return; }
|
| 1132 |
+
box.innerHTML = state.palFiltered.map((it, i) => `
|
| 1133 |
+
<div class="pal-item${i === state.palSel ? ' sel' : ''}" data-i="${i}">
|
| 1134 |
+
<span class="pal-kind ${it.kind}">${it.kind}</span>
|
| 1135 |
+
<span class="pal-text"><span class="pal-label">${highlightMatch(it.label, q)}</span><span class="pal-sub">${esc(it.sub)}</span></span>
|
| 1136 |
+
</div>`).join('');
|
| 1137 |
+
$$('#palResults .pal-item').forEach(n => {
|
| 1138 |
+
n.addEventListener('mousemove', () => { if (state.palSel !== +n.dataset.i) { state.palSel = +n.dataset.i; markPalSel(); } });
|
| 1139 |
+
n.addEventListener('click', () => choosePal(+n.dataset.i));
|
| 1140 |
+
});
|
| 1141 |
+
}
|
| 1142 |
+
function markPalSel() { $$('#palResults .pal-item').forEach((n, i) => n.classList.toggle('sel', i === state.palSel)); }
|
| 1143 |
+
function scrollPalSel() { $$('#palResults .pal-item')[state.palSel]?.scrollIntoView({ block: 'nearest' }); }
|
| 1144 |
+
function choosePal(i) { const it = state.palFiltered[i]; if (it) { closePalette(); go(it.hash); } }
|
| 1145 |
+
function openPalette() {
|
| 1146 |
+
const sc = $('#palScrim'); sc.hidden = false;
|
| 1147 |
+
const inp = $('#palInput'); inp.value = ''; state.palSel = 0;
|
| 1148 |
+
renderPalette(); inp.focus();
|
| 1149 |
+
}
|
| 1150 |
+
function closePalette() { $('#palScrim').hidden = true; }
|
| 1151 |
+
function palKeydown(e) {
|
| 1152 |
+
if (e.key === 'ArrowDown') { e.preventDefault(); state.palSel = Math.min(state.palSel + 1, state.palFiltered.length - 1); markPalSel(); scrollPalSel(); }
|
| 1153 |
+
else if (e.key === 'ArrowUp') { e.preventDefault(); state.palSel = Math.max(state.palSel - 1, 0); markPalSel(); scrollPalSel(); }
|
| 1154 |
+
else if (e.key === 'Enter') { e.preventDefault(); choosePal(state.palSel); }
|
| 1155 |
+
else if (e.key === 'Escape') { e.preventDefault(); closePalette(); }
|
| 1156 |
+
}
|
| 1157 |
|
| 1158 |
/* ── mobile nav ───────────���────────────────────────────── */
|
| 1159 |
function closeNav() { document.body.classList.remove('nav-open'); }
|
|
|
|
| 1202 |
}
|
| 1203 |
try { state.taxonomy = taxT && window.jsyaml ? jsyaml.load(taxT) : null; } catch (e) { state.taxonomy = null; }
|
| 1204 |
buildTaxonomy();
|
| 1205 |
+
buildPaletteIndex();
|
| 1206 |
}
|
| 1207 |
const SOURCES_CACHE_KEY = 'viz-sources-v1';
|
| 1208 |
function setSources(items) {
|
|
|
|
| 1213 |
if (items.some(s => s.title)) state.sourceTitlesLoaded = true; // tree stage has none yet
|
| 1214 |
}
|
| 1215 |
function refreshSourceViews() {
|
| 1216 |
+
buildPaletteIndex(); updateCounts();
|
| 1217 |
softRefresh(); // re-renders the current view (incl. the sources browser), upgrading titles/resolution
|
| 1218 |
}
|
| 1219 |
// Reverse the filename sanitization (`:`/`/` → `-`): `arxiv-1707.06347.md` → `arxiv:1707.06347`.
|
|
|
|
| 1294 |
state.tax = cats;
|
| 1295 |
}
|
| 1296 |
|
| 1297 |
+
function buildPaletteIndex() {
|
| 1298 |
+
const mk = (kind, label, sub, hash) => ({ kind, label, sub, hash, _l: label.toLowerCase(), _s: sub.toLowerCase() });
|
| 1299 |
+
state.palItems = [
|
| 1300 |
+
...state.pages.map(p => mk('topic', p.title || p.path, catLabel(p._cat || p.parent || ''), `#/topic/${encodeURIComponent(p.path)}`)),
|
| 1301 |
+
...state.sources.map(s => mk('source', s.title || s.id, s.id, `#/source/${encodeURIComponent(s.id)}`)),
|
| 1302 |
+
];
|
| 1303 |
+
}
|
| 1304 |
const catLabel = (c) => prettyTitle((c || '').replace(/-/g, ' '));
|
| 1305 |
// Sentence-cased category for headings, e.g. "reward-modeling" → "Reward modeling".
|
| 1306 |
const catDisplay = (c) => { const s = catLabel(c); return s.charAt(0).toUpperCase() + s.slice(1); };
|
|
|
|
| 1348 |
ids.forEach(id => { if (!state.citedBy.has(id)) state.citedBy.set(id, new Set()); state.citedBy.get(id).add(path); });
|
| 1349 |
state.refCount.set(path, refs);
|
| 1350 |
state.wordCount.set(path, countWords(content));
|
|
|
|
|
|
|
| 1351 |
} catch (e) { /* skip unreadable article */ }
|
| 1352 |
}
|
| 1353 |
};
|
|
|
|
| 1400 |
$('#navScrim').addEventListener('click', closeNav);
|
| 1401 |
$('#brandHome').addEventListener('click', () => go('#/'));
|
| 1402 |
let st;
|
| 1403 |
+
$('#navSearch').addEventListener('input', () => { clearTimeout(st); st = setTimeout(renderNav, 120); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1404 |
window.addEventListener('hashchange', route);
|
| 1405 |
|
| 1406 |
+
// command palette
|
| 1407 |
+
$('#paletteOpen').addEventListener('click', openPalette);
|
| 1408 |
+
$('#palInput').addEventListener('input', renderPalette);
|
| 1409 |
+
$('#palInput').addEventListener('keydown', palKeydown);
|
| 1410 |
+
$('#palScrim').addEventListener('click', (e) => { if (e.target === $('#palScrim')) closePalette(); });
|
| 1411 |
window.addEventListener('keydown', (e) => {
|
| 1412 |
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); $('#palScrim').hidden ? openPalette() : closePalette(); return; }
|
|
|
|
| 1413 |
const typing = /^(input|textarea)$/i.test(document.activeElement?.tagName || '');
|
| 1414 |
+
if (e.key === '/' && !typing && $('#palScrim').hidden) { e.preventDefault(); openPalette(); }
|
| 1415 |
});
|
| 1416 |
initHovercards();
|
| 1417 |
|
styles.css
CHANGED
|
@@ -24,8 +24,6 @@
|
|
| 24 |
--accent-deep: #0a275f;
|
| 25 |
--accent-soft: #dde6f5;
|
| 26 |
--accent-hover-row: #c8d6ee;
|
| 27 |
-
--hl-bg: #0f3787; /* search-hit rectangle */
|
| 28 |
-
--hl-ink: #ffffff;
|
| 29 |
--good: #1c7c3f;
|
| 30 |
--warn: #9a6b00;
|
| 31 |
--danger: #a12d2d;
|
|
@@ -55,8 +53,6 @@
|
|
| 55 |
--accent-deep: #b6cdf7;
|
| 56 |
--accent-soft: #20304d;
|
| 57 |
--accent-hover-row: #28395a;
|
| 58 |
-
--hl-bg: #4f7fe0;
|
| 59 |
-
--hl-ink: #ffffff;
|
| 60 |
--good: #6fcf8f;
|
| 61 |
--warn: #e0b15a;
|
| 62 |
--danger: #e08585;
|
|
@@ -163,7 +159,7 @@ a:hover { color: var(--accent-deep); }
|
|
| 163 |
content: "⌕"; position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
|
| 164 |
color: var(--muted-3); font-size: 15px;
|
| 165 |
}
|
| 166 |
-
/* the
|
| 167 |
.search-k {
|
| 168 |
position: absolute; right: 7px; top: 50%; transform: translateY(-50%);
|
| 169 |
font-family: var(--mono); font-size: 10px; padding: 2px 6px; border-radius: 4px;
|
|
@@ -171,9 +167,6 @@ a:hover { color: var(--accent-deep); }
|
|
| 171 |
cursor: pointer; transition: all .12s;
|
| 172 |
}
|
| 173 |
.search-k:hover { border-color: var(--accent); color: var(--accent-deep); }
|
| 174 |
-
/* once a query is entered, the badge becomes a ✕ clear button */
|
| 175 |
-
.search-k.is-clear { font-family: var(--sans); font-size: 12px; line-height: 1; padding: 3px 6.5px; }
|
| 176 |
-
.search-k.is-clear:hover { background: var(--accent-soft); }
|
| 177 |
|
| 178 |
.nav-group { margin-bottom: 14px; }
|
| 179 |
.nav-group-title {
|
|
@@ -198,25 +191,6 @@ a:hover { color: var(--accent-deep); }
|
|
| 198 |
display: block; font-family: var(--mono); font-size: 9.5px;
|
| 199 |
color: var(--muted-4); margin-top: 1px; letter-spacing: 0.2px;
|
| 200 |
}
|
| 201 |
-
/* content-search: a matched-body excerpt under the topic name, query bolded */
|
| 202 |
-
.nav-item .nav-item-title { display: block; }
|
| 203 |
-
.nav-item .nav-item-title b { color: var(--accent-deep); font-weight: 600; }
|
| 204 |
-
.nav-item .nav-snippets { display: block; margin-top: 2px; }
|
| 205 |
-
.nav-item .nav-snippet {
|
| 206 |
-
display: block; margin-top: 2px; padding: 2px 6px; font-size: 11px; line-height: 1.4;
|
| 207 |
-
color: var(--muted-3); font-weight: 400; cursor: pointer;
|
| 208 |
-
border-left: 2px solid var(--border); border-radius: 0 4px 4px 0;
|
| 209 |
-
transition: background .12s, border-color .12s, color .12s;
|
| 210 |
-
}
|
| 211 |
-
.nav-item .nav-snippet:hover { background: var(--accent-soft); border-left-color: var(--accent); color: var(--ink); }
|
| 212 |
-
.nav-item .nav-snippet b { color: var(--accent-deep); font-weight: 600; }
|
| 213 |
-
.nav-item .nav-snippet.more { font-style: italic; cursor: default; border-left-color: transparent; color: var(--muted-4); }
|
| 214 |
-
.nav-item .nav-snippet.more:hover { background: none; }
|
| 215 |
-
.nav-hit-count {
|
| 216 |
-
display: inline-block; margin-left: 5px; padding: 0 5px; font-family: var(--mono);
|
| 217 |
-
font-size: 9px; line-height: 15px; vertical-align: 1px; border-radius: 8px;
|
| 218 |
-
background: var(--accent-soft); color: var(--accent-deep); font-weight: 600;
|
| 219 |
-
}
|
| 220 |
.nav-empty { color: var(--muted-4); font-size: 11.5px; padding: 8px; font-style: italic; }
|
| 221 |
.mini-spin { display: inline-block; width: 10px; height: 10px; vertical-align: -1px; margin-right: 4px;
|
| 222 |
border: 1.5px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite; }
|
|
@@ -234,6 +208,49 @@ a:hover { color: var(--accent-deep); }
|
|
| 234 |
.crumbs .sep { color: var(--muted-5); }
|
| 235 |
|
| 236 |
/* doc header */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
.doc-head { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
|
| 238 |
.doc-kicker {
|
| 239 |
font-family: var(--mono); font-size: 10.5px; letter-spacing: 1.5px;
|
|
@@ -338,23 +355,6 @@ a:hover { color: var(--accent-deep); }
|
|
| 338 |
font-size: 12.8px; line-height: 1.55;
|
| 339 |
}
|
| 340 |
.prose pre code { background: none; border: none; padding: 0; font-size: inherit; color: var(--ink-2); }
|
| 341 |
-
/* cross-reference: a `category/node` code span linked to its topic page */
|
| 342 |
-
.prose a.xref { border-bottom: none; text-decoration: none; }
|
| 343 |
-
.prose a.xref code {
|
| 344 |
-
background: var(--accent-soft); border-color: transparent; color: var(--accent-deep);
|
| 345 |
-
cursor: pointer; transition: background .12s, color .12s;
|
| 346 |
-
}
|
| 347 |
-
.prose a.xref:hover code { background: var(--accent); color: #fff; }
|
| 348 |
-
[data-theme="dark"] .prose a.xref:hover code { color: #0b1220; }
|
| 349 |
-
/* active-search term highlighting inside the article (cleared with the search) —
|
| 350 |
-
bold, slightly larger blue text (no pill, so it doesn't read like a link chip) */
|
| 351 |
-
mark.search-hl {
|
| 352 |
-
background: var(--hl-bg); color: var(--hl-ink);
|
| 353 |
-
font-weight: 600; border-radius: 3px;
|
| 354 |
-
padding: 0.5px 3.5px; margin: 0 0.5px;
|
| 355 |
-
box-shadow: 0 0 0 1px var(--hl-bg);
|
| 356 |
-
-webkit-box-decoration-break: clone; box-decoration-break: clone;
|
| 357 |
-
}
|
| 358 |
|
| 359 |
/* blockquote */
|
| 360 |
.prose blockquote {
|
|
@@ -595,6 +595,52 @@ mark.search-hl {
|
|
| 595 |
}
|
| 596 |
@media (min-width: 901px) { .nav-scrim { display: none !important; } }
|
| 597 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
|
| 599 |
/* ── Collapsible taxonomy tree (topics sidebar) ────────── */
|
| 600 |
.nav-cat { margin-bottom: 10px; }
|
|
@@ -768,7 +814,7 @@ body.book-mode .main { padding: 0; }
|
|
| 768 |
|
| 769 |
/* ── Print (browser "Save as PDF") ─────────────────────── */
|
| 770 |
@media print {
|
| 771 |
-
.topbar, .sidebar, .aside, .nav-scrim, .book-toolbar, #hovercard, #toast, .foot, .menu-toggle { display: none !important; }
|
| 772 |
html, body { background: #fff; color: #000; }
|
| 773 |
.layout, .main, body.book-mode .main { display: block; padding: 0; margin: 0; }
|
| 774 |
.book { max-width: none; margin: 0; padding: 0; color: #111; font-size: 10.5pt; }
|
|
@@ -787,4 +833,11 @@ body.book-mode .main { padding: 0; }
|
|
| 787 |
.cite .cite-mark { display: none; }
|
| 788 |
a.secref { color: #111; border: none; }
|
| 789 |
.prose abbr.ac { border: none; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
}
|
|
|
|
| 24 |
--accent-deep: #0a275f;
|
| 25 |
--accent-soft: #dde6f5;
|
| 26 |
--accent-hover-row: #c8d6ee;
|
|
|
|
|
|
|
| 27 |
--good: #1c7c3f;
|
| 28 |
--warn: #9a6b00;
|
| 29 |
--danger: #a12d2d;
|
|
|
|
| 53 |
--accent-deep: #b6cdf7;
|
| 54 |
--accent-soft: #20304d;
|
| 55 |
--accent-hover-row: #28395a;
|
|
|
|
|
|
|
| 56 |
--good: #6fcf8f;
|
| 57 |
--warn: #e0b15a;
|
| 58 |
--danger: #e08585;
|
|
|
|
| 159 |
content: "⌕"; position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
|
| 160 |
color: var(--muted-3); font-size: 15px;
|
| 161 |
}
|
| 162 |
+
/* the one global-search affordance: click (or ⌘K) → command palette */
|
| 163 |
.search-k {
|
| 164 |
position: absolute; right: 7px; top: 50%; transform: translateY(-50%);
|
| 165 |
font-family: var(--mono); font-size: 10px; padding: 2px 6px; border-radius: 4px;
|
|
|
|
| 167 |
cursor: pointer; transition: all .12s;
|
| 168 |
}
|
| 169 |
.search-k:hover { border-color: var(--accent); color: var(--accent-deep); }
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
.nav-group { margin-bottom: 14px; }
|
| 172 |
.nav-group-title {
|
|
|
|
| 191 |
display: block; font-family: var(--mono); font-size: 9.5px;
|
| 192 |
color: var(--muted-4); margin-top: 1px; letter-spacing: 0.2px;
|
| 193 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
.nav-empty { color: var(--muted-4); font-size: 11.5px; padding: 8px; font-style: italic; }
|
| 195 |
.mini-spin { display: inline-block; width: 10px; height: 10px; vertical-align: -1px; margin-right: 4px;
|
| 196 |
border: 1.5px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite; }
|
|
|
|
| 208 |
.crumbs .sep { color: var(--muted-5); }
|
| 209 |
|
| 210 |
/* doc header */
|
| 211 |
+
/* single reading column for topic articles (no right rail) */
|
| 212 |
+
.doc { max-width: 820px; margin: 0 auto; }
|
| 213 |
+
.doc .prose { max-width: none; }
|
| 214 |
+
|
| 215 |
+
/* collapsible disclosures used in the article flow (Contents, Open questions) */
|
| 216 |
+
.disclosure { border: 1px solid var(--border); border-radius: 9px; background: var(--bg-card); margin: 14px 0; overflow: hidden; }
|
| 217 |
+
.disclosure > summary {
|
| 218 |
+
list-style: none; cursor: pointer; padding: 10px 14px; display: flex; align-items: center; gap: 8px;
|
| 219 |
+
font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase; color: var(--ink-3);
|
| 220 |
+
}
|
| 221 |
+
.disclosure > summary::-webkit-details-marker { display: none; }
|
| 222 |
+
.disclosure > summary:hover { color: var(--ink); }
|
| 223 |
+
.disclosure > summary::after { content: "▸"; margin-left: auto; color: var(--muted-4); transition: transform .15s; }
|
| 224 |
+
.disclosure[open] > summary::after { transform: rotate(90deg); }
|
| 225 |
+
.disclosure .disc-ic { color: var(--accent); font-weight: 700; }
|
| 226 |
+
.disclosure .disc-n { color: var(--muted-4); font-weight: 500; }
|
| 227 |
+
.disclosure.oq-d { border-left: 3px solid var(--accent); }
|
| 228 |
+
.disclosure .oq-list, .disclosure .toc { padding: 2px 16px 14px; }
|
| 229 |
+
.disclosure .toc { display: block; }
|
| 230 |
+
.disclosure .toc a { display: block; font-size: 12.5px; color: var(--muted); padding: 3px 0 3px 10px; border-left: 2px solid var(--border-soft); }
|
| 231 |
+
.disclosure .toc a.h3 { padding-left: 22px; font-size: 11.5px; }
|
| 232 |
+
.disclosure .toc a:hover { color: var(--accent); border-left-color: var(--accent); }
|
| 233 |
+
|
| 234 |
+
/* references (sources cited), at the end of the article */
|
| 235 |
+
.refs-section { margin: 40px 0 8px; padding-top: 20px; border-top: 1px solid var(--border); }
|
| 236 |
+
.refs-h { font-family: var(--mono); font-size: 15px; font-weight: 600; color: var(--ink); margin-bottom: 12px; display: flex; align-items: baseline; gap: 8px; }
|
| 237 |
+
.refs-h .refs-n { font-size: 11px; color: var(--muted-4); font-weight: 500; }
|
| 238 |
+
.refs-list { list-style: decimal; margin: 0 0 0 26px; padding: 0; }
|
| 239 |
+
.refs-list li { margin: 5px 0; font-size: 13px; line-height: 1.5; }
|
| 240 |
+
.refs-list li::marker { color: var(--muted-4); font-family: var(--mono); font-size: 11px; }
|
| 241 |
+
.ref-id { font-family: var(--mono); font-size: 11.5px; color: var(--accent); border-bottom: none; }
|
| 242 |
+
.ref-id:hover { border-bottom: 1px solid var(--accent); }
|
| 243 |
+
.ref-title { color: var(--ink-3); margin-left: 8px; }
|
| 244 |
+
.ref-ext { color: var(--muted-4); border-bottom: none; }
|
| 245 |
+
.page-links { margin: 22px 0 0; display: flex; gap: 16px; font-family: var(--mono); font-size: 11px; }
|
| 246 |
+
.page-links a { color: var(--muted-3); }
|
| 247 |
+
.page-links a:hover { color: var(--accent); }
|
| 248 |
+
|
| 249 |
+
/* figures — images, inline SVG diagrams, generated plots */
|
| 250 |
+
.prose .fig { margin: 22px 0; text-align: center; }
|
| 251 |
+
.prose .fig img, .prose .fig svg { max-width: 100%; height: auto; border-radius: 8px; }
|
| 252 |
+
.prose .fig figcaption { font-size: 12px; color: var(--muted-2); margin-top: 8px; line-height: 1.45; text-align: center; }
|
| 253 |
+
|
| 254 |
.doc-head { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
|
| 255 |
.doc-kicker {
|
| 256 |
font-family: var(--mono); font-size: 10.5px; letter-spacing: 1.5px;
|
|
|
|
| 355 |
font-size: 12.8px; line-height: 1.55;
|
| 356 |
}
|
| 357 |
.prose pre code { background: none; border: none; padding: 0; font-size: inherit; color: var(--ink-2); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
|
| 359 |
/* blockquote */
|
| 360 |
.prose blockquote {
|
|
|
|
| 595 |
}
|
| 596 |
@media (min-width: 901px) { .nav-scrim { display: none !important; } }
|
| 597 |
|
| 598 |
+
/* ── command palette ───────────────────────────────────── */
|
| 599 |
+
.palette-foot kbd, .pal-esc {
|
| 600 |
+
font-family: var(--mono); font-size: 10px; padding: 1.5px 5px; border-radius: 4px;
|
| 601 |
+
border: 1px solid var(--border); background: var(--bg-soft); color: var(--muted-2); line-height: 1.4;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
.palette-scrim {
|
| 605 |
+
position: fixed; inset: 0; z-index: 200; background: rgba(15,18,22,0.42);
|
| 606 |
+
backdrop-filter: blur(2px); display: flex; align-items: flex-start; justify-content: center;
|
| 607 |
+
padding: 12vh 16px 16px;
|
| 608 |
+
}
|
| 609 |
+
.palette-scrim[hidden] { display: none; }
|
| 610 |
+
.palette {
|
| 611 |
+
width: 100%; max-width: 580px; background: var(--bg-card); border: 1px solid var(--border);
|
| 612 |
+
border-radius: 12px; box-shadow: 0 12px 48px rgba(0,0,0,0.28); overflow: hidden;
|
| 613 |
+
display: flex; flex-direction: column; max-height: 70vh;
|
| 614 |
+
}
|
| 615 |
+
.palette-search { display: flex; align-items: center; gap: 10px; padding: 12px 15px; border-bottom: 1px solid var(--border); }
|
| 616 |
+
.pal-icon { color: var(--muted-3); font-size: 17px; }
|
| 617 |
+
.palette-input {
|
| 618 |
+
flex: 1; border: none; background: none; outline: none; color: var(--ink);
|
| 619 |
+
font-family: var(--sans); font-size: 15px; font-weight: 300;
|
| 620 |
+
}
|
| 621 |
+
.palette-results { overflow-y: auto; padding: 6px; }
|
| 622 |
+
.pal-item {
|
| 623 |
+
display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 7px; cursor: pointer;
|
| 624 |
+
}
|
| 625 |
+
.pal-item.sel { background: var(--accent-soft); }
|
| 626 |
+
.pal-kind {
|
| 627 |
+
flex: 0 0 auto; font-family: var(--mono); font-size: 9px; font-weight: 600; text-transform: uppercase;
|
| 628 |
+
letter-spacing: 0.5px; padding: 2px 6px; border-radius: 4px; width: 50px; text-align: center;
|
| 629 |
+
}
|
| 630 |
+
.pal-kind.topic { background: var(--accent-soft); color: var(--accent-deep); }
|
| 631 |
+
.pal-kind.source { background: var(--bg-soft); color: var(--muted-2); border: 1px solid var(--border-soft); }
|
| 632 |
+
.pal-text { flex: 1; min-width: 0; }
|
| 633 |
+
.pal-label { font-size: 13.5px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 634 |
+
.pal-label b { color: var(--accent-deep); font-weight: 600; }
|
| 635 |
+
.pal-sub { font-family: var(--mono); font-size: 10px; color: var(--muted-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 636 |
+
.pal-empty { padding: 24px; text-align: center; color: var(--muted-3); font-size: 13px; }
|
| 637 |
+
.palette-foot {
|
| 638 |
+
display: flex; gap: 14px; align-items: center; padding: 8px 14px; border-top: 1px solid var(--border-soft);
|
| 639 |
+
font-size: 10.5px; color: var(--muted-3); font-family: var(--mono); flex-wrap: wrap;
|
| 640 |
+
}
|
| 641 |
+
.pal-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; vertical-align: middle; margin: 0 2px 0 6px; }
|
| 642 |
+
.pal-dot.pal-t { background: var(--accent); }
|
| 643 |
+
.pal-dot.pal-s { background: var(--muted-4); }
|
| 644 |
|
| 645 |
/* ── Collapsible taxonomy tree (topics sidebar) ────────── */
|
| 646 |
.nav-cat { margin-bottom: 10px; }
|
|
|
|
| 814 |
|
| 815 |
/* ── Print (browser "Save as PDF") ─────────────────────── */
|
| 816 |
@media print {
|
| 817 |
+
.topbar, .sidebar, .aside, .nav-scrim, .book-toolbar, .palette-scrim, #hovercard, #toast, .foot, .menu-toggle { display: none !important; }
|
| 818 |
html, body { background: #fff; color: #000; }
|
| 819 |
.layout, .main, body.book-mode .main { display: block; padding: 0; margin: 0; }
|
| 820 |
.book { max-width: none; margin: 0; padding: 0; color: #111; font-size: 10.5pt; }
|
|
|
|
| 833 |
.cite .cite-mark { display: none; }
|
| 834 |
a.secref { color: #111; border: none; }
|
| 835 |
.prose abbr.ac { border: none; }
|
| 836 |
+
/* single-article print: show collapsed disclosures + drop their chrome */
|
| 837 |
+
.disclosure { border: none; }
|
| 838 |
+
.disclosure > summary { padding-left: 0; }
|
| 839 |
+
.disclosure > summary::after { display: none; }
|
| 840 |
+
.disclosure > *:not(summary) { display: block !important; }
|
| 841 |
+
.page-links { display: none; }
|
| 842 |
+
.prose .fig { break-inside: avoid; }
|
| 843 |
}
|