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
#+title: Journal Week XX, XXXX #+BEGIN_CENTER Previous | Index #+END_CENTER * Journal Week XX, XXXX ** Monday, XXth XXXXXXXX :journal: :PROPERTIES: :DAILIES-YEAR: 2025 :DAILIES-DAY: XXX :END: *** NO Journal :jentry: :PROPERTIES: :CUSTOM_ID: dailies-blogging :END: *** Habits :autocollapse:habits: **** NO Meditation :PROPERTIES: :CUSTOM_ID: dailies-meditation :END: **** NO Drawing :PROPERTIES: :CUSTOM_ID: dailies-drawing :END: **** NO Engineering :PROPERTIES: :CUSTOM_ID: dailies-engineering :END: **** NO French :PROPERTIES: :CUSTOM_ID: dailies-french :END: **** NO Social :PROPERTIES: :CUSTOM_ID: dailies-social :END: **** NO Exercise :PROPERTIES: :CUSTOM_ID: dailies-exercise :END: **** NO Reading :PROPERTIES: :CUSTOM_ID: dailies-reading :END:
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")) 25 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: .2em; 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); }
Colored Table Rows autocollapse
By adding some invisible helper elements like so:
| Cool colors: | |-------------------------| | Red {{{c(red4)}}} | |-------------------------| | Green {{{c(green4)}}} | |-------------------------| | Blue {{{c(blue4)}}} | |-------------------------| | Purple {{{c(purple4)}}} | |-------------------------| | Grey {{{c(grey4)}}} | |-------------------------|
We can color table rows like so:
| Cool colors: |
|---|
| Red |
| Green |
| Blue |
| Purple |
| Grey |
CSS autocollapse
tbody:has(.row-color-green1) { background-color: var(--green1); } tbody:has(.row-color-green2) { background-color: var(--green2); } tbody:has(.row-color-green3) { background-color: var(--green3); } tbody:has(.row-color-green4) { background-color: var(--green4); } tbody:has(.row-color-green5) { background-color: var(--green5); } tbody:has(.row-color-red1) { background-color: var(--red1); } tbody:has(.row-color-red2) { background-color: var(--red2); } tbody:has(.row-color-red3) { background-color: var(--red3); } tbody:has(.row-color-red4) { background-color: var(--red4); } tbody:has(.row-color-red5) { background-color: var(--red5); } tbody:has(.row-color-blue1) { background-color: var(--blue1); } tbody:has(.row-color-blue2) { background-color: var(--blue2); } tbody:has(.row-color-blue3) { background-color: var(--blue3); } tbody:has(.row-color-blue4) { background-color: var(--blue4); } tbody:has(.row-color-blue5) { background-color: var(--blue5); } tbody:has(.row-color-purple1) { background-color: var(--purple1); } tbody:has(.row-color-purple2) { background-color: var(--purple2); } tbody:has(.row-color-purple3) { background-color: var(--purple3); } tbody:has(.row-color-purple4) { background-color: var(--purple4); } tbody:has(.row-color-purple5) { background-color: var(--purple5); } tbody:has(.row-color-grey1) { background-color: var(--grey1); } tbody:has(.row-color-grey2) { background-color: var(--grey2); } tbody:has(.row-color-grey3) { background-color: var(--grey3); } tbody:has(.row-color-grey4) { background-color: var(--grey4); } tbody:has(.row-color-grey5) { background-color: var(--grey5); }
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", "Noto Sans Symbols 2"; --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); }
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); }
No unstyled content autocollapse
html { visibility: visible; opacity: 1; }
Footnotes:
Like this
![Validate my RSS feed [Valid RSS]](/static/images/badges/css.png)