Compare commits

..
17 Commits
423 changed files with 1740 additions and 355 deletions
+7 -11
View File
@@ -6,7 +6,7 @@ This is the static website for Protospace, a non-profit makerspace in Calgary, A
## Source of truth
- Edit the HTML, CSS, and metadata files directly.
- Edit the HTML, CSS, and metadata files directly under `public/`.
- Do not create or reintroduce a static-site generator.
- Do not add a build script that overwrites manually maintained pages.
- There is intentionally no `build.js` or equivalent generator.
@@ -14,10 +14,10 @@ This is the static website for Protospace, a non-profit makerspace in Calgary, A
## Page structure
- Every page must be an `index.html` inside its own route directory:
- Every page must be an `index.html` inside its own route directory under `public/`:
- `/index.html`
- `/about/index.html`
- `/the-space/index.html`
- `/tools/index.html`
- `/join-us/index.html`
- `/contact/index.html`
- `/wood-shop/index.html`
@@ -67,7 +67,7 @@ Keep source code pleasant for humans to read and edit.
When adding or editing a page, match the existing structure:
- Header with Protospace logo and links to About, Spaces, Membership, Wiki, Portal, and Contact
- Header with Protospace logo and links to Spaces, Tools, Membership, Contact, Portal, and Wiki
- Main page content with one meaningful `<h1>`
- Sidebar containing the current open-house information, address, parking details, map iframe, and search control where applicable
- Footer containing YouTube, FAQ, and Facebook links
@@ -81,15 +81,9 @@ Keep the current open-house information consistent:
If this information changes, update the sidebar, relevant page copy, JSON-LD, and `llms.txt` together.
## Stylesheet cache versioning
- Every HTML page references `styles.css` with a cache-busting epoch query parameter, such as `/styles.css?v=1790211252`.
- Whenever `styles.css` changes, update the `?v=` value in every HTML page to the current Unix epoch timestamp.
- Keep the preload and stylesheet URLs synchronized so both use the same version.
## Assets
- Store local images in `/assets/`.
- Store local images in `public/assets/`.
- Use descriptive filenames rather than WordPress upload names such as `photo_6_2023-06-06_16-39-08.jpg`.
- Remove unused downloaded image variants when it is safe to do so.
- Update every reference when renaming or removing an asset.
@@ -177,6 +171,7 @@ Before finishing an HTML change:
- Run structural validation when available, for example:
```bash
cd public/
npx html-validate \
--rule doctype-style:off \
--rule void-style:off \
@@ -190,6 +185,7 @@ Before finishing an HTML change:
- Start a local static server and smoke-test every route:
```bash
cd public/
python3 -m http.server 8765
```
+2 -1
View File
@@ -19,9 +19,10 @@ The Protospace WordPress site has been replaced with static HTML and CSS pages.
git checkout -b update-page
```
4. Edit the HTML pages, `styles.css`, or files in `assets/`. Preview the site locally with any static web server, for example:
4. Edit the HTML pages, `public/styles.css`, or files in `public/assets/`. Preview the site locally from the public directory with any static web server, for example:
```bash
cd public/
python3 -m http.server
```
-202
View File
@@ -1,202 +0,0 @@
<!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="Learn about Protospace, Calgary’s volunteer-run non-profit makerspace and community workshop." />
<link rel="canonical" href="https://protospace.ca/about/" />
<meta property="og:title" content="About Protospace – Protospace" />
<meta property="og:description" content="Learn about Protospace, Calgary’s volunteer-run non-profit makerspace and community workshop." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://protospace.ca/about/" />
<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>About Protospace – Protospace</title>
<link rel="preload" href="/styles.css?v=1790211252" as="style" />
<link rel="stylesheet" href="/styles.css?v=1790211252" />
<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>
<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="/about/">About</a>
<a href="/join-us/">Membership</a>
<a href="/the-space/">Spaces</a>
<a href="/contact/">Contact</a>
<a href="https://wiki.protospace.ca" class="external">Wiki</a>
<a href="https://my.protospace.ca" class="external">Portal</a>
</nav>
</div>
</header>
<div class="content">
<div class="layout">
<main>
<h1>About Protospace</h1>
<div class="entry">
<p>
Protospace is a volunteer-run, non-profit makerspace in Calgary, Alberta. We bring together
artists, builders, hackers, engineers, and curious people who want to learn, make things, and
share what they know.
</p>
<h2>A place to make things</h2>
<p>
Our 6,500-square-foot space includes dedicated areas for woodworking, metalworking, sewing,
electronics, laser cutting, 3D printing, machining, welding, and larger projects. Members
can use shared tools and workspaces around the clock, with training available for equipment
that requires an orientation.
</p>
<p>
Explore the <a href="/the-space/">spaces and equipment</a> available to members, or visit the
individual pages for the <a href="/wood-shop/">wood shop</a>,
<a href="/metal-shop/">metal shop</a>, <a href="/laser-cutters/">laser cutters</a>,
<a href="/sewing-room/">sewing room</a>, <a href="/electronics-lab/">electronics lab</a>,
and <a href="/3d-printers/">3D printers</a>.
</p>
<h2>Community-powered</h2>
<p>
Protospace has no owners, employees, or shareholders. The space is run by its members,
who volunteer their time to maintain equipment, teach classes, organize events, and keep
the community welcoming. Beginners and experienced makers are equally welcome.
</p>
<h2>Come visit</h2>
<p>
The best way to get to know Protospace is to come to our open house, every Tuesday from
7:00 pm to 9:00 pm, including holidays. Drop in for a tour, meet members, and see what
we are building. You don't need to register.
</p>
<h2>Protospace FAQ</h2>
<p>We receive a number of repeat questions in our inbox:</p>
<p><strong>Do you offer day rates?</strong></p>
<p>
No, we are not set up to host and monitor users on a per day basis. We don&#8217;t have
staff, everything we do is done by our wonderful and amazing volunteers!
</p>
<p><strong>Can I just join for a month to do my one project?</strong></p>
<p>
Not exactly, as a new member your first month is a probationary month where you are unable to
use the space alone. Also, the training courses necessary to use certain equipment may not be
offered at times convenient to facilitate this. That said, we are not a like gym and there
are no contracts so you are free to cancel or pause your membership at any time with no
penalties.
</p>
<p>
<strong>Can I get immediate/urgent access to your space to do my school project or that thing I
left to the last minute?</strong>
</p>
<p>No, all new members must go through the same new membership process described above.</p>
<p>
<strong>I need to do just one small thing, but it&#8217;s urgent or I don&#8217;t have the
skills
to do it. Can someone do it for me?</strong>
</p>
<p>
You are welcome to post a commission request on
<a href="https://forum.protospace.ca/c/chattymcchatface/commissions/35" class="external">our forum</a>.
However, please be aware that there is <strong>no guarantee</strong> that we have any members
willing or able to take on your request. It&#8217;s up to you to work something out with a
member.
</p>
</div>
</main>
<aside class="sidebar">
<section>
<h2>2026 Schedule:</h2>
<p class="green">
Open House every Tuesday from 7-9 pm (even holidays). Just drop in for a tour!
</p>
<p>
<a href="https://youtu.be/B9w5G6Rn99A" class="external">Watch the Protospace mini tour video</a>
</p>
</section>
<section>
<p>1530 27th Avenue NE<br />Bay 108<br />Calgary, Alberta, Canada<br />T2E 7S6</p>
<p><b>Parking:</b> For open house, park in the front lot (southeast) or on the street.</p>
<iframe
class="map"
loading="lazy"
src="https://maps.google.com/maps?width=300&amp;height=100&amp;hl=en&amp;q=Protospace%2C%20Calgary&amp;ie=UTF8&amp;t=&amp;z=12&amp;iwloc=B&amp;output=embed"
title="Map to Protospace"
></iframe>
</section>
</aside>
</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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Export the Protospace Wiki tool catalog and its thumbnails.
Usage:
python3 export_tools.py
The wiki uses a small JavaScript challenge. The equivalent cookie is sent on
both the page request and each thumbnail request.
"""
from __future__ import annotations
import json
import re
import sys
from html.parser import HTMLParser
from pathlib import Path
from urllib.parse import urljoin, urlparse
from urllib.request import Request, urlopen
WIKI_URL = "https://wiki.protospace.ca/Tools_we_have"
WIKI_ROOT = "https://wiki.protospace.ca"
COOKIE = "human_check=verified"
ROOT = Path(__file__).resolve().parent
PUBLIC_ROOT = ROOT / "public"
OUTPUT = PUBLIC_ROOT / "tools.json"
IMAGE_DIR = PUBLIC_ROOT / "assets" / "tools"
class ToolsParser(HTMLParser):
"""Extract ordered category/gallery data from the MediaWiki HTML."""
def __init__(self) -> None:
super().__init__(convert_charrefs=True)
self.category: str | None = None
self.in_h2 = False
self.h2_text: list[str] = []
self.item: dict | None = None
self.item_depth = 0
self.anchor: dict | None = None
self.items: list[dict] = []
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
attrs_dict = dict(attrs)
if tag == "h2":
self.in_h2 = True
self.h2_text = []
elif tag == "li" and "gallerybox" in (attrs_dict.get("class") or "").split():
self.item = {"category": self.category, "anchors": [], "src": None}
self.item_depth = 1
elif self.item is not None and tag == "li":
self.item_depth += 1
elif self.item is not None and tag == "a":
self.anchor = {"href": attrs_dict.get("href"), "text": []}
elif self.item is not None and tag == "img" and self.item["src"] is None:
self.item["src"] = attrs_dict.get("src")
def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
self.handle_starttag(tag, attrs)
self.handle_endtag(tag)
def handle_endtag(self, tag: str) -> None:
if tag == "h2" and self.in_h2:
category = " ".join("".join(self.h2_text).split())
if category and category.lower() != "contents":
self.category = category
self.in_h2 = False
elif tag == "a" and self.anchor is not None:
self.anchor["text"] = " ".join("".join(self.anchor["text"]).split())
if self.item is not None:
self.item["anchors"].append(self.anchor)
self.anchor = None
elif tag == "li" and self.item is not None:
self.item_depth -= 1
if self.item_depth == 0:
if self.item["category"] and self.item["category"].lower() != "removed equipment":
self.items.append(self.item)
self.item = None
def handle_data(self, data: str) -> None:
if self.in_h2:
self.h2_text.append(data)
if self.anchor is not None:
self.anchor["text"].append(data)
def fetch(url: str) -> tuple[bytes, str]:
request = Request(url, headers={"Cookie": COOKIE, "User-Agent": "Protospace tools exporter/1.0"})
with urlopen(request, timeout=30) as response:
content_type = response.headers.get_content_type()
return response.read(), content_type
def image_filename(src: str) -> str:
parts = [part for part in urlparse(src).path.split("/") if part]
# MediaWiki thumbnails end with e.g. 120px-182.jpg; the preceding part is
# the original filename and is stable even when thumbnail dimensions change.
if len(parts) >= 2:
return parts[-2]
return Path(parts[-1]).name
def main() -> int:
try:
page_bytes, content_type = fetch(WIKI_URL)
except Exception as exc:
print(f"Could not fetch {WIKI_URL}: {exc}", file=sys.stderr)
return 1
if content_type != "text/html" or b"Verifying you are human" in page_bytes:
print("The wiki returned the human-verification page; check the cookie.", file=sys.stderr)
return 1
parser = ToolsParser()
parser.feed(page_bytes.decode("utf-8", errors="replace"))
IMAGE_DIR.mkdir(parents=True, exist_ok=True)
exported: list[dict[str, str]] = []
skipped = 0
for raw in parser.items:
anchors = raw["anchors"]
name_anchor = next((a for a in anchors if "_ID:" in (a["href"] or "") or " ID:" in a["text"]), None)
name = name_anchor["text"] if name_anchor else ""
id_match = re.search(r"\bID:(\d+)\s*$", name)
photo_src = raw["src"]
if not name or not id_match or not photo_src:
skipped += 1
continue
photo_url = urljoin(WIKI_ROOT, photo_src)
source_filename = image_filename(photo_url)
filename = f"{id_match.group(1)}{Path(source_filename).suffix.lower()}"
destination = IMAGE_DIR / filename
try:
image_bytes, image_type = fetch(photo_url)
if not image_type.startswith("image/") or not image_bytes:
raise ValueError(f"response was {image_type}, not an image")
destination.write_bytes(image_bytes)
except Exception as exc:
print(f"Skipping {name}: broken photo ({exc})", file=sys.stderr)
skipped += 1
continue
link = urljoin(WIKI_ROOT, name_anchor["href"])
exported.append(
{
"ID": id_match.group(1),
"name": re.sub(r"\s*ID:\d+\s*$", "", name).strip(),
"photo file": f"/assets/tools/{filename}",
"link": link,
"category": raw["category"],
}
)
OUTPUT.write_text(json.dumps(exported, ensure_ascii=False, indent=2) + "\n")
print(f"Exported {len(exported)} tools to {OUTPUT}")
print(f"Skipped {skipped} tools without valid metadata or photos")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -11,12 +11,12 @@
<meta property="og:description" content="Explore Protospace’s 3D printer village in Calgary, with filament and resin printers available to trained members." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://protospace.ca/3d-printers/" />
<meta property="og:image" content="https://protospace.ca/assets/3d-printers-1-1024x768.jpg" />
<meta property="og:image" content="https://protospace.ca/assets/3d-printers-1-800.jpg" />
<meta property="og:site_name" content="Protospace" />
<meta name="twitter:card" content="summary_large_image" />
<title>3D Printers – Protospace</title>
<link rel="preload" href="/styles.css?v=1790211252" as="style" />
<link rel="stylesheet" href="/styles.css?v=1790211252" />
<link rel="preload" href="/styles.css" as="style" />
<link rel="stylesheet" href="/styles.css" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
@@ -68,12 +68,12 @@
<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="/about/">About</a>
<a href="/join-us/">Membership</a>
<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://wiki.protospace.ca" class="external">Wiki</a>
<a href="https://my.protospace.ca" class="external">Portal</a>
<a href="https://wiki.protospace.ca" class="external">Wiki</a>
</nav>
</div>
</header>
@@ -84,26 +84,24 @@
<div class="entry">
<figure>
<picture>
<source type="image/webp" srcset="/assets/3d-printers-1-1024x768.webp 1024w, /assets/3d-printers-1.webp 1280w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" />
<img width="1024" height="768" src="/assets/3d-printers-1-1024x768.jpg" srcset="/assets/3d-printers-1-1024x768.jpg 1024w, /assets/3d-printers-1.jpg 1280w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" alt="3D printer in the printer village" decoding="async" fetchpriority="high" />
<source type="image/webp" srcset="/assets/3d-printers-1-400.webp 400w, /assets/3d-printers-1-800.webp 800w, /assets/3d-printers-1-1600.webp 1600w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" />
<img width="800" height="602" src="/assets/3d-printers-1-800.jpg" srcset="/assets/3d-printers-1-400.jpg 400w, /assets/3d-printers-1-800.jpg 800w, /assets/3d-printers-1-1600.jpg 1600w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" alt="Resin 3D printers and printing supplies" fetchpriority="high" decoding="async" />
</picture>
</figure>
<figure>
<picture>
<source type="image/webp" srcset="/assets/3d-printers-2-1024x768.webp 1024w, /assets/3d-printers-2.webp 1280w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" />
<img width="1024" height="768" src="/assets/3d-printers-2-1024x768.jpg" srcset="/assets/3d-printers-2-1024x768.jpg 1024w, /assets/3d-printers-2.jpg 1280w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" alt="3D printer equipment" loading="lazy" decoding="async" />
<source type="image/webp" srcset="/assets/3d-printers-2-400.webp 400w, /assets/3d-printers-2-800.webp 800w, /assets/3d-printers-2-1600.webp 1600w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" />
<img width="800" height="602" src="/assets/3d-printers-2-800.jpg" srcset="/assets/3d-printers-2-400.jpg 400w, /assets/3d-printers-2-800.jpg 800w, /assets/3d-printers-2-1600.jpg 1600w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" alt="3D printer village with filament and resin printers" loading="lazy" decoding="async" />
</picture>
</figure>
<figure>
<picture>
<source type="image/webp" srcset="/assets/3d-printers-3-1024x768.webp 1024w, /assets/3d-printers-3.webp 1280w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" />
<img width="1024" height="768" src="/assets/3d-printers-3-1024x768.jpg" srcset="/assets/3d-printers-3-1024x768.jpg 1024w, /assets/3d-printers-3.jpg 1280w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" alt="3D printer in operation" loading="lazy" decoding="async" />
<source type="image/webp" srcset="/assets/3d-printers-3-400.webp 400w, /assets/3d-printers-3-800.webp 800w, /assets/3d-printers-3-1600.webp 1600w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" />
<img width="800" height="602" src="/assets/3d-printers-3-800.jpg" srcset="/assets/3d-printers-3-400.jpg 400w, /assets/3d-printers-3-800.jpg 800w, /assets/3d-printers-3-1600.jpg 1600w" sizes="(max-width: 760px) calc(100vw - 24px), 750px" alt="Desktop 3D printers and print supplies" loading="lazy" decoding="async" />
</picture>
</figure>
<p></p>
</div>
</main>
<aside class="sidebar">
@@ -133,10 +131,10 @@
<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://github.com/Protospace" class="external">GitHub</a></div>
<div><a href="https://www.facebook.com/CalgaryProtospace/" class="external">Facebook</a></div>
</div>
<p>PROTOSPACE 2026 · All rights reserved</p>
<p>© PROTOSPACE 2026 · <a href="https://github.com/Protospace/main-website" class="external">Source Code</a></p>
</div>
</footer>
</body>
Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Some files were not shown because too many files have changed in this diff Show More