204 lines
7.8 KiB
HTML
204 lines
7.8 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<link rel="icon" href="/assets/favicon.png" type="image/png" />
|
||
<meta name="description" content="Browse the current tools and equipment available to Protospace members." />
|
||
<link rel="canonical" href="https://protospace.ca/tools/" />
|
||
<meta property="og:title" content="Tools – Protospace" />
|
||
<meta property="og:description" content="Browse the current tools and equipment available to Protospace members." />
|
||
<meta property="og:type" content="website" />
|
||
<meta property="og:url" content="https://protospace.ca/tools/" />
|
||
<meta property="og:image" content="https://protospace.ca/assets/FrontProto-1024x455.jpg" />
|
||
<meta property="og:site_name" content="Protospace" />
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<title>Tools – Protospace</title>
|
||
<link rel="preload" href="/styles.css?v=1790385126" as="style" />
|
||
<link rel="stylesheet" href="/styles.css?v=1790385126" />
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": [
|
||
"LocalBusiness",
|
||
"NonprofitOrganization"
|
||
],
|
||
"@id": "https://protospace.ca/#organization",
|
||
"name": "Protospace",
|
||
"description": "Protospace is a volunteer-run, non-profit makerspace in Calgary with shared woodworking, metalworking, sewing, electronics, laser cutting, and 3D printing facilities.",
|
||
"url": "https://protospace.ca/",
|
||
"logo": "https://protospace.ca/assets/logo.png",
|
||
"image": "https://protospace.ca/assets/FrontProto-1024x455.jpg",
|
||
"telephone": "+1-587-774-5672",
|
||
"email": "info@protospace.ca",
|
||
"address": {
|
||
"@type": "PostalAddress",
|
||
"streetAddress": "1530 27th Avenue NE, Bay 108",
|
||
"addressLocality": "Calgary",
|
||
"addressRegion": "AB",
|
||
"postalCode": "T2E 7S6",
|
||
"addressCountry": "CA"
|
||
},
|
||
"areaServed": "Calgary, Alberta, Canada",
|
||
"priceRange": "$55/month",
|
||
"sameAs": [
|
||
"https://www.facebook.com/CalgaryProtospace/",
|
||
"https://www.youtube.com/user/calgaryprotospace/playlists"
|
||
],
|
||
"openingHoursSpecification": {
|
||
"@type": "OpeningHoursSpecification",
|
||
"dayOfWeek": "https://schema.org/Tuesday",
|
||
"opens": "19:00",
|
||
"closes": "21:00",
|
||
"description": "Public open house every Tuesday from 7:00 pm to 9:00 pm, including holidays. Visitors can drop in for a tour."
|
||
},
|
||
"contactPoint": {
|
||
"@type": "ContactPoint",
|
||
"telephone": "+1-587-774-5672",
|
||
"email": "info@protospace.ca",
|
||
"contactType": "customer service"
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
|
||
<body>
|
||
<header id="top">
|
||
<div class="nav-wrap">
|
||
<a class="brand" href="/"><img width="360" height="70" src="/assets/logo-white.svg" alt="Protospace" /></a>
|
||
<nav class="nav">
|
||
<a href="/the-space/">Spaces</a>
|
||
<a href="/tools/">Tools</a>
|
||
<a href="/join-us/">Membership</a>
|
||
<a href="/contact/">Contact</a>
|
||
<a href="https://my.protospace.ca" class="external">Portal</a>
|
||
<a href="https://wiki.protospace.ca" class="external">Wiki</a>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
<div class="content">
|
||
<div class="layout full">
|
||
<main>
|
||
<h1>Tools</h1>
|
||
<div class="entry">
|
||
<p>
|
||
Browse the tools and equipment available to Protospace members. This catalog
|
||
comes from the <a href="https://wiki.protospace.ca/Tools_we_have" class="external">Protospace Wiki</a>.
|
||
</p>
|
||
<h2>Search</h2>
|
||
<div class="tools-search">
|
||
<label for="tool-search">Search tools <span id="tool-results-count" class="tools-results-count" hidden> - Results: …</span></label>
|
||
<div class="tools-search-controls">
|
||
<input id="tool-search" type="search" placeholder="Search by tool name…" autocomplete="off" />
|
||
<button id="tool-search-clear" type="button" hidden>Clear</button>
|
||
</div>
|
||
</div>
|
||
<h2 id="tools-categories-heading">Categories</h2>
|
||
<nav id="tools-toc" class="tools-toc" aria-label="Tool categories">
|
||
<strong>Jump to a category</strong>
|
||
<ul id="tools-toc-list"></ul>
|
||
</nav>
|
||
<div id="tools-list" class="tools-list" aria-live="polite">
|
||
<p>Loading tools…</p>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
fetch("/tools.json")
|
||
.then((response) => {
|
||
if (!response.ok) throw new Error("Could not load the tools catalog.");
|
||
return response.json();
|
||
})
|
||
.then((tools) => {
|
||
const list = document.querySelector("#tools-list");
|
||
const toc = document.querySelector("#tools-toc-list");
|
||
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 = "#tools-toc";
|
||
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);
|
||
}
|
||
};
|
||
searchInput.addEventListener("input", render);
|
||
clearButton.addEventListener("click", () => {
|
||
searchInput.value = "";
|
||
searchInput.focus();
|
||
render();
|
||
});
|
||
render();
|
||
})
|
||
.catch((error) => {
|
||
document.querySelector("#tools-list").textContent = error.message;
|
||
});
|
||
</script>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
<footer>
|
||
<div class="footer-inner">
|
||
<div class="footer-links">
|
||
<div><a href="https://www.youtube.com/user/calgaryprotospace/playlists" class="external">YouTube</a></div>
|
||
<div><a href="https://wiki.protospace.ca/Faq" class="external">F.A.Q.</a></div>
|
||
<div><a href="https://www.facebook.com/CalgaryProtospace/" class="external">Facebook</a></div>
|
||
</div>
|
||
<p>PROTOSPACE 2026 · All rights reserved</p>
|
||
</div>
|
||
</footer>
|
||
</body>
|
||
|
||
</html>
|