#+title: Webbieweb.org Sources * Webbieweb.org Literate Sources :h4left:h5left:h3underline: ** Introduction These are the [[https://en.wikipedia.org/wiki/Literate_programming][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: #+BEGIN_EXAMPLE . |-- 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 ; will be prepended to every output file. |-- postamble.html ;; Navbar; will be prepended to of every output file. `-- preamble.html ;; Footer; as above. #+END_EXAMPLE #+BEGIN_CENTER (Apologies for the misalignment, I don't know why that happens.) #+END_CENTER ** Export Commands Load this file up in Emacs and hit C-c C-c on the code block below to export changes to HTML. #+NAME: blog-publish #+BEGIN_SRC emacs-lisp :noweb no-export :results silent <> (blog/publish) (org-babel-tangle) #+END_SRC The one below will ignore caching; necessary in some situations, for instance: changes under =util/=. #+name: force-publish #+BEGIN_SRC emacs-lisp :noweb no-export :results silent <> (blog/force-publish) (org-babel-tangle) #+END_SRC ** Elisp *** Basic Settings/Scaffolding :autocollapse: #+NAME: blog-scaffolding #+BEGIN_SRC emacs-lisp :results output :noweb no-export (require 'ox-publish) <> (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 "")))) ("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)) #+END_SRC ** Features *** Sitemap :autocollapse: Files with =#+FILETAGS: :no-sitemap:= at the top will be excluded from [[./sitemap.org][the sitemap]]. Every directory =dir/= will link to =dir/index.org=, with =index.org= being removed as a child entry. #+NAME: no-sitemap #+BEGIN_SRC emacs-lisp (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))) #+END_SRC *** Tag-based Section Control :autocollapse: Org-mode headers support tags: #+BEGIN_SRC org ,* Header :foo: Sample text #+END_SRC Which will compile into something like: #+BEGIN_SRC html
Header foo

Sample text

#+END_SRC We can use these tags to granularly control styling and JS features per header. We will hide the tags themselves: #+BEGIN_SRC css :tangle ../html/static/style.css .tag { display: none; } #+END_SRC *** Header Styling Tags :autocollapse: Tags for granular styling. This page has =:h4left:h5left:h3underline:= at the top level. #+BEGIN_SRC css :tangle ../html/static/style.css div:has(* .tag .h4left) h4 { text-align: left; } div:has(* .tag .h5left) h5 { text-align: left; } div:has(* .tag .h3underline) h3 { text-decoration: underline; } #+END_SRC *** Journal :autocollapse: Here's a template for the journal files, as reference for what follows: **** Template :autocollapse: #+INCLUDE: "./journal/template.org" src org **** Styling #+BEGIN_SRC css :tangle ../html/static/style.css 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; } #+END_SRC *** Habit Tracker :autocollapse: Implements the habit tracking table on the [[./index.org][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: #+NAME: habit-tracker #+BEGIN_SRC emacs-lisp :results silent (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 "\n")) ;; Header row with day numbers (setq html (concat html " \n \n")) (dotimes (i days-back) (let ((day (+ (- day-number days-back) i 1))) (setq html (concat html (cond ((= day day-number) "") ((= 0 (% (- day day-number) 7)) "") (t "\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 " \n \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 " \n" class style-var escaped-body doy custom-id todo-state symbol))))) (setq html (concat html " \n")))) (setq html (concat html "
")))))) (setq html (concat html "
%s%s
\n")) ;; Legend and expansion section (setq html (concat html "Key: Unknown | × No | ● Yes | ♦ Excellent | ♣ Freed Up
This section intentionally left blank.
")) html)) (akk0/habits-to-html-table habit-alist (string-to-number (format-time-string "%j")) 30 5) #+END_SRC **** Expansion Section :autocollapse: A bit of JS for enabling the expanding of entries. #+BEGIN_SRC javascript :tangle ../html/static/footnote.js 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 = "This section intentionally left blank." bodyHtml = `

${activity} — ${formatDayOfYear(parseInt(doy), 2025)}

${bodyHtml}` document.getElementById('habitPopupContent').innerHTML = bodyHtml; if (selectedCell) { selectedCell.classList.remove('habitgrid-selected'); } cell.classList.add('habitgrid-selected'); selectedCell = cell; enableFootnotes(); } #+END_SRC **** CSS :autocollapse: Color habit entries based on their completion status: #+BEGIN_SRC css :tangle ../html/static/style.css .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); } #+END_SRC Style the cells, set legible text color, outline clickable: #+BEGIN_SRC css :tangle ../html/static/style.css .habit-cell { text-align: center; vertical-align: middle; width: 20px; height: 20px; cursor: pointer; user-select: none; } .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; } #+END_SRC *** 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). #+BEGIN_SRC css :tangle ../html/static/style.css .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 > h4:has(.tag .autocollapse)::before { content: '⮟ '; } #+END_SRC #+BEGIN_SRC javascript :tangle ../html/static/hide.js 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(); } #+END_SRC *** Hover Notes :autocollapse: Turns org-mode footnotes (=[fn::Like this]=) into hover-notes[fn::Like this]. #+BEGIN_SRC javascript :tangle ../html/static/footnote.js 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); #+END_SRC ** Appearance *** Global :autocollapse: **** Variables #+BEGIN_SRC css :tangle ../html/static/style.css :root { --site-width: 1000px; --vert-content-margin: 0.3rem; --font-size: 1.2rem; --line-height: 1.6rem; --font-family: "Source Serif Pro"; --scale: 2; } #+END_SRC **** Color Variables #+BEGIN_SRC css :tangle ../html/static/style.css :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); } #+END_SRC **** Background image #+BEGIN_SRC css :tangle ../html/static/style.css 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); } #+END_SRC ***** TODO This is a weird approach and has some issues - White line in center in some setups - Crunchy lines depending on DPI **** Fonts #+BEGIN_SRC css :tangle ../html/static/style.css body { font-family: var(--font-family); font-size: var(--font-size); line-height: var(--line-height); word-spacing: 0.25ch; font-weight: 400; } #+END_SRC **** Header/Content/Footer blocks #+BEGIN_SRC css :tangle ../html/static/style.css #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; } #+END_SRC *** Colors :autocollapse: **** Color Assignments #+BEGIN_SRC css :tangle ../html/static/style.css body { color: var(--black); background-color: var(--background); } #+END_SRC **** Syntax Highlighting #+BEGIN_SRC css :tangle ../html/static/style.css 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;} #+END_SRC *** General CSS :autocollapse: **** Et Cetera #+BEGIN_SRC css :tangle ../html/static/style.css hr { border: 0; border-top: 2px dotted var(--black); } 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; } #+END_SRC **** Utility Classes #+BEGIN_SRC css :tangle ../html/static/style.css .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; } #+END_SRC *** Elements :autocollapse: **** Looking for Work #+BEGIN_SRC css :tangle ../html/static/style.css #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; } #+END_SRC **** Code Blocks #+BEGIN_SRC css :tangle ../html/static/style.css .src, .example { font-family: "monospace"; font-size: 1rem; } #+END_SRC **** Publish/modified Date #+BEGIN_SRC css :tangle ../html/static/style.css #publish-date, #modified-date { font-style: italic; } #+END_SRC **** Table of Contents #+BEGIN_SRC css :tangle ../html/static/style.css #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; } #+END_SRC **** To-Dos #+BEGIN_SRC css :tangle ../html/static/style.css .TODO { color: var(--red3); } .DONE { color: var(--green3); } #+END_SRC