`;
shadow.appendChild(panel);
// ββ THEME SAMPLING ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function sampleSiteTheme() {
const contentRoot = document.querySelector(CONTENT_SELECTOR) || document.body;
const docStyle = getComputedStyle(document.documentElement);
function cssVar(...names) {
for (const name of names) {
const val = docStyle.getPropertyValue(name).trim();
if (val) return val;
}
return null;
}
function usable(c) {
return c && c !== 'rgba(0, 0, 0, 0)' && c !== 'transparent';
}
const vpBg = cssVar('--vp-c-bg', '--vp-c-bg-soft', '--c-bg');
const vpText = cssVar('--vp-c-text-1', '--vp-c-text', '--c-text');
const vpLink = cssVar('--vp-c-brand-1', '--vp-c-brand', '--c-brand');
const vpFont = cssVar('--vp-font-family-base');
const dsBg = cssVar('--ifm-background-color', '--ifm-background-surface-color');
const dsText = cssVar('--ifm-font-color-base');
const dsLink = cssVar('--ifm-link-color', '--ifm-color-primary');
const dsFont = cssVar('--ifm-font-size-base');
const gbBg = cssVar('--color-base', '--background');
const gbText = cssVar('--color-text-default', '--text-default');
const gbLink = cssVar('--color-link', '--link');
const mkBg = cssVar('--md-default-bg-color');
const mkText = cssVar('--md-default-fg-color');
const mkLink = cssVar('--md-accent-fg-color', '--md-primary-fg-color');
function getBgFromEl(el) {
let node = el;
while (node && node !== document.documentElement) {
const bg = getComputedStyle(node).backgroundColor;
if (usable(bg)) return bg;
node = node.parentElement;
}
return getComputedStyle(document.documentElement).backgroundColor;
}
function getLinkColorNear(el) {
for (const a of (el ? el.querySelectorAll('a') : [])) {
const c = getComputedStyle(a).color;
if (usable(c)) return c;
}
let parent = el ? el.parentElement : null;
while (parent && parent !== document.body) {
for (const a of parent.querySelectorAll('a')) {
if (a.closest('#pagegrep-host, nav, header, aside, [role="navigation"]')) continue;
const c = getComputedStyle(a).color;
if (usable(c)) return c;
}
parent = parent.parentElement;
}
return null;
}
const textEl = contentRoot.querySelector('li, p');
const elemBg = getBgFromEl(contentRoot);
const elemText = textEl ? getComputedStyle(textEl).color : null;
const elemLink = getLinkColorNear(textEl || contentRoot);
const elemFont = textEl ? getComputedStyle(textEl).fontSize : null;
const finalBg = vpBg || dsBg || gbBg || mkBg || (usable(elemBg) ? elemBg : null);
const finalText = vpText || dsText || gbText || mkText || (usable(elemText) ? elemText : null);
const finalLink = vpLink || dsLink || gbLink || mkLink || (usable(elemLink) ? elemLink : null);
const finalFont = vpFont || dsFont || elemFont;
// Apply to the shadow root's :host via the panel (vars cascade inside shadow)
const root = shadow.querySelector('#wgs-panel');
setTimeout(() => {
if (finalBg) { root.style.setProperty('--wgs-site-bg', finalBg); overlay.querySelector('#wgs-spotlight').style.setProperty('--wgs-site-bg', finalBg); }
if (finalText) { root.style.setProperty('--wgs-site-text', finalText); overlay.querySelector('#wgs-input').style.color = finalText; }
if (finalLink) root.style.setProperty('--wgs-site-link', finalLink);
if (finalFont) root.style.setProperty('--wgs-site-fontsize', finalFont);
}, 0);
}
sampleSiteTheme();
setTimeout(sampleSiteTheme, 800);
new MutationObserver(sampleSiteTheme).observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme', 'data-dark-mode'] });
const input = shadow.getElementById('wgs-input');
const closeBtn = shadow.getElementById('wgs-close');
const copyBtn = shadow.getElementById('wgs-copy');
const results = shadow.getElementById('wgs-results');
const titleEl = shadow.getElementById('wgs-panel-title');
// ββ INDEX βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Each entry:
// text β full visible text of the line element (whole
,
, etc.)
// searchText β lowercased: all ancestor heading texts + line text (for matching)
// headings β [{level, text, id, node}, β¦] outermostβinnermost
let index = null;
const LINE_TAGS = new Set(['LI','P','DT','DD','TD','TH','BLOCKQUOTE','FIGCAPTION']);
const HEADING_TAGS = new Set(['H1','H2','H3','H4','H5','H6']);
function headingLevel(tag) { return parseInt(tag[1], 10); }
function buildIndex() {
if (index) return;
index = [];
const root = document.querySelector(CONTENT_SELECTOR) || document.body;
// We walk the DOM in tree order using querySelectorAll (DOM order guaranteed).
// We maintain a heading stack as we encounter headings.
// For LINE_TAGS we emit an index entry β but skip ones that only contain
// other LINE_TAG descendants (avoid double-indexing outer
of nested lists).
const headingStack = []; // [{level, text, id, node}]
const allEl = root.querySelectorAll('*');
for (const el of allEl) {
const tag = el.tagName;
// ββ Update heading stack ββ
if (HEADING_TAGS.has(tag)) {
const lv = headingLevel(tag);
while (headingStack.length && headingStack[headingStack.length - 1].level >= lv) {
headingStack.pop();
}
// Strip leading decorator symbols common on wiki sites (β· βΊ β β’ etc.)
const rawText = el.textContent.trim();
const cleanText = rawText.replace(/^[\s\u00a0\u2000-\u27bf|>\/-]+/g, '').trim() || rawText;
headingStack.push({ level: lv, text: cleanText, id: el.id || null, node: el });
continue;
}
// ββ Emit line entry ββ
if (!LINE_TAGS.has(tag)) continue;
// Skip if this element's meaningful text is entirely inside nested line elements
// (e.g. a bare
wrapping
with no own text)
let ownText = '';
for (const child of el.childNodes) {
if (child.nodeType === Node.TEXT_NODE) ownText += child.textContent;
}
// If the element contains a nested LINE_TAG and has no meaningful direct text, skip
if (el.querySelector('li,p,dt,dd,td,th,blockquote,figcaption') && ownText.trim().length < 2) {
continue;
}
const text = el.textContent.trim().replace(/\s+/g, ' ');
if (text.length < 2) continue;
// snapshot: one heading per level max, sorted ascending
const seenLevels = new Map();
for (const h of headingStack) seenLevels.set(h.level, h);
const headings = [...seenLevels.values()].sort((a, b) => a.level - b.level).map(h => ({ ...h }));
const headingText = headings.map(h => h.text).join(' ');
// Also include href values so queries like "codeberg bypass" can match
// link URLs even when the URL text isn't part of the visible line text
const hrefText = Array.from(el.querySelectorAll('a[href]'))
.map(a => a.getAttribute('href'))
.join(' ');
const searchText = (headingText + ' ' + text + ' ' + hrefText).toLowerCase();
index.push({ text, searchText, headings, el });
}
}
// ββ SCORING βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 3 β exact phrase in line text
// 2 β exact phrase in headings text
// Scoring rationale:
// Primary: how many query words match as full words (0..N), normalised to 0..4
// so even one full-word match beats all-substring matches.
// Secondary: phrase/order/unordered tier (0..3), same as before.
// Final: primary * 4 + secondary β range 0..19 (no overlap between bands)
//
// Example with query "ha ai":
// "AI Studio" β ai=full, ha=partial β 1 full-word β primary=2 β beats
// "Chat - Decentralized" β ai=partial, ha=partial β 0 full-words β primary=0
function countFullWords(searchL, words) {
let count = 0;
for (const w of words) {
const escaped = w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp('(?/g,'>');
if (!words.length) return safe;
const pat = new RegExp(`(${words.map(escapeRe).join('|')})`, 'gi');
return safe.replace(pat, '$1');
}
// ββ SEARCH ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function doSearch() {
const query = input.value.trim();
if (!query) return;
buildIndex();
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
const phrase = words.join(' ');
let matches = [];
for (const entry of index) {
if (words.every(w => entry.searchText.includes(w))) {
matches.push({ entry, sc: score(entry, words, phrase) });
}
}
matches.sort((a, b) => b.sc - a.sc);
const total = matches.length;
matches = matches.slice(0, MAX_RESULTS);
titleEl.textContent = total === 0
? `No results β "${query}"`
: total > matches.length
? `top ${matches.length} of ${total} β "${query}"`
: `${total} result${total > 1 ? 's' : ''} β "${query}"`;
results.innerHTML = '';
if (!matches.length) {
results.innerHTML = '