diff --git a/.gitignore b/.gitignore
index 09b9279..84379db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 target
 dist
 oldicons
+result
diff --git a/Cargo.lock b/Cargo.lock
index 9c60d73..76f0d83 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,15 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "android-tzdata"
 version = "0.1.1"
@@ -131,6 +140,12 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
 [[package]]
 name = "libc"
 version = "0.2.161"
@@ -160,6 +175,9 @@ dependencies = [
  "pulldown-latex",
  "serde",
  "toml",
+ "tree-sitter",
+ "tree-sitter-highlight",
+ "tree-sitter-rust",
 ]
 
 [[package]]
@@ -204,6 +222,35 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
 [[package]]
 name = "serde"
 version = "1.0.214"
@@ -240,16 +287,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
-name = "syn"
-version = "2.0.85"
+name = "streaming-iterator"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
+name = "syn"
+version = "2.0.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-ident",
 ]
 
+[[package]]
+name = "thiserror"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "toml"
 version = "0.8.19"
@@ -284,6 +357,48 @@ dependencies = [
  "winnow",
 ]
 
+[[package]]
+name = "tree-sitter"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9871f16d6cf5c4757dcf30d5d2172a2df6987c510c017bbb7abfb7f9aa24d06"
+dependencies = [
+ "cc",
+ "regex",
+ "regex-syntax",
+ "streaming-iterator",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-highlight"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48859aa39513716018d81904220960f415dbb72e071234a721304d20bf245e4c"
+dependencies = [
+ "lazy_static",
+ "regex",
+ "streaming-iterator",
+ "thiserror",
+ "tree-sitter",
+]
+
+[[package]]
+name = "tree-sitter-language"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600"
+
+[[package]]
+name = "tree-sitter-rust"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cffbbcb780348fbae8395742ae5b34c1fd794e4085d43aac9f259387f9a84dc8"
+dependencies = [
+ "cc",
+ "tree-sitter-language",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.13"
diff --git a/Cargo.toml b/Cargo.toml
index a630aea..1d4dc81 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,3 +14,6 @@ toml = "0.8.19"
 # before switching to the std version.
 once_cell = "1.20.2"
 chrono = { version = "0.4.38", features = ["serde"] }
+tree-sitter = "0.24.3"
+tree-sitter-rust = "0.23.0"
+tree-sitter-highlight = "0.24.3"
diff --git a/content/posts/arcaea.dj b/content/echoes/arcaea.dj
similarity index 97%
rename from content/posts/arcaea.dj
rename to content/echoes/arcaea.dj
index f153b41..74ac3b7 100644
--- a/content/posts/arcaea.dj
+++ b/content/echoes/arcaea.dj
@@ -3,6 +3,11 @@
 created_at = "2024-11-02T05:13:44+01:00"
 ```
 
+{ role=description }
+:::
+This article goes over pretty much everything about the mobile rhythm game "Arcaea". Along the way, you'll learn about everything the game does right, and the areas it could improve on. By the end, I hope you'll come to understand my love for this game.
+:::
+
 # Why I love arcaea
 
 ## What is arcaea
diff --git a/content/posts/games/icons/arcaea.png b/content/echoes/games/icons/arcaea.png
similarity index 100%
rename from content/posts/games/icons/arcaea.png
rename to content/echoes/games/icons/arcaea.png
diff --git a/content/posts/games/icons/babaisyou.png b/content/echoes/games/icons/babaisyou.png
similarity index 100%
rename from content/posts/games/icons/babaisyou.png
rename to content/echoes/games/icons/babaisyou.png
diff --git a/content/posts/games/icons/celeste.png b/content/echoes/games/icons/celeste.png
similarity index 100%
rename from content/posts/games/icons/celeste.png
rename to content/echoes/games/icons/celeste.png
diff --git a/content/posts/games/icons/downpour.jpg b/content/echoes/games/icons/downpour.jpg
similarity index 100%
rename from content/posts/games/icons/downpour.jpg
rename to content/echoes/games/icons/downpour.jpg
diff --git a/content/posts/games/icons/factorio.png b/content/echoes/games/icons/factorio.png
similarity index 100%
rename from content/posts/games/icons/factorio.png
rename to content/echoes/games/icons/factorio.png
diff --git a/content/posts/games/icons/hades.ico b/content/echoes/games/icons/hades.ico
similarity index 100%
rename from content/posts/games/icons/hades.ico
rename to content/echoes/games/icons/hades.ico
diff --git a/content/posts/games/icons/hollow-knight.png b/content/echoes/games/icons/hollow-knight.png
similarity index 100%
rename from content/posts/games/icons/hollow-knight.png
rename to content/echoes/games/icons/hollow-knight.png
diff --git a/content/posts/games/icons/noita.png b/content/echoes/games/icons/noita.png
similarity index 100%
rename from content/posts/games/icons/noita.png
rename to content/echoes/games/icons/noita.png
diff --git a/content/posts/games/icons/portal2.png b/content/echoes/games/icons/portal2.png
similarity index 100%
rename from content/posts/games/icons/portal2.png
rename to content/echoes/games/icons/portal2.png
diff --git a/content/posts/games/icons/rainworld.png b/content/echoes/games/icons/rainworld.png
similarity index 100%
rename from content/posts/games/icons/rainworld.png
rename to content/echoes/games/icons/rainworld.png
diff --git a/content/posts/games/icons/slaythespire.png b/content/echoes/games/icons/slaythespire.png
similarity index 100%
rename from content/posts/games/icons/slaythespire.png
rename to content/echoes/games/icons/slaythespire.png
diff --git a/content/posts/games/icons/thetalosprinciple.jpg b/content/echoes/games/icons/thetalosprinciple.jpg
similarity index 100%
rename from content/posts/games/icons/thetalosprinciple.jpg
rename to content/echoes/games/icons/thetalosprinciple.jpg
diff --git a/content/posts/games/icons/tunic.ico b/content/echoes/games/icons/tunic.ico
similarity index 100%
rename from content/posts/games/icons/tunic.ico
rename to content/echoes/games/icons/tunic.ico
diff --git a/content/posts/games/icons/ultrakill.png b/content/echoes/games/icons/ultrakill.png
similarity index 100%
rename from content/posts/games/icons/ultrakill.png
rename to content/echoes/games/icons/ultrakill.png
diff --git a/content/posts/games/index.dj b/content/echoes/games/index.dj
similarity index 100%
rename from content/posts/games/index.dj
rename to content/echoes/games/index.dj
diff --git a/content/echoes/index.dj b/content/echoes/index.dj
new file mode 100644
index 0000000..97211d4
--- /dev/null
+++ b/content/echoes/index.dj
@@ -0,0 +1,9 @@
+# Echoes
+
+> "Remnants of the One who once dwelled within the Silver of the Sky. They now wander endlessly through the mists of the heavens, at times drawn to lost vessels who seek to hear them. Thence, thou with this knowledge are yet to attain the full knowledge of the Plan, but worry not — the World shall linger much longer still."
+
+This page contains a list of all my long-form blog posts.
+
+- [Why I love Arcaea](./arcaea)
+- [Games I love](./games)
+- [The rhythm of the moon](./the-realm-s-secrets)
diff --git a/content/posts/the-realm-s-secrets.dj b/content/echoes/the-realm-s-secrets.dj
similarity index 100%
rename from content/posts/the-realm-s-secrets.dj
rename to content/echoes/the-realm-s-secrets.dj
diff --git a/content/posts/yugioh-my-beloved.dj b/content/echoes/yugioh-my-beloved.dj
similarity index 100%
rename from content/posts/yugioh-my-beloved.dj
rename to content/echoes/yugioh-my-beloved.dj
diff --git a/content/index.dj b/content/index.dj
index 9e4bf38..88a9a06 100644
--- a/content/index.dj
+++ b/content/index.dj
@@ -8,4 +8,8 @@ You can message me in the following places \^-\^
 
 - via email at <hi@moonythm.dev>
 - on discord as `@prescientmoon`
-- on the ~ IRC network as `prescientmoon`
+- on certain IRC networks as `prescientmoon`
+
+## Lore
+
+Some pages will have lore bits sprinkled throughout. English is not my native tongue, so writing in a more archaic-sounding style comes difficult to me, but I'm doing my best. That is, some of the lore snippets might sound utterly meaningless, although I guess that's half the vibe.
diff --git a/flake.nix b/flake.nix
index b275b7b..f91b47c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -17,6 +17,7 @@
               pkgs.rust-analyzer
               pkgs.rustfmt
               pkgs.imagemagick
+              pkgs.http-server
             ];
 
             buildInputs = with pkgs; [ ];
diff --git a/public/styles.css b/public/styles.css
index 739295f..7068e42 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -195,3 +195,112 @@ math[display="block"] {
   /* }}}*/
 }
 /* }}}*/
+
+/* {{{ Light theme */
+@media (prefers-color-scheme: light) {
+  body {
+    color: #4c4f69;
+  }
+
+  code {
+    background: #eff1f5;
+  }
+
+  pre > code {
+    display: block;
+    padding: 1rem;
+  }
+
+  /* {{{ Syntax highlighting*/
+  span.variable {
+    color: #4c4f69;
+  }
+
+  span.attribute,
+  span.constant {
+    color: #fe640b;
+  }
+
+  span.comment,
+  span.comment-documentation {
+    color: #9ca0b0;
+    font-style: italic;
+  }
+
+  span.constant-builtin {
+    color: #fe640b;
+  }
+
+  span.constructor {
+    color: #209fb5;
+  }
+
+  span.function,
+  span.function-method {
+    color: #1e66f5;
+  }
+
+  span.function-builtin {
+    color: #fe640b;
+  }
+
+  span.function-macro {
+    color: #179299;
+  }
+
+  span.keyword {
+    color: #8839ef;
+  }
+
+  span.label {
+    color: #209fb5;
+  }
+
+  span.operator {
+    color: #04a5e5;
+  }
+
+  span.property {
+    color: #7287fd;
+  }
+
+  span.punctuation,
+  span.punctuation-delimiter {
+    color: #7c7f93;
+  }
+
+  span.punctuation-bracket {
+    color: #7c7f93;
+  }
+
+  span.string {
+    color: #40a02b;
+  }
+
+  span.string-special {
+    color: #ea76cb;
+  }
+
+  span.tag {
+    color: #8839ef;
+  }
+
+  span.type,
+  span.type-builtin {
+    color: #df8e1d;
+  }
+
+  span.variable {
+    color: #4c4f69;
+  }
+
+  span.variable-builtin {
+    color: #d20f39;
+  }
+
+  span.variable-parameter {
+    color: #e64553;
+  }
+  /* }}}*/
+}
+/* }}} */
diff --git a/src/html.rs b/src/html.rs
index 5df5581..cb7346d 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -1,16 +1,23 @@
 use std::collections::HashMap;
+use std::fmt::Display;
 
 use anyhow::anyhow;
 use anyhow::bail;
 use chrono::DateTime;
 use chrono::TimeZone;
 use jotdown::Alignment;
+use jotdown::AttributeValue;
 use jotdown::Container;
 use jotdown::Event;
 use jotdown::LinkType;
 use jotdown::ListKind;
 use jotdown::OrderedListNumbering::*;
 use jotdown::SpanLinkType;
+use tree_sitter::Language;
+use tree_sitter_highlight::Highlight;
+use tree_sitter_highlight::HighlightConfiguration;
+use tree_sitter_highlight::HighlightEvent;
+use tree_sitter_highlight::Highlighter;
 
 use crate::metadata::PageMetadata;
 use crate::metadata::PageRoute;
@@ -36,7 +43,7 @@ pub struct Writer<'s> {
 	list_tightness: Vec<bool>,
 	states: Vec<State<'s>>,
 	footnotes: Footnotes<'s>,
-	metadata: Option<&'s PageMetadata>,
+	metadata: Option<&'s PageMetadata<'s>>,
 }
 
 #[derive(Debug, Clone)]
@@ -45,8 +52,10 @@ enum State<'s> {
 	Ignore,
 	Raw,
 	Math(bool),
+	CodeBlock(String),
 	Aside(TemplateRenderer<'s>),
 	Article(TemplateRenderer<'s>),
+	Footnote(Vec<jotdown::Event<'s>>),
 }
 
 impl<'s> Writer<'s> {
@@ -65,80 +74,72 @@ impl<'s> Writer<'s> {
 		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());
-			return Ok(());
-		} else if let Some(events) = self.footnotes.current() {
-			if matches!(e, Event::End(Container::Footnote { .. })) {
-				self.footnotes.end();
-			} else {
-				events.push(e.clone());
-			}
-			return Ok(());
-		}
-		// }}}
-		// {{{ Handle important state changes
-		match e {
-			Event::Start(Container::LinkDefinition { .. }, ..) => {
-				self.states.push(State::Ignore);
-				return Ok(());
-			}
-			Event::End(Container::LinkDefinition { .. }) => {
-				// Sanity check
-				assert!(matches!(self.states.last(), Some(State::Ignore)));
-				self.states.pop();
-			}
-
-			Event::Start(Container::RawBlock { format } | Container::RawInline { format }, ..) => {
-				if format == &"html" {
-					self.states.push(State::Raw);
-				} else {
-					self.states.push(State::Ignore);
+		// {{{ Handle "footnote" states
+		if matches!(self.states.last(), Some(State::Footnote(_))) {
+			if let Event::End(Container::Footnote { label }) = e {
+				let Some(State::Footnote(events)) = self.states.pop() else {
+					unreachable!()
 				};
 
+				self.footnotes.insert(label, events);
+			} else {
+				let Some(State::Footnote(events)) = self.states.last_mut() else {
+					unreachable!()
+				};
+
+				events.push(e.clone());
 				return Ok(());
 			}
-			Event::End(Container::RawBlock { .. } | Container::RawInline { .. }) => {
-				// Sanity check
-				assert!(matches!(
-					self.states.last(),
-					Some(State::Raw | State::Ignore)
-				));
-
-				self.states.pop();
-			}
-
-			_ => {}
 		}
 		// }}}
-
-		if matches!(self.states.last(), Some(State::Ignore)) {
-			return Ok(());
+		// {{{ Handle "text-only" states
+		if matches!(self.states.last(), Some(State::TextOnly)) {
+			match e {
+				Event::End(Container::Image(..)) => {
+					self.states.pop();
+				}
+				Event::Str(s) => write!(out, "{}", Escaped(s))?,
+				_ => return Ok(()),
+			}
 		}
+		// }}}
+		// {{{ Handle "ignore" states
+		if matches!(self.states.last(), Some(State::Ignore)) {
+			match e {
+				Event::End(
+					Container::RawBlock { .. }
+					| Container::RawInline { .. }
+					| Container::LinkDefinition { .. }
+					| Container::Div { .. },
+				) => {
+					self.states.pop();
+					return Ok(());
+				}
+				_ => return Ok(()),
+			}
+		}
+		// }}}
 
 		match e {
 			// {{{ Container start
 			Event::Start(c, attrs) => {
-				if matches!(self.states.last(), Some(&State::TextOnly)) {
-					return Ok(());
-				}
-
 				match &c {
-					Container::RawBlock { .. } => {}
-					Container::RawInline { .. } => unreachable!(),
-					Container::Footnote { .. } => unreachable!(),
 					// {{{ Section
 					Container::Section { id } => match self.metadata {
 						Some(meta)
-							if &meta.title.id == id && matches!(meta.route, PageRoute::Post(_)) =>
+							if meta.title.id == *id && matches!(meta.route, PageRoute::Post(_)) =>
 						{
-							let renderer = template!("templates/post.html", &mut out)?;
-							// Sanity check
+							let mut renderer = template!("templates/post.html", &mut out)?;
+
 							assert_eq!(renderer.current(), Some("attrs"));
+							write!(out, "{}", Attr("aria-labeledby", id))?;
+							renderer.next(&mut out)?;
+
 							self.states.push(State::Article(renderer));
 						}
-						_ => out.write_str("<section")?,
+						_ => {
+							write!(out, "<section {}>", Attr("aria-labeledby", id))?;
+						}
 					},
 					// }}}
 					// {{{ Aside
@@ -154,27 +155,25 @@ impl<'s> Writer<'s> {
 						};
 
 						while let Some(label) = renderer.current() {
-							if label == "character" {
-								let character = attrs.get_value("character").ok_or_else(|| {
-									anyhow!("Cannot find `character` attribute on `aside` element")
-								})?;
+							match label {
+								"character" => {
+									let character =
+										attrs.get_value("character").ok_or_else(|| {
+											anyhow!("Cannot find `character` attribute on `aside` element")
+										})?;
 
-								character
-									.parts()
-									.try_for_each(|part| write_attr_contents(part, &mut out))?;
-								renderer.next(&mut out)?;
-							} else if label == "title" {
-								let title = attrs.get_value("title").ok_or_else(|| {
-									anyhow!("Cannot find `title` attribute on `aside` element")
-								})?;
+									write_attribute(&mut out, &character)?;
+								}
+								"title" => {
+									let title = attrs.get_value("title").ok_or_else(|| {
+										anyhow!("Cannot find `title` attribute on `aside` element")
+									})?;
 
-								title
-									.parts()
-									.try_for_each(|part| write_attr_contents(part, &mut out))?;
-								renderer.next(&mut out)?;
-							} else {
-								break;
+									write_attribute(&mut out, &title)?;
+								}
+								_ => break,
 							}
+							renderer.next(&mut out)?;
 						}
 
 						self.states.push(State::Aside(renderer));
@@ -184,7 +183,7 @@ impl<'s> Writer<'s> {
 					Container::List { kind, tight } => {
 						self.list_tightness.push(*tight);
 						match kind {
-							ListKind::Unordered(..) => out.write_str("<ul")?,
+							ListKind::Unordered(..) => out.write_str("<ul>")?,
 							ListKind::Ordered {
 								numbering, start, ..
 							} => {
@@ -202,6 +201,8 @@ impl<'s> Writer<'s> {
 								} {
 									write!(out, r#" type="{}""#, ty)?;
 								}
+
+								write!(out, ">")?;
 							}
 							ListKind::Task(_) => bail!("Task lists are not supported"),
 						}
@@ -210,179 +211,130 @@ impl<'s> Writer<'s> {
 					// {{{ Link
 					Container::Link(dst, ty) => {
 						if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
-							out.write_str("<a")?;
+							out.write_str("<a>")?;
 						} else {
-							out.write_str(r#"<a href=""#)?;
-							if matches!(ty, LinkType::Email) {
-								out.write_str("mailto:")?;
-							}
-							write_attr_contents(dst, &mut out)?;
-							out.write_char('"')?;
+							let prefix = if matches!(ty, LinkType::Email) {
+								"mailto:"
+							} else {
+								""
+							};
+
+							write!(out, r#"<a href="{prefix}{}"">"#, Escaped(dst))?;
 						}
 					}
 					// }}}
+					// {{{ Table cell
+					Container::TableCell {
+						head, alignment, ..
+					} => {
+						if *head {
+							out.write_str("<td")?;
+						} else {
+							out.write_str("<th")?;
+						}
+
+						if !matches!(alignment, Alignment::Unspecified) {
+							// TODO: move this to css
+							let a = match alignment {
+								Alignment::Unspecified => unreachable!(),
+								Alignment::Left => "left",
+								Alignment::Center => "center",
+								Alignment::Right => "right",
+							};
+
+							write!(out, r#" style="text-align: {};""#, a)?;
+						}
+
+						write!(out, ">")?;
+					}
+					// }}}
+					// {{{ Heading
+					Container::Heading { level, id, .. } => {
+						write!(
+							out,
+							r##"<h{level} id="{}"><a href="#{}">◇</a> "##,
+							Escaped(id),
+							Escaped(id)
+						)?;
+					}
+					// }}}
 					// {{{ Paragraph
 					Container::Paragraph => {
 						if self.list_tightness.last() == Some(&true) {
 							return Ok(());
 						}
 
-						out.write_str("<p")?;
+						out.write_str("<p>")?;
 					}
 					// }}}
-					Container::Blockquote => out.write_str("<blockquote")?,
-					Container::ListItem { .. } => out.write_str("<li")?,
-					Container::DescriptionList => out.write_str("<dl")?,
-					Container::DescriptionDetails => out.write_str("<dd")?,
-					Container::Table => out.write_str("<table")?,
-					Container::TableRow { .. } => out.write_str("<tr")?,
-					Container::Div { .. } => out.write_str("<div")?,
-					Container::Heading { level, .. } => write!(out, "<h{}", level)?,
-					Container::TableCell { head: false, .. } => out.write_str("<td")?,
-					Container::TableCell { head: true, .. } => out.write_str("<th")?,
-					Container::Caption => out.write_str("<caption")?,
-					Container::Image(..) => out.write_str("<img")?,
-					Container::DescriptionTerm => out.write_str("<dt")?,
-					Container::CodeBlock { .. } => out.write_str("<pre")?,
-					Container::Span | Container::Math { .. } => out.write_str("<span")?,
-					Container::Verbatim => out.write_str("<code")?,
-					Container::Subscript => out.write_str("<sub")?,
-					Container::Superscript => out.write_str("<sup")?,
-					Container::Insert => out.write_str("<ins")?,
-					Container::Delete => out.write_str("<del")?,
-					Container::Strong => out.write_str("<strong")?,
-					Container::Emphasis => out.write_str("<em")?,
-					Container::Mark => out.write_str("<mark")?,
+					// {{{ Div
+					Container::Div { class } => {
+						if attrs
+							.get_value("role")
+							.map_or(false, |role| format!("{role}") == "description")
+						{
+							self.states.push(State::Ignore);
+						} else {
+							write!(out, "<div{}>", Attr("class", class))?;
+						}
+					}
+					// }}}
+					// {{{ Raw block
+					Container::RawBlock { format } | Container::RawInline { format } => {
+						if format == &"html" {
+							self.states.push(State::Raw);
+						} else {
+							self.states.push(State::Ignore);
+						};
+					}
+					// }}}
+					Container::CodeBlock { .. } => {
+						self.states.push(State::CodeBlock(String::new()));
+						out.write_str("<pre><code>")?;
+					}
+					Container::Math { display } => {
+						self.states.push(State::Math(*display));
+						out.write_str("<math>")?;
+					}
+					Container::Image(_, _) => {
+						self.states.push(State::TextOnly);
+						out.write_str(r#"<img alt=""#)?;
+					}
+					Container::Footnote { .. } => self.states.push(State::Footnote(Vec::new())),
+					Container::Blockquote => out.write_str("<blockquote>")?,
+					Container::ListItem { .. } => out.write_str("<li>")?,
+					Container::DescriptionList => out.write_str("<dl>")?,
+					Container::DescriptionDetails => out.write_str("<dd>")?,
+					Container::Table => out.write_str("<table>")?,
+					Container::TableRow { .. } => out.write_str("<tr>")?,
+					Container::Caption => out.write_str("<caption>")?,
+					Container::DescriptionTerm => out.write_str("<dt>")?,
+					Container::Span => out.write_str("<span>")?,
+					Container::Verbatim => out.write_str("<code>")?,
+					Container::Subscript => out.write_str("<sub>")?,
+					Container::Superscript => out.write_str("<sup>")?,
+					Container::Insert => out.write_str("<ins>")?,
+					Container::Delete => out.write_str("<del>")?,
+					Container::Strong => out.write_str("<strong>")?,
+					Container::Emphasis => out.write_str("<em>")?,
+					Container::Mark => out.write_str("<mark>")?,
 					Container::LinkDefinition { .. } => return Ok(()),
 					e => bail!("DJot element {e:?} is not supported"),
 				}
-
-				// {{{ Decide whether this element supports attributes
-				let mut write_attr_contentsibs = true;
-				if matches!(
-					c,
-					Container::Div {
-						class: "aside" | "long-aside" | "char-aside"
-					}
-				) {
-					write_attr_contentsibs = false;
-				}
-				// }}}
-
-				if write_attr_contentsibs {
-					// {{{ Write attributes
-					let mut id_written = false;
-					let mut class_written = false;
-
-					if write_attr_contentsibs {
-						for (a, v) in attrs.unique_pairs() {
-							let is_class = a == "class";
-							let is_id = a == "id";
-							if (!is_id || !id_written) && (!is_class || !class_written) {
-								write!(out, r#" {}=""#, a)?;
-								v.parts()
-									.try_for_each(|part| write_attr_contents(part, &mut out))?;
-								out.write_char('"')?;
-
-								id_written |= is_id;
-								class_written |= is_class;
-							};
-						}
-					}
-					// }}}
-					// {{{ Write default ids/classes
-					match c {
-						Container::Heading { id, .. } if !id_written => {
-							write_attr("id", id, &mut out)?;
-						}
-						Container::Section { id, .. } => {
-							write_attr("aria-labeledby", id, &mut out)?;
-						}
-						Container::Div { class } if !class.is_empty() && !class_written => {
-							write_attr("class", class, &mut out)?;
-						}
-						_ => {}
-					}
-					// }}}
-					// {{{ Write special attributes
-					match c {
-						// {{{ Write css for aligning table cell text
-						Container::TableCell { alignment, .. }
-							if !matches!(alignment, Alignment::Unspecified) =>
-						{
-							let a = match alignment {
-								Alignment::Unspecified => unreachable!(),
-								Alignment::Left => "left",
-								Alignment::Center => "center",
-								Alignment::Right => "right",
-							};
-							write!(out, r#" style="text-align: {};">"#, a)?;
-						}
-						// }}}
-						// {{{ Write language for codeblock
-						Container::CodeBlock { language } => {
-							if language.is_empty() {
-								out.write_str("><code>")?;
-							} else {
-								out.write_str(r#"><code class="language-"#)?;
-								write_attr_contents(language, &mut out)?;
-								out.write_str(r#"">"#)?;
-							}
-						}
-						// }}}
-						Container::Image(..) => out.write_str(r#" alt=""#)?,
-						Container::Math { display } => {
-							out.write_str(r#">"#)?;
-							self.states.push(State::Math(*display));
-						}
-						_ => match self.states.last_mut() {
-							Some(State::Article(renderer))
-								if renderer.current() == Some("attrs") =>
-							{
-								renderer.next(&mut out)?;
-							}
-							_ => out.write_char('>')?,
-						},
-					}
-					// }}}
-				}
-
-				// {{{ Post-start effects
-				match &c {
-					Container::Heading { id, .. } => {
-						out.write_str(r##"<a href="#"##)?;
-						write_attr_contents(id, &mut out)?;
-						out.write_str(r#"">◇</a> "#)?;
-					}
-					Container::Image(..) => {
-						self.states.push(State::TextOnly);
-					}
-					_ => {}
-				}
-				// }}}
 			}
 			// }}}
 			// {{{ Container end
 			Event::End(c) => {
-				// {{{ Pre-end effects
-				match &c {
-					Container::Image(..) => {
+				match c {
+					Container::Footnote { .. } => unreachable!(),
+					// {{{ Raw block
+					Container::RawBlock { .. } | Container::RawInline { .. } => {
 						// Sanity check
-						assert!(matches!(self.states.last(), Some(State::TextOnly)));
+						assert!(matches!(self.states.last(), Some(State::Raw)));
+
 						self.states.pop();
 					}
-					_ => {}
-				}
-				// }}}
-
-				if matches!(self.states.last(), Some(State::TextOnly)) {
-					return Ok(());
-				}
-
-				match c {
-					Container::RawBlock { .. } => {}
-					Container::RawInline { .. } => unreachable!(),
-					Container::Footnote { .. } => unreachable!(),
+					// }}}
 					// {{{ List
 					Container::List { kind, .. } => {
 						self.list_tightness.pop();
@@ -403,28 +355,18 @@ impl<'s> Writer<'s> {
 						out.write_str("</p>")?;
 					}
 					// }}}
-					// {{{ Image
-					Container::Image(src, ..) => {
-						if !src.is_empty() {
-							out.write_str(r#"" src=""#)?;
-							write_attr_contents(src, &mut out)?;
-						}
-
-						out.write_str(r#"">"#)?;
-					}
-					// }}}
 					// {{{ Math
 					Container::Math { .. } => {
 						// Sanity check
 						assert!(matches!(self.states.last(), Some(State::Math(_))));
 						self.states.pop();
-						out.write_str(r#"</span>"#)?;
+						out.write_str(r#"</math>"#)?;
 					}
 					// }}}
 					// {{{ Section
 					Container::Section { id, .. } => match self.metadata {
 						Some(meta)
-							if &meta.title.id == id
+							if meta.title.id == *id
 								&& matches!(self.states.last(), Some(State::Article(_))) =>
 						{
 							let Some(State::Article(renderer)) = self.states.pop() else {
@@ -445,8 +387,6 @@ impl<'s> Writer<'s> {
 							panic!("Finished `aside` element without being in the `Aside` state.")
 						};
 
-						// Sanity check
-						assert_eq!(renderer.current(), Some("content"));
 						renderer.finish(&mut out)?;
 					}
 					// }}}
@@ -462,42 +402,42 @@ impl<'s> Writer<'s> {
 								while let Some(label) = renderer.next(&mut out)? {
 									if label == "posted_on" {
 										if let Some(d) = meta.config.created_at {
-											write!(&mut out, "Posted on ")?;
-											write_datetime(&d, &mut out)?;
+											write!(out, "Posted on ")?;
+											write_datetime(&mut out, &d)?;
 										} else {
-											write!(&mut out, "Being conjured by ")?;
+											write!(out, "Being conjured by ")?;
 										}
 									} else if label == "updated_on" {
-										write_datetime(&meta.last_modified, &mut out)?;
+										write_datetime(&mut out, &meta.last_modified)?;
 									} else if label == "word_count" {
 										let wc = meta.word_count;
 										if wc < 400 {
-											write!(&mut out, "{}", wc)?;
+											write!(out, "{}", wc)?;
 										} else if wc < 1000 {
-											write!(&mut out, "{}", wc / 10 * 10)?;
+											write!(out, "{}", wc / 10 * 10)?;
 										} else if wc < 2000 {
-											write!(&mut out, "{}", wc / 100 * 100)?;
+											write!(out, "{}", wc / 100 * 100)?;
 										} else {
-											write!(&mut out, "{} thousand", wc / 1000)?;
+											write!(out, "{} thousand", wc / 1000)?;
 										}
 									} else if label == "reading_duration" {
 										let minutes = meta.word_count / 200;
 										if minutes == 0 {
 											let seconds = meta.word_count * 60 / 200;
-											write!(&mut out, "very short {seconds} second")?;
+											write!(out, "very short {seconds} second")?;
 										} else if minutes < 10 {
-											write!(&mut out, "short {minutes} minute")?;
+											write!(out, "short {minutes} minute")?;
 										} else if minutes < 20 {
-											write!(&mut out, "somewhat short {minutes} minute")?;
+											write!(out, "somewhat short {minutes} minute")?;
 										} else if minutes < 30 {
-											write!(&mut out, "somewhat long {minutes}")?;
+											write!(out, "somewhat long {minutes}")?;
 										} else if minutes < 60 {
-											write!(&mut out, "long {minutes}")?;
+											write!(out, "long {minutes}")?;
 										} else {
 											let hours = minutes / 60;
 											let minutes = minutes % 60;
 											write!(
-												&mut out,
+												out,
 												"very long {hours} hour and {minutes} minute"
 											)?;
 										}
@@ -509,6 +449,8 @@ impl<'s> Writer<'s> {
 						}
 						// }}}
 					}
+					Container::Image(src, ..) => write!(out, r#" {}>"#, Attr("src", src))?,
+					Container::LinkDefinition { .. } => self.states.push(State::Ignore),
 					Container::Blockquote => out.write_str("</blockquote>")?,
 					Container::ListItem { .. } => out.write_str("</li>")?,
 					Container::DescriptionList => out.write_str("</dl>")?,
@@ -520,7 +462,87 @@ impl<'s> Writer<'s> {
 					Container::TableCell { head: true, .. } => out.write_str("</th>")?,
 					Container::Caption => out.write_str("</caption>")?,
 					Container::DescriptionTerm => out.write_str("</dt>")?,
-					Container::CodeBlock { .. } => out.write_str("</code></pre>")?,
+					// {{{ Syntax highlighting
+					Container::CodeBlock { language } => {
+						let Some(State::CodeBlock(buffer)) = self.states.pop() else {
+							panic!("Arrived at end of code block without being in the approriate state.");
+						};
+
+						if *language == "rust" {
+							let mut highlighter = Highlighter::new();
+							let language = Language::new(tree_sitter_rust::LANGUAGE);
+
+							let mut config = HighlightConfiguration::new(
+								language,
+								"rust",
+								tree_sitter_rust::HIGHLIGHTS_QUERY,
+								tree_sitter_rust::INJECTIONS_QUERY,
+								"",
+							)?;
+
+							let highlight_names = [
+								"attribute",
+								"comment",
+								"comment.documentation",
+								"constant",
+								"constant.builtin",
+								"constructor",
+								"function",
+								"function.builtin",
+								"function.macro",
+								"function.method",
+								"keyword",
+								"label",
+								"operator",
+								"property",
+								"punctuation",
+								"punctuation.bracket",
+								"punctuation.delimiter",
+								"string",
+								"string.special",
+								"tag",
+								"type",
+								"type.builtin",
+								"variable",
+								"variable.builtin",
+								"variable.parameter",
+							];
+
+							let highlight_classes = highlight_names
+								.iter()
+								.map(|s| s.replace(".", "-"))
+								.collect::<Vec<_>>();
+
+							config.configure(&highlight_names);
+
+							let highlights =
+								highlighter
+									.highlight(&config, buffer.as_bytes(), None, |_| None)?;
+
+							for event in highlights {
+								match event? {
+									HighlightEvent::Source { start, end } => {
+										write!(out, "{}", Escaped(&buffer[start..end]))?;
+									}
+									HighlightEvent::HighlightStart(Highlight(index)) => {
+										write!(
+											&mut out,
+											r#"<span class="{}">"#,
+											highlight_classes[index]
+										)?;
+									}
+									HighlightEvent::HighlightEnd => {
+										write!(&mut out, r#"</span>"#)?;
+									}
+								}
+							}
+						} else {
+							write!(out, "{}", Escaped(&buffer))?;
+						}
+
+						out.write_str("</code></pre>")?
+					}
+					// }}}
 					Container::Span => out.write_str("</span>")?,
 					Container::Link(..) => out.write_str("</a>")?,
 					Container::Verbatim => out.write_str("</code>")?,
@@ -531,15 +553,14 @@ impl<'s> Writer<'s> {
 					Container::Strong => out.write_str("</strong>")?,
 					Container::Emphasis => out.write_str("</em>")?,
 					Container::Mark => out.write_str("</mark>")?,
-					Container::LinkDefinition { .. } => unreachable!(),
 					e => bail!("DJot element {e:?} is not supported"),
 				}
 			}
 			// }}}
 			// {{{ Raw string
-			Event::Str(s) => match self.states.last() {
-				Some(State::TextOnly) => write_attr_contents(s, &mut out)?,
+			Event::Str(s) => match self.states.last_mut() {
 				Some(State::Raw) => out.write_str(s)?,
+				Some(State::CodeBlock(buffer)) => buffer.push_str(s),
 				// {{{ Math
 				Some(State::Math(display)) => {
 					let config = pulldown_latex::RenderConfig {
@@ -564,7 +585,7 @@ impl<'s> Writer<'s> {
 					out.write_str(&mathml)?;
 				}
 				// }}}
-				_ => write_escape(s, false, &mut out)?,
+				_ => write!(out, "{}", Escaped(s))?,
 			},
 			// }}}
 			// {{{ Footnote reference
@@ -573,8 +594,13 @@ impl<'s> Writer<'s> {
 				if !matches!(self.states.last(), Some(State::TextOnly)) {
 					write!(
 						out,
-						r##"<sup><a id="fnref{}" href="#fn{}" role="doc-noteref">{}</a></sup>"##,
-						number, number, number
+						r##"
+              <sup>
+                <a id="fnref{number}" href="#fn{number}" role="doc-noteref">
+                  {number}
+                </a>
+              </sup>
+            "##
 					)?;
 				}
 			}
@@ -592,18 +618,7 @@ impl<'s> Writer<'s> {
 			Event::Hardbreak => out.write_str("<br>")?,
 			Event::Softbreak => out.write_char('\n')?,
 			// }}}
-			// {{{ Thematic break
-			Event::ThematicBreak(attrs) => {
-				out.write_str("<hr")?;
-				for (a, v) in attrs.unique_pairs() {
-					write!(out, r#" {}=""#, a)?;
-					v.parts()
-						.try_for_each(|part| write_attr_contents(part, &mut out))?;
-					out.write_char('"')?;
-				}
-				out.write_str(">")?;
-			}
-			// }}}
+			Event::ThematicBreak(_) => out.write_str("<hr>")?,
 			Event::Escape | Event::Blankline | Event::Attributes(..) => {}
 		}
 
@@ -617,7 +632,7 @@ impl<'s> Writer<'s> {
 			out.write_str("<section role=\"doc-endnotes\"><hr><ol>")?;
 
 			while let Some((number, events)) = self.footnotes.next() {
-				write!(out, "<li id=\"fn{}\">", number)?;
+				write!(out, r#"<li id="fn{number}">"#)?;
 
 				for e in events.iter().flatten() {
 					self.render_event(e, &mut out)?;
@@ -625,9 +640,14 @@ impl<'s> Writer<'s> {
 
 				write!(
 					out,
-					"<a href=\"#fnref{}\" role=\"doc-backlink\">Return to content \u{21A9}\u{FE0E}</a></li>",
-					number,
+					r##"
+            <a href="#fnref{number}" role="doc-backlink">
+                Return to content 
+            </a></li>
+          "##,
 				)?;
+
+				println!("\u{21A9}\u{FE0E}");
 			}
 
 			out.write_str("</ol></section>")?;
@@ -638,50 +658,52 @@ impl<'s> Writer<'s> {
 	// }}}
 }
 
-// {{{ Writing helpers
-#[inline]
-fn write_attr_contents(s: &str, out: impl std::fmt::Write) -> std::fmt::Result {
-	write_escape(s, true, out)
-}
+// {{{ HTMl escaper
+pub struct Escaped<'a>(&'a str);
 
-#[inline]
-fn write_attr(attr: &str, content: &str, mut out: impl std::fmt::Write) -> std::fmt::Result {
-	write!(&mut out, r#" {attr}=""#)?;
-	write_attr_contents(content, &mut out)?;
-	out.write_char('"')?;
-	Ok(())
-}
-
-fn write_escape(
-	mut s: &str,
-	escape_quotes: bool,
-	mut out: impl std::fmt::Write,
-) -> std::fmt::Result {
-	let mut ent = "";
-	while let Some(i) = s.find(|c| {
-		match c {
-			'<' => Some("&lt;"),
-			'>' => Some("&gt;"),
-			'&' => Some("&amp;"),
-			'"' if escape_quotes => Some("&quot;"),
-			_ => None,
+impl<'s> Display for Escaped<'s> {
+	fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		let mut s = self.0;
+		let mut ent = "";
+		while let Some(i) = s.find(|c| {
+			match c {
+				'<' => Some("&lt;"),
+				'>' => Some("&gt;"),
+				'&' => Some("&amp;"),
+				'"' => Some("&quot;"),
+				_ => None,
+			}
+			.map_or(false, |s| {
+				ent = s;
+				true
+			})
+		}) {
+			out.write_str(&s[..i])?;
+			out.write_str(ent)?;
+			s = &s[i + 1..];
 		}
-		.map_or(false, |s| {
-			ent = s;
-			true
-		})
-	}) {
-		out.write_str(&s[..i])?;
-		out.write_str(ent)?;
-		s = &s[i + 1..];
+		out.write_str(s)
 	}
-	out.write_str(s)
 }
+// }}}
+// {{{ Render attributes
+pub struct Attr<'a>(&'static str, &'a str);
 
+impl<'s> Display for Attr<'s> {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		if !self.1.is_empty() {
+			write!(f, r#" {}="{}""#, self.0, Escaped(self.1))?;
+		}
+
+		Ok(())
+	}
+}
+// }}}
+// {{{ Render datetimes
 #[inline]
 fn write_datetime<T: TimeZone>(
-	datetime: &DateTime<T>,
 	mut out: impl std::fmt::Write,
+	datetime: &DateTime<T>,
 ) -> std::fmt::Result {
 	let datetime = datetime.to_utc();
 	write!(
@@ -692,6 +714,13 @@ fn write_datetime<T: TimeZone>(
 	)
 }
 // }}}
+// {{{ Jotdown attribute helpers
+#[inline]
+fn write_attribute(mut out: impl std::fmt::Write, attr: &AttributeValue) -> std::fmt::Result {
+	attr.parts()
+		.try_for_each(|part| write!(out, "{}", Escaped(part)))
+}
+// }}}
 // {{{ Footnotes
 /// Helper to aggregate footnotes for rendering at the end of the document. It will cache footnote
 /// events until they should be emitted at the end.
@@ -700,8 +729,6 @@ fn write_datetime<T: TimeZone>(
 /// the order they were first referenced.
 #[derive(Default)]
 struct Footnotes<'s> {
-	/// Stack of current open footnotes, with label and staging buffer.
-	open: Vec<(&'s str, Vec<Event<'s>>)>,
 	/// Footnote references in the order they were first encountered.
 	references: Vec<&'s str>,
 	/// Events for each footnote.
@@ -730,20 +757,9 @@ impl<'s> Footnotes<'s> {
 			)
 	}
 
-	/// Start aggregating a footnote.
-	fn start(&mut self, label: &'s str, events: Vec<Event<'s>>) {
-		self.open.push((label, events));
-	}
-
-	/// Obtain the current (most recently started) footnote.
-	fn current(&mut self) -> Option<&mut Vec<Event<'s>>> {
-		self.open.last_mut().map(|(_, e)| e)
-	}
-
-	/// End the current (most recently started) footnote.
-	fn end(&mut self) {
-		let (label, stage) = self.open.pop().unwrap();
-		self.events.insert(label, stage);
+	/// Insert a new footnote to be renderer later
+	fn insert(&mut self, label: &'s str, events: Vec<jotdown::Event<'s>>) {
+		self.events.insert(label, events);
 	}
 }
 
diff --git a/src/main.rs b/src/main.rs
index 27bb76d..92e7b6a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -38,7 +38,7 @@ fn generate_page(path: &Path) -> anyhow::Result<()> {
 		} else if label == "navigation" {
 			out.write_str(r#"<a href="/"><code>~</code></a>"#)?;
 			out.write_str(" / ")?;
-			out.write_str(r#"<a href="/posts"><code>posts</code></a>"#)?;
+			out.write_str(r#"<a href="/echoes"><code>echoes</code></a>"#)?;
 			page_renderer.next(&mut out)?;
 		} else {
 			break;
diff --git a/src/metadata.rs b/src/metadata.rs
index 37d212a..3f98876 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -4,12 +4,11 @@ use std::path::{Component, Path, PathBuf};
 use std::process::Command;
 
 use anyhow::{anyhow, bail, Context};
-use chrono::{DateTime, FixedOffset};
+use chrono::{DateTime, FixedOffset, Utc};
 use jotdown::{Container, Event};
 use serde::Deserialize;
 
-use crate::html;
-
+// {{{ Config
 #[derive(Deserialize, Debug, Default)]
 pub struct PageConfig {
 	pub created_at: Option<DateTime<FixedOffset>>,
@@ -32,7 +31,8 @@ impl PageConfig {
 		Ok(())
 	}
 }
-
+// }}}
+// {{{ Routing
 #[derive(Debug)]
 pub enum PageRoute {
 	Home,
@@ -49,16 +49,19 @@ impl PageRoute {
 
 		let result = if first == OsStr::new("index.dj") {
 			Self::Home
-		} else if first == OsStr::new("posts") {
-			if let Some(Component::Normal(second)) = path.components().nth(2) {
-				let mut slice = second.to_str().unwrap();
-				if slice.ends_with(".dj") {
-					slice = slice.strip_suffix(".dj").unwrap();
-				}
+		} else if first == OsStr::new("echoes") {
+			let Some(Component::Normal(second)) = path.components().nth(2) else {
+				bail!("Cannot convert path '{:?}' to page route", path);
+			};
+			let mut slice = second.to_str().unwrap();
+			if slice.ends_with(".dj") {
+				slice = slice.strip_suffix(".dj").unwrap();
+			}
 
-				Self::Post(slice.to_owned())
-			} else {
+			if slice == "index" {
 				Self::Posts
+			} else {
+				Self::Post(slice.to_owned())
 			}
 		} else {
 			bail!("Cannot convert path '{:?}' to page route", path);
@@ -67,26 +70,33 @@ impl PageRoute {
 		Ok(result)
 	}
 }
-
-#[derive(Debug)]
-pub struct PageMetadata {
-	pub title: Heading,
-	pub config: PageConfig,
-	pub word_count: usize,
-	pub last_modified: DateTime<FixedOffset>,
-	pub route: PageRoute,
-
+// }}}
+// {{{ Metadata
+#[derive(Debug, Clone)]
+pub struct Heading<'a> {
 	#[allow(dead_code)]
-	pub toc: Vec<Heading>,
-	#[allow(dead_code)]
-	pub path: PathBuf,
+	pub level: u8,
+	pub id: String, // Heading events own their ID, so we have to clone
+	pub events: Vec<jotdown::Event<'a>>,
 }
 
-impl PageMetadata {
-	pub fn new<'s>(
-		mut events: impl Iterator<Item = Event<'s>>,
-		path: PathBuf,
-	) -> anyhow::Result<Self> {
+#[derive(Debug)]
+pub struct PageMetadata<'s> {
+	pub config: PageConfig,
+	pub route: PageRoute,
+
+	pub title: Heading<'s>,
+	#[allow(dead_code)]
+	pub description: Vec<jotdown::Event<'s>>,
+	#[allow(dead_code)]
+	pub toc: Vec<Heading<'s>>,
+
+	pub word_count: usize,
+	pub last_modified: DateTime<FixedOffset>,
+}
+
+impl<'a> PageMetadata<'a> {
+	pub fn new(mut events: impl Iterator<Item = Event<'a>>, path: PathBuf) -> anyhow::Result<Self> {
 		let mut w = Writer::new();
 		events.try_for_each(|e| w.render_event(&e))?;
 
@@ -104,56 +114,53 @@ impl PageMetadata {
 			.with_context(|| anyhow!("Could not read the last modification date for file"))?
 			.stdout;
 		let last_modified = String::from_utf8(last_modified_output)?;
-		let last_modified = DateTime::parse_from_rfc3339(&last_modified).with_context(|| {
-			anyhow!(
-				"Failed to parse datetime returned by git '{}'",
-				last_modified
-			)
-		})?;
+
+		let last_modified = if last_modified.is_empty() {
+			Utc::now().fixed_offset()
+		} else {
+			DateTime::parse_from_rfc3339(&last_modified).with_context(|| {
+				anyhow!(
+					"Failed to parse datetime returned by git '{}'",
+					last_modified
+				)
+			})?
+		};
 
 		Ok(Self {
 			title: title.to_owned(),
+			description: w.description,
 			route: PageRoute::from_path(&path)?,
 			config: w.config,
 			toc: w.toc,
 			word_count: w.word_count,
 			last_modified,
-			path,
 		})
 	}
 }
-
-#[derive(Debug, Clone)]
-pub struct Heading {
-	#[allow(dead_code)]
-	pub level: u8,
-	pub id: String,
-	pub text: String,
-	pub html: String,
-}
+// }}}
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
 enum State {
 	Toplevel,
 	Heading,
 	Config,
+	Description,
 }
 
 struct Writer<'s> {
-	/// This renderer is used for generating the html for the titles
-	html_renderer: html::Writer<'s>,
 	config: PageConfig,
-	toc: Vec<Heading>,
+	toc: Vec<Heading<'s>>,
 	toml_text: String,
 	state: State,
 	word_count: usize,
+	description: Vec<jotdown::Event<'s>>,
 }
 
 impl<'s> Writer<'s> {
 	fn new() -> Self {
 		Self {
-			html_renderer: html::Writer::new(None),
 			config: PageConfig::default(),
+			description: Vec::new(),
 			toc: Vec::new(),
 			toml_text: String::new(),
 			state: State::Toplevel,
@@ -161,7 +168,7 @@ impl<'s> Writer<'s> {
 		}
 	}
 
-	fn render_event(&mut self, e: &jotdown::Event<'s>) -> anyhow::Result<()> {
+	fn render_event<'a>(&mut self, e: &'a jotdown::Event<'s>) -> anyhow::Result<()> {
 		if let Event::Str(content) = e {
 			if self.state != State::Config {
 				self.word_count += content
@@ -172,20 +179,23 @@ impl<'s> Writer<'s> {
 		}
 
 		match e {
+			// {{{ Headings
 			Event::Start(Container::Heading { level, id, .. }, _) => {
 				assert_eq!(self.state, State::Toplevel);
 				self.state = State::Heading;
 				self.toc.push(Heading {
 					level: *level as u8,
+					events: Vec::new(),
+					// These ids are always borrowed, unless modified by the user (i.e. me)
 					id: id.to_string(),
-					text: String::new(),
-					html: String::new(),
 				})
 			}
 			Event::End(Container::Heading { .. }) => {
 				assert_eq!(self.state, State::Heading);
 				self.state = State::Toplevel;
 			}
+			// }}}
+			// {{{ TOML config blocks
 			Event::Start(Container::RawBlock { format: "toml" }, attrs) => {
 				assert_eq!(self.state, State::Toplevel);
 				if let Some(role_attr) = attrs.get_value("role") {
@@ -205,16 +215,28 @@ impl<'s> Writer<'s> {
 					self.toml_text.clear();
 				}
 			}
+			// }}}
+			// {{{ Descriptions
+			Event::Start(Container::Div { .. }, attrs) if self.state == State::Toplevel => {
+				if let Some(role_attr) = attrs.get_value("role") {
+					if format!("{}", role_attr) == "description" {
+						self.state = State::Description
+					}
+				}
+			}
+			Event::End(Container::Div { .. }) if self.state == State::Description => {
+				self.state = State::Toplevel;
+			}
+			// }}}
 			Event::Str(str) if self.state == State::Config => {
 				self.toml_text.write_str(str)?;
 			}
-			other if self.state == State::Heading => {
+			_ if self.state == State::Description => {
+				self.description.push(e.clone());
+			}
+			_ 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)?;
-				}
+				last_heading.events.push(e.clone());
 			}
 			_ => {}
 		}
diff --git a/src/template.rs b/src/template.rs
index 762609d..fee24b3 100644
--- a/src/template.rs
+++ b/src/template.rs
@@ -127,7 +127,6 @@ impl<'a> TemplateRenderer<'a> {
 			None => (self.template.text.len(), self.template.text.len()),
 		}
 	}
-
 	// }}}
 }
 // }}}
diff --git a/src/templates/post-summary.html b/src/templates/post-summary.html
new file mode 100644
index 0000000..5ac07b4
--- /dev/null
+++ b/src/templates/post-summary.html
@@ -0,0 +1,13 @@
+<article>
+  <h2>
+    <a href="/echoes/{{id}}" rel="bookmark">{{title}}</a>
+  </h2>
+
+  <ul>
+    <li>{{posted_on}} by <a href="about:blank">prescientmoon</a></li>
+    <li>Last updated on {{updated_on}}.</li>
+    <li>About {{word_count}} words; a {{reading_duration}} read</li>
+  </ul>
+
+  {{description}}
+</article>