diff --git a/public/code_highlighter.js b/public/code_highlighter.js
new file mode 100644
index 0000000..c8ee430
--- /dev/null
+++ b/public/code_highlighter.js
@@ -0,0 +1,40 @@
+const highlight = (map, source) => {
+  let output = source;
+
+  output = output.replace(/("[^"]+")/g, `<span class="string">$1</span>`)
+
+  for (let keyword of map.keywords) {
+    output = output.replaceAll(keyword, `<span class="keyword">${keyword}</span>`)
+  }
+
+  for (let keyword of map.altkeywords) {
+    output = output.replaceAll(keyword, `<span class="keyword-alt">${keyword}</span>`)
+  }
+
+  // TODO: predicate for function calls to be replaced
+  // for (let func in map.functions) {
+  //   output = output.replace(func, `<span class="function">${func}</span>`)
+  // }
+
+  output = output.replace(/(\d+)/g, `<span class="number">$1</span>`)
+
+  return output;
+}
+
+const maps = {
+  cpp: {
+    keywords: [
+      "auto",
+      "void",
+      "struct"
+    ],
+    altkeywords: [
+      "const",
+      "static",
+      "int",
+      "std::string",
+      "std::unique_ptr",
+      "char",
+    ],
+  }
+};
diff --git a/public/colours.css b/public/colours.css
new file mode 100644
index 0000000..e43956c
--- /dev/null
+++ b/public/colours.css
@@ -0,0 +1,43 @@
+:root {
+  --background: #2e3440;
+  --background-text: #3b4252;
+  --border: #5e81ac;
+  --border2: #b48ead;
+  --text: #eceff4;
+  --link: #88c0d0;
+  --link-visited: #81a1c1;
+
+  --padding: 10px 20px;
+  --margin: 4px;
+  --gap: 10px;
+
+  --radius: 4px;
+}
+
+pre.codeblock {
+  --keyword: #ebcb8b;
+  --keyword-alt: #bf616a;
+  --string: #a3be8c;
+  --function: #b48ead;
+  --number: #81a1c1;
+}
+
+pre.codeblock span.keyword {
+  color: var(--keyword);
+}
+
+pre.codeblock span.keyword-alt {
+  color: var(--keyword-alt);
+}
+
+pre.codeblock span.string {
+  color: var(--string)
+}
+
+pre.codeblock span.function {
+  color: var(--function)
+}
+
+pre.codeblock span.number {
+  color: var(--number)
+}
diff --git a/public/css.css b/public/css.css
index bb9fba4..c256e10 100644
--- a/public/css.css
+++ b/public/css.css
@@ -1,20 +1,5 @@
 @import url(https://fonts.bunny.net/css?family=chivo-mono:500);
-
-:root {
-  --background: #2e3440;
-  --background-text: #3b4252;
-  --border: #5e81ac;
-  --border2: #b48ead;
-  --text: #eceff4;
-  --link: #88c0d0;
-  --link-visited: #81a1c1;
-
-  --padding: 10px 20px;
-  --margin: 4px;
-  --gap: 10px;
-
-  --radius: 4px;
-}
+@import url(/colours.css);
 
 code {
   background: var(--background-text);
@@ -23,6 +8,14 @@ code {
   border-radius: var(--radius);
 }
 
+pre.codeblock {
+  background: var(--background-text);
+  color: var(--text);
+  padding: 2px 4px;
+  border-radius: var(--radius);
+  width: 100%;
+}
+
 html {
   color: var(--text);
   background-color: var(--background);
@@ -31,6 +24,7 @@ html {
 
 body {
   background-color: initial !important;
+  font-variant-ligatures: none;
 }
 
 a {
diff --git a/public/md2html.js b/public/md2html.js
index 5d3e2a2..df197c6 100644
--- a/public/md2html.js
+++ b/public/md2html.js
@@ -16,6 +16,10 @@ const md2tokens = (markdown, newline) => {
     return false;
   }
 
+  let peek = (a) => {
+    return markdown[i + a];
+  }
+
   let advance = () => {
     i++;
     if (i < markdown.length)
@@ -25,8 +29,12 @@ const md2tokens = (markdown, newline) => {
   }
 
   let capture_until = (c) => {
+    return capture_until_predicate((i) => markdown[i] == c);
+  }
+
+  let capture_until_predicate = (f) => {
     let capture = "";
-    while (markdown[i] != c && markdown.length > i) {
+    while (!f(i) && markdown.length > i) {
       capture += markdown[i];
       i++;
     }
@@ -95,7 +103,6 @@ const md2tokens = (markdown, newline) => {
         let depth_he = 0;
         do {
           depth_he++;
-
         } while (match("#"));
 
         while (markdown[i] != "\n" && markdown.length > i) {
@@ -124,17 +131,19 @@ const md2tokens = (markdown, newline) => {
           break;
         }
 
-        let url = capture_until("\"");
-        advance();
-        let title = capture_until("\"");
-        advance();
+        let url = capture_until_predicate((i) => markdown[i] == "\"" || markdown[i] == ")");
+        let title = "";
+        if (match("\"")) { // has title
+          title = capture_until("\"");
+          advance();
+        }
         if (!match(")")) {
           current_string = `![${alt_text}](${url} "${title}"`;
           def();
           break;
         }
 
-        tokens.push({type:"image", alt: alt_text, url: url, title: title})
+        tokens.push({ type: "image", alt: alt_text, url: url, title: title })
         break;
 
       case '-':
@@ -153,25 +162,54 @@ const md2tokens = (markdown, newline) => {
           items.push(md2tokens(text, false));
         } while (match("-"));
 
-        tokens.push({type:"ul", items: items});
+        tokens.push({ type: "ul", items: items });
 
         break;
 
       case '\n':
         if (newline_count > 2)
           break;
-        
+
         newline = true;
         newline_count++;
         finish();
         tokens.push({ type: "newline" })
         break;
 
+      case '[':
+        finish();
+        advance();
+        let text = capture_until("]");
+        advance();
+
+        if (!match("(")) {
+          current_string = `[${text}]`;
+          def();
+          break;
+        }
+
+        let link = capture_until(")");
+
+        tokens.push({ type: "link", link: link, text: text });
+        break;
+
       case '`':
         finish();
         advance();
-        current_string = capture_until("`");
-        finish("inlinecode");
+        let stop = (i) => markdown[i] == "`";
+        let type_c = "inlinecode";
+        let language = "";
+        if (peek(0) == "`" && peek(1) == "`") { // multiline code block
+          advance(); advance();
+          language = capture_until("\n");
+          let f = (i) => markdown[i] == "`";
+          stop = (i) => f(i) && f(i + 1) && f(i + 2);
+          type_c = "codeblock";
+        }
+        current_string = capture_until_predicate(stop);
+        tokens.push({ type: type_c, content: current_string, language: language });
+        current_string = "";
+        if (type_c == "codeblock") { advance(); advance(); } // remove trailing `
         break;
 
       default:
@@ -187,6 +225,14 @@ const md2tokens = (markdown, newline) => {
   return tokens;
 };
 
+const highlight_code = (language, code) => {
+  let map = maps[language];
+  if (map == undefined)
+    return code;
+
+  return highlight(map, code);
+}
+
 const tokens2html = (tokens) => {
   let output = "";
 
@@ -208,7 +254,7 @@ const tokens2html = (tokens) => {
         output += "<br>"
         break;
       case "image":
-        output += `<p style="text-align:center"><img src="${token.url}" alt="${token.alt}"></p>`;
+        output += `<p style="text-align:center"><img src="${token.url}" alt="${token.alt}"><br>${token.title}</p>`;
         break
       case "ul":
         console.log(token);
@@ -221,6 +267,12 @@ const tokens2html = (tokens) => {
       case "inlinecode":
         output += `<code>${token.content}</code>`;
         break;
+      case "codeblock":
+        output += `<pre class="codeblock" data-language="${token.language}">${highlight_code(token.language, token.content)}</pre>`
+        break;
+      case "link":
+        output += `<a href="${token.link}">${token.text}</a>`;
+        break;
     }
   }
 
diff --git a/public/posts/index.html b/public/posts/index.html
index 5168d8f..1323957 100644
--- a/public/posts/index.html
+++ b/public/posts/index.html
@@ -25,6 +25,7 @@
       <a href="/" style="color: inherit; text-decoration: inherit"><h1>SpoodyThe<span style="color:var(--background-text)">.</span>One</h1></a>
     </div>
     <div class="content" style="grid-area: content; text-align: left">
+      <h2>Loading...</h2>
     </div>
     <div class="footer" style="grid-area: footer; text-align: center">
       <a href="#">Back to the top</a>
@@ -33,12 +34,10 @@
 
   <div id="background"></div>
   <script src="/main.js"></script>
+  <script src="/code_highlighter.js"></script>
   <script src="/md2html.js"></script>
   <script>
     (async () => {
-      // let terms = "Functions Vectors Evaluate Isolate Solve Matrix Tensor Calculus Derivation Limit Procedures Scopes Statements Interpreter Parser Lexer".split(" ");
-      // generate_background(terms);
-      // document.querySelector(".header").innerHTML = md2html({{}});
       try {
         let post = new URLSearchParams(window.location.search).get("post");
         let response = await fetch(`/posts/${post}.md`);
diff --git a/public/posts/openbirch.md b/public/posts/openbirch.md
index d8a898b..d9d0264 100644
--- a/public/posts/openbirch.md
+++ b/public/posts/openbirch.md
@@ -1,13 +1,5 @@
 Functions Vectors Evaluate Isolate Solve Matrix Tensor Calculus Derivation Limit Procedures Scopes Statements Interpreter Parser Lexer
 # Openbirch
-Openbirch is a programming language that I made together with my friend from school.
-It was an attempt at making a CAS tool to use instead of Maple (the UX is terrible).
+Cool fucking cas tool.
 
-The biggest challenge besides creating a programming language was creating a programming language
-where any variable could be undefined at runtime, but still used to define other variables.
-
-An example of this would be `x := 2*y`. Here `y` is not defined, but it is still used to define `x`.
-
-This was a unique challenge that was solved by representing everything as a *tree*
-
-![tree representation](/openbirch/tree.png "Tree Representation")
+![image of openbirch](/openbirch/openbirch.png "Cool image of the cool fucking cas tool")