diff --git a/Extensions/RenderContentHtml.cs b/Extensions/RenderContentHtml.cs
new file mode 100644
index 0000000..18056fc
--- /dev/null
+++ b/Extensions/RenderContentHtml.cs
@@ -0,0 +1,72 @@
+namespace QuestReader.Extensions;
+
+using System.Web;
+using QuestReader.Models;
+using QuestReader.Models.ParsedContent;
+
+public static class RenderContentHtmlExtension
+{
+ public static string RenderContentHtml(this ContentNode node, TemplateModel model)
+ {
+ return node switch
+ {
+ RootNode rootNode => string.Join("", rootNode.Nodes.Select(n => n.RenderContentHtml(model))),
+ TextNode textNode => textNode.Text,
+ NewlineNode => "
\n",
+ ReferenceNode refNode => refNode.RenderReferenceHtml(model),
+ ContainerNode container => container.RenderContailerHtml(model),
+ _ => throw new NotImplementedException(),
+ };
+ }
+
+
+ static string RenderContailerHtml(this ContainerNode node, TemplateModel model)
+ {
+ var tag = node switch
+ {
+ QuoteNode => "span",
+ BoldNode => "strong",
+ ItalicsNode => "em",
+ StrikeoutNode => "s",
+ UnderlineNode => "span",
+ SpoilerNode => "span",
+ ExternalLinkNode => "a",
+ null => throw new NullReferenceException("Node is null, something is ver wrong"),
+ _ => throw new NotImplementedException($"Rendering not implemented for {node.GetType().Name}"),
+ };
+
+ string? extra = node switch
+ {
+ QuoteNode quote => " class=\"text-quote\"",
+ UnderlineNode => " class=\"text-underline\"",
+ SpoilerNode => " class=\"text-spoiler\"",
+ ExternalLinkNode externalLink => $" href=\"{HttpUtility.HtmlAttributeEncode(externalLink.Destination)}\"",
+ _ => null,
+ };
+ var content = string.Join("", node.Nodes.Select(n => n.RenderContentHtml(model)));
+
+ // The spaces around this are absolutely stopgap and will NOT work for something like "abso-fucking-lutely"
+ // HTML is... frustrating
+ // Note for future implementation: don't trim, just collapse to single space
+ // Search regex "[^>\s;]<[^>]>". And yes there /are/ hits. Fuck!
+ return $" <{tag}{extra}>{content}{tag}> ";
+ }
+
+ static string RenderReferenceHtml(this ReferenceNode node, TemplateModel model)
+ {
+ var type = node.ReferenceType switch
+ {
+ ReferenceType.QuestActive => "quest",
+ ReferenceType.QuestArchive => "quest-archived",
+ ReferenceType.QuestDiscussion => "discussion",
+ _ => throw new NotImplementedException(),
+ };
+
+ var included = model.Metadata.Threads.Any(t => t == node.ThreadId) && model.Posts.Any(p => p.Id == node.PostId);
+ var href = "";
+ if (included)
+ href = $"href=\"#post-{node.PostId}\" ";
+
+ return $">>{node.PostId}";
+ }
+}
\ No newline at end of file