(() => {
const input = document.getElementById("homeToolSearch");
const results = document.getElementById("homeSearchResults");
const dataEl = document.getElementById("searchable-tools-data");
if (!input || !results || !dataEl) return;
let tools = [];
try {
tools = JSON.parse(dataEl.textContent || "[]");
} catch {
tools = [];
}
const MAX_RESULTS = 8;
function scoreTool(tool, term) {
const name = (tool.name || "").toLowerCase();
const slug = (tool.slug || "").toLowerCase();
const description = (tool.description || "").toLowerCase();
if (name === term) return 100;
if (slug === term) return 95;
if (name.startsWith(term)) return 80;
if (slug.startsWith(term)) return 70;
if (name.includes(term)) return 55;
if (description.includes(term)) return 35;
return 0;
}
function render(items, term) {
if (!term) {
results.innerHTML = "";
results.classList.remove("is-visible");
return;
}
if (!items.length) {
results.innerHTML = '
No tools found.
';
results.classList.add("is-visible");
return;
}
const escapeHtml = (value) =>
String(value)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/\"/g, """)
.replace(/'/g, "'");
const rows = items.map((tool) => {
const safeName = escapeHtml(tool.name || "");
const safeSlug = escapeHtml(tool.slug || "");
return (
`` +
`${safeName}` +
``
);
});
results.innerHTML = rows.join("");
results.classList.add("is-visible");
}
function runSearch() {
const term = input.value.trim().toLowerCase();
if (!term) {
render([], "");
return [];
}
const filtered = tools
.map((tool) => ({ tool, score: scoreTool(tool, term) }))
.filter((entry) => entry.score > 0)
.sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name))
.slice(0, MAX_RESULTS)
.map((entry) => entry.tool);
render(filtered, term);
return filtered;
}
input.addEventListener("input", runSearch);
input.addEventListener("keydown", (event) => {
if (event.key !== "Enter") return;
event.preventDefault();
const items = runSearch();
if (items.length) {
window.location.href = `/${items[0].slug}.html`;
}
});
document.addEventListener("click", (event) => {
if (event.target === input || results.contains(event.target)) return;
results.classList.remove("is-visible");
});
})();