34 KiB
Webbieweb.org Sources
Webbieweb.org Literate Sources h4left h5left h3underline
Introduction
These are the literate sources of webbieweb.org. The code blocks here constitute the actual sources; therefore, this document is guaranteed to be up to date.
This page is self-contained; the only dependency is ox-publish. The following directory structure is expected:
.
|-- html ;; Export destination; you can copy this dir to any static file host and host the website that way.
|-- org ;; Org-mode sources; these will get compiled and the output copied to html/.
`-- source.org ;; This file.
|-- static ;; Images, videos, CSS files, fonts etc will be copied unchanged to html/static.
`-- util
|-- head.html ;; Contains HTML <head>; will be prepended to every output file.
|-- postamble.html ;; Navbar; will be prepended to <body> of every output file.
`-- preamble.html ;; Footer; as above.
(Apologies for the misalignment, I don't know why that happens.)
Export Commands
Load this file up in Emacs and hit C-c C-c on the code block below to export changes to HTML.
<<blog-scaffolding>>
(blog/publish)
(org-babel-tangle)
The one below will ignore caching; necessary in some situations, for instance: changes under util/.
Currently the habit tracker table also gets erronously cached.
<<blog-scaffolding>>
(blog/force-publish)
(org-babel-tangle)
Elisp
Basic Settings/Scaffolding autocollapse
(require 'ox-publish)
<<no-sitemap>>
(defun blog/get-util (x)
(with-temp-buffer
(insert-file-contents (concat "../util/" x))
(buffer-string)))
(setq org-html-metadata-timestamp-format "%Y-%m-%d")
(defun blog/spec ()
"Return project settings for use with `org-publish-project-alist'."
(let* ((html-head (blog/get-util "head.html"))
(html-preamble (blog/get-util "preamble.html"))
(html-postamble (blog/get-util "postamble.html")))
`(("pages"
:base-directory "."
:base-extension "org"
:recursive t
:publishing-directory "../html"
:publishing-function org-html-publish-to-html
:html-doctype "html5"
:html-html5-fancy t
:html-viewport ((width "100%")
(initial-scale "0.7")
(minimum-scale "")
(maximum-scale "")
(user-scalable ""))
:language "en"
:section-numbers nil
:with-toc nil
:with-date t
:with-title nil
:with-author nil
:auto-sitemap t
:sitemap-sort-files anti-chronologically
:sitemap-format-entry blog/sitemap-format-entry
:sitemap-function
blog/sitemap-function
:headline-levels 4
:html-head ,html-head
:html-preamble ,html-preamble
:html-postamble
(lambda (p)
(let* ((timestamp-format (plist-get p :html-metadata-timestamp-format))
(date (org-export-data (org-export-get-date p timestamp-format)
p))
(file (plist-get p :input-file))
(modified (format-time-string
timestamp-format
(and file (file-attribute-modification-time
(file-attributes file))))))
(concat
"<div id='footer'>"
(when (not (string-blank-p (format "%s" date)))
(format "<div id='publish-date'>Published: %s</div>" date))
(format "<div id='modified-date'>Last modified: %s</div>" modified)
,html-postamble
"</div>"))))
("static"
:base-directory "../static"
:base-extension "css\\|txt\\|jpg\\|gif\\|png\\|ttf\\|js\\|mp4\\|webm"
:recursive t
:publishing-directory "../html/static"
:publishing-function org-publish-attachment)
("RSS"
:base-directory "../RSS"
:base-extension "xml"
:publishing-directory "../html"
:publishing-function org-publish-attachment)
("blog" :components ("pages" "static" "RSS")))))
(defun blog/publish ()
(setq org-publish-project-alist (blog/spec))
(org-publish-all))
(defun blog/force-publish ()
(setq org-publish-project-alist (blog/spec))
(org-publish-remove-all-timestamps)
(org-publish-all))
Features
Sitemap autocollapse
Files with #+FILETAGS: :no-sitemap: at the top will be excluded from the sitemap. Every directory dir/ will link to dir/index.org, with index.org being removed as a child entry.
(defun blog/sitemap-format-entry
(entry style project)
;; Blank out entries with :no_sitemap:
(let* ((tags (org-publish-find-property entry :filetags project))
(skip (member "no_sitemap" tags)))
(cond
(skip "")
((not (directory-name-p entry))
(format
"[[file:%s][%s]]\n"
entry
(org-publish-find-title entry project)))
;; Link index to top level
((eq style 'tree)
(format
"[[file:%sindex.org][%s]]\n"
entry
(capitalize (file-name-nondirectory (directory-file-name entry)))))
(t entry))))
;; Remove blank entries from sitemap
(defun blog/sitemap-function
(title lst)
(let* ((sitemap (org-publish-sitemap-default title lst)))
(replace-regexp-in-string "^[ \t]*-[ \t]*\n" "" sitemap)))
Tag-based Section Control autocollapse
Org-mode headers support tags:
* Header :foo:
Sample text
Which will compile into something like:
<div id="outline-container-orgf43334f" class="outline-5">
<h5 id="orgf43334f" style="cursor: pointer;">Header
<span class="tag">
<span class="foo">foo</span>
</span>
</h5>
<div class="outline-text-5" id="text-orgf43334f">
<p> Sample text </p>
</div>
</div>
We can use these tags to granularly control styling and JS features per header. We will hide the tags themselves:
.tag { display: none; }
Header Styling Tags autocollapse
Tags for granular styling. This page has :h4left:h5left:h3underline: at the top level.
div:has(* .tag .h4left) h4 {
text-align: left;
}
div:has(* .tag .h5left) h5 {
text-align: left;
}
div:has(* .tag .h3underline) h3 {
text-decoration: underline;
}
Journal autocollapse
Here's a template for the journal files, as reference for what follows:
Template autocollapse
Styling
div:has(h3 .tag .journal) {
border: none;
box-shadow: none;
}
div:has(> h3 .tag .journal)::after {
content: '~ ❦ ~';
display: block;
text-align: center;
padding-top: 0.2rem;
padding-bottom: 0.2rem;
color: var(--grey4);
font-size: 2rem;
}
h3:has(.tag .journal) {
text-align: left;
}
h4:has(.tag .jentry) {
display: none;
}
h4:has(.tag .habits) {
text-align: left;
}
Habit Tracker autocollapse
Implements the habit tracking table on the front page. Habits are extracted from journal entries and rendered into a table, including a "streak" heatmap and the ability to click cells to see the corresponding entry.
Elisp autocollapse
(defun akk0/org-to-html (org-string)
"Convert ORG-STRING to HTML."
(with-temp-buffer
(insert org-string)
(org-mode)
(org-export-as 'html nil nil t nil)))
(defun akk0/sort-habits (habit-alist)
"Sort habit-alist by a predefined order of custom-ids."
(let ((order '("dailies-blogging" "dailies-meditation" "dailies-french" "dailies-engineering"
"dailies-exercise" "dailies-drawing" "dailies-reading" "dailies-social")))
(sort (copy-sequence habit-alist)
(lambda (a b)
(let ((pos-a (or (cl-position (car a) order :test #'equal) 999))
(pos-b (or (cl-position (car b) order :test #'equal) 999)))
(< pos-a pos-b))))))
(defun akk0/extract-habits (file)
"Extract habits with date context from FILE.
Returns a list of plists with :custom-id, :todo-state, :date, :day-of-year."
(with-temp-buffer
(insert-file-contents file)
(org-mode)
(let (results)
(org-element-map (org-element-parse-buffer) 'headline
(lambda (hl)
(let ((custom-id (org-element-property :CUSTOM_ID hl))
(todo-state (org-element-property :todo-keyword hl)))
(when (and custom-id todo-state)
;; Get parent properties for context
(let* ((parent (org-element-property :parent hl))
(day-of-year (or (org-element-property :DAILIES-DAY parent)
(let ((grandparent (org-element-property :parent parent)))
(when grandparent
(org-element-property :DAILIES-DAY grandparent)))))
(body (org-element-interpret-data
(org-element-contents hl))))
(push (list :custom-id custom-id
:todo-state todo-state
:day-of-year day-of-year
:file file
:body body)
results))))))
(nreverse results))))
(defun akk0/extract-all-habits (files)
(mapcan #'akk0/extract-habits files))
(setq akk0/journal-files
(directory-files "./journal/" t "^w.*\\.org$"))
(defun akk0/habits-alist (habits)
"Transform HABITS list into nested alists: custom-id → day-of-year → habit-data."
(let (result)
(dolist (habit habits)
(let* ((custom-id (plist-get habit :custom-id))
(day-of-year (plist-get habit :day-of-year))
(todo-state (plist-get habit :todo-state))
(body (plist-get habit :body))
;; Get the alist for this custom-id
(inner-alist (alist-get custom-id result nil nil #'equal))
;; Store full data instead of just todo-state
(habit-data (list :todo-state todo-state :body body :day-of-year day-of-year))
;; Update the inner alist
(updated-inner (cons (cons day-of-year habit-data) inner-alist)))
;; Update result
(setf (alist-get custom-id result nil nil #'equal) updated-inner)))
result))
(setq habit-alist (akk0/habits-alist (akk0/extract-all-habits akk0/journal-files)))
(defun akk0/get-habit-history (habit-alist custom-id day-number days-back window-size)
(let* ((inner-alist (alist-get custom-id habit-alist nil nil #'equal))
(result nil)
(all-states nil)
(score-for-state (lambda (state)
(cond ((equal state "NO") -1)
((equal state "YES") 1)
((equal state "EXCELLENT") 2)
(t 0)))))
(dotimes (i days-back)
(let* ((current-day (+ (- day-number days-back) i 1))
(current-day-str (number-to-string current-day))
(habit-data (alist-get current-day-str inner-alist nil nil #'equal))
(todo-state (if habit-data
(plist-get habit-data :todo-state)
"NODATA"))
(body (if habit-data
(plist-get habit-data :body)
""))
(doy (if habit-data
(plist-get habit-data :day-of-year)
""))
)
(push todo-state all-states)
(let* ((window-states (seq-take all-states window-size))
(rolling-score (apply #'+ (mapcar score-for-state window-states))))
(push (list :todo-state todo-state
:score (max 1 (min 5 (/ (+ rolling-score 5) 2)))
:body body
:doy doy)
result))))
(nreverse result)))
(defun akk0/habits-to-html-table (habit-alist day-number days-back window-size)
"Generate HTML table of habits with rolling scores.
Rows are custom-ids, columns are days."
(let ((color-map '(("NODATA" . "grey")
("YES" . "green")
("NO" . "red")
("FREED" . "purple")
("EXCELLENT" . "blue")))
(symbol-map '(("NODATA" . "")
("YES" . "●")
("NO" . "×")
("FREED" . "♣")
("EXCELLENT" . "♦")))
(sorted-habits (akk0/sort-habits habit-alist))
(html ""))
;; Start table
(setq html (concat html "<table class='habit-table' style='margin-left: auto; margin-right:auto; margin-bottom: 0.8rem;'>\n"))
;; Header row with day numbers
(setq html (concat html " <tr>\n <th></th>\n"))
(dotimes (i days-back)
(let ((day (+ (- day-number days-back) i 1)))
(setq html (concat
html
(cond ((= day day-number) "<th class='habit-click-me'>click me ↓</th>")
((= 0 (% (- day day-number) 7)) "<th>○</th>")
(t "<th />"))))))
(setq html (concat html " </tr>\n"))
;; Data rows - one per habit
(dolist (entry sorted-habits)
(let* ((custom-id (car entry))
(history (akk0/get-habit-history habit-alist custom-id day-number days-back window-size)))
(setq html (concat html (format " <tr>\n <td class='habit-name' style='padding-right: 20px; padding-top: 5px; padding-bottom: 5px;'><i>%s</i></td>\n" (capitalize (string-remove-prefix "dailies-" custom-id)))))
;; Cell for each day
(dolist (day-data history)
(let* ((todo-state (plist-get day-data :todo-state))
(score (plist-get day-data :score))
(body (plist-get day-data :body))
(doy (plist-get day-data :doy))
(body-html (if (and body (not (string-empty-p body)))
(akk0/org-to-html body)
""))
(color (alist-get todo-state color-map nil nil #'equal))
(symbol (alist-get todo-state symbol-map nil nil #'equal))
(class (format "habit-brightness-%d" score))
(style-var (format "--%s%d" color score))
(escaped-body (replace-regexp-in-string "\"" """
(replace-regexp-in-string "\n" " " body-html)))
)
(setq html (concat html (format " <td class=\"%s habit-cell\" style=\"background-color:var(%s)\" data-body=\"%s\" onclick=\"showHabitPopup(this)\" data-doy=\"%s\" data-activity=\"%s\"
data-status=\"%s\">%s</td>\n"
class style-var
escaped-body
doy
custom-id
todo-state
symbol)))))
(setq html (concat html " </tr>\n"))))
(setq html (concat html "</table>\n"))
;; Legend and expansion section
(setq html (concat html "<span class='center'><b>Key:</b>
<span style='color: var(--grey3);'>Unknown</span> |
<span style='color: var(--red3);'>× No</span> |
<span style='color: var(--green3);'>● Yes</span> |
<span style='color: var(--blue3);'>♦ Excellent</span> |
<span style='color: var(--purple3);'>♣ Freed Up</span>
</span>
<hr />
<div class='habit-popup' id='habitPopup'>
<div class='habit-popup-content' id='habitPopupContent'>
<span class='center'><i>This section intentionally left blank.</i></span>
</div>
</div>
"))
html))
(akk0/habits-to-html-table habit-alist (string-to-number (format-time-string "%j")) 21 5)
Expansion Section autocollapse
A bit of JS for enabling the expanding of entries.
function formatDayOfYear(dayOfYear, year) {
const date = new Date(year, 0, dayOfYear);
return date.toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
year: 'numeric'
});
}
let selectedCell = null;
function showHabitPopup(cell) {
var bodyHtml = cell.getAttribute('data-body');
var doy = cell.getAttribute('data-doy');
var activity = cell.getAttribute('data-activity');
var status = cell.getAttribute('data-status');
activity = activity.replace(/^dailies-/, '').replace(/^./, c => c.toUpperCase())
if (!bodyHtml) bodyHtml = "<span class='center'><i>This section intentionally left blank.</i></span>"
bodyHtml = `<h3><span class="grid ${status}">${activity}</span> — ${formatDayOfYear(parseInt(doy), 2025)}</h3> ${bodyHtml}`
document.getElementById('habitPopupContent').innerHTML = bodyHtml;
if (selectedCell) {
selectedCell.classList.remove('habitgrid-selected');
}
cell.classList.add('habitgrid-selected');
selectedCell = cell;
enableFootnotes();
}
CSS autocollapse
Color habit entries based on their completion status:
.done.YES, .todo.NO, .done.EXCELLENT, .done.FREED {
display: none;
}
h2:has(.done.YES), h3:has(.done.YES), h4:has(.done.YES), h5:has(.done.YES), .grid.YES {
color: var(--green3);
}
h2:has(.todo.NO), h3:has(.todo.NO), h4:has(.todo.NO), h5:has(.todo.NO), .grid.NO {
color: var(--red3);
}
h2:has(.done.EXCELLENT), h3:has(.done.EXCELLENT), h4:has(.done.EXCELLENT), h5:has(.done.EXCELLENT), .grid.EXCELLENT {
color: var(--blue3);
}
h2:has(.done.FREED), h3:has(.done.FREED), h4:has(.done.FREED), h5:has(.done.FREED), .grid.FREED {
color: var(--purple3);
}
Style the cells, set legible text color, outline clickable:
.habit-table th, .habit-table td {
text-align: center;
vertical-align: middle;
width: 20px;
height: 20px;
cursor: pointer;
user-select: none;
padding: 8px;
max-width: 22px;
}
.habit-table th {
padding: 0;
}
.habit-click-me {
font-weight: normal;
font-size: calc(var(--font-size) * 0.7);
line-height: calc(var(--line-height) * 0.7);
text-align: center;
}
.habit-name {
max-width: 15rem !important;
}
.habit-cell.habit-brightness-1 { color: var(--grey5); outline-color: var(--grey5) !important; }
.habit-cell.habit-brightness-2 { color: var(--grey5); outline-color: var(--grey5) !important; }
.habit-cell.habit-brightness-3 { color: var(--grey5); outline-color: var(--grey5) !important; }
.habit-cell.habit-brightness-4 { color: var(--grey2); outline-color: var(--grey5) !important; }
.habit-cell.habit-brightness-5 { color: var(--grey3); outline-color: var(--grey5) !important; }
.habit-cell:not([data-body=""]) {
outline: dotted 2px;
outline-offset: -2px;
}
.habitgrid-selected {
outline: solid 2px var(--purple5) !important;
outline-offset: -2px;
}
Header Collapsing autocollapse
Sections can be expanded and collapsed by clicking on their headers; this will assign .orgjq-expanded and .orgjq-contracted CSS classes as appropriate. Headers with the :autocollapse: tag will be collapsed by default (like this section).
.orgjq-expanded p {
margin-top: 0;
padding-bottom: 1.5rem;
margin-bottom: 0;
}
.orgjq-contracted > div {
display: none;
}
.orgjq-contracted h2, .orgjq-contracted h3, .orgjq-contracted h4, orgjq-contracted h5 {
padding-top: 0.3rem !important;
padding-bottom: 0.3rem !important;
}
.orgjq-contracted > :first-child::before {
content: '⮞ ';
}
.orgjq-expanded > h3:has(.tag .autocollapse)::before,
.orgjq-expanded > h4:has(.tag .autocollapse)::before,
.orgjq-expanded > h5:has(.tag .autocollapse)::before {
content: '⮟ ';
}
function isHideable(div_obj) {
// Ignore for TOC since it is handled differently
if (div_obj.id === "text-table-of-contents") return false;
if (div_obj.id === "table-of-contents") return false;
// No point in hiding top level
if (div_obj.classList.contains("outline-2")) return false;
if (div_obj.classList.contains("outline-text-2")) return false;
return true;
}
function orgjqHide(div_obj) {
if (!isHideable(div_obj)) return;
const parent = div_obj.parentElement;
parent.classList.remove("orgjq-expanded");
parent.classList.add("orgjq-contracted");
}
function orgjqShow(div_obj) {
const parent = div_obj.parentElement;
parent.classList.remove("orgjq-contracted");
parent.classList.add("orgjq-expanded");
}
function orgjqToggle(div_obj) {
const parent = div_obj.parentElement;
if (parent.classList.contains("orgjq-expanded")) {
orgjqHide(div_obj);
} else {
orgjqShow(div_obj);
}
}
function orgjqEnable() {
// Called once e.g. the first time the page is loaded
// handle the click event for each header
for (let i = 2; i <= 7; ++i) {
const headers = document.querySelectorAll(`h${i}`);
headers.forEach(header => {
header.style.cursor = "pointer";
header.addEventListener('click', function() {
// Get the first div sibling after the header
const parent = this.parentElement;
const divs = parent.querySelectorAll(':scope > div');
if (divs.length > 0) {
orgjqToggle(divs[0]);
}
});
});
}
// Mark everything as open...
for (let i = 2; i <= 7; ++i) {
const headers = document.querySelectorAll(`h${i}`);
headers.forEach(header => {
const parent = header.parentElement;
parent.classList.remove("orgjq-contracted");
parent.classList.add("orgjq-expanded");
});
}
// ... except TOC ...
const toc = document.querySelector("div#table-of-contents");
if (toc) {
toc.classList.remove("orgjq-expanded");
toc.classList.add("orgjq-contracted");
}
// ... and autocollapse.
const autocollapse = document.querySelectorAll(".autocollapse");
autocollapse.forEach(element => {
const grandparent = element.parentElement?.parentElement;
if (grandparent) {
orgjqHide(grandparent);
}
});
}
// Run when DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', orgjqEnable);
} else {
orgjqEnable();
}
Hover Notes autocollapse
Turns org-mode footnotes ([fn::Like this]) into hover-notes1.
function enableFootnotes() {
const footnoteRefs = document.querySelectorAll('.footref')
footnoteRefs.forEach(ref => {
const footnoteId = ref.href.split('#')[1]
ref.setAttribute('data-footnote-id', footnoteId)
ref.href = 'javascript:void(0)';
ref.addEventListener('mouseenter', function() {
// Find footnote element
const footnoteId = this.getAttribute('data-footnote-id')
const footnoteElement = document.getElementById(footnoteId).parentElement.nextElementSibling.children[0]
if (footnoteElement) {
// Create tooltip container
const tooltip = document.createElement('div');
tooltip.className = 'footnote-tooltip'
tooltip.innerHTML = footnoteElement.innerHTML
// Position tooltip
const rect = this.getBoundingClientRect();
tooltip.style.position = 'absolute'
tooltip.style.top = (rect.bottom + window.scrollY + 5) + 'px'
tooltip.style.left = rect.left + 'px'
// Add to page
document.body.appendChild(tooltip)
// Store reference for cleanup
this._tooltip = tooltip
}
});
ref.addEventListener('mouseleave', function() {
// Remove tooltip
if (this._tooltip) {
this._tooltip.remove()
this._tooltip = null
}
});
});
// Hide footnotes section
document.querySelector("#footnotes").style.display='none'
}
document.addEventListener('DOMContentLoaded', enableFootnotes);
.footnote-tooltip {
position: 'absolute';
z-index: 1000;
background-color: var(--background-tooltip);
border: 1px solid var(--accent1);
border-radius: = 4px;
padding: 8px 12px;
max-width: 300px;
font-size: calc(var(--font-size) * 0.9);
box-shadow: 1px 1px 1px var(--grey2);
}
Appearance
Global autocollapse
Variables
:root {
--site-width: 1000px;
--vert-content-margin: 0.3rem;
--font-size: 1.2rem;
--line-height: 1.6rem;
--font-family: "Source Serif Pro";
--scale: 2;
}
Color Variables
:root {
--green1: #123218;
--green2: #254f1b;
--green3: #356c22;
--green4: #5e8e40;
--green5: #85aa5f;
--blue1: #1b2459;
--blue2: #153a79;
--blue3: #2f5394;
--blue4: #417eaf;
--blue5: #69a8c6;
--purple1: #2e1e58;
--purple2: #493281;
--purple3: #6a45a6;
--purple4: #855aa3;
--purple5: #9b77b5;
--red1: #4b1313;
--red2: #682017;
--red3: #84301c;
--red4: #9c4830;
--red5: #c46849;
--grey1: #202124;
--grey2: #33333b;
--grey3: #605b66;
--grey4: #a79fa7;
--grey5: #c5bcbc;
--accent1: var(--purple4);
--link: var(--blue3);
--black: var(--grey1);
--grey: var(--grey3);
--background: var(--grey5);
--background-tooltip: var(--grey5);
}
Background image
body::before,
body::after {
content: '';
position: fixed;
top: 0;
height: 100vh;
width: 50%;
background-image: url('/static/images/background.png');
background-repeat: no-repeat;
z-index: -1;
image-rendering: pixelated; /* For Chrome/Safari */
image-rendering: -moz-crisp-edges; /* For Firefox */
image-rendering: crisp-edges; /* Fallback */
background-size: calc(960px * var(--scale)) calc(1080px * var(--scale));
}
body::before {
left: 0;
background-position: top right;
}
body::after {
right: 0;
background-position: top right;
/* Flip the right side horizontally */
transform: scaleX(-1);
}
TODO This is a weird approach and has some issues
- White line in center in some setups
- Crunchy lines depending on DPI
Fonts
body {
font-family: var(--font-family);
font-size: var(--font-size);
line-height: var(--line-height);
word-spacing: 0.25ch;
font-weight: 400;
}
Header/Content/Footer blocks
#content, #header, #footer {
min-width: 500px;
max-width: min(var(--site-width), 90vw);
margin: 0 auto;
padding: 5px 25px;
border: 2px double var(--grey1);
margin-bottom: 1.2rem;
background-color: var(--background);
}
#header {
display: flex;
}
#content, #footer {
box-shadow: 7px 7px 7px var(--grey1);
}
#footer {
padding-top: 1rem;
padding-bottom: 1rem;
}
#header a, #header a:visited{
color: var(--purple2);
}
#header hr {
margin-bottom: var(--vert-content-margin);
}
#footer hr {
margin-top: var(--vert-content-margin);
}
#content h2, #content h3, #content h4, #content h5 {
margin-bottom: 1rem;
margin-top: 0.5rem;
margin-left: 0.5rem;
}
#content h2 {
margin-top: 1rem;
}
#content h3, #content h4 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
#content h5 {
padding-top: 0.3rem;
padding-bottom: 0.3rem;
}
Colors autocollapse
Color Assignments
body {
color: var(--black);
background-color: var(--background);
}
Syntax Highlighting
pre {background-color:var(--grey1); color:var(--grey5);}
pre span.org-builtin {color:var(--blue4);font-weight:bold;}
pre span.org-string {color:var(--green4);}
pre span.org-keyword {color:var(--purple5);font-weight:bold;}
pre span.org-variable-name {color:var(--green5);font-style:italic;}
pre span.org-function-name {color:var(--blue5);}
pre span.org-type {color:var(--purple4);}
pre span.org-preprocessor {color:var(--grey5);font-weight:bold;}
pre span.org-constant {color:var(--red5);}
pre span.org-comment-delimiter {color:var(--grey3);}
pre span.org-comment {color:var(--grey3);font-style:italic}
pre span.org-outshine-level-1 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-2 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-3 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-4 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-5 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-6 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-7 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-8 {color:var(--grey5);font-style:italic}
pre span.org-outshine-level-9 {color:var(--grey5);font-style:italic}
pre span.org-rainbow-delimiters-depth-1 {color:var(--grey4);}
pre span.org-rainbow-delimiters-depth-2 {color:var(--blue4);}
pre span.org-rainbow-delimiters-depth-3 {color:var(--green4);}
pre span.org-rainbow-delimiters-depth-4 {color:var(--red4);}
pre span.org-rainbow-delimiters-depth-5 {color:var(--purple4);}
pre span.org-rainbow-delimiters-depth-6 {color:var(--blue4);}
pre span.org-rainbow-delimiters-depth-7 {color:var(--green4);}
pre span.org-rainbow-delimiters-depth-8 {color:var(--red4);}
pre span.org-rainbow-delimiters-depth-9 {color:var(--purple4);}
pre span.org-sh-quoted-exec {color:var(--purple3);}
pre span.org-doc {color:var(--green5);font-style:italic;}
pre span.org-css-selector {color:var(--blue5);font-weight:bold;}
pre span.org-css-property {color:var(--purple4); font-weight: bold;}
General CSS autocollapse
Et Cetera
tbody { border-bottom: 1px dotted var(--grey1); }
thead { border-bottom: 1px solid var(--grey1); }
th, td { padding-right: 4rem; }
th.org-left { text-align: left; }
hr {
border: 0;
border-top: 2px dotted var(--grey1);
}
a, a:visited {
color: var(--link);
text-decoration: none;
}
.figure-number {
display: none;
}
.caption {
padding-top: 0.5rem;
font-style: italic;
}
.outline-3, .outline-4, .outline-5 {
padding-left: 0.6rem;
border-radius: 0.2em;
margin: 0.7rem;
}
.outline-text-3, .outline-text-4, .outline-text-5 {
margin: 0.3rem 1rem 0.5rem 0.5rem;
}
h2 {
line-height: 1.5rem;
text-align: center;
}
h3, h4, h5 {
text-align: center;
}
.section-number-1, .section-number-2, .section-number-3, .section-number-4 { display: none; }
ul { margin-top: 0; }
Utility Classes
.center {
display: block;
margin-left: auto;
margin-right: auto;
text-align: center;
}
.navbar-link {
margin-right: 5px;
margin-left: 5px;
}
.center, div.center {
text-align: center;
margin-left: auto;
margin-right: auto;
}
.center figure,
.center figcaption,
div.center figure,
div.center figcaption {
text-align: center;
margin-left: auto;
margin-right: auto;
}
.multi-img {
display: flex;
justify-content: center;
text-align: center;
margin: 20px;
gap: 20px;
}
.multi-img figure { margin: 0px; }
.poetry {
padding-top: 4rem;
padding-bottom: 4rem;
text-align: center;
}
Elements autocollapse
Looking for Work
#looking-for-work {
background-color: var(--red2);
color: var(--grey5);
padding: 5px 25px;
margin-left: auto;
margin-top: -0.32rem;
margin-bottom: -0.32rem;
margin-right: -1.55rem;
border: 2px double var(--purple5);
}
#looking-for-work a, #looking-for-work a:visited {
color: var(--blue5) !important;
}
Code Blocks
.src, .example {
font-family: monospace;
font-size: 1rem;
}
Publish/modified Date
#publish-date, #modified-date {
font-style: italic;
}
Table of Contents
#table-of-contents {
z-index: 1;
margin-top: 105px;
margin-right: 5%;
font-size: calc(var(--font-size) * 0.8);
position: fixed;
right: 0em;
top: 0em;
background: var(--background-toc);
text-align: right;
min-height: 3rem;
box-shadow: 0 0 0.5em var(--shadow-toc);
-webkit-box-shadow: 0 0 0.5em var(--shadow-toc);
-moz-box-shadow: 0 0 0.5em var(--shadow-toc);
-webkit-border-bottom-left-radius: 5px;
-moz-border-radius-bottomleft: 5px;
/* ensure doesn't flow off the screen when expanded */
max-height: 80%;
overflow: auto;
}
#table-of-contents h2 {
font-size: 13pt;
max-width: 9em;
border: 0;
font-weight: normal;
margin-top: 0.75em;
margin-bottom: 0.75em;
padding-left: 0.5em;
padding-right: 0.5em;
padding-top: 0.05em;
padding-bottom: 0.05em; }
#table-of-contents #text-table-of-contents {
display: none;
text-align: left; }
#table-of-contents:hover #text-table-of-contents {
display: block;
padding: 0.5em;
margin-top: -1.5em;
padding-right: 20px;
}
#table-of-contents {
display: none;
}
To-Dos
.TODO { color: var(--red3); }
.DONE { color: var(--green3); }
Like this