diff --git a/config.yaml b/config.yaml
index 0866ebe..453999e 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,9 +1,9 @@
baseURL: http://yequari.com/
languageCode: en-us
title: yequari.com
-theme: saturn
+theme: dreamcast
params:
- about: "I'm yequari. I write code and occasionally blog posts."
+ about: "I write code and occasionally blog posts."
permalinks:
page:
blog: /blog/:year/:month/:slugorfilename/
@@ -25,23 +25,23 @@ menu:
pageRef: /links
weight: 50
social:
- - name: Mastodon
+ - name: yequari@retro.pizza
params:
rel: external
icon: mastodon-fill.png
class: masto
url: https://retro.pizza/@yequari
weight: 10
- - name: RSS
+ - name: rss feed
params:
icon: rss-fill.png
class: rss
url: /blog/index.xml
- weight: 20
- - name: Email
+ weight: 5
+ - name: yequari[a]32bit[.]cafe
params:
icon: mail-fill.png
rel: external
class: email
- url: mailto:yequari@32bit.cafe
+ url:
weight: 30
diff --git a/layouts/partials/sidebar.html b/layouts/partials/sidebar.html
new file mode 100644
index 0000000..edb182f
--- /dev/null
+++ b/layouts/partials/sidebar.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Social
+
+ {{ range site.Menus.social }}
+
+ {{ if .URL }}
+
+ {{ .Name }}
+
+
+ {{ else }}
+ {{ .Name }}
+ {{ end }}
+
+ {{ end }}
+
+
+
diff --git a/static/images/waves.svg b/static/images/waves.svg
new file mode 100644
index 0000000..79adc1f
--- /dev/null
+++ b/static/images/waves.svg
@@ -0,0 +1,7901 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/js/quotes.js b/static/js/quotes.js
new file mode 100644
index 0000000..7b80913
--- /dev/null
+++ b/static/js/quotes.js
@@ -0,0 +1,19 @@
+var subtitles = [
+ "null",
+ "George likes his chicken spicy",
+ "Graphic design is my passion",
+ "Twitter is a fandom site for current events",
+ "Genesis does what Nintendon\'t",
+ "1000 JS libraries in your pocket",
+ "A large boulder the size of a small boulder",
+ "Smooth jazz for a smooth brain",
+ "Back at it again for the very first time",
+ "I have no spoons and I must cope",
+ "Everyone loves to barbeque, and most people are no exception",
+ "The flavor of the bbq justifies the means"
+];
+var index = Math.floor(Math.random() * subtitles.length);
+console.log("setting subtitle to " + subtitles[index]);
+window.onload = function() {
+ document.getElementById("subtitle").textContent = subtitles[index];
+}
diff --git a/themes/dreamcast/LICENSE b/themes/dreamcast/LICENSE
new file mode 100644
index 0000000..17993f6
--- /dev/null
+++ b/themes/dreamcast/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 YOUR_NAME_HERE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/themes/dreamcast/archetypes/default.md b/themes/dreamcast/archetypes/default.md
new file mode 100644
index 0000000..ac36e06
--- /dev/null
+++ b/themes/dreamcast/archetypes/default.md
@@ -0,0 +1,2 @@
++++
++++
diff --git a/themes/dreamcast/layouts/404.html b/themes/dreamcast/layouts/404.html
new file mode 100644
index 0000000..9aecf45
--- /dev/null
+++ b/themes/dreamcast/layouts/404.html
@@ -0,0 +1,3 @@
+{{ define "main" }}
+
Not found
+{{ end }}
diff --git a/themes/dreamcast/layouts/_default/baseof.html b/themes/dreamcast/layouts/_default/baseof.html
new file mode 100644
index 0000000..54877b9
--- /dev/null
+++ b/themes/dreamcast/layouts/_default/baseof.html
@@ -0,0 +1,25 @@
+
+
+ {{- partial "head.html" . -}}
+
+
+
+ {{- partial "header.html" . -}}
+
+
+ {{- partial "nav.html" . -}}
+
+
+
+
+ {{- block "main" . }}{{- end }}
+
+
+
+ {{- partial "footer.html" . -}}
+
+
+
+
diff --git a/themes/dreamcast/layouts/_default/list.html b/themes/dreamcast/layouts/_default/list.html
new file mode 100644
index 0000000..196ca7f
--- /dev/null
+++ b/themes/dreamcast/layouts/_default/list.html
@@ -0,0 +1,20 @@
+{{ define "main" }}
+
+ {{ .Title }}
+ {{ range .Pages }}
+
+
+ {{ .PublishDate.Format "Mon, Jan 2, 2006" }}
+ {{ partial "tags.html" .}}
+
+ {{ .Summary }}
+
+ {{ if .Truncated }}
+
+ {{ end }}
+
+ {{ end }}
+
+{{ end }}
diff --git a/themes/dreamcast/layouts/_default/single.html b/themes/dreamcast/layouts/_default/single.html
new file mode 100644
index 0000000..28749ec
--- /dev/null
+++ b/themes/dreamcast/layouts/_default/single.html
@@ -0,0 +1,5 @@
+{{ define "main" }}
+
+{{ end }}
diff --git a/themes/dreamcast/layouts/blog/list.html b/themes/dreamcast/layouts/blog/list.html
new file mode 100644
index 0000000..dddce62
--- /dev/null
+++ b/themes/dreamcast/layouts/blog/list.html
@@ -0,0 +1,24 @@
+{{ define "main" }}
+
+ {{ range .Paginator.Pages.ByPublishDate.Reverse }}
+
+
+ {{ .PublishDate.Format "Mon, Jan 2, 2006" }}
+ {{ partial "tags.html" .}}
+
+ {{ .Summary }}
+
+ {{ if .Truncated }}
+
+ {{ end }}
+
+ {{ end }}
+ {{ template "_internal/pagination.html" . }}
+
+
+{{ end }}
diff --git a/themes/dreamcast/layouts/blog/single.html b/themes/dreamcast/layouts/blog/single.html
new file mode 100644
index 0000000..f5738c3
--- /dev/null
+++ b/themes/dreamcast/layouts/blog/single.html
@@ -0,0 +1,16 @@
+{{ define "main" }}
+
+ {{ if .Params.reply_to }}
+ ⬑ Reply to {{ .Params.reply_to.title }} by {{ .Params.reply_to.author }}.
+ {{ end }}
+ yequari
+ {{ .PublishDate.Format "Mon, Jan 02, 2006" }}
+ Permalink
+ {{ partial "tags.html" .}}
+
+
{{ .Title }}
+ {{ .Content }}
+
+
+ {{ partial "webmentions.html" }}
+{{ end }}
diff --git a/themes/dreamcast/layouts/index.html b/themes/dreamcast/layouts/index.html
new file mode 100644
index 0000000..292ca26
--- /dev/null
+++ b/themes/dreamcast/layouts/index.html
@@ -0,0 +1,11 @@
+{{ define "main" }}
+
+ {{ .Content }}
+ Latest Blog Posts
+ {{ range first 3 (where .Site.RegularPages.ByDate.Reverse "Section" "blog") }}
+ {{ .Name }}
+ {{ .Summary }}
+ Read More >
+ {{ end }}
+
+{{ end }}
diff --git a/themes/dreamcast/layouts/notes/single.html b/themes/dreamcast/layouts/notes/single.html
new file mode 100644
index 0000000..7f03dd8
--- /dev/null
+++ b/themes/dreamcast/layouts/notes/single.html
@@ -0,0 +1,14 @@
+{{ define "main" }}
+
+
+ {{ .Title }}
+ yequari
+ {{ .PublishDate.Format "Mon, Jan 02, 2006" }}
+ Permalink
+ {{ partial "tags.html" .}}
+
+ {{ .Content }}
+
+
+
+{{ end }}
diff --git a/themes/dreamcast/layouts/partials/footer.html b/themes/dreamcast/layouts/partials/footer.html
new file mode 100644
index 0000000..97cd14a
--- /dev/null
+++ b/themes/dreamcast/layouts/partials/footer.html
@@ -0,0 +1,3 @@
+
+ peanut butter in the internet vent.
+
diff --git a/themes/dreamcast/layouts/partials/head.html b/themes/dreamcast/layouts/partials/head.html
new file mode 100644
index 0000000..ce29370
--- /dev/null
+++ b/themes/dreamcast/layouts/partials/head.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ {{ .Page.Title }}
+
+
+
+
+
+
+ {{ range .AlternativeOutputFormats -}}
+ {{ printf ` ` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
+{{ end -}}
+
diff --git a/themes/dreamcast/layouts/partials/header.html b/themes/dreamcast/layouts/partials/header.html
new file mode 100644
index 0000000..ba14ad9
--- /dev/null
+++ b/themes/dreamcast/layouts/partials/header.html
@@ -0,0 +1,6 @@
+
diff --git a/themes/dreamcast/layouts/partials/nav.html b/themes/dreamcast/layouts/partials/nav.html
new file mode 100644
index 0000000..e16db99
--- /dev/null
+++ b/themes/dreamcast/layouts/partials/nav.html
@@ -0,0 +1,5 @@
+
+ {{ range site.Menus.main }}
+ {{ .Name }}
+ {{ end }}
+
diff --git a/themes/dreamcast/layouts/partials/sidebar.html b/themes/dreamcast/layouts/partials/sidebar.html
new file mode 100644
index 0000000..e69de29
diff --git a/themes/dreamcast/layouts/partials/tags.html b/themes/dreamcast/layouts/partials/tags.html
new file mode 100644
index 0000000..e193e49
--- /dev/null
+++ b/themes/dreamcast/layouts/partials/tags.html
@@ -0,0 +1,6 @@
+{{ $taxonomy := "tags" }} {{ with .Param $taxonomy }}
+ {{ range $index, $tag := . }} {{ with $.Site.GetPage (printf "/%s/%s"
+ $taxonomy $tag) -}}
+ #{{ $tag | urlize }}
+ {{- end -}} {{- end -}}
+{{ end }}
diff --git a/themes/dreamcast/layouts/partials/webmentions.html b/themes/dreamcast/layouts/partials/webmentions.html
new file mode 100644
index 0000000..ac2e756
--- /dev/null
+++ b/themes/dreamcast/layouts/partials/webmentions.html
@@ -0,0 +1,11 @@
+
+
Webmentions
+
+
Have you written a response to this post? Send me a webmention!
+
+
diff --git a/themes/dreamcast/static/css/main.css b/themes/dreamcast/static/css/main.css
new file mode 100644
index 0000000..2c3547c
--- /dev/null
+++ b/themes/dreamcast/static/css/main.css
@@ -0,0 +1,387 @@
+@import url(https://fonts.bunny.net/css?family=b612-mono:400|phudu:400|noto-sans:400|martian-mono:700);
+/* CSS HEX */
+:root {
+ --jet: #353535ff;
+ --chinese-red: #aa3322ff;
+ --pale-silver: #c4bbafff;
+ --space-cadet: #292640ff;
+ --xiketic: #0f101aff;
+ --blue-ryb: #4056f4ff;
+ --raisin-black: #241e1edd;
+ --dim-gray: #756b6bff;
+ --pewter-blue: #93A8ACff;
+
+ --celestial-blue: #5299D3;
+ --white-smoke: #F1EDEE;
+ --midnight-blue: #18206F;
+ --penn-blue: #17255A;
+ --eerie-black: #172121;
+ --vista-blue: #8EA4D2;
+
+ --dark-purple: #331832;
+ --fairy-tale: #FFC6D9;
+ --english-violet: #694D75;
+ --platinum: #D0DDD7;
+ --non-photo-blue: #8AC6D0;
+
+
+ /* solarized dark colors */
+ --base00: #657b83;
+ --base01: #586e75;
+ --base02: #073642;
+ --base03: #002b36;
+ --base0: #839496;
+ --base1: #93a1a1;
+ --base2: #eee8d5;
+ --base3: #fdf6e3;
+
+ --yellow: #b58900;
+ --orange: #cb4b16;
+ --red: #dc322f;
+ --magenta: #d33682;
+ --violet: #6c71c4;
+ --blue: #268bd2;
+ --cyan: #2aa198;
+ --green: #859900;
+
+ --main-bg: var(--dark-purple);
+ --content-bg: #353535;
+ --primary-text: var(--platinum);
+ --secondary-text: var(--pewter-blue);
+ --primary-link: var(--chinese-red);
+ --primary-link-hover: var(--pewter-blue);
+ --secondary-link: var(--cyan);
+ --secondary-link-hover: var(--pewter-blue);
+ --ternary-link: var(--fairy-tale);
+ --ternary-link-hover: var(--pewter-blue);
+}
+
+body {
+ background: fixed no-repeat url('/images/waves.svg') #00c2f6;
+ /* placeholder blueprint bg */
+ /* background-color:#269; */
+ /* background-image: linear-gradient(white 2px, transparent 2px),
+ linear-gradient(90deg, white 2px, transparent 2px),
+ linear-gradient(rgba(255,255,255,.3) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255,255,255,.3) 1px, transparent 1px);
+ background-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
+ background-position:-2px -2px, -2px -2px, -1px -1px, -1px -1px; */
+ /* end placeholder */
+ color: var(--primary-text);
+ font-size: 16px;
+ font-family: 'Noto Sans', sans-serif;
+ scrollbar-width: none;
+ padding: 0;
+ margin: 0;
+}
+
+body::-webkit-scrollbar {
+ display: none;
+}
+
+h1,h2,h3,h4,h5,h6 {
+ color: var(--secondary-text);
+ font-family: 'Martian Mono', monospace;
+ text-transform: lowercase;
+}
+
+p {
+ line-height: 1.8;
+}
+
+a {
+ transition: color 0.5s;
+}
+
+a:link, a:visited {
+ color: var(--primary-link);
+ text-decoration: none;
+}
+
+a:hover, a:active {
+ color: var(--primary-link-hover);
+}
+
+a.tag {
+ color: var(--secondary-link);
+}
+
+a.tag:hover, a.tag:active {
+ color: var(--secondary-link-hover);
+}
+
+a.permalink {
+ padding: 0 5px;
+}
+
+a.summary {
+ color: var(--primary-text);
+}
+
+header h1 {
+ font-family: 'Phudu', monospace;
+ /* font-size: 1rem; */
+ color: var(--pewter-blue);
+}
+
+ul.social {
+ list-style-type: none;
+ padding: 0 0;
+ margin: 0;
+}
+
+li.social {
+ padding: 0;
+}
+
+header a.sitetitle {
+ text-decoration: none;
+}
+
+header .profile {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: space-between;
+ margin: 10px;
+}
+
+header .profile img {
+ display: inline-block;
+}
+
+header .profile h1 {
+ display: inline;
+}
+
+p.about {
+ padding: 15px;
+}
+
+p.context {
+ font-style: italic;
+}
+
+.wrapper {
+ max-width: 980px;
+ margin: 0 auto;
+}
+
+nav {
+ /* From https://css.glass */
+ background: rgba(255, 255, 255, 0.13);
+ border-radius: 16px;
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
+ backdrop-filter: blur(5px);
+ -webkit-backdrop-filter: blur(5px);
+ border: 1px solid rgba(255, 255, 255, 0.3);
+
+ text-align: center;
+ padding: 0;
+ border-radius: 15px;
+ margin-bottom: 10px;
+}
+
+nav ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+nav li {
+ display: inline;
+}
+
+nav li a {
+ display: inline-block;
+ padding: 20px;
+ font-family: 'B612 Mono', monospace;
+ font-weight: bold;
+ transition: background 0.5s;
+}
+
+nav li a:link, nav li a:visited {
+ color: var(--ternary-link);
+}
+
+nav li a:hover, nav li a:active {
+ background-color: var(--ternary-link-hover);
+}
+
+.main-sidebar {
+ background-color: var(--content-bg);
+ border-radius: 15px;
+ z-index: 1;
+ display: flex;
+ flex-flow: row wrap-reverse;
+ box-shadow: 10px 5px 5px #00000066;
+ padding: 30px;
+}
+
+.sidebar {
+ flex: 0 1 20%;
+ border-right: 1px solid grey;
+ margin-right: 15px;
+ padding-right: 15px;
+}
+
+.sidebar h1 {
+ border-bottom: 1px inset grey;
+ padding-bottom: 5px;
+}
+
+main {
+ flex: 1 3 70%;
+ padding-left: 10px;
+}
+
+main li {
+ padding: 0.2em 0;
+}
+
+
+article p.context {
+ margin-top: -25px;
+ padding-left: 25px;
+}
+
+article p {
+ text-align: justify;
+ font-size: 1.1rem;
+}
+
+article img {
+ display: block;
+ max-width: 400px;
+ margin: 8px auto;
+ text-align: center;
+}
+
+article figcaption {
+ text-align: center;
+ font-size: 14px;
+}
+
+section.posts ul {
+ list-style-type: none;
+}
+
+article time {
+ color: var(--secondary-text);
+}
+
+article a.title {
+ color: var(--primary-link);
+}
+
+article a.title:hover, article a.title:active {
+ color: var(--primary-link-hover);
+}
+
+blockquote {
+ font: 14px/22px normal helvetica, sans-serif;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ margin-left: 50px;
+ padding-left: 15px;
+ border-left: 3px solid #ccc;
+}
+
+code {
+ background-color: black;
+ overflow-x: scroll;
+ padding: 0 5px;
+ color: white;
+}
+
+pre code{
+ padding: 15px;
+ display: block;
+}
+
+ul.pagination {
+ list-style: none;
+ text-align: center;
+}
+
+li.page-item {
+ display: inline;
+ padding: 0.3rem;
+}
+
+
+footer {
+ padding: 0 0 15px 0px;
+ text-align: center;
+ color: var(--secondary-text);
+}
+
+
+#webmentions img { max-height: 1.2em; margin-right: -1ex; }
+
+.hidden {
+ display: none;
+}
+
+.center {
+ text-align: center;
+}
+
+/*
+ * Links page
+ */
+
+
+#links-main {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: flex-start;
+}
+
+#links-main li {
+ padding: 7px 0;
+ list-style-type: none;
+}
+
+.link-list details {
+ border: 1px dotted var(--pewter-blue);
+ padding: 0.5em 0.5em 0;
+}
+
+.link-list details[open] {
+ padding: 0.5em;
+}
+
+.link-list details[open] summary {
+ border-bottom: 1px dotted var(--pewter-blue);
+ margin-bottom: 0.5em;
+}
+
+.link-list summary {
+ margin: -0.5em -0.5em 0;
+ padding: 0.5em;
+}
+
+.links-column {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 30%;
+ overflow-wrap: break-word;
+}
+
+.send-webmention {
+ margin: 55px 25px;
+ max-width: fit-content;
+ border: 1px dotted var(--primary-text);
+ padding: 0 20px 20px;
+}
+
+.send-webmention p {
+ font-size: 1.3rem;
+}
+
+@media only screen and (max-width: 700px) {
+ .sidebar {
+ border: none;
+ margin: 0;
+ }
+}
diff --git a/themes/dreamcast/static/js/webmention.js b/themes/dreamcast/static/js/webmention.js
new file mode 100644
index 0000000..eceaab2
--- /dev/null
+++ b/themes/dreamcast/static/js/webmention.js
@@ -0,0 +1,458 @@
+/* webmention.js
+
+Simple thing for embedding webmentions from webmention.io into a page, client-side.
+
+(c)2018-2022 fluffy (http://beesbuzz.biz)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+GitHub repo (for latest released versions, issue tracking, etc.):
+
+ https://github.com/PlaidWeb/webmention.js
+
+Basic usage:
+
+
+
+
+Allowed parameters:
+
+ page-url:
+
+ The base URL to use for this page. Defaults to window.location
+
+ add-urls:
+
+ Additional URLs to check, separated by |s
+
+ id:
+
+ The HTML ID for the object to fill in with the webmention data.
+ Defaults to "webmentions"
+
+ wordcount:
+
+ The maximum number of words to render in reply mentions.
+
+ max-webmentions:
+
+ The maximum number of mentions to retrieve. Defaults to 30.
+
+ prevent-spoofing:
+
+ By default, Webmentions render using the mf2 'url' element, which plays
+ nicely with webmention bridges (such as brid.gy and telegraph)
+ but allows certain spoofing attacks. If you would like to prevent
+ spoofing, set this to a non-empty string (e.g. "true").
+
+ sort-by:
+
+ What to order the responses by; defaults to 'published'. See
+ https://github.com/aaronpk/webmention.io#api
+
+ sort-dir:
+
+ The order to sort the responses by; defaults to 'up' (i.e. oldest
+ first). See https://github.com/aaronpk/webmention.io#api
+
+ comments-are-reactions:
+
+ If set to a non-empty string (e.g. "true"), will display comment-type responses
+ (replies/mentions/etc.) as being part of the reactions
+ (favorites/bookmarks/etc.) instead of in a separate comment list.
+
+A more detailed example:
+
+
+
+
+
+
+
+*/
+
+// Begin LibreJS code licensing
+// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt
+
+(function () {
+ "use strict";
+
+ // Shim i18next
+ window.i18next = window.i18next || {
+ t: function t(/** @type {string} */key) { return key; }
+ }
+ const t = window.i18next.t.bind(window.i18next);
+
+ /**
+ * Read the configuration value.
+ *
+ * @param {string} key The configuration key.
+ * @param {string} dfl The default value.
+ * @returns {string}
+ */
+ function getCfg(key, dfl) {
+ return document.currentScript.getAttribute("data-" + key) || dfl;
+ }
+
+ const refurl = getCfg("page-url", window.location.href.replace(/#.*$/, ""));
+ const addurls = getCfg("add-urls", undefined);
+ const containerID = getCfg("id", "webmentions");
+ /** @type {Number} */
+ const textMaxWords = getCfg("wordcount");
+ const maxWebmentions = getCfg("max-webmentions", 30);
+ const mentionSource = getCfg("prevent-spoofing") ? "wm-source" : "url";
+ const sortBy = getCfg("sort-by", "published");
+ const sortDir = getCfg("sort-dir", "up");
+ /** @type {boolean} */
+ const commentsAreReactions = getCfg("comments-are-reactions");
+
+ /**
+ * @typedef MentionType
+ * @type {"in-reply-to"|"like-of"|"repost-of"|"bookmark-of"|"mention-of"|"rsvp"|"follow-of"}
+ */
+
+ /**
+ * Maps a reaction to a hover title.
+ *
+ * @type {Record}
+ */
+ const reactTitle = {
+ "in-reply-to": t("replied"),
+ "like-of": t("liked"),
+ "repost-of": t("reposted"),
+ "bookmark-of": t("bookmarked"),
+ "mention-of": t("mentioned"),
+ "rsvp": t("RSVPed"),
+ "follow-of": t("followed")
+ };
+
+ /**
+ * Maps a reaction to an emoji.
+ *
+ * @type {Record}
+ */
+ const reactEmoji = {
+ "in-reply-to": "💬",
+ "like-of": "❤️",
+ "repost-of": "🔄",
+ "bookmark-of": "⭐️",
+ "mention-of": "💬",
+ "rsvp": "📅",
+ "follow-of": "🐜"
+ };
+
+ /**
+ * @typedef RSVPEmoji
+ * @type {"yes"|"no"|"interested"|"maybe"|null}
+ */
+
+ /**
+ * Maps a RSVP to an emoji.
+ *
+ * @type {Record}
+ */
+ const rsvpEmoji = {
+ "yes": "✅",
+ "no": "❌",
+ "interested": "💡",
+ "maybe": "💭"
+ };
+
+ /**
+ * HTML escapes the string.
+ *
+ * @param {string} text The string to be escaped.
+ * @returns {string}
+ */
+ function entities(text) {
+ return text.replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+ }
+
+ /**
+ * Creates the markup for an reaction image.
+ *
+ * @param {Reaction} r
+ * @param {boolean} isComment
+ * @returns {string}
+ */
+ function reactImage(r, isComment) {
+ const who = entities(
+ r.author?.name || r.url.split("/")[2]
+ );
+ /** @type {string} */
+ let response = reactTitle[r["wm-property"]] || t("reacted");
+ if (!isComment && r.content && r.content.text) {
+ response += ": " + extractComment(r);
+ }
+
+ let authorPhoto = '';
+ if (r.author && r.author.photo) {
+ authorPhoto = `
+
+ `;
+ }
+
+ let rsvp = '';
+ if (r.rsvp && rsvpEmoji[r.rsvp]) {
+ rsvp = `${rsvpEmoji[r.rsvp]} `;
+ }
+
+ return`
+
+ ${authorPhoto}
+ ${(reactEmoji[r['wm-property']] || '💥')}
+ ${rsvp}
+
+ `;
+ }
+
+ /**
+ * Strip the protocol off a URL.
+ *
+ * @param {string} url The URL to strip protocol off.
+ * @returns {string}
+ */
+ function stripurl(url) {
+ return url.substr(url.indexOf('//'));
+ }
+
+ /**
+ * Deduplicate multiple mentions from the same source URL.
+ *
+ * @param {Array} mentions Mentions of the source URL.
+ * @return {Array}
+ */
+ function dedupe(mentions) {
+ /** @type {Array} */
+ const filtered = [];
+ /** @type {Record} */
+ const seen = {};
+
+ mentions.forEach(function(r) {
+ // Strip off the protocol (i.e. treat http and https the same)
+ const source = stripurl(r.url);
+ if (!seen[source]) {
+ filtered.push(r);
+ seen[source] = true;
+ }
+ });
+
+ return filtered;
+ }
+
+ /**
+ * Extract comments from a reaction.
+ *
+ * @param {Reactions} c
+ * @returns string
+ */
+ function extractComment(c) {
+ let text = entities(c.content.text);
+
+ if (textMaxWords) {
+ let words = text.replace(/\s+/g,' ').split(' ', textMaxWords + 1);
+ if (words.length > textMaxWords) {
+ words[textMaxWords - 1] += '…';
+ words = words.slice(0, textMaxWords);
+ text = words.join(' ');
+ }
+ }
+
+ return text;
+ }
+
+ /**
+ * Format comments as HTML.
+ *
+ * @param {Array} comments The comments to format.
+ * @returns string
+ */
+ function formatComments(comments) {
+ const headline = `${t('Responses')} `;
+ const markup = comments
+ .map((c) => {
+ const image = reactImage(c, true);
+
+ let source = entities(c.url.split('/')[2]);
+ if (c.author && c.author.name) {
+ source = entities(c.author.name);
+ }
+ const link = `${source} `;
+
+ let linkclass = "name";
+ let linktext = `(${t("mention")})`;
+ if (c.name) {
+ linkclass = "name";
+ linktext = c.name;
+ } else if (c.content && c.content.text) {
+ linkclass = "text";
+ linktext = extractComment(c);
+ }
+
+ const type = `${linktext} `;
+
+ return `${image} ${link} ${type} `;
+ })
+ .join('');
+ return `
+ ${headline}
+
+ `;
+ }
+
+ /**
+ * @typedef {Object} Reaction
+ * @property {string} url
+ * @property {Object?} author
+ * @property {string?} author.name
+ * @property {string?} author.photo
+ * @property {Object?} content
+ * @property {string?} content.text
+ * @property {RSVPEmoji?} rsvp
+ * @property {MentionType?} wm-property
+ * @property {string?} wm-source
+ */
+
+ /**
+ * Formats a list of reactions as HTML.
+ *
+ * @param {Array} reacts List of reactions to format
+ * @returns string
+ */
+ function formatReactions(reacts) {
+ const headline = `${t('Reactions')} `;
+
+ const markup = reacts.map((r) => reactImage(r)).join('');
+
+ return `
+ ${headline}
+
+ `;
+ }
+
+ /**
+ * @typedef WebmentionResponse
+ * @type {Object}
+ * @property {Array} children
+ */
+
+ /**
+ * Register event listener.
+ */
+ window.addEventListener("load", async function () {
+ const container = document.getElementById(containerID);
+ if (!container) {
+ // no container, so do nothing
+ return;
+ }
+
+ const pages = [stripurl(refurl)];
+ if (!!addurls) {
+ addurls.split('|').forEach(function (url) {
+ pages.push(stripurl(url));
+ });
+ }
+
+ let apiURL = `https://webmention.io/api/mentions.jf2?per-page=${maxWebmentions}&sort-by=${sortBy}&sort-dir=${sortDir}`;
+
+ pages.forEach(function (path) {
+ apiURL += `&target[]=${encodeURIComponent('http:' + path)}&target[]=${encodeURIComponent('https:' + path)}&target[]=${encodeURIComponent('https:' + path.replace(/\/$/, ""))}`;
+ });
+
+ /** @type {WebmentionResponse} */
+ let json = {};
+ try {
+ const response = await window.fetch(apiURL);
+ if (response.status >= 200 && response.status < 300) {
+ json = await response.json();
+ } else {
+ console.error("Could not parse response");
+ new Error(response.statusText);
+ }
+ } catch(error) {
+ // Purposefully not escalate further, i.e. no UI update
+ console.error("Request failed", error);
+ }
+
+ /** @type {Array} */
+ let comments = [];
+ /** @type {Array} */
+ const collects = [];
+
+ if (commentsAreReactions) {
+ comments = collects;
+ }
+
+ /** @type {Record>} */
+ const mapping = {
+ "in-reply-to": comments,
+ "like-of": collects,
+ "repost-of": collects,
+ "bookmark-of": collects,
+ "follow-of": collects,
+ "mention-of": comments,
+ "rsvp": comments
+ };
+
+ json.children.forEach(function(child) {
+ // Map each mention into its respective container
+ const store = mapping[child['wm-property']];
+ if (store) {
+ store.push(child);
+ }
+ });
+
+ // format the comment-type things
+ let formattedComments = '';
+ if (comments.length > 0 && comments !== collects) {
+ formattedComments = formatComments(dedupe(comments));
+ }
+
+ // format the other reactions
+ let reactions = '';
+ if (collects.length > 0) {
+ reactions = formatReactions(dedupe(collects));
+ }
+
+ container.innerHTML = `${formattedComments}${reactions}`;
+ });
+}());
+
+// End-of-file marker for LibreJS
+// @license-end
diff --git a/themes/dreamcast/theme.toml b/themes/dreamcast/theme.toml
new file mode 100644
index 0000000..e8135e3
--- /dev/null
+++ b/themes/dreamcast/theme.toml
@@ -0,0 +1,21 @@
+# theme.toml template for a Hugo theme
+# See https://github.com/gohugoio/hugoThemes#themetoml for an example
+
+name = "Genesis"
+license = "MIT"
+licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
+description = ""
+homepage = "http://example.com/"
+tags = []
+features = []
+min_version = "0.41.0"
+
+[author]
+ name = ""
+ homepage = ""
+
+# If porting an existing theme
+[original]
+ name = ""
+ homepage = ""
+ repo = ""