528 lines
21 KiB
HTML
528 lines
21 KiB
HTML
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta author="Alex Mikhailov"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta name="color-scheme" content="light dark"/><meta http-equiv="content-language" content="en-us"/><meta name="description" content="Blog post about publishing my blog with Org Mode"/><meta property="og:description" content="Blog post about publishing my blog with Org Mode"/><meta property="og:image" content="https://fidonode.me/resources/images/preview/posts/about_blog.org.png"/><meta property="og:title" content="Org to HTML and back"/><meta name="twitter:description" content="Blog post about publishing my blog with Org Mode"/><meta name="twitter:title" content="Org to HTML and back"/><meta name="twitter:image" content="https://fidonode.me/resources/images/preview/posts/about_blog.org.png"/><meta name="twitter:card" content="summary_large_image"/><link rel="icon" type="image/x-icon" href="/resources/favicon.ico"/><link rel="stylesheet" type="text/css" href="/resources/css/pico.sand.min.css"/><script defer="true" src="https://umami.dokutsu.xyz/script.js" data-website-id="d52d9af1-0c7d-4531-84c6-0b9c2850011f"></script><title>Org to HTML and back</title><link id="highlight-theme" rel="stylesheet" type="text/css"/><script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script><script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/bash.min.js"></script><script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/lisp.min.js"></script><script src="/resources/js/theme-selector.js"></script></head><body><header class="header"><div class="container"><nav><ul><li><strong>Alex Mikhailov</strong></li></ul><ul><li><a href="/index.html">About</a></li><li><a href="/posts.html">Blog</a></li><li><a href="/rss.xml">RSS</a></li></ul></nav></div></header><main class="container blog-post"><hgroup><h1>Org to HTML and back</h1><p>Blog post about publishing my blog with Org Mode</p><nav><ul><li>Tags:</li><li><mark><a href="/tags/@org-mode.html" class="secondary">@org-mode</a></mark></li></ul></nav></hgroup><div id="table-of-contents" role="doc-toc">
|
|
<h2>Table of Contents</h2>
|
|
<div id="text-table-of-contents" role="doc-toc">
|
|
<ul>
|
|
<li><a href="#org98c997d">Disclaimer</a></li>
|
|
<li><a href="#org52886b0">What is Org?</a></li>
|
|
<li><a href="#org96925dc">Why Org Mode?</a></li>
|
|
<li><a href="#org58ca260">Render Org to blog or whatever</a>
|
|
<ul>
|
|
<li><a href="#org7a403ab">Render HTML</a></li>
|
|
<li><a href="#org3f73524">Static files</a></li>
|
|
<li><a href="#orge802c30">Whole build script</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#org721f45d">Publish through GitHub Action</a>
|
|
<ul>
|
|
<li><a href="#org3e853cb">Install Emacs</a></li>
|
|
<li><a href="#org9e433e6">Just bring everything</a></li>
|
|
<li><a href="#org6b1febc">BTW I use GNU Emacs</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#orgffb06c3">What is next</a>
|
|
<ul>
|
|
<li><a href="#orgfe230fa">RSS Feed</a></li>
|
|
<li><a href="#org75be7c5">Open Graph image preview</a></li>
|
|
<li><a href="#org8e08eeb">Code highlighting</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-org98c997d" class="outline-2">
|
|
<h2 id="org98c997d">Disclaimer</h2>
|
|
<div class="outline-text-2" id="text-org98c997d">
|
|
<p>
|
|
I'm neither proficient in Org Mode (further on "Org"), nor a good front-end engineer. I think that a simple solution is better than no solution. If you see a mistake, you can contact me via <a href="mailto:iam@fidonode.me">iam@fidonode.me</a>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-org52886b0" class="outline-2">
|
|
<h2 id="org52886b0">What is Org?</h2>
|
|
<div class="outline-text-2" id="text-org52886b0">
|
|
<blockquote>
|
|
<p>
|
|
Your life in plain text
|
|
</p>
|
|
|
|
<p>
|
|
A GNU Emacs major mode for keeping notes, authoring documents, computational notebooks, literate programming, maintaining to-do lists, planning projects, and more — in a fast and effective plain text system.
|
|
</p>
|
|
</blockquote>
|
|
<p>
|
|
Everything you can do in Org is to write a text. With a special markup, of course. This makes it versatile and extensible.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="outline-container-org96925dc" class="outline-2">
|
|
<h2 id="org96925dc">Why Org Mode?</h2>
|
|
<div class="outline-text-2" id="text-org96925dc">
|
|
<ol class="org-ol">
|
|
<li>Plain text.
|
|
Plain text as a data source offers significant versatility. You can read and understand what happens in org files without needing Emacs.</li>
|
|
<li>Everything tool.
|
|
Org Mode is a planner with an agenda, a to-do list, a note-taking app, a Jupyter Notebook-like tool, and a zettelkasten tool. You can manage almost every aspect of your life with Org Mode.</li>
|
|
<li>The only way to eat an elephant is piece by piece.
|
|
I do not have a habit of collecting and keeping information. I believe that discovering other aspects of Org Mode will lead me to better note-taking practices.</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="outline-container-org58ca260" class="outline-2">
|
|
<h2 id="org58ca260">Render Org to blog or whatever</h2>
|
|
<div class="outline-text-2" id="text-org58ca260">
|
|
<p>
|
|
Org already has a way to render files into HTML, allowing you to create simple HTML files with minimal styling. I'm not interesting in styling from org, so I decide to use <a href="https://picocss.com">picocss</a> framework.
|
|
</p>
|
|
</div>
|
|
<div id="outline-container-org7a403ab" class="outline-3">
|
|
<h3 id="org7a403ab">Render HTML</h3>
|
|
<div class="outline-text-3" id="text-org7a403ab">
|
|
<p>
|
|
I want to change some templates here and there. I've found <code>esxml</code> package. It is a decent DSL for writing XML/HTML.
|
|
Here is how page header and footer look in this DSL.
|
|
</p>
|
|
<pre><code class="language-lisp">(defun my/header (info)
|
|
`(header (@ (class "header"))
|
|
(nav
|
|
(ul
|
|
(li
|
|
(strong
|
|
,(org-export-data (plist-get info :title) info))))
|
|
(ul
|
|
(li (a (@ (href "/index.html")) "About"))
|
|
(li (a (@ (href "/blog.html")) "Blog"))
|
|
(li (a (@ (href "/rss.xml")) "RSS"))
|
|
)
|
|
))
|
|
)
|
|
|
|
(defun my/footer (info)
|
|
`(footer (@ (class "footer"))
|
|
(hr)
|
|
(p "Alex Mikhailov")
|
|
(p "Built with: "
|
|
(a (@ (href "https://www.gnu.org/software/emacs/")) "GNU Emacs") " "
|
|
(a (@ (href "https://orgmode.org/")) "Org Mode") " "
|
|
(a (@ (href "https://picocss.com/")) "picocss")
|
|
)
|
|
))
|
|
|
|
</code></pre>
|
|
<p>
|
|
Looks neat for me. At least I don't need to mess with string concatenation.
|
|
Whole template wiring looks like that. Not much, but it works and easy to maintain.
|
|
</p>
|
|
<pre><code class="language-lisp">(defun my/template (contents info)
|
|
(concat
|
|
"&lt;!DOCTYPE html&gt;"
|
|
(sxml-to-xml
|
|
`(html (@ (lang "en"))
|
|
(head
|
|
(meta (@ (charset "utf-8")))
|
|
(meta (@ (author "Alex Mikhailov")))
|
|
(meta (@ (name "viewport")
|
|
(content "width=device-width, initial-scale=1, shrink-to-fit=no")))
|
|
(meta (@ (name "color-scheme") (content "light dark")))
|
|
(meta (@ (http-equiv "content-language") (content "en-us")))
|
|
(meta (@ (name "description") (content "Personal page with a blog about my technical adventures")))
|
|
(link (@ (rel "icon") (type "image/x-icon") (href "/resources/favicon.ico")))
|
|
(link (@ (rel "stylesheet") (href "/resources/css/pico.sand.min.css")))
|
|
|
|
(script (@ (defer "true") (src "https://umami.dokutsu.xyz/script.js") (data-website-id "d52d9af1-0c7d-4531-84c6-0b9c2850011f")) ())
|
|
(title ,(org-export-data (plist-get info :title) info)))
|
|
|
|
(body
|
|
(main (@ (class "container"))
|
|
,(my/header info)
|
|
(*RAW-STRING* ,contents)
|
|
,(my/footer info)
|
|
)
|
|
))
|
|
))
|
|
)
|
|
</code></pre>
|
|
<p>
|
|
Ok, now we need some additional steps to wire these templating function.
|
|
</p>
|
|
|
|
<pre><code class="language-lisp">;; Derive new backend with our custom tepmplating function
|
|
;; We derive it from regular HTML backend
|
|
|
|
(org-export-define-derived-backend 'my-html 'html
|
|
:translate-alist '((template . my/template)
|
|
))
|
|
|
|
;; Define publish function which uses our freshly derived backend
|
|
(defun my/publish-to-html (plist filename pub-dir)
|
|
"Publish an Org file to HTML using the custom backend."
|
|
(org-publish-org-to 'my-html filename ".html" plist pub-dir))
|
|
</code></pre>
|
|
|
|
<p>
|
|
So everything is almost done. Time to use our custom publishing function in projects list.
|
|
</p>
|
|
|
|
<pre><code class="language-lisp">(setq org-publish-project-alist
|
|
(list
|
|
(list "blog"
|
|
:recursive t
|
|
:base-directory my/blog-src-path
|
|
:publishing-directory my/web-export-path
|
|
:publishing-function 'my/publish-to-html
|
|
:html-html5-fancy t
|
|
:htmlized-source t
|
|
:with-author nil
|
|
:with-creator t
|
|
:with-toc t
|
|
:section-numbers nil
|
|
:time-stamp-file nil
|
|
)
|
|
))
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-org3f73524" class="outline-3">
|
|
<h3 id="org3f73524">Static files</h3>
|
|
<div class="outline-text-3" id="text-org3f73524">
|
|
<p>
|
|
Yep, you may want to publish some photos with your blog or any other static files.
|
|
</p>
|
|
<pre><code class="language-nil">(setq org-publish-project-alist
|
|
(list
|
|
(list "static"
|
|
:base-directory my/blog-src-path
|
|
:base-extension "css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|pdf\\|ico\\|txt"
|
|
:publishing-directory my/web-export-path
|
|
:recursive t
|
|
:publishing-function 'org-publish-attachment
|
|
)
|
|
))
|
|
</code></pre>
|
|
<p>
|
|
Looks self explanatory.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-orge802c30" class="outline-3">
|
|
<h3 id="orge802c30">Whole build script</h3>
|
|
<div class="outline-text-3" id="text-orge802c30">
|
|
<p>
|
|
Here is the whole elisp script which I use to publish my blog. It have some additional quirks to work with <code class="src src-sh">doomscript ./build-site.el</code>.
|
|
</p>
|
|
<pre><code class="language-lisp">;; Load the publishing system
|
|
;; Configure environment
|
|
;;
|
|
(setq debug-on-error t)
|
|
|
|
(let ((default-directory (concat "~/.config/emacs/.local/straight/build-" emacs-version "/")))
|
|
(normal-top-level-add-subdirs-to-load-path))
|
|
|
|
(add-to-list 'custom-theme-load-path
|
|
(concat "~/.config/emacs/.local/straight/build-" emacs-version "/doom-themes"))
|
|
(add-to-list 'custom-theme-load-path (concat "~/.config/emacs/.local/straight/build-" emacs-version "/base16-theme"))
|
|
(add-to-list 'custom-theme-load-path (concat "~/.config/emacs/.local/straight/build-" emacs-version "/moe-theme"))
|
|
|
|
|
|
(require 'xml)
|
|
(require 'dom)
|
|
(require 'ox-publish)
|
|
(require 'ox-rss)
|
|
(require 'org)
|
|
(require 'esxml)
|
|
|
|
;;
|
|
;;Variables
|
|
;;
|
|
(setq
|
|
my/url "https://fidonode.me"
|
|
my/web-export-path "./public"
|
|
my/blog-src-path "./home/05 Blog"
|
|
org-html-validation-link nil ;; Don't show validation link
|
|
org-html-htmlize-output-type 'inline-css
|
|
org-src-fontify-natively t)
|
|
|
|
;;
|
|
;;Templates
|
|
;;
|
|
(defun my/footer (info)
|
|
`(footer (@ (class "footer"))
|
|
(hr)
|
|
(p "Alex Mikhailov")
|
|
(p "Built with: "
|
|
(a (@ (href "https://www.gnu.org/software/emacs/")) "GNU Emacs") " "
|
|
(a (@ (href "https://orgmode.org/")) "Org Mode") " "
|
|
(a (@ (href "https://picocss.com/")) "picocss")
|
|
)
|
|
))
|
|
|
|
(defun my/header (info)
|
|
`(header (@ (class "header"))
|
|
(nav
|
|
(ul
|
|
(li
|
|
(strong
|
|
,(org-export-data (plist-get info :title) info))))
|
|
(ul
|
|
(li (a (@ (href "/index.html")) "About"))
|
|
(li (a (@ (href "/blog.html")) "Blog"))
|
|
(li (a (@ (href "/rss.xml")) "RSS"))
|
|
)
|
|
))
|
|
)
|
|
|
|
(defun my/template (contents info)
|
|
(concat
|
|
"&lt;!DOCTYPE html&gt;"
|
|
(sxml-to-xml
|
|
`(html (@ (lang "en"))
|
|
(head
|
|
(meta (@ (charset "utf-8")))
|
|
(meta (@ (author "Alex Mikhailov")))
|
|
(meta (@ (name "viewport")
|
|
(content "width=device-width, initial-scale=1, shrink-to-fit=no")))
|
|
(meta (@ (name "color-scheme") (content "light dark")))
|
|
(meta (@ (http-equiv "content-language") (content "en-us")))
|
|
(meta (@ (name "description") (content "Personal page with a blog about my technical adventures")))
|
|
(link (@ (rel "icon") (type "image/x-icon") (href "/resources/favicon.ico")))
|
|
(link (@ (rel "stylesheet") (href "/resources/css/pico.sand.min.css")))
|
|
|
|
(script (@ (defer "true") (src "https://umami.dokutsu.xyz/script.js") (data-website-id "d52d9af1-0c7d-4531-84c6-0b9c2850011f")) ())
|
|
(title ,(org-export-data (plist-get info :title) info)))
|
|
|
|
(body
|
|
(main (@ (class "container"))
|
|
,(my/header info)
|
|
(*RAW-STRING* ,contents)
|
|
,(my/footer info)
|
|
)
|
|
))
|
|
))
|
|
)
|
|
|
|
|
|
(org-export-define-derived-backend 'my-html 'html
|
|
:translate-alist '((template . my/template)
|
|
))
|
|
|
|
(defun my/publish-to-html (plist filename pub-dir)
|
|
"Publish an Org file to HTML using the custom backend."
|
|
(org-publish-org-to 'my-html filename ".html" plist pub-dir))
|
|
|
|
;;
|
|
;;Helpers
|
|
;;
|
|
(defun my/format-date-subtitle (file project)
|
|
"Format the date found in FILE of PROJECT."
|
|
(format-time-string "posted on %Y-%m-%d" (org-publish-find-date file project)))
|
|
|
|
|
|
|
|
;;
|
|
;;Clear folder with results
|
|
;;
|
|
(when (file-directory-p my/web-export-path)
|
|
(delete-directory my/web-export-path t))
|
|
(mkdir my/web-export-path)
|
|
|
|
|
|
;;
|
|
;;Main blog configuration
|
|
;;
|
|
(setq org-publish-project-alist
|
|
(list
|
|
(list "static"
|
|
:base-directory my/blog-src-path
|
|
:base-extension "css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|pdf\\|ico\\|txt"
|
|
:publishing-directory my/web-export-path
|
|
:recursive t
|
|
:publishing-function 'org-publish-attachment
|
|
)
|
|
(list "blog"
|
|
:recursive t
|
|
:base-directory my/blog-src-path
|
|
:publishing-directory my/web-export-path
|
|
:publishing-function 'my/publish-to-html
|
|
:html-html5-fancy t
|
|
:htmlized-source t
|
|
:with-author nil
|
|
:with-creator t
|
|
:with-toc t
|
|
:section-numbers nil
|
|
:time-stamp-file nil
|
|
)
|
|
))
|
|
|
|
|
|
;; Generate the site output
|
|
(org-publish-all t)
|
|
|
|
(message "Build complete!")
|
|
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="outline-container-org721f45d" class="outline-2">
|
|
<h2 id="org721f45d">Publish through GitHub Action</h2>
|
|
<div class="outline-text-2" id="text-org721f45d">
|
|
<p>
|
|
With all previous preparations, this step sounds simple like: <code class="src src-sh">emacs -Q --script ./build-site.el</code>
|
|
I've chosen a pretty standard way to publish static sites through GitHub Pages. Since I keep my Org files in a private repo, I need some additional steps to address it. I use the <code>peaceiris/actions-gh-pages@v3</code> action to publish from my Org repo to the Pages repo.
|
|
However, since I use <code>Doom Emacs</code> as my configuration framework, we need to address some more problems.
|
|
</p>
|
|
</div>
|
|
|
|
<div id="outline-container-org3e853cb" class="outline-3">
|
|
<h3 id="org3e853cb">Install Emacs</h3>
|
|
<div class="outline-text-3" id="text-org3e853cb">
|
|
<p>
|
|
If you want to run <code>Emacs Lisp</code>, you need the whole Emacs, at least without GUI. In a GitHub Action, you can simply run:
|
|
</p>
|
|
<pre><code class="language-sh">sudo apt install emacs-nox --yes
|
|
</code></pre>
|
|
<p>
|
|
This way has a downside - you will install Emacs on each action run since the system state is disposable.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-org9e433e6" class="outline-3">
|
|
<h3 id="org9e433e6">Just bring everything</h3>
|
|
<div class="outline-text-3" id="text-org9e433e6">
|
|
<p>
|
|
I need to take extra steps since I use Doom Emacs and have my configs in Org. You may also need to install dependencies for your configuration.
|
|
</p>
|
|
|
|
<p>
|
|
Fetch doom guts
|
|
</p>
|
|
<pre><code class="language-sh">git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs
|
|
</code></pre>
|
|
|
|
<p>
|
|
Prepare minimal config for rendering Org file to config.
|
|
</p>
|
|
<pre><code class="language-sh">echo '(doom! :config literate)' &gt; ~/.config/doom/init.el
|
|
echo '(setq +literate-config-file "'$(pwd)'/config/config.org")' &gt; ~/.config/doom/cli.el
|
|
</code></pre>
|
|
|
|
<p>
|
|
Finally, install all dependencies according to my config. Yes, it is overhead, but I can be sure that I have the same dependencies as on my dev machine.
|
|
</p>
|
|
<pre><code class="language-sh">~/.config/emacs/bin/doom sync -B
|
|
</code></pre>
|
|
<p>
|
|
Of course, I use a caching step to make the whole process faster:
|
|
</p>
|
|
<pre><code class="language-yaml">- name: Cache doom-emacs
|
|
uses: actions/cache@v4
|
|
id: cache-doom-save
|
|
with:
|
|
path: ~/.config/emacs
|
|
key: ${{ runner.os }}-doom
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="outline-container-org6b1febc" class="outline-3">
|
|
<h3 id="org6b1febc">BTW I use GNU Emacs</h3>
|
|
<div class="outline-text-3" id="text-org6b1febc">
|
|
<p>
|
|
Here's the whole publishing workflow.
|
|
</p>
|
|
<pre><code class="language-yaml">name: pages
|
|
on:
|
|
push:
|
|
branches:
|
|
- "main"
|
|
# Do not trigger build on changes in other folders
|
|
paths-ignore:
|
|
- "./home/02 Action"
|
|
- "./home/03 PKM"
|
|
- "./home/04 Log"
|
|
- "./home/06 Projects"
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out
|
|
uses: actions/checkout@v1
|
|
|
|
#Install emacs without GUI components
|
|
- name: Install Emacs
|
|
run: sudo apt install emacs-nox --yes
|
|
|
|
#Clone doomemacs. Yep, always the most fresh master. Let it fire.
|
|
- name: Install doom
|
|
run: git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs
|
|
|
|
# Use cached files to shave some time
|
|
- name: Restore cached doom-emacs
|
|
id: cache-doom-restore
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: ~/.config/emacs
|
|
key: ${{ runner.os }}-doom
|
|
|
|
- name: Create folder
|
|
run: mkdir -p ~/.config/doom/
|
|
|
|
# I use literate config, so we need some extra steps to botstrap my config
|
|
- name: Put template for literate config
|
|
run: echo '(doom! :config literate)' &gt; ~/.config/doom/init.el
|
|
|
|
# Yep. I also keep my emacs config in org in my org repo
|
|
- name: Propagate org conf
|
|
run: echo '(setq +literate-config-file "'$(pwd)'/config/config.org")' &gt; ~/.config/doom/cli.el
|
|
|
|
# Build doomemacs deps. Should be relativelly fast, cause almost everything cached.
|
|
- name: Sync doom
|
|
run: ~/.config/emacs/bin/doom sync -B
|
|
|
|
#Put files into cache
|
|
- name: Cache doom-emacs
|
|
uses: actions/cache@v4
|
|
id: cache-doom-save
|
|
with:
|
|
path: ~/.config/emacs
|
|
key: ${{ runner.os }}-doom
|
|
|
|
- name: Build the site
|
|
run: ~/.config/emacs/bin/doomscript ./build-site.el
|
|
|
|
# Deploy from this repo to that ~external_repository~
|
|
- name: Deploy
|
|
uses: peaceiris/actions-gh-pages@v3
|
|
with:
|
|
deploy_key: ${{ secrets.PRIVATE_KEY }}
|
|
external_repository: fido-node/fido-node.github.io
|
|
publish_branch: gh-pages
|
|
publish_dir: ./public
|
|
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-orgffb06c3" class="outline-2">
|
|
<h2 id="orgffb06c3">What is next</h2>
|
|
<div class="outline-text-2" id="text-orgffb06c3">
|
|
<p>
|
|
I have a plans to make posts about next features:
|
|
</p>
|
|
</div>
|
|
<div id="outline-container-orgfe230fa" class="outline-3">
|
|
<h3 id="orgfe230fa"><a href="./add_rss_to_blog.html">RSS Feed</a></h3>
|
|
</div>
|
|
<div id="outline-container-org75be7c5" class="outline-3">
|
|
<h3 id="org75be7c5"><a href="./posts_preview.html">Open Graph image preview</a></h3>
|
|
</div>
|
|
<div id="outline-container-org8e08eeb" class="outline-3">
|
|
<h3 id="org8e08eeb"><a href="./improve_code_blocks.html">Code highlighting</a></h3>
|
|
</div>
|
|
</div>
|
|
</main><footer class="footer"><div class="container"><hr/><small><p>Alex Mikhailov</p><p>Built with: <a href="https://www.gnu.org/software/emacs/">GNU Emacs</a> <a href="https://orgmode.org/">Org Mode</a> <a href="https://picocss.com/">picocss</a></p></small></div></footer></body></html>
|