diff --git a/templates/48/card.html.j2 b/templates/48/card.html.j2
new file mode 100644
index 0000000..f870750
--- /dev/null
+++ b/templates/48/card.html.j2
@@ -0,0 +1,43 @@
+
{{ name }}
+
+
+
+
+{{ description }}
+
+
+{% if procs %}
+procs:
+{% for proc in procs.splitlines() %}
+{{ proc.split("➡", 1)[0].strip() }}
+➡
+{{ proc.split("➡", 1)[1].strip() }}
+
+{% endfor %}
+{% endif %}
+
+{% if cp %}
+cp: {{ cp }}
+{% endif %}
+
+{% if range %}
+range: {{ range|int }}
+{% endif %}
+
+{% if power %}
+power: {{ power }}
+{% endif %}
+
+{% if playcost %}
+playcost:
+
+{% for cost in playcost.splitlines() %}
+- {{ cost.rstrip(",") }}
+{% endfor %}
+
+{% endif %}
+
+{% if duration %}
+duration:
+{{ duration }}
+{% endif %}
diff --git a/templates/48/card.scss b/templates/48/card.scss
new file mode 100644
index 0000000..8072ea3
--- /dev/null
+++ b/templates/48/card.scss
@@ -0,0 +1,5 @@
+.a {
+ .b {
+ color: red;
+ }
+}
diff --git a/templates/48/example.json b/templates/48/example.json
new file mode 100644
index 0000000..ec0afac
--- /dev/null
+++ b/templates/48/example.json
@@ -0,0 +1,569 @@
+[
+ {
+ "node": null,
+ "xpath": "/",
+ "data": null,
+ "content": "\n\n\tSome Attack!\n\tSpis meg\n\thttp://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png\n\t1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage\n\t10\n\t1\n\t\n\t\n\tATT\n\t2 ACT,\nBare hands or melee weapon equipped\n\t\n\t\n\t\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n",
+ "attributes": {}
+ },
+ {
+ "node": "ability_card",
+ "xpath": "/ability_card",
+ "data": "\n\n\tSome Attack!\n\tSpis meg\n\thttp://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png\n\t1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage\n\t10\n\t1\n\t\n\t\n\tATT\n\t2 ACT,\nBare hands or melee weapon equipped\n\t\n\t\n\t\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n",
+ "content": "Some Attack!\n\n\t\nSpis meg\n\n\t\nhttp://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png\n\n\t\n1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage\n\n\t\n10\n\n\t\n1\n\n\t\n\n\n\t\n\n\n\t\nATT\n\n\t\n2 ACT,\nBare hands or melee weapon equipped\n\n\t\n\n\n\t\n\n\n\t\n\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[1]",
+ "data": "\n\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "name",
+ "xpath": "/ability_card/name",templates/48/example.json
+ "data": "Some Attack!",
+ "content": "Some Attack!",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/name/text()",
+ "data": "Some Attack!",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[2]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "description",
+ "xpath": "/ability_card/description",
+ "data": "Spis meg",
+ "content": "Spis meg",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/description/text()",
+ "data": "Spis meg",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[3]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "image",
+ "xpath": "/ability_card/image",
+ "data": "http://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png",
+ "content": "http://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/image/text()",
+ "data": "http://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[4]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "procs",
+ "xpath": "/ability_card/procs",
+ "data": "1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage",
+ "content": "1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/procs/text()",
+ "data": "1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[5]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "cp",
+ "xpath": "/ability_card/cp",
+ "data": "10",
+ "content": "10",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/cp/text()",
+ "data": "10",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[6]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "range",
+ "xpath": "/ability_card/range",
+ "data": "1",
+ "content": "1",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/range/text()",
+ "data": "1",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[7]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "aoe",
+ "xpath": "/ability_card/aoe",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[8]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "duration",
+ "xpath": "/ability_card/duration",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[9]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "power",
+ "xpath": "/ability_card/power",
+ "data": "ATT",
+ "content": "ATT",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/power/text()",
+ "data": "ATT",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[10]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "playcost",
+ "xpath": "/ability_card/playcost",
+ "data": "2 ACT,\nBare hands or melee weapon equipped",
+ "content": "2 ACT,\nBare hands or melee weapon equipped",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/playcost/text()",
+ "data": "2 ACT,\nBare hands or melee weapon equipped",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[11]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "companionstats",
+ "xpath": "/ability_card/companionstats",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[12]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "companionequip",
+ "xpath": "/ability_card/companionequip",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[13]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "companiondefences",
+ "xpath": "/ability_card/companiondefences",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[14]",
+ "data": "\n\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[1]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "1",
+ "db_entry": "🧩 Set Ability Name"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[15]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[2]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "2",
+ "db_entry": "🧩 Set Description"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[16]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[3]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "1",
+ "db_entry": "🧩 Set Image"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[17]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[4]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "4",
+ "y": "7",
+ "db_entry": "🧩 Set Stat",
+ "stat": "KUK",
+ "value": "5"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[18]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[5]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "5",
+ "db_entry": "🧩 CP Cost"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[19]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[6]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "7",
+ "db_entry": "🧩 Range"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[20]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[7]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "8",
+ "db_entry": "🧩 AoE"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[21]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[8]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "6",
+ "db_entry": "🧩 Duration"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[22]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[9]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "4",
+ "y": "8",
+ "db_entry": "🧩 Power"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[23]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[10]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "5",
+ "db_entry": "🧩 Play Cost"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[24]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[11]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "7",
+ "db_entry": "🧩 Companion Stats"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[25]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[12]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "9",
+ "db_entry": "🧩 Companion Equipment"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[26]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[13]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "9",
+ "db_entry": "🧩 Companion Defences"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[27]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[14]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "5",
+ "y": "1",
+ "db_entry": "⚔️ Learn Ability"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[28]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[15]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "6",
+ "y": "1",
+ "db_entry": "⚔️ Melee Attack"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[29]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[16]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "7",
+ "y": "1",
+ "db_entry": "⚔️ Attack"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[30]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[17]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "8",
+ "y": "1",
+ "db_entry": "⚔️ White Hit Dice"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[31]",
+ "data": "\n\n",
+ "content": "",
+ "attributes": {}
+ }
+]
diff --git a/templates/48/example.sh b/templates/48/example.sh
new file mode 100755
index 0000000..f56385c
--- /dev/null
+++ b/templates/48/example.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+python pyscript.py
diff --git a/templates/48/js.py b/templates/48/js.py
new file mode 100644
index 0000000..2e7de5d
--- /dev/null
+++ b/templates/48/js.py
@@ -0,0 +1,10 @@
+# this file is a hack to allow loading exampledata when running locally
+import json
+from pathlib import Path
+HERE = Path(__file__).parent
+
+with (HERE / "example.json").open() as f:
+ CARD_XPATHS_ENTRIES = json.load(f)
+
+with (HERE / "card.html.j2").open() as f:
+ CARD_TEMPLATE = f.read()
diff --git a/templates/48/pyscript.py b/templates/48/pyscript.py
new file mode 100644
index 0000000..b0648e5
--- /dev/null
+++ b/templates/48/pyscript.py
@@ -0,0 +1,67 @@
+from dataclasses import dataclass
+from jinja2 import Environment, BaseLoader, Template, StrictUndefined
+from js import CARD_XPATHS_ENTRIES, CARD_TEMPLATE
+from typing import Optional
+import jinja2
+
+@dataclass
+class Entry:
+ node : Optional[str]
+ xpath : str
+ content : Optional[str]
+ attributes : dict
+
+# breaks because firefox and chrome normalizes html tags inside py-script
+CARD_XPATHS_ENTRIES_JSON = r"""
+##JSON:://*##
+"""
+#import json
+#CARD_XPATHS_ENTRIES = json.loads(CARD_XPATHS_ENTRIES_JSON)
+
+# time to abuse the JsProxy objects!
+XPATH_MAP = {}
+for entry in CARD_XPATHS_ENTRIES:
+ node = entry.node if hasattr(entry, "node") else entry["node"]
+ xpath = entry.xpath if hasattr(entry, "xpath") else entry["xpath"]
+ content = entry.content if hasattr(entry, "content") else entry["content"]
+ attributes = entry.attributes if hasattr(entry, "attributes") else entry["attributes"]
+
+ if node is None and not content:
+ assert "text()" in xpath
+ continue
+
+ XPATH_MAP[xpath] = Entry(node, xpath, content, attributes)
+
+def get_all(xpath) -> Entry:
+ xpath = f"/ability_card/{xpath}"
+ for key, entry in XPATH_MAP.items():
+ if key.startswith(xpath):
+ yield entry
+
+def get(xpath) -> Entry:
+ xpath = f"/ability_card/{xpath}"
+ if xpath in XPATH_MAP:
+ return XPATH_MAP[xpath]
+ assert len(list(get_all(xpath))) == 0
+
+#print(*sorted( f"{k} - {v.node} - {len(v.content)}" for k, v in XPATH_MAP.items() ), sep="\n")
+
+print(
+ Environment(
+ trim_blocks = True,
+ lstrip_blocks = True,
+ loader = BaseLoader,
+ autoescape = True,
+ undefined = StrictUndefined,
+ )
+ .from_string(CARD_TEMPLATE)
+ .render(
+ get = get,
+ get_all = get_all,
+ **{
+ v.node: v.content
+ for k, v in XPATH_MAP.items()
+ if v.node is not None and k == f"/ability_card/{v.node}"
+ }
+ )
+)
diff --git a/templates/48/render.sh b/templates/48/render.sh
new file mode 100755
index 0000000..63fa721
--- /dev/null
+++ b/templates/48/render.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+
+card_pyscript="$(cat pyscript.py)" \
+card_css_style="$(sass --style expanded card.scss)" \
+card_jinja2_template="$(cat card.html.j2)" \
+j2 style.html.j2 --filters ../../filters.py --customize ../../environ.py
diff --git a/templates/48/style.html.j2 b/templates/48/style.html.j2
new file mode 100644
index 0000000..0336e87
--- /dev/null
+++ b/templates/48/style.html.j2
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+#packages = [ "jinja2" ]
+packages = [
+ "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/Jinja2-3.1.2-py3-none-any.whl",
+ "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/MarkupSafe-2.1.1-cp310-cp310-emscripten_3_1_14_wasm32.whl",
+ "pyyaml"
+]
+
+autoclose_loader = true
+[[runtime]]
+name = "pyodide-0.21.2"
+lang = "python"
+src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
+
+
+
+
+
+
+
+{##}
+{{ card_pyscript | safe }}
+{##}
+
diff --git a/templates/49/.gitignore b/templates/49/.gitignore
new file mode 100644
index 0000000..a9b203a
--- /dev/null
+++ b/templates/49/.gitignore
@@ -0,0 +1 @@
+main.js
diff --git a/templates/49/example.json b/templates/49/example.json
new file mode 100644
index 0000000..8d0c09e
--- /dev/null
+++ b/templates/49/example.json
@@ -0,0 +1,569 @@
+[
+ {
+ "node": null,
+ "xpath": "/",
+ "data": null,
+ "content": "\n\n\tSome Attack!\n\tSpis meg\n\thttp://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png\n\t1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage\n\t10\n\t1\n\t\n\t\n\tATT\n\t2 ACT,\nBare hands or melee weapon equipped\n\t\n\t\n\t\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n",
+ "attributes": {}
+ },
+ {
+ "node": "ability_card",
+ "xpath": "/ability_card",
+ "data": "\n\n\tSome Attack!\n\tSpis meg\n\thttp://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png\n\t1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage\n\t10\n\t1\n\t\n\t\n\tATT\n\t2 ACT,\nBare hands or melee weapon equipped\n\t\n\t\n\t\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n",
+ "content": "Some Attack!\n\n\t\nSpis meg\n\n\t\nhttp://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png\n\n\t\n1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage\n\n\t\n10\n\n\t\n1\n\n\t\n\n\n\t\n\n\n\t\nATT\n\n\t\n2 ACT,\nBare hands or melee weapon equipped\n\n\t\n\n\n\t\n\n\n\t\n\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n\n\n\t\n",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[1]",
+ "data": "\n\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "name",
+ "xpath": "/ability_card/name",
+ "data": "Some Attack!",
+ "content": "Some Attack!",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/name/text()",
+ "data": "Some Attack!",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[2]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "description",
+ "xpath": "/ability_card/description",
+ "data": "Spis meg",
+ "content": "Spis meg",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/description/text()",
+ "data": "Spis meg",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[3]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "image",
+ "xpath": "/ability_card/image",
+ "data": "http://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png",
+ "content": "http://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/image/text()",
+ "data": "http://www.pvv.ntnu.no/~pederbs/cards/img/data/piuy/awe.png",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[4]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "procs",
+ "xpath": "/ability_card/procs",
+ "data": "1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage",
+ "content": "1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/procs/text()",
+ "data": "1 🗡️🗡️🗡️ ➡ 3 Damage\n1 🗡️🗡️🗡️🗡️ ➡ 5 Damage",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[5]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "cp",
+ "xpath": "/ability_card/cp",
+ "data": "10",
+ "content": "10",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/cp/text()",
+ "data": "10",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[6]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "range",
+ "xpath": "/ability_card/range",
+ "data": "1",
+ "content": "1",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/range/text()",
+ "data": "1",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[7]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "aoe",
+ "xpath": "/ability_card/aoe",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[8]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "duration",
+ "xpath": "/ability_card/duration",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[9]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "power",
+ "xpath": "/ability_card/power",
+ "data": "ATT",
+ "content": "ATT",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/power/text()",
+ "data": "ATT",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[10]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "playcost",
+ "xpath": "/ability_card/playcost",
+ "data": "2 ACT,\nBare hands or melee weapon equipped",
+ "content": "2 ACT,\nBare hands or melee weapon equipped",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/playcost/text()",
+ "data": "2 ACT,\nBare hands or melee weapon equipped",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[11]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "companionstats",
+ "xpath": "/ability_card/companionstats",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[12]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "companionequip",
+ "xpath": "/ability_card/companionequip",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[13]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "companiondefences",
+ "xpath": "/ability_card/companiondefences",
+ "data": "",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[14]",
+ "data": "\n\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[1]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "1",
+ "db_entry": "🧩 Set Ability Name"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[15]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[2]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "2",
+ "db_entry": "🧩 Set Description"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[16]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[3]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "1",
+ "db_entry": "🧩 Set Image"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[17]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[4]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "4",
+ "y": "7",
+ "db_entry": "🧩 Set Stat",
+ "stat": "KUK",
+ "value": "5"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[18]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[5]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "5",
+ "db_entry": "🧩 CP Cost"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[19]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[6]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "7",
+ "db_entry": "🧩 Range"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[20]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[7]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "8",
+ "db_entry": "🧩 AoE"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[21]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[8]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "6",
+ "db_entry": "🧩 Duration"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[22]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[9]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "4",
+ "y": "8",
+ "db_entry": "🧩 Power"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[23]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[10]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "5",
+ "db_entry": "🧩 Play Cost"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[24]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[11]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "7",
+ "db_entry": "🧩 Companion Stats"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[25]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[12]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "1",
+ "y": "9",
+ "db_entry": "🧩 Companion Equipment"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[26]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[13]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "3",
+ "y": "9",
+ "db_entry": "🧩 Companion Defences"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[27]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[14]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "5",
+ "y": "1",
+ "db_entry": "⚔️ Learn Ability"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[28]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[15]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "6",
+ "y": "1",
+ "db_entry": "⚔️ Melee Attack"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[29]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[16]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "7",
+ "y": "1",
+ "db_entry": "⚔️ Attack"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[30]",
+ "data": "\n\t",
+ "content": "",
+ "attributes": {}
+ },
+ {
+ "node": "component",
+ "xpath": "/ability_card/component[17]",
+ "data": "",
+ "content": "",
+ "attributes": {
+ "x": "8",
+ "y": "1",
+ "db_entry": "⚔️ White Hit Dice"
+ }
+ },
+ {
+ "node": null,
+ "xpath": "/ability_card/text()[31]",
+ "data": "\n\n",
+ "content": "",
+ "attributes": {}
+ }
+]
diff --git a/templates/49/example.sh b/templates/49/example.sh
new file mode 100755
index 0000000..56a4c6a
--- /dev/null
+++ b/templates/49/example.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+
+set -eo pipefail
+
+./render.sh development | sd --string-mode "##JSON:://.##" "$(cat example.json)"
diff --git a/templates/49/main.nim b/templates/49/main.nim
new file mode 100644
index 0000000..8def725
--- /dev/null
+++ b/templates/49/main.nim
@@ -0,0 +1,69 @@
+import dom
+import json
+import nimja/parser
+import sequtils
+import strutils
+import strformat
+import tables
+import sugar
+#import jscore
+
+# XPATH JSON stuff
+
+type Entry = object
+ node : string
+ xpath : string
+ content : string
+ attributes : Table[string, string]
+
+let entries = parseJson($document.getElementById("json_data").innerText).to(seq[Entry])
+ #.map do (entry: Entry ) -> Entry: entry. TODO: strip content
+ .filter do (entry: Entry) -> bool:
+ not entry.xpath.contains("text()") or entry.content.strip() != ""
+
+proc get(key: string): Entry =
+ for entry in entries:
+ if entry.xpath == fmt"/ability_card/{key}":
+ return entry
+ assert false
+
+#proc get_first(path: str): Entry =
+# for entry in entries:
+# if entry
+
+type Component = object
+ key : string # db_entry
+ x : int
+ y : int
+ attrs : Table[string, string]
+
+var components: seq[Component] = @[]
+for entry in entries:
+ if entry.node == "component" and entry.xpath.startswith("/ability_card/component["):
+ var attrs = entry.attributes
+ attrs.del("x")
+ attrs.del("y")
+ attrs.del("db_entry")
+ components.add Component(
+ x : entry.attributes["x"].parseInt(),
+ y : entry.attributes["y"].parseInt(),
+ key : entry.attributes["db_entry"],
+ attrs : attrs,
+ )
+
+# YAML stuff
+
+
+
+proc renderIndex(
+ title : string,
+ entries : seq[Entry],
+ get : (string) -> Entry,
+ ): string =
+ compileTemplateFile(getScriptDir() / "main.nimja", )
+
+document.write renderIndex(
+ "index",
+ entries,
+ get,
+)
diff --git a/templates/49/main.nimble b/templates/49/main.nimble
new file mode 100644
index 0000000..72f1fc0
--- /dev/null
+++ b/templates/49/main.nimble
@@ -0,0 +1,15 @@
+# Package
+
+version = "0.1.0"
+author = "Peder Bergebakken Sundt"
+description = "A new awesome nimble package"
+license = "BSD-3-Clause"
+srcDir = "."
+bin = @["main"]
+
+
+# Dependencies
+
+requires "nim >= 1.6.6"
+requires "nimja"
+requires "yaml"
diff --git a/templates/49/main.nimja b/templates/49/main.nimja
new file mode 100644
index 0000000..66c216e
--- /dev/null
+++ b/templates/49/main.nimja
@@ -0,0 +1,25 @@
+{##}
+
+{% for entry in entries %}
+ -
+ {{ ($entry).replace("\\", "\\\\") }}
+{% endfor %}
+
+{##}
+
+
+ {{ get("name").content }}
+
+
+
+
+
+
+{% for (idx, component) in components.pairs %}
+ -
+ {{ component.x }}
+ {{ component.y }}
+ {{ component.key }}
+ {{ component.attrs }}
+{% endfor %}
+
diff --git a/templates/49/render.sh b/templates/49/render.sh
new file mode 100755
index 0000000..1b11b91
--- /dev/null
+++ b/templates/49/render.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+set -eo pipefail
+
+if test "main.js" -ot "main.nim" \
+|| test "main.js" -ot "main.nimle" \
+|| test "main.js" -ot "main.nimja"
+then
+ >&2 nimble js main -d:${1-release}
+fi
+
+j2 -f json style.html.j2 <({
+ echo "card_js = $(cat main.js | jq -s --raw-input);"
+ echo "card_css_style = $(sass --style expanded style.scss | jq -s --raw-input);"
+ } | gron -u) --filters ../../filters.py --customize ../../environ.py
diff --git a/templates/49/style.html.j2 b/templates/49/style.html.j2
new file mode 100644
index 0000000..2b4846f
--- /dev/null
+++ b/templates/49/style.html.j2
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/templates/49/style.scss b/templates/49/style.scss
new file mode 100644
index 0000000..8072ea3
--- /dev/null
+++ b/templates/49/style.scss
@@ -0,0 +1,5 @@
+.a {
+ .b {
+ color: red;
+ }
+}