' +
'
';
if (mount) {
var headerNode = parse(header);
mount.replaceWith(headerNode);
if (adminMode) headerNode.insertAdjacentHTML('afterend', adminBanner);
}
function mountFooter() {
var m = document.getElementById('cz-footer');
if (m) m.replaceWith(parse(footer));
}
// Most pages load this script above their footer mount, which doesn't exist yet.
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mountFooter);
else mountFooter();
// Search suggestions, mirroring fragments/search_suggestions.templ: repos, users and orgs whose name contains the
// typed text (two characters minimum), then a "Search for" row. The real fragment has no issue or code rows.
var SUGGEST = {
repo: [
['mkappworks/cloudzilla', 'Self-hosted git forge', 'repo.html', false],
['mkappworks/benchlabs-site', 'Marketing site for Bench Labs', 'repo.html', false],
['mkappworks/go-service-starter', 'Opinionated Go service template', 'repo.html', true],
['acme-corp/oauth-proxy', 'Reverse proxy that fronts apps with OAuth 2.0', 'repo.html', false],
['stitch-labs/htmx-kit', 'Tiny HTMX component toolkit', 'repo.html', false],
['openforge/runner', 'Sandboxed CI runner', 'repo.html', false],
],
user: [['malith', '', 'profile.html'], ['daisy', '', 'profile.html'], ['kiyo', '', 'profile.html'],
['rhea', '', 'profile.html'], ['oauth-bot', '', 'profile.html']],
org: [['acme-corp', 'Acme Corp', 'org_profile.html'], ['stitch-labs', 'Stitch Labs', 'org_profile.html'],
['openforge', 'Open Forge', 'org_profile.html']],
};
var SUGGEST_LABEL = { repo: 'Repositories', user: 'Users', org: 'Organizations' };
var BOOK = '';
var SUGGEST_ROW = 'flex items-center gap-2 px-3 py-1.5 text-[13px] hover:bg-accent aria-selected:bg-accent';
function escHtml(t) {
var d = document.createElement('div');
d.textContent = t;
return d.innerHTML;
}
// components.initials: first letters of the first and last words, so one letter for a username.
function initialsOf(n) {
var w = n.trim().split(/\s+/);
return w[0] ? (w.length > 1 ? w[0][0] + w[w.length - 1][0] : w[0][0]).toUpperCase() : '?';
}
// Pages with their own search box (search.html) attach the same behaviour: czAttachSuggest(input, panel).
function attachSuggest(input, panel) {
var active = -1;
function isOpen() { return !panel.classList.contains('hidden'); }
function setOpen(open) {
panel.classList.toggle('hidden', !open);
input.setAttribute('aria-expanded', open ? 'true' : 'false');
if (!open) { active = -1; input.removeAttribute('aria-activedescendant'); }
}
function options() { return panel.querySelectorAll('[data-suggest-item]'); }
function highlight(i) {
active = i;
options().forEach(function (a, n) { a.setAttribute('aria-selected', n === i ? 'true' : 'false'); });
if (i >= 0) input.setAttribute('aria-activedescendant', panel.id + '-' + i);
else input.removeAttribute('aria-activedescendant');
}
function render() {
var q = input.value.trim();
if (q.length < 2) { panel.innerHTML = ''; setOpen(false); return; }
var needle = q.toLowerCase();
var rows = [];
['repo', 'user', 'org'].forEach(function (kind) {
SUGGEST[kind].forEach(function (r) {
if (r[0].toLowerCase().indexOf(needle) < 0) return;
rows.push({ kind: kind, title: r[0], detail: r[1], href: r[2], priv: kind === 'repo' && r[3] });
});
});
var html = '';
rows.forEach(function (r, i) {
if (i === 0 || rows[i - 1].kind !== r.kind) {
html += '
';
panel.innerHTML = html;
active = -1;
setOpen(true);
document.dispatchEvent(new CustomEvent('cz-suggest-opened', { detail: panel }));
}
function move(delta) {
var n = options().length;
if (!n) return;
setOpen(true);
highlight(active < 0 ? (delta > 0 ? 0 : n - 1) : (active + delta + n) % n);
options()[active].scrollIntoView({ block: 'nearest' });
}
input.addEventListener('input', render);
input.addEventListener('focus', function () { if (options().length) setOpen(true); });
input.addEventListener('keydown', function (e) {
if (e.key === 'ArrowDown') { e.preventDefault(); move(1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1); }
else if (e.key === 'Enter') {
// Nothing highlighted: Enter submits the form.
if (isOpen() && active >= 0) { e.preventDefault(); options()[active].click(); }
} else if (e.key === 'Escape' && isOpen()) { e.preventDefault(); setOpen(false); }
});
panel.addEventListener('mouseover', function (e) {
var a = e.target.closest && e.target.closest('[data-suggest-item]');
if (a) highlight(Array.prototype.indexOf.call(options(), a));
});
document.addEventListener('click', function (e) {
if (isOpen() && !input.contains(e.target) && !panel.contains(e.target)) setOpen(false);
});
// The topnav and the results page each have a box; only one list shows at a time.
document.addEventListener('cz-suggest-opened', function (e) { if (e.detail !== panel) setOpen(false); });
return { close: function () { panel.innerHTML = ''; setOpen(false); } };
}
window.czAttachSuggest = attachSuggest;
var searchForm = document.querySelector('form[data-cz-search]');
if (searchForm) {
var searchInput = searchForm.querySelector('input[name=q]');
var clearBtn = searchForm.querySelector('[data-cz-clear]');
var kbd = searchForm.querySelector('[data-cz-kbd]');
var topSuggest = attachSuggest(searchInput, document.getElementById('topnav-suggest'));
var syncClear = function () {
var has = searchInput.value !== '';
clearBtn.classList.toggle('hidden', !has);
kbd.classList.toggle('hidden', has);
};
searchInput.addEventListener('input', syncClear);
clearBtn.addEventListener('click', function () {
searchInput.value = '';
topSuggest.close();
syncClear();
searchInput.focus();
});
// The generic submit handler below would only toast; this form really navigates.
searchForm.addEventListener('submit', function (e) {
e.preventDefault();
var q = searchInput.value.trim();
location.href = 'search.html' + (q ? '?q=' + encodeURIComponent(q) : '');
});
document.addEventListener('keydown', function (e) {
if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return;
var t = e.target;
if (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)) return;
if (searchInput.offsetParent === null) return;
e.preventDefault();
searchInput.focus();
searchInput.select();
});
}
// Theme: same storage key and class polarity as the layout's bootstrap (dark by default, `light` opts out).
var root = document.documentElement;
// highlight.DefaultLight and DefaultDark: code-themes.css keys its .hl colours off these attributes.
if (!root.dataset.codeLight) root.dataset.codeLight = 'github';
if (!root.dataset.codeDark) root.dataset.codeDark = 'github-dark';
var toggle = document.getElementById('theme-toggle');
try {
var saved = localStorage.getItem('cz-theme');
if (saved === 'light' || (!saved && window.matchMedia('(prefers-color-scheme: light)').matches)) {
root.classList.remove('dark');
root.classList.add('light');
}
} catch (e) { /* storage blocked */ }
function syncToggle() {
if (!toggle) return;
var light = root.classList.contains('light');
toggle.setAttribute('aria-pressed', light ? 'true' : 'false');
toggle.setAttribute('aria-label', light ? 'Switch to dark theme' : 'Switch to light theme');
toggle.querySelector('.dark-only').classList.toggle('hidden', light);
toggle.querySelector('.light-only').classList.toggle('hidden', !light);
}
document.addEventListener('keydown', function (e) {
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'l' && toggle) {
e.preventDefault();
toggle.click();
}
});
if (toggle) {
toggle.addEventListener('click', function () {
var light = !root.classList.contains('light');
root.classList.toggle('light', light);
root.classList.toggle('dark', !light);
try { localStorage.setItem('cz-theme', light ? 'light' : 'dark'); } catch (e) { /* storage blocked */ }
syncToggle();
});
syncToggle();
}
// Dropdowns: `' + SUGGEST_LABEL[r.kind] + '
';
}
html += '' +
(r.kind === 'repo' ? BOOK : '') +
'' + escHtml(r.title) + '' +
(r.priv ? 'Private' : '') +
(r.detail ? '' + escHtml(r.detail) + '' : '') + '';
});
html += '' +
'' +
'Search for “' + escHtml(q) + '”`, one open at a time; click
// outside or Escape closes. Pages with their own menu script mark the trigger `data-dropdown-btn`; those are left alone.
function managed(d) {
return !d.querySelector('[data-dropdown-btn]') && d.querySelector(':scope > [role=menu]');
}
function closeAll(except) {
document.querySelectorAll('[data-dropdown], [data-shell-menu]').forEach(function (d) {
var panel = managed(d);
if (!panel || d === except || panel.classList.contains('hidden')) return;
panel.classList.add('hidden');
d.querySelector(':scope > button').setAttribute('aria-expanded', 'false');
});
}
document.addEventListener('click', function (e) {
var wrap = e.target.closest && e.target.closest('[data-dropdown], [data-shell-menu]');
if (wrap && !managed(wrap)) wrap = null;
var onTrigger = wrap && e.target.closest('[data-dropdown] > button, [data-shell-menu] > button');
closeAll(onTrigger ? wrap : null);
if (onTrigger) {
var open = managed(wrap).classList.toggle('hidden') === false;
onTrigger.setAttribute('aria-expanded', open ? 'true' : 'false');
}
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeAll(null); });
// Anything left unwired says so instead of failing silently: a `#` link or a button that changed nothing on the
// page gets a toast. Editor toolbars and copy buttons act on text or the clipboard, which isn't a DOM change.
function toast(text) {
var t = document.createElement('div');
t.setAttribute('role', 'status');
t.className = 'fixed bottom-4 right-4 z-[100] max-w-sm rounded-md border border-border bg-popover px-3 py-2 ' +
'text-sm text-popover-foreground shadow-md';
t.textContent = text;
document.body.appendChild(t);
setTimeout(function () { t.remove(); }, 2600);
}
window.czToast = toast;
// The click listener below runs in the capture phase: a target's own handler would otherwise change the page, and
// the observer would count it, before the baseline is taken.
var changes = 0;
new MutationObserver(function (l) { changes += l.length; }).observe(document.body, {
subtree: true, attributes: true, childList: true, characterData: true,
});
document.addEventListener('click', function (e) {
var el = e.target.closest && e.target.closest('a[href="#"], button, [role=button]');
if (!el || el.disabled || el.closest('[data-md-editor], [data-md-action]') || el.hasAttribute('data-md-action')) return;
if (el.matches('button[type=submit], [type=reset]') || el.closest('form[method=dialog]')) return;
if (/^(copy|bold|italic|code|insert link)/i.test((el.getAttribute('aria-label') || el.textContent).trim())) return;
var seen = changes;
var label = (el.getAttribute('aria-label') || el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 40);
setTimeout(function () {
if (changes === seen) toast((label ? '“' + label + '”' : 'This control') + ' isn’t wired up in the mock.');
}, 60);
}, true);
document.addEventListener('submit', function (e) {
if (e.defaultPrevented) return;
e.preventDefault();
toast(e.target.dataset.toast || 'Submitted. The mock stays on this page.');
});
})();