/* ============================================================ ChronoSeal Website — search.js Client-side full-text search across all pages ============================================================ */ var ChronoSearch = (function () { 'use strict'; /* ---- Search index ---- */ var pages = [ { title: 'Home', url: 'index.html', desc: 'ChronoSeal overview, features, and quick start', keywords: 'home overview features quick start install' }, { title: 'Architecture', url: 'architecture.html', desc: 'System architecture, crate layout, and data flow', keywords: 'architecture crates workspace wasm shared server' }, { title: 'Protocol', url: 'protocol.html', desc: 'Heartbeat protocol, hash chain, and mutation engine', keywords: 'protocol heartbeat hash chain blake3 ed25519 mutation gene' }, { title: 'API Reference', url: 'api.html', desc: 'HTTP endpoints: /init, /hb, /health, /metrics, /stats', keywords: 'api http post init heartbeat health metrics stats json' }, { title: 'Deployment', url: 'deployment.html', desc: 'Installation, systemd, Docker, nginx reverse proxy', keywords: 'deploy install systemd docker nginx proxy production' }, { title: 'Threat Model', url: 'threat-model.html', desc: 'Security threat model and defense boundaries', keywords: 'threat model security attack replay automation defense' }, { title: 'Performance', url: 'performance.html', desc: 'Performance tuning, benchmarks, and optimization', keywords: 'performance tuning benchmark latency throughput' }, { title: 'Philosophy', url: 'philosophy.html', desc: 'Design philosophy and engineering priorities', keywords: 'philosophy design unix privacy operator control' }, { title: 'Privacy Policy', url: 'privacy.html', desc: 'Data handling, storage, and privacy commitments', keywords: 'privacy policy data collection storage session' }, { title: 'Security', url: 'security.html', desc: 'Security assumptions, hardening, and response headers', keywords: 'security assumptions headers hardening csp' }, { title: 'Operations', url: 'operations.html', desc: 'Monitoring, health checks, metrics, and logging', keywords: 'operations health status metrics stats logging monitor' }, { title: 'Testing', url: 'testing.html', desc: 'Test suite, fuzzing, and validation coverage', keywords: 'testing tests cargo fuzz validation fingerprint' }, { title: 'Comparison', url: 'comparison.html', desc: 'ChronoSeal vs commercial anti-bot solutions', keywords: 'comparison cloudflare akamai recaptcha datadome kasada' }, ]; var overlay = null; var input = null; var results = null; function init() { overlay = document.querySelector('.search-overlay'); input = document.querySelector('.search-input'); results = document.querySelector('.search-results'); if (!overlay || !input || !results) return; input.addEventListener('input', debounce(onInput, 150)); overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); }); // Keyboard nav inside results input.addEventListener('keydown', function (e) { if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); navigateResults(e.key === 'ArrowDown' ? 1 : -1); } if (e.key === 'Enter') { var sel = results.querySelector('.search-result.selected'); if (sel) { window.location.href = sel.getAttribute('href'); close(); } } }); } function open() { if (!overlay) init(); if (!overlay) return; overlay.classList.add('open'); input.value = ''; results.innerHTML = renderHints(); setTimeout(function () { input.focus(); }, 100); } function close() { if (overlay) overlay.classList.remove('open'); } function onInput() { var q = input.value.trim().toLowerCase(); if (!q) { results.innerHTML = renderHints(); return; } var matches = []; for (var i = 0; i < pages.length; i++) { var p = pages[i]; var hay = (p.title + ' ' + p.desc + ' ' + p.keywords).toLowerCase(); if (hay.indexOf(q) !== -1) { matches.push(p); } } if (matches.length === 0) { results.innerHTML = '
No results for “' + escHtml(q) + '”
'; return; } var html = ''; for (var j = 0; j < matches.length; j++) { var m = matches[j]; html += ''; html += '
' + highlightMatch(m.title, q) + '
'; html += '
' + highlightMatch(m.desc, q) + '
'; html += '
'; } results.innerHTML = html; } function navigateResults(dir) { var items = results.querySelectorAll('.search-result'); if (!items.length) return; var idx = -1; for (var i = 0; i < items.length; i++) { if (items[i].classList.contains('selected')) { idx = i; break; } } if (idx >= 0) items[idx].classList.remove('selected'); idx = Math.max(0, Math.min(items.length - 1, idx + dir)); items[idx].classList.add('selected'); items[idx].scrollIntoView({ block: 'nearest' }); } function renderHints() { return '
Type to search documentation…
↑↓ Navigate · ↵ Open · Esc Close
'; } function highlightMatch(text, q) { var idx = text.toLowerCase().indexOf(q); if (idx === -1) return escHtml(text); return escHtml(text.substring(0, idx)) + '' + escHtml(text.substring(idx, idx + q.length)) + '' + escHtml(text.substring(idx + q.length)); } function escHtml(s) { var div = document.createElement('div'); div.appendChild(document.createTextNode(s)); return div.innerHTML; } function debounce(fn, ms) { var t; return function () { clearTimeout(t); t = setTimeout(fn, ms); }; } /* ---- Auto-init ---- */ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } return { open: open, close: close }; })();