diff --git a/styles.css b/styles.css
index 55cc383..92176bc 100644
--- a/styles.css
+++ b/styles.css
@@ -379,6 +379,38 @@ footer p {
}
}
+.tools-search {
+ margin: 0 0 32px;
+}
+.tools-search label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 700;
+}
+.tools-search-controls {
+ display: flex;
+ gap: 8px;
+}
+.tools-search input {
+ min-width: 0;
+ flex: 1;
+ padding: 10px 12px;
+ border: 1px solid #0005;
+ border-radius: 4px;
+ font: inherit;
+}
+.tools-search button {
+ padding: 10px 16px;
+ border: 1px solid #0005;
+ border-radius: 4px;
+ background: #eee;
+ font: inherit;
+ cursor: pointer;
+}
+.tools-results-count {
+ font-size: 14px;
+ font-weight: 400;
+}
.tools-toc {
margin: 32px 0;
padding: 16px 20px;
diff --git a/tools/index.html b/tools/index.html
index 59f7fe5..9bbb220 100644
--- a/tools/index.html
+++ b/tools/index.html
@@ -84,10 +84,19 @@
- Browse the tools and equipment available to Protospace members. This catalog follows the
- categories and ordering on the Protospace Wiki .
+ Browse the tools and equipment available to Protospace members. This catalog
+ comes from the Protospace Wiki .
-
+ Search
+
+
+
Jump to a category
@@ -104,50 +113,74 @@
.then((tools) => {
const list = document.querySelector("#tools-list");
const toc = document.querySelector("#tools-toc-list");
- const categories = new Map();
- tools.forEach((tool) => {
- if (!categories.has(tool.category)) categories.set(tool.category, []);
- categories.get(tool.category).push(tool);
- });
- list.replaceChildren();
- toc.replaceChildren();
- for (const [category, categoryTools] of categories) {
- const sectionId = `tools-${category.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
- const tocItem = document.createElement("li");
- const tocLink = document.createElement("a");
- tocLink.href = `#${sectionId}`;
- tocLink.textContent = category;
- tocItem.append(tocLink);
- toc.append(tocItem);
- const heading = document.createElement("h3");
- heading.id = sectionId;
- const headingText = document.createElement("span");
- headingText.textContent = category;
- const backLink = document.createElement("a");
- backLink.className = "category-back";
- backLink.href = "#top";
- backLink.textContent = "Back to Top";
- heading.append(headingText, backLink);
- list.append(heading);
- const grid = document.createElement("div");
- grid.className = "tools-grid";
- for (const tool of categoryTools) {
- const card = document.createElement("a");
- card.className = "tool-card external";
- card.href = tool.link;
- card.target = "_blank";
- card.rel = "noopener noreferrer";
- const image = document.createElement("img");
- image.src = tool["photo file"];
- image.alt = tool.name;
- image.loading = "lazy";
- const name = document.createElement("span");
- name.textContent = tool.name;
- card.append(image, name);
- grid.append(card);
+ const searchInput = document.querySelector("#tool-search");
+ const clearButton = document.querySelector("#tool-search-clear");
+ const resultsCount = document.querySelector("#tool-results-count");
+ const categoriesHeading = document.querySelector("#tools-categories-heading");
+ const tocNav = document.querySelector("#tools-toc");
+ const normalize = (value) => value.normalize("NFKD").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
+ const render = () => {
+ const query = normalize(searchInput.value);
+ const categories = new Map();
+ const matchingTools = tools
+ .filter((tool) => !query || normalize(tool.name).includes(query));
+ resultsCount.hidden = !query;
+ categoriesHeading.hidden = Boolean(query);
+ tocNav.hidden = Boolean(query);
+ resultsCount.textContent = ` - Results: ${matchingTools.length}`;
+ matchingTools
+ .forEach((tool) => {
+ if (!categories.has(tool.category)) categories.set(tool.category, []);
+ categories.get(tool.category).push(tool);
+ });
+ list.replaceChildren();
+ toc.replaceChildren();
+ clearButton.hidden = !query;
+ for (const [category, categoryTools] of categories) {
+ const sectionId = `tools-${normalize(category)}`;
+ const tocItem = document.createElement("li");
+ const tocLink = document.createElement("a");
+ tocLink.href = `#${sectionId}`;
+ tocLink.textContent = category;
+ tocItem.append(tocLink);
+ toc.append(tocItem);
+ const heading = document.createElement("h3");
+ heading.id = sectionId;
+ const headingText = document.createElement("span");
+ headingText.textContent = category;
+ const backLink = document.createElement("a");
+ backLink.className = "category-back";
+ backLink.href = "#top";
+ backLink.textContent = "Back to Top";
+ heading.append(headingText, backLink);
+ list.append(heading);
+ const grid = document.createElement("div");
+ grid.className = "tools-grid";
+ for (const tool of categoryTools) {
+ const card = document.createElement("a");
+ card.className = "tool-card external";
+ card.href = tool.link;
+ card.target = "_blank";
+ card.rel = "noopener noreferrer";
+ const image = document.createElement("img");
+ image.src = tool["photo file"];
+ image.alt = tool.name;
+ image.loading = "lazy";
+ const name = document.createElement("span");
+ name.textContent = tool.name;
+ card.append(image, name);
+ grid.append(card);
+ }
+ list.append(grid);
}
- list.append(grid);
- }
+ };
+ searchInput.addEventListener("input", render);
+ clearButton.addEventListener("click", () => {
+ searchInput.value = "";
+ searchInput.focus();
+ render();
+ });
+ render();
})
.catch((error) => {
document.querySelector("#tools-list").textContent = error.message;