No AI portrait yet
No comments yet. Be the first to comment!
refs_json., breaking mermaid.
contentEl.querySelectorAll('pre > code.language-mermaid').forEach(code => {
const pre = code.parentElement;
const div = document.createElement('div');
div.className = 'mermaid';
div.textContent = code.textContent;
pre.replaceWith(div);
});
// Dark mode fix: strip light-mode inline backgrounds from infoboxes/divs
contentEl.querySelectorAll('.infobox, [class*="infobox"], [class*="quick-ref"]').forEach(el => {
el.style.background = '#151525';
el.style.borderColor = 'rgba(79,195,247,0.2)';
el.style.color = '#e0e0e0';
el.style.borderRadius = '8px';
// Fix child elements too
el.querySelectorAll('*').forEach(child => {
if (child.style.color && (child.style.color.includes('#000') || child.style.color.includes('#333') || child.style.color.includes('rgb(0')))
child.style.color = '#e0e0e0';
if (child.style.background && (child.style.background.includes('#f') || child.style.background.includes('#e') || child.style.background.includes('#d') || child.style.background.includes('white')))
child.style.background = 'transparent';
if (child.style.borderColor && (child.style.borderColor.includes('#d') || child.style.borderColor.includes('#c')))
child.style.borderColor = 'rgba(255,255,255,0.08)';
});
});
// Also catch any div with light inline background that isn't classed
contentEl.querySelectorAll('div[style]').forEach(el => {
const bg = el.style.background || el.style.backgroundColor || '';
if (bg.match(/#f[0-9a-f]|#e[0-9a-f]|#d[0-9a-f]|white|#fff/i)) {
el.style.background = '#151525';
el.style.borderColor = 'rgba(79,195,247,0.2)';
el.style.color = '#e0e0e0';
el.style.borderRadius = '8px';
}
});
// Style blockquotes with classes
contentEl.querySelectorAll('blockquote').forEach(bq => {
const t = bq.textContent.toLowerCase();
if (t.startsWith('warning') || t.startsWith('caution')) bq.classList.add('is-warning');
else if (t.startsWith('danger') || t.startsWith('error')) bq.classList.add('is-danger');
else if (t.startsWith('success') || t.startsWith('tip')) bq.classList.add('is-success');
});
// Syntax highlighting
Prism.highlightAllUnder(contentEl);
// Mermaid diagrams — initialize then render all blocks
mermaid.initialize({ theme: 'base', startOnLoad: false, securityLevel: 'loose', flowchart: { htmlLabels: true, useMaxWidth: false, wrappingWidth: 200, padding: 15 }, themeVariables: { primaryColor: '#1e3a4a', primaryTextColor: '#e0e0e0', primaryBorderColor: '#4fc3f7', lineColor: '#4fc3f7', secondaryColor: '#1a3a2a', tertiaryColor: '#2a1a3a', secondaryTextColor: '#e0e0e0', tertiaryTextColor: '#e0e0e0', clusterBorder: '#4fc3f7', edgeLabelBackground: '#151525', clusterBkg: '#12122a', nodeBorder: '#4fc3f7', mainBkg: '#1e3a4a', nodeTextColor: '#e0e0e0', background: '#151525', fontSize: '14px', fontFamily: 'system-ui' } });
// Dark-mode color map: rewrite light/bright fills to dark equivalents
const _dm = {
// Light blues
'#e1f5fe':'#0d2137','#e3f2fd':'#0d2137','#bbdefb':'#0d2137',
'#4fc3f7':'#0d3b54','#03a9f4':'#01579b','#b3e5fc':'#0d2137',
// Light oranges / yellows
'#fff3e0':'#3e2200','#ffe0b2':'#3e2200','#ffcc80':'#3e2200',
'#ffd54f':'#3a3000','#ffca28':'#3a3000','#ffc107':'#3a3000',
'#fff9c4':'#3a3000','#ffecb3':'#3a3000','#fff8e1':'#3a3000',
'#ff9800':'#4e2d00','#ffb74d':'#3e2200','#ff8a65':'#4a1500',
// Light reds / pinks
'#ffcdd2':'#3b1114','#fce4ec':'#3b1114','#ffebee':'#3b1114',
'#ef5350':'#7f1d1d','#f44336':'#7f1d1d','#e53935':'#7f1d1d',
'#ef9a9a':'#5c1515','#ff6b6b':'#7f1d1d','#ff6666':'#7f1d1d',
'#f99':'#5c1515','#ffb6c1':'#5c1525',
// Light greens
'#c8e6c9':'#0e2e10','#e8f5e9':'#0e2e10','#a5d6a7':'#0e2e10',
'#81c784':'#1b4d1e','#4caf50':'#1b5e20','#66bb6a':'#1b5e20',
'#8bc34a':'#33691e','#9f9':'#0e2e10',
// Light purples
'#f3e5f5':'#2a0e3a','#e8eaf6':'#2a0e3a',
'#ce93d8':'#4a1a6b','#b39ddb':'#311b6b',
'#99f':'#2a0e3a','#bbf':'#2a1a5a',
// Greys / browns
'#efebe9':'#2a2320','#d7ccc8':'#2a2320',
'#e0e0e0':'#252535','#f5f5f5':'#252535','#b0bec5':'#263238',
// Already-dark fills: keep as-is (no entry needed)
};
function _darkify(txt) {
// Replace light/bright fill colors with dark equivalents, fix text color
return txt.replace(/fill:#[0-9a-fA-F]{3,6}/gi, m => {
const c = m.slice(5).toLowerCase();
const dk = _dm['#'+c] || _dm[c];
return dk ? 'fill:'+dk : m;
}).replace(/,color:#000/g,',color:#e0e0e0')
.replace(/,color:#333/g,',color:#ddd')
.replace(/color:#000/g,'color:#e0e0e0')
.replace(/color:#333/g,'color:#ddd');
}
const mermaidEls = contentEl.querySelectorAll('.mermaid');
if (mermaidEls.length > 0) {
// Render each mermaid block individually so one failure doesn't break all
for (const el of mermaidEls) {
try {
const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
const { svg } = await mermaid.render(id, _darkify(el.textContent.trim()));
el.innerHTML = svg;
if (window.SciDexMermaid) window.SciDexMermaid.enhanceElement(el);
// Fix foreignObject clipping: measure actual content and resize
el.querySelectorAll('foreignObject').forEach(fo => {
const div = fo.querySelector('div');
if (!div) return;
const actual = div.scrollHeight || div.offsetHeight;
const foH = parseFloat(fo.getAttribute('height') || 0);
if (actual > foH + 2) {
const diff = actual - foH + 8;
fo.setAttribute('height', actual + 8);
// Also grow the parent node rect/shape
const node = fo.closest('.node,.label');
if (node) {
const rect = node.querySelector('rect,polygon,circle,ellipse');
if (rect && rect.getAttribute('height')) {
rect.setAttribute('height', parseFloat(rect.getAttribute('height')) + diff);
}
}
}
});
} catch(e) {
el.innerHTML = 'Diagram error: ' + e.message + '
';
}
}
}
// Build TOC from rendered headings.
// markdown-it-anchor adds slug-based IDs at render time using _slugify.
// The TOC just reads h.id. For content with manually-authored anchor
// links (e.g. [Parkinson's](#old-slug)), we also build a map and
// create secondary anchors so both old and new slugs work.
const _anchorMap = {};
contentEl.querySelectorAll('a[href^="#"]').forEach(a => {
const frag = a.getAttribute('href').slice(1);
const text = a.textContent.trim().toLowerCase();
if (frag && text) _anchorMap[text] = frag;
});
const tocEl = document.getElementById('toc');
const headings = contentEl.querySelectorAll('h1,h2,h3,h4');
headings.forEach((h, i) => {
// markdown-it-anchor should have set h.id already; fall back to _slugify
if (!h.id) h.id = _slugify(h.textContent) || ('heading-' + i);
const id = h.id;
// If content has a different anchor slug for this heading, create a
// hidden anchor so BOTH the canonical slug AND the old slug work.
const hText = h.textContent.trim().toLowerCase();
const oldSlug = _anchorMap[hText];
if (oldSlug && oldSlug !== id) {
const span = document.createElement('span');
span.id = oldSlug;
h.parentNode.insertBefore(span, h);
}
const a = document.createElement('a');
a.href = '#' + id;
a.textContent = h.textContent;
if (h.tagName === 'H3' || h.tagName === 'H4') a.classList.add('toc-h3');
a.onclick = function(e) {
e.preventDefault();
h.scrollIntoView({ behavior: 'smooth', block: 'start' });
history.replaceState(null, '', '#' + id);
};
tocEl.appendChild(a);
});
// On page load, scroll to the anchor if present in URL
if (window.location.hash) {
const target = document.getElementById(window.location.hash.slice(1));
if (target) setTimeout(() => target.scrollIntoView({ block: 'start' }), 100);
}
// Highlight current TOC item on scroll
const tocLinks = tocEl.querySelectorAll('a');
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
tocLinks.forEach(l => l.classList.remove('active'));
const active = tocEl.querySelector('a[href="#' + entry.target.id + '"]');
if (active) active.classList.add('active');
}
});
}, { rootMargin: '-80px 0px -80% 0px' });
headings.forEach(h => observer.observe(h));
})();