DrolleryMedieval drollery of a knight on a horse
flowery border with man falling
flowery border with man falling

Introduction

This is my org-publish build script. I was just writing directly to the build.el file, but then I started thinking about it, and that made no sense. If I’m using Orgmode to write a blog, I should be using it to write the thing that writes that thing that builds the blog! It’s elementary!

Why a build.el and not just include this in my emacs config so that it all automatically runs when I publish through emacs? Because my workflow doesn’t include me publishing via emacs. Instead I git add . && git commit -m "Updates!" && git push and then expect that my CI/CD will build and deploy my website for me. Because of this expectation, I’d rather have the org-publish related stuff separate from my emacs configuration so that the CI/CD has a much easier time setting up emacs. Plus, when I do want to build the site locally I just have to run sh build.sh which then will trigger build.el.

Resources

Alternatives

README.org

It seems a little weird to build an org file, with an org file. I’m really only doing this because the build directory is entry built from this org file, but the intention is that I compile the files in build locally and commit them. There is a chance that someone — including future me — might come upon this build directory and be uninformed about it’s creation and use.

#+title: Readme

NOTICE: This file and all files in this directory were built with =build.org=
and should not be directly edited!

This is the build dir for use with org-publish. It should work, but I'm not
using it at the moment. Should just have to run `./build.sh` and it will spit
out the org files in the parent directory into a `./_html/` dir.

To use the Dockerfile, you'll need to be in the parent directory, aka ~org/~,
and then you will run =docker build -t test -f build/Dockerfile .=

The final nginx container has the http port exposed.

build.sh

This is just a little shell script we’re using to make it easy to call the elisp code that does the real work of building the website.

rm -rf ./_html/
rm -rf ./_html/ ./org-timestamps/
mkdir ./org-timestamps
emacs -Q --script build.el
cp ./*.css ./_html/

build.el

Now onto the main attraction, build.el will do all the heavy lifting of building the website.

Front Matter

Just a little front matter to describe the software, probably pointless really, but it’s here just in-case. I’ll probably eventually fill in the commentary and description…

;;; build.el --- Description -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2022 Ian S. Pringle
;;
;; Author: Ian S. Pringle <[email protected]>
;; Maintainer: Ian S. Pringle <[email protected]>
;; Created: August 02, 2022
;; Modified: August 24, 2022
;; Version: 0.2.0
;; Keywords: bib convenience docs files hypermedia lisp outlines processes tools
;; Homepage: https://github.com/pard68/org
;; Package-Requires: ((emacs "24.4"))
;;
;; This file is not part of GNU Emacs.
;;
;;; Commentary:
;;
;;  Description
;;
;;; Code:

Make sh-set-shell quiet

The sh-set-shell gets called on files when there is shell scripts in it. I’m not sure what it does but it is very noisy and I dislike the noise. So we can inhibit it with this:

(advice-add 'sh-set-shell :around
            (lambda (orig-fun &rest args)
              (let ((inhibit-message t))
                (apply orig-fun args))))

Dependencies

We need to be able to install some dependencies, since we can’t count on the emacs.d directory having them installed already during CI/CD, plus we can also separate this from our packages we use for normal, everyday emacs, which means we can depend on different versions or even on things we don’t want polluting the rest of our setup.

straight.el

I’m messing around with using straight.el in addition to use-package because the package ox-attach-publish is not on Melpa or any other package repo currently. If this works well, I will work on refactoring the above dependencies to use straight.el instead of package.el.

This will bootstrap straight.el, I got it straight from their git repo:

(setq package-enable-at-startup nil)

(setq straight-build-dir (expand-file-name "./.packages"))
(setq straight-use-package-by-default t)

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package)

Getting And Requiring Packages

Now that we can use-package we can get on with, well, using packages. Let’s start with org and htmlize. Org is… well it’s org! and htmlize is more or less pygments or highlight.js, but for org-publish.

;; Install needed packages
(use-package org)
(use-package htmlize)
(require 'ox-publish)
(require 'ox-html)
(require 'htmlize)

s is a package for handling strings, f is a package for working with files. I’m not actually using either right now, I just am leaving this here (untangled) as a reminder to myself to invest some time into making use of it at a future date.

(use-package s)
(use-package f)
(require 's)
(require 'f)

This doesn’t work because it creates a new export type, but I’m leaving it here to maybe one day figure out…

(use-package ox-tufte
  :config
  (setq org-tufte-include-footnotes-at-bottom t))
(require 'ox-tufte)

ox-attach-publish is a tool that converts attached files into valid links for orgmode to then export:

(use-package ox-attach-publish
  :straight '(ox-attach-publish
              :type git
              :host github
              :repo "simoninireland/ox-attach-publish"))
(require 'ox-attach-publish)
  ;;; Define Back-End
  (org-export-define-derived-backend 'tufte-html-with-attachments 'tufte-html
                                    :filters-alist '((:filter-parse-tree org-attach-publish--filter-parse-tree)))

  (defun org-attach-publish-to-tufte-html (info fn pub-dir)
  "Publish FN as HTML with attachments to directory PUB-DIR using settings from the INFO plist.

Returns the published file name."
  (let* ((old-hooks org-export-before-parsing-hook)
	 (org-export-before-parsing-hook old-hooks))

    ;; remove the default link-expansion function from the hook
    (remove-hook 'org-export-before-parsing-hook #'org-attach-expand-links)

    ;; publish using the new backend, which will pick up the restricted
    ;; hook function using dynamic binding
    (org-publish-org-to 'tufte-html-with-attachments
			fn
			(concat (when (> (length org-html-extension) 0) ".")
				(or (plist-get info :html-extension)
				    org-html-extension
				    "html"))
			info pub-dir)))

Finally, we’ll require everything we need, including some things that we didn’t have to download first:

(require 'find-lisp)

Some of my org files use a custom function to determine if it should be tangled or not. Because this function exists I need to provide it in here, as this won’t always execute from inside my emacs env.

(defun fv--tangle-maybe (&optional dir)
               (if (member-ignore-case "tangleno" (org-get-tags))
                   "no"
                 (or dir ("yes"))))

The Meat and Potatoes

This is ephemeral, we don’t need no stinking backups!

(setq make-backup-files nil)

Common Variables

Now to setup some variables for later use:

(defvar build--build-dir (getenv "PWD"))
(defvar build--project-dir (concat build--build-dir "/.."))
(defvar build-publish-dir (concat build--build-dir "/_html"))
(defvar build--site-name "Drollery")
(defvar build--publish-url "https://drollery.org")
(defvar build--date-format "%Y-%m-%d")

Initialize org-roam

We initialize the org-roam project and DB so that we can lean on it later to generate backlinks.

(setq org-roam-directory build--project-dir
      org-roam-db-location (concat build--project-dir "/org-roam.db"))

(org-roam-update-org-id-locations)
;; (require 'org-roam-export)

org-publish settings

There are some settings we need to tweak to get org-publish and ox-publish working the way we want.

This sets the org-timestamps dir to something local to our build dir. It’s probably not needed, but I like to keep this together to reduce on clutter since the default is ~/

(setq org-publish-timestamp-directory (concat build--build-dir "/org-timestamps/")

Now we’ll set some default HTML stuff. First is the `org-html-divs` alist which tells org-export what html element and id to use for the preamble, content, and postamble on each page:

org-html-divs '((preamble "header" "preamble")
                (content "main" "content")
                (postamble "footer" "postamble"))
org-html-container-element "section"
org-html-metadata-timestamp-format build--date-format
org-html-checkbox-type 'ascii
org-html-html5-fancy t
org-html-doctype "html5"
org-html-htmlize-output-type 'css
org-html-fontify-natively t)

Here we turn off inlining CSS and then inject our own CSS into the <head>

(defvar build--html-head
  (concat
   "<link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\">"
   ;; "<link rel=\"stylesheet\" type=\"text/css\" href=\"https://gongzhitaao.org/orgcss/org.css\" />"
   "<link rel=\"stylesheet\" href=\"/tufte.css\"/>"
   "<link rel=\"stylesheet\" href=\"/ox-tufte.css\"/>"
   "<link rel=\"stylesheet\" href=\"/style.css\" type=\"text/css\" />"
   "<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=IM+Fell+English\" />"))

Some macros

(setq org-export-global-macros
      '(("timestamp" . "@@html:<span class=\"timestamp\">[$1]</span>@@")
        ("h" . "@@html:$1@@")))

Sitemap Maker

We’ll use this later to setup our /{dir}/index.html pages, for example to list all blog posts:

(defun build--org-sitemap-date-entry-format (entry _ project)
  "Build sitemap/index for a number of pages. Format ENTRY in org-publish
  PROJECT Sitemap format ENTRY ENTRY STYLE format that includes date."
  (format "{{{h(<span class=\"sitemap-entry\">)}}}{{{timestamp(%s)}}} [[file:%s][%s]]{{{h(</span>)}}}"
          (format-time-string build--date-format (org-publish-find-date entry project))
          entry
          (org-publish-find-title entry project)))

This is the same as above, but without the date field.

(defun build--org-sitemap-entry-format (entry _ project)
  "Build sitemap/index for a number of pages. Format ENTRY in org-publish
  PROJECT Sitemap format ENTRY ENTRY STYLE format that includes date."
  (format "{{{h(<span class=\"sitemap-entry\">)}}}[[file:%s][%s]]{{{h(</span>)}}}"
          entry
          (org-publish-find-title entry project)))

CSS Inliner

This inlines CSS, but I’m not using it right now. Eventually I’d like to pursue optimizations that would allow for in-lining critical CSS and then throwing the rest into the styles.css.

(defun my-org-inline-css-hook (exporter)
  (when (eq exporter 'html)
    (let* ((dir (ignore-errors (file-name-directory (buffer-file-name))))
           (path (concat dir "style.css"))
           (homestyle (or (null dir) (null (file-exists-p path))))
           (final (if homestyle (concat build--build-dir "/style.css") path))) ;; <- set your own style file path
      (setq org-html-head-include-default-style nil)
      (setq org-html-head (concat
                           "<style type=\"text/css\">\n"
                           "<!--/*--><![CDATA[/*><!--*/\n"
                           (with-temp-buffer
                             (insert-file-contents final)
                             (buffer-string))
                           "/*]]>*/-->\n"
                           "</style>\n")))))

(add-hook 'org-export-before-processing-hook 'my-org-inline-css-hook)

We’ll use this later on as the preamble for every page:

(defvar build--nav-bar "<nav><a href=\"/index.html\">index</a>
                |
                <a href=\"/about.html\">about</a>
                |
                <a href=\"/blog/index.html\">blog</a>
                |
                <a href=\"/grok/grok.html\">grok</a>")
(defvar build--logo
  (concat "<pre id=\"logo\">"
          (shell-command-to-string (concat "figlet " build--site-name))
          "</pre>"))

(defvar build--header (concat build--logo build--nav-bar))

This is the footer or postamble:

(defvar build--footer-left "<div id=\"footer-left\">
<p class=\"author\">Author: Ian S. Pringle</p>
<p class=\"date\">Site Updated: %T</p>
<p class=\"creator\">Created with %c</p>
</div>")

(defvar build--footer-mid "<div id=\"footer-mid\">
<img class=\"fleuron\" src=\"/fleuron.svg\"><img/>
</div>")

(defvar build--footer-right "<div id=\"footer-right\">
<p class=\"copyright-notice\">Creative Commons</p>
<a href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\">BY-NC-SA</a>
</div>")

(defvar build--scripts "<script type='module'>
import hotwiredTurbo from 'https://cdn.skypack.dev/@hotwired/turbo';
</script>")

(defvar build--footer (concat build--footer-left build--footer-mid build--footer-right))

org-publish projects

We’ll now build out the projects. Each “project” is a like a group of pages. So there is a “blog” project for all the stuff under blog directory, for example. I think I could pull this off with just one project, however I’d only get one index page and I want an index page for the blog posts and another for the kb and a third for everything.

(setq org-html-validation-link nil)
(setq org-publish-project-alist
      (list

“pages” project

(list "pages"
      :base-directory build--project-dir
      :publishing-directory build-publish-dir
      :publishing-function 'org-attach-publish-to-tufte-html
      :html-head-extra build--html-head
      :html-preamble build--header
      :html-postamble build--footer
      :html-head-include-default-style nil
      :auto-sitemap t
      :sitemap-filename "pages.org"
      :sitemap-format-entry 'build--org-sitemap-entry-format
      :sitemap-sort-files 'alphabetically
      :recursive nil
      :with-author t
      :with-creator t
      :with-drawer t
      :with-toc nil
      :section-numbers nil
      :exclude "./\\(life.org\\|dates.org\\|journal.org\\|jha.org\\|weekly.org\\|bible-plan.org\\)")

“grok” project

(list "grok"
      :base-directory (concat build--project-dir "/grok")
      :publishing-directory (concat build-publish-dir "/grok")
      :recursive t
      :publishing-function 'org-attach-publish-to-tufte-html
      :html-head-extra build--html-head
      :html-preamble build--header
      :html-postamble build--footer
      :html-head-include-default-style nil
      :auto-sitemap t
      :sitemap-filename "index.org"
      :sitemap-format-entry 'build--org-sitemap-entry-format
      :sitemap-sort-files 'alphabetically
      :with-author t
      :with-creator t
      :with-drawer t
      :with-toc nil
      :section-numbers nil)

“blog” project

This contains all the files in the blog dir. I eventually would like to figure out how to make this work with a single blog.org file and then each heading or maybe subheading in that file is a “page” on the site.

(list "blog"
      :base-directory (concat build--project-dir "/blog")
      :publishing-directory (concat build-publish-dir "/blog")
      :publishing-function 'org-attach-publish-to-tufte-html
      :attachments-project "static"
      :attachments-base-directory "files"
      :html-head-extra build--html-head
      :html-preamble build--header
      :html-postamble build--footer
      :html-head-include-default-style nil
      :auto-sitemap t
      :sitemap-filename "index.org"
      :sitemap-format-entry 'build--org-sitemap-date-entry-format
      :sitemap-sort-files 'anti-chronologically
      :with-author t
      :with-creator t
      :with-drawer t
      :with-toc nil
      :section-numbers nil)

“books” project

This contains all the files in the blog dir. I eventually would like to figure out how to make this work with a single blog.org file and then each heading or maybe subheading in that file is a “page” on the site.

(list "books"
      :base-directory (concat build--project-dir "/books")
      :publishing-directory (concat build-publish-dir "/books")
      :publishing-function 'org-attach-publish-to-tufte-html
      :attachments-project "static"
      :attachments-base-directory "files"
      :html-head-extra build--html-head
      :html-preamble build--header
      :html-postamble build--footer
      :html-head-include-default-style nil
      :auto-sitemap t
      :sitemap-filename "index.org"
      :sitemap-format-entry 'build--org-sitemap-date-entry-format
      :sitemap-sort-files 'alphabetically
      :with-author t
      :with-creator t
      :with-drawer t
      :with-toc nil
      :section-numbers nil)

“static” project

This contains all the static content in my org directory.

(list "static"
      :base-directory "~/org/"
      :base-extension "txt\\|jpg\\|jpeg\\|png\\|svg\\|gif\\|js"
      :recursive t
      :publishing-directory build-publish-dir
      :publishing-function 'org-publish-attachment)

“assets” project

This contains all the assets in my org/build directory.

(list "assets"
      :base-directory "~/org/build"
      :base-extension "css\\|js\\|svg"
      :recursive nil
      :publishing-directory build-publish-dir
      :publishing-function 'org-publish-attachment)

“site” project

This is just a “meta” project that contains all the above projects as components:

(list "site"
      :components (list "pages" "grok" "blog" "books" "static" "assets")
      :auto-sitemap t
      :sitemap-filename "sitemap.org"
      :sitemap-format-entry 'build--org-sitemap-date-entry-format
      :sitemap-sort-files 'anti-chronologically
      :html-doctype "html5"
      :html-html5-fancy t)))

Extras

This is not working atm

(defun build--collect-backlinks-string (backend)
  "Insert backlinks into the end of the org file before parsing it."
  (when (org-roam-node-at-point)
    (goto-char (point-max))
    ;; Add a new header for the references
    (insert "\n** Xrefs\n")
    (let* ((backlinks (org-roam-backlinks-get (org-roam-node-at-point))))
      (dolist (backlink backlinks)
        (let* ((source-node (org-roam-backlink-source-node backlink))
               (point (org-roam-backlink-point backlink)))
          (insert
           (format "- [[./%s][%s]]\n"
                   (file-name-nondirectory (org-roam-node-file source-node))
                   (org-roam-node-title source-node))))))))

(defun build--add-extra-sections (backend)
  (when (org-roam-node-at-point)
    (save-excursion
      (goto-char (point-max))
      (build--collect-backlinks-string backend)
      (insert "\n** Mentions\n\n")
      (insert "#+BEGIN_EXPORT html
<div id='webmentions'></div>
#+END_EXPORT"))))

(add-hook 'org-export-before-processing-hook 'build--add-extra-sections)

org-publish

And finally, we build the project!

(org-publish "site" t)
(message "Build completed!")
(provide 'build)
;;; build.el ends here

style.css

html,
body {
  height: 100%;
  width: 100%;
}

html {
  font-family: "IM Fell English";
  text-rendering: geometricPrecision;
  -webkit-font-smoothing: antialiased;
}

body {
  display: flex;
  flex-direction: column;
  font-family: unset;
  font-size: 15px;
  margin: unset;
}

h1, h2, h3, h4, h5, h6 {
  color: unset;
  font-family: unset;
}

#content {
  flex: 1 0 auto;
  margin: auto;
  max-width: min(669px, 60vw);
  font-size: 1.5rem;
  line-height: 2rem;
}

#preamble {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: unset;
}

#preamble.status {
  margin: unset;
}

#preamble nav {
  font-size: 2em;
}

#preamble #logo {
  background-color: white;
  border: unset;
  font-size: 1.25em;
  padding: unset;
  width: fit-content;
  font-family: monospace;
}

#postamble {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 100%;
  justify-content: space-between;
  font-size: 0.7em;
  line-height: 1em;
}

#postamble * {
  margin: 5px;
}

#postamble p {
  margin: unset;
}

#postamble div:first-child,
#postamble div:last-child {
  display: flex;
  flex-direction: column;
  flex: 1;
}

#postamble div:last-child * {
  align-self: flex-end;
}

#postamble div:nth-child(2) {
  flex: 0 0 auto;
}

.sitemap-entry {
  display: flex;
  gap: 1rem;
}

.sitemap-entry > :last-child {
  flex: 1;
}

Here’s a few things to make the site nicer on smaller devices:

@media (max-width: 669px) {
    #content {
        margin: 0 1em;
        max-width: unset;
    }
}

tufte.css

  @charset "UTF-8";

/* Import ET Book styles
   adapted from https://github.com/edwardtufte/et-book/blob/gh-pages/et-book.css */

@font-face {
    font-family: "et-book";
    src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot");
    src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff") format("woff"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf") format("truetype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg#etbookromanosf") format("svg");
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "et-book";
    src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot");
    src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff") format("woff"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf") format("truetype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg#etbookromanosf") format("svg");
    font-weight: normal;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: "et-book";
    src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot");
    src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff") format("woff"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf") format("truetype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg#etbookromanosf") format("svg");
    font-weight: bold;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "et-book-roman-old-style";
    src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot");
    src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff") format("woff"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf") format("truetype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg#etbookromanosf") format("svg");
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

/* Tufte CSS styles */
html {
    font-size: 15px;
}

body {
    width: 87.5%;
    margin-left: auto;
    margin-right: auto;
    padding-left: 12.5%;
    font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
    background-color: #fffff8;
    color: #111;
    max-width: 1400px;
    counter-reset: sidenote-counter;
}

/* Adds dark mode */
@media (prefers-color-scheme: dark) {
    body {
        background-color: #151515;
        color: #ddd;
    }
}

h1 {
    font-weight: 400;
    margin-top: 4rem;
    margin-bottom: 1.5rem;
    font-size: 3.2rem;
    line-height: 1;
}

h2 {
    font-style: italic;
    font-weight: 400;
    margin-top: 2.1rem;
    margin-bottom: 1.4rem;
    font-size: 2.2rem;
    line-height: 1;
}

h3 {
    font-style: italic;
    font-weight: 400;
    font-size: 1.7rem;
    margin-top: 2rem;
    margin-bottom: 1.4rem;
    line-height: 1;
}

hr {
    display: block;
    height: 1px;
    width: 55%;
    border: 0;
    border-top: 1px solid #ccc;
    margin: 1em 0;
    padding: 0;
}

p.subtitle {
    font-style: italic;
    margin-top: 1rem;
    margin-bottom: 1rem;
    font-size: 1.8rem;
    display: block;
    line-height: 1;
}

.numeral {
    font-family: et-book-roman-old-style;
}

.danger {
    color: red;
}

article {
    padding: 5rem 0rem;
}

section {
    padding-top: 1rem;
    padding-bottom: 1rem;
}

p,
dl,
ol,
ul {
    font-size: 1.4rem;
    line-height: 2rem;
}

p {
    margin-top: 1.4rem;
    margin-bottom: 1.4rem;
    padding-right: 0;
    vertical-align: baseline;
}

/* Chapter Epigraphs */
div.epigraph {
    margin: 5em 0;
}

div.epigraph > blockquote {
    margin-top: 3em;
    margin-bottom: 3em;
}

div.epigraph > blockquote,
div.epigraph > blockquote > p {
    font-style: italic;
}

div.epigraph > blockquote > footer {
    font-style: normal;
}

div.epigraph > blockquote > footer > cite {
    font-style: italic;
}
/* end chapter epigraphs styles */

blockquote {
    font-size: 1.4rem;
}

blockquote p {
    width: 55%;
    margin-right: 40px;
}

blockquote footer {
    width: 55%;
    font-size: 1.1rem;
    text-align: right;
}

section > p,
section > footer,
section > table {
    width: 55%;
}

/* 50 + 5 == 55, to be the same width as paragraph */
section > dl,
section > ol,
section > ul {
    width: 50%;
    -webkit-padding-start: 5%;
}

dt:not(:first-child),
li:not(:first-child) {
    margin-top: 0.25rem;
}

figure {
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
    max-width: 55%;
    -webkit-margin-start: 0;
    -webkit-margin-end: 0;
    margin: 0 0 3em 0;
}

figcaption {
    float: right;
    clear: right;
    margin-top: 0;
    margin-bottom: 0;
    font-size: 1.1rem;
    line-height: 1.6;
    vertical-align: baseline;
    position: relative;
    max-width: 40%;
}

figure.fullwidth figcaption {
    margin-right: 24%;
}

/* Links: replicate underline that clears descenders */
a:link,
a:visited {
    color: inherit;
}

.no-tufte-underline:link {
    background: unset;
    text-shadow: unset;
}

a:link, .tufte-underline, .hover-tufte-underline:hover {
    text-decoration: none;
    background: -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(currentColor, currentColor);
    background: linear-gradient(#fffff8, #fffff8), linear-gradient(#fffff8, #fffff8), linear-gradient(currentColor, currentColor);
    -webkit-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
    -moz-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
    background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
    background-repeat: no-repeat, no-repeat, repeat-x;
    text-shadow: 0.03em 0 #fffff8, -0.03em 0 #fffff8, 0 0.03em #fffff8, 0 -0.03em #fffff8, 0.06em 0 #fffff8, -0.06em 0 #fffff8, 0.09em 0 #fffff8, -0.09em 0 #fffff8, 0.12em 0 #fffff8, -0.12em 0 #fffff8, 0.15em 0 #fffff8, -0.15em 0 #fffff8;
    background-position: 0% 93%, 100% 93%, 0% 93%;
}

@media screen and (-webkit-min-device-pixel-ratio: 0) {
    a:link, .tufte-underline, .hover-tufte-underline:hover {
        background-position-y: 87%, 87%, 87%;
    }
}

/* Adds dark mode */
@media (prefers-color-scheme: dark) {
    a:link, .tufte-underline, .hover-tufte-underline:hover {
        text-shadow: 0.03em 0 #151515, -0.03em 0 #151515, 0 0.03em #151515, 0 -0.03em #151515, 0.06em 0 #151515, -0.06em 0 #151515, 0.09em 0 #151515, -0.09em 0 #151515, 0.12em 0 #151515, -0.12em 0 #151515, 0.15em 0 #151515, -0.15em 0 #151515;
    }
}

a:link::selection,
a:link::-moz-selection {
    text-shadow: 0.03em 0 #b4d5fe, -0.03em 0 #b4d5fe, 0 0.03em #b4d5fe, 0 -0.03em #b4d5fe, 0.06em 0 #b4d5fe, -0.06em 0 #b4d5fe, 0.09em 0 #b4d5fe, -0.09em 0 #b4d5fe, 0.12em 0 #b4d5fe, -0.12em 0 #b4d5fe, 0.15em 0 #b4d5fe, -0.15em 0 #b4d5fe;
    background: #b4d5fe;
}

/* Sidenotes, margin notes, figures, captions */
img {
    max-width: 100%;
}

.sidenote,
.marginnote {
    float: right;
    clear: right;
    margin-right: -60%;
    width: 50%;
    margin-top: 0.3rem;
    margin-bottom: 0;
    font-size: 1.1rem;
    line-height: 1.3;
    vertical-align: baseline;
    position: relative;
}

.sidenote-number {
    counter-increment: sidenote-counter;
}

.sidenote-number:after,
.sidenote:before {
    font-family: et-book-roman-old-style;
    position: relative;
    vertical-align: baseline;
}

.sidenote-number:after {
    content: counter(sidenote-counter);
    font-size: 1rem;
    top: -0.5rem;
    left: 0.1rem;
}

.sidenote:before {
    content: counter(sidenote-counter) " ";
    font-size: 1rem;
    top: -0.5rem;
}

blockquote .sidenote,
blockquote .marginnote {
    margin-right: -82%;
    min-width: 59%;
    text-align: left;
}

div.fullwidth,
table.fullwidth {
    width: 100%;
}

div.table-wrapper {
    overflow-x: auto;
    font-family: "Trebuchet MS", "Gill Sans", "Gill Sans MT", sans-serif;
}

.sans {
    font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
    letter-spacing: .03em;
}

code, pre > code {
    font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
    font-size: 1.0rem;
    line-height: 1.42;
    -webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
}

.sans > code {
    font-size: 1.2rem;
}

h1 > code,
h2 > code,
h3 > code {
    font-size: 0.80em;
}

.marginnote > code,
.sidenote > code {
    font-size: 1rem;
}

pre > code {
    font-size: 0.9rem;
    width: 52.5%;
    margin-left: 2.5%;
    overflow-x: auto;
    display: block;
}

pre.fullwidth > code {
    width: 90%;
}

.fullwidth {
    max-width: 90%;
    clear:both;
}

span.newthought {
    font-variant: small-caps;
    font-size: 1.2em;
}

input.margin-toggle {
    display: none;
}

label.sidenote-number {
    display: inline-block;
    max-height: 2rem; /* should be less than or equal to paragraph line-height */
}

label.margin-toggle:not(.sidenote-number) {
    display: none;
}

.iframe-wrapper {
    position: relative;
    padding-bottom: 56.25%; /* 16:9 */
    padding-top: 25px;
    height: 0;
}

.iframe-wrapper iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

@media (max-width: 760px) {
    body {
        width: 84%;
        padding-left: 8%;
        padding-right: 8%;
    }

    hr,
    section > p,
    section > footer,
    section > table {
        width: 100%;
    }

    pre > code {
        width: 97%;
    }

    section > dl,
    section > ol,
    section > ul {
        width: 90%;
    }

    figure {
        max-width: 90%;
    }

    figcaption,
    figure.fullwidth figcaption {
        margin-right: 0%;
        max-width: none;
    }

    blockquote {
        margin-left: 1.5em;
        margin-right: 0em;
    }

    blockquote p,
    blockquote footer {
        width: 100%;
    }

    label.margin-toggle:not(.sidenote-number) {
        display: inline;
    }

    .sidenote,
    .marginnote {
        display: none;
    }

    .margin-toggle:checked + .sidenote,
    .margin-toggle:checked + .marginnote {
        display: block;
        float: left;
        left: 1rem;
        clear: both;
        width: 95%;
        margin: 1rem 2.5%;
        vertical-align: baseline;
        position: relative;
    }

    label {
        cursor: pointer;
    }

    div.table-wrapper,
    table {
        width: 85%;
    }

    img {
        width: 100%;
    }
}

ox-tufte.css

:root {
    --ox-tufte-content-width: 55%; /* set value consistent with "section > p" */
    --ox-tufte-fullwidth: 163.636%; /* 90% of body, when ancestor is at 55% */
    --ox-tufte-src-code-width: 97%;
    /* 50+5=55% this is what is used by section > {dl, ol, ul} */
    --ox-tufte-list-width: 50%;
    --ox-tufte-list-padding-left: 5%;
    /* font-size and line-height settings from tufte.css */
    --ox-tufte-content-font-size: 1.4rem;
    --ox-tufte-content-line-height: 2rem;
    --ox-tufte-content-epigraph-margin-vertical: calc(5em / 1.4);
    /* ^ equivalent of 5em when element has 1rem font-size */
    --ox-tufte-note-line-height: 1.3;
    --ox-tufte-note-font-size: 1.1rem;
    --ox-tufte-note-code-font-size: 1rem;
    --ox-tufte-code-font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
    --ox-tufte-code-font-size: 1.0rem;
    --ox-tufte-code-font-size-sans: 1.2rem; /* from ".sans > code" */
    --ox-tufte-code-line-height: 1.42;
    /* values tufte.css reuses for h1,h3,h3 */
    --ox-tufte-heading-font-style: italic;
    --ox-tufte-heading-font-weight: 400;
    --ox-tufte-heading-line-height: 1;
    --ox-tufte-heading-code-font-size: 0.80em;
    --ox-tufte-heading-min-margin-bottom: 1.4rem; /* h3=1.4rem; p=1.4rem; */
    --ox-tufte-heading-min-font-size: 1.4rem; /* p=1.4rem; */
}

/* 1. Prevent adjustments of font size after orientation changes in iOS. */
/*    <https://github.com/necolas/normalize.css/blob/fc091cce1534909334c1911709a39c22d406977b/normalize.css#L13> */
html {
    -webkit-text-size-adjust: 100%; /* 1 */
}

/**************************************************************************/
/* re-specify tufte.css values, for compatibility with org-generated html */
/**************************************************************************/
article { /* add missing default from tufte.css */
    font-size: var(--ox-tufte-content-font-size);
    line-height: var(--ox-tufte-content-line-height);
}
article > p,
article > div.epigraph,
article > table,
article > mjx-container,
article > div.zeroth-section,
section > div,
section > h2 { /* set value consistent with "section > p" */
    width: var(--ox-tufte-content-width);
}
article > object {
    max-width: var(--ox-tufte-content-width);
}
article figure object {
    max-width: 100%;
}
article > p .fullwidth,
article > div .fullwidth,
section > div .fullwidth {
    max-width: var(--ox-tufte-fullwidth);
    width: max-content;
}
article > blockquote.fullwidth {
    width: max-content;
}
@media (max-width: 760px) {
    div.epigraph > blockquote.fullwidth {
        width: unset;
    }
}
section > dl, section > ol, section > ul,
article > dl, article > ol, article > ul {
    width: var(--ox-tufte-list-width);
    /* instead of tufte.css's -webkit-padding-start, which doesn't work */
    padding-inline-start: var(--ox-tufte-list-padding-left);
}
blockquote .marginnote, blockquote .sidenote {
    font-style: normal;
}
/* code blocks. tufte.css also allows for code within headers and
 * sidenotes/marginnotes. these are currently not supported by us. */
.org-src-container {
    max-width: var(--ox-tufte-fullwidth);
    width: var(--ox-tufte-fullwidth);
    clear: both; /* necessary to prevent overlap between long label and sidenote */
}
body pre { /* monospace content: .src .example */
    /* FROM: pre.fullwidth > code in tufte.css; but adjusted */
    width: var(--ox-tufte-src-code-width);
    clear: both; /* ensure that it doesn't overlap sidenotes */
    /* unset some unnecessary 'org' defaults, which interfere with tufte */
    padding-left: 0;
    padding-right: 0;
    margin-left: auto;
    margin-right: 0;
}
pre, code {
    /* FROM: "code, pre > code" in tufte.css */
    font-family: var(--ox-tufte-code-font-family);
    font-size: var(--ox-tufte-code-font-size);
    line-height: var(--ox-tufte-code-line-height);
}
.sans pre, .sans code {
    font-size: var(--ox-tufte-code-font-size-sans);
    line-height: var(--ox-tufte-code-line-height);
}
.sans h1 code, .sans h2 code, .sans h3 code,
h1 code, h2 code, h3 code { /* fix for tufte.css */
    font-size: var(--ox-tufte-heading-code-font-size);
}
code {
    vertical-align: middle;
}
.sidenote code, .marginnote code, /* inline code */
.marginnote pre {                 /* code blocks */
    font-size: var(--ox-tufte-note-code-font-size);
    line-height: var(--ox-tufte-note-line-height);
}

article div figure {
    /* tufte.css doesn't intend figure to be within div, but org generates it as
     * such. reset width to full width of article as tufte.css intends */
    max-width: 100%
}
blockquote p, blockquote footer {
    width: 100%
}

/**********************************************************/
/* tufte.css fixes; should probably be submitted upstream */
/**********************************************************/
.sans .numeral { /* don't use et-book-roman-old-style when using .sans */
    font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
}
article { /* needed, if sidenotes have large content */
    /* we don't apply this on "section", because we do want the sections to be
     * overlappable with the sidenotes of previous section */
    overflow-y: auto;
}

/****************************/
/* unset/alter org defaults */
/****************************/
#content { /* ID corresponding to main article (excluding pre/postamble) */
    max-width: 100%;
}
/* verse */
p.verse { /* this verse element will always be within a <blockquote> */
    /* tufte.css already provides margin-left/right for enclosing blockquote */
    margin-left: 0;
}
#footnotes { /* needed, if sidenotes have large content */
     /* we want to make sure that the footnotes section starts after all the content
      * for "sections" and "sidenotes" is done */
    clear: both;
    width: unset;
}

/***************************/
/* undo tufte.css defaults */
/***************************/
body pre.example { /* in org, this is within blockquote */
    font-style: normal;
}
/* verse: undoing "blockquote p, blockquote footer" from tufte.css */
div.verse blockquote p, div.verse blockquote footer {
    width: 100%
}

/********************************************************/
/* verse blocks: support and distinguish from epigraphs */
/********************************************************/
div.verse > blockquote {
    /* gray bar on the left */
    border-left: 3px solid #ccc;
    padding-left: 10px;
}

/***********************************/
/* tufte.css consistent extensions */
/***********************************/
/***************************/
/* support for h4 headings */
/***************************/
h4 > code {
    font-size: var(--ox-tufte-heading-code-font-size);
}
h4 {
    font-style: var(--ox-tufte-heading-font-style);
    font-weight: var(--ox-tufte-heading-font-weight);
    margin-bottom: var(--ox-tufte-heading-min-margin-bottom);
    line-height: var(--ox-tufte-heading-line-height);
}
h4 {
    font-size: 1.5rem; /* h3=1.7rem; */
    margin-top: 1.8rem; /* h3=2rem; */
}

/************************************/
/* sidenotes in headings h2, h3, h4 */
/************************************/
h2 .sidenote, h2 .marginnote,
h3 .sidenote, h3 .marginnote,
h4 .sidenote, h4 .marginnote {
    font-style: normal;
    font-weight: normal;
}

/**********************/
/* footnote reference */
/**********************/
.sidenote-number > sup.numeral, .sidenote > sup.numeral {
    /* same as ".sidenote-number:after", ".sidenote:before" */
    font-size: 1rem;
    line-height: 1rem;
}
/* we don't rely on CSS numbering */
.sidenote-number:after, .sidenote:before {
    content: none;
}

/****************/
/* small screen */
/****************/
@media (max-width: 760px) {
    :root {
        --ox-tufte-content-width: 100%;
        --ox-tufte-fullwidth: 100%;
        --ox-tufte-list-width: 90;
        --ox-tufte-list-padding-left: 10%;
    }
    /**********************************************************/
    /* tufte.css fixes; should probably be submitted upstream */
    /**********************************************************/
    .margin-toggle:checked + .sidenote,
    .margin-toggle:checked + .marginnote {
        /* what tufte.css does results in minute horizontal scrolling on*/
        /* sufficiently small screens; below fixes that */
        margin-left: auto;
        margin-right: 0;
        left: 0;
        float: right;
    }
    .marginnote img, .sidenote img {
        height: auto;
        width: auto;
    }
}

/*****************************************************************************/
/* assorted bugfixes and additional tweaks                                   */
/* NOTE: code below likely needs to be refactored and assimilated into above */
/*****************************************************************************/



/* BEGIN: width and placement of sidenotes + lists, epigraphs */
:root {
    --ox-tufte-sidenote-width-max: 385px;
    --ox-tufte-sidenote-width-standard: 50%;
    --ox-tufte-sidenote-margin-right-min: -462px;
    --ox-tufte-sidenote-margin-right-standard: -60%;
}
article > blockquote { /* assuming within an element 55% in width */
    width: 50%;
    margin-left: 5%;
}
blockquote {
    margin-left: calc(100% / 11);
    margin-right: 0;
}
blockquote .sidenote, blockquote .marginnote {
    min-width: unset;
    width:calc(var(--ox-tufte-sidenote-width-standard) / (10 / 11));
    margin-right:calc(var(--ox-tufte-sidenote-margin-right-standard) / (10 / 11));
}
@media (max-width: 760px) {
    div.epigraph blockquote p { overflow-y: auto; }
}
article ul, article ol, article dl {
    width: var(--ox-tufte-list-width); /* 50% */
    padding-inline-start: var(--ox-tufte-list-padding-left); /* 5% */
}
article ul ul, article ol ol, article ul ol, article ol ul {
    padding-inline-start: calc(100% / 10); /* 5% wrt body, when 100% is 50% */
    width: calc(100% * 9 / 10); /* 45% wrt body, when 100% is 50% */
}
article ul ul ul, article ol ol ul, article ul ol ul, article ol ul ul,
article ul ul ol, article ol ol ol, article ul ol ol, article ol ul ol {
    padding-inline-start: calc(100% / 9); /* 5% wrt body, when 100% is 45% */
    width: calc(100% * 8 / 9); /* 40% wrt body, when 100% is 45% */
}
section ul, section ol, section dl {
    padding-inline-start: calc(100% / 11);
    width: calc(100% * 10 / 11);
}
article ul .sidenote, article ol .sidenote,
article ul .marginnote, article ol .marginnote {
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (10 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (10 / 11)));
}
article ul ul .sidenote, article ul ul .marginnote,
article ul ol .sidenote, article ul ol .marginnote,
article ol ol .sidenote, article ol ol .marginnote,
article ol ul .sidenote, article ol ul .marginnote { /* 11/9 = 11/10 X(10/9) */
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (9 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (9 / 11)));
}
article ul ul ul .sidenote, article ul ul ul .marginnote,
article ul ul ol .sidenote, article ul ul ol .marginnote,
article ul ol ul .sidenote, article ul ol ul .marginnote,
article ul ol ol .sidenote, article ul ol ol .marginnote,
article ol ol ul .sidenote, article ol ol ul .marginnote,
article ol ol ol .sidenote, article ol ol ol .marginnote,
article ol ul ul .sidenote, article ol ul ul .marginnote
article ol ul ol .sidenote, article ol ul ol .marginnote { /* 11/8 = 11/10 X(10/9) X(9/8) */
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (8 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (8 / 11)));
}
@media (max-width: 760px) {
    ul li, ol li {
        overflow-y: auto;
        /* however, this causes issues: https://stackoverflow.com/a/40912185 */
        list-style-position: inside;
        margin-left: -1em;
    }
}
/* fix padding issues first and make them percentages */
article dl > dd {
    margin-left: calc(100% / 10);
}
/* fix sidenote width and margin-right */
article dl dt .sidenote, article dl dt .marginnote {
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (10 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (10 / 11)));
    font-weight: normal;
}
article dl dd .sidenote, article dl dd .marginnote { /* 11/9 = 11/10 X(10/9) */
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (9 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (9 / 11)));
}
/* also check on smaller screens */
@media (max-width: 760px) {
    dl dt, dl dd {
        overflow-y: auto;
    }
}
article dl dl {
    padding-inline-start: calc(100% / 9); /* 5% wrt body, when 100% is 45% wrt body */
    width: calc(100% * 8 / 9); /* 40% wrt body */
}
article dl dl > dd {
    margin-left: calc(100% / 8) /* 5% wrt body, when 100% is 40% wrt body */
}
article dl dl dt .sidenote, article dl dl dt .marginnote { /* 11/8 = 11/10 x 10/9 x 9/8 */
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (8 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (8 / 11)));
    font-weight: normal;
}
article dl dl dd .sidenote, article dl dl dd .marginnote { /* 11/7 */
    width: min(var(--ox-tufte-sidenote-width-max), calc(var(--ox-tufte-sidenote-width-standard) / (7 / 11)));
    margin-right: max(var(--ox-tufte-sidenote-margin-right-min), calc(var(--ox-tufte-sidenote-margin-right-standard) / (7 / 11)));
}

/* BEGIN BONUS: fix top-level code blocks */
article>section .org-src-container { /* make .org-src-container in ox-tufte.css more specific */
    max-width: var(--ox-tufte-fullwidth);
    width: var(--ox-tufte-fullwidth);
}
article > div.org-src-container { /* override .org-src-container in ox-tufte.css for toplevel */
    max-width: 90%;
    width: 90%;
}
/* END BONUS: fix top-level code blocks */
/* END: width and placement of sidenotes + lists, epigraphs */

/* BEGIN: width of result blocks */
article > pre.example {
    width: calc(97% * 0.9);
    margin-right: 10%;
}
section pre.example {
    width: calc(var(--ox-tufte-fullwidth) * 0.97);
    margin-left: calc(var(--ox-tufte-fullwidth) * 0.03);
}
@media (max-width: 760px) {
    section pre.example {
        margin-left: auto;
    }
}
/* END: width of result blocks */

/* BEGIN: fix horizontal scroll on small screens */
.org-src-name {
    display: inline-block;
    max-width: 100%;
    overflow-x: auto;
}
/* END: fix horizontal */

/* BEGIN: block margin notes */
/***************************************************************************/
/* block marginnotes - allows us to include lists, paragraphs, tables etc. */
/***************************************************************************/
article div { /* add missing default from tufte.css */
    font-size: var(--ox-tufte-content-font-size);
    line-height: var(--ox-tufte-content-line-height);
}
div.epigraph {
    /* NOTE: tufte.css has margins for epigraph in "em" which relies on font-size
     * for div element not being altered from default; otherwise epigraph margin
     * will scale with it (<https://zellwk.com/blog/rem-vs-em/>). now epigraphs,
     * unlike quotes don't have any business in the margin area. but if we did,
     * it wouldn't make sense to use the same margin absolutely. thus
     * blockquotes should be in 'em' instead of 'rem'. however, in order to get
     * same behaviour as tufte either the 'em' value will need to be modified
     * for epigraph or the font size specified for 'article div' will need to be
     * modified and will no longer be consistent with 'p'. */
    margin: var(--ox-tufte-content-epigraph-margin-vertical) 0;
}
div.marginnote dl,
div.marginnote ol,
div.marginnote p,
div.marginnote ul {
    font-size: var(--ox-tufte-note-font-size);
    line-height: var(--ox-tufte-note-line-height);
}
div.marginnote p {
    margin-top: var(--ox-tufte-note-font-size);
    margin-bottom: var(--ox-tufte-note-font-size);
}
@media (max-width: 760px) {
    div.marginnote + *, /* needed for block marginnotes */
    h2, h3, h4, p { /* these are needed for margin/sidenotes in general */
        clear: both;
    }
}
/* END: block margin notes */

/* BEGIN: captions on figures */
figure > figcaption { /* undo tufte-css tweaks, till they're better supported */
    max-width: unset;
    float: left;
    width: 100%;
}
/* undo tufte-css tweaks, till they're better supported */
figure.fullwidth figcaption {
    margin-right: unset;
}
img.fullwidth + figcaption {
    width: var(--ox-tufte-fullwidth);
}
/* END: captions on figures */

/* BEGIN: fullwidth images */
img.fullwidth {
    display: block;
}
/* END: fullwidth images */

/* BEGIN: prevent side-scroll when result of inline call is long */
article code {
    overflow-x: auto;
    max-width: 100%;
    display: inline-block;
}
/* END: prevent side-scroll when result of inline call is long */

fleuron.svg

Since SVGs are just XML, I can document my favicon/fleuron in the build doc here, it’ll generate and be exported when I tangle the doc. Pretty neat!

<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="20"
height="20" fill-rule="evenodd" clip-rule="evenodd" image-rendering="optimizeQuality"
shape-rendering="geometricPrecision" text-rendering="geometricPrecision"> <path
    d="M9 0v1h-.5v2H8V2H7v-.5h-.5V1H6V.5H3.5V1H3v.5h-.5V2H2v.5h-.5V3H1v1H0v2.5h.5V7h1v-.5H2V6h1v-.5h-.5V5H2V4h-.5v-.5H2V3h.5v-.5H3V2h1v-.5h.5V1H5v1h1v.5h1.5v1H8V4h1v.5h1V5h1.5v1H11v1h-1v1h.5v1h.5v.5h-.5v.5H10V8.5h-.5V8H9v-.5H8V7h-.5v-.5H6V7h-.5v.5h-1v1h-1V10H3v3.5h.5v2h1v1H5v.5h.5v.5H6v.5h.5v.5H7v.5h1v.5h2.5v.5h3v-.5H15V19h.5v-.5h.5V18h.5v-.5h.5V17h-1v.5h-.5v-1H15v.5h-.5v.5H14v1h-.5v.5H13v.5h-2V19h-.5v-2h.5v-.5h1V16h1v-.5h1.5V15h.5v-.5h.5V14h.5v-.5h.5V12h.5v-.5h.5V9H17v-.5h-.5V8H16v-.5h-3.5V8H12v.5h-1v-1h.5v-1h.5v-1h1V6h.5v.5h2V6h.5v-.5h1V5h1V4h.5v-.5h.5V3h.5v-.5h.5V2h-.5v-.5H19V1h-.5V.5H18V0h-.5v.5H17V1h-1v.5h-.5V2h.5v.5h.5V3h1v.5H17V4h.5v.5h-1V5h-1v.5H14V5h-2V4h.5V2.5H12V1h-1V0H9.5zm6.5 16.5h.5V16h-.5ZM10 .5h.5v1h.5V2h.5v.5H11v1h.5v1h-1V4H10v-.5h-.5V3H9V2h.5V1h.5Z">
</path> </svg>

Dockerfile

This is the Dockerfile that will run the build.el build script, and then put that into an nginx container for hosting or testing. Should be noted that this needs to run from the parent directory to build, in this particular case that means in ~/org/..

Start by grabbing silex/emacs and name it ‘builder’.

FROM silex/emacs AS builder

Make the working directory /org. Then make the emacs.d directory, this is mostly useless but it ensures it exists when we install stuff with our use-package in build.el. Then we just add some extra dependencies. I am not sure if I even need build-essential… we need sqlite3 to build the org-roam database. And git-restorem-time is currently unused but I think I will eventually make use of it and so I’m just leaving it here as a reminder:

WORKDIR /org
RUN mkdir -p ~/.emacs.d/private/ && apt-get update && apt-get --yes install build-essential sqlite3 git-restore-mtime

Next, copy the entire org directory to the working directory, cd into build/ and kick off the build.sh script:

COPY .. .
run cd ./build/ && ./build.sh

Finamly, create an nginx container named ‘server’, copy the statically compiled assets to it, and then annotate port 80 as the port to use.

from nginx as server
copy --from=builder /org/build/_html/ /usr/share/nginx/html/
expose 80