Compare commits

...
26 Commits
Author SHA1 Message Date
Tanner 8c6e807653 Mobile menu header nav spacing fixes 2026-09-29 23:01:58 +00:00
Tanner 0725d3eb1a Convert srcset images to 1600, 800, 400px 2026-09-29 22:16:36 +00:00
Tanner bb8fd62041 Tools Back to Top links go to Search 2026-09-27 22:16:56 +00:00
Tanner 1745ea9e93 Tools page sticky header margin fixes 2026-09-27 20:47:24 +00:00
Tanner 2f1bb6deb5 Make gear icon smaller 2026-09-27 20:29:24 +00:00
Tanner 40d5b54055 Fix footer source code external link icon 2026-09-27 20:26:12 +00:00
Tanner e2934f84b8 Always display header font 2026-09-27 20:00:17 +00:00
Tanner a4069d27e4 Stop cache busting styles.css with query param 2026-09-27 19:55:42 +00:00
Tanner b33a72ced0 Move static assets to public/ 2026-09-27 19:18:35 +00:00
Tanner dde33d0907 Update footer links, add source code 2026-09-27 19:09:50 +00:00
Tanner fbf3dbe33e Update Electonics, Sewing, Lasers, 3D printer photos 2026-09-27 18:04:33 +00:00
Tanner 21785ed493 Cache bust styles.css 2026-09-26 01:12:25 +00:00
Tanner 557fa92e1f Reorder header: Spaces Tools Membership Contact Portal Wiki 2026-09-26 01:10:39 +00:00
Tanner 9e730bbcfd Remove About page 2026-09-26 01:08:04 +00:00
Tanner 05593346fc Tool page fixes 2026-09-26 01:02:50 +00:00
Tanner 7bac8b4011 Add Tools page search 2026-09-26 00:48:55 +00:00
Tanner 03b124f295 Add Tools page 2026-09-26 00:33:25 +00:00
Tanner 8f1d545ab4 Add 400px versions of the home page images 2026-09-24 16:13:06 +00:00
Tanner f5cfa74dde Don't lazy load the four home page images 2026-09-24 01:09:32 +00:00
Tanner d3d2421ac3 Cache bust styles.css with query param 2026-09-24 00:54:54 +00:00
Tanner 428398820c Make links look better 2026-09-24 00:42:02 +00:00
Tanner 3ad2c47bb3 Put Spaces URL back to /the-space/ 2026-09-24 00:33:21 +00:00
Tanner b9c7791f95 Refine About, Membership, and Spaces pages 2026-09-24 00:30:56 +00:00
Tanner 35c513d08b Reorder header, make mobile 3x2 2026-09-24 00:24:30 +00:00
Tanner 1a264905ea Rename About to Spaces, add About page 2026-09-23 20:58:23 +00:00
Tanner 933720125a Add external link icons 2026-09-23 20:50:51 +00:00
414 changed files with 1780 additions and 174 deletions
+7 -4
View File
@@ -6,7 +6,7 @@ This is the static website for Protospace, a non-profit makerspace in Calgary, A
## Source of truth ## 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 create or reintroduce a static-site generator.
- Do not add a build script that overwrites manually maintained pages. - Do not add a build script that overwrites manually maintained pages.
- There is intentionally no `build.js` or equivalent generator. - There is intentionally no `build.js` or equivalent generator.
@@ -14,9 +14,10 @@ This is the static website for Protospace, a non-profit makerspace in Calgary, A
## Page structure ## 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` - `/index.html`
- `/the-space/index.html` - `/the-space/index.html`
- `/tools/index.html`
- `/join-us/index.html` - `/join-us/index.html`
- `/contact/index.html` - `/contact/index.html`
- `/wood-shop/index.html` - `/wood-shop/index.html`
@@ -66,7 +67,7 @@ Keep source code pleasant for humans to read and edit.
When adding or editing a page, match the existing structure: When adding or editing a page, match the existing structure:
- Header with Protospace logo and links to About, 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>` - Main page content with one meaningful `<h1>`
- Sidebar containing the current open-house information, address, parking details, map iframe, and search control where applicable - Sidebar containing the current open-house information, address, parking details, map iframe, and search control where applicable
- Footer containing YouTube, FAQ, and Facebook links - Footer containing YouTube, FAQ, and Facebook links
@@ -82,7 +83,7 @@ If this information changes, update the sidebar, relevant page copy, JSON-LD, an
## Assets ## 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`. - 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. - Remove unused downloaded image variants when it is safe to do so.
- Update every reference when renaming or removing an asset. - Update every reference when renaming or removing an asset.
@@ -170,6 +171,7 @@ Before finishing an HTML change:
- Run structural validation when available, for example: - Run structural validation when available, for example:
```bash ```bash
cd public/
npx html-validate \ npx html-validate \
--rule doctype-style:off \ --rule doctype-style:off \
--rule void-style:off \ --rule void-style:off \
@@ -183,6 +185,7 @@ Before finishing an HTML change:
- Start a local static server and smoke-test every route: - Start a local static server and smoke-test every route:
```bash ```bash
cd public/
python3 -m http.server 8765 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 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 ```bash
cd public/
python3 -m http.server python3 -m http.server
``` ```
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: 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: 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: 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: 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,7 +11,7 @@
<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: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:type" content="website" />
<meta property="og:url" content="https://protospace.ca/3d-printers/" /> <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 property="og:site_name" content="Protospace" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<title>3D Printers – Protospace</title> <title>3D Printers – Protospace</title>
@@ -68,11 +68,12 @@
<div class="nav-wrap"> <div class="nav-wrap">
<a class="brand" href="/"><img width="360" height="70" src="/assets/logo-white.svg" alt="Protospace" /></a> <a class="brand" href="/"><img width="360" height="70" src="/assets/logo-white.svg" alt="Protospace" /></a>
<nav class="nav"> <nav class="nav">
<a href="/the-space/">About</a> <a href="/the-space/">Spaces</a>
<a href="/tools/">Tools</a>
<a href="/join-us/">Membership</a> <a href="/join-us/">Membership</a>
<a href="https://wiki.protospace.ca">Wiki</a>
<a href="https://my.protospace.ca">Portal</a>
<a href="/contact/">Contact</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> </nav>
</div> </div>
</header> </header>
@@ -83,26 +84,24 @@
<div class="entry"> <div class="entry">
<figure> <figure>
<picture> <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" /> <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="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" /> <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> </picture>
</figure> </figure>
<figure> <figure>
<picture> <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" /> <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="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" /> <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> </picture>
</figure> </figure>
<figure> <figure>
<picture> <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" /> <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="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" /> <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> </picture>
</figure> </figure>
<p></p>
</div> </div>
</main> </main>
<aside class="sidebar"> <aside class="sidebar">
@@ -112,7 +111,7 @@
Open House every Tuesday from 7-9 pm (even holidays). Just drop in for a tour! Open House every Tuesday from 7-9 pm (even holidays). Just drop in for a tour!
</p> </p>
<p> <p>
<a href="https://youtu.be/B9w5G6Rn99A">Watch the Protospace mini tour video</a> <a href="https://youtu.be/B9w5G6Rn99A" class="external">Watch the Protospace mini tour video</a>
</p> </p>
</section> </section>
<section> <section>
@@ -131,11 +130,11 @@
<footer> <footer>
<div class="footer-inner"> <div class="footer-inner">
<div class="footer-links"> <div class="footer-links">
<div><a href="https://www.youtube.com/user/calgaryprotospace/playlists">YouTube</a></div> <div><a href="https://www.youtube.com/user/calgaryprotospace/playlists" class="external">YouTube</a></div>
<div><a href="https://wiki.protospace.ca/Faq">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/">Facebook</a></div> <div><a href="https://www.facebook.com/CalgaryProtospace/" class="external">Facebook</a></div>
</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> </div>
</footer> </footer>
</body> </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

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

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