Crest
30 Dec 2024

About the website

I think every personal blog that is somewhat custom built should have documentation written on its design philosophy and implementation. I see this as an extension of the free-software ideal; you should not only be allowed but also able to extent and modify another program to your needs.

Design

The website is written in Emacs and in org mode, because I find the markup to be intuitive, natural, and easy1. I generally took a lot of inspiration from others using the same tooling as me, as well as from Gwern, Maggie Appleton and various other blogs and homepages I have read over the years. The colour scheme is a slightly adapted version of Protesilaos Stavrou’s modus themes. I find them visually pleasing, and they are apparently very accessible to those with impaired vision, for example with colour blindness. I use them daily in Emacs, and so they fit nicely visually on my desktop when editing content.

The primary font used is Goudy Bookletter 1911, but as a fallback font I also use Sorts Mill Goudy and the legendary Computer Modern, the default typeface used in TeX. Code (Such as the TeX you just read) uses Protesilaos’ Iosevka Comfy font.

Static website generation

The main landing page (https://joarvarndt.se/) is handwritten in HTML and doesn’t change with a new generation of the site. The blog is all that is generated. It is done so using org-static-blog. I have an elisp file that runs the necessary configuration code, and it looks like this:

(setq org-static-blog-publish-title "Joar von Arndt")
(setq org-static-blog-publish-url "https://joarvarndt.se/blog/")
(setq org-static-blog-publish-directory "~/Documents/blog")
(setq org-static-blog-posts-directory "~/Documents/blog/posts/")
(setq org-static-blog-drafts-directory "~/Documents/blog/drafts/")
(setq org-static-blog-enable-tags t)
(setq org-export-with-toc nil)
(setq org-export-with-section-numbers nil)
(setq  org-static-blog-index-length 1)
(setq org-static-blog-use-preview nil)

(setq org-static-blog-page-header
      "<meta name=\"Joar von Arndt\" content=\"Joar von Arndt\">
<meta name=\"referrer\" content=\"no-referrer\">
<meta name=\"viewport\" content=\"initial-scale=1,width=device-width,minimum-scale=1\">
<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.jsdelivr.net/gh/bitmaks/cm-web-fonts@latest/fonts.css\">
<link href=\"static/style.css\" rel=\"stylesheet\" type=\"text/css\" />
<link rel=\"icon\" href=\"static/favicon.ico\">
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>
<script src=\"static/sidenotes.js\"></script>
<script id=\"MathJax-script\" async src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"></script>")


(setq org-static-blog-page-preamble "<div class=\"header\">
      <a href=\"https://joarvarndt.se/\"><img id=\"themeImageHeader\" src=\"vonArndtCrestBlack.png\" class=\"header-img\" alt=\"Crest\" width=\"50\"></a>
    <hr size=\"1\" width=\"100%\" align=\"center\">
    </div>")

(setq org-static-blog-page-postamble
      "<div id=\"archive\">
      <a href=\"https://joarvarndt.se/blog/archive.html\">Other posts</a><script src=\"static/theme.js\" defer></script>
    </div>")

(setq org-static-blog-index-front-matter "<a href=\"https://joarvarndt.se/blog/archive.html\"><h1 class=\"post-title\">All Posts</h1></a>")

(setq org-static-blog-enable-tag-rss t)

;; For syntax highlighting of code (Like this).
(setq org-src-fontify-natively t)
(setq org-html-htmlize-output-type 'css)
(setq org-src-preserve-indentation t)

(setq org-image-actual-width nil)

This loads the required javascript2 and CSS for themeing, both of which you should have received if you’re reading this. I am a big fan of org-static-blog due to it’s simplicity3, extensibility (its just a simple HTML static site) and obvious org-mode support. I used to use Hugo, a widely praised static website generator, but it only has nominal org-mode support, it requires a special non-org-mode YAML frontmatter that I really dislike. When writing a post, I prefer to leave formatting mostly out of it, and with my main focus on text. Tinkering with the website’s appearance is great fun of course, but I like to keep the two as separate activities. Writing any sort of special syntax brings me out of the experience, and that is partly why I wrote my own javascript to create sidenotes out of basic pandoc (and therefore org-mode) style footnotes.

Sidenotes

The sidenotes are heavily inspired by Gwern and Tufte CSS, but the Gwern sidenotes.js is not portable and seems way too complex for my needs (it is 1150 lines long!!). In comparison, here is the the script that creates sidenotes. It is merely 70 lines long, including whitespace and comments. It also allows for easy reading on mobile, vertical monitors, and JS-free browsers, since if the dimensions are not adequate for displaying sidenotes or the javascript doesn’t run the content does not disappear. Instead it is shown as a footnote at the bottom of the page with bidirectional links between it and the reference. The Tufte CSS approach is admirable — relying only on pure HTML and CSS — but my aforementioned dislike of jumping between prose and “syntax” means that is is not an approach for me. I have however taken the liberty of inspiration from Tufte CSS in the CSS of sidenotes and margin notes. Margin notes are written in the form of an org-mode export block, and so have to be located at the beginning or end of paragraph.

This is how I write a margin note!

#+begin_marginnote
This is how I write a margin note!
#+end_marginnote

I can do this since org treats “special blocks” as a <div> with a synonymously named marginnote class. This can then be styled however you want using CSS. Margin notes are usually not as critical in my mind as foot/sidenotes, since they serve merely as a general guide to the text rather than an explicit pointed “reference”4. That’s why margin notes are not displayed on mobile devices. This is an opinionated change from Tufte CSS’ approach, where margin notes are instead treated as unnumbered sidenotes when there is no margin to display them in.

Hosting

The website is hosted on my own server at home, on an Intel NUC machine. It runs Debian GNU/Linux and serves the website primarily through an NGINX web server, but the Diplomacy page is served through a gunicorn server that displays a python application so that users can input data that is then saved. I use loopia as my domain registrar, primarily due to having been recommended them from people I trust, however I have made no significant research into alternatives myself. After writing the posts I used to use sftp to remotely connect to the server from my desktop, laptop, or phone (through Termux) and upload my files but this became quite tedious to do, so I wrote a quick script in elisp that does all this for me:

;;; blogPublish.el --- Summary:

;;; Uploads the contents of the blog folder to the required one on the server.

;;; Commentary:
;;; Probably not the most elegant nor beautiful solution, but it seems to work for me.
;;; The HTML files on the remote server are first deleted through tramp, and then
;;; the content is reuploaded in case there are any changes.  Since plain text is so
;;; quick to upload there is no issue with reuploading unaltered content.  Video files
;;; are however quite large and so aren't reupload or deleted.  This is usually not a
;;; problem since the only work being done iteratively is writing.

;;; Code:

(require 'org-static-blog)

(defun blog-delete-remote-html ()
  "Deletes all html files on the server."
  (let ((tramp-default-method "ssh")
        (remote-directory "/ssh:jovo@parana:/var/www/html/blog/"))
    (with-current-buffer (find-file-noselect remote-directory)
      (let ((default-directory remote-directory))
        ;; Ensure the remote directory is accessible and its contents are loaded
        (revert-buffer)

        (dolist (file (directory-files default-directory t "\\.html$"))
          (when (file-exists-p file)
            (message "Deleting %s ..." file)
            (delete-file file)
            (message "Deleted all .html files in %s" remote-directory)))))))

(defun upload-blog-contents ()
  "Uploads the required HTML to the blog via SCP for async."
  (let* ((local-dir "/home/jovo/Documents/blog/")
         (remote-dir "/var/www/html/blog/")
         (remote-host "parana")
         (remote-user "jovo")
         (remote-files-tramp (delq nil (mapcar (lambda (file) ;; Use TRAMP to check the contents of REMOTE-DIR.
                                                 (when (file-regular-p file) file))
                                               (directory-files (concat "/ssh:" remote-user "@" remote-host ":" remote-dir) t "\\`[^.]")))))
    (dolist (file (directory-files local-dir t "\\`[^.]")) ;; Exclude "." and ".."
      (when (file-regular-p file)
        (let* ((file-name (file-name-nondirectory file))
               (remote-file (replace-regexp-in-string local-dir "" (concat remote-user "@" remote-host ":" remote-dir file))))
          (if (and (string= (file-name-extension file) "mp4")
                   (member file-name (mapcar 'file-name-nondirectory remote-files-tramp)))
              (message "%s already exists on remote, skipping upload." file)
            (message "Uploading %s to %s" file remote-file)
            (async-start-process "scp-upload" "scp" nil file remote-file)))))))

(defun website-publish ()
  "Function running after org-static-blog-publish to publish to the website."
  (interactive)
  (blog-delete-remote-html)
  (upload-blog-contents))

(advice-add #'org-static-blog-publish :after #'website-publish)

;;; blogPublish.el ends here.

The function is triggered using advice-add after org-static-blog-publish so as to fit seamlessly into my preëxisting workflow. It removes old HTML content from the website using TRAMP, and then uploads it using scp. I avoid uploading the video files since they are quite large and only need to be uploaded once. TRAMP is quite capable of uploading the files, but it is quite slow compared to scp. Deleting files however takes a comparatively short amount of time. Authentication is handled by tailscale SSH, so I don’t have to expose port 22 to the public internet and don’t have to enter any passwords for upload.

Footnotes:

1

Why would any markup not use “//” to italicise text? Markdown’s use of just asterisks is admirable, but unintuitive for reading in plaintext (Does two asterisks mean italic or bold?). Org mode’s choice of using __ to underline text is similarly beautiful.

2

For dark/light mode, mathjax, and of course these sidenotes (But only if you are reading on a wide screen). Sadly the external mathjax reference makes this nontrivial javascript, but that is a sacrifice I am willing to make for easy and beautiful \(\LaTeX\) renderings.

3

From the repository:

Above all, I tried to make org-static-blog as simple as possible. There are no magic tricks, and all of the source code is meant to be easy to read, understand and modify.

4

Even if it is not a literal reference, but it may often be, especially when it comes to my academic writings.

Tags: technology: website:
Other posts