From 2218a678b1ec8f68c2ec2d48a300783fe44f306f Mon Sep 17 00:00:00 2001
From: Adam Piontek <adam@73k.us>
Date: Mon, 5 Apr 2021 16:51:59 -0400
Subject: [PATCH] syntax highlighting and blog/post liveviews working; numerous
 other styling updates

---
 .gitignore                                    |   3 +
 .iex.exs                                      |   1 +
 README.md                                     |  31 +-
 assets/css/_bs-custom.scss                    |   8 +-
 assets/css/_fonts.scss                        |  48 ++--
 assets/css/_pygments.css                      | 269 ++++++++++++++++++
 assets/css/app.scss                           |  39 ++-
 assets/js/app.js                              |   3 +
 assets/package-lock.json                      | 133 +++++++--
 assets/package.json                           |   5 +-
 assets/webpack.config.js                      |   2 +-
 config/config.exs                             |   5 +-
 lib/home73k.ex                                |   6 +-
 lib/home73k/blog.ex                           |   4 +-
 lib/home73k/blog/post.ex                      |  16 +-
 lib/home73k/highlighter.ex                    |  52 ++++
 lib/home73k/temp.ex                           |  19 ++
 lib/home73k_web.ex                            |   3 +
 lib/home73k_web/live/blog_live.ex             |   3 -
 lib/home73k_web/live/blog_live.html.leex      |  14 +-
 lib/home73k_web/live/post_live.ex             |   3 -
 lib/home73k_web/live/post_live.html.leex      |  10 +-
 lib/home73k_web/templates/home/about.html.eex |   2 +-
 lib/home73k_web/templates/home/folio.html.eex |   2 +-
 lib/home73k_web/templates/home/index.html.eex |   2 +-
 .../templates/home/resume.html.eex            |   2 +-
 lib/home73k_web/views/date_helpers.ex         |   9 +
 mix.lock                                      |   1 +
 .../content/2018/03/2018-03-24_pihole-love.md |   2 +-
 ...-17_if-no-one-will-do-it-neednt-be-done.md |   2 +-
 ...lana-del-arr-ultrapirates-parody-lyrics.md |   2 +-
 .../05/2020-05-23_creating-gif-from-video.md  |   2 +-
 .../08/2020-08-01_enable-vs-cli-env-in-ps.md  |   2 +-
 .../2020-12-07_shame-american-leadership.md   |  23 ++
 .../2020/12/2020-12-29_moms-meatloaf.md       |   2 +-
 35 files changed, 610 insertions(+), 120 deletions(-)
 create mode 100644 assets/css/_pygments.css
 create mode 100644 lib/home73k/highlighter.ex
 create mode 100644 lib/home73k/temp.ex
 create mode 100644 lib/home73k_web/views/date_helpers.ex
 create mode 100644 priv/content/2020/12/2020-12-07_shame-american-leadership.md

diff --git a/.gitignore b/.gitignore
index de96d9a..c16d981 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,5 +39,8 @@ npm-debug.log
 # for vscode elixir_ls extension files
 /.elixir_ls
 
+# Ignore the pygments venv directory
+/priv/pygments/
+
 # dev
 TODO.md
diff --git a/.iex.exs b/.iex.exs
index 701cbda..0e09382 100644
--- a/.iex.exs
+++ b/.iex.exs
@@ -1,2 +1,3 @@
+alias Home73k.Temp
 alias Home73k.Blog
 alias Home73k.Blog.Post
diff --git a/README.md b/README.md
index eeafdf6..b18fcce 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,26 @@
 # Home73k
 
-To start your Phoenix server:
+Personal website with blog.
 
-  * Install dependencies with `mix deps.get`
-  * Install Node.js dependencies with `npm install` inside the `assets` directory
-  * Start Phoenix endpoint with `mix phx.server`
+## Blog posts
 
-Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
+Posts are markdown files stored under `priv/content` and parsed by [Earmark](https://hexdocs.pm/earmark/Earmark.html). This can be configured in `config.exs` under `config :home73k, :app_global_vars, blog_content: "path/to/content"`
 
-Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
+### Syntax highlighting
 
-## Learn more
+For the challenge of it, and to keep user's browsers from having to run javascript just to highlight some code, I chose to do server-side syntax highlighting.
 
-  * Official website: https://www.phoenixframework.org/
-  * Guides: https://hexdocs.pm/phoenix/overview.html
-  * Docs: https://hexdocs.pm/phoenix
-  * Forum: https://elixirforum.com/c/phoenix-forum
-  * Source: https://github.com/phoenixframework/phoenix
+Due to the lexer limitations of elixir-native solutions, the highlighter uses [Pygments](https://pygments.org/) by calling [pygmentize](https://pygments.org/docs/cmdline/) via [System.cmd](https://hexdocs.pm/elixir/System.html#cmd/3)
+
+However, this requires installing python3 & Pygments. Best way to do this is with a venv (virtual python environment), so you'll also want `python3-venv` installed on a debian system, for example.
+
+By default, Home73k is configured to look for pygmentize in a venv at `priv/pygments/bin/pygmentize` -- here's a quick howto for how to set that up:
+
+```shell
+cd priv
+python3 -m venv pygments
+source pygments/bin/activate
+pip3 install Pygments
+```
+
+The location of bin/pygmentize can be configured in `config.exs` under `config :home73k, :app_global_vars, pygmentize_bin: "path/to/bin/pygmentize"` 
diff --git a/assets/css/_bs-custom.scss b/assets/css/_bs-custom.scss
index a211a99..0fb2442 100644
--- a/assets/css/_bs-custom.scss
+++ b/assets/css/_bs-custom.scss
@@ -4,8 +4,8 @@
 //
 // Font, line-height, and color for body text, headings, and more.
 
-// stylelint-disable value-keyword-case
-$font-family-sans-serif: "Work Sans", system-ui, -apple-system, "Segoe UI", Roboto,
+$font-size-base: 1.125rem;
+$font-family-sans-serif: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto,
   "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
   "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
 $font-family-base: $font-family-sans-serif;
@@ -14,10 +14,8 @@ $font-family-brand: Righteous, system-ui, -apple-system, "Segoe UI", Roboto,
   "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
   "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
 
-$font-family-monospace: "Fira Mono", SFMono-Regular, Menlo, Monaco, Consolas,
+$font-family-monospace: "Fira Code", SFMono-Regular, Menlo, Monaco, Consolas,
   "Liberation Mono", "Courier New", monospace !important;
-$font-family-code: "Fira Code", "Fira Mono", SFMono-Regular, Menlo, Monaco,
-  Consolas, "Liberation Mono", "Courier New", monospace !important;
 
 // Features
 $enable-shadows: true;
diff --git a/assets/css/_fonts.scss b/assets/css/_fonts.scss
index 145e8d2..a96629e 100644
--- a/assets/css/_fonts.scss
+++ b/assets/css/_fonts.scss
@@ -1,35 +1,23 @@
-/* Fontsource Work Sans */
-@import "../node_modules/@fontsource/work-sans/100.css";        /* thin | normal */
-@import "../node_modules/@fontsource/work-sans/100-italic.css"; /* thin | italic */
-// @import "../node_modules/@fontsource/work-sans/200.css"; /* thin-light | normal */
-// @import "../node_modules/@fontsource/work-sans/200-italic.css"; /* thin-light | italic */
-@import "../node_modules/@fontsource/work-sans/300.css"; /* light | normal */
-@import "../node_modules/@fontsource/work-sans/300-italic.css"; /* light | italic */
-@import "../node_modules/@fontsource/work-sans/400.css"; /* normal | normal */
-@import "../node_modules/@fontsource/work-sans/400-italic.css"; /* normal | italic */
-// @import "../node_modules/@fontsource/work-sans/500.css"; /* heavier | normal */
-// @import "../node_modules/@fontsource/work-sans/500-italic.css"; /* heavier | italic */
-@import "../node_modules/@fontsource/work-sans/600.css"; /* heavier? | normal */
-@import "../node_modules/@fontsource/work-sans/600-italic.css"; /* heavier | italic */
-@import "../node_modules/@fontsource/work-sans/700.css"; /* bold | normal */
-@import "../node_modules/@fontsource/work-sans/700-italic.css"; /* bold | italic */
-// @import "../node_modules/@fontsource/work-sans/800.css"; /* bolder? | normal */
-// @import "../node_modules/@fontsource/work-sans/800-italic.css"; /* bolder? | italic */
-// @import "../node_modules/@fontsource/work-sans/900.css";        /* black | normal */
-// @import "../node_modules/@fontsource/work-sans/900-italic.css"; /* black | italic */
-
-
-/* Fontsource Righteous */
+/* brand : Fontsource Righteous */
 @import "../node_modules/@fontsource/righteous/400.css"; /* normal | normal */
 
-/* Fontsource Fira Mono */
-@import "../node_modules/@fontsource/fira-mono/400.css"; /* normal | normal */
-@import "../node_modules/@fontsource/fira-mono/500.css"; /* heavier normal? */
-// @import "../node_modules/@fontsource/fira-mono/700.css"; /* bold | normal */
-
-/* Fontsource Fira Code */
+/* monospace : Fontsource Fira Code */
 @import "../node_modules/@fontsource/fira-code/300.css"; /* light | normal */
 @import "../node_modules/@fontsource/fira-code/400.css"; /* normal | normal */
-// @import "../node_modules/@fontsource/fira-code/500.css"; /* heavier normal? */
-// @import "../node_modules/@fontsource/fira-code/600.css"; /* heavier normal? */
+@import "../node_modules/@fontsource/fira-code/500.css"; /* heavier normal? */
+@import "../node_modules/@fontsource/fira-code/600.css"; /* heavier normal? */
 @import "../node_modules/@fontsource/fira-code/700.css"; /* bold | normal */
+
+
+/* Fontsource Open Sans */
+@import "../node_modules/@fontsource/open-sans/300.css"; /* light | normal */
+@import "../node_modules/@fontsource/open-sans/300-italic.css"; /* light | italic */
+@import "../node_modules/@fontsource/open-sans/400.css"; /* normal | normal */
+@import "../node_modules/@fontsource/open-sans/400-italic.css"; /* normal | italic */
+@import "../node_modules/@fontsource/open-sans/600.css"; /* heavier? | normal */
+@import "../node_modules/@fontsource/open-sans/600-italic.css"; /* heavier | italic */
+@import "../node_modules/@fontsource/open-sans/700.css"; /* bold | normal */
+@import "../node_modules/@fontsource/open-sans/700-italic.css"; /* bold | italic */
+@import "../node_modules/@fontsource/open-sans/800.css"; /* bolder? | normal */
+@import "../node_modules/@fontsource/open-sans/800-italic.css"; /* bolder? | italic */
+
diff --git a/assets/css/_pygments.css b/assets/css/_pygments.css
new file mode 100644
index 0000000..2931fbd
--- /dev/null
+++ b/assets/css/_pygments.css
@@ -0,0 +1,269 @@
+pre {
+  line-height: 125%;
+}
+td.linenos .normal {
+  color: #37474f;
+  background-color: #263238;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+span.linenos {
+  color: #37474f;
+  background-color: #263238;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+td.linenos .special {
+  color: #607a86;
+  background-color: #263238;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+span.linenos.special {
+  color: #607a86;
+  background-color: #263238;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+pre.pygments .hll {
+  background-color: #2c3b41;
+}
+pre.pygments {
+  background: #263238;
+  color: #eeffff;
+}
+pre.pygments .c {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment */
+pre.pygments .err {
+  color: #ff5370;
+} /* Error */
+pre.pygments .esc {
+  color: #89ddff;
+} /* Escape */
+pre.pygments .g {
+  color: #eeffff;
+} /* Generic */
+pre.pygments .k {
+  color: #bb80b3;
+} /* Keyword */
+pre.pygments .l {
+  color: #c3e88d;
+} /* Literal */
+pre.pygments .n {
+  color: #eeffff;
+} /* Name */
+pre.pygments .o {
+  color: #89ddff;
+} /* Operator */
+pre.pygments .p {
+  color: #89ddff;
+} /* Punctuation */
+pre.pygments .ch {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment.Hashbang */
+pre.pygments .cm {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment.Multiline */
+pre.pygments .cp {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment.Preproc */
+pre.pygments .cpf {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment.PreprocFile */
+pre.pygments .c1 {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment.Single */
+pre.pygments .cs {
+  color: #546e7a;
+  font-style: italic;
+} /* Comment.Special */
+pre.pygments .gd {
+  color: #ff5370;
+} /* Generic.Deleted */
+pre.pygments .ge {
+  color: #89ddff;
+} /* Generic.Emph */
+pre.pygments .gr {
+  color: #ff5370;
+} /* Generic.Error */
+pre.pygments .gh {
+  color: #c3e88d;
+} /* Generic.Heading */
+pre.pygments .gi {
+  color: #c3e88d;
+} /* Generic.Inserted */
+pre.pygments .go {
+  color: #546e7a;
+} /* Generic.Output */
+pre.pygments .gp {
+  color: #ffcb6b;
+} /* Generic.Prompt */
+pre.pygments .gs {
+  color: #ff5370;
+} /* Generic.Strong */
+pre.pygments .gu {
+  color: #89ddff;
+} /* Generic.Subheading */
+pre.pygments .gt {
+  color: #ff5370;
+} /* Generic.Traceback */
+pre.pygments .kc {
+  color: #89ddff;
+} /* Keyword.Constant */
+pre.pygments .kd {
+  color: #bb80b3;
+} /* Keyword.Declaration */
+pre.pygments .kn {
+  color: #89ddff;
+  font-style: italic;
+} /* Keyword.Namespace */
+pre.pygments .kp {
+  color: #89ddff;
+} /* Keyword.Pseudo */
+pre.pygments .kr {
+  color: #bb80b3;
+} /* Keyword.Reserved */
+pre.pygments .kt {
+  color: #bb80b3;
+} /* Keyword.Type */
+pre.pygments .ld {
+  color: #c3e88d;
+} /* Literal.Date */
+pre.pygments .m {
+  color: #f78c6c;
+} /* Literal.Number */
+pre.pygments .s {
+  color: #c3e88d;
+} /* Literal.String */
+pre.pygments .na {
+  color: #bb80b3;
+} /* Name.Attribute */
+pre.pygments .nb {
+  color: #82aaff;
+} /* Name.Builtin */
+pre.pygments .nc {
+  color: #ffcb6b;
+} /* Name.Class */
+pre.pygments .no {
+  color: #eeffff;
+} /* Name.Constant */
+pre.pygments .nd {
+  color: #82aaff;
+} /* Name.Decorator */
+pre.pygments .ni {
+  color: #89ddff;
+} /* Name.Entity */
+pre.pygments .ne {
+  color: #ffcb6b;
+} /* Name.Exception */
+pre.pygments .nf {
+  color: #82aaff;
+} /* Name.Function */
+pre.pygments .nl {
+  color: #82aaff;
+} /* Name.Label */
+pre.pygments .nn {
+  color: #ffcb6b;
+} /* Name.Namespace */
+pre.pygments .nx {
+  color: #eeffff;
+} /* Name.Other */
+pre.pygments .py {
+  color: #ffcb6b;
+} /* Name.Property */
+pre.pygments .nt {
+  color: #ff5370;
+} /* Name.Tag */
+pre.pygments .nv {
+  color: #89ddff;
+} /* Name.Variable */
+pre.pygments .ow {
+  color: #89ddff;
+  font-style: italic;
+} /* Operator.Word */
+pre.pygments .w {
+  color: #eeffff;
+} /* Text.Whitespace */
+pre.pygments .mb {
+  color: #f78c6c;
+} /* Literal.Number.Bin */
+pre.pygments .mf {
+  color: #f78c6c;
+} /* Literal.Number.Float */
+pre.pygments .mh {
+  color: #f78c6c;
+} /* Literal.Number.Hex */
+pre.pygments .mi {
+  color: #f78c6c;
+} /* Literal.Number.Integer */
+pre.pygments .mo {
+  color: #f78c6c;
+} /* Literal.Number.Oct */
+pre.pygments .sa {
+  color: #bb80b3;
+} /* Literal.String.Affix */
+pre.pygments .sb {
+  color: #c3e88d;
+} /* Literal.String.Backtick */
+pre.pygments .sc {
+  color: #c3e88d;
+} /* Literal.String.Char */
+pre.pygments .dl {
+  color: #eeffff;
+} /* Literal.String.Delimiter */
+pre.pygments .sd {
+  color: #546e7a;
+  font-style: italic;
+} /* Literal.String.Doc */
+pre.pygments .s2 {
+  color: #c3e88d;
+} /* Literal.String.Double */
+pre.pygments .se {
+  color: #eeffff;
+} /* Literal.String.Escape */
+pre.pygments .sh {
+  color: #c3e88d;
+} /* Literal.String.Heredoc */
+pre.pygments .si {
+  color: #89ddff;
+} /* Literal.String.Interpol */
+pre.pygments .sx {
+  color: #c3e88d;
+} /* Literal.String.Other */
+pre.pygments .sr {
+  color: #89ddff;
+} /* Literal.String.Regex */
+pre.pygments .s1 {
+  color: #c3e88d;
+} /* Literal.String.Single */
+pre.pygments .ss {
+  color: #89ddff;
+} /* Literal.String.Symbol */
+pre.pygments .bp {
+  color: #89ddff;
+} /* Name.Builtin.Pseudo */
+pre.pygments .fm {
+  color: #82aaff;
+} /* Name.Function.Magic */
+pre.pygments .vc {
+  color: #89ddff;
+} /* Name.Variable.Class */
+pre.pygments .vg {
+  color: #89ddff;
+} /* Name.Variable.Global */
+pre.pygments .vi {
+  color: #89ddff;
+} /* Name.Variable.Instance */
+pre.pygments .vm {
+  color: #82aaff;
+} /* Name.Variable.Magic */
+pre.pygments .il {
+  color: #f78c6c;
+} /* Literal.Number.Integer.Long */
diff --git a/assets/css/app.scss b/assets/css/app.scss
index 54ad96b..7754710 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -16,6 +16,29 @@
 /* Navbar custom styling */
 @import "nav-bar-help";
 
+/* Pygments syntax highlighting styles */
+@import "pygments";
+
+/* code highlighting */
+pre.pygments {
+  line-height: 125%;
+  background-color: #272822 !important;
+  overflow: wrap !important;
+  padding: 0.75rem;
+  border-radius: .25em;
+  code {
+    font-family: $font-family-code;
+    color: #eeffff;
+  }
+}
+code.inline {
+  background-color: #272822;
+  color: #eeffff;
+  padding: .2em .5em;
+  border-radius: .25em;
+  display: inline;
+}
+
 /* main */
 html,
 body {
@@ -24,12 +47,12 @@ body {
   height: 100%;
 }
 a {
-  color: $primary;
+  color: $secondary;
   &:visited {
     color: $info;
   }
   &:hover {
-    color: $secondary;
+    color: $primary;
   }
 }
 .border-gray-900 {
@@ -59,15 +82,9 @@ a {
 .fw-600 {
   font-weight: 600;
 }
-.font-sans-serif {
-  font-family: $font-family-sans-serif;
-}
 .font-brand {
   font-family: $font-family-brand;
 }
-.font-code {
-  font-family: $font-family-code;
-}
 .fs-larger {
   font-size: larger;
 }
@@ -124,10 +141,8 @@ a {
     margin-top: 2rem;
   }
 }
-.post-lede.lead {
-  font-size: 1.25rem;
-  font-weight: 300;
-  color: $gray-300;
+.post li {
+  line-height: 1.75rem;
 }
 
 /* extra */
diff --git a/assets/js/app.js b/assets/js/app.js
index 3a2ff03..925e425 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -43,6 +43,9 @@ import { Socket } from "phoenix";
 import topbar from "topbar";
 import { LiveSocket } from "phoenix_live_view";
 
+// // Prismjs import
+// import Prism from "prismjs"
+
 // // Bootstrap v5 js imports
 // import "bootstrap/js/dist/alert";
 import "bootstrap/js/dist/collapse";
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 6237e00..2d34020 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -7,17 +7,18 @@
       "license": "MIT",
       "dependencies": {
         "@fontsource/fira-code": "^4.x",
-        "@fontsource/fira-mono": "^4.x",
+        "@fontsource/open-sans": "^4.2.2",
         "@fontsource/righteous": "^4.x",
-        "@fontsource/work-sans": "^4.2.2",
         "@mdi/svg": "^5.x",
         "@popperjs/core": "^2.x",
+        "babel-plugin-prismjs": "^2.0.1",
         "bootstrap": "^5.0.0-beta3",
         "bootstrap-icons": "^1.x",
         "hamburgers": "^1.x",
         "phoenix": "file:../deps/phoenix",
         "phoenix_html": "file:../deps/phoenix_html",
         "phoenix_live_view": "file:../deps/phoenix_live_view",
+        "prismjs": "^1.23.0",
         "topbar": "^1.x"
       },
       "devDependencies": {
@@ -1287,21 +1288,16 @@
       "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz",
       "integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ=="
     },
-    "node_modules/@fontsource/fira-mono": {
+    "node_modules/@fontsource/open-sans": {
       "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz",
-      "integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA=="
+      "resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.2.2.tgz",
+      "integrity": "sha512-NbsL1a9asJO6N/5kRxYPCy0kNhKMi9T75kl4QfIGtmpd/5IfB+UIAUxd9AICmCLaH4Osc2TImeTJj94xc9MNKg=="
     },
     "node_modules/@fontsource/righteous": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
       "integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg=="
     },
-    "node_modules/@fontsource/work-sans": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-4.2.2.tgz",
-      "integrity": "sha512-fFm8a1TbE+qDnRNsf4R6Z/yZP2f3mbj54zSoWUMIWVkgTHM0RfpcOTqoIp7Aj5jon6na0Oynlq5yXvMA5p3pKA=="
-    },
     "node_modules/@mdi/svg": {
       "version": "5.9.55",
       "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",
@@ -1876,6 +1872,14 @@
         "@babel/core": "^7.0.0-0"
       }
     },
+    "node_modules/babel-plugin-prismjs": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-prismjs/-/babel-plugin-prismjs-2.0.1.tgz",
+      "integrity": "sha512-GqQGa3xX3Z2ft97oDbGvEFoxD8nKqb3ZVszrOc5H7icnEUA56BIjVYm86hfZZA82uuHLwTIfCXbEKzKG1BzKzg==",
+      "peerDependencies": {
+        "prismjs": "^1.18.0"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -2315,6 +2319,17 @@
         "node": ">=6"
       }
     },
+    "node_modules/clipboard": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
+      "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
+      "optional": true,
+      "dependencies": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
     "node_modules/cliui": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -3133,6 +3148,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
+      "optional": true
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3946,6 +3967,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+      "optional": true,
+      "dependencies": {
+        "delegate": "^3.1.2"
+      }
+    },
     "node_modules/graceful-fs": {
       "version": "4.2.6",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@@ -7588,6 +7618,14 @@
         "posthtml-render": "^1.0.6"
       }
     },
+    "node_modules/prismjs": {
+      "version": "1.23.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
+      "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
+      "optionalDependencies": {
+        "clipboard": "^2.0.0"
+      }
+    },
     "node_modules/promise-inflight": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -8099,6 +8137,12 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
+      "optional": true
+    },
     "node_modules/semver": {
       "version": "6.3.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -9164,6 +9208,12 @@
       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
       "dev": true
     },
+    "node_modules/tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
+      "optional": true
+    },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -10914,21 +10964,16 @@
       "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz",
       "integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ=="
     },
-    "@fontsource/fira-mono": {
+    "@fontsource/open-sans": {
       "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz",
-      "integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA=="
+      "resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.2.2.tgz",
+      "integrity": "sha512-NbsL1a9asJO6N/5kRxYPCy0kNhKMi9T75kl4QfIGtmpd/5IfB+UIAUxd9AICmCLaH4Osc2TImeTJj94xc9MNKg=="
     },
     "@fontsource/righteous": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
       "integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg=="
     },
-    "@fontsource/work-sans": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-4.2.2.tgz",
-      "integrity": "sha512-fFm8a1TbE+qDnRNsf4R6Z/yZP2f3mbj54zSoWUMIWVkgTHM0RfpcOTqoIp7Aj5jon6na0Oynlq5yXvMA5p3pKA=="
-    },
     "@mdi/svg": {
       "version": "5.9.55",
       "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",
@@ -11394,6 +11439,12 @@
         "@babel/helper-define-polyfill-provider": "^0.1.5"
       }
     },
+    "babel-plugin-prismjs": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-prismjs/-/babel-plugin-prismjs-2.0.1.tgz",
+      "integrity": "sha512-GqQGa3xX3Z2ft97oDbGvEFoxD8nKqb3ZVszrOc5H7icnEUA56BIjVYm86hfZZA82uuHLwTIfCXbEKzKG1BzKzg==",
+      "requires": {}
+    },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -11738,6 +11789,17 @@
       "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
       "dev": true
     },
+    "clipboard": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
+      "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
+      "optional": true,
+      "requires": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
     "cliui": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -12356,6 +12418,12 @@
         "is-descriptor": "^1.0.0"
       }
     },
+    "delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
+      "optional": true
+    },
     "dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -12979,6 +13047,15 @@
         "slash": "^3.0.0"
       }
     },
+    "good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+      "optional": true,
+      "requires": {
+        "delegate": "^3.1.2"
+      }
+    },
     "graceful-fs": {
       "version": "4.2.6",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@@ -15706,6 +15783,14 @@
         "posthtml-render": "^1.0.6"
       }
     },
+    "prismjs": {
+      "version": "1.23.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
+      "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
+      "requires": {
+        "clipboard": "^2.0.0"
+      }
+    },
     "promise-inflight": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -16072,6 +16157,12 @@
         "ajv-keywords": "^3.5.2"
       }
     },
+    "select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
+      "optional": true
+    },
     "semver": {
       "version": "6.3.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -16930,6 +17021,12 @@
       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
       "dev": true
     },
+    "tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
+      "optional": true
+    },
     "to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
diff --git a/assets/package.json b/assets/package.json
index 98bc41b..f63b9f5 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -8,17 +8,18 @@
   },
   "dependencies": {
     "@fontsource/fira-code": "^4.x",
-    "@fontsource/fira-mono": "^4.x",
+    "@fontsource/open-sans": "^4.2.2",
     "@fontsource/righteous": "^4.x",
-    "@fontsource/work-sans": "^4.2.2",
     "@mdi/svg": "^5.x",
     "@popperjs/core": "^2.x",
+    "babel-plugin-prismjs": "^2.0.1",
     "bootstrap": "^5.0.0-beta3",
     "bootstrap-icons": "^1.x",
     "hamburgers": "^1.x",
     "phoenix": "file:../deps/phoenix",
     "phoenix_html": "file:../deps/phoenix_html",
     "phoenix_live_view": "file:../deps/phoenix_live_view",
+    "prismjs": "^1.23.0",
     "topbar": "^1.x"
   },
   "devDependencies": {
diff --git a/assets/webpack.config.js b/assets/webpack.config.js
index 65d8d8a..1c82cd8 100644
--- a/assets/webpack.config.js
+++ b/assets/webpack.config.js
@@ -93,7 +93,7 @@ module.exports = (env, options) => {
                 "../**/live/**/*.ex",
                 "./js/**/*.js",
               ]),
-              safelist: [/phx/, /topbar/],
+              safelist: [/phx/, /topbar/, /linenos/, /pygments/],
             }),
           ]
     ),
diff --git a/config/config.exs b/config/config.exs
index 2b6d096..43a2dfd 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -11,7 +11,10 @@ use Mix.Config
 config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
 
 # Custom application global variables
-config :home73k, :app_global_vars, time_zone: "America/New_York"
+config :home73k, :app_global_vars,
+  time_zone: "America/New_York",
+  blog_content: "priv/content",
+  pygmentize_bin: "priv/pygments/bin/pygmentize"
 
 # Configures the endpoint
 config :home73k, Home73kWeb.Endpoint,
diff --git a/lib/home73k.ex b/lib/home73k.ex
index 44aa497..b7c5af1 100644
--- a/lib/home73k.ex
+++ b/lib/home73k.ex
@@ -8,7 +8,9 @@ defmodule Home73k do
   """
 
   @app_vars Application.compile_env(:home73k, :app_global_vars, time_zone: "America/New_York")
-  @app_time_zone @app_vars[:time_zone]
 
-  def app_time_zone, do: @app_time_zone
+  def app_vars, do: @app_vars
+  def app_time_zone, do: @app_vars[:time_zone]
+  def app_blog_content, do: @app_vars[:blog_content]
+  def app_pygmentize_bin, do: @app_vars[:pygmentize_bin]
 end
diff --git a/lib/home73k/blog.ex b/lib/home73k/blog.ex
index 806d291..7f002f6 100644
--- a/lib/home73k/blog.ex
+++ b/lib/home73k/blog.ex
@@ -3,7 +3,7 @@ defmodule Home73k.Blog do
 
   Application.ensure_all_started(:earmark)
 
-  posts_paths = "priv/content/**/*.md" |> Path.wildcard()
+  posts_paths = "#{Home73k.app_blog_content()}/**/*.md" |> Path.wildcard()
 
   posts =
     for post_path <- posts_paths do
@@ -11,7 +11,7 @@ defmodule Home73k.Blog do
       Post.parse!(post_path)
     end
 
-  @posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime})
+  @posts Enum.sort_by(posts, & &1.date, {:desc, Date})
 
   @tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort()
 
diff --git a/lib/home73k/blog/post.ex b/lib/home73k/blog/post.ex
index 1a5d1a6..c5ef717 100644
--- a/lib/home73k/blog/post.ex
+++ b/lib/home73k/blog/post.ex
@@ -1,4 +1,6 @@
 defmodule Home73k.Blog.Post do
+  alias Home73k.Highlighter
+
   @enforce_keys [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
   defstruct [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
 
@@ -59,12 +61,10 @@ defmodule Home73k.Blog.Post do
   defp parse_lede(_), do: nil
 
   # """ parse_body/1
-  # Convert body markdown to html
-  # TODO: handle syntax highlighting
+  # Convert body markdown to html, and highlight code fence blocks
   defp parse_body({fm, md}) do
-    Map.put(fm, :body, Earmark.as_html!(md))
-    # TODO: Earmark.as_ast(md) |> parse_body(fm)
-    # def parse_body({:ok, ast, _}, fm)
+    html = Earmark.as_html!(md) |> Highlighter.highlight_code_blocks()
+    Map.put(fm, :body, html)
   end
 
   defp parse_body(_), do: nil
@@ -128,8 +128,10 @@ defmodule Home73k.Blog.Post do
   # Handle split of post body. If lede found, return as html with body.
   # Otherwise return nil with body.
   # """
-  defp extract_lede([lede, body]),
-    do: {String.trim_trailing(lede) |> Earmark.as_html!(), String.trim_leading(body)}
+  defp extract_lede([lede, body]) do
+    lede_html = String.trim_trailing(lede) |> Earmark.as_html!() |> Highlighter.highlight_code_blocks()
+    {lede_html, String.trim_leading(body)}
+  end
 
   defp extract_lede([body]), do: {nil, body}
 
diff --git a/lib/home73k/highlighter.ex b/lib/home73k/highlighter.ex
new file mode 100644
index 0000000..b76ff18
--- /dev/null
+++ b/lib/home73k/highlighter.ex
@@ -0,0 +1,52 @@
+defmodule Home73k.Highlighter do
+  @moduledoc """
+  Performs code highlighting.
+  """
+
+  alias Home73k.Temp
+
+  @pygments_cmd Home73k.app_pygmentize_bin() |> Path.expand()
+
+  @doc """
+  Highlights all code block in an already generated HTML document.
+  """
+  def highlight_code_blocks(html) do
+    ~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
+    |> Regex.replace(html, &highlight_code_block(&1, &2, &3))
+  end
+
+  defp highlight_code_block(_full_block, lang, code) do
+    # unescape the code
+    unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
+
+    # write code to temp file
+    tmp_file = Temp.file()
+    File.write!(tmp_file, unescaped_code)
+
+    # pygmentize the code via temp file
+    pyg_args = ["-l", lang, "-f", "html", "-O", "cssclass=pygments", tmp_file]
+    {highlighted, _} = System.cmd(@pygments_cmd, pyg_args)
+
+    # correct pygment wrapping markup
+    highlighted
+    |> String.replace("<span></span>", "")
+    |> String.replace("<div class=\"pygments\"><pre>", "<pre class=\"pygments\"><code class=\"language-#{lang}\">")
+    |> String.replace("</pre></div>", "</code></pre>")
+  end
+
+  entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]
+
+  for {encoded, decoded} <- entities do
+    defp unescape_html(unquote(encoded) <> rest) do
+      [unquote(decoded) | unescape_html(rest)]
+    end
+  end
+
+  defp unescape_html(<<c, rest::binary>>) do
+    [c | unescape_html(rest)]
+  end
+
+  defp unescape_html(<<>>) do
+    []
+  end
+end
diff --git a/lib/home73k/temp.ex b/lib/home73k/temp.ex
new file mode 100644
index 0000000..08f78fe
--- /dev/null
+++ b/lib/home73k/temp.ex
@@ -0,0 +1,19 @@
+defmodule Home73k.Temp do
+  @moduledoc """
+  Simple module to generate temporary files
+  """
+  def file do
+    System.tmp_dir!()
+    |> Path.join(random_filename())
+    |> touch_file()
+  end
+
+  defp random_filename do
+    :crypto.strong_rand_bytes(32) |> Base.url_encode64 |> binary_part(0, 32)
+  end
+
+  defp touch_file(fdname) do
+    File.touch!(fdname)
+    fdname
+  end
+end
diff --git a/lib/home73k_web.ex b/lib/home73k_web.ex
index 29f5403..6da50d5 100644
--- a/lib/home73k_web.ex
+++ b/lib/home73k_web.ex
@@ -93,6 +93,9 @@ defmodule Home73kWeb do
       # Import SVG Icon helper
       import Home73kWeb.IconHelpers
 
+      # Import Date formatter helper
+      import Home73kWeb.DateHelpers
+
       import Home73kWeb.ErrorHelpers
       import Home73kWeb.Gettext
       alias Home73kWeb.Router.Helpers, as: Routes
diff --git a/lib/home73k_web/live/blog_live.ex b/lib/home73k_web/live/blog_live.ex
index fb8a99c..6100d73 100644
--- a/lib/home73k_web/live/blog_live.ex
+++ b/lib/home73k_web/live/blog_live.ex
@@ -48,7 +48,4 @@ defmodule Home73kWeb.BlogLive do
   #       do: {app, vsn}
   # end
 
-  defp format_date(date) do
-    Calendar.strftime(date, "%B %-d, %Y")
-  end
 end
diff --git a/lib/home73k_web/live/blog_live.html.leex b/lib/home73k_web/live/blog_live.html.leex
index 1e055c9..5dd8a55 100644
--- a/lib/home73k_web/live/blog_live.html.leex
+++ b/lib/home73k_web/live/blog_live.html.leex
@@ -1,22 +1,22 @@
 <main class="container d-flex justify-content-center">
 
-  <div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 pb-2 mb-4 mt-3">
+  <div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 pb-2 mb-4 mt-3">
 
     <%= for post <- @posts do %>
 
       <div class="post border-bottom border-gray pb-4 mb-3">
 
-        <h2 class="post-title fs-2 fw-normal mb-2">
+        <h2 class="post-title fs-2 fw-600 mb-2">
           <%=  live_redirect "#{post.title}", to: Routes.post_path(@socket, :show, post) %>
         </h2>
 
-        <div class="post-date font-monospace text-gray-500 <%= if length(post.tags) == 0, do: "mb-3" %>">
-          <%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
-          <%= post.date |> format_date() %>
+        <div class="post-date font-monospace text-gray-400 <%= if length(post.tags) == 0, do: "mb-3" %>">
+          <%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= post.date |> format_date() %>
+          by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
         </div>
 
         <%= if length(post.tags) > 0 do %>
-          <div class="post-tags fs-6 mb-3">
+          <div class="post-tags fs-smaller mb-3">
           <%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
           <%= for {tag, i} <- Enum.with_index(post.tags) do %>
             #<%= tag %><%= i < (length(post.tags) - 1) && "," || "" %>
@@ -24,7 +24,7 @@
           </div>
         <% end %>
 
-        <div class="post-lede lead">
+        <div class="post-lede">
           <%= raw post.lede %>
         </div>
 
diff --git a/lib/home73k_web/live/post_live.ex b/lib/home73k_web/live/post_live.ex
index dbdb003..781322f 100644
--- a/lib/home73k_web/live/post_live.ex
+++ b/lib/home73k_web/live/post_live.ex
@@ -55,7 +55,4 @@ defmodule Home73kWeb.PostLive do
   #       do: {app, vsn}
   # end
 
-  defp format_date(date) do
-    Calendar.strftime(date, "%B %-d, %Y")
-  end
 end
diff --git a/lib/home73k_web/live/post_live.html.leex b/lib/home73k_web/live/post_live.html.leex
index 6ed92d1..3b2b9d1 100644
--- a/lib/home73k_web/live/post_live.html.leex
+++ b/lib/home73k_web/live/post_live.html.leex
@@ -6,13 +6,13 @@
 
         <h2 class="post-title fs-2 fw-normal mb-2"><%= raw @post.title %></h2>
 
-        <div class="post-date font-monospace text-gray-500 <%= if length(@post.tags) == 0, do: "mb-3" %>">
-          <%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
-          <%= @post.date |> format_date() %>
+        <div class="post-date font-monospace text-gray-400 <%= if length(@post.tags) == 0, do: "mb-3" %>">
+          <%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= @post.date |> format_date() %>
+          by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
         </div>
 
         <%= if length(@post.tags) > 0 do %>
-          <div class="post-tags  fs-6 mb-3">
+          <div class="post-tags  fs-smaller mb-3">
           <%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
           <%= for {tag, i} <- Enum.with_index(@post.tags) do %>
             #<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %>
@@ -20,7 +20,7 @@
           </div>
         <% end %>
 
-        <div class="post-lede lead">
+        <div class="post-lede">
         <%= raw @post.lede %>
         </div>
 
diff --git a/lib/home73k_web/templates/home/about.html.eex b/lib/home73k_web/templates/home/about.html.eex
index 36637ee..12d55d0 100644
--- a/lib/home73k_web/templates/home/about.html.eex
+++ b/lib/home73k_web/templates/home/about.html.eex
@@ -1,6 +1,6 @@
 <main class="container d-flex justify-content-center">
 
-  <div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 border-bottom border-gray pb-3 mb-5 mt-3">
+  <div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 border-bottom border-gray pb-3 mb-5 mt-3">
 
     <h2 class="fs-2 fw-600 mb-0">About</h2>
 
diff --git a/lib/home73k_web/templates/home/folio.html.eex b/lib/home73k_web/templates/home/folio.html.eex
index a94deec..22c078b 100644
--- a/lib/home73k_web/templates/home/folio.html.eex
+++ b/lib/home73k_web/templates/home/folio.html.eex
@@ -13,7 +13,7 @@
 
       <div class="col-12 col-sm-10 col-md-7 col-lg-6 col-xl-5 col-xxl-4 justify-content-start ms-lg-3">
 
-        <h2 class="font-monospace fs-2 fw-500 mb-0">
+        <h2 class="fs-2 fw-600 mb-0">
           <%= icon_div @conn, "mdi-account-hard-hat", [class: "icon baseline me-2"] %><span>Working on it!</span>
         </h2>
 
diff --git a/lib/home73k_web/templates/home/index.html.eex b/lib/home73k_web/templates/home/index.html.eex
index 83a7720..5600183 100644
--- a/lib/home73k_web/templates/home/index.html.eex
+++ b/lib/home73k_web/templates/home/index.html.eex
@@ -13,7 +13,7 @@
 
     <div class="col-auto justify-content-start ms-lg-3">
 
-      <h2 class="font-monospace fs-2 fw-500 mb-0">
+      <h2 class="fs-2 fw-600 mb-0">
         <%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
       </h2>
 
diff --git a/lib/home73k_web/templates/home/resume.html.eex b/lib/home73k_web/templates/home/resume.html.eex
index 1b8f2cc..5725230 100644
--- a/lib/home73k_web/templates/home/resume.html.eex
+++ b/lib/home73k_web/templates/home/resume.html.eex
@@ -4,7 +4,7 @@
 
     <div class="col-auto justify-content-start">
 
-      <h2 class="font-monospace fs-2 fw-500 mb-0">
+      <h2 class="fs-2 fw-600 mb-0">
         <%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
       </h2>
 
diff --git a/lib/home73k_web/views/date_helpers.ex b/lib/home73k_web/views/date_helpers.ex
new file mode 100644
index 0000000..c795cff
--- /dev/null
+++ b/lib/home73k_web/views/date_helpers.ex
@@ -0,0 +1,9 @@
+defmodule Home73kWeb.DateHelpers do
+  @moduledoc """
+  Formatters for dates
+  """
+
+  def format_date(date) do
+    Calendar.strftime(date, "%B %-d, %Y")
+  end
+end
diff --git a/mix.lock b/mix.lock
index 4fc7c6b..5c2e7a0 100644
--- a/mix.lock
+++ b/mix.lock
@@ -44,6 +44,7 @@
   "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
   "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},
   "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
+  "temp": {:hex, :temp, "0.4.7", "2c78482cc2294020a4bc0c95950b907ff386523367d4e63308a252feffbea9f2", [:mix], [], "hexpm", "6af19e7d6a85a427478be1021574d1ae2a1e1b90882586f06bde76c63cd03e0d"},
   "toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"},
   "tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
diff --git a/priv/content/2018/03/2018-03-24_pihole-love.md b/priv/content/2018/03/2018-03-24_pihole-love.md
index 2022f30..a66ccbe 100644
--- a/priv/content/2018/03/2018-03-24_pihole-love.md
+++ b/priv/content/2018/03/2018-03-24_pihole-love.md
@@ -2,7 +2,7 @@
 %{
   title: "Pi-Hole Love",
   id: "pihole-love",
-  date: ~N[2018-03-24 14:00:00],
+  date: ~D[2018-03-24],
   author: "Adam Piontek",
   tags: ["home", "privacy", "tech", "raspberrypi"],
 }
diff --git a/priv/content/2018/05/2018-05-17_if-no-one-will-do-it-neednt-be-done.md b/priv/content/2018/05/2018-05-17_if-no-one-will-do-it-neednt-be-done.md
index 4c134ce..be32ae8 100644
--- a/priv/content/2018/05/2018-05-17_if-no-one-will-do-it-neednt-be-done.md
+++ b/priv/content/2018/05/2018-05-17_if-no-one-will-do-it-neednt-be-done.md
@@ -2,7 +2,7 @@
 %{
   title: "If no one will do it, it needn’t be done — on BS jobs and toddler bosses",
   id: "if-noone-will-do-it-neednt-be-done-bs-jobs-toddler-bosses",
-  date: ~N[2018-05-17 15:10:00],
+  date: ~D[2018-05-17],
   author: "Adam Piontek",
   tags: ["economics", "society", "work"],
 }
diff --git a/priv/content/2018/11/2018-11-09_lana-del-arr-ultrapirates-parody-lyrics.md b/priv/content/2018/11/2018-11-09_lana-del-arr-ultrapirates-parody-lyrics.md
index eea38a1..c67bbfd 100644
--- a/priv/content/2018/11/2018-11-09_lana-del-arr-ultrapirates-parody-lyrics.md
+++ b/priv/content/2018/11/2018-11-09_lana-del-arr-ultrapirates-parody-lyrics.md
@@ -2,7 +2,7 @@
 %{
   title: "Lana Del Arr's ‘Ultrapirates’ (parody lyrics for songs from Lana Del Rey’s ‘Ultraviolence’",
   id: "lana-del-arr-ultrapirates-parody-lyrics",
-  date: ~N[2018-11-09 13:30:00],
+  date: ~D[2018-11-09],
   author: "Adam Piontek",
   tags: ["song", "lyrics", "lanadelrey", "music", "parody", "pirates"],
 }
diff --git a/priv/content/2020/05/2020-05-23_creating-gif-from-video.md b/priv/content/2020/05/2020-05-23_creating-gif-from-video.md
index 42d9bf4..24f719f 100644
--- a/priv/content/2020/05/2020-05-23_creating-gif-from-video.md
+++ b/priv/content/2020/05/2020-05-23_creating-gif-from-video.md
@@ -2,7 +2,7 @@
 %{
   title: "Creating a gif from a video",
   id: "creating-gif-from-video",
-  date: ~N[2020-05-23 04:41:00],
+  date: ~D[2020-05-23],
   author: "Adam Piontek",
   tags: ["cli", "ffmpeg ", "gif", "multimedia", "video", "notes"],
 }
diff --git a/priv/content/2020/08/2020-08-01_enable-vs-cli-env-in-ps.md b/priv/content/2020/08/2020-08-01_enable-vs-cli-env-in-ps.md
index b2ec8f6..023041a 100644
--- a/priv/content/2020/08/2020-08-01_enable-vs-cli-env-in-ps.md
+++ b/priv/content/2020/08/2020-08-01_enable-vs-cli-env-in-ps.md
@@ -1,7 +1,7 @@
 ---
 %{
   title: "Enable Visual Studio CLI environment in PowerShell",
-  date: ~N[2020-08-01 02:00:00],
+  date: ~D[2020-08-01],
   author: "Adam Piontek",
   tags: ["coding", "tech", "elixir", "windows", "powershell", "scripting"],
 }
diff --git a/priv/content/2020/12/2020-12-07_shame-american-leadership.md b/priv/content/2020/12/2020-12-07_shame-american-leadership.md
new file mode 100644
index 0000000..4c079b1
--- /dev/null
+++ b/priv/content/2020/12/2020-12-07_shame-american-leadership.md
@@ -0,0 +1,23 @@
+---
+%{
+  title: "Shame on American leadership",
+  id: "shame-on-american-leadership",
+  date: ~D[2020-12-07],
+  author: "Adam Piontek",
+  tags: ["politics", "society", "policy", "pandemic"]
+}
+---
+
+If you haven't read this yet, you should. But you're not going to feel good:
+
+"[Headlines Don’t Capture the Horror We Saw](https://www.theatlantic.com/ideas/archive/2020/12/new-york-doctors-know-how-bad-pandemic-can-get/617302/)" (subtitle: "I chronicled what COVID-19 did to a hospital. America must not let down its guard.")
+
+<!--more-->
+
+I'd blockquote something but it should really be read in full.
+
+My partner shared this experience as a nurse at a big NY hospital.
+
+It's abominable that this was allowed to get so bad, and allowed to continue across the country, and is being allowed to get so bad again.
+
+Shame on our leadership. Shame on America.
diff --git a/priv/content/2020/12/2020-12-29_moms-meatloaf.md b/priv/content/2020/12/2020-12-29_moms-meatloaf.md
index 8a987ef..1f649ce 100644
--- a/priv/content/2020/12/2020-12-29_moms-meatloaf.md
+++ b/priv/content/2020/12/2020-12-29_moms-meatloaf.md
@@ -1,7 +1,7 @@
 ---
 %{
   title: "Mom's Meatloaf",
-  date: ~N[2020-12-29 01:00:00],
+  date: ~D[2020-12-29],
   author: "Adam Piontek",
   tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"]
 }