From ec65fafa1a04a7f120cde4c83b32d7d4f62e6045 Mon Sep 17 00:00:00 2001
From: prescientmoon <git@moonythm.dev>
Date: Thu, 31 Oct 2024 18:43:13 +0100
Subject: [PATCH] Generate multiple pages & metadata collection

Signed-off-by: prescientmoon <git@moonythm.dev>
---
 .gitignore                           |   4 +-
 Cargo.lock                           | 624 +++------------------------
 Cargo.toml                           |   7 +-
 build.py                             |  16 -
 content/index.dj                     |  14 +
 content/{ => posts}/arcaea.dj        |   2 +-
 content/posts/games.dj               |   1 +
 content/posts/the-realm-s-secrets.dj |  15 +
 flake.nix                            |   3 +-
 public/styles.css                    |  23 +-
 src/html.rs                          |  79 +---
 src/main.rs                          |  93 ++--
 src/metadata.rs                      | 138 ++++++
 src/template.rs                      |  39 +-
 src/tex.rs                           |  32 --
 15 files changed, 352 insertions(+), 738 deletions(-)
 delete mode 100755 build.py
 create mode 100644 content/index.dj
 rename content/{ => posts}/arcaea.dj (99%)
 create mode 100644 content/posts/games.dj
 create mode 100644 content/posts/the-realm-s-secrets.dj
 create mode 100644 src/metadata.rs
 delete mode 100644 src/tex.rs

diff --git a/.gitignore b/.gitignore
index ea8c4bf..09b9279 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-/target
+target
+dist
+oldicons
diff --git a/Cargo.lock b/Cargo.lock
index 6124af0..850a188 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,48 +2,12 @@
 # It is not intended for manual editing.
 version = 3
 
-[[package]]
-name = "ahash"
-version = "0.8.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[package]]
-name = "allocator-api2"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
-
 [[package]]
 name = "anyhow"
 version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
 
-[[package]]
-name = "autocfg"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
-
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
-
 [[package]]
 name = "bumpalo"
 version = "3.16.0"
@@ -51,193 +15,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
-name = "byteorder"
-version = "1.5.0"
+name = "equivalent"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
-[[package]]
-name = "cached"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4d73155ae6b28cf5de4cfc29aeb02b8a1c6dab883cb015d15cd514e42766846"
-dependencies = [
- "ahash",
- "cached_proc_macro",
- "cached_proc_macro_types",
- "directories",
- "hashbrown",
- "once_cell",
- "rmp-serde",
- "serde",
- "sled",
- "thiserror",
- "web-time",
-]
-
-[[package]]
-name = "cached_proc_macro"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
-dependencies = [
- "darling",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "cached_proc_macro_types"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "crc32fast"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.9.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
-dependencies = [
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
-
-[[package]]
-name = "darling"
-version = "0.20.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
-dependencies = [
- "darling_core",
- "darling_macro",
-]
-
-[[package]]
-name = "darling_core"
-version = "0.20.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
-dependencies = [
- "fnv",
- "ident_case",
- "proc-macro2",
- "quote",
- "strsim",
- "syn",
-]
-
-[[package]]
-name = "darling_macro"
-version = "0.20.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
-dependencies = [
- "darling_core",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "directories"
-version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
-dependencies = [
- "dirs-sys",
-]
-
-[[package]]
-name = "dirs-sys"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
-dependencies = [
- "libc",
- "option-ext",
- "redox_users",
- "windows-sys",
-]
-
-[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
-[[package]]
-name = "fs2"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "fxhash"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-dependencies = [
- "byteorder",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "hashbrown"
-version = "0.14.5"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-dependencies = [
- "ahash",
- "allocator-api2",
-]
+checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
 
 [[package]]
-name = "ident_case"
-version = "1.0.1"
+name = "indexmap"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
-
-[[package]]
-name = "instant"
-version = "0.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
 dependencies = [
- "cfg-if",
+ "equivalent",
+ "hashbrown",
 ]
 
 [[package]]
@@ -247,63 +43,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19fa1db3b285830176c8a940d4e3376679772bee9f58a83dd9285a4a54e30f6e"
 
 [[package]]
-name = "js-sys"
-version = "0.3.72"
+name = "memchr"
+version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
-dependencies = [
- "wasm-bindgen",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.159"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
-
-[[package]]
-name = "libredox"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
-dependencies = [
- "bitflags 2.6.0",
- "libc",
-]
-
-[[package]]
-name = "lock_api"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "moonythm"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cached",
  "jotdown",
+ "once_cell",
  "pulldown-latex",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
+ "serde",
+ "toml",
 ]
 
 [[package]]
@@ -312,48 +66,11 @@ version = "1.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
 
-[[package]]
-name = "option-ext"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-
-[[package]]
-name = "parking_lot"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
-dependencies = [
- "instant",
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
-dependencies = [
- "cfg-if",
- "instant",
- "libc",
- "redox_syscall",
- "smallvec",
- "winapi",
-]
-
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
 [[package]]
 name = "proc-macro2"
-version = "1.0.87"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
+checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
 dependencies = [
  "unicode-ident",
 ]
@@ -376,68 +93,20 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
-dependencies = [
- "bitflags 1.3.2",
-]
-
-[[package]]
-name = "redox_users"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
-dependencies = [
- "getrandom",
- "libredox",
- "thiserror",
-]
-
-[[package]]
-name = "rmp"
-version = "0.8.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
-dependencies = [
- "byteorder",
- "num-traits",
- "paste",
-]
-
-[[package]]
-name = "rmp-serde"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
-dependencies = [
- "byteorder",
- "rmp",
- "serde",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
 [[package]]
 name = "serde"
-version = "1.0.210"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.210"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -445,38 +114,19 @@ dependencies = [
 ]
 
 [[package]]
-name = "sled"
-version = "0.34.7"
+name = "serde_spanned"
+version = "0.6.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
 dependencies = [
- "crc32fast",
- "crossbeam-epoch",
- "crossbeam-utils",
- "fs2",
- "fxhash",
- "libc",
- "log",
- "parking_lot",
+ "serde",
 ]
 
-[[package]]
-name = "smallvec"
-version = "1.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
-
-[[package]]
-name = "strsim"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-
 [[package]]
 name = "syn"
-version = "2.0.79"
+version = "2.0.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
+checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -484,23 +134,37 @@ dependencies = [
 ]
 
 [[package]]
-name = "thiserror"
-version = "1.0.64"
+name = "toml"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
 dependencies = [
- "thiserror-impl",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
 ]
 
 [[package]]
-name = "thiserror-impl"
-version = "1.0.64"
+name = "toml_datetime"
+version = "0.6.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
 ]
 
 [[package]]
@@ -510,186 +174,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
 
 [[package]]
-name = "version_check"
-version = "0.9.5"
+name = "winnow"
+version = "0.6.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
-[[package]]
-name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
 dependencies = [
- "cfg-if",
- "once_cell",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
-dependencies = [
- "bumpalo",
- "log",
- "once_cell",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
-
-[[package]]
-name = "web-time"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "zerocopy"
-version = "0.7.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.7.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "memchr",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 0c1188c..493c17a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,11 @@ edition = "2021"
 
 [dependencies]
 anyhow = "1.0.89"
-cached = { version = "0.53.1", features = ["disk_store"] }
 jotdown = "0.6.0"
 pulldown-latex = "0.7.0"
+serde = { version = "1.0.214", features = ["derive"] }
+toml = "0.8.19"
+
+# Waiting for https://github.com/rust-lang/rust/issues/109737
+# before switching to the std version.
+once_cell = "1.20.2"
diff --git a/build.py b/build.py
deleted file mode 100755
index ab6538a..0000000
--- a/build.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env nix-shell
-#!nix-shell -p python3 -i python3
-import shutil
-import subprocess
-
-shutil.rmtree("dist", ignore_errors=True)
-shutil.copytree("public", "dist")
-
-with open("dist/index.html", "r") as file:
-    template = file.read()
-
-output = subprocess.check_output("cargo run", shell=True).decode("utf-8")
-result = template.replace("$CONTENT", output)
-
-with open("dist/index.html", "w") as file:
-    file.write(result)
diff --git a/content/index.dj b/content/index.dj
new file mode 100644
index 0000000..0c70dcb
--- /dev/null
+++ b/content/index.dj
@@ -0,0 +1,14 @@
+# Hiii
+
+Welcome to my ethereal realm (read: website). This place is still being conjured (read: under construction), so some things might be startlingly empty. Enjoy your stay on the moon!
+
+I'm known as `prescientmoon`, or sometimes `PGW`. I study mathematics, but I also really like coding and all computer-related sorcery. I love games of all kinds, although rhythm games have a special place in my heart.
+
+{ title="Contact me" character="canyou" }
+::: aside
+You can message me in the following places \^-\^
+
+- via email at `hi@thisdomain`
+- on discord as `@prescientmoon`
+- on the ~ IRC network as `prescientmoon`
+:::
diff --git a/content/arcaea.dj b/content/posts/arcaea.dj
similarity index 99%
rename from content/arcaea.dj
rename to content/posts/arcaea.dj
index eabee96..22533ea 100644
--- a/content/arcaea.dj
+++ b/content/posts/arcaea.dj
@@ -1,5 +1,5 @@
 ``` =toml
-created-at: 2024-10-31 15:28
+created-at = "2024-10-31T15:28:OO"
 ```
 
 # Why I love arcaea
diff --git a/content/posts/games.dj b/content/posts/games.dj
new file mode 100644
index 0000000..e909b6c
--- /dev/null
+++ b/content/posts/games.dj
@@ -0,0 +1 @@
+# My game tier list
diff --git a/content/posts/the-realm-s-secrets.dj b/content/posts/the-realm-s-secrets.dj
new file mode 100644
index 0000000..7547412
--- /dev/null
+++ b/content/posts/the-realm-s-secrets.dj
@@ -0,0 +1,15 @@
+# The realm's secrets
+
+## Djot (why not markdown?)
+- extensionability
+- writing my own html generator
+- templating
+- metadata
+
+## Hosting
+- hosted on my nixos server
+- there's interesting stuff happening there too, but that deserves it's own post
+
+## LaTeX
+- tried using `cached` with `latexmlmath`, but results were slow.
+- ended up using the `pulldown-latex` crate.
diff --git a/flake.nix b/flake.nix
index 6c93e8d..b275b7b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -16,8 +16,7 @@
               pkgs.clippy
               pkgs.rust-analyzer
               pkgs.rustfmt
-              pkgs.ruff
-              pkgs.perl538Packages.LaTeXML
+              pkgs.imagemagick
             ];
 
             buildInputs = with pkgs; [ ];
diff --git a/public/styles.css b/public/styles.css
index 1280f81..4e85f9f 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -6,6 +6,7 @@ html {
   max-width: 70ch;
   margin-left: auto;
   margin-right: auto;
+  padding: 1em;
 }
 
 blockquote {
@@ -28,19 +29,11 @@ ol {
   align-items: center;
 }
 
-.aside-summary > .aside-title {
-  text-decoration: underline;
-}
-
-.aside-summary > * {
-  padding: 0;
-  margin: 0;
-}
-
 img.aside-icon {
+  /* display: inline-block; */
   height: 1.75rem;
   margin-right: 0.5rem;
-  transform: translateY(-2px);
+  /* transform: translateY(-100px); */
 }
 
 .aside {
@@ -53,6 +46,7 @@ img.aside-icon {
 .aside-summary {
   display: inline-flex;
   align-items: center;
+  box-sizing: border-box;
 }
 
 .aside-summary::marker {
@@ -66,6 +60,15 @@ img.aside-icon {
   box-sizing: border-box;
 }
 
+.aside-summary > .aside-title {
+  text-decoration: underline;
+}
+
+.aside-summary > * {
+  padding: 0;
+  margin: 0;
+}
+
 .aside[open] .aside_summary:before {
   content: "▼";
 }
diff --git a/src/html.rs b/src/html.rs
index 42df1b5..eb8a0bd 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -12,66 +12,26 @@ use jotdown::OrderedListNumbering::*;
 use jotdown::SpanLinkType;
 
 use crate::template;
-use crate::template::Template;
 use crate::template::TemplateRenderer;
 
 // {{{ Renderer
-/// Render events into a string.
-pub fn render_to_string<'s, I>(events: I) -> anyhow::Result<String>
-where
-	I: Iterator<Item = Event<'s>>,
-{
-	let mut s = String::new();
-	Renderer::new()?.push(events, &mut s)?;
-	Ok(s)
-}
+/// Render djot content as HTML.
+pub fn render_html<'s>(
+	mut events: impl Iterator<Item = Event<'s>>,
+	mut out: impl std::fmt::Write,
+) -> anyhow::Result<()> {
+	let mut w = Writer::new();
+	events.try_for_each(|e| w.render_event(&e, &mut out))?;
+	w.render_epilogue(&mut out)?;
 
-/// [`Render`] implementor that writes HTML output.
-#[derive(Clone, Debug)]
-pub struct Renderer {
-	templates: BuiltinTempaltes,
-}
-
-impl Renderer {
-	pub fn new() -> anyhow::Result<Self> {
-		Ok(Self {
-			templates: BuiltinTempaltes::new()?,
-		})
-	}
-
-	pub fn push<'s>(
-		&self,
-		mut events: impl Iterator<Item = Event<'s>>,
-		mut out: impl std::fmt::Write,
-	) -> anyhow::Result<()> {
-		let mut w = Writer::new(self);
-		events.try_for_each(|e| w.render_event(&e, &mut out))?;
-		w.render_epilogue(&mut out)?;
-
-		Ok(())
-	}
-}
-// }}}
-// {{{ Bring in templates
-#[derive(Clone, Debug)]
-struct BuiltinTempaltes {
-	aside_template: Template,
-}
-
-impl BuiltinTempaltes {
-	fn new() -> anyhow::Result<Self> {
-		Ok(BuiltinTempaltes {
-			aside_template: template!("./templates/aside.html")?,
-		})
-	}
+	Ok(())
 }
 // }}}
 
-struct Writer<'s> {
+pub struct Writer<'s> {
 	list_tightness: Vec<bool>,
 	states: Vec<State<'s>>,
 	footnotes: Footnotes<'s>,
-	renderer: &'s Renderer,
 }
 
 #[derive(Debug, Clone)]
@@ -84,20 +44,20 @@ enum State<'s> {
 }
 
 impl<'s> Writer<'s> {
-	fn new(renderer: &'s Renderer) -> Self {
+	pub fn new() -> Self {
 		Self {
 			list_tightness: Vec::new(),
 			states: Vec::new(),
 			footnotes: Footnotes::default(),
-			renderer,
 		}
 	}
 
 	#[allow(clippy::single_match)]
-	fn render_event<W>(&mut self, e: &Event<'s>, mut out: W) -> anyhow::Result<()>
-	where
-		W: std::fmt::Write,
-	{
+	pub fn render_event(
+		&mut self,
+		e: &Event<'s>,
+		mut out: impl std::fmt::Write,
+	) -> anyhow::Result<()> {
 		// {{{ Handle footnotes
 		if let Event::Start(Container::Footnote { label }, ..) = e {
 			self.footnotes.start(label, Vec::new());
@@ -157,7 +117,7 @@ impl<'s> Writer<'s> {
 				}
 
 				match &c {
-					Container::RawBlock { .. } => unreachable!(),
+					Container::RawBlock { .. } => {}
 					Container::RawInline { .. } => unreachable!(),
 					Container::Footnote { .. } => unreachable!(),
 					// {{{ List
@@ -226,7 +186,7 @@ impl<'s> Writer<'s> {
 						})?;
 
 						let mut renderer =
-							TemplateRenderer::new(&self.renderer.templates.aside_template);
+							TemplateRenderer::new(template!("templates/aside.html")?);
 
 						while let Some(label) = renderer.current(&mut out)? {
 							if label == "character" {
@@ -378,7 +338,7 @@ impl<'s> Writer<'s> {
 				}
 
 				match c {
-					Container::RawBlock { .. } => unreachable!(),
+					Container::RawBlock { .. } => {}
 					Container::RawInline { .. } => unreachable!(),
 					Container::Footnote { .. } => unreachable!(),
 					// {{{ List
@@ -463,7 +423,6 @@ impl<'s> Writer<'s> {
 				Some(State::TextOnly) => write_attr(s, &mut out)?,
 				Some(State::Raw) => out.write_str(s)?,
 				Some(State::Math(display)) => {
-					// let string: String = format!("{}{s}{}", delim, delim);
 					let config = pulldown_latex::RenderConfig {
 						display_mode: {
 							use pulldown_latex::config::DisplayMode::*;
diff --git a/src/main.rs b/src/main.rs
index 486632f..595e76f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,15 @@
+use std::fs::{self};
 use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::str::FromStr;
 
-use anyhow::Context;
-use html::Renderer;
+use anyhow::{anyhow, Context};
+use html::render_html;
 use template::TemplateRenderer;
 
 mod html;
+mod metadata;
 mod template;
-mod tex;
 
 fn copy_recursively(from: &Path, to: &Path) -> anyhow::Result<()> {
 	Command::new("cp").arg("-r").arg(from).arg(to).output()?;
@@ -16,9 +17,68 @@ fn copy_recursively(from: &Path, to: &Path) -> anyhow::Result<()> {
 	Ok(())
 }
 
+// {{{ Generate single page
+fn generate_page(path: &Path) -> anyhow::Result<()> {
+	let content_path = PathBuf::from_str("content")?.join(path);
+
+	let djot_input = std::fs::read_to_string(content_path).unwrap();
+	let mut out = String::new();
+
+	let page_template = template!("templates/page.html")?;
+	let mut page_renderer = TemplateRenderer::new(page_template);
+
+	// let events = jotdown::Parser::new(&djot_input);
+	// let meta = PageMetadata::new(events)?;
+	// println!("Metadata: {meta:?}");
+
+	while let Some(label) = page_renderer.next(&mut out)? {
+		if label == "content" {
+			let events = jotdown::Parser::new(&djot_input);
+			render_html(events, &mut out)?;
+		} else {
+			break;
+		}
+	}
+
+	page_renderer.finish(&mut out)?;
+
+	let mut out_path = PathBuf::from_str("dist")?.join(path);
+	out_path.set_file_name(format!(
+		"{}.html",
+		path.file_stem()
+			.ok_or_else(|| anyhow!("Empty filestem encountered"))?
+			.to_str()
+			.unwrap()
+	));
+	std::fs::write(out_path, out).with_context(|| "Failed to write `arcaea.html` post")?;
+
+	Ok(())
+}
+// }}}
+// {{{ Generate an entire directory
+fn generate_dir(path: &Path) -> anyhow::Result<()> {
+	let content_path = PathBuf::from_str("content")?.join(path);
+	let out_path = PathBuf::from_str("dist")?.join(path);
+	fs::create_dir_all(out_path)
+		.with_context(|| format!("Could not generate directory {path:?}"))?;
+
+	for file in fs::read_dir(content_path)? {
+		let file_path = file?.path();
+		let path = path.join(file_path.file_name().unwrap());
+		if file_path.is_dir() {
+			generate_dir(&path)?;
+		} else {
+			generate_page(&path)?;
+		}
+	}
+
+	Ok(())
+}
+// }}}
+
 fn main() -> anyhow::Result<()> {
-	let dist_path = PathBuf::from_str("dist")?;
 	let public_path = PathBuf::from_str("public")?;
+	let dist_path = PathBuf::from_str("dist")?;
 
 	if dist_path.exists() {
 		std::fs::remove_dir_all(&dist_path).with_context(|| "Cannot delete `dist` directory")?;
@@ -31,30 +91,7 @@ fn main() -> anyhow::Result<()> {
 			.with_context(|| "Cannot copy `public` -> `dist`")?;
 	}
 
-	// {{{ Generate contents
-	let djot_input = std::fs::read_to_string("content/arcaea.dj").unwrap();
-	let mut out = String::new();
-	let page_template = template!("templates/page.html")?;
-	let mut page_renderer = TemplateRenderer::new(&page_template);
-
-	while let Some(label) = page_renderer.next(&mut out)? {
-		if label == "content" {
-			let events = jotdown::Parser::new(&djot_input);
-			let html = Renderer::new()?;
-			html.push(events, &mut out)?;
-		} else {
-			break;
-		}
-	}
-
-	page_renderer.finish(&mut out)?;
-	// }}}
-
-	let posts_dir = dist_path.join("posts");
-	std::fs::create_dir(&posts_dir).with_context(|| "Cannot create `dist/posts` directory")?;
-
-	std::fs::write(posts_dir.join("arcaea.html"), out)
-		.with_context(|| "Failed to write `arcaea.html` post")?;
+	generate_dir(&PathBuf::new())?;
 
 	Ok(())
 }
diff --git a/src/metadata.rs b/src/metadata.rs
new file mode 100644
index 0000000..4515250
--- /dev/null
+++ b/src/metadata.rs
@@ -0,0 +1,138 @@
+#![allow(dead_code)]
+use std::fmt::Write;
+
+use anyhow::{anyhow, bail, Context};
+use jotdown::{Container, Event};
+use serde::Deserialize;
+
+use crate::html;
+
+#[derive(Deserialize, Debug, Default)]
+pub struct PageConfig {
+	created_at: Option<String>,
+}
+
+impl PageConfig {
+	/// Merge another config into the current one. Might error out on duplicate values.
+	fn merge(&mut self, other: PageConfig) -> anyhow::Result<()> {
+		match &self.created_at {
+			None => self.created_at = other.created_at,
+			Some(first_created) => {
+				if let Some(second_created) = other.created_at {
+					if second_created != *first_created {
+						bail!("Conflicting values for `created_at` page attribute: {first_created} and {second_created}");
+					}
+				}
+			}
+		};
+
+		Ok(())
+	}
+}
+
+#[derive(Debug)]
+pub struct PageMetadata {
+	title: Heading,
+	config: PageConfig,
+	toc: Vec<Heading>,
+}
+
+impl PageMetadata {
+	pub fn new<'s>(mut events: impl Iterator<Item = Event<'s>>) -> anyhow::Result<Self> {
+		let mut w = Writer::new();
+		events.try_for_each(|e| w.render_event(&e))?;
+
+		let title = w
+			.toc
+			.first()
+			.ok_or_else(|| anyhow!("No heading found to infer title from"))?;
+
+		Ok(Self {
+			title: title.to_owned(),
+			config: w.config,
+			toc: w.toc,
+		})
+	}
+}
+
+#[derive(Debug, Clone)]
+struct Heading {
+	pub level: u8,
+	pub id: String,
+	pub text: String,
+	pub html: String,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum State {
+	Toplevel,
+	Heading,
+	Toml,
+}
+
+struct Writer<'s> {
+	html_renderer: html::Writer<'s>,
+	config: PageConfig,
+	toc: Vec<Heading>,
+	toml_text: String,
+	state: State,
+}
+
+impl<'s> Writer<'s> {
+	fn new() -> Self {
+		Self {
+			html_renderer: html::Writer::new(),
+			config: PageConfig::default(),
+			toc: Vec::new(),
+			toml_text: String::new(),
+			state: State::Toplevel,
+		}
+	}
+
+	fn render_event(&mut self, e: &jotdown::Event<'s>) -> anyhow::Result<()> {
+		match e {
+			Event::Start(Container::Heading { level, id, .. }, _) => {
+				assert_eq!(self.state, State::Toplevel);
+				self.state = State::Heading;
+				self.toc.push(Heading {
+					level: *level as u8,
+					id: id.to_string(),
+					text: String::new(),
+					html: String::new(),
+				})
+			}
+			Event::End(Container::Heading { .. }) => {
+				assert_eq!(self.state, State::Heading);
+				self.state = State::Toplevel;
+			}
+			Event::Start(Container::RawBlock { format: "toml" }, _) => {
+				assert_eq!(self.state, State::Toplevel);
+				self.state = State::Toml
+			}
+			Event::End(Container::RawBlock { format: "toml" }) => {
+				assert_eq!(self.state, State::Toml);
+				self.state = State::Toplevel;
+
+				let config: PageConfig = toml::from_str(&self.toml_text)
+					.with_context(|| "Failed to parse page config in TOML format")?;
+				self.config.merge(config)?;
+
+				self.toml_text.clear();
+			}
+			Event::Str(str) if self.state == State::Toml => {
+				self.toml_text.write_str(str)?;
+			}
+			other if self.state == State::Heading => {
+				let last_heading = self.toc.last_mut().unwrap();
+				self.html_renderer.render_event(e, &mut last_heading.html)?;
+
+				if let Event::Str(str) = other {
+					last_heading.text.write_str(str)?;
+				}
+			}
+			_ => {}
+		}
+
+		Ok(())
+	}
+}
diff --git a/src/template.rs b/src/template.rs
index a206ad4..389bd35 100644
--- a/src/template.rs
+++ b/src/template.rs
@@ -1,25 +1,23 @@
-use std::fmt::Write;
-
 use anyhow::bail;
 
 // {{{ Templates & stops
 #[derive(Clone, Debug)]
-struct Stop {
-	label: String,
+struct Stop<'s> {
+	label: &'s str,
 	start: usize,
 	length: usize,
 }
 
 #[derive(Clone, Debug)]
-pub struct Template {
-	text: String,
-	stops: Vec<Stop>,
+pub struct Template<'s> {
+	text: &'s str,
+	stops: Vec<Stop<'s>>,
 }
 
-impl Template {
+impl<'s> Template<'s> {
 	// {{{ Parse template
 	#[allow(clippy::iter_nth_zero)]
-	pub fn parse(text: String) -> anyhow::Result<Template> {
+	pub fn parse(text: &'s str) -> anyhow::Result<Template<'s>> {
 		let mut stops = Vec::new();
 
 		let open_stop = "{{";
@@ -27,25 +25,23 @@ impl Template {
 
 		let mut current_stop: Option<Stop> = None;
 		let mut prev_ix = None;
-		for (ix, c) in text.char_indices() {
+		for (ix, _) in text.char_indices() {
 			if let Some(prev) = prev_ix {
 				// This char, together with the previous one
 				let last_two = &text[prev..=ix];
 				if close_stop == last_two {
 					if let Some(mut stop) = current_stop.take() {
-						stop.label.pop().unwrap();
-						// I think this is safe, as } is ascii
+						// I think this is safe, as { and } are ascii
+						stop.label = &text[stop.start + 2..=ix - 2];
 						stop.length = ix + 1 - stop.start;
 						stops.push(stop);
 					}
 				} else if open_stop == last_two && current_stop.is_none() {
 					current_stop = Some(Stop {
-						label: String::new(),
+						label: "",
 						start: prev,
 						length: 0,
 					});
-				} else if let Some(stop) = current_stop.as_mut() {
-					stop.label.write_char(c)?;
 				}
 			}
 
@@ -67,7 +63,7 @@ enum RendererState {
 
 #[derive(Clone, Debug)]
 pub struct TemplateRenderer<'a> {
-	template: &'a Template,
+	template: &'a Template<'a>,
 	state: RendererState,
 }
 
@@ -84,7 +80,7 @@ impl<'a> TemplateRenderer<'a> {
 	pub fn current(&mut self, w: impl std::fmt::Write) -> anyhow::Result<Option<&'a str>> {
 		let current_label = match self.state {
 			RendererState::Started => self.next(w)?,
-			RendererState::InStop(ix) => Some(self.template.stops[ix].label.as_str()),
+			RendererState::InStop(ix) => Some(self.template.stops[ix].label),
 			RendererState::Finished => None,
 		};
 
@@ -125,7 +121,7 @@ impl<'a> TemplateRenderer<'a> {
 		w.write_str(&self.template.text[current_pos..next_pos])?;
 
 		let current_label = match self.state {
-			RendererState::InStop(ix) => Some(self.template.stops[ix].label.as_str()),
+			RendererState::InStop(ix) => Some(self.template.stops[ix].label),
 			_ => None,
 		};
 
@@ -150,8 +146,13 @@ impl<'a> TemplateRenderer<'a> {
 #[macro_export]
 macro_rules! template {
 	($path:literal) => {{
+		use once_cell::sync::OnceCell;
+		use $crate::template::Template;
+
 		static TEMPLATE_TEXT: &str = include_str!($path);
-		$crate::template::Template::parse(TEMPLATE_TEXT.to_owned())
+		static CELL: OnceCell<Template<'static>> = OnceCell::new();
+
+		CELL.get_or_try_init(|| Template::parse(TEMPLATE_TEXT))
 	}};
 }
 // }}}
diff --git a/src/tex.rs b/src/tex.rs
deleted file mode 100644
index 84c5775..0000000
--- a/src/tex.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use anyhow::anyhow;
-use cached::{proc_macro::io_cached, DiskCache};
-
-fn make_cache() -> DiskCache<&'static str, String> {
-	DiskCache::new("tex-cache")
-		.set_disk_directory("/home/moon/.cache/moonythm")
-		.build()
-		.unwrap()
-}
-
-#[io_cached(
-	disk = true,
-	map_error = r##"|err| anyhow::Error::from(err)"##,
-	create = r"{ make_cache() }"
-)]
-pub fn compile_tex(string: &str) -> Result<String, anyhow::Error> {
-	let res = std::process::Command::new("latexmlmath")
-		.arg("--preload=amsfonts")
-		.arg("--strict")
-		.arg("--quiet")
-		.arg(string)
-		.output()
-		.expect("failed to execute latexmathml process");
-
-	if res.status.success() {
-		let output = String::from_utf8_lossy(&res.stdout);
-		Ok(output.trim().to_owned())
-	} else {
-		let output = String::from_utf8_lossy(&res.stderr);
-		Err(anyhow!("{}", output.trim()))
-	}
-}