diff --git a/.lycheeignore b/.lycheeignore
index 6562b48..9dc627f 100644
--- a/.lycheeignore
+++ b/.lycheeignore
@@ -1,2 +1,3 @@
/themes
https://old.reddit.com/r/rust
+https://zubanls.com/pricing/
\ No newline at end of file
diff --git a/config.toml b/config.toml
index 99c8fe1..877bdf1 100644
--- a/config.toml
+++ b/config.toml
@@ -4,7 +4,7 @@ default_language = "en"
title = "Rob's Blog | Python • Rust • Ramblings?"
# Whether to automatically compile all Sass files in the sass directory
-compile_sass = false
+compile_sass = true
# Whether to build a search index to be used later on by a JavaScript library
build_search_index = false
@@ -29,6 +29,7 @@ after_dark_menu = [
{ name = "Home", url = "$BASE_URL" },
{ name = "About", url = "$BASE_URL/about/" },
{ name = "Now", url = "$BASE_URL/now/" },
+ { name = "Boardgames", url = "$BASE_URL/boardgames" },
{ name = "TILs", url = "$BASE_URL/tils/" },
{ name = "Tags", url = "$BASE_URL/tags" },
{ name = "Source", url = "https://github.com/sinon/sinon.github.io" },
diff --git a/content/future-python-type-checkers.md b/content/future-python-type-checkers.md
index b39e862..7a670f5 100644
--- a/content/future-python-type-checkers.md
+++ b/content/future-python-type-checkers.md
@@ -79,7 +79,7 @@ Before examining these new Rust-based tools, it's worth understanding the curren
- Created by the author of the popular Python LSP tool `jedi`
- Aims for high-degree of compatibility with `mypy` to make adoption in large existing codebases seamless.
-- Not FOSS[^2], will require a license for codebases above 1.5 MB (~50,000 lines of code)[^3].
+- ~~Not FOSS[^2], will require a license for codebases above 1.5 MB (~50,000 lines of code)[^3]~~**Update (September 2025):** Now open source under the AGPL license, though commercial licensing is available for business who prefer to avoid AGPL compliance.
- Currently maintained by a single author seems a potential risk to long-term sustainability as Python typing does not stand still.
**Philosophy:** Zuban aims to provide the smoothest possible migration path from existing type checkers, particularly `mypy`, making it attractive for organizations with substantial existing typed codebases.
@@ -108,7 +108,7 @@ __Generated 29/08/2025__
> That being said even though `ty` is lagging on this metric at the moment it is still the type checker that I am most excited to use long-term because of the quality of the tooling Astral has built so far.
| Type Checker | Total Test Case Passes | Total Test Case Partial | Total False Positives | Total False Negatives |
-| :---------------------------------------------: | :--------------------: | :---------------------: | :-------------------: | :-------------------: |
+| :---------------------------------------------: | :--------------------: | :---------------------: | :-------------------: | :-------------------: |
| zuban 0.0.20 | 97 | 42 | 152 | 89 |
| ty 0.0.1-alpha.19 (e9cb838b3 2025-08-19) | 20 | 119 | 371 | 603 |
| Local:ty ruff/0.12.11+27 (0bf5d2a20 2025-08-29) | 20 | 119 | 370 | 590 |
@@ -165,7 +165,7 @@ For teams evaluating these type checkers, the conformance scores provide valuabl
[^1]: Which can be demonstrated in the [open issues](https://github.com/astral-sh/ruff/issues?q=is%3Aissue%20state%3Aopen%20label%3Atype-inference ) on ruff tagged with `type-inference` which are bugs or new features that can only be resolved with `ruff` having access to deeper type inference data that `ty` can supply.
[^2]: David has indicated a plan to make [source available in the future](https://github.com/python/typing/pull/2067#issuecomment-3177937964) when adding Zuban to the Python typing conformance suite.
-[^3]: Full pricing information at:
+[^3]: ~~Full pricing information at: https://zubanls.com/pricing/~~ **Update (September 2025):** Pricing is now available on request for the non-AGPL license.
[^4]: This is just for this blog post, no plans to seek merging this.
diff --git a/content/pages/boardgames.md b/content/pages/boardgames.md
new file mode 100644
index 0000000..8bcbae0
--- /dev/null
+++ b/content/pages/boardgames.md
@@ -0,0 +1,7 @@
++++
+title = "Boardgames"
+date = 2025-08-27
+template = "boardgames.html"
+sort_by = "none"
+path = "boardgames"
++++
diff --git a/sass/boardgames.sass b/sass/boardgames.sass
new file mode 100644
index 0000000..be1201e
--- /dev/null
+++ b/sass/boardgames.sass
@@ -0,0 +1,77 @@
+.boardgames-container
+ max-width: 1200px
+ margin: 0 auto
+ padding: 20px
+
+.games-grid
+ display: grid
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))
+ gap: 20px
+ margin-top: 20px
+
+.game-card
+ border: 1px solid #404040
+ border-radius: 8px
+ padding: 16px
+ background: var(--bg-primary)
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
+ transition: transform 0.2s, box-shadow 0.2s
+
+ &:hover
+ transform: translateY(-2px)
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4)
+ border-color: var(--note-color)
+
+.game-header
+ display: flex
+ gap: 12px
+ margin-bottom: 12px
+
+.game-thumbnail
+ width: 85px
+ height: 110px
+ object-fit: cover
+ border-radius: 4px
+ border: 1px solid #404040
+
+.game-info
+ flex: 1
+
+.game-title
+ font-weight: bold
+ margin: 0 0 4px 0
+ color: #ffffff
+
+.game-year
+ color: #cccccc
+ font-size: 0.9em
+ margin: 0
+
+.game-stats
+ display: flex
+ gap: 16px
+ margin-bottom: 8px
+ font-size: 0.9em
+ color: #aaaaaa
+
+.game-rating
+ font-weight: bold
+ color: var(--note-color)
+
+ &.rating-high
+ color: #27ae60
+
+ &.rating-medium
+ color: #f39c12
+
+ &.rating-low
+ color: #e74c3c
+
+ &.rating-unrated
+ color: #999999
+
+.game-comment
+ color: #cccccc
+ font-size: 0.9em
+ margin-top: 8px
+ font-style: italic
diff --git a/scripts/boardgames.py b/scripts/boardgames.py
new file mode 100755
index 0000000..ddab4fe
--- /dev/null
+++ b/scripts/boardgames.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env -S uv run
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+# "requests",
+# ]
+# ///
+
+import requests
+import xml.etree.ElementTree as ET
+import json
+import time
+import logging
+from typing import List, Dict, Any
+
+logging.basicConfig(
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
+)
+logger = logging.getLogger(__name__)
+
+
+def fetch_boardgame_collection(username: str) -> str:
+ url = f"https://boardgamegeek.com/xmlapi2/collection?username={username}&own=1&stats=1&excludesubtype=boardgameexpansion"
+
+ max_retries = 5
+ retry_delay = 2
+
+ for attempt in range(max_retries):
+ response = requests.get(url)
+
+ if response.status_code == 202:
+ logger.info(
+ f"Collection still processing, retrying in {retry_delay} seconds... (attempt {attempt + 1}/{max_retries})"
+ )
+ time.sleep(retry_delay)
+ retry_delay *= 2
+ continue
+
+ response.raise_for_status()
+ return response.text
+
+ raise Exception(f"Failed to fetch collection after {max_retries} attempts")
+
+
+def parse_collection_xml(xml_content: str) -> List[Dict[str, Any]]:
+ root = ET.fromstring(xml_content)
+ games = []
+
+ for item in root.findall("item"):
+ game = {
+ "objectid": item.get("objectid"),
+ "name": None,
+ "yearpublished": None,
+ "my_rating": None,
+ "stats": {},
+ "comment": None,
+ }
+
+ name_elem = item.find("name")
+ if name_elem is not None:
+ game["name"] = name_elem.text
+
+ year_elem = item.find("yearpublished")
+ if year_elem is not None:
+ game["yearpublished"] = year_elem.text
+
+ thumbnail_elem = item.find("image")
+ if thumbnail_elem is not None:
+ game["image"] = thumbnail_elem.text
+ comment_elem = item.find("comment")
+ if comment_elem is not None:
+ game["comment"] = comment_elem.text
+
+ stats_elem = item.find("stats")
+ if stats_elem is not None:
+ rating_elem = stats_elem.find("rating")
+ if rating_elem is not None:
+ game["stats"] = {
+ "minplayers": stats_elem.get("minplayers"),
+ "maxplayers": stats_elem.get("maxplayers"),
+ "playingtime": stats_elem.get("playingtime"),
+ }
+ if rating_elem.get("value") != "N/A":
+ game["my_rating"] = float(rating_elem.get("value"))
+ games.append(game)
+ games = sorted(games, key=lambda x: x["my_rating"] or 0, reverse=True)
+ return games
+
+
+def save_to_json(data: List[Dict[str, Any]], filename: str) -> None:
+ with open(filename, "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=2, ensure_ascii=False)
+
+
+def main():
+ username = "sinon88"
+ output_file = "../static/boardgames_collection.json"
+
+ try:
+ logger.info(f"Fetching board game collection for user: {username}")
+ xml_content = fetch_boardgame_collection(username)
+
+ logger.info("Parsing XML content...")
+ games_list = parse_collection_xml(xml_content)
+
+ logger.info(f"Found {len(games_list)} games in collection")
+
+ logger.info(f"Saving to {output_file}...")
+ save_to_json(games_list, output_file)
+
+ logger.info(f"Successfully saved board game collection to {output_file}")
+
+ except requests.exceptions.RequestException as e:
+ logger.error(f"Error fetching data: {e}")
+ except ET.ParseError as e:
+ logger.error(f"Error parsing XML: {e}")
+ except Exception as e:
+ logger.error(f"Unexpected error: {e}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/static/boardgames_collection.json b/static/boardgames_collection.json
new file mode 100644
index 0000000..ff571c7
--- /dev/null
+++ b/static/boardgames_collection.json
@@ -0,0 +1,795 @@
+[
+ {
+ "objectid": "373106",
+ "name": "Sky Team",
+ "yearpublished": "2023",
+ "my_rating": 9.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "20"
+ },
+ "comment": "Excellent, the perfect light easy to setup game that is tense and engaging throughout. The modules add interesting new worries to each game, meaning it still hasn't gotten stale after 10+ plays.",
+ "image": "https://cf.geekdo-images.com/uXMeQzNenHb3zK7Hoa6b2w__original/img/mWOQnkpyYBorh_Y1-0Y2o-ew17k=/0x0/filters:format(jpeg)/pic7398904.jpg"
+ },
+ {
+ "objectid": "28143",
+ "name": "Race for the Galaxy",
+ "yearpublished": "2007",
+ "my_rating": 8.55,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "60"
+ },
+ "comment": "Excellent tableau builder, quick once players internalise the symbolic language. Never overstays it's welcome and still come across new combinations with each play.",
+ "image": "https://cf.geekdo-images.com/-DOqixs8uwKUvvWPKI4f9w__original/img/Vh-DCkTPa8OU45LaJdUywwhiYqE=/0x0/filters:format(jpeg)/pic5261714.jpg"
+ },
+ {
+ "objectid": "124361",
+ "name": "Concordia",
+ "yearpublished": "2013",
+ "my_rating": 8.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "100"
+ },
+ "comment": "Simple to teach with a nice strategic depth.",
+ "image": "https://cf.geekdo-images.com/CzwSm8i7tkLz6cBnrILZBg__original/img/BhJ3sB3uk-eSdR1iW4EP3cu0Wi0=/0x0/filters:format(jpeg)/pic3453267.jpg"
+ },
+ {
+ "objectid": "161936",
+ "name": "Pandemic Legacy: Season 1",
+ "yearpublished": "2015",
+ "my_rating": 8.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "60"
+ },
+ "comment": "4p, never finished. The theme and novelty are probably what keeps this rating so high.",
+ "image": "https://cf.geekdo-images.com/-Qer2BBPG7qGGDu6KcVDIw__original/img/PlzAH7swN1nsFxOXbfUvE3TkE5w=/0x0/filters:format(png)/pic2452831.png"
+ },
+ {
+ "objectid": "162886",
+ "name": "Spirit Island",
+ "yearpublished": "2017",
+ "my_rating": 8.5,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "120"
+ },
+ "comment": "Great theme, great variety in spirits/difficulty/scenarios.",
+ "image": "https://cf.geekdo-images.com/kjCm4ZvPjIZxS-mYgSPy1g__original/img/9uLd9C3XAvInLCLhAoXqKVk56zs=/0x0/filters:format(jpeg)/pic7013651.jpg"
+ },
+ {
+ "objectid": "503",
+ "name": "Through the Desert",
+ "yearpublished": "1998",
+ "my_rating": 8.2,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/Rq_hJIAO7jDgIpAW943hJA__original/img/9lIQGNwBYmAlsgXOI6Iw2Q2CIkA=/0x0/filters:format(jpeg)/pic7697747.jpg"
+ },
+ {
+ "objectid": "386937",
+ "name": "Lacuna",
+ "yearpublished": "2023",
+ "my_rating": 8.1,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "15"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/kYZoWRZUfVD_g4TefiBu-Q__original/img/0S_hY9a3JRwwJPLNONiLjDLCTcI=/0x0/filters:format(png)/pic7488897.png"
+ },
+ {
+ "objectid": "93",
+ "name": "El Grande",
+ "yearpublished": "1995",
+ "my_rating": 8.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "120"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/RRKDHaYtFPHhczkUDcHOmg__original/img/E_QazS4f8ffj6oBcUl3C_VROCEw=/0x0/filters:format(jpeg)/pic7906240.jpg"
+ },
+ {
+ "objectid": "204493",
+ "name": "Sakura Arms",
+ "yearpublished": "2016",
+ "my_rating": 8.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/IMslo9J8RnWXQospAjO6Dw__original/img/DPPq9spt37SJlliXpyqKXFVwu_4=/0x0/filters:format(jpeg)/pic3545811.jpg"
+ },
+ {
+ "objectid": "424975",
+ "name": "Wilmot's Warehouse",
+ "yearpublished": "2024",
+ "my_rating": 8.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "6",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/me8N7vrfzBRD83d0VcRM-g__original/img/4LHTwSEhk4OPd5ZHExPvfHXNOJs=/0x0/filters:format(png)/pic8327408.png"
+ },
+ {
+ "objectid": "368061",
+ "name": "Zoo Vadis",
+ "yearpublished": "2023",
+ "my_rating": 8.0,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "7",
+ "playingtime": "40"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/Kl3NjtNKpuJNPjdBQtdsow__original/img/3ABkMQwQ-85hqaBO7gHP5ekBAQ0=/0x0/filters:format(jpeg)/pic6988937.jpg"
+ },
+ {
+ "objectid": "200680",
+ "name": "Agricola (Revised Edition)",
+ "yearpublished": "2016",
+ "my_rating": 7.9,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "120"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/YCGWJMFwOI5efji2RJ2mSw__original/img/jC_He46LcIcKWU-kSwkYdr9Z45E=/0x0/filters:format(jpeg)/pic8093340.jpg"
+ },
+ {
+ "objectid": "73439",
+ "name": "Troyes",
+ "yearpublished": "2010",
+ "my_rating": 7.9,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "90"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/fBIe-aEPtJKC-ceNlspK6A__original/img/otcIVdG4ebZmK1SjUtzma18JtJ4=/0x0/filters:format(jpeg)/pic750583.jpg"
+ },
+ {
+ "objectid": "84876",
+ "name": "The Castles of Burgundy",
+ "yearpublished": "2011",
+ "my_rating": 7.8,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "90"
+ },
+ "comment": "Go to game with Wife for a non-confrontational resource gathering game. Not too headsdown, still a lot of focus on what moves the opponent is making. \n\nOnly played 2 player",
+ "image": "https://cf.geekdo-images.com/sH2YTQ10dHj1ibfS-KKtGA__original/img/L_gsMsuhbAe0kyq1QLAmyeKOeSs=/0x0/filters:format(jpeg)/pic8745814.jpg"
+ },
+ {
+ "objectid": "124742",
+ "name": "Android: Netrunner",
+ "yearpublished": "2012",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/2ewHIIG_TRq8bYlqk0jIMw__original/img/cassW39WF2QrPImJF59efADAmM0=/0x0/filters:format(jpeg)/pic3738560.jpg"
+ },
+ {
+ "objectid": "178900",
+ "name": "Codenames",
+ "yearpublished": "2015",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "8",
+ "playingtime": "15"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/nC6ifPCDnAItwoKSKXVrnw__original/img/Id-jjIer_61ZbvI2_RVRCeBZFY4=/0x0/filters:format(jpeg)/pic8907965.jpg"
+ },
+ {
+ "objectid": "207016",
+ "name": "Flick 'em Up!: Dead of Winter",
+ "yearpublished": "2017",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "10",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/P7W3dCZliN22VxH7JPUG3A__original/img/FJ0Y-DDM5A_rmkXvNXEm-brHaHk=/0x0/filters:format(jpeg)/pic3365608.jpg"
+ },
+ {
+ "objectid": "339924",
+ "name": "Hot Lead",
+ "yearpublished": "2022",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "15"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/yM17m8v87Gj_kIpc3hBQXw__original/img/o0a_QN1SLe3kXV4K1rrPx2jGzQ8=/0x0/filters:format(png)/pic6231247.png"
+ },
+ {
+ "objectid": "118",
+ "name": "Modern Art",
+ "yearpublished": "2021",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "5",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/DHO7YSykDvUQqLMRJL37BQ__original/img/AmSoL3kBrXSlTGG-cJkMzxAVOfE=/0x0/filters:format(jpeg)/pic7161137.jpg"
+ },
+ {
+ "objectid": "141572",
+ "name": "Paperback",
+ "yearpublished": "2014",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/iZl2U-2BjftbWfLw5y25Vg__original/img/2kvlCGamVRWxUarnVS0nuV8LudM=/0x0/filters:format(jpeg)/pic7069377.jpg"
+ },
+ {
+ "objectid": "237182",
+ "name": "Root",
+ "yearpublished": "2018",
+ "my_rating": 7.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "90"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/JUAUWaVUzeBgzirhZNmHHw__original/img/E0s2LvtFA1L5YKk-_44D4u2VD2s=/0x0/filters:format(jpeg)/pic4254509.jpg"
+ },
+ {
+ "objectid": "372",
+ "name": "Schotten Totten",
+ "yearpublished": "1999",
+ "my_rating": 7.3,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/69mwXe7c6HNYmT6S35Y4zg__original/img/C5YBiD7Qhpkf5FO1GteEjo9KRVE=/0x0/filters:format(jpeg)/pic2932872.jpg"
+ },
+ {
+ "objectid": "12333",
+ "name": "Twilight Struggle",
+ "yearpublished": "2005",
+ "my_rating": 7.3,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "180"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/pNCiUUphnoeWOYfsWq0kng__original/img/Iae47UtAd_RXVd5tJ3YzbDHOv4E=/0x0/filters:format(jpeg)/pic3530661.jpg"
+ },
+ {
+ "objectid": "36218",
+ "name": "Dominion",
+ "yearpublished": "2008",
+ "my_rating": 7.1,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/j6iQpZ4XkemZP07HNCODBA__original/img/96COuakNiLRrjDLc1sM4Zxsw4WE=/0x0/filters:format(jpeg)/pic394356.jpg"
+ },
+ {
+ "objectid": "163412",
+ "name": "Patchwork",
+ "yearpublished": "2014",
+ "my_rating": 7.1,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/RDOwMRBnIb3Ehl6GyXj9xg__original/img/iVl8KAZ8JMmDXsnBqQecFRkOhK8=/0x0/filters:format(jpeg)/pic8669620.jpg"
+ },
+ {
+ "objectid": "70919",
+ "name": "Takenoko",
+ "yearpublished": "2011",
+ "my_rating": 7.1,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/uvz-5V6A2R6dp2oWIXmj_g__original/img/HxMG1z7bIVGCGPI6u6ciVO0Oubk=/0x0/filters:format(jpeg)/pic1912529.jpg"
+ },
+ {
+ "objectid": "227224",
+ "name": "The Red Cathedral",
+ "yearpublished": "2020",
+ "my_rating": 7.05,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "80"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/-PuiCnbxlBzMSjnUegp9AQ__original/img/hH1AwTR4fdtYY_cO8IYp1-I8dhE=/0x0/filters:format(jpeg)/pic5556025.jpg"
+ },
+ {
+ "objectid": "247367",
+ "name": "Air, Land, & Sea",
+ "yearpublished": "2019",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/3wpYeNwO7k_yjpIN7IQwaw__original/img/_qQC98hufgmPn_fwMrP4I0sYKO8=/0x0/filters:format(jpeg)/pic4387681.jpg"
+ },
+ {
+ "objectid": "219475",
+ "name": "Axio",
+ "yearpublished": "2017",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/wROIUYusbdD2Lov5cnMCNw__original/img/KZ4ikPksvug5e2zrTM0tdFtAT3U=/0x0/filters:format(jpeg)/pic3742268.jpg"
+ },
+ {
+ "objectid": "147151",
+ "name": "Concept",
+ "yearpublished": "2013",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "4",
+ "maxplayers": "12",
+ "playingtime": "40"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/hHFs0_KRoW_FJ0cMIVgZcw__original/img/vbR7Wrdlq4fPcwUE1Ck9CowlJHY=/0x0/filters:format(jpeg)/pic4991925.jpg"
+ },
+ {
+ "objectid": "98778",
+ "name": "Hanabi",
+ "yearpublished": "2010",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "25"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/JDVksMwfcqoem1k_xtZrOA__original/img/5vNHZiTEhK4aRDuGXv5KImp9cmQ=/0x0/filters:format(jpeg)/pic2007286.jpg"
+ },
+ {
+ "objectid": "161882",
+ "name": "Irish Gauge",
+ "yearpublished": "2014",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "5",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/BZW9WTKwGMfb3R4EELel3w__original/img/K3rGKFzRT3m1MAgyAUQaDXdnFMM=/0x0/filters:format(jpeg)/pic7628587.jpg"
+ },
+ {
+ "objectid": "54043",
+ "name": "Jaipur",
+ "yearpublished": "2009",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/_LTujSe_o16nvjDC-J0seA__original/img/zUnAdsgt4dCxvtoRDc_9DHbic0M=/0x0/filters:format(jpeg)/pic5100947.jpg"
+ },
+ {
+ "objectid": "253399",
+ "name": "Journal 29: Interactive Book Game",
+ "yearpublished": "2017",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "10",
+ "playingtime": "120"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/Y1dibOE_gxTh9wEfThT55w__original/img/uwJazrctvqI7ZA5zCCQsedI_fyo=/0x0/filters:format(jpeg)/pic4675327.jpg"
+ },
+ {
+ "objectid": "181304",
+ "name": "Mysterium",
+ "yearpublished": "2015",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "7",
+ "playingtime": "42"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/1nQ3ZKudtDeAP7IiKE-kNg__original/img/6igmXolQesapNU9yEnexip6m9B8=/0x0/filters:format(jpeg)/pic8625343.jpg"
+ },
+ {
+ "objectid": "147949",
+ "name": "One Night Ultimate Werewolf",
+ "yearpublished": "2014",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "10",
+ "playingtime": "10"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/rqLju4uaZq-G9z4g91aPPQ__original/img/eOxUlbCuix0lOjuyBYpjtIXVihc=/0x0/filters:format(png)/pic8783294.png"
+ },
+ {
+ "objectid": "30549",
+ "name": "Pandemic",
+ "yearpublished": "2008",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/S3ybV1LAp-8SnHIXLLjVqA__original/img/IsrvRLpUV1TEyZsO5rC-btXaPz0=/0x0/filters:format(jpeg)/pic1534148.jpg"
+ },
+ {
+ "objectid": "25669",
+ "name": "Qwirkle",
+ "yearpublished": "2006",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/k7zHj8j_a6uUAtXUt5Fvuw__original/img/FNi6yap-UmVKX8X6rj0A_YW2q8Y=/0x0/filters:format(jpeg)/pic309353.jpg"
+ },
+ {
+ "objectid": "92415",
+ "name": "Skull",
+ "yearpublished": "2011",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "6",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/OPrd2iXm43dir7BwKAMOuQ__original/img/ygPnbmg8FCpA7jO5gXw38uyNQ10=/0x0/filters:format(jpeg)/pic6097488.jpg"
+ },
+ {
+ "objectid": "14996",
+ "name": "Ticket to Ride: Europe",
+ "yearpublished": "2005",
+ "my_rating": 7.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/0K1AOciqlMVUWFPLTJSiww__original/img/O37sCRSJLq4S8EpCxFDNVsNBuxE=/0x0/filters:format(jpeg)/pic66668.jpg"
+ },
+ {
+ "objectid": "822",
+ "name": "Carcassonne",
+ "yearpublished": "2000",
+ "my_rating": 6.99,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "45"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/okM0dq_bEXnbyQTOvHfwRA__original/img/aVZEXAI-cUtuunNfPhjeHlS4fwQ=/0x0/filters:format(png)/pic6544250.png"
+ },
+ {
+ "objectid": "131357",
+ "name": "Coup",
+ "yearpublished": "2012",
+ "my_rating": 6.9,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "6",
+ "playingtime": "15"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/MWhSY_GOe2-bmlQ2rntSVg__original/img/ayAAWBK1rAEumARNmROsOtvqW-4=/0x0/filters:format(jpeg)/pic2016054.jpg"
+ },
+ {
+ "objectid": "148203",
+ "name": "Dutch Blitz",
+ "yearpublished": "1960",
+ "my_rating": 6.9,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "15"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/ipuSaHTjxDpXAee-3vttiA__original/img/oyPs5ZLZb6e2g_8nA0uZAOKE4EM=/0x0/filters:format(jpeg)/pic336418.jpg"
+ },
+ {
+ "objectid": "118048",
+ "name": "Targi",
+ "yearpublished": "2012",
+ "my_rating": 6.7,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "2",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/wHg4fOf48cs1kw1PDOk1tQ__original/img/St4ABQhpauYiq4Mt6-f38JtLOjM=/0x0/filters:format(jpeg)/pic3958793.jpg"
+ },
+ {
+ "objectid": "41114",
+ "name": "The Resistance",
+ "yearpublished": "2009",
+ "my_rating": 6.5,
+ "stats": {
+ "minplayers": "5",
+ "maxplayers": "10",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/cAPTkL2BR3snLO71dkH8rw__original/img/_UahoIUTqh39xRqzCc2jwgdYbSA=/0x0/filters:format(jpeg)/pic2576459.jpg"
+ },
+ {
+ "objectid": "133473",
+ "name": "Sushi Go!",
+ "yearpublished": "2013",
+ "my_rating": 6.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "15"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/Fn3PSPZVxa3YurlorITQ1Q__original/img/5vRsn2TT9eJmq27i9HtLWKpB_xs=/0x0/filters:format(jpeg)/pic1900075.jpg"
+ },
+ {
+ "objectid": "16992",
+ "name": "Tsuro",
+ "yearpublished": "2005",
+ "my_rating": 6.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "8",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/2V3d4ryhxkS3RoPtDrvpUw__original/img/TP5U-O2eujPBK9IVmmifdfkWwdQ=/0x0/filters:format(jpeg)/pic875761.jpg"
+ },
+ {
+ "objectid": "157969",
+ "name": "Sheriff of Nottingham",
+ "yearpublished": "2014",
+ "my_rating": 6.3,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "5",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/BBgLFKUzr6tcKtlIM2JSFw__original/img/fll2MuTBPVKb7fyBJ8D-4_c1XHM=/0x0/filters:format(jpeg)/pic2075830.jpg"
+ },
+ {
+ "objectid": "110327",
+ "name": "Lords of Waterdeep",
+ "yearpublished": "2012",
+ "my_rating": 6.1,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "5",
+ "playingtime": "120"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/-hk8f8iGk_DyWyMrfiPBkg__original/img/GWNOhno3y7g0XE00VacKaRxoczk=/0x0/filters:format(jpeg)/pic1116080.jpg"
+ },
+ {
+ "objectid": "129622",
+ "name": "Love Letter",
+ "yearpublished": "2012",
+ "my_rating": 6.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/T1ltXwapFUtghS9A7_tf4g__original/img/xIAzJY7rl-mtPStRZSqnTVsAr8Y=/0x0/filters:format(jpeg)/pic1401448.jpg"
+ },
+ {
+ "objectid": "150312",
+ "name": "Welcome to the Dungeon",
+ "yearpublished": "2013",
+ "my_rating": 6.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/6eQXDEMIWldaut9w7jXihg__original/img/E_d5wpS2jEaH7mdO6humOGULycA=/0x0/filters:format(jpeg)/pic6771265.jpg"
+ },
+ {
+ "objectid": "6249",
+ "name": "Alhambra",
+ "yearpublished": "2003",
+ "my_rating": 5.9,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "6",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/OiqKsYDh7pqeRYKG__kMSw__original/img/Oe2ZTGE2hUnyZQIEoIjM_zwYfcY=/0x0/filters:format(jpeg)/pic4893652.jpg"
+ },
+ {
+ "objectid": "295949",
+ "name": "Adventure Games: The Grand Hotel Abaddon",
+ "yearpublished": "2020",
+ "my_rating": 5.1,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "270"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/dKRPsLgTrkFVAwUqU4BBqQ__original/img/pAum8eEQuvv0gpvP7VMADmaH1dM=/0x0/filters:format(jpeg)/pic6210950.jpg"
+ },
+ {
+ "objectid": "342207",
+ "name": "echoes: The Dancer",
+ "yearpublished": "2021",
+ "my_rating": 5.0,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "6",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/znvaTlswIC23fYH5oHMrGg__original/img/mR9QatN077Q3u1DnRWobE6m0l3E=/0x0/filters:format(jpeg)/pic6306043.jpg"
+ },
+ {
+ "objectid": "62871",
+ "name": "Zombie Dice",
+ "yearpublished": "2010",
+ "my_rating": 5.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "99",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/iPy584JMAJYrupqRdQp8gA__original/img/zPbWjPYgLscrjd96Uitf3GTPb34=/0x0/filters:format(jpeg)/pic4991962.jpg"
+ },
+ {
+ "objectid": "1269",
+ "name": "Skip-Bo",
+ "yearpublished": "1967",
+ "my_rating": 4.5,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "6",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/aaUJua2zRXA6lqbVbNcBGg__original/img/dWxG7nwxGBijXQ1849TFwtym06I=/0x0/filters:format(jpeg)/pic8204244.jpg"
+ },
+ {
+ "objectid": "2593",
+ "name": "Pass the Pigs",
+ "yearpublished": "1977",
+ "my_rating": 4.0,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "10",
+ "playingtime": "30"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/blW_a3O5Md5bE1jFygJ6SA__original/img/rbBCCKoEY8QvfaXeDSySuVVTUXk=/0x0/filters:format(jpeg)/pic697422.jpg"
+ },
+ {
+ "objectid": "292032",
+ "name": "Funkoverse Strategy Game: Harry Potter 100",
+ "yearpublished": "2019",
+ "my_rating": null,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "4",
+ "playingtime": "60"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/9JmZzK_PuB_1OT0YCpvlbg__original/img/KJPsSH6epyHV18r4DKcT5wMbMoY=/0x0/filters:format(png)/pic5008393.png"
+ },
+ {
+ "objectid": "174430",
+ "name": "Gloomhaven",
+ "yearpublished": "2017",
+ "my_rating": null,
+ "stats": {
+ "minplayers": "1",
+ "maxplayers": "4",
+ "playingtime": "120"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/sZYp_3BTDGjh2unaZfZmuA__original/img/7d-lj5Gd1e8PFnD97LYFah2c45M=/0x0/filters:format(jpeg)/pic2437871.jpg"
+ },
+ {
+ "objectid": "54138",
+ "name": "Imperial 2030",
+ "yearpublished": "2009",
+ "my_rating": null,
+ "stats": {
+ "minplayers": "2",
+ "maxplayers": "6",
+ "playingtime": "180"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/YSopSx1x5iea7qI21ogYBw__original/img/BTpxWEL57yDfISpu-WZdfzX6Di0=/0x0/filters:format(jpeg)/pic586346.jpg"
+ },
+ {
+ "objectid": "338353",
+ "name": "Soda Smugglers",
+ "yearpublished": "2022",
+ "my_rating": null,
+ "stats": {
+ "minplayers": "3",
+ "maxplayers": "8",
+ "playingtime": "20"
+ },
+ "comment": null,
+ "image": "https://cf.geekdo-images.com/zRdItFkzzgkuSn-Ix8PwWA__original/img/OyshAnPREylCv0ZfeYd2a7HiLS8=/0x0/filters:format(png)/pic6231245.png"
+ }
+]
\ No newline at end of file
diff --git a/templates/boardgames.html b/templates/boardgames.html
new file mode 100644
index 0000000..a238fc9
--- /dev/null
+++ b/templates/boardgames.html
@@ -0,0 +1,51 @@
+{% extends "after-dark/templates/page.html" %}
+{% block css %}
+{{ super() }}
+
+{% endblock css %}
+
+{% block content %}
+{% block header %}
+{{ super() }}
+{% endblock header %}
+
+
Board Games Collection
+
+ {% set data = load_data(path="static/boardgames_collection.json") %}
+ {% for game in data %}
+ {% set rating_class = "" %}
+ {% if game.my_rating %}
+ {% if game.my_rating >= 8 %}
+ {% set rating_class = "rating-high" %}
+ {% elif game.my_rating >= 6 %}
+ {% set rating_class = "rating-medium" %}
+ {% else %}
+ {% set rating_class = "rating-low" %}
+ {% endif %}
+ {% else %}
+ {% set rating_class = "rating-unrated" %}
+ {% endif %}
+
+
+
+ 👥 {{ game.stats.minplayers }}-{{ game.stats.maxplayers }} players
+ ⏱️ {{ game.stats.playingtime }} min
+
+
+ Rating: {% if game.my_rating %}{{ game.my_rating }}{% else %}N/A{% endif %}
+
+ {% if game.comment %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+{% endblock %}
\ No newline at end of file