about summary refs log tree commit diff homepage
diff options
context:
space:
mode:
-rw-r--r--_css/style.css14
-rw-r--r--blog/dedep.md2
-rw-r--r--blog/pixml.md550
3 files changed, 559 insertions, 7 deletions
diff --git a/_css/style.css b/_css/style.css
index 354e65e..421b75f 100644
--- a/_css/style.css
+++ b/_css/style.css
@@ -92,10 +92,12 @@ p, details {
 .note p:first-child { font-weight: bold }
 .franklin-content blockquote {
     background: var(--overlay-bg);
-    color: ButtonText;
     border-left: 0.25rem solid var(--overlay-border);
 }
-.note { background-color: var(--overlay-bg) }
+.note {
+    background-color: #2482;
+    border-left: 0.25rem solid #2484;
+}
 
 /* Header */
 header {
@@ -156,7 +158,7 @@ tr, th, td { padding: 0.5em }
 /* highlight.js */
 code, .hljs {
     background-color: ButtonFace;
-    color: CanvasText;
+    color: ButtonText;
     font-size: inherit;
     padding: 0.1em 0.2em;
 }
@@ -167,11 +169,11 @@ code, .hljs {
     overflow-x: auto;
 }
 
-.hljs-built_in .hljs-name, .hljs-title, .hljs-type { color: Teal }
+.hljs-built_in .hljs-name, .hljs-title, .hljs-type { color: DarkCyan }
 .hljs-code, .hljs-selector-class { color: LinkText }
 .hljs-comment, .hljs-deletion, .hljs-meta { color: GrayText }
 .hljs-comment, .hljs-emphasis { font-style: italic; }
-.hljs-string { color: Green }
+.hljs-string { color: ForestGreen }
 .hljs-strong { font-weight: bold; }
 
 .hljs-bullet, .hljs-quote, .hljs-link, .hljs-number,
@@ -182,7 +184,7 @@ code, .hljs {
 
 .hljs-subst, .hljs-symbol, .hljs-selector-id, .hljs-selector-attr,
 .hljs-selector-pseudo, .hljs-template-tag, .hljs-template-variable,
-.hljs-addition { color: Olive }
+.hljs-addition { color: Chocolate }
 
 .comment {
     background-color: var(--overlay-bg);
diff --git a/blog/dedep.md b/blog/dedep.md
index a77dee7..c251880 100644
--- a/blog/dedep.md
+++ b/blog/dedep.md
@@ -132,7 +132,7 @@ join me in a De-Dependency December and fight for the users!
 [Fead]: https://trong.loang.net/~cnx/fead
 [SSG]: https://en.wikipedia.org/wiki/Static_site_generator
 [power]: https://www.youtube.com/watch?v=3Mpyias9ek4
-[context]: https://media.handmade-seattle.com/context-is-everything
+[context]: https://guide.handmade-seattle.com/c/2021/context-is-everything
 [standards]: https://xkcd.com/927
 [utilities libraries]: https://raku-advent.blog/2021/12/11/unix_philosophy_without_leftpad_part2
 [old]: https://wiki.debian.org/DontBreakDebian#Don.27t_suffer_from_Shiny_New_Stuff_Syndrome
diff --git a/blog/pixml.md b/blog/pixml.md
new file mode 100644
index 0000000..79b8129
--- /dev/null
+++ b/blog/pixml.md
@@ -0,0 +1,550 @@
++++
+rss = "Comments for Static Sites without JavaScript via Emails"
+date = Date(2023, 3, 17)
+tags = ["fun", "recipe"]
++++
+
+# XML and Photo Gallery Generation: A Love Story
+
+> I'm just a language, whose style sheets are good\
+> Oh, Lord, please, don't let me be misunderstood
+
+!!! note "Tips"
+
+    As usual, the article starts with a text wall of random rambling.
+    If you are only interested in the technical aspects, feel free to skip
+    the first two sections.
+
+\toc
+
+## Introduction
+
+Neural-optic live streaming probably, no, definitely offers
+the most photorealistic graphics one can set eyes on.  [CGI] is just
+a pathetic mimic, and photography or videography is no more
+than a poor plagiarism attempt when compared to quantum ray-tracing
+and other advanced physics simulations^W happenings.
+
+On the other hand, we humen are rather shite at replaying visual memories,
+whilst ([bit rot] aside) media can be archived [for forever].  Besides,
+many of us are too busy to *touch grass* or go see cool things
+as regularly as we wish to.  This is how an industry based on showing us
+[mundane stuff] or [obvious bullcrap] can still manage to make tens
+of thousands of [craploads] each year any why the interwebs are flooded
+with pictures of cats, kitties and pussies.
+
+Finding new shits means dopamine dispensation and that's why
+[they are dope][new is always better].  As a model netizen, I adhere
+to the web's social contract of mutual [shitposting] so that everyone
+can have a piece.  Every blue moon, I also enjoy posting more quality
+stuff like what you are reading right now, should you ignore the number
+of [Mozart] references in the last three paragraphs.
+
+## Motivation
+
+Some other times, I also want to share the living things and sceneries
+I encounter in the [new][move] place.  My camera was gifted by father
+before I moved and yet I shared more photos [with strangers][pixelfed]
+than with my family.  The PixelFed instance I landed on irreversibly
+shrank and lossily compressed them, while dumping 5 MB images to the family
+chat room just feels weird, hence I decided to gather the decency
+to build a photo gallery to show my loved ones (and admittedly,
+flex with online strangers).
+
+There are not many [CMS] in the wild for photo hosting, which are
+often either acts as a wall garden and/or a social network.
+Building and hosting a new one is quite overkill, thus the obvious
+solution left would be generating a static site.  Out of the gazillion [SSG],
+I couldn't found any that meets the my requirements:
+
+1. Generate a [web feed]
+2. Automate filling [image] title and alt text
+3. Offer fine-grain control for permanent [pagination]
+4. Generate thumbnails with custom size and name
+
+I mean, they perhaps exist, but the number I had to try and fight through
+would cost more time than writing the web pages and feed by hand.
+So I wrote them from scratch.  Y'all can stand up and clap now!
+
+## Preliminary
+
+Yes, I really started with writing [XHTML] and [Atom] by hand.
+A web page has the following structure with namespaces omitted
+and denoted in WXML ([Wisp]$\times$[SXML]) so I don't have
+to close the tags (have I given up on XML too early?-).
+
+!!! note "Syntax hints"
+
+    For the uninitiated, any indentation or colon in Wisp represents
+    an additional nest level, while a dot escape the nesting.  The at signs
+    are used by SXML to denote attributes, which may remind you of [XPath].
+    For example, the anchor to the previous page is `<a href=41>PREV</a>`.
+
+```
+html
+  head
+    link
+      @ : rel "alternate"
+          type "application/atom+xml"
+          href "/atom.xml"
+    ...
+  body
+    nav
+      a : @ : href "41"
+        . "PREV"
+      h1 "PAGE 42"
+      a : @ : href "43"
+        . "NEXT"
+    article
+      @ : id "foobar"
+      h2
+        a : @ : href "#foobar"
+          . "foobar"
+      a : @ : href "/42/foo.jpg"
+          img
+            @ : src "/42/foo.small.jpg"
+                alt "pic of foo"
+                title "pic of foo"
+      a : @ : href "/42/bar.jpg"
+          ...
+    article ...
+    ...
+    footer ...
+```
+
+So far, adding an `article` is not yet too cumbersome, there's only a bit
+of redundancy for permanent links and the nesting level is acceptable
+with the deepest being `/html/body/article/a/img`.  It gets more repetitive
+once we publish it to to the linked Atom feed:
+
+```
+feed
+  entry
+    link
+      @ : rel "alternate"
+          type "application/xhtml+xml"
+          href "https://gallery.example/42/#foobar"
+    id "https://gallery.example/42/#foobar"
+    title "foobar"
+    content
+      @ : type "xhtml"
+      div
+        img
+          @ : src "https://gallery.example/42/foo.jpg"
+              alt "pic of foo"
+              title "pic of foo"
+        img ...
+    updated ...
+  entry ...
+  ...
+```
+
+Since web feeds are standalone documents, they must always use absolute URLs.
+(Welp that's not entirely true, [XML Base] does exists, but not all readers
+support it, and more importantly, certain elements such as `atom:id` disallow
+relative references.)  In addition, whilst the web page links a thumbnail
+to the original image to save bandwidths, the feed can be consumed one post
+at a time, which thus points to the full size version.  Therefore,
+copying the markup to embed it inside the Atom is error-prone and doesn't
+exactly spark joy.
+
+!!! note "Fun fact"
+
+    What does spark joy is that we can embed XHTML directly into the web feed,
+    which means the content is still XML and we don't need to quote it in CDATA.
+    For other sites where contents don't accumulate up to hundreds of megabytes,
+    this will allow us to slap some (SPOILER ALERT!) stylesheet on the Atom feed
+    and let the user agent render it in a [human-readable form][XSL].
+
+## Approach
+
+I actually already spoiled it in the epigraph,[^spoiler] but for the sake
+of completeness let us [discuss a few possible solutions][efficiency].
+What I wanted was to reduce the redundancy of manual input, in other words,
+a system transforming a custom information-dense format to standard
+yet sparser ones, which in this case are XHTML and Atom.  Given some new photos
+and their relevant data, the purpose was to minimize the publishing friction.
+
+It's worth mentioning that the goal was not to minimize the input format,
+the transformation speed, or feedback latency, but all of the above,
+plus the cost of constructing the tool, incrementally as our requirements
+slightly changes over time.  Our choice for the base [programming system]
+shall affect each and every of these aspects and more.
+
+Some technical dimensions are more equal than others, though.
+For this use case, IMHO immediate feedback loop should be given
+the number one priority, not only because it'd be frustrating
+to have to complete multiple rituals just to preview the changes,
+but also as watching and reflecting file system changes is (sadly still)
+a difficult problem.
+
+For Linux[^interjection] there's [inotify] which doesn't suck,
+except when it does and misses events, and the standard POSIX build tool
+[make] relies on [mtime which is also flaky][mtime].  Some SSG
+work around this by spawning up a server with more sophisticated
+caching mechanism and even include a HTTP server sending out refresh events.
+Implementing such system is easily [more expensive][automation] than doing
+the original task manually.
+
+Luckily, there is another way.  *After* the birth of imperative
+DOM manipulation programs running on VM inside browsers (Ecma scripts),
+there came a (now forgotten) art of purely functional DOM transformation.
+More specifically, [XSLT] can declaratively transform any XML document
+to another, and its best part is that modern browser native support it,
+i.e. there's no difference between editing the input document
+and the hypothetical output XHTML.  For better portability
+and rendering performance, we can still generate the latter
+ahead-of-time (AoT) during deployment.
+
+## Implementation
+
+Going back to the example, the input format could boil down
+to a more concise XML file, e.g. `42/index.xml`:
+
+```
+page
+  @ : prev "41"
+      curr "42"
+      next "43"
+  post : @ : title "foobar"
+         picture
+           @ : filename "foo"
+               desc "pic of foo"
+         picture ...
+         ...
+         time ...
+  post ...
+  ...
+```
+
+### Page Generation
+
+The stylesheet should then be declared at the beginning of the file,
+so that the user agent can automatically fetch and apply it
+to render the output XHML:
+
+```
+<?xml-stylesheet href="/page.xslt" type="text/xsl"?>
+```
+
+XSLT is essentially a templating language, similar to PHP (which is also older)
+and template libraries in your favorite languages.  For the ease of reading,
+I will let the target document's namespace be the default, while aliasing
+the transformation one as `xsl`.  The stylesheet for the web pages would
+look something like the following, which should be self-explanatory.
+
+```
+xsl:stylesheet
+  xsl:template : @ : match "/page"
+    xsl:variable : @ : name "base"
+      xsl:text "/"
+      xsl:value-of : @ : select "@curr"
+      xsl:text "/"
+    html
+      head ...
+      body
+        nav
+          xsl:if : @ : test "@prev != ''"
+            a : @ : href "/{@prev}/"
+              . "PREV"
+          h1 : xsl:text "PAGE "
+               xsl:value-of : @ : select "@curr"
+          xsl:if : @ : test "@next != ''"
+            ...
+        xsl:for-each : @ : select "post"
+          xsl:variable : @ : name "id"
+            xsl:value-of
+              @ : select "translate(@title, ' ', '-')"
+          article
+            @ : id "{$id}"
+            h2
+              a : @ : href "#{$id}"
+                  xsl:value-of : @ : select "@title"
+            xsl:for-each : @ : select "picture"
+              a : @ : href "{$base}{@filename}.jpg"
+                  img
+                    @ : src "{$base}{@filename}.small.jpg"
+                        alt "{@desc}"
+                        title "{@desc}"
+        footer ...
+```
+
+### Feed Generation
+
+Similarly, for Atom entries on a single page,
+
+```
+xsl:stylesheet
+  xsl:variable : @ : name "root"
+    . "https://gallery.example/"
+  xsl:template : @ : match "/page"
+    xsl:variable : @ : name "base"
+      xsl:value-of : @ : select "$root"
+      xsl:value-of : @ : select "@curr"
+      xsl:text "/"
+    xsl:for-each : @ : select "post"
+      xsl:variable : @ : name "url"
+        xsl:value-of : @ : select "$base"
+        xsl:text "#"
+        xsl:value-of
+          @ : select "translate(@title, ' ', '-')"
+      entry
+        link
+          @ : rel "alternate"
+              type "application/xhtml+xml"
+              href "{$url}"
+        id : xsl:value-of : @ : select "$id"
+        title : xsl:value-of : @ : select "@title"
+        content
+          @ : type "xhtml"
+          div
+            xsl:for-each : @ : select "picture"
+              img
+                @ : src "{$base}{@filename}.jpg"
+                    alt "{@desc}"
+                    title "{@desc}"
+        updated : xsl:value-of : @ : select "time"
+```
+
+The trickier part here is concatenating the entries together.
+Simple enough, instead of linking to the stylesheet in the data,
+we can read XML files directly from XSLT.
+
+```
+xsl:template
+  @ : match "/"
+  ...
+  xsl:apply-templates
+    @ : select "document('42/index.xml')/page"
+  xsl:apply-templates ...
+  ...
+```
+
+This allows us to do other cool things, such as embedding SVG in XHTML
+to make use of the parent element's [currentcolor], while keeping
+the source files separate.  It is especially useful for monochromatic icons,
+e.g.
+
+```
+xsl:copy-of : @ : select "document('cc.svg')/*"
+xsl:copy-of : @ : select "document('by.svg')/*"
+xsl:copy-of : @ : select "document('sa.svg')/*"
+```
+
+### Thumbnail Generation
+
+So far, we have met three out of the [four requirements](#motivation),
+only thing left is creating the thumbnails.  Inspired by Ethan Dalool,
+I am going for [fairly large ones of 1024 px in width][big thumbs],
+
+> large enough to comfortably browse the photos without clicking through
+> to the big version of each, and the thumbnails are decently light
+> and not too jpeggy at about 125-150 kilobytes on average.
+
+At such size, I can aim for around ten photoes[^toes] per page
+while maintaining a somewhat decent load time.  Plus, since the width
+of images are hardcoded, page [margin] could be automatically inferred
+to never stretch them.
+
+```css
+html {
+    box-sizing: border-box;
+    margin: auto;
+    max-width: calc(1024px + 2ch);
+}
+body { margin: 0 1ch }
+```
+
+To generate the thumbnails, I use [epeg] together with `make` for wildcarding:
+
+```
+PICTURES := $(filter-out %.small.jpg $(PREFIX)/%.jpg, $(wildcard */*.jpg))
+THUMBNAILS := $(patsubst %.jpg,%.small.jpg,$(PICTURES))
+
+%.small.jpg: %.jpg
+	epeg -w 1024 -p -q 80 $< $@
+```
+
+The Makefile also define rules for AoT compilation using [xsltproc]
+for the web pages and feed.  Apparently no feed reader supports XSLT,
+and for pages runtime processing negatively affect the performance
+due to the multiple round trips for the stylesheet and the vector icons.
+
+```
+DATA := $(wildcard */index.xml) index.xml
+PAGES := $(patsubst %.xml,%.xhtml,$(DATA))
+OUTPUTS := $(THUMBNAILS) $(PAGES) atom.xml
+
+all: $(OUTPUTS)
+
+index.xml: $(LATEST)/index.xml
+	ln -fs $< $@
+
+%.xhtml: %.xml page.xslt
+	xsltproc page.xslt $< > $@
+
+atom.xml: atom.xslt $(DATA) $(wildcard *.svg)
+	xsltproc atom.xslt > atom.xml
+```
+
+The [full implementation][src] is deployed to [px.cnx.gdn],
+mirrored to the [OpenNIC] domain [pix.sinyx.indy] reusing
+the former's TLS certificate, because CA/Browser Forum
+disallows support for domains not recognized by ICANN and no
+[CA for OpenNIC] is mature enough.
+
+## Discussion
+
+> *Okay you built your site using XML macros, so what?
+> The syntax is clunky and you hate it so much yourself
+> that not even a single line of code example here is in actual XML.
+> Doesn't seem like a love story to me!*
+
+Like all relationships, it's not that simple.  I've learned to not judge
+a book by its cover and come to the understanding that XML is the (ugly)
+equivalence of [sexp].[^sex]  Unlike afterthoughts such as C preprocessors,
+[Django]-like templates, or even the Wisp-lookalike syntax of [Slim],
+XML stylesheets is in the same data structure.  To put it another way,
+one can use XSLT to generate XSLT from XSLT.  Do I need it in this case
+or ever at all?  Probably not, but that certainly makes XSL a lot more
+attractive in my eyes.
+
+Furthermore, the tooling for XML is highly mature, from editors to linters
+and processors to rendering engines.  It'd be lying to say you ain't
+fascinated that tis possible to directly feed browsers pure data
+instead of markup representations.  More than that, one can have
+entirely static API endpoints that are both human- and machine-readable.
+
+> *XSL is just declarative JS!  You are so blinded
+> by your lust for functional programming that you have
+> become [the very thing you swore to destroy](/blog/reply)!*
+
+My distaste for Ecma scripts is not due to DOM manipulation.
+Sure, I do find in-place modification inelegant for documents,
+but if only that's the only issue.  I block them on most sites
+because they can interact with many things other than just the DOM,
+imposing [privacy] and [security] risks while [fucking up the UX].
+
+Architecturally, Ecma scripts enable the absolute bloody worst possible
+kind of web pages with zero data at all, fetching tiny pieces of content
+in JSON and turn performance [to shit].  The user agents then try to salvage
+efficiency by turning themselves into a distributed system component
+and adding optimizations that shall never be (ab)used for the sake of users.
+O ye [cycle of doom]!
+
+Note that one can make a similar mistake with XSL regarding the number
+of round trips, and XML stylesheets can provide the same front-end/back-end
+separation.  Both can be used to provide hot loading during development
+and AoT rendering in production (if not all, then many JS libraries support
+pre-rendering, ignoring the monstrous [dependency graph](/blog/dedep)).
+At the end of the day, it's not the matter of technology but principle:
+to be the [users' best interest].
+
+> *There is nothing complex about the photo gallery,
+> any existing SSG can do the same with minor tweaks!
+> You never needed to write a new one to begin with!*
+
+I am wondering the same myself, but keep in mind there are details
+I've been hiding from in the example.  I went all-in for the semantic web
+with the hope for best portability and accessibility.  One thing
+I haven't mentioned is the `lang` attribute, e.g. `en`, `vi` or `fr`
+depending on the post.  Adding this to the web pages requires the SSG
+to be somewhat modular, and even harder for the web feed.
+
+Moreover, generic SSG are not designed to handle the difference
+in content between a page's `article` and the feed's corresponding `entry`,
+neither for having multiple posts in a single page.  Pagination is
+also commonly implemented backwards, i.e. page 2 being the second latest one,
+making it impossible to avoid link rot.
+
+Not to suggest that the majority of SSG are poorly designed, just that
+from a certain amount of [context] difference, tis cheaper to just redesign
+from scratch.  This is not about XSL vs Go/Python/JS for SSG or web dev
+in general, but this specific and happen-to-be-far-from-complex case.
+
+## Conclusion
+
+At the time of writing, XML has pretty much been superseded by JSON or YAML,
+for the better or worse.  I have no love for YAML for obvious reasons,
+but it also saddens me to sometimes see JSON being solely used as a container
+for HTML.  I hope that this essay can [awaken something in you] about XML
+and remind you about the semantic web in your next project.  It worked out
+for me, maybe it'll work out for you too!
+
+The story between XML and my photo gallery is a fond love story.
+They were born for each other, there was no drama, everything just werkt.
+Their romance inspire me to better appreciate stability and maturity,
+and value those right in front of my eyes yet I had been *too blind to see*.
+Anyway, this is getting too long, so Imma end it with another [song].
+
+> Lookin' for perfect\
+> Surrounded by artificial\
+> You're the closest thing to real I've seen\
+> Sure, everyone has their problems\
+> That's a given\
+> Yours are the easiest to tolerate
+
+[^spoiler]: If you know, you know.
+[^interjection]: Yup, just the kernel.
+[^toes]: *Thumb*nails, pho*toes*, get it?-)
+[^sex]: Or conventionally in most Lisp 1's, `sex?`.
+
+[CGI]: https://en.wikipedia.org/wiki/Computer-generated_imagery
+[bit rot]: https://en.wikipedia.org/wiki/Data_degradation
+[for forever]: https://xkcd.com/1683
+[mundane stuff]: https://en.wikipedia.org/wiki/Drama
+[obvious bullcrap]: https://en.wikipedia.org/wiki/Fiction
+[craploads]: https://antifandom.com/how-i-met-your-mother/wiki/Crapload
+[new is always better]: https://www.youtube.com/watch?v=1SNRULEnTVQ
+[shitposting]: https://fe.disroot.org/@mcsinyx
+[Mozart]: https://peervideo.club/w/uByA7Czy7PWYMqnu8FgXvW
+
+[move]: https://github.com/zig-community/user-map/pull/120
+[pixelfed]: https://fotofed.nl/cnx
+[CMS]: https://en.wikipedia.org/wiki/Content_management_system
+[SSG]: https://en.wikipedia.org/wiki/Static_site_generator
+[web feed]: https://en.wikipedia.org/wiki/Web_feed
+[image]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img
+[pagination]: https://en.wikipedia.org/wiki/Pagination
+
+[XHTML]: https://en.wikipedia.org/wiki/XHTML
+[Atom]: https://www.rfc-editor.org/rfc/rfc4287
+[Wisp]: https://www.draketo.de/software/wisp
+[SXML]: https://okmij.org/ftp/Scheme/SXML.html
+[XPath]: https://www.w3.org/TR/xpath
+[XML Base]: https://www.w3.org/TR/xmlbase
+[XSL]: https://simonesilvestroni.com/blog/build-a-human-readable-rss-with-jekyll
+
+[efficiency]: https://xkcd.com/1445
+[programming system]: https://programming-journal.org/2023/7/13
+[inotify]: https://man7.org/linux/man-pages/man7/inotify.7.html
+[make]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html
+[mtime]: https://apenwarr.ca/log/20181113
+[automation]: https://xkcd.com/1319
+[XSLT]: https://www.w3.org/standards/xml/transformation
+
+[currentcolor]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword
+[big thumbs]: https://voussoir.net/writing/sharing_photos
+[epeg]: https://github.com/mattes/epeg
+[margin]: https://en.wikipedia.org/wiki/Margin_(typography)
+[xsltproc]: https://gnome.pages.gitlab.gnome.org/libxslt/xsltproc.html
+[src]: https://trong.loang.net/~cnx/px
+[px.cnx.gdn]: https://px.cnx.gdn
+[OpenNIC]: https://www.opennic.org
+[pix.sinyx.indy]: https://pix.sinyx.indy
+[CA for OpenNIC]: https://wiki.opennic.org/opennic/tls
+
+[sexp]: https://en.wikipedia.org/wiki/S-expression
+[Django]: https://docs.djangoproject.com/en/dev/topics/templates
+[Slim]: https://github.com/slim-template/slim
+[privacy]: https://en.wikipedia.org/wiki/Mouse_tracking
+[security]: https://react-etc.net/entry/exploiting-speculative-execution-meltdown-spectre-via-javascript
+[fucking up the UX]: https://meta.stackexchange.com/q/2980/698165
+[to shit]: https://unixsheikh.com/articles/so-called-modern-web-developers-are-the-culprits.html
+[cycle of doom]: https://en.wikipedia.org/wiki/Wirth%27s_law
+[users' best interest]: https://pluralistic.net/2023/01/21/potemkin-ai/#hey-guys
+[link rot]: https://en.wikipedia.org/wiki/Link_rot
+[context]: https://guide.handmade-seattle.com/c/2021/context-is-everything
+
+[awaken something in you]: https://www.youtube.com/watch?v=F3QPWrLFsOA
+[song]: https://www.youtube.com/watch?v=5LvOdWi3Qno